commit 3f422218a5291cc3f5e46f2523dbf76467a0e9be Author: WpyQwq <3911625973@qq.com> Date: Sat Sep 19 13:04:56 2026 +0800 Initial commit: RHINE LAB · ANALYSIS OS:三维界面与动效实验(本仓库不含个人归档索引数据) diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..be54bb1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,33 @@ +node_modules/ +backups/ +dist/ +.tools/ +*.mp4 +*.blend1 +optix7cache.db +reference/*.jpg +reference/**/*.jpg +art/.cache/ +.worktrees/ +release/ +video/ +__pycache__/ +*.pyc +.env +.env.* +!.env.example + +reference/local-files-connection.js + +# Local integration checkout +.main-integration/ + +# Individually licensed MyFonts kit; install locally, never publish to Git. +public/fonts/novecento/ +.vercel/ + +# 本地归档服务每次启动生成的访问令牌 +.archive-token + +# 一键启动脚本产生的运行日志与 PID 记录 +logs/ diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..22cafd2 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,369 @@ +# Rhine Lab UI + +## 索引与阵列:当前机制(权威,动它之前先读这一节) + +> 下方时间线是历史记录,其中有几段**已经被回退**。读到时以本节为准。 +> **不存在**的文件(曾在时间线里出现,现已删除,不要再去找): +> `src/archive-array.ts`、`scripts/check-array.mjs`;`src/data.ts` 里的 +> `loadArchiveDataset` / `searchDataset` / `columnCount` / `recordKey` / `indexOfKey` / +> `childrenOfPath` / `datasetSource` / `laneCenter` / `rowPeriod` 也都已移除。 + +**数据流**:`content/curated.mjs`(手写)→ `npm run archive:snapshot` → `content/archives.json`(生成物) +→ `src/data.ts` → 阵列(`src/archive-loop.ts` / `src/archive-visibility.ts` / `src/scene.ts`)。 +`public/archive-index.json` 只服务 LOCAL ARCHIVE 面板与检索,**不参与阵列**。 + +**现状**:5 个分类,每列条数**不限**(校验器只要求每列 ≥1、总数 ≥5),当前内容 **47 条**,五列 **8 / 9 / 11 / 9 / 10**(刻意不等长,用来证明上限确实放开了)。 +阵列窗口是 32 行、内容从第 12 行起编号,`slot = lane * 32 + (row % 32)`。 + +### 想改数量时动哪里 + +| 目标 | 动这里 | 别动 / 注意 | +| --- | --- | --- | +| 每列条数、总条数 | `content/curated.mjs` 增删条目 → `npm run archive:snapshot` | **不要手改 `content/archives.json`**,它是生成物,下次生成会覆盖 | +| 单列超过 20 条 | 不用改任何东西 | 交互路径的行空间无上限(`fileAtCell` 按 `files.length` 取模循环);`slot` 已用 `% 32` 防止串到下一列 | +| 分类数量 5 → n | `curated.mjs` 的 `categories`/`columns` **+** `scripts/archive-content.mjs` 的 `names.length !== 5` **+** `src/archive-loop.ts` 的 `POOL_LANES` / `LOOP_COLUMNS` **+** 行内写死的列原点 `(lane - 2)`、`/ 5`、`/ 8`(`archive-visibility.ts:36,42`、`scene.ts` 的相机/动效/取值多处) | ⚠️ 列数一改就动了**原片逐帧对照**的构图基准,必须重新校准;不要只改一处 | +| 刻度条长度 | `src/main.ts` 的 `TICK_WINDOW` | 短列必须走自然顺序分支(见下) | +| 校验规则 | `scripts/archive-content.mjs`;同步改 `scripts/check-content.mjs` 的 `invalidCases` | 校验不过时 `predev`/`prebuild` 会**直接 throw**,dev server 和 build 都起不来 | + +### 改完怎么自检(改了阵列/内容/场景就跑这一套) + +```powershell +npx tsc --noEmit # 类型必须 exit 0 +npm run check:content # 档案数据校验,20 项 +node --experimental-strip-types scripts/check-archive.mjs # 相机/抽取几何 +node --experimental-strip-types scripts/check-loop.mjs # 循环阵列 +npm run build # prebuild 会跑快照+校验+字体 +``` + +⚠️ `check:archive` / `check:loop` **没有** npm script(只有 `check:content` / `check:viewport` 有), +必须用上面的 node 形式跑文件。`check:array` 已随方案回退一并删除,**不要再引用**。 + +### 已经踩过的坑(改之前先看) + +1. **别把每列条数写死成 8。** 旧版校验器与阵列数学都假定每列恰好 8 条;用户要求"每列无限"后已放开,但阵列窗口只有 32 行、内容从第 12 行起 → 每列**超过 20 条**时若 `slot` 不取模就会串到下一列的行上。`data.ts` 的 `slot: lane * 32 + (row % 32)` 就是为这个加的。 +2. **刻度条窗口化时,短列必须保持自然顺序。** `main.ts` 的 `tickWindow()` 里 `const start = len <= TICK_WINDOW ? 0 : position - Math.floor(count / 2)`。若一律"以选中项居中",列内 8 条时窗口会绕回来变成 `5,6,7,8,1,2,3,4`——顺序整体转位、高亮卡在正中,看起来就是"滑条坏了"。这个 bug 已经发生过一次。 +3. **`fileAtSlot` 是截断、`fileAtCell`(archive-loop)是循环取模**,两者语义不同,不要合并。`fileAtSlot` 服务 boot 与原片对照的固定 160 布局,必须与原版一致。 +4. **别再用"往阵列里塞全量归档文件"的方案。** 试过一次(每个文件一张盘),造成检索列表里全是 `00_索引` 的 CSV/MD/TXT、选中列外条目还会错位到第一列的盘上,用户判定"索引完全改坏",已整体回退。要扩内容请往 `curated.mjs` 加条目。 +5. **`records` 是静态 import**,模块顶层就会用到它(`main.ts` 里 `columnFiles(...)[0]`、`fileLocation(selected).lane`)。若日后要改成异步加载,必须先处理这些顶层访问,否则启动即崩。 + +### 开场与退出的入口序列 + +`src/main.ts` 的 `beginOpening()`:加载完成 → 尝试解锁音频 → logo 停留 `ENTRY_LOGO_HOLD = 1000ms` → 渐出 → 自动播放(**没有点击门槛**,`src/startup.ts` 的 `StartupGate` 已无调用方)。 +浏览器要求用户手势才允许启动音频设备,解锁失败时本次开场静音,但**不清空** `prefs.sound/music`,改由 `armAudioUnlock()` 在首次 `pointerdown`/`keydown` 补解锁。 +右上角那个 46px 圆形按钮:开场中是 `↗` 跳过(`.skip`),进入档案/详情后变成 `⏻` 退出(`.exit`),两者同坐标不重合。 +退出序列(`exitSystem()`,CSS 时序在 `style.css` 的 `.shutdown` 段):白屏 + logo(**不闪动**)→ 字符本体明灭(`shutdownFlicker`)→ 黑条从左扫到**完全覆盖**为止、**不向右滑出**(`shutdownSweep` 无 100% 那一段)→ `SHUTDOWN_WINDOW` 之后 `window.close()`,关不掉则退化为纯白页。 + +- 2026-09-11:官网已独立集成 Novecento 开场字体;授权 kit 不进入 Git。正式 Vercel 项目在构建时从当前生产版本恢复并校验字体,首次部署使用本机授权 kit 的静态输出。见 verification/WEBFONT-DEPLOYMENT.md。 + +## 开场中央字体 + +- 2026-09-11:用户要求中央开场文案中适合的位置统一使用 Novecento Sans Wide。访问、身份、请求、处理、授权提示使用 Normal;欢迎三行使用 Bold。后续按用户纠正与原片叠加比较,左上 RHINE LAB 使用 DemiBold,下方两行保留 MiSans。沿用原时间轴与 HUD。 +- 用户后续授权从 MyFonts 领取 Normal、DemiBold、Bold 正式 Webfont 并直接替换,本机已领取并接入。public/fonts/novecento 为独立许可资源,排除 Git;有完整字体包时使用原始 WOFF2,其他检出或加载失败保留既有固定图形。来源、再制作与回归见 verification/BOOT-LETTERING.md。网页年度许可不等于已确认创意工坊分发授权,本轮未上传。 + +## Wallpaper Engine 实验分支 + +- 用户追加授权音乐律动、手动点击开始且可隐藏入口的波纹接力小游戏,以及入场详情开关。工作台默认短入场,展示模式默认完整;可强制覆盖。工作台内容轻微错峰淡入,减少动态效果时直接显示。 +- WE 属性采用原生可折叠分组和条件显示;选中档案支持保持原样、仅音乐时归齐、始终归齐。原生实现继续沿用,不启用前端或动效 Skill。详见 docs/WALLPAPER-ENGINE.md。 +- 用户进一步要求登录入场适配多分辨率并移除底部字幕。正常入场背景与三维画布铺满实际视口,中央图形保持比例;只有显式逐帧对照保留固定 1920×1080 舞台。此前“所有开场固定比例”规则由此更新。 + +- 用户于 2026-09-10 授权新建 `codex/wallpaper-engine`,在本机 Wallpaper Engine 中试用网页壁纸。当前作为独立实验保留,不合并主分支、不上传创意工坊。 +- 沿用现有原生实现和视觉规范;`npm run build:wallpaper` 输出 `release/wallpaper`,网页正式构建保持原流程。 +- 壁纸资源全部本地打包,关闭 PWA,自动启动,接入宿主设置、帧率和暂停通知。使用说明与实际验证见 `docs/WALLPAPER-ENGINE.md`。 + +- 用户明确要求:以目录内视频 5–40 秒为视觉与动效参考,尽量逐像素还原;不使用前端 Skill,由代理选择技术栈。 +- 本项目采用参考视频逐帧分析与原生实现流程,不启用主前端设计或动效 Skill。 +- 美术资源必须通过 Blender MCP 制作,保留源文件与可复现脚本。 +- 技术栈:TypeScript、Three.js、Vite。1920×1080 为布局基准,等比例适应窗口。 +- 视觉与时间轴规范见 DESIGN.md。视频仅用于参考与验证,不能作为产品交互背景。 + +## 阵列拖动与持续惯性 + +- 用户要求悬停轻抬,拖动浏览阵列,鼠标滚轮切换列内档案;鼠标与触摸共用原生输入逻辑。 +- 用户进一步明确需要类似网页快速滑动的持续惯性:快拖与慢拖按实际松手速度产生不同滑行距离,不能采用固定最多两列/三档的追加距离。松手后持续减速,档案随经过的位置依次切换,接近停止时才吸附;再次按住从当前画面接管。 +- 用户最新明确选择自由平面拖动:用当前相机的两条投影轨道共同还原屏幕位移,取消横竖快捷手势和斜向锁轴。一次拖动可随时转弯或反向,两个轨道共同衰减并最终吸附。拖动按实际物理位置选择档案并更新列内记忆;键盘和按钮切列继续恢复该列记忆。 +- 已确认的波浪、竖直抽取、归位、列内记忆与循环规则继续沿用。实现与验证见 `verification/ARRAY-INPUT.md`。 + +## 详情与弹窗过渡 + +- 详情退出约 180ms,快速返回再进入从当前透明度接续;背景遮罩随实际镜头进度变化。抽取、转正、归位与波浪继续沿用已确认的三维规则。 +- 检索、收藏和设置弹窗进入约 300ms、退出约 200ms。退出期间保留节点与背景输入隔离,结束后恢复焦点,再执行选档或重播。 +- 详情页签使用 150ms 内容淡入与 180ms 下划线移动;收藏仅更新按钮状态、总数和反馈,保留正文节点、页签及滚动位置。减少动态效果时直接切换。验证见 `verification/UI-TRANSITIONS.md`。 + +## 标注修订约束 + +- 用户标注对应原片约 27.60、29.16、31.56 秒;相机校准同时检查端面厚度、空隙和前后排边界。 +- 同一档案从阵列升起并进入特写时,仅允许竖直升降,不能把模型向前或横向平移来重新构图;靠近与转向由摄像机完成。初始整组阵列入场及切换档案的轨道移动不属于抽取动作。 +- 背景卡片不能随特写进度额外下沉来让出画面。 +- 交互模式左右切换五类档案列,上下切换同一列的前后档案;每列至少 8 份可读档案。切列保留该列的选择记忆。 +- 预览时卡片抬起 0.4;交互详情升至 4.05(原片时间轴仍为 3.35),只比早先略高,必须保留后方阵列的可见层次。镜头负责左侧构图,右侧展示信息。 +- 卡片获得净空后才允许拖拽旋转;收回时保持高度先转正,再下降。被换选的旧卡片也遵守此顺序。 +- 阵列与抽出卡片的材质、内部细节必须连续过渡,快速切回正在归位的卡片时保留当前升降状态。 +- 标签复用主界面的同一份莱茵生命标志,不使用另画的简笔版本。 + +## 独立模型查看器 + +- 用户要求在详情页增加 360° 模型查看、方向键平移、滚轮缩放、拆解和一键重组,先提供可体验的版本。 +- 独立查看器沿用既有视觉规范与原生实现流程;部件模型通过 Blender MCP 制作,保留源文件与生成脚本,见 DESIGN.md。 +- 阵列中的抽取与归位继续遵守上文约束。独立查看器在自己的场景内旋转观察与分层拆解,关闭后回到原档案。 + +## 循环档案阵列 + +- 用户确认:主界面的档案阵列上下切档案、左右切列均可持续循环,画面不出现队列尽头。 +- 五类档案、每类八份的内容顺序循环复用;跨首尾仍沿按键方向移动到相邻位置,切列保留该列的档案选择记忆。 +- 使用固定的可见窗口与外围卡片补位。归位副本按实际循环位置区分,保留快速重新选中时的运动状态。 +- 原片参考时间轴继续使用原来的五列、160 个位置;循环只用于交互阵列。相关实现与验证见 DESIGN.md、verification/LOOPING-ARCHIVE.md。 + +## 当前动效选择与光影微调 + +- 用户试用后明确选择原始基线 aeddd8f:选中抬起和波浪同时开始,恢复原有负向波谷与 4.2 阻尼速率。此前去波谷、先抬起后波浪及源点静止方案只保留为对照实验,不用于主界面。 +- 用户对比后选择恢复整体基线的颜色与光照。生产场景及独立查看器默认采用 baseline 配置;光影微调只保留为对照实验。见 DESIGN.md。 +- 档案序号和列编号使用 Rolling Number,保留两位数;X-001 形式的档案编号与悬停提示使用三位数滚动,X- 前缀固定。连续输入延续当前运动,减少动态效果设置关闭滚动。 + +## 标题与查看器过渡 + +- 主界面随选档变化的顶部分类、底部列名、悬停标题与权限标签统一复用主标题的文字滚动。相同内容不重复播放,悬停首次出现直接显示;跨列刻度复用固定八个按钮,保留连续伸缩和各列选档记忆。见 `verification/ROLLING-TITLE.md`。 +- 用户最新选择:主档案标题改为类似数字的滚动文字,替代此前快切黑条。直接复用已有库的 `createRollingText`,与编号共享 460ms 时长;使用 direct 模式、各字同时启动、轻微模糊与宽度过渡。文字固定向上滚动,快速输入衔接最新目标;进入详情或减少动态效果时直接显示最终标题。见 `verification/ROLLING-TITLE.md`。 +- 360° 独立查看器增加进出过渡。进入约 320ms 淡入,模型载入后约 380ms 轻微展开;退出约 220ms 淡出。退出结束前保留模型与模态焦点,结束后释放并返回原按钮;进入中途按 Esc 可衔接退出。 + +## 前段 2D 动效逐帧修订 + +- 用户明确范围:黑底的鹰角网络片头不做,直接从画面变白开始。正常启动和重播从原片 6.76 秒进入;本地对照保持原片时间码。 +- 继续使用逐帧分析和原生 DOM/SVG 实现,不启用前端或动效 Skill。2D 时间轴见 `src/boot-motion.ts`,渲染见 `src/boot.ts`;规范与复核见 `verification/BOOT-MOTION.md`。 +- 已认可的三维交互、基线颜色和档案阵列时间轴继续沿用。启动标志沿共享标志的同一组贝塞尔曲线连续绘制,缺口通过路径首尾控制,保留与档案标签一致的轮廓。 +- 用户进一步要求按原片逐帧修订非线性运动。左上三行分别在 278、280、282 帧滑入,各自加速后减速;圆环半径、弧端、内环与橙点使用独立轨迹,名称黑底快速扫过后长尾收束。测量轨迹见 `src/boot-tracks.ts`;局部放大对照及限制见 `verification/BOOT-MOTION.md`。 +- 用户进一步强调两侧小圆主要绕自身圆心旋转并补全弧段;仅保留原片测得的轻微位置收拢。中央大点闪变后缩小,六颗小点错峰绕行。Logo 以连续笔画首尾及移动缺口表现运动,不能替换成整枚标志闪烁;加号与方点同帧展开,方点随后拉成减号。见 `src/boot-orbit-tracks.ts`、`src/boot-logo-tracks.ts`。 + +## 品牌文字与 360° 导航过渡 + +- 用户要求放大并叠加原片,校准左上角三行文字。保留 MiSans,三行约 270px 宽;中间一行使用 600 字重,ANALYSIS 按原片字母位置分别校准间隔,OS 右对齐。具体尺寸见 DESIGN.md,叠加页为 `reference/brand-review.html`。 +- 360° 查看器的平移、滚轮及加减键缩放均平滑跟随;复位约 560ms,按球面短路径返回。复位过程中输入从当前显示位置接管;减少动态效果时直接到位。仅用于独立查看器。 +## 声音实现 + +- 用户要求依据原视频与莱茵生命相关设定重设计音效和背景音乐。本次实现采用原创三轨循环配乐和 Web Audio 音效,详见 `verification/AUDIO-DESIGN.md`。 +- 音效与音乐独立开关、独立音量;保留旧静音偏好。启动声音沿用原片时间码,跳帧不补播;三轨在切场景时连续播放并改变比例。 +- 听感尚待用户试听,技术验证不视为用户已经确认声音方向。源谱与音色生成脚本必须保留。 +- 用户进一步指定玻璃交互应采用清脆音色。当前音效使用薄玻璃轻碰与短共鸣,切档、抽取、归位及拆组有不同的衰减;保留快速输入时的叠音限制。用户尚未确认本版最终听感。 +- 用户明确要求复刻原视频已有的逐字输入音效。输入现在使用三个原片短音(各 38ms),保留来源记录;其他卡片交互沿用玻璃音色。输入节奏跟随真实文字揭示,不把故障闪动恢复当作新输入。 +- 用户接受档案切换使用玻璃声,并要求检查 OS 页面打开、Logo 与文字出现的声音。窗口开合、系统操作、启动标志、整块文字及验证反馈采用独立电子音色;玻璃声用于档案实体运动。完整开场试听见 `reference/boot-audio.html`,只临时开启声音,结束后恢复偏好。具体音色尚待用户试听。 + +## 档案内容维护 + +- 档案内容独立保存在 `content/archives.json`,页面和 TXT 导出共用;修改步骤见 `content/README.md`。五列保持不变,**每列条数不限**(2026-09-12 起;校验器只要求每列 ≥1、总数 ≥5),编号 `X-0NN` 按顺序稳定、宽度随总量自适应,构建前校验;内容按纯文本显示。 +- 2026-09-12:`archives.json` 已改为**生成物**,手写内容在 `content/curated.mjs`,由 `scripts/archive-source.mjs` 扫描 `E:\归档` 后写出,直接编辑 JSON 会被下次生成覆盖。五列、每列八份、`X-0NN` 稳定编号的约束不变,校验规则仅在 `source` 上额外接受 `file:` 链接。 +- 同期新增本地归档接入:**LOCAL ARCHIVE** 面板检索归档全部条目并执行移动/复制/解压/新建目录等安全写操作,由只监听回环、带每次启动令牌的 `scripts/archive-service.mjs` 承担;归档变动会自动重建快照。使用与安全边界见 `docs/LOCAL-ARCHIVE.md`,实现与实测证据见 `verification/ARCHIVE-INTEGRATION.md`。该面板与四十条阵列互相独立,阵列的数学与三个消费方(`src/scene.ts`、`src/archive-loop.ts`、`src/data.ts` 的位置映射)未改动。 +- 2026-09-12:用户要求把详情页的 EXPORT 按钮(原为下载 TXT)改为直接打开本地文件资源管理器中的文件位置。现为 **SHOW IN FOLDER / 文件位置**,用该档案的真实 `path` 调 `POST /reveal`;服务不在线时提示启动方式而非静默失败。`public/archives/*.txt` 仍会生成但详情页不再提供下载入口。同时修掉详情页脚注的 `file:` 参考链接(浏览器不允许从网页跳转 `file:`),改为显示归档内相对路径文本。 +- ⚠️ **以下四条(全量归档驱动 → 修正盘的粒度 → 独立详情导航 → 文件夹即列)已于 2026-09-12 当天全部回退。** 留在时间线里只作历史与教训,**不要再按它们实现**;当前机制以本文件顶部「索引与阵列:当前机制(权威)」为准,回退范围见下文「索引与阵列方案整体回退」。 +- 【已回退】2026-09-12:**三维阵列改为全量归档驱动**(用户明确选择"真正的全量阵列")。列 = 归档顶层内容分类(当前 7 列),列内是该分类下全部真实归档条目(合计 49,997 条);标题/摘要/研究记录由索引自动派生,用 Proxy 惰性物化,只给被读到的条目生成文本。四十条手写精选档案降级为**备用数据集**(索引不可用时兜底,应用照常启动)与 TXT 摘要来源,因此 `archive-content.mjs` 的校验器与 `check-content` 的约束**保持不变**。 + - `data.ts` 重写为双数据集 + O(1) 预计算寻址(`laneMembers` / `laneOf` / `posOf` / `counts`)。旧实现每次调用全量扫描 + `indexOf`,全量下每次输入事件约 19 ms、一次拖动累计约 1.2 秒(注意:**不是**每帧开销,渲染循环不碰 `records`)。改造后 2000 次寻址 38,365 ms → 0.33 ms。 + - 引入**全局行周期**(`rowPeriod()` = 最长一列的条数)解决 rebase 取整:各列长度不同,不存在公共周期,rebase 位移必须是全局周期的整数倍才能保持内容不变。 + - `fileAtSlot`(**截断**,服务 boot 与 `?time=`/`review=1` 逐帧对照的固定 160 布局)与 `indexAtCell`(**循环取模**,服务交互阵列)语义不同,不可合并。 + - 收藏改按归档相对路径键存放(`recordKey`),否则新增文件导致编号位移会让旧收藏指向别的档案。`#file-ticks` 改为固定 8 个按钮的滑动窗口,避免单列上万条时渲染上万个 DOM 节点。 + - 列数参数化:`archive-loop.ts` 新增 `configureLoop()`(`LOOP_COLUMNS`/`POOL_LANES` 改为 live binding,前五项恒为真实列以保证固定 160 布局成立);`archive-visibility.ts`、`scene.ts` 的 lane 原点改用 `laneCenter()`;`motion.ts` 增加 `laneOrigin` 可选参数。`fixed` 路径保持 5 列不变(原片对照基准),正常入场走动态路径,因此阶段之间不跳变。 + - 新增自包含回归检查 `npm run check:array`(桩掉 fetch 直读快照,可进 CI),断言反向寻址一致、格子互为逆、rebase 平移不变性、列循环回绕、检索不物化、寻址吞吐。设计与证据见 `verification/ARCHIVE-ARRAY.md`。 + - 远距离跳转不再长距离飞行:`scene.ts` 的 `ROW_SNAP_DISTANCE = 64`(可视窗口 32 行的两倍)。超长列上 `nearestOccurrence` 最远会给出半列(约 10,337 行)的目标,超过阈值就让 `shoulder`/`rail` 直接落位、速度清零;只作用于行轴,列轴最远半个列数、保留原动画,拖动/惯性/抽取期间显式排除。阈值两侧由 `check-array` 用真实数据钉住。 + - 已知未决:7 列下的实机构图与剔除数值需人工确认(无浏览器自动化环境)。 +- 【已回退】2026-09-12:**修正盘的粒度**——用户指出"一个项目就是一个 3D 盘,在盘内的页面可以自由访问所有文件"。此前把**每个文件**都做成一张盘(49,997 张)是错的,会导致列表里全是 `00_索引` 的 CSV/MD/TXT、选中这类列外条目还会错位到第一列的盘上。 + - 现在 **盘 = 分类下的第 1 层目录**(项目单元),文件与更深的目录都不是盘。列分布变为 `4 / 2 / 11 / 3 / 4 / 4 / 4 = 32 张盘`,列均衡、可浏览。 + - 列外条目(`laneOf < 0`)**不再参与** `searchDataset`,也不会出现在检索列表里——它们的正确入口是所属盘的页面,或 LOCAL ARCHIVE 面板。 + - 未输入关键字时,检索列表改为列出阵列成员(各列的盘),不再退回 `records` 开头若干条(那正是元数据文件)。 + - 详情页新增 **04 盘内文件** 页签:列出当前盘的直接子项(目录在前),目录可逐层进入并带面包屑返回,文件可直接【打开】【定位】。子项表在 `archive-array.ts` 里惰性建一次(一趟遍历),之后 O(1)。 + - 副作用:`ROW_SNAP_DISTANCE = 64`(远距离跳转落位)在盘模型下**处于休眠**——最长列只有 11 张盘,最近同现点最远 5 行。阈值保留以应对日后粒度变化,`check-array` 会报告 `snapEngaged` 的值。 +- 2026-09-12:用户要求**开场播完直接进入浏览,不要自动展开头一张盘**。原先 `main.ts` 的 `bootFrame` 在原片时间轴走完(t ≥ 35)无条件 `setMode("detail")`,而「入场详情」属性只在 `isWallpaper` 分支里被读过——网页版没有开关,永远自动展开选中的头一张盘。 + - 现在该处改为:网页版 `setMode("archive")`;壁纸版仍按 `openingShowsDetail(openingdetail, workbench)` 决定是否展开,行为不变。 + - `?scene=detail`、`?time=`/`review=1` 逐帧对照(frozen time 恒 < 35)、以及"减少动态效果"直进阵列等既有路径都不受影响。 +- 2026-09-12:用户要求**去掉开场的"点击进入"门槛、启动即自动播放**,并**把 ENTER SYSTEM 做成右上角不起眼的圆形按钮**,以及 logo 先显示 1 秒再渐出。 + - 开场入口序列改为 `beginOpening()`:尝试解锁音频 → logo 停留 `ENTRY_LOGO_HOLD = 1000ms` → 渐出 → 自动 `completeStartup()`。`StartupGate` 不再被引用(`src/startup.ts` 保留但已无调用方)。 + - **音频的现实约束**:浏览器要求用户手势才允许启动音频设备,自动开场时 `unlock()` 可能失败。此时本次开场静音,但**不再清空 `prefs.sound/music`**(原先 `completeStartup(silent)` 会写死静音偏好,自动开场下那样做等于擅自关掉用户的设置);改为 `armAudioUnlock()` 在首次 `pointerdown`/`keydown` 时补解锁,音乐与后续交互音效接上。开场自身的音效在那种情况下会是静音——这是浏览器策略,不是缺陷。 + - `completeStartup()` 去掉了 `silent` 参数;`?time=`/`?review=1` 逐帧对照不加 1 秒停留,避免影响对照工具。 + - `.skip` 由文字按钮改为 46px 圆形(`right: 38px; top: 30px`,`opacity: .3`,hover/focus 提到 1),`data-action="skip"` 不变;不再在开场结束后抢焦点。移动端的 `.mobile-entry` 保持原样。 +- 【已回退】2026-09-12:用户指出**盘内的文件打不开详情页**——只能看盘(列内成员)的详情。原因是详情页绑在阵列选中项 `selected` 上,而列外的文件/子目录没有盘位(`laneOf < 0`),`fileLocation` 会退化成第一列的盘。 + - 引入**独立的详情页导航状态** `detailEntry`(-1 = 跟随阵列选中项),`detailIndex()` 统一取当前应展示的条目。`renderDetail` / `overview` / `setTab` / `contentTab` / `toggleSaved` / `revealSelectedArchive` / 页脚编号全部改用它;`updateSelection` 仍用 `selected`(它渲染的是阵列选中盘的界面)。 + - 新增 `openEntryDetail(index)`:详情页可切换到**任意**条目(盘或盘内文件/目录)。盘内文件列表与检索命中里都加了【查看】按钮;面包屑逐级可点,点上级目录即打开那一级的详情页,实现"返回上级"。 + - `select()` 与 `setMode("archive")` 都会把 `detailEntry` 复位为 -1,避免回到阵列后再进入时还停在上次看的那一层。 + - 顺带修正:`SHOW IN FOLDER`(`revealSelectedArchive`)现在作用于**当前查看的条目**,因此盘内任意文件都能直接在资源管理器中定位。 +- 【已回退】2026-09-12:用户进一步明确阵列层级——"**每一个文件夹一个横列,里面的每一个文件就是一个盘;子目录也算盘,可以在详情里继续查看子目录里的文件**"。 + - 现在 **列 = 分类下的第 1 层文件夹**(32 列,按路径排序);**列内成员 = 该文件夹的直接文件(盘)+ 直接子目录(项目,同样是盘)**。子目录盘点开后进详情页,用「盘内文件」页签继续往下看,可逐层进入。 + - **为什么子目录也要进列**:只放文件的话,`02_游戏/Minecraft` 这类只有子目录的文件夹会既不是列、也不是盘 → **从阵列里彻底消失**(实测 32 个文件夹里有 11 个是这种)。把子目录也算作盘之后 **空列为 0**,所有文件夹都在阵列里可达。 + - 实测分布(`check:array`):32 列 / 435 张盘 / 0 空列;最多 251、中位 2、最少 1;10 列只有 1 张盘。列内循环由 `indexAtCell` 的取模保证(到末尾回到第一张)。 + - 修复一个被 32 列暴露的真 bug:`archive-loop.ts` 的 `poolCell()` 直接取 `POOL_LANES[floor(index/LOOP_ROWS)]`,而 `selectedSlot = laneCenter()*32+12` 在 32 列下是 508 → 下标 15 越界(`POOL_LANES` 只有 9 项)→ 模型坐标变 `NaN`。已改为按 `POOL_LANES.length` 回绕。 + - 副作用:`ROW_SNAP_DISTANCE = 64` 的保护**重新生效**(最长列 251 张盘,半列跳转 125 行 > 64,`snapEngaged: true`)。 +- 2026-09-12:**索引与阵列方案整体回退 + 每列条数放开**(用户原话:"你把索引机制完全改坏了,直接退回到原版")。上一条 ⚠️ 标注的四条全在此次回退范围内,本节是它们的收尾。 + - **删除**:`src/archive-array.ts`、`scripts/check-array.mjs`,以及 `package.json` 里指向它的 `check:array` 脚本项(那个文件没了以后 `npm run check:array` 只会报缺文件)。**还原**:`src/data.ts` 回到 38 行原版(`columnFiles` / `fileLocation` / `fileAtSlot`);`src/archive-loop.ts`、`src/archive-visibility.ts`、`src/scene.ts` 的 `laneCenter()` / `configureLoop()` / `rowPeriod()` / `ROW_SNAP` / lane 原点全部回到原版;`main.ts` 里依赖它的 `detailEntry` 独立导航、盘内文件页签、检索分支一并移除。**保留**(用户单独要求过,与阵列无关):LOCAL ARCHIVE 面板、`POST /reveal`、SHOW IN FOLDER、`beginOpening()` 自动开场、圆形 `.skip`、boot 结束进 `archive`、退出系统。 + - **每列条数已放开到不限**:`scripts/archive-content.mjs` 只要求"每列 ≥1、总数 ≥5"(原为恰好 5 列 × 8 条),`idWidth = Math.max(3, String(records.length).length)` 让编号宽度随条数自适应(1200 条时是 `X-0001` 而不是溢出到 `X-1000` 变四位)。`data.ts` 的 `slot: lane * 32 + (row % 32)` 是配套的防串列护栏(见顶部坑 1)。 + - `scripts/check-content.mjs` 相应改判:原"列数不均衡""缺记录"两个用例换成"空列"(`/至少要有一条档案/`)与"条数过少"(`/至少需要每个分类一条档案/`),并新增两条正向用例——不等长列(1/12/5/2/9 = 29 条)与 1200 条(钉住 id 宽度补齐)。**20 项全绿**。 + - 全量方案带来的**盘内文件浏览、按路径收藏、列数参数化**能力随之失去,这是用户明确选择的取舍,不要再"顺手恢复"。要扩内容请往 `content/curated.mjs` 加条目(原因见顶部坑 4)。 + - **顺带修一个交互 bug**:刻度条窗口化在 8 条短列上把顺序整体转位成 `5,6,7,8,1,2,3,4`、高亮卡在正中(用户反馈"切换列左边的切换文件滑条坏掉了")。`main.ts` 的 `tickWindow()` 现在对 `len <= TICK_WINDOW` 走自然顺序分支,见顶部坑 2。 + - **新增右上角退出系统**(`#exit`,46px 圆形 `⏻`,与开场 `.skip` 同坐标切换):白屏 + logo(**不闪动**)→ 字符本体明灭(`shutdownFlicker`)→ 黑条从左扫到**完全覆盖字体**为止、**不向右滑出**(`shutdownSweep` 的 keyframes 只到 `inset(0 0 0 0)`)→ `SHUTDOWN_CLOSE = 880ms` 后 `window.close()`,关不掉时退化为纯白页。CSS 时序集中在 `style.css` 的 `.shutdown` 段,详见顶部「开场与退出的入口序列」。 + - ⚠️ **验证边界**:本机没有浏览器自动化、也没有无头 WebGL 环境,下面这套自检只能证明工程能构建、数据结构自洽,**不能证明动画观感**。退出序列、开场铺屏、刻度条观感这类结论必须由用户实机确认后才算通过。 + - **自检命令**(注意 `check:archive` / `check:loop` **没有** npm script,要直接用 node 跑文件;`check:content` 才是 npm test 形式): + + ```powershell + npx tsc --noEmit # 类型 + npm run check:content # 档案数据校验(20 项) + node --experimental-strip-types scripts/check-archive.mjs # 相机/抽取几何 + node --experimental-strip-types scripts/check-loop.mjs # 循环阵列 + npm run build # prebuild 会先跑归档快照 + 校验 + 字体 + ``` + - `verification/ARCHIVE-ARRAY.md` 已标注为**存档**(描述的是被回退的方案),不要照它实现。 +- 2026-09-12:**归档重组 + 精选档案扩到 47 条 + 回归检查去掉写死条数**。用户指示"不要老是问我,你来下最优决策",于是把两件挂起的事一起做了。 + - **归档侧重组(18 项同盘 rename,零数据丢失)**:把纯货架内容上提到分类一级(`01_人工智能\01_训练产物\*`、`02_数据集\*`、`05_媒体\02_视频\*`、`03_截图\*`),并压平两类同名套娃——`07_日志与排查\下载日志\X.log\X.log`(5 份)、`ArmouryCrateInstallTool\ArmouryCrateInstaller_3.3.6.0\ArmouryCrateInstaller.exe`、以及 `03_代码与项目\{opencode-dev, awesome-design-md-main, CangjieCommunity-main, mc-bots-main}\<同名>\`。完整清单与回滚办法在 `E:\归档\00_索引\重组计划-*.json`,约定写在 `E:\归档\AGENTS.md` §3.3 与 §3 决策表。 + - **守恒已核对**(拿重组前的 `archive-index.json` totals 对比):文件 +2(两份计划文件)、目录 −15(11+4,全是搬空的)、字节 +5,207(计划文件 + 账目行),归档内容零丢失。 + - `content/curated.mjs` 里 X-001 / X-002 / X-003 的 `path` 随之上提,`department` 改为 `01_人工智能`;X-039 的摘要改成"五份日志"(原来描述的是目录套娃,已被压平)。 + - **精选档案 40 → 47**:补上此前**完全没有精选档案覆盖**的真实大件——NM2 原版权重(6.7 GB)、V2_dpskw 备份集(13.9 GB)、设计资料集、darwin 客户端、musicbase 曲库、录屏片段、`00_索引` 自身。编号接在 X-041…X-047,**既有编号一个没动**(校验器要求 `id === X-{index+1}`,所以只能追加)。五列现在是 **8 / 9 / 11 / 9 / 10**,这是**刻意不等长**,用来证明"每列不限"真的生效,不是数据错乱。 + - **`check-archive.mjs` / `check-loop.mjs` 里写死的 40 与 8 已拔掉**(它们正是坑 1 的翻版:改了条数就崩)。现在从数据推导:`documents` / `perColumn` / `maxPerColumn` 如实输出,只保留真正的契约断言(每列 ≥1、总数 ≥5、列内槽位不冲突;`slot` 复用在单列 >32 条时才允许)。`check-loop.mjs` 的 `files[wrap(row - 12, 8)]` 改成 `files.length`。 + - 全套自检:`tsc` 0 · `check:content` 20/20 · `check-archive` 0(47 条 / maxPerColumn 11)· `check-loop` 0(20000 次定向移动)· `build` 0(825 files / 33.9 MiB)。 + +## 正式功能提交与同步 + +- 用户授权:非实验性质的正式功能完成相关验证后,直接提交并合并到主分支,或提交后同步到对应远程分支,无需再次询问。 +- 当前已经位于主分支时,可直接提交并推送;在功能分支上则按项目分支状态完成合并与同步。 +- 提交应聚焦当前任务;同一工作区中尚在进行或等待用户确认的其他工作,保留原状,不混入正式功能提交。 + +## 解密与清晰内构 + +- 用户要求参考原片 41 秒正面与 46–51 秒多角度制作内部结构,并省略左侧三根高塔。模型继续通过 Blender MCP 生成,保留共享脚本与源文件。 +- 详情打开后由磨砂变清晰,解密线按原片 34–39 秒沿同一物理对角线合拢、保持、向中央收束,采用非线性运动。交互压缩时间,原片对照使用原时间码。 +- 360° 查看器可切换清晰/磨砂,切换不改变视角或拆解状态;返回详情保持解密,退回阵列恢复磨砂。 +- 长焦主场景近裁剪面为 5,以避免薄层内构深度冲突;保持原镜头和构图。验证见 verification/DECRYPTION.md。 + +## 内部模型的锐利截面(历史方案,已撤回) + +- 用户要求内部环形模型更锐利,采用半透明磨砂质感,并打磨两环的连接处。内构沿圆周平滑,屋面、侧壁和切边之间保留硬法线;不再用多层圆角制造体积。 +- 两环通过一条切向接入、两端略展宽的连续薄片连接;内部仍完全容纳在盖板与基板之间。外壳几何与法线保持原样。 +- 源脚本为 art/internal_architecture.py;打磨前后与装配状态对照为 reference/internal-review.html,验证见 verification/INTERNAL-OPTICS.md。 + +## 外壳结构与磨砂揭示 + +- 用户要求黄色方块位于顶边并与外壳齐平,去掉左侧黄色竖条;螺丝仅保留右上与左下两处。 +- 外框刻线与螺丝周围的压槽全部置于盖板后。解密前整面磨砂,原片 38.84–39.56 秒由顶部向底部逐渐变清晰;上下半清晰差异只出现在移动过程,不能保留固定高度的清晰渐变。 +- 外壳细节通过 Blender MCP 制作,共用 art/shell_reference_details.py。新外壳保持既有内构顶点及法线,验证与对照见 verification/SHELL-REFINEMENT.md。 + +## 第一版内构恢复与轻磨砂 + +- 用户明确选择对照页左侧的第一版完整内构,来源为 89c7742 与 reference/internal-study/assembly-before.glb。恢复其两环截面、双连接带、平滑法线和原始白色/橙色材质;此前锐利截面、单连接片和追加内圈细节均已撤回。 +- 通过 Blender MCP 重新生成单体与拆解资产,保留 art/internal_architecture.py 和两个 .blend 源场景。已修订的外壳继续使用 art/shell_reference_details.py。 +- 选中盖板粗糙度为 0.42,并将折射模糊范围限制在投影盖板高度的约 1.6%,避免小尺寸预览过度模糊。自上而下揭示、阵列参数和完全清晰端点继续沿用。 +- 原始内构与当前内构对照见 reference/internal-review.html;验证见 verification/RING-TEXTURE.md。 + +- 用户查看恢复后的版本与磨砂修正后反馈‘效果很不错’,确认保留本版。 + +## 屏幕比例与触摸适配 + +- 用户授权按桌面不同比例、手机横屏、竖屏构图与触摸、实机性能校准的顺序完成适配;实机目标为 iPhone / Safari。继续采用本项目原生实现流程,不启用前端或动效 Skill。 +- 逐帧对照保留 1920×1080 舞台,完整等比展示;用户于 2026-09-10 追加要求正常登录入场铺满实际视口并移除字幕。交互阶段按实际视口布局。16:9 为视觉基准,桌面延展阵列,竖屏上方模型、下方独立滚动正文。 +- 镜头适配不能改变抽取高度、模型横向/纵深坐标、波浪、归位规则与基线材质。竖屏预览镜头也不能跟随卡片实时升降或轨道位置抵消运动。 +- 触摸在阵列区域左右切列、上下切档;详情获得净空后可单指检查,360° 查看器单指旋转、双指缩放和平移。正文滚动与场景手势隔离,旋转屏幕保留当前选档、视角与拆解状态。 +- 画质仍采用已有用户设置与原始默认值;不得以手机类型直接切到删减光影的性能档。实机校准必须区分真实 iPhone 数据、桌面 WebKit 兼容性检查与 Chromium 视口模拟。实现见 `src/viewport-layout.ts`、`src/responsive.css`,验证见 `verification/RESPONSIVE.md`。 + +## PWA 与线上入口 + +- 用户要求增加 PWA,以便 iPhone 从主屏幕进入独立窗口;沿用现有 Vercel 项目与正式域名 https://rhine.lubeiluchen.cc/,README 保留在线入口。 +- 正式构建缓存完整资源版本,更新下载失败时保留旧版。新版本准备好后由用户在设置中更新并重启,不中断当前浏览;保留收藏和偏好。 +- 开发模式不注册 Service Worker。图标复用项目共享品牌路径;主屏幕安装、离线与更新说明见 `docs/PWA.md`,验证见 `verification/RESPONSIVE.md`。 +- 针对 Win11 Chrome / Edge 的反馈,设置图标保留可见“设置”文字,新版本提供可直接点击的更新入口;`update.html` 从网络获取,用于旧版页面无法找到更新按钮时恢复,不清除收藏或偏好。 +- 动态效果默认遵循系统偏好,设置中明确说明跳过开机和简化选档的影响,并提供“启用完整动效并重播”。本站明确选择优先于系统默认,正文解密遮罩也遵循同一设置。验证见 `verification/PWA-RECOVERY.md`。 + +## 字体分包与开场启动(issue #6、#7) + +- 用户于 2026-09-10 明确批准:比较 `misans` 与 `misans-webfont` 第三方分包,在确认加载收益和显示效果后替换官方整包。本项目允许采用第三方 MiSans 分包,此明确授权覆盖此前禁止第三方分包的全局默认;保留字体署名、许可、来源和固定版本记录。 +- 分包随项目同源部署,按页面字符加载;保持现有 MiSans 的字形、字重映射与排版。PWA 仍缓存完整版本,避免新档案或离线检索缺字。 +- 比较后采用 `misans-webfont@4.3.1`,字体 4.003;固定版本与源哈希保存在 public/fonts。品牌三行宽度按基线重新校准,首次入口字体约 415 KiB。选择依据、完整离线体积取舍与验证见 `verification/STARTUP-LOADING.md`。 +- 开场等待用户点击、轻触或键盘启动,解锁声音并完成准备后统一开始音画;保留静音和减少动态效果偏好,加载失败提供重试与无声进入。 +- 用户授权完成验证、提交及同步后,回复 GitHub issue #6、#7 并关闭它们。 + +## 桌面工作台(实验) + +- 用户于 2026-09-10 批准在同一壁纸中切换档案展示与实用工作台,首版为时间日期、三条今日事项、重要日程倒计时、系统媒体信息与专注计时。沿用既有原生实现、视觉和 Blender 资产。 +- 模式与事项内容、日期、计时时长从 Wallpaper Engine 属性读取。事项完成状态每日重置,专注计时按绝对截止时间恢复;这些进度独立存储,不受宿主声音与画质覆盖。 +- 用户提出把玻璃档案交互用于真实文件夹。本次仅评估可行性,尚未授权安装本地桥接服务;研究见 docs/DESKTOP-FOLDERS.md。 +- 用户于 2026-09-10 要求工作台严格沿用原主页设计,撤回首版绿色强调色与有底色的面板。采用原主页近黑/暖灰、细线与竖线刻度,规范见 DESIGN.md。 + +## 工作台显示内容与纯净效果 + +- 用户于 2026-09-10 决定将纯净效果与工作台合并,通过按需隐藏元素配置画面,不另建纯净模式。 +- 七项显示开关全部位于 Wallpaper Engine 壁纸属性:时钟日期、今日事项、右侧功能内容、底部功能导航、品牌文字、页脚信息、设置入口。默认全部显示,仅影响工作台交互阶段;开场与档案展示沿用原样。 +- 用户明确允许隐藏设置入口,恢复通过 WE 属性完成。壁纸内不提供另一套临时显示开关。隐藏内容不清除任务状态或停止专注计时。 +- 音乐频谱、自动演出和小游戏仍为讨论中的后续方向,本次显示配置不代表已实现这些效果。 + +## 明暗配色(壁纸实验) + +- 设置及 Wallpaper Engine「入场与画面」分组提供亮色/暗色,默认亮色。暗色采用石墨背景、烟灰玻璃、暖白文字与琥珀标记,沿用原生实现。 +- 切换时以当前档案为起点,沿行列依次改变卡片材质;背景与文字同步渐变。中途反向从当前颜色接续,减少动态效果时直接到位。 +- 详情解密后盖板恢复清透,内部结构保持可读;查看器、工作台与开场共同使用配色。实现与验证见 verification/THEME.md。 + +- 用户要求静止呼吸可独立开关,且「始终归齐」也可生效。WE 工作台显示分组新增该开关,默认开启;音乐时淡出、静音后恢复。用户认为现有音乐律动杂乱,已提出鼓点主导宽波的后续方案,当前未替换音乐算法。 + +- 用户批准原版、鼓点宽波和整组轻抬三种音乐律动并列试用;WE 属性中选择,默认保留原版,尚未决定最终方案。原有呼吸开关继续独立生效。 + +## 自适应阵列与暗色雾气 + +- 用户要求解决边角露出阵列尽头,并将屏幕外档案裁剪、按屏幕覆盖范围动态决定数量作为同一任务。 +- 交互阵列按当前实际镜头与视口计算候选范围,逐实例裁剪并紧凑提交;替代固定 9 列 × 32 行的交互窗口。显式原片对照继续保持原来的 160 个位置。 +- WE「入场与画面」提供自动标准余量/自动额外余量,默认标准。余量包括边缘预留、卡片完整高度和附近阴影贡献;相同宽高比与构图不因像素数量增加而复制更多档案。 +- 暗色采用可见灰雾,并加强顶部、底部和边缘淡出。连续循环、点击、抽取、归位与原片对照回归见 verification/ARRAY-COVERAGE.md。 + +## 工作台衔接与连续频谱修订 + +- 用户要求删除媒体无进度与本机时区说明,修复暗色刻度、重播悬停和接力退出与设置的重叠。工作台开场隐藏选档标签,Powered 淡出;工作台与档案展示均保留退场过渡。 +- 用户比较后保留原版律动为默认,撤回鼓点 A 与整组轻抬 B。重做 A 为按屏幕从左低频、中间中频到右高频的持续频谱,B 为持续层叠流波,两者均响应持续音乐。新 A/B 待试用。 +- 正常开场同样动态覆盖实际视口,只有显式逐帧对照保留 160 个档案位置。实现与复核见 verification/WORKBENCH-REVISION.md。 + +- 媒体信息各通道独立更新:不能在歌曲文字回调中删除封面或进度。保留先封面后文字、同封面切曲与播放器切换;只有相应通道的显式空值替换旧状态。见 verification/WORKBENCH-REVISION.md。 + +- 用户进一步要求游戏模式进出也有过渡:工作台淡出、游戏信息进入,退出反向衔接;快速打断延续当前状态,退场期间保持输入隔离,完成后恢复焦点,减少动态效果直接切换。 + +## UI 视差与画面质感(壁纸实验) + +用户明确要求仅 UI 产生 HUD 曲面视差,壁纸三维镜头不随被动鼠标移动。按参考项目的镜头畸变思路,UI 各区域以共同径向曲面投影到不同梯形;独立「鼠标追踪」关闭后保留静态形状。WE「HUD 与画面质感」提供曲面纵深、全局屏幕效果、局部界面磨砂底,三项默认关闭,强度可调。 + +用户追加明确:色散、暗角与颗粒处理最终全局合成,覆盖 UI、三维画面和弹窗,替代此前仅三维画布的 ShaderPass。颗粒增加尺寸控制;减少动态效果时冻结颗粒并停止鼠标追踪,静态曲面仍可保留。开场无 UI 视差,全局屏幕效果同样可见。磨砂底随 UI 投影。继续原生实现,见 verification/VISUAL-EFFECTS.md。 + +用户追加要求 UI 上、下、左、右四边距分别使用滑杆,并纠正为允许负数。WE「界面边距」提供四个 −300~300px 的独立偏移,默认 0;正值内收,负值外扩,抵消舞台缩放。仅调整交互 UI 布局,三维画布保持铺满;支持曲面与磨砂。实现与验证见 verification/UI-INSETS.md。 + + +2026-09-10 修订:曲面纵深、细颗粒、颗粒尺寸、边缘色散、暗角的默认数值统一为 20。下边距不再缩短桌面工作台的中部定位容器;底部导航和页脚先上移,实际接近中部内容时才推动对应内容,上方品牌限制继续上移,空间不足时内容独立滚动。手机窄屏继续采用原有纵向滚动布局。 + +WE 新增「工作台 · 可用功能」,时间日期、今日事项、重要日程、正在播放、专注计时各自可开关,默认全开。关闭功能同时移除其右侧内容入口和底部按钮;当前项关闭时选择首个仍开启功能,全关时隐藏右侧内容和导航。左侧时钟与事项仍由原显示开关控制,任务和计时状态保留。用户明确要求自行验证,本轮仅完成实现和构建打包,未进行运行时与视觉验收。 + + +2026-09-10 后续修正(覆盖上述中部避让方案):用户明确下边距只移动底部两排,排间距固定,中部组件不压缩、不滚动。已删除 workbench-insets 自动避让;工作台高度不再随下边距变化。底部导航、游戏入口与页脚在曲面投影之后使用相同 translate 偏移。另修复 :is 内 ID 提高优先级导致原规则覆盖,以及减少动态效果的隐式 all 过渡污染曲面测量的问题。Edge 实际页面完成 1920×1080、2560×1440、1600×900,曲面 0/20/80,下边距 60/192/300/−80/0,共 45 组检查:中部坐标及尺寸固定,无新增 overflow,底部各行位移一致。画面对照与数据见 verification/ui-insets/。 + +WE「入场与画面」新增暗色专用开关「暗色模式仅选中档案标签使用强调色」,默认关闭保留现有选择。开启后阵列与归位档案的顶边标签使用暗色,当前选中档案保留琥珀强调;浅色不受影响。通过既有材质 uniform 实现,无新增网格与绘制通道,暗色开/关及浅色截图已检查。 + + +## 超级性能模式与自定义画质 + +用户于 2026-09-10 要求低配壁纸模式与减少动态效果独立,并恢复 WE 原生自定义画质。新增「超级性能模式(保留动态效果)」开关,默认关闭;开启时以独立的有效画质覆盖已保存画质,不改减少动态效果、宿主帧率或音乐律动设置,关闭恢复当前预设/自定义配置。 + +超级模式:三维比例 60%、DPR 上限 1、总像素不超过 921600;关闭阵列阴影、AO、景深和 SMAA,直接输出主场景;背景阵列使用无折射、无清漆材质并隐藏小螺丝,选中/详情模型保留完整结构和低分辨率折射。全局屏幕滤镜暂时停用,文字磨砂改为无模糊底色,HUD 曲面与追踪继续运行。DOM 文字不降低分辨率。 + +WE 画质下拉增加「自定义」,仅此选择且超级模式关闭时显示九项控制:渲染比例、像素密度、抗锯齿、阴影、AO 采样、AO 分辨率、景深、透明材质分辨率、纹理过滤。连续参数用滑杆,离散参数用原生下拉。部分宿主回调保留其他参数;超级模式开启期间保留自定义更新,关闭恢复最新配置。完整证据见 verification/SUPER-PERFORMANCE.md。 + +## 三维资源开关与自定义壁纸 + +- 用户于 2026-09-10 授权在左下 SESSION 右侧增加 3D 开关:档案错峰下落退场后实际释放全部三维场景、查看器和 WebGL 资源;再次开启重新加载并从下方逆向升起,保留选档。关闭途中可反向恢复,减少动态效果时直接切换。 +- 3D 退场、关闭与载入期间隐藏游戏入口及模型专用入口;重新开启按原显示偏好恢复。2D 工作台和正文继续可用。 +- WE「入场与画面」增加可选自定义壁纸,默认不启用;用户选图后,仅在三维资源释放后淡入。重新载入成功时图片淡出,失败保持图片并允许重试。沿用原生实现,见 verification/THREE-RELEASE.md。 +- 用户明确保留三种音乐律动并列选择:原版、连续频谱、层叠流波。原版仍为默认,不再标为待定 A/B。 +- 修复壁纸自身交互声音触发音乐归齐:静音状态抑制本地音效引起的新频谱活动,已建立的音乐继续响应。全部归齐时取消标签强调,暗色仅选中强调随实际升降连续过渡,见 verification/SELECTION-STATE.md。 + +## 创意工坊发布准备 + +- 用户于 2026-09-10 曾授权发布,随后明确改为自行操作发布,代理负责 GIF 和操作说明;当前尚未上传。 +- 正式名称为 Rhine Lab · 莱茵生命交互桌面,去掉“本地测试”。独立正式工程放在 Wallpaper Engine 的 myprojects/rhine-lab-workshop,保留之前本地试用工程。 +- 预览参照用户指定的 3088099655 预览的全景/局部剪辑方法,素材全部来自本工程工作台实际运行画面。最终 GIF 256×256、13.2 秒,含时钟事项、专注与明暗切换;没有将参考壁纸的图片放入发行包。 +- 发布操作和后续更新须保留 workshopid,见 docs/WORKSHOP-PUBLISH.md;预览来源与验证见 verification/WORKSHOP-PREVIEW.md。 + +- 用户要求增加「启动时加载 3D」WE 属性,默认开启;关闭时只播放前段 2D 入场并进入组件页,不创建三维场景,左下可手动加载。属性下次加载生效。自定义图片在 2D 开场后显示,支持 WE 编码盘符和失败后同路径重试。见 docs/WALLPAPER-ENGINE.md。 + +- 用户要求自定义壁纸上下遮罩范围可调:WE 图片设置下新增 0–100 滑杆,默认 100,0 完全关闭遮罩;仅自定义图片显示时生效,沿用原生实现。见 docs/WALLPAPER-ENGINE.md。 + +- 用户要求改善无遮罩图片背景下浅色页脚按钮的对比,并将既有滚动库应用到时钟、播放器时间及切歌文字。使用原生实现与现有 createRollingText,460ms direct,媒体原位更新,减少动态效果直接显示。见 docs/WALLPAPER-ENGINE.md。 + +- 用户撤回额外白底的可读性方案:删除页脚新增底色与外扩阴影,仅调整底部导航、待办、提示及按钮的文字颜色;原玻璃背景和模糊参数保留。 + +## 壁纸功能回合主分支(2026-09-11) + +用户明确授权评估兼容性后更新 main,覆盖此前不合并主分支的实验限制。共用暗色主题、自适应阵列、开场铺屏、滚动数字修复与资源管理进入网页;超级性能模式适配为网页持久化设置,默认关闭,独立于减少动态效果。工作台、系统音频/媒体、HUD 属性、自定义图片与 3D 卸载入口仍仅在 wallpaper 构建启用。普通网页保留启动声音解锁、键盘与触摸、PWA 和自定义画质。继续沿用原生实现流程。验证见 verification/WEB-INTEGRATION.md。 + +- 2026-09-11:用户要求开启 HUD 时开场同样应用曲面与鼠标追踪,覆盖此前开场无 UI 视差的限制。Logo、验证文字、圆环和欢迎画面按当帧原始动作叠加投影,品牌与 Powered 共用同一曲面;切换阶段保留深度与指针跟随状态。磨砂底仍仅交互阶段启用。见 verification/BOOT-HUD.md。 diff --git a/DESIGN.md b/DESIGN.md new file mode 100644 index 0000000..3d1b670 --- /dev/null +++ b/DESIGN.md @@ -0,0 +1,306 @@ +# 视觉与行为基准 + +## 2026-09-09 · 屏幕比例与触摸适配 + +用户授权执行桌面比例 → 手机横屏 → 竖屏与触摸 → iPhone / Safari 实机校准。沿用已确认的模型、字体、色彩、光照与原生实现方法。 + +- `viewport-layout.ts` 统一舞台尺寸与取景参数。启动/重播保留完整 16:9 参考舞台;桌面交互以 1080 逻辑高度延展宽度;窄屏和竖屏使用真实 CSS 像素布局,文字与按钮不再整体缩小。16:9 基准仍落在原坐标。 +- 横屏保持左侧模型、右侧档案。窄横屏正文独立滚动,导航适当错行;竖屏正文从屏高 54% 开始,保留上方模型、返回与 360° 入口。短屏的取景范围额外受返回按钮与正文上沿约束。 +- 模型位置与运动函数继续沿用原值。通过相机锚点和垂直取景范围保证构图,竖屏预览使用固定基准焦点,避免抵消抬起、呼吸和轨道运动。开场取消留白时,镜头先维持模型的实际显示尺寸,再通过既有相机插值接入交互构图。 +- 品牌使用原版 MiSans 排版和同一标志,小屏缩放整组品牌;正文保持 15px,检索输入 16px。主要操作至少 44px 触控高度,档案刻度为次要跳转目标。顶部/底部与两侧边距读取安全区域。 +- 左滑进入下一列,上滑进入下一档案,反向手势对应上一项。滑动需超过 36 CSS px、主方向至少为次方向的 1.3 倍,且在 1.4 秒内完成;轻点继续用射线选择,多指或取消事件不触发切档。详情的模型拖动使用实际坐标差,保留净空与转正限制。 +- 独立查看器单指旋转、双指缩放和平移。竖屏为模型保留独立画布,装配结构移至下方三列;拆解时取景轻微展开,缩放和平移仍由用户控制。旋转屏幕只调整投影与布局,不复位视角、内容或拆解进度。 +- 软键盘只改变弹窗可用高度,检索、收藏、设置均可滚动;详情排版变化后重新测量解密遮盖与页签下划线。页面隐藏和二维片头完全覆盖时停止三维绘制,画质、透射和光照默认值不变。 +- 校准入口 `reference/mobile-review.html` 通过 `npm run review:mobile` 启动;只在开发服务器接收有限大小的帧时报告,保存至 `.tools/mobile-calibration`。该入口不进入生产构建,也不将桌面 WebKit 跑分视为 iPhone 成绩。 + +验证与实机状态见 `verification/RESPONSIVE.md`。 + +## 2026-09-09 · 档案标题滚动 + +按用户新要求,主档案标题采用与数字一致的滚动效果,替代历史快切黑条。复用已安装 `@kitlangton/rolling-number` 的 `createRollingText`,与编号共享 460ms 运动配置;使用 direct 模式直接替换中文、关闭逐字延迟,字数变化交由相同的宽度弹簧处理。固定向上滚动,标题模糊强度为 0.2;字号、颜色与位置沿用原样。保留单份可访问与可选择的语义文字,装饰字槽不进入复制内容。 + +连续输入由现有引擎从当前运动接续,只显示可见过渡与最新目标,不排队播放每个中间标题。进入详情、重播或开启减少动态效果时立即落到最终文字。历史黑条实现已移除;规范与回归检查见 `verification/ROLLING-TITLE.md`。 + +## 2026-09-09 · 详情与弹窗过渡补全 + +合并 `codex/boot-motion-polish` 的查看器平移、缩放与 560ms 球面复位,沿用该分支的品牌文字校准。详情层以 180ms 进出,正文仍跟随实际镜头进度显示;返回时不再立即删除可见内容。阵列氛围与右侧浅色遮罩按同一镜头进度交叉渐变,保留两端既有画面。快速反向从当前透明度接续,进入正文的焦点等待可读状态。 + +检索、收藏和设置弹窗统一使用 300ms 入场、200ms 退出。面板入场位移 12px、退出位移 8px;进入中途关闭时从实际透明度及位移继续。退出前保留对话框节点、焦点和背景输入隔离,重复 Esc 不向底层传递;选档与重播在关闭完成后执行。 + +页签正文采用 150ms 淡入,下划线使用 180ms 连续移动;快速切换取消旧内容动画并显示最终页签。收藏仅修改按钮、收藏计数及 220ms 颜色反馈,保留阅读区域节点、滚动位置与按钮焦点。新增过渡遵守减少动态效果设置。实现见 `src/ui-transitions.ts`,验证见 `verification/UI-TRANSITIONS.md`。 + +## 参考与既有规范 + +参考为目录原片的 5–40 秒,1920×1080、25fps。用户选择参考驱动的原生实现,不使用前端或动效 Skill。美术模型通过 Blender MCP 制作,界面使用 TypeScript、Three.js、Vite。 + +- 暖灰白底、黑色文字、暖杏金选中信号。界面以细线和紧凑排字组织,保留原片标题与右下角署名位置。 +- 字体为用户指定的 MiSans。2026-09-10 用户明确批准为加快加载改用第三方分包;比较后采用 `misans-webfont@4.3.1`(源字体 4.003),固定版本同源部署,按字符加载。保留 300、400、600、700 四个字重映射与 `font-display: swap`;后备为 PingFang SC / Microsoft YaHei / system-ui。字体分片字节不改动,移除上游 CSS 的 local() 以免设备字体覆盖固定版本。版权、许可和分片哈希在 public/fonts,设置页含署名与许可入口。详见 `verification/STARTUP-LOADING.md`。 +- 5–27 秒为接入、身份验证、授权扫描、欢迎和白场。26.92–27.67 秒,阵列从左侧滑入,镜头从接近侧视快速抬高,随后进入斜俯视扫描。 +- 27–29 秒为第一轮传播;29–30.5 秒反向回传。30.58 秒开始第一段小幅抽取,之后有停留;32.55 秒开始第二段抽取,镜头随后继续转向和推近至 39 秒。 +- 选中卡片两侧在回传后继续起伏。主通道保留较高的连续肩部,其余通道幅度较低;按距选中位置的距离延迟并衰减。 +- 相机校准必须使用未被遮挡的顶部边线、标签和重复卡片端点。不能把前排遮挡后的可见高度当作整张卡片高度,也不能通过扩大通道间距或设置通道高度阶梯补偿可见列数。 +- 当前 31.48 秒镜头约为方位角 59°、仰角 19°、垂直取景范围 7.33 世界单位。远距镜头配合小视场角压缩透视。相邻通道同高、中心距 5.2;卡片宽 5、高 3.7,行距 0.62。后半段镜头转向与抽取升降分别控制。 +- 39 秒特写由屏幕锚点控制构图,镜头逐渐转至约 18° 方位角、13.8° 仰角。保密等级扫描线投影在同一个实时模型上。 +- 模型由磨砂盖板、基板、内部光学腔、环槽、边框、螺钉、标签与刻线组成;圆环位于盖板后方。重复卡片也启用物理透射,使用简化内部基板与象牙色端面。 +- 交互保留检索、切换、详情、收藏、导出和设置。轨道与升降采用保留速度的临界阻尼;旧卡片独立归位,快速重新选中时接续运动。 +- reference/review.html 仅在开发环境对照原片与实时场景;暂停使用原片逐帧图,播放使用视频。产品入口和生产构建不嵌入原片。 + +实时屏幕空间透射无法递归恢复原片全部透明层间折射。当前实现以关键帧投影和连续播放复核,尚未通过逐像素差分认证。 + +## 用户标注后的镜头与几何修订 + +- 三张标注经去除黄色笔迹区域后与原片帧匹配,对应 27.60、29.16、31.56 秒。 +- 档案端面深度约 0.31、行距 0.62;原来的端面偏薄。由 Blender MCP 沿模型厚度方向调整并重新导出,标签与投影标记同时贴合新表面。 +- 前半段采用较近的透视镜头:27.60 秒约 28 世界单位镜头距离、43° 仰角;29.16 秒约 35 世界单位距离、35° 仰角、67° 方位角。之后连续转至既有选中构图,特写镜头距离为 72。 +- 回传波进入时机向后校正,29.16 秒尚未形成明显右侧回波,与标注帧对应。 +- 删除所选模型朝 z=3.1 的位置插值,以及背景阵列随 detail 额外下沉的逻辑。选中槽位在抽取全过程固定横向与纵深位置,模型只有高度变化,镜头负责构图。 + +## 交互预览与进入详情 + +- 左右切换停在原片 31.56 秒对应的浅抬起状态(0.4 世界单位),邻排沿同一肩部轮廓排列;点击 ACCESS FILE 或按 Enter 后继续升至 3.35。返回恢复预览,详情中换选也先回到预览。 +- 卡片超过浅抬起高度后,镜头才逐渐转向并把卡片放在画面左侧;详情文字随镜头进度在右侧渐显。抽取不再自动旋转模型,详情拖拽仍可检查角度。 +- 阵列与抽出模型共用外壳几何,在同一个表面上随升降连续插值颜色、粗糙度与透光率;内部结构以连续覆盖率显现,文字标签渐显。每张归位卡片拥有独立材质状态,接近完全归位且外观一致后才恢复阵列实例。 + +## 最新交互修订(覆盖上文旧的按键和详情高度) + +- 左右键切列,上下键切换列内前后档案。五列分别为工程研究、生命科学、机构档案、能量研究、特别项目,各 8 份,共 40 份;切回一列保留上次的档案选择。 +- 760 帧(30.40 秒)各列波峰等高,随后摄像机右移,把同一物理列置于画面中央;785 帧附近非选中列仍保留 25% 以上的肩部波幅。列间突出程度采用连续插值。 +- 交互详情升高 4.05 世界单位;预览仍为 0.4,原片时间轴的完整升高仍为 3.35。删除试调的 5.0 高度,保持邻片可见。 +- 旋转限幅为 ±0.8 弧度。只有卡片完全避开邻片后才接受拖拽;返回先在原高度转正,再下降,镜头在转正期间维持构图。附近旧卡片转正时,新预览暂缓抬起,避免相互穿插。 +- 标签和启动界面共用 src/brand.ts 的标志路径,标签不再手绘近似曲线。 +- 档案正文为基于公开世界观资料的原创编目文章,不冒充游戏内原始档案。每份包含设定参考链接,详见 reference/LORE-SOURCES.md。 +- 详情切换时,雾效距离跟随阻尼之后的实际镜头位置,避免进入时相机尚未到位、卡片提前被远雾覆盖。保留既有镜头轨迹与最终雾效。 +- 交互预览停止操作 2.5 秒后渐入呼吸起伏:8 秒与 13 秒周期叠加,最大单向位移 0.102 世界单位(约卡片高度的 2.8%),相邻卡片略微错相。根据用户反馈,幅度由最初版本增至 3 倍,保留缓慢节奏。切换档案、进入详情或开启减少动态效果时渐退;原片时间轴不叠加此效果。 +- 琥珀色侧条与透明盖板只有 0.002 世界单位的前后间距,斜视时会出现深度冲突条纹。仅为 Amber_Lightguide 材质增加 polygonOffset(factor=-1,units=-2),克隆归位卡片同时继承。保留原有相机裁剪范围、模型几何、材质色彩与全局 AO 参数。 + +## 独立模型查看器 + +- 详情页的「360° 查看文档模型」打开独立场景。沿用暖灰背景、既有字体、细线与克制的中英文字阶;不引入新的前端或动效设计方法。 +- 鼠标左键拖动可完整环绕模型;上下左右键平移视角,滚轮或加减键缩放。Home 或「复位视角」恢复初始取景;ESC 或「返回档案」恢复原档案与键盘焦点。 +- 「拆解档案」将紧固件、透明盖板、折射环组、光学核心、信息基板、背板与框架六组部件沿厚度轴分离。「一键重组」使各部件准确回到原位;途中反向操作保留当前运动位置与速度。减少动态效果设置下直接切换。 +- 独立场景暂停主场景绘制,并复用校准后的材质、环境光配置与当前档案标签。原阵列模型与抽取镜头不受查看器变换影响。查看器的近裁剪面为 0.3,远裁剪面为 120,初始相机位置为 (7.2, 3.8, 12),可缩放距离为 5–28。 +- 部件资源由 Blender MCP 执行 art/build_assembly.py 生成:复用原建模脚本的几何,按物理部件和材质合并,导出带 assemblyPart 信息的 public/assets/archive-assembly.glb,并保留 art/archive-assembly.blend。 +- 复现时通过 Blender MCP 在 Blender 内执行该脚本,并在执行作用域中传入脚本的绝对 __file__ 路径;脚本以自身位置定位项目。几何双向顶点距离检查允许 1e-5 的浮点误差,实测最大误差约 6.08e-7。 +- 根据用户的内外光照对照,查看器改为在自己的 WebGL 上下文内生成相同的 RoomEnvironment/PMREM。禁止跨 renderer 直接复用 GPU 渲染目标纹理,否则环境光缺失会使内部基板和圆环发暗。背景统一为 #eae5e1,复用主界面的氛围渐变,雾效范围相对实际模型距离设为 -1 至 +12,使放大缩小时保留浅色磨砂质感。 + +## 循环档案阵列 + +- 主界面将内容编号与物理位置分开:五类档案和每列八份档案按周期映射到连续的行列坐标。上下、左右的方向操作每次改变一个物理位置;最后一项接续第一项时,轨道继续沿原方向运动。 +- 切换列时保留该列的档案选择记忆,并选择附近的对应周期位置;点击场景卡片使用当前实例实际对应的档案与位置。 +- 交互时维护 9 列 × 32 行、共 288 个位置,按镜头附近的可见窗口循环补位。主模型与归位副本按物理坐标独立管理,避免同一档案的不同周期副本互相覆盖。 +- 横向采用整组阵列轨道平移,纵向保留原有前后轨道;两者都保留阻尼与速度。取景和灯光附近的实际坐标保持有限,选定后的抽取仍只改变高度。 +- 当逻辑坐标超过 2048 时,按内容周期统一平移坐标与运动状态;保持世界位置、速度、波纹与呼吸相位连续,避免长期运行后浮点精度下降。 +- 显式原片逐帧对照保留原始的前 160 个位置;正常开场与交互阶段均按实际视口动态生成背景档案。循环后的重播会清理归位副本。 + +## 切换波浪微调(历史实验) + +- 以循环阵列提交 aeddd8f 为基线。用户试用减弱波浪后,明确要求恢复原来的波浪幅度与节奏,仅去掉负向波谷。 +- 交互选中波浪保留 0.8 幅度系数、0.2 秒起势、原衰减率和传播速度,仅把余弦的负值截为零。正向波峰仍会升起并回落,波浪本身不再将档案压低到基础肩部轮廓以下。 +- 验证入口为 reference/rise-review.html;原片时间轴、浅抬起高度、详情抽取与归位规则沿用当前基线。 +- 用户请求的可视对比页为 reference/wave-compare.html:两份实际 ArchiveScene 共用资源与时间步长。左侧可选择原始基线 aeddd8f 或仅去除负向波谷的 2048536,两者均为同时播放;右侧保留先抬起后波浪的实验方案。支持单步、连续、反向切换,0.25×/0.5×/1× 播放、逐帧定位、选中区域放大与高度曲线。每次回放检查实际位置与预先采样的轨迹一致。该页仅用于开发环境审阅。 + +## 先抬起,再波动(历史实验,当前不采用) + +- 该实验的新选中档案先抬起,再发出选中波浪。预览目标仍为 0.4,向上抬起的临界阻尼速率从 4.2 调为 7.6;高度达到 0.35,且同列前后四格内的归位旧档案比它低至少 0.015 后,才启动波浪。单次正常切换约 0.48 秒后启动;旋转归位等特殊情况按实际状态等待。 +- 每次选择替换尚未发出的波浪。进入详情、重播时取消待发波浪;减少动态效果时不发波浪。原片时间轴继续采用原值。 +- 用户进一步要求消除选中档案再次跳起的效果:涟漪在源点的位移为零,并在周围 2.5 行的距离内平滑展开;正余弦包络改为平方,使边缘以零速度起落,保留远处波峰的幅度和传播速度。选中档案仅完成原定的 0.4 抬起,自己的涟漪不再给它叠加高度。 +- reference/sequence-review.html 验证先后关系、归位高度、连续快速切换、反向、详情旋转归位、减少动态效果与重播取消。 +- 档案序号与列编号采用 @kitlangton/rolling-number 0.4.1 的原生 DOM 接口,460ms 滚动,固定两位数,低强度运动模糊。方向键跨循环边界时保留滚动方向,快速输入由组件延续当前运动状态;系统减少动态效果与页面设置均可关闭滚动。MIT 许可随包保存到 public/licenses/rolling-number.txt。 + +## 当前采用:原始基线动效与光影微调 + +- 用户对比后明确回到原始基线 aeddd8f。生产场景默认使用完整的有正负波谷的选中波浪,选中与波浪同时开始,升降阻尼速率恢复 4.2。此前实验保留在参考页面;Rolling Number 沿用。 +- 灯光统一到 src/archive-lighting.ts:曝光 1.00,环境强度 0.52,暖白主光 1.7,主光位置 (-8,14,4),补光降至 0.3,半球光降至 0.5。主场景和独立查看器各自在自己的 WebGL 上下文生成环境贴图。 +- 阵列盖板增加少量暖色吸收,斜面和背板偏向浅香槟色;内部圆环与基板保持可读的暖灰层次。吸收色和吸收距离与其他材质属性一起随升降过渡。阴影偏移小幅收紧,AO 半径从 0.38 调为 0.44。 +- 模型、几何、布局与镜头沿用已确认版本。reference/light-review.html 可切换四个关键帧以及原视频/调整前参照;两侧实时场景使用相同模型位置与镜头,并自动检查一致性。原片关键帧需在本地 reference/motion 中准备,详见 verification/LIGHTING-POLISH.md。 + +## 当前采用:基线配色与档案编号滚动 + +用户对比后确认整体基线版本的颜色更合适。生产场景和独立查看器默认切回 baseline,包括材质颜色、透光参数、曝光、灯光与阴影/AO;refined 仅保留在光影对照页中作为实验。 + +X-001 形式的主档案编号和悬停编号复用现有 Rolling Number:固定 X- 前缀,三位数字保留前导零,460ms 滚动并支持快速输入衔接。方向键切换沿导航方向滚动;悬停首次出现直接显示,再切换目标时滚动;减少动态效果时立即更新。编号的字号和布局保持原样。 + +## 标题快切与 360° 查看器进出 + +历史标题方案(已由 2026-09-09 的滚动文字替换):主档案标题在连续变化间隔小于 240ms 时触发短促闪动,140ms 后变成稳定的黑色横条;横条锁定本轮开始时的宽度。停止 260ms 后以 180ms 短闪显示最终标题,单次切换直接显示。切入详情、启动或减少动态效果时清理计时和动画。 + +360° 查看器以 320ms 淡入覆盖原详情,页眉和页脚轻微上移就位;模型完成载入并渲染后以 380ms 从 0.97 倍展开并淡入。退出以 220ms 淡出和轻微收拢接回原档案。使用可取消的原生动画,进入途中按 Esc 从当前透明度退出;退出结束之前继续绘制、保持模态输入隔离,之后才释放模型、恢复详情和原按钮焦点。过期的异步载入结果按请求序号释放。减少动态效果时直接切换。 + +reference/micro-transition-review.html 验证真实应用中的快切、最终标题恢复、切入详情取消横条、进入打断、重复关闭、拆解中退出、焦点归还和重新载入。2026-09-08 实测 13 项检查全部通过。标题短时动画通过定位到动画末端检查,避免后台标签页对计时器的节流导致误判。 + +## 2026-09-08 · 启动前段逐帧修订 + +以原片 25 fps 连续帧复核 6.76–27.00 秒。用户排除黑底鹰角片头,启动和重播从画面变白后开始。应用的内部参考时间仍为原片时间减 5 秒;首次加载遮罩退出后才开始显示英文输入。 + +`boot-motion.ts` 将逐字输入、停顿、清空、闪切固定到原片帧点,空间位置使用连续插值。`boot.ts` 驱动 DOM/SVG,不依赖循环 CSS 动画的已播放时长,因此正常播放、慢放和前后跳帧使用同一画面状态。英文从固定左端输入,标志从右上弧线开始绘制、绕左环再补全右环,完成后向左移位;品牌三行错开出现。 + +身份段恢复原文 `ID CONFIRMED : JOYCE MOORE`、`REQUEST RECEIVED`、`START PROCESSING...`。权限段用从画面外收拢的黑白双环、反向内弧、橙色节点、四帧缩放跳变和中心图形切换。欢迎段保留黑白闪切、公司名称黑底横向扫过、第三行 `INTERNAL DATABASE` 闪现,最后缩小、失焦、带轻微青色过渡进入白场。进入阵列之后的 reveal/lift/zoom 时间函数未修改。 + +参考帧与检查结果见 `verification/BOOT-MOTION.md`。`reference/boot-review.html` 可正常播放、半速、四分之一速度和逐帧核对;原片及提取帧只用于本地验证,不作为产品背景。 + +## 2026-09-08 · 品牌排版与 360° 导航 + +用户要求放大并叠加原片后进一步校准字重和字间距。以原片 13.48 秒左上角为参考,最终保留 MiSans:品牌容器左 59px、上 114px、宽 270px;第一行 44px / 48px 行高 / 2.5px 字距;第二行 19px / 23px 行高 / 600 字重 / 0.65px 字距;第三行 35px / 40px 行高。ANALYSIS 的八个字符按原片黑色字面起点调整各自间隔,OS 在 270px 容器右端对齐、字距 3px。浏览器测得三行宽度约 269.78、269.63、270px。此为固定品牌字样的排版,不修改字体文件。字体轮廓仍有差异,叠加页保留原片、当前版本及透明度滑杆供复核。 + +360° 查看器使用独立目标相机记录 OrbitControls 输入,显示相机对观察中心、球面角度和对数距离进行跟随。平移与缩放的大部分运动在约 200–250ms 内完成;复位采用 560ms 减速曲线和球面短路径,不穿过模型。拖动、滚轮、方向键可从正在复位的当前显示位置接管。进入新档案直接初始化到默认位置,减少动态效果时跳过镜头插值。原有平移半径 5、缩放距离 5–28 的边界保留。 + +验证:`reference/viewer-motion-check.html` 的 14 项检查通过,包含 30/120fps 一致性、复位中断、旋转跨正负 180°、连续平移和缩放、Home 精确归位、边界以及重新进入。`reference/brand-review.html` 使用生产界面的共享文字与样式进行 2.4 倍放大及红色叠加。 +## 2026-09-09 · 可调渲染画质 + +用户要求开放更高画质与细分控制。系统设置增加性能、原始、高、极高四个预设及九项独立参数:渲染比例、像素密度上限、SMAA 抗锯齿、纹理过滤、透明材质分辨率、阴影分辨率、环境遮蔽采样与分辨率、景深强度。默认采用原始预设,继续沿用已确认的基线颜色、灯光与镜头。画质仅调整渲染精度与可选效果,不重新制作模型。 + +清晰度与材质参数同步至 360° 查看器;阴影、AO 和景深标明仅用于阵列。设置自动保存并兼容原先的高画质开关。面板显示实际渲染尺寸与硬件限制后的纹理倍率;全尺寸缓冲上限为 8,294,400 像素,渲染比例开放至 200%。完整参数、资源生命周期和验证记录见 `verification/RENDER-QUALITY.md`。 + +## 2026-09-09 · 原创音效与配乐 + +依据原片 6.76–40 秒画面及音轨动态、莱茵生命相关设定,新增原创「观测室」三轨循环配乐与交互音效,后续修订共 17 类。浏览、详情、独立模型查看以及启动阶段采用不同声部比例;用户可以独立控制音乐和音效。旧版静音偏好继续生效。 + +设计、参考依据、复现命令、技术验证及听感限制见 `verification/AUDIO-DESIGN.md`。本次未进行人工听辨确认;试听入口为 `reference/audio-check.html`,生成器为 `scripts/render-audio.mjs`。 + +### 玻璃交互声音修订 + +用户进一步指定玻璃交互应有清脆声音。当前版本采用短起音、不等间隔共鸣和迅速衰减的高频,区分切档轻碰、抽取余音与归位落定。详见 `verification/AUDIO-DESIGN.md` 的玻璃质感修订;用户指定的是声音方向,尚未确认本版听感。 + +### 原片逐字输入音效复刻 + +依据用户明确要求,逐字输入改用原片约 6.864–6.986 秒的三个 38ms 短音轮替,保留源波形,按实际文字揭示帧触发。输入最短间隔为 24ms,避免 25fps 相邻输入漏拍;新字段首字有反馈,闪动恢复不误触发。提取脚本、源校验、对照试听与限制见 `verification/AUDIO-DESIGN.md`、`public/audio/typing-source.json`。 + +### OS 场景的声音语义 + +窗口开合使用短电子升降音,启动标志使用低频缓起和声,整块文字显现使用轻微数据脉冲,授权使用双电子短音;逐字输入继续使用原片短音。玻璃共鸣对应档案切换、抽取、归位、阵列入场与拆组。绑定和原片时间码详见 `verification/AUDIO-DESIGN.md`。完整实机试听入口为 `reference/boot-audio.html`,不改写声音偏好;该入口补全此前的开场音效复刻试听。 + +## 2026-09-09 · 档案导航文字与刻度 + +顶部档案分类、底部列名、悬停标题与权限标签复用主标题的 460ms 文字滚动;内容相同不重复播放,悬停首次出现直接显示。统一处理快速输入、进入详情及减少动态效果。底部八个刻度位置复用节点,跨列保留原有 400ms 伸缩过渡、各列选择记忆及正确的点击映射。实现与 19 项实机检查见 `verification/ROLLING-TITLE.md`。 + +## 2026-09-09 · 原片模型细化 + +按用户要求放大检查原片 37–39 秒特写与端面,继续通过 Blender MCP 制作。两组环腔改为闭合阶梯截面,细化斜肩、内孔、香槟色嵌件;增加顶部双边压槽、盖板前沿细阶、内周边与接缝,螺钉使用真实凹槽。主模型与六组拆解模型同步更新,保持原有外包围盒、基线配色光照和抽取规则。 + +对照入口为 reference/model-review.html,可切换原片、细化前及 2×/3× 放大,使用相同镜头与 1920×1080 缓冲。Blender 源场景与可复现脚本保留。几何变化、检查与局限见 verification/MODEL-REFINEMENT.md;本轮效果尚待用户查看。 + +## 2026-09-09 · 清晰内构、解密与深度精度 + +按原片 41 秒正面与 46–51 秒多角度细化内部两组环形结构:分段外环、宽斜肩、内孔台阶、橙色嵌带、立面细竖肋与连接薄带。按用户要求完全省略左侧三根高塔。新增顶部压槽、内周边、竖向浮雕字与右下服务孔。通过 Blender MCP 生成,共享 art/clear_reference_details.py,单体与拆解资产几何保持一致。 + +黑色解密线按 25 fps 采样重建:34.24–36.04 秒两端沿同一物理对角线向中央延伸,36.04–37.72 秒保持完整,37.72–38.84 秒向中央收束,39.04–39.56 秒逐渐透明。使用单调三次插值,无过冲;原片时间轴可前后跳帧。交互详情在卡片抬起和镜头就位后启动,时间压缩至约 3.63 秒;减少动态效果时直接清晰。 + +查看器增加清晰/磨砂两态,透明度与粗糙度连续变化;切换不重置观察位置或拆解进度。退出查看器回到已解密的原档案,退回阵列时恢复磨砂,下一次打开重新解密。 + +修正用户报告的大环斑驳闪烁:详情长焦距离约 72,原 near=0.1 导致邻近表面深度量化冲突。near 改为 5、far 保持 300,镜头位置、视场角及构图不变。24 位深度在距离 72 的理论步长由 0.003089 降至 0.00006077。原片 11 个代表镜头的可见模型包围盒最近深度至少 16.66,均在裁剪面之外。 + +验证、范围和复现记录见 verification/DECRYPTION.md。逐帧对照入口为 reference/decryption-review.html;实际产品进入详情后可体验,360° 查看器右上角切换玻璃模式。 + +## 2026-09-09 · 前段非线性轨迹 + +按原片连续帧修订权限圆环、名称黑底及左上角三行文字。三行分别在原片 11.12、11.20、11.28 秒出现,使用独立的先加速后减速位移,第三行到约 12.20 秒落定。黑底在 23.52 秒进入,快速扫过后缓慢收束至 24.40 秒;灰闪时保留遮罩与未覆盖文字。 + +外环、白环的半径及弧端独立变化,内环与橙点减速,黑白端点同步缩小。空间运动使用测量关键帧的单调三次插值,离散闪切保持 25 fps 时间码。仍采用原生 DOM/SVG;轨迹见 `src/boot-tracks.ts`,测量脚本及实机验证见 `verification/BOOT-MOTION.md`。本地对照页增加三处局部放大和逐帧入口。 + +### 两侧小圆与 Logo 连续轮廓修订 + +用户进一步指出两侧小圆以原位旋转和弧段补全为主。原片 544 帧开始出现短弧,左右反向旋转并减速,568 帧分别达到约 332°、339°;随即切至欢迎页。圆心只保留测量到的轻微收拢,中央圆点周围的六颗小点才沿约 35 px 的轨道绕行,小点每隔三帧进入。中央大点保留 546、547、549、550 帧的闪变,然后缩至约 10–11 px 半径。 + +Logo 改用共享贝塞尔轮廓的连续路径,绘制首尾独立变速。加号与方点于 247 帧同时出现、向各自位置展开,256 帧后加号转动、方点拉成长横。Logo 左移按 260–294 帧实测位置拟合。425 帧后缺口开始沿轮廓移动,整个标志保持黑色,不再使用整枚透明度闪烁。曲线和复核入口见 `verification/BOOT-MOTION.md`。 + +## 2026-09-09 · 内部锐利截面与半透明材质(历史方案,已撤回) + +依据用户新提供的 46–51 秒内部结构近景,专门重做两组环体的截面。宽屋面、直立内外壁、内孔薄台阶和橙色竖向嵌带采用明确的平面与小切边。自定义分裂法线沿圆周插值、在截面折边断开;厚度烘焙正确变换内构法线,同时保留外壳原先的顶点和法线。继续省略左侧高塔。 + +两条细窄连接物改为一条连续薄片:大环在 -74°、小环在 215° 附近切向接入,宽度 0.225–0.260,厚度烘焙后 0.008,两端略展宽,中央轻微抬高。全部内构深度在 -0.034 至 0.1424 之间,小于盖板内面 0.174。连接薄片、屋面与壁板分别使用灰色半透明粗糙材质,切边更亮,后方细肋可透出。 + +实时渲染采用薄层 alpha 合成与粗糙高光、掠射角密度,再由外盖折射完整内构,避免 WebGL 外盖的 transmission 捕获忽略另一层 transmissive 材质。内部闭合薄片只绘制正面,避免双面透明叠加过度浑浊。该实现是多层玻璃的实时近似,未进行完整体积光线追踪。对照页网格只用于检查透视和层次,不出现在产品中。 + +单体与六组拆解模型对应顶点误差 1.688e-7;外壳位置与法线相对打磨前误差均为 0。测试、导出与审阅入口见 verification/INTERNAL-OPTICS.md。 + +## 2026-09-09 · 外壳与自上而下的清晰过渡 + +根据用户提供的完整正面及四处局部截图,黄色嵌片改为 0.25 × 0.2239,顶边 y=3.7、前面 z=0.206 均与盖板齐平,并建立真实嵌槽。删除左侧黄条、宽端盖及盖板前的额外细阶。螺丝只保留右上和左下两处,使用十字凹槽及齐平垫圈;周围 C 形压槽与上下回路位于前盖之后。模型源脚本 art/shell_reference_details.py 由单体与拆解导出共享。 + +原先静态的高度粗糙度渐变被移动的清晰边界取代。选中盖板在进度 0 时整面 roughness=0.48;进度 1 时整面 roughness=0.025。中间羽化宽度为高度的 24%,边界从顶向下走,覆盖刻线与环体。原片时间码 38.84 秒开始,在 39.0 秒达到约 0.51,39.56 秒结束;这是从参考帧观察重建的近似时间曲线。360° 清晰/磨砂切换复用同一材质逻辑。 + +局部与前后对照见 reference/shell-review.html,可拖动清晰进度、慢放及改变观察角度。原片同步对照仍在 reference/decryption-review.html。验证见 verification/SHELL-REFINEMENT.md;听感与其他视觉方向的待确认状态不受本次技术验证影响。 + +## 2026-09-09 · 恢复第一版完整内构并减轻磨砂 + +用户最终指定 reference/internal-review.html 左侧第一版模型,完整恢复 89c7742 的两环截面、白色内沿、橙色嵌环、原始立面分隔、两条弯曲连接带与平滑法线。几何和材质来自 reference/internal-study/assembly-before.glb 对应的源脚本,并通过 Blender MCP 重建单体与拆解资产。此前锐利截面、灰色半透明单连接片及额外细槽方案不再用于产品。 + +当前内构世界坐标、法线和原始材质与第一版逐项核对一致,位置/法线比较精度为 1e-5。已经完成的外壳相对 dde61fd 保持一致;该版本快照存放于 reference/ring-study/assembly-before.glb。生成源文件为 art/internal_architecture.py、art/rhine-archive.blend 和 art/archive-assembly.blend。 + +磨砂盖板粗糙度由 0.48 减至 0.42,同时修正 Three.js 默认折射 mip 采样在小尺寸预览中相对过重的问题。根据盖板竖轴在当前镜头中的投影长度,将全磨砂采样范围限制在盖板投影高度的约 1.6%。粗糙度下降时模糊范围单调缩小,完全清晰端仍使用原采样;阵列质量为 0 时也使用原采样。保留现有由顶向底的羽化揭示,详情与独立查看器共用。 + +对照页左侧明确标记“第一版 · 原始内构”,右侧标记“已恢复 · 第一版内构”,避免把恢复误解为继续细化。原片磨砂对照仍为 reference/decryption-review.html。验证见 verification/RING-TEXTURE.md。 + +## 2026-09-09 · 主屏幕与离线 + +正式域名为 https://rhine.lubeiluchen.cc/,沿用 Vercel 部署。PWA 使用 standalone 窗口、浅暖色主题与共享莱茵生命图标,iPhone 使用 180 px Apple Touch Icon,其他设备提供 192 / 512 px 和 maskable 图标。状态栏、底部手势和横屏刘海通过安全区域留白处理。 + +安装与离线状态集中在设置中。首次联网准备完整资源,当前约 34 MiB,包含字体、模型、声音和 40 份档案;HTML 与资源使用同一个内容版本。下载成功后新版本进入等待状态,用户选择“更新并重启”后应用。下载失败保留完整旧版本,收藏和偏好不随资源更新清除。字体完整分包比此前整包略大,但网页进入前实际只加载约 415 KiB 字体。见 `docs/PWA.md` 与 `verification/STARTUP-LOADING.md`。 + +针对更新提示难以操作的反馈,设置图标增加下方的“设置”文字,新版就绪时在底部状态区提供直接更新按钮。弹窗和查看器打开时,外部更新入口遵循原有输入隔离;开场不显示该入口。`update.html` 提供网络恢复入口,供旧版用户更新完整缓存并返回。 + +减少动态效果设置补充开机、选档和文字的具体影响,提供用户主动启用完整动效并重播的操作。默认仍尊重系统偏好;正文遮罩由本站最终动效设置控制,避免用户启用完整动效后被系统媒体查询再次隐藏。 + +## 2026-09-10 · 阵列悬停、直接拖动与滚轮 + +用户最新选择自由平面拖动:鼠标与触摸的屏幕位移同时驱动列与档案两个轨道,拖动中可以自由转弯、反向。选中实际经过的档案,松手后最终吸附到最近位置;滚轮继续只用于列内切档,向上上一档、向下下一档。 + +用户试用后进一步要求类似网页快速滑动的手感。松手后保留实际速度并持续减速,快拖和慢拖产生不同滑行距离;去掉最多两列/三档的固定投影上限。选档随实际轨道位置变化,临近停止再吸附。按住正在滑行的阵列立即停止,可从当前显示位置反向或换轴接管。减少动态效果时不继续惯性滚动。 + +按住时采样负 X 切列轨道与负 Z 切档轨道的每格 CSS 位移,对两列投影向量组成的 2×2 矩阵求逆,将屏幕位移同时分解到两个轨道。取消角度分类与方向锁定,仅保留 10px 起拖阈值。本次手势内固定投影,旋转屏幕后下一次手势重新采样。自由滑行期间两个速度分量使用相同衰减,较慢分量不会提前吸附而折向另一条轨道;整体放慢后再共同吸附。自由拖动按物理格位选档并更新记忆,避免切列记忆将画面拉离指针;键盘与按钮切列仍恢复记忆。 + +悬停卡片额外竖直抬起 0.28,约 200ms 显现,移开平滑回落。悬停高度独立于选中抬起 0.4 与详情抽取 4.05,不改变镜头进度;所有网格部件一起移动。详情与独立查看器继续采用已确认的旋转、缩放和拆解操作。实现与验证见 `verification/ARRAY-INPUT.md`。 + +## 工作台沿用主页视觉 + +用户于 2026-09-10 指出首版绿色强调色与原主页不一致。工作台沿用原主页近黑正文(#080a08)、暖灰辅助文字(#77756d)、灰色细线(#aaa59a)及暖棕悬停(#9b7247)。取消新增的绿色填充、面板底块和导航色块;使用透明内容、细线分隔与原阵列长短竖线刻度表达选择。保留原字体、模型、灯光与阵列动效。 + +## 明暗配色 + +暗色以 #11181b 为背景,#e0e3dc 为正文,#a6b0b1 为辅助文字,#536166 为分隔线,#c5a16b 为琥珀强调。保留原版排版与布局,不使用全屏反色。三维阵列使用独立的烟灰材质端点及冷灰边缘,清晰盖板随原有自上而下解密前沿恢复接近无色的透射,避免遮黑内部结构。 + +切换以选中单元为起点:每行延迟 34ms、每列 110ms,最远延迟 600ms;单卡过渡 580ms,背景 850ms。切换不附加升降或位移,快速反向接续各卡当前颜色。减少动态效果直接应用目标配色。 + +## 自适应阵列覆盖 + +交互阵列根据渲染当帧完成插值后的相机重新计算覆盖范围。截取相机视锥与阵列可占据的高度区间,换算为行列候选,再用完整卡片边界裁剪,连续紧凑写入实例矩阵。鼠标点击与小游戏目标使用同一份实际实例映射。相机移动、屏幕比例变化与档案轨道移动都在当前帧更新。 + +默认保留 18% 的屏幕方向余量,额外档为 50%;保留附近阴影贡献,远处完全雾化后不继续扩展无限阵列。实例缓冲按需增长并复用容量,实际绘制数独立于缓冲容量。既有 Blender 几何与材质细节不变。 + +暗色雾颜色调整为 #263136,预览雾起点为相机至目标距离加 1,终点加 16;浅色仍为加 5/加 25。详情过渡到原来的加 -1/加 12 距离。暗色增加顶部和底部渐隐,使边缘轮廓融入背景。原片固定舞台仍维持原始阵列位置。 + +工作台细节、退场及连续频谱 A/B 的当前规范见 docs/WALLPAPER-ENGINE.md 与 verification/WORKBENCH-REVISION.md。 + +## 可选界面景深与质感 + +壁纸 UI 使用共同径向曲面,按各区块的四角计算投影矩阵,左/右与上/下产生相反的斜率;替代此前同方向平移与倾斜。纵深默认 20%,曲率系数为纵深乘 0.08,屏幕四角约束保持边距;鼠标追踪独立开启,切向项系数 0.0075,平滑速率 7。浏览器原生变换同时更新点击区域。工作台入场使用独立 translate 与投影叠加。 + +全局 SVG 合成滤镜使用 sRGB,覆盖三维、DOM 与弹窗。色散在画面边缘减弱至零以避免采样越界彩框,默认 20%;颗粒默认 20%、尺寸 20%,以 12Hz 更新,减少动态效果时冻结;暗角默认 20%。滑杆高值提供明显的校准范围。局部磨砂最大 24px 模糊和 0.65 主题底色不透明度,默认 55%。三种总开关仍默认关闭。详见 verification/VISUAL-EFFECTS.md。 + +用户配置的四边距作为原始 UI 框架的额外偏移,范围 −300~300px、默认 0,正值向内。通过 --stage-scale 抵消基准舞台缩放;布局先按新边界定位,再投影 UI 曲面,保持字号。背景、卡片及场景坐标标记继续使用完整视口。详情的轻移使用独立 translate,与曲面矩阵叠加。 + + +壁纸下边距最新规则:底部两排在 HUD 投影之后整体平移,相对间距固定;中部不因下边距移动或产生滚动区域。暗色新增可选的仅选中标签强调,其余 Index_Inlay 使用深灰;默认关闭保留原琥珀标签。 + + +## 超级性能模式与自定义画质 + +用户于 2026-09-10 要求低配壁纸模式与减少动态效果独立,并恢复 WE 原生自定义画质。新增「超级性能模式(保留动态效果)」开关,默认关闭;开启时以独立的有效画质覆盖已保存画质,不改减少动态效果、宿主帧率或音乐律动设置,关闭恢复当前预设/自定义配置。 + +超级模式:三维比例 60%、DPR 上限 1、总像素不超过 921600;关闭阵列阴影、AO、景深和 SMAA,直接输出主场景;背景阵列使用无折射、无清漆材质并隐藏小螺丝,选中/详情模型保留完整结构和低分辨率折射。全局屏幕滤镜暂时停用,文字磨砂改为无模糊底色,HUD 曲面与追踪继续运行。DOM 文字不降低分辨率。 + +WE 画质下拉增加「自定义」,仅此选择且超级模式关闭时显示九项控制:渲染比例、像素密度、抗锯齿、阴影、AO 采样、AO 分辨率、景深、透明材质分辨率、纹理过滤。连续参数用滑杆,离散参数用原生下拉。部分宿主回调保留其他参数;超级模式开启期间保留自定义更新,关闭恢复最新配置。完整证据见 verification/SUPER-PERFORMANCE.md。 + +## 三维资源开关与自定义壁纸 + +- 用户于 2026-09-10 授权在左下 SESSION 右侧增加 3D 开关:档案错峰下落退场后实际释放全部三维场景、查看器和 WebGL 资源;再次开启重新加载并从下方逆向升起,保留选档。关闭途中可反向恢复,减少动态效果时直接切换。 +- 3D 退场、关闭与载入期间隐藏游戏入口及模型专用入口;重新开启按原显示偏好恢复。2D 工作台和正文继续可用。 +- WE「入场与画面」增加可选自定义壁纸,默认不启用;用户选图后,仅在三维资源释放后淡入。重新载入成功时图片淡出,失败保持图片并允许重试。沿用原生实现,见 verification/THREE-RELEASE.md。 +- 用户明确保留三种音乐律动并列选择:原版、连续频谱、层叠流波。原版仍为默认,不再标为待定 A/B。 +- 修复壁纸自身交互声音触发音乐归齐:静音状态抑制本地音效引起的新频谱活动,已建立的音乐继续响应。全部归齐时取消标签强调,暗色仅选中强调随实际升降连续过渡,见 verification/SELECTION-STATE.md。 + +## 开场中央字体补充(2026-09-11) + +后续从 MyFonts 取得正式 Webfont 后,本机使用原始 WOFF2 原生字形;既有轮廓仅作为无本地许可包或加载失败的后备。复用逐字尺寸和基线,不重新调整已确认排版。授权字体单独存放并排除公开 Git。 + +中央访问、身份、请求、处理、授权提示采用 Novecento Sans Wide Normal,欢迎三行采用 Bold。按固定文案制作轮廓,保留原字距动画与逐字时间轴;处理提示字号 21.35px,顶部 527px,图标间距 10px(1920×1080 基准)。左上 RHINE LAB 按用户后续纠正改用 DemiBold,字号 50.75px、字距 1px,标题行固定高 48px,图形顶部偏移 1.425px;下方两行保留 MiSans 与既有位置。来源与验证见 verification/BOOT-LETTERING.md。 diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..eb6d6ba --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 LBEILC + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..48bc726 --- /dev/null +++ b/README.md @@ -0,0 +1,297 @@ +# RHINE LAB · ANALYSIS OS + +**把莱茵生命的终端,做成可以操作的三维界面。** + +**[在线体验 → rhine.lubeiluchen.cc](https://rhine.lubeiluchen.cc/)** + +iPhone 可用 Safari 打开在线版,通过“分享 → 添加到主屏幕”安装;从主屏幕图标进入可使用独立窗口。首次联网后,设置中显示“离线资源已就绪”即可离线浏览档案和模型。支持桌面不同比例、手机横竖屏和触摸操作。[安装与更新说明](docs/PWA.md) + +![莱茵生命终端:由透明档案盒构成的三维阵列](docs/media/archive.jpg) + +这是对《明日方舟》特别映像「莱茵生命:访问」终端界面的非官方复刻。从白底开场进入五列循环档案阵列,抽取一份档案,等待玻璃与正文解密,再进入独立查看器观察内部结构。 + +项目以原 PV 的 **5–40 秒**为主要视觉与动效参考,实际开场从 **6.76 秒的白色画面**开始;内部结构另参考约 **41 秒及 46–51 秒**的正面与多角度画面。检索、收藏、正文阅读、结构拆解与声音设置是可操作的扩展功能。 + +代码由 GPT-6 Astra 协助完成,模型通过 Blender MCP 制作。界面采用 **TypeScript + Three.js + Vite**,运行时实时渲染三维模型,开场由 DOM / SVG 与场景时间轴驱动。 + +[快速运行](#快速运行) · [界面与动效](#界面与动效) · [操作说明](#操作说明) · [源码与 Blender 工程下载](https://pan.quark.cn/s/762d9ee9dfc3) · [参考原 PV](https://www.bilibili.com/video/BV1rr4y1b7sz/) + +## Wallpaper Engine 壁纸与独立仓库 + +壁纸版已从本项目拆分为独立公共仓库,后续功能在 **[RhineLabWallpaper](https://github.com/LBEILC/RhineLabWallpaper)** 的 `main` 分支开发。本仓库继续维护网页版本;[原壁纸分支 codex/wallpaper-engine](https://github.com/LBEILC/RhineLabUI/tree/codex/wallpaper-engine) 保留迁移记录。需要最新壁纸源码,请前往独立仓库。 + +**[在 Steam 创意工坊订阅壁纸 → Rhine Lab · 莱茵生命交互桌面](https://steamcommunity.com/sharedfiles/filedetails/?id=3799142774)** + +![Wallpaper Engine 工作台预览](wallpaper/preview.gif) + +- **两种工作模式**:三维档案展示与桌面工作台,可显示时间日期、今日事项、日程倒计时、系统媒体信息和专注计时。 +- **壁纸交互**:音乐律动、呼吸效果、波纹接力小游戏,以及亮暗配色、玻璃模糊、HUD 曲面与视差。 +- **按需保留画面**:在 WE 属性中选择显示哪些组件;可关闭启动时加载 3D,播放 2D 开场后直接进入工作台,再从左下角手动载入模型。 +- **自定义图片**:关闭 3D 后可显示自行选择的壁纸,支持调整上下遮罩范围,设为 0 即关闭;时钟和媒体信息复用滚动数字与文字效果。 +- **宿主适配**:接入 WE 属性、音频响应、媒体信息、帧率及暂停通知,壁纸资源本地打包,壁纸构建不启用 PWA。 + +使用壁纸请通过上方创意工坊链接订阅,在 Wallpaper Engine 中应用并调整属性。需要自行构建时: + +```sh +git clone https://github.com/LBEILC/RhineLabWallpaper.git +cd RhineLabWallpaper +npm ci +npm run build:wallpaper +``` + +输出目录为 `release/wallpaper`,在 Wallpaper Engine 编辑器中打开其中的 `index.html`。系统媒体信息取决于播放器支持及 WE 媒体集成设置。更多细节见 [壁纸使用与开发说明](docs/WALLPAPER-ENGINE.md) 和 [创意工坊发布说明](docs/WORKSHOP-PUBLISH.md)。 + +以下为项目共用的网页与三维档案功能介绍。 + + +## 新版效果 + +- **亮暗配色**:设置中切换,卡片依次变色,详情与独立查看器同步适配。 +- **自适应阵列**:按实际镜头与屏幕范围布置、裁剪档案,改善宽屏边角露出尽头;开场铺满实际视口。 +- **超级性能模式**:设置中独立开启,降低三维渲染负担并保留动效,关闭恢复之前的画质。 +- **数字时钟**:页脚时、分、秒独立滚动,修复变换布局下部分数字动画中断。 + +整合范围与验证见 [主分支整合记录](verification/WEB-INTEGRATION.md)。 + +- **模型与正文同步解密**:对角解密线合拢、保持并收束,盖板自上而下由磨砂变清晰;右侧文档的遮挡条随之退开,露出标题、资料字段与正文。 +- **可看清的双环内构**:双环、连接带与白色/橙色部件封装在盖板与基板之间。顶边黄色方块、两处螺丝和盖板后的刻线补全了外壳细节。 +- **清晰/磨砂切换**:独立查看器可以随时改变玻璃状态,保留当前视角和拆解位置;支持平滑缩放、平移及复位。 +- **滚动文字与编号**:档案标题、分类、权限标签及编号连续滚动,快速输入衔接最新选择。 +- **重新校准的开场**:逐字输入、圆环绕行、连续 Logo 笔画、身份验证和欢迎转场,按原片逐帧修订轨迹与节奏。 +- **声音与画质设置**:玻璃交互音、系统电子音与三轨循环配乐;音效和音乐可独立开关、调节音量。画质提供四档预设及精细设置。 + +## 界面与动效 + +以下截图与动图于 **2026-09-09** 重新采集,均来自当前版本的实际浏览器运行。截图为 **1600 × 900**,GIF 为 **8–12 fps**、原速播放;压缩后的帧率与颜色不代表实时渲染质量。采集版本与复现步骤见 [素材说明](docs/media/README.md)。 + +### 抽取、解密与阅读 + +档案竖直升起,镜头靠近并转向详情构图。解密时玻璃与文档一起揭示;完成后可阅读概述、研究记录及访问日志,也可以收藏或导出 UTF-8 文本。 + +![档案抽取与同步解密:对角线收束,玻璃从上向下变清晰,正文遮挡退开](docs/media/decryption.gif) + +| 解密完成 · 清晰内构与档案概述 | 研究记录 · 正文阅读 | +| --- | --- | +| [![解密后的档案:左侧可见双环内构,右侧显示机构资料](docs/media/detail.jpg)](docs/media/detail.jpg) | [![档案研究记录页签](docs/media/research.jpg)](docs/media/research.jpg) | + +### 清晰内构与磨砂玻璃 + +在 360° 查看器中比较两种玻璃状态。切换不会重置镜头或拆解状态,返回详情后仍保留已解密状态。 + +| 清晰 · 双环与连接带 | 磨砂 · 轻柔折射 | +| --- | --- | +| [![清晰玻璃下的档案内部结构](docs/media/viewer-clear.jpg)](docs/media/viewer-clear.jpg) | [![相同视角下的磨砂玻璃](docs/media/viewer-frosted.jpg)](docs/media/viewer-frosted.jpg) | + +
+查看动图:清晰/磨砂连续切换 + +![玻璃由清晰过渡到磨砂,再恢复清晰](docs/media/glass-motion.gif) + +
+ +### 360° 旋转、拆解与重组 + +紧固件、透明盖板、折射环组、光学核心、信息基板、背板与框架按六组展开。拆解后仍可旋转、平移和缩放,观察部件之间的关系,再一键重组。 + +[![新版档案模型:六组结构分层展开](docs/media/assembly.jpg)](docs/media/assembly.jpg) + +
+查看动图:模型拆解、旋转与重组 + +![操作动图:档案盒从完整状态拆开,旋转观察后连续重组](docs/media/assembly-motion.gif) + +
+ +### 循环阵列与滚动标题 + +五类、每类八份,共 **40 份档案**。上下翻阅、左右切列均可持续循环;切回某列时保留上次选择。选中抬起与阵列波浪同时开始,标题和编号跟随输入滚动;返回阵列时,档案先转正再下降。 + +
+查看动图:切列、翻阅与连续文字滚动 + +![循环切换档案,标题、编号、分类及刻度同步更新](docs/media/browse.gif) + +
+ +### 白底开场 + +从终端逐字输入到圆环、Logo 绘制,再进入身份接入与权限验证。可以重播,也可以跳过开场直接进入阵列。 + +[![白底开场中的莱茵生命标志与身份接入文字](docs/media/boot.jpg)](docs/media/boot.jpg) + +
+查看动图:新版开场片段 + +![新版白底开场:逐字输入、圆环和连续标志绘制、身份验证与欢迎转场](docs/media/boot-motion.gif) + +
+ +### 检索、声音与画质 + +检索支持编号、标题、英文名、科室、相关人物与分类筛选。收藏和设置保存在当前浏览器中。音效区分档案的玻璃碰触与系统操作的电子反馈,背景配乐随开场、阵列、详情和查看器调整三轨比例。 + +| 档案索引 · 关键词与分类筛选 | 系统设置 · 声音与画质 | +| --- | --- | +| [![输入莱茵关键词后的档案检索结果](docs/media/search.jpg)](docs/media/search.jpg) | [![独立音效和音乐音量、减少动态效果与画质预设](docs/media/settings.jpg)](docs/media/settings.jpg) | + +GIF 不含声音。可单独[试听原创配乐「观测室」](public/audio/observatory-preview.mp3),完整声音效果请启动应用体验。浏览器可能需要一次点击或按键才允许播放音频。 + +## 快速运行 + +需要 **Node.js 22.12 或更高版本**(可使用 Node.js 24),以及支持 WebGL 2 的现代桌面浏览器。首次安装依赖需要网络;应用不需要 API Key,也不需要启动后端服务。 + +### 获取项目 + +```sh +git clone https://github.com/LBEILC/RhineLabUI.git +cd RhineLabUI +``` + +也可以从 GitHub 的 **Code → Download ZIP** 下载当前源码,或获取[夸克项目包](https://pan.quark.cn/s/762d9ee9dfc3)。夸克包是 **2026-09-08 的打包快照**,包含源码、运行模型与 Blender 源工程;后续更新以本仓库为准。 + +### 安装并启动 + +```sh +npm ci +npm run dev +``` + +打开终端显示的地址,通常为 `http://127.0.0.1:5173/`。如果端口被占用,以终端实际输出为准。 + +Windows 用户安装 Node.js 并解压项目后,也可以双击 [`启动终端.cmd`](启动终端.cmd):首次运行会安装依赖,然后启动本地服务并打开浏览器。 + +### 构建与预览 + +```sh +npm run build +npm run preview +``` + +生产文件输出到 `dist/`,可以交给静态 HTTP 服务托管。请通过服务地址访问,不要直接双击 `dist/index.html`。 + +## 操作说明 + +### 终端与档案 + +| 操作 | 效果 | +| --- | --- | +| 开场中按 `Enter` / `Esc`,或点击 `ENTER SYSTEM` | 资源就绪后进入交互阵列 | +| `←` / `→` | 切换档案类别,首尾循环 | +| `↑` / `↓` | 翻阅同类档案,首尾循环 | +| `Enter`、`ACCESS FILE` 或文件编号 | 读取当前档案 | +| 在详情模型上拖动 | 档案获得净空后,旋转观察 | +| `/` 或 `ARCHIVE INDEX` | 打开检索,可搜索编号、标题、英文名、科室、负责人和分类 | +| `SAVE ARCHIVE` / `SAVED` | 收藏当前档案 / 查看收藏 | +| `EXPORT` | 下载当前档案的文本文件 | +| `Esc` | 关闭当前弹窗,或从详情返回阵列 | + +### 独立模型查看器 + +在详情页点击 **「360° 查看文档模型」** 进入。 + +| 操作 | 效果 | +| --- | --- | +| 鼠标拖动 | 环绕旋转模型 | +| 滚轮、`+` / `−` | 平滑缩放 | +| 方向键 | 平移观察位置 | +| 「复位视角」或 `Home` | 平滑恢复初始观察位置 | +| 「清晰」 / 「磨砂」 | 切换玻璃状态,保留视角和拆解位置 | +| 「拆解档案」 / 「一键重组」 | 展开六组部件 / 连续收回 | +| `Esc` 或「返回档案」 | 关闭查看器,返回原档案 | + +### 显示与偏好 + +布局以 **1920 × 1080** 为基准等比例适应窗口,主要面向桌面与横向屏幕。设置页提供音效与背景音乐的独立开关、独立音量,以及减少动态效果、画质、全屏和重新播放开场。收藏和偏好保存在当前浏览器中。 + +首次载入需要加载字体与 GLB 模型。项目保留了四份官方 MiSans WOFF2,合计约 19.7 MB,按实际使用加载。画质预设为**性能、原始、高、极高**,默认使用原始;精细设置可调整渲染比例、像素密度、抗锯齿、纹理过滤、透明材质分辨率、阴影、环境遮蔽及景深。运行不够流畅时可选性能档;需要简化镜头、文字与揭示动画时,可启用减少动态效果。 + +## 工程结构 + +| 目录或文件 | 内容 | +| --- | --- | +| [`src/main.ts`](src/main.ts) | 页面状态、档案阅读、检索、收藏与快捷键 | +| [`src/boot.ts`](src/boot.ts)、[`src/boot-motion.ts`](src/boot-motion.ts) | 开场界面与逐帧时间轴 | +| [`src/scene.ts`](src/scene.ts)、[`src/archive-loop.ts`](src/archive-loop.ts) | Three.js 场景、循环阵列、抽取与归位 | +| [`src/model-viewer.ts`](src/model-viewer.ts) | 独立模型查看器与拆解动画 | +| [`src/decryption.ts`](src/decryption.ts)、[`src/document-decryption.ts`](src/document-decryption.ts) | 模型解密轨迹与正文同步揭示 | +| [`src/audio.ts`](src/audio.ts)、[`public/audio/`](public/audio/) | 交互音效、三轨配乐与音源记录 | +| [`src/render-quality.ts`](src/render-quality.ts)、[`src/quality-renderer.ts`](src/quality-renderer.ts) | 画质预设与渲染管线 | +| [`content/archives.json`](content/archives.json) | 页面与下载共用的五类、40 份档案数据 | +| [`src/data.ts`](src/data.ts) | 档案类型与阵列位置映射 | +| [`public/assets/`](public/assets/) | 运行所需的 GLB 模型 | +| [`public/archives/`](public/archives/) | 导出的档案文本;启动和构建前自动生成 | +| [`art/`](art/) | Blender 源文件、建模与审阅脚本 | +| [`scripts/`](scripts/) | 档案导出与行为检查 | +| [`reference/`](reference/)、[`verification/`](verification/) | 开发对照工具与分阶段验证记录 | +| [`docs/media/`](docs/media/) | README 截图与动图 | +| [`DESIGN.md`](DESIGN.md) | 视觉、相机、材质与运动约束 | + +原片时间轴使用 160 个阵列位置;交互模式使用固定的可见窗口与外围卡片补位,让有限的档案内容可以持续循环。 + +### 修改与复核 + +修改档案内容从 [`content/archives.json`](content/archives.json) 入手,字段与操作步骤见 [档案修改说明](content/README.md)。`npm run dev` 与 `npm run build` 会先校验数据,再更新 `public/archives/` 中的文本导出;开发过程中修改数据后,可执行 `npm run export:archives` 同步下载文件。`npm run check:content` 检查数据规则与导出一致性。 + +```sh +node scripts/check-motion.mjs +node scripts/check-loop.mjs +node scripts/check-appearance.mjs +node scripts/check-assembly.mjs +node scripts/check-decryption.mjs +node scripts/check-shell.mjs +node scripts/check-internal-optics.mjs +node scripts/check-quality.mjs +``` + +这些脚本检查运动、循环位置、外观、装配结构、解密轨迹、外壳、内构与画质参数。视觉效果仍需在浏览器中实际查看,尤其是快速切换、模型归位、文档揭示及查看器进出过渡。 + +| 本地调试路径 | 用途 | +| --- | --- | +| `/?scene=archive` | 直接进入档案阵列 | +| `/?scene=detail` | 直接进入档案详情 | +| `/?time=28&freeze=1` | 固定在参考时间轴的指定时刻 | +| `/reference/review.html`、`/reference/boot-review.html` | 原片与复刻对照工具 | +| `/reference/decryption-review.html` | 玻璃解密逐帧对照 | +| `/reference/document-decryption-check.html` | 正文同步解密与布局检查 | +| `/reference/boot-audio.html` | 完整开场声音试听 | + +原 PV 不随仓库分发。使用视频对照工具时,需要自行准备对应参考视频;正常运行应用不依赖它。 + +### Blender 源工程 + +| 文件 | 用途 | +| --- | --- | +| [`art/rhine-archive.blend`](art/rhine-archive.blend) | 档案盒基础模型与审阅灯光 | +| [`art/archive-assembly.blend`](art/archive-assembly.blend) | 可按六组结构拆解的模型 | +| [`art/build_archive.py`](art/build_archive.py) | 生成基础模型与 GLB | +| [`art/build_assembly.py`](art/build_assembly.py) | 生成拆解模型与 GLB | +| [`art/internal_architecture.py`](art/internal_architecture.py) | 当前双环内构与连接带 | +| [`art/shell_reference_details.py`](art/shell_reference_details.py) | 顶边方块、螺丝及盖板后刻线 | +| [`art/setup_studio.py`](art/setup_studio.py) | 配置资产审阅灯光与相机 | + +普通运行直接使用现有 GLB 即可,无需安装 Blender。重新建模时,可在 Blender 的脚本环境中通过 `runpy.run_path()` 执行对应脚本,或通过 Blender MCP 调用。脚本根据自身位置确定项目目录,重新生成会更新对应模型输出。 + +## 参考与资源说明 + +参考作品为《明日方舟》特别映像「莱茵生命:访问」:[BV1rr4y1b7sz](https://www.bilibili.com/video/BV1rr4y1b7sz/)。本项目与官方制作方无隶属关系,原 PV、相关名称、标志与设定的权利归各自权利人所有。原片未展示的档案摘要、日期、研究记录等属于本项目的扩展演示内容。 + +模型为重新制作;实时折射、景深、灯光与局部细节和原 PV 仍有差异。身份验证画面是演示状态机,不连接真实身份或业务服务。 + +- **MiSans**:使用小米官方字体文件,保留[字体许可](public/fonts/MiSans-license.pdf)及字体目录内的版权说明,设置页也提供署名与许可入口。 +- **Rolling Number**:用于编号和文字滚动,许可见 [`public/licenses/rolling-number.txt`](public/licenses/rolling-number.txt)。 +- **声音**:三轨配乐为本项目程序编配;逐字输入使用原 PV 的三个 38ms 短音,来源与处理记录见 [音频说明](public/audio/README.md)。原片短音及其衍生片段不纳入原创配乐的 MIT 授权声明。 +- **其他依赖**:各自遵循其原有许可。源码公开不改变第三方资源的权利。 + +源码包包含运行代码、模型、Blender 工程、说明与验证脚本,不包含依赖目录、本机缓存、原 PV 或完整录制素材。 + +## 开源许可 + +本项目自行编写且有权授权的程序代码、建模脚本及配套技术文档采用 [MIT License](LICENSE),版权署名为 **Copyright (c) 2026 LBEILC**。你可以使用、修改、分发这些内容,也可以将其用于商业或闭源项目;分发代码或其重要部分时,须保留版权声明和许可证。软件按原样提供,不作担保,具体以许可证全文为准。 + +MIT 授权不覆盖第三方权利或自动覆盖仓库内全部素材: + +- 《明日方舟》及莱茵生命相关名称、标志、设定、原 PV 和原作视觉设计,以及它们在模型、界面、截图或演示文本中的呈现,不因本项目公开而获得额外授权。本项目无法代替相应权利人授予这些权利。 +- Blender / GLB 模型、图像、动图等非代码资产未另行声明为 MIT;建模脚本采用 MIT 不表示脚本生成的原作相关视觉内容也已获授权。 +- MiSans、Rolling Number 及其他第三方依赖继续遵循各自的许可证和版权声明,见上方「参考与资源说明」。 + +复用代码时,请根据用途处理涉及的第三方素材与标志。GitHub 当前源码包已包含本许可证;上方夸克链接为早期打包快照,未包含本次新增的项目许可证文件,最新许可说明以本仓库为准。 diff --git a/archive.config.json b/archive.config.json new file mode 100644 index 0000000..7d2dad3 --- /dev/null +++ b/archive.config.json @@ -0,0 +1,7 @@ +{ + "root": "E:\\归档", + "port": 43117, + "exclude": [], + "indexFile": "public/archive-index.json", + "logFile": "00_索引\\操作日志.jsonl" +} diff --git a/art/archive-assembly.blend b/art/archive-assembly.blend new file mode 100644 index 0000000..94ef604 Binary files /dev/null and b/art/archive-assembly.blend differ diff --git a/art/archive-studio.png b/art/archive-studio.png new file mode 100644 index 0000000..0888d7f Binary files /dev/null and b/art/archive-studio.png differ diff --git a/art/build_archive.py b/art/build_archive.py new file mode 100644 index 0000000..7288229 --- /dev/null +++ b/art/build_archive.py @@ -0,0 +1,208 @@ +import bpy, math, os +from mathutils import Vector +from pathlib import Path + +ROOT = str(Path(__file__).resolve().parents[1]) +scene = bpy.data.scenes.new('Rhine_Archive_Work') +bpy.context.window.scene = scene +for old in list(bpy.data.scenes): + if old != scene and old.name.startswith('Rhine_Archive_Asset'): + for obj in list(old.objects): + if len(obj.users_scene)==1:bpy.data.objects.remove(obj,do_unlink=True) + bpy.data.scenes.remove(old) +scene.name='Rhine_Archive_Asset' +for m in list(bpy.data.materials): + if m.users==0:bpy.data.materials.remove(m) + +def material(name, color, rough=.3, metal=0, transmission=0): + m=bpy.data.materials.new(name); m.diffuse_color=(*color,1); m.use_nodes=True + p=m.node_tree.nodes.get('Principled BSDF') + p.inputs['Base Color'].default_value=(*color,1) + p.inputs['Roughness'].default_value=rough + p.inputs['Metallic'].default_value=metal + p.inputs['Transmission Weight'].default_value=transmission + p.inputs['IOR'].default_value=1.46 + return m + +shell=material('Frosted_Polymer',(.985,.975,.963),.36,0,.78) +edge=material('Ivory_Edges',(.94,.916,.892),.28,.02,.72) +core=material('Internal_Ceramic',(.74,.705,.68),.52,.06) +metal=material('Titanium_Fasteners',(.58,.60,.61),.19,.82) +gold=material('Champagne_Index',(.64,.46,.29),.33,.48) +paper=material('Printed_Label',(.91,.89,.84),.65) +diffuser=material('Optical_Diffuser',(.925,.902,.881),.67,0,0) +optics=material('Subsurface_Optics',(.70,.675,.66),.39,.12) +optical_edge=material('Optical_Edges',(.94,.92,.90),.3,.03,.45) +ink=material('Carbon_Ink',(.025,.026,.023),.75) + +def cube(name, loc, size, mat, bevel=.015): + bpy.ops.mesh.primitive_cube_add(size=1, location=loc) + o=bpy.context.object; o.name=name; o.dimensions=size + bpy.ops.object.transform_apply(location=False, rotation=False, scale=True) + o.data.materials.append(mat) + if bevel: + m=o.modifiers.new('Precision radiused edge','BEVEL'); m.width=bevel; m.segments=3 + bpy.context.view_layer.objects.active=o; bpy.ops.object.modifier_apply(modifier=m.name) + o.modifiers.new('Weighted corner normals','WEIGHTED_NORMAL') + return o + +def torus(name,x,z,radius,tube,mat,y=-.091): + bpy.ops.mesh.primitive_torus_add(major_radius=radius,minor_radius=tube,major_segments=64,minor_segments=10,location=(x,y,z),rotation=(math.pi/2,0,0)) + o=bpy.context.object; o.name=name; o.data.materials.append(mat) + for p in o.data.polygons:p.use_smooth=True + return o + +def text(name, body,x,z,size,mat=ink): + c=bpy.data.curves.new(name,'FONT'); c.body=body;c.size=size;c.extrude=.0002;c.space_character=1.05 + o=bpy.data.objects.new(name,c);scene.collection.objects.link(o) + o.location=(x,-.123,z);o.rotation_euler=(math.pi/2,0,0);c.materials.append(mat) + return o + +def annular_profile(name, x, z, profile, mat, segments=128, start=0, end=2*math.pi, sharp=False): + # Closed revolved cross-section: a shallow moulded lens, not a round tube. + vertices=[]; faces=[]; n=len(profile) + closed=abs(end-start-2*math.pi)<1e-6 + segments=max(8,math.ceil(segments*(end-start)/(2*math.pi))) + rows=segments if closed else segments+1 + for i in range(rows): + a=start+(end-start)*i/segments + for r,y in profile: vertices.append((x+r*math.cos(a),y,z+r*math.sin(a))) + for i in range(segments): + for j in range(n): + faces.append((i*n+j,((i+1)%rows)*n+j,((i+1)%rows)*n+(j+1)%n,i*n+(j+1)%n)) + if not closed: + faces.extend([tuple(reversed(range(n))),tuple(segments*n+j for j in range(n))]) + mesh=bpy.data.meshes.new(name);mesh.from_pydata(vertices,[],faces);mesh.update() + obj=bpy.data.objects.new(name,mesh);scene.collection.objects.link(obj);mesh.materials.append(mat) + # The clockwise section above yields outward normals, including the bore. + for p in mesh.polygons:p.use_smooth=len(p.vertices)==4 + if sharp: + # Keep each section edge hard, but interpolate around the circumference. + # Flat shading alone would facet the circle; all-smooth shading balloons + # the roof/wall junction into a rounded tube. + normals=[] + for face in mesh.polygons: + if face.index>=segments*n: + face.use_smooth=False + normals.extend([tuple(face.normal)]*len(face.loop_indices)) + continue + j=face.index%n + dr=profile[(j+1)%n][0]-profile[j][0] + dy=profile[(j+1)%n][1]-profile[j][1] + for loop in face.loop_indices: + row=mesh.loops[loop].vertex_index//n + angle=start+(end-start)*row/segments + normal=Vector((-dy*math.cos(angle),dr,-dy*math.sin(angle))).normalized() + normals.append(tuple(normal)) + mesh.normals_split_custom_set(normals) + return obj + +def channel(name, points, depth, radius, mat): + curve=bpy.data.curves.new(name,'CURVE');curve.dimensions='3D' + curve.resolution_u=8;curve.bevel_depth=radius;curve.bevel_resolution=2 + spline=curve.splines.new('POLY');spline.points.add(len(points)-1) + for point,(x,z) in zip(spline.points,points):point.co=(x,depth,z,1) + obj=bpy.data.objects.new(name,curve);scene.collection.objects.link(obj);curve.materials.append(mat) + return obj + +front_cover=cube('Front frosted optical cover',(0,-.095,1.85),(5,.016,3.7),shell,.007) +cube('Rear translucent carrier',(0,.055,1.85),(4.97,.02,3.68),edge,.009) +cube('Information substrate',(0,.025,1.86),(4.80,.012,3.47),diffuser,.006) +for z in [.028,3.672]:cube('Polished perimeter rail',(0,-.021,z),(4.95,.155,.034),edge,.009) +for x in [-2.476,2.476]:cube('Polished perimeter rail',(x,-.021,1.85),(.034,.155,3.66),edge,.009) +# Wide optical cavities sit BEHIND the frosted cover. Their lenticular profiles +# are shallow; no torus protrudes from the exterior face. +for x,z,r in [(-.44,1.92,.79),(1.13,2.48,.435)]: + width=.145 if r>.5 else .102 + annular_profile('Embedded optical cavity',x,z,[ + (r-width,.016),(r+width,.016),(r+width+.012,.003), + (r+width,-.014),(r+width-.022,-.027),(r+.032,-.040), + (r-.012,-.039),(r-.045,-.052),(r-width+.025,-.054), + (r-width,-.036)],optics) + annular_profile('Subsurface refractive shoulder',x,z,[ + (r+.028,-.031),(r+width+.025,-.009),(r+width+.029,-.020), + (r+width+.012,-.032),(r+.056,-.053),(r+.028,-.049)],optical_edge) + annular_profile('Inner optical bevel',x,z,[ + (r-width-.012,-.017),(r-width+.036,-.041), + (r-width+.041,-.057),(r-width+.023,-.064), + (r-width+.004,-.061),(r-width-.012,-.036)],optical_edge) + for offset,tube,y in [(width+.009,.006,-.031),(.030,.007,-.054),(-width+.022,.006,-.064)]: + o=torus('Concentric optical machining',x,z,r+offset,tube,optical_edge,y) + o.scale.z=.42 + if r<.5: + annular_profile('Embedded amber annulus',x,z,[ + (r-.170,-.047),(r-.070,-.047),(r-.067,-.060), + (r-.077,-.071),(r-.156,-.071),(r-.170,-.060)],gold) +cube('Serial label',(-1.36,-.099,3.04),(.99,.02,.41),paper,.003) +cube('Label top rule',(-1.36,-.116,3.23),(.98,.004,.008),ink,0) +cube('Label bottom rule',(-1.36,-.116,2.847),(.98,.004,.005),ink,0) +text('Company label','RHINE LAB, LLC.',-1.825,3.105,.105) +text('Database label','INTERNAL DATABASE',-1.825,3.017,.042,core) +text('Serial number','NO.001',-1.825,2.875,.148) +text('Information label','INFO',-.99,3.075,.076) +text('Symbol label','+ / -',-.99,2.9,.095) +for i in range(16): + o=cube('Laser etched vent',(1.04+i*.054,-.111,.57),(.023,.009,.1),core,.003);o.rotation_euler.y=.4 +for i in range(25):cube('Calibration mark',(-2.23,-.108,.61+i*.052),(.035 if i%5 else .075,.005,.006),core,0) +text('Edge inscription','R H I N E L A B',-1.81,.28,.063,core) +for z,x1,x2 in [(3.36,-1.8,.2),(3.36,.4,1.55),(.4,-1.45,1.75)]: + cube('Engraved circuit trace',((x1+x2)/2,-.108,z),(x2-x1,.004,.005),core,.002) +# Two sides of a shallow pressed channel, observed in the 37–39 second close-up. +# Keep the entire channel behind the front cover to avoid coplanar stippling. +top=[(-2.27,3.06),(-2.27,3.30),(-2.10,3.45),(-1.72,3.45),(-1.61,3.51), + (-.65,3.51),(-.55,3.46),(.37,3.46),(.43,3.52),(.49,3.46), + (1.08,3.46),(1.17,3.52),(1.20,3.50),(1.13,3.42), + (1.79,3.42),(1.89,3.51),(2.19,3.51),(2.29,3.41),(2.29,3.03)] +channel('Moulded circuit channel shadow',top,-.071,.005,core) +channel('Moulded circuit channel lip',[(x,z-.018) for x,z in top],-.075,.008,optical_edge) +perimeter=[(-2.18,2.85),(-2.23,2.72),(-2.23,.43),(-2.12,.30), + (2.08,.30),(2.24,.44),(2.24,2.97)] +channel('Moulded inner perimeter',perimeter,-.067,.008,optical_edge) +for z in [.080,3.620]: + cube('Carrier mating seam',(0,.002,z),(4.82,.012,.010),optical_edge,.003) +for x in [-2.420,2.420]: + cube('Carrier mating seam',(x,.002,1.85),(.010,.012,3.54),optical_edge,.003) +# Small raised pads under the diagonal calibration vents. +for i in range(16): + cube('Moulded vent footing',(1.04+i*.054,-.071,.435),(.015,.014,.018),optical_edge,.004) + +detail_script=Path(ROOT)/'art/clear_reference_details.py' +exec(compile(detail_script.read_text(encoding='utf-8-sig'),str(detail_script),'exec')) + +# Convert text, bake modifiers, and group by material for efficient instancing. +bpy.ops.object.select_all(action='SELECT') +for o in list(scene.objects): + bpy.context.view_layer.objects.active=o + if o.type in ['FONT','CURVE']:bpy.ops.object.convert(target='MESH') + for m in list(o.modifiers): + try:bpy.ops.object.modifier_apply(modifier=m.name) + except:pass +for mat in list(dict.fromkeys(o.data.materials[0] for o in scene.objects if o.type=='MESH' and o.data.materials)): + bpy.ops.object.select_all(action='DESELECT') + obs=[o for o in scene.objects if o.type=='MESH' and o.data.materials and o.data.materials[0]==mat] + if not obs:continue + for o in obs:o.select_set(True) + bpy.context.view_layer.objects.active=obs[0];bpy.ops.object.join() + o=bpy.context.object;o.name=mat.name + scene.cursor.location=(0,0,0);bpy.ops.object.origin_set(type='ORIGIN_CURSOR') + # Bake orientation to make every exported group share the same coordinate frame. + bpy.ops.object.transform_apply(location=False,rotation=True,scale=True) +# Annotated reference: the visible end face occupies roughly half a row pitch. +for obj in scene.objects: + if obj.type=='MESH': + if obj.data.materials[0].name.startswith(('Optical_Glass_', 'Optical_Bridge_Glass', 'Amber_Optical_Inlay')): + bpy.context.view_layer.objects.active=obj + bpy.ops.object.select_all(action='DESELECT');obj.select_set(True) + obj.scale.y *= 2.0 + bpy.ops.object.transform_apply(location=False,rotation=False,scale=True) + else: + for vertex in obj.data.vertices:vertex.co.y *= 2.0 +bpy.ops.object.select_all(action='SELECT') +export_path=Path(ROOT)/'art/.cache/archive-cassette.glb' +export_path.parent.mkdir(parents=True,exist_ok=True) +bpy.ops.export_scene.gltf(filepath=str(export_path),export_format='GLB',use_selection=True,use_active_scene=True,export_apply=True) +os.replace(str(export_path),ROOT+'/public/assets/archive-cassette.glb') +bpy.data.libraries.write(ROOT+'/art/rhine-archive.blend', {scene}, fake_user=True) +print('Exported archive cassette:',len(scene.objects),'material groups') + + diff --git a/art/build_assembly.py b/art/build_assembly.py new file mode 100644 index 0000000..9d5fad1 --- /dev/null +++ b/art/build_assembly.py @@ -0,0 +1,57 @@ +import bpy +from pathlib import Path +BUILD_ROOT = Path(__file__).resolve().parents[1] +ROOT = BUILD_ROOT +source = (ROOT/'art/build_archive.py').read_text(encoding='utf-8') +prefix = source.split('# Convert text, bake modifiers')[0] +prefix = prefix.replace("scene = bpy.data.scenes.new('Rhine_Archive_Work')", "scene = bpy.data.scenes.new('Rhine_Assembly_Work')") +prefix = prefix.replace("if old != scene and old.name.startswith('Rhine_Archive_Asset'):", "if False:") +prefix = prefix.replace("scene.name='Rhine_Archive_Asset'", "scene.name='Rhine_Assembly_Asset'") +exec(compile(prefix, str(ROOT/'art/build_archive.py'), 'exec')) +ROOT = BUILD_ROOT +def part_for(name): + if name.startswith(('Rear translucent carrier', 'Polished perimeter rail', 'Ivory spine cap', 'Carrier mating seam')): + return 'carrier' + if name.startswith('Information substrate'): return 'substrate' + if name.startswith(('Embedded optical cavity', 'Embedded amber annulus', 'Folded optical tab')): return 'optical-core' + if name.startswith(('Subsurface refractive shoulder', 'Inner optical bevel', 'Concentric optical machining', 'Optical ribbon')): + return 'optical-lenses' + if name.startswith(('Countersunk washer', 'Machined screw', 'Screw slot')): return 'fasteners' + return 'cover' +for obj in list(scene.objects): + obj['assemblyPart'] = part_for(obj.name) + bpy.context.view_layer.objects.active = obj + bpy.ops.object.select_all(action='DESELECT') + obj.select_set(True) + if obj.type in ['FONT','CURVE']: bpy.ops.object.convert(target='MESH') + for mod in list(obj.modifiers): + bpy.ops.object.modifier_apply(modifier=mod.name) + if obj.data.materials[0].name.split('.')[0] == 'Carbon_Ink': + bpy.data.objects.remove(obj, do_unlink=True) +pairs = {} +for obj in scene.objects: + key = (obj['assemblyPart'], obj.data.materials[0].name) + pairs.setdefault(key, []).append(obj) +for (part, surface), objects in pairs.items(): + bpy.ops.object.select_all(action='DESELECT') + for obj in objects: obj.select_set(True) + bpy.context.view_layer.objects.active = objects[0] + bpy.ops.object.join() + obj = bpy.context.object + obj.name = part+'__'+surface.split('.')[0] + obj['assemblyPart'] = part + scene.cursor.location = (0,0,0) + bpy.ops.object.origin_set(type='ORIGIN_CURSOR') + bpy.ops.object.transform_apply(location=False, rotation=True, scale=True) + if surface.startswith(('Optical_Glass_', 'Optical_Bridge_Glass', 'Amber_Optical_Inlay')): + obj.scale.y *= 2.0 + bpy.ops.object.transform_apply(location=False, rotation=False, scale=True) + else: + for vertex in obj.data.vertices:vertex.co.y *= 2.0 +bpy.ops.object.select_all(action='SELECT') +export_path=ROOT/'art/.cache/archive-assembly.glb' +export_path.parent.mkdir(parents=True,exist_ok=True) +bpy.ops.export_scene.gltf(filepath=str(export_path), export_format='GLB', use_selection=True, use_active_scene=True, export_apply=True, export_extras=True) +os.replace(str(export_path),str(ROOT/'public/assets/archive-assembly.glb')) +bpy.data.libraries.write(str(ROOT/'art/archive-assembly.blend'), {scene}, fake_user=True) +print('Assembly exported:', len(scene.objects), 'meshes, parts:', sorted({o['assemblyPart'] for o in scene.objects})) diff --git a/art/clear_reference_details.py b/art/clear_reference_details.py new file mode 100644 index 0000000..ea203de --- /dev/null +++ b/art/clear_reference_details.py @@ -0,0 +1,47 @@ +# Detail reconstruction from the clear 40–42 second reference. +# Executed by build_archive.py in the same scene; assembly uses this identical source. +# All dimensions below are BEFORE the shared x2 thickness bake. +from pathlib import Path + +obsolete=('Embedded optical cavity','Subsurface refractive shoulder','Inner optical bevel', + 'Concentric optical machining','Embedded amber annulus','Moulded circuit channel', + 'Moulded inner perimeter','Moulded vent footing','Laser etched vent', + 'Calibration mark','Edge inscription','Engraved circuit trace') +for obj in list(scene.objects): + if obj.name.startswith(obsolete): bpy.data.objects.remove(obj,do_unlink=True) + +emboss=material('Moulded_Lettering',(.81,.786,.75),.24,.13) +architecture_script=Path(ROOT)/'art/internal_architecture.py' +exec(compile(architecture_script.read_text(encoding='utf-8'),str(architecture_script),'exec')) + +# Rebuild the moulding from normalized positions on the complete 41.0-second face. +def face_point(px,py):return ((px-432)/1022*5-2.5,(1018-py)/676*3.7) +def mould(name,pixels,depth=-.074,radius=.004,mat=core): + return channel(name,[face_point(x,y) for x,y in pixels],depth,radius,mat) + +shell_script=Path(ROOT)/'art/shell_reference_details.py' +exec(compile(shell_script.read_text(encoding='utf-8'),str(shell_script),'exec')) + +# Fine, lightly recessed rectangular backing routes, beneath the ring structures. +for route in [[(681,470),(681,899),(1061,899),(1061,821),(1310,821),(1310,464),(1028,464),(1028,406)]]: + mould('Information substrate fine route',route,.014,.0018,core) + +# Embossed vertical company inscription belongs to the inner cover, not the ink label. +inscription=text('Moulded vertical lettering','RHINE LAB, LLC.',0,0,.155,emboss) +# Text baseline follows Blender local X; rotate it down the left edge in the X/Z plane. +inscription.location=(-2.27,-.078,2.29) +inscription.rotation_euler=(math.pi/2,math.pi/2,0) +inscription.data.extrude=.003;inscription.data.bevel_depth=.0015;inscription.data.bevel_resolution=1 +inscription.data.space_character=1.10 +# Two small registration pads under the floating tabs. +for x in [-1.61,-1.45]:cube('Information substrate registration pad',(x,-.029,.53),(.014,.009,.038),gold,.002) + +# Lower-right circular service detail with a slanted slotted rail, seen under haze. +px,pz=face_point(1312,923) +for radius,depth,mat in [(.145,-.070,optical_edge),(.120,-.065,core),(.083,-.072,optical_edge)]: + torus('Moulded service socket',px,pz,radius,.0055,mat,depth).scale.z=.45 +annular_profile('Moulded service socket center',px,pz,[(.021,-.040),(.043,-.040),(.048,-.060),(.035,-.074),(.021,-.068)],gold,32) +for i in range(11): + x=1.03+i*.063 + channel('Moulded diagonal vent',[(x-.035,.38),(x+.030,.53)],-.075,.008,optical_edge) + channel('Moulded diagonal vent recess',[(x-.029,.38),(x+.036,.53)],-.071,.003,core) diff --git a/art/internal_architecture.py b/art/internal_architecture.py new file mode 100644 index 0000000..7913e5e --- /dev/null +++ b/art/internal_architecture.py @@ -0,0 +1,107 @@ +"""User-selected first internal model, restored from revision 89c7742. + +Keep the current outer case and reveal animation. This shared Blender source +restores the annular lenses, inner hubs, muted orange and paired ribbons. +""" +amber_inlay=material('Amber_Optical_Inlay',(.48,.18,.052),.32,.28) +film=material('Optical_Film',(.79,.755,.712),.30,.06) +film_edge=material('Optical_Film_Edge',(.57,.545,.515),.27,.20) +clip_metal=material('Optical_Clips',(.34,.335,.32),.34,.55) + +# Large annular volume: broad, segmented shallow chamber surrounding a narrow raised bore. +# The previous uniformly thick concentric tubes hid the asymmetric breaks. +bx,bz=-.425,1.80 +outer_profile=[(.625,.013),(.918,.013),(.950,.004),(.949,-.010), + (.928,-.023),(.897,-.037),(.848,-.046),(.717,-.048), + (.667,-.044),(.635,-.030)] +for start,end in [(1,89.7),(90.3,177.5),(181,227.5),(229,308.7),(309.3,359.7)]: + annular_profile('Embedded optical cavity segment',bx,bz,outer_profile,optics,128, + math.radians(start),math.radians(end)) +# Each lip has its own depth so the cross-section stays readable when unassembled. +annular_profile('Inner optical bevel hub',bx,bz,[ + (.501,-.020),(.614,-.020),(.635,-.033),(.632,-.057), + (.615,-.069),(.547,-.071),(.512,-.056),(.501,-.040)],optical_edge) +for start,end in [(2,177),(181,227),(230,359)]: + annular_profile('Subsurface refractive shoulder sector',bx,bz,[ + (.670,-.031),(.929,-.010),(.948,-.017),(.942,-.026), + (.914,-.034),(.867,-.043),(.731,-.054),(.673,-.049)],optical_edge,128, + math.radians(start),math.radians(end)) +# Thin glazing lips and a partial inner copper track seen at the upper-left. +for radius,depth in [(.943,-.029),(.650,-.054),(.526,-.064)]: + annular_profile('Concentric optical machining lip',bx,bz,[ + (radius-.004,depth+.003),(radius+.004,depth+.003), + (radius+.005,depth-.001),(radius,depth-.004), + (radius-.004,depth-.002)],film_edge) +annular_profile('Embedded amber annulus inner arc',bx,bz,[ + (.546,-.061),(.568,-.061),(.574,-.070),(.568,-.075),(.548,-.075)],amber_inlay, + 128,math.radians(109),math.radians(225)) +annular_profile('Embedded amber annulus outer arc',bx,bz,[ + (.910,.009),(.982,.009),(.986,-.004),(.978,-.011),(.953,-.020),(.922,-.017)],amber_inlay, + 128,math.radians(229),math.radians(364)) +# Radial joints cross the wide outer lens, staying under the cover. +for angle in [90,103,179,228,260,309]: + a=math.radians(angle) + channel('Concentric optical machining radial seam', + [(bx+r*math.cos(a),bz+r*math.sin(a)) for r in [.674,.74,.88,.937]], + -.057,.0024,film_edge) + +# Small annular volume: concentric clear shoulder, darker seat, flat amber annulus and hub. +sx,sz=1.125,2.455 +annular_profile('Embedded optical cavity small',sx,sz,[ + (.220,.014),(.511,.014),(.555,.000),(.560,-.015), + (.529,-.035),(.471,-.050),(.360,-.055),(.231,-.037)],optics) +annular_profile('Subsurface refractive shoulder small',sx,sz,[ + (.388,-.034),(.536,-.013),(.573,-.017),(.579,-.030), + (.553,-.047),(.502,-.059),(.424,-.063),(.386,-.051)],optical_edge) +annular_profile('Embedded amber annulus small',sx,sz,[ + (.230,-.045),(.347,-.045),(.356,-.056),(.351,-.071), + (.241,-.071),(.230,-.060)],amber_inlay) +annular_profile('Inner optical bevel small hub',sx,sz,[ + (.170,-.041),(.226,-.041),(.235,-.054),(.231,-.069), + (.184,-.069),(.172,-.057)],optical_edge) +for radius in [.381,.478,.565]: + annular_profile('Concentric optical machining small lip',sx,sz,[ + (radius-.003,-.057),(radius+.003,-.057),(radius+.004,-.062), + (radius,-.064),(radius-.003,-.062)],film_edge) + +# The reference shows two nearly clear ribbon-like spans. Their physical role is +# unknown; recreate visible sheet/rim geometry without inventing a mechanism. +def ribbon(name, controls, width, depth): + points=[];verts=[];faces=[];segments=40 + for i in range(segments+1): + t=i/segments;u=1-t + x=sum(w*p[0] for w,p in zip([u**3,3*u*u*t,3*u*t*t,t**3],controls)) + z=sum(w*p[1] for w,p in zip([u**3,3*u*u*t,3*u*t*t,t**3],controls)) + dx=3*u*u*(controls[1][0]-controls[0][0])+6*u*t*(controls[2][0]-controls[1][0])+3*t*t*(controls[3][0]-controls[2][0]) + dz=3*u*u*(controls[1][1]-controls[0][1])+6*u*t*(controls[2][1]-controls[1][1])+3*t*t*(controls[3][1]-controls[2][1]) + length=math.hypot(dx,dz);nx=-dz/length;nz=dx/length + points.append(((x-width*nx/2,z-width*nz/2),(x+width*nx/2,z+width*nz/2))) + for yy in [depth-.002,depth+.002]: + for sign in [-1,1]:verts.append((x+sign*width*nx/2,yy,z+sign*width*nz/2)) + for i in range(segments): + a=i*4;b=(i+1)*4 + faces.extend([(a,b,b+1,a+1),(a+2,a+3,b+3,b+2), + (a,a+2,b+2,b),(a+1,b+1,b+3,a+3)]) + faces.extend([(0,1,3,2),(segments*4,segments*4+2,segments*4+3,segments*4+1)]) + mesh=bpy.data.meshes.new(name);mesh.from_pydata(verts,[],faces);mesh.update() + obj=bpy.data.objects.new(name,mesh);scene.collection.objects.link(obj);mesh.materials.append(film) + for face in mesh.polygons:face.use_smooth=True + for side in [0,1]:channel(name+' edge',[p[side] for p in points],depth-.003,.0018,film_edge) + +ribbon('Optical ribbon inner',[(.31,1.16),(.99,1.33),(1.00,1.78),(.73,2.20)],.10,-.008) +ribbon('Optical ribbon outer',[(.03,.94),(.97,1.13),(1.14,1.75),(.84,2.13)],.11,.007) +channel('Optical ribbon tangent upper',[(-.67,2.73),(.98,3.01)],.003,.0018,film_edge) +channel('Optical ribbon tangent lower',[(.06,.91),(1.44,2.03)],.006,.0018,film_edge) + +# Omit the three tall structures to the left, as requested: their depth would +# exceed the cassette. Use the 46–51 second views only for the annular buildings. +# Closely spaced facade mullions sit under the roof edge, visible from oblique views. +for cx,cz,r,count in [(bx,bz,.945,84),(sx,sz,.544,52)]: + for i in range(count): + angle=2*math.pi*i/count + if cx==bx and 177`。 +- `npm run build:wallpaper` 与正常网页 `npm run build` 均通过,网页 PWA 构建成功。 + +运行数据有一次阵列采样为 22 FPS(宿主上限 30),不能把上限写成稳定实际帧率。本次未做长时间功耗测试、4K / 多屏压力测试或真实桌面图标遮挡下的完整输入验证。 + +![Wallpaper Engine 实际设置页,音量同步与画质切换](../reference/wallpaper-engine/settings.png) + +### 本机 CEF 的下拉框崩溃 + +打开原设置页会稳定出现 `STATUS_ILLEGAL_INSTRUCTION`。诊断确认页面逻辑完成后、首次绘制前崩溃;最小弹窗和声音控件正常,单独画质区域可复现,去掉其中 `包含背景音乐单项音效试听正在载入场景… + +
同步检查
等待播放。
+ + diff --git a/reference/boot-check.html b/reference/boot-check.html new file mode 100644 index 0000000..b0b60b4 --- /dev/null +++ b/reference/boot-check.html @@ -0,0 +1,55 @@ +前段动效检查 + +

前段动效检查

正在检查…
+ diff --git a/reference/boot-frames/.gitignore b/reference/boot-frames/.gitignore new file mode 100644 index 0000000..d6b7ef3 --- /dev/null +++ b/reference/boot-frames/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/reference/boot-lettering-review.html b/reference/boot-lettering-review.html new file mode 100644 index 0000000..ad5e524 --- /dev/null +++ b/reference/boot-lettering-review.html @@ -0,0 +1,49 @@ +开场 Novecento 字体复核 + +

开场中央文字 · Novecento Sans Wide

+

访问、身份、处理与授权提示使用 Normal;欢迎画面使用 Bold,左上 RHINE LAB 使用 DemiBold。本机授权包启用真实 WOFF2,字形位置与原时间轴保持一致。

+
+ +
+
+

预览使用原片 1920×1080 坐标。时间、字距动画和逐字音效触发由原时间轴驱动。

+
正在加载…
+ diff --git a/reference/boot-review.html b/reference/boot-review.html new file mode 100644 index 0000000..90b5e3f --- /dev/null +++ b/reference/boot-review.html @@ -0,0 +1,45 @@ + + + + +Rhine Lab · 前段 2D 动效对照 + +
+

前段 2D 动效对照

+

左侧为原片,右侧为实时界面。拖动时间轴或逐帧前后移动,两侧会同步;原片每秒 25 帧。

+
+
原视频 · 6.76–27.00 秒
原视频当前帧
+
实时界面 · 逐帧重建
+
+ + + +
+仅用于本地复核。产品从原片画面变白后开始,不包含黑底鹰角片头;原视频不进入产品页面。← / → 逐帧前后移动。 +
+ + + diff --git a/reference/brand-review.html b/reference/brand-review.html new file mode 100644 index 0000000..238adff --- /dev/null +++ b/reference/brand-review.html @@ -0,0 +1,106 @@ + + + 品牌文字放大对照 +
+

原片左上角 · 2.4 倍放大

+
+ 原片品牌文字放大 +
+
+

当前界面 · 同倍率

+
+
+

RHINE LAB

+
SYNTHESIZE INFORMATION
+

ANALYSIS OS

+
+
+
+

原片与当前叠加 · 当前文字用红色标出

+ +
+ 原片与当前文字叠加 +
+

RHINE LAB

+
SYNTHESIZE INFORMATION
+

ANALYSIS OS

+
+
+
+

+  
+ + diff --git a/reference/clear-study/archive-before.glb b/reference/clear-study/archive-before.glb new file mode 100644 index 0000000..e1e7437 Binary files /dev/null and b/reference/clear-study/archive-before.glb differ diff --git a/reference/compare-boot-details.py b/reference/compare-boot-details.py new file mode 100644 index 0000000..7e79615 --- /dev/null +++ b/reference/compare-boot-details.py @@ -0,0 +1,14 @@ +"""Compare matching source/DOM crops after capturing the review page.""" +from pathlib import Path +from PIL import Image,ImageDraw +out=Path(__file__).resolve().parent/'boot-frames' +for name,frames in [('logo',[237,248,258,460,474,479]),('orbits',[546,551,556,560,564,568])]: + sheet=Image.new('RGB',(1520,len(frames)*464),'#242424');d=ImageDraw.Draw(sheet) + for i,f in enumerate(frames): + crop=(780,440,1140,660) if f<265 else (500,440,840,660) if f<500 else (770,430,1150,650) + a=Image.open(out/f'{f:04}.jpg').crop(crop).resize((760,440)) + b=Image.open(out/f'detail-replica-{f}.jpg').resize((760,440)) + y=i*464;d.text((8,y+5),f'{f} / {f/25:.2f}s reference / replica',fill='white') + sheet.paste(a,(0,y+24));sheet.paste(b,(760,y+24)) + pair=Image.new('RGB',(1520,440));pair.paste(a,(0,0));pair.paste(b,(760,0));pair.save(out/f'pair-{f}.jpg',quality=96) + sheet.save(out/f'{name}-comparison.jpg',quality=96) diff --git a/reference/coverage-review.html b/reference/coverage-review.html new file mode 100644 index 0000000..f993293 --- /dev/null +++ b/reference/coverage-review.html @@ -0,0 +1,10 @@ +阵列覆盖与裁剪验证
diff --git a/reference/decryption-check.html b/reference/decryption-check.html new file mode 100644 index 0000000..5dcc4cf --- /dev/null +++ b/reference/decryption-check.html @@ -0,0 +1,37 @@ +解密与玻璃模式检查 + +

解密与玻璃模式检查

正在检查…
+ diff --git a/reference/decryption-review.html b/reference/decryption-review.html new file mode 100644 index 0000000..0cc5eeb --- /dev/null +++ b/reference/decryption-review.html @@ -0,0 +1,40 @@ + + + + +Rhine Lab · 解密线与透明过渡对照 + +
+

解密线与透明过渡对照

+

左侧为原片,右侧为实时三维场景。拖动时间轴或逐帧前后移动,两侧会同步;原片每秒 25 帧。

+
+
原视频 · 34.00–39.96 秒
原视频当前帧
+
实时场景 · Blender 模型 / Three.js
+
+ + +
+此页仅用于本地复核,产品入口没有播放或嵌入参考视频。两侧仍存在几何、光照与景深差异;时间轴为逐帧观察后的运动重建,没有从原片恢复相机或三维场景数据。 +
+ + diff --git a/reference/depth-check.html b/reference/depth-check.html new file mode 100644 index 0000000..f8eb732 --- /dev/null +++ b/reference/depth-check.html @@ -0,0 +1,35 @@ +相机深度范围检查 + +

相机深度范围检查

正在检查…
+ diff --git a/reference/detail-transition.html b/reference/detail-transition.html new file mode 100644 index 0000000..a81d2bf --- /dev/null +++ b/reference/detail-transition.html @@ -0,0 +1,81 @@ + + + +详情切换雾效回归检查 + + +
运行检查…
+ + diff --git a/reference/document-decryption-check.html b/reference/document-decryption-check.html new file mode 100644 index 0000000..984f2b5 --- /dev/null +++ b/reference/document-decryption-check.html @@ -0,0 +1,51 @@ + + + +文档同步解密 · 验证 + +

文档同步解密

+ +
正在验证遮挡、同步揭示和页签切换…
+ + + diff --git a/reference/edge-study.png b/reference/edge-study.png new file mode 100644 index 0000000..770c626 Binary files /dev/null and b/reference/edge-study.png differ diff --git a/reference/extract-boot.py b/reference/extract-boot.py new file mode 100644 index 0000000..baff8a8 --- /dev/null +++ b/reference/extract-boot.py @@ -0,0 +1,28 @@ +import sys +from pathlib import Path +root=next(p for p in Path(__file__).resolve().parents if any(p.glob('*.mp4'))) +for parent in Path(__file__).resolve().parents: + if (parent/'.tools').is_dir():sys.path.insert(0,str(parent/'.tools')) +import cv2 +from PIL import Image,ImageDraw +out=Path('reference/boot-frames');out.mkdir(parents=True,exist_ok=True) +v=cv2.VideoCapture(str(next(root.glob('*.mp4')))) +print('VIDEO',v.get(3),v.get(4),v.get(5),v.get(7)) +v.set(cv2.CAP_PROP_POS_FRAMES,125) +for fr in range(125,676): + ok,im=v.read() + if not ok:break + cv2.imwrite(str(out/f'{fr:04}.jpg'),im,[cv2.IMWRITE_JPEG_QUALITY,94]) +sheet=Image.new('RGB',(1600,1845),'#242424');d=ImageDraw.Draw(sheet) +for i,fr in enumerate(range(125,676,12)): + im=Image.open(out/f'{fr:04}.jpg');im.thumbnail((320,180));x=i%5*320;y=i//5*200 + sheet.paste(im,(x,y+20));d.text((x+8,y+3),f'{fr/25:.2f}s / {fr}',fill='white') +sheet.save(out/'overview.jpg',quality=95) +for sec in range(5,27): + sheet=Image.new('RGB',(1920,1200),'#242424');d=ImageDraw.Draw(sheet) + for i in range(25): + fr=sec*25+i;im=Image.open(out/f'{fr:04}.jpg');im.thumbnail((384,216));x=i%5*384;y=i//5*240 + sheet.paste(im,(x,y+24));d.text((x+8,y+5),f'{fr/25:.2f}s / {fr}',fill='white') + sheet.save(out/f'second-{sec:02}.jpg',quality=95) +print('Extracted',551,'frames and 22 consecutive-frame sheets') +Image.open(out/'0337.jpg').crop((45,105,350,233)).resize((915,384)).save(out.parent/'brand-original.jpg',quality=97) diff --git a/reference/extract.py b/reference/extract.py new file mode 100644 index 0000000..bc73cfe --- /dev/null +++ b/reference/extract.py @@ -0,0 +1,15 @@ +import sys +sys.path.insert(0,'.tools') +import cv2 +from PIL import Image,ImageDraw +from pathlib import Path +v=cv2.VideoCapture(str(next(Path('.').glob('*.mp4')))) +print('VIDEO',v.get(3),v.get(4),v.get(5),v.get(7)) +sheet=Image.new('RGB',(1280,1800),'#202020'); d=ImageDraw.Draw(sheet) +for i,t in enumerate(range(5,41,2)): + v.set(0 if False else cv2.CAP_PROP_POS_MSEC,t*1000); ok,f=v.read() + if ok: + cv2.imwrite(f'reference/{t:02}.jpg',f) + im=Image.fromarray(cv2.cvtColor(f,cv2.COLOR_BGR2RGB)); im.thumbnail((640,340)); x=(i%2)*640; y=(i//2)*200 + im.thumbnail((640,180)); sheet.paste(im,(x,y+20)); d.text((x+5,y+3),str(t)+'s',fill='white') +sheet.save('reference/contact.jpg') diff --git a/reference/inspect-boot-logo.py b/reference/inspect-boot-logo.py new file mode 100644 index 0000000..76d3d97 --- /dev/null +++ b/reference/inspect-boot-logo.py @@ -0,0 +1,12 @@ +"""Enlarged consecutive-frame sheets for the drawn contour and moving cut.""" +from pathlib import Path +from PIL import Image,ImageDraw +out=Path(__file__).resolve().parent/'boot-frames' +for first,last,crop in [(229,249,(795,450,1125,650)),(249,269,(795,450,1125,650)),(440,460,(505,450,825,650)),(460,480,(505,450,825,650)),(480,487,(505,450,825,650))]: + w,h=640,390;cols=4 + sheet=Image.new('RGB',(cols*w,((last-first+cols-1)//cols)*(h+24)),'#242424');d=ImageDraw.Draw(sheet) + for i,f in enumerate(range(first,last)): + x=i%cols*w;y=i//cols*(h+24) + sheet.paste(Image.open(out/f'{f:04}.jpg').crop(crop).resize((w,h)),(x,y+24)) + d.text((x+8,y+5),f'{f} / {f/25:.2f}s',fill='white') + sheet.save(out/f'logo-detail-{first}.jpg',quality=96) diff --git a/reference/internal-review.html b/reference/internal-review.html new file mode 100644 index 0000000..0251bc7 --- /dev/null +++ b/reference/internal-review.html @@ -0,0 +1,46 @@ + +内部模型 · 第一版恢复核对 + +

内部模型 · 第一版恢复核对

右侧已恢复左侧第一版的完整内构与材质,装配模式使用修订后的外壳。两边使用相同灯光和视角。

+ +

第一版 · 原始内构

已恢复 · 第一版内构

正在加载…
+ diff --git a/reference/internal-study/assembly-before.glb b/reference/internal-study/assembly-before.glb new file mode 100644 index 0000000..77e75a6 Binary files /dev/null and b/reference/internal-study/assembly-before.glb differ diff --git a/reference/light-review.html b/reference/light-review.html new file mode 100644 index 0000000..68772db --- /dev/null +++ b/reference/light-review.html @@ -0,0 +1,203 @@ + + + + + + Rhine Lab · 光影对照 + + + + + +
+
+
+ RHINE LAB / LIGHT STUDY +

光影对照

+
+ 体验当前版本 ↗ +
+

+ 主界面已恢复原始基线的运动、颜色与光照。右侧保留光影实验供回看。 +

+
+ +
+
+
+
+

原视频

+ 同一关键帧 +
+
+
+ 原视频第 787 帧 +
+
+
+
+

光影实验

+ 相同模型与镜头 +
+
+
+
+

正在加载两份实时场景…

+
+ 可切换左侧为基线(当前采用),直接观察光影变化。原片图片仅用于本地审阅。运动版本对比 ↗ +
+
+ + + diff --git a/reference/loop-review.html b/reference/loop-review.html new file mode 100644 index 0000000..37f84f6 --- /dev/null +++ b/reference/loop-review.html @@ -0,0 +1,160 @@ + + + +循环阵列运行检查 + +
运行实际场景检查…
+ + diff --git a/reference/match-annotations.py b/reference/match-annotations.py new file mode 100644 index 0000000..63c0c49 --- /dev/null +++ b/reference/match-annotations.py @@ -0,0 +1,17 @@ +from pathlib import Path +import sys +sys.path.insert(0,'.tools') +import cv2 +import numpy as np +sources=[ + 'C:/Users/LBLC/AppData/Local/Temp/codex-clipboard-159c6dd1-8880-4ed7-8638-037d13c5a65b.png', + 'C:/Users/LBLC/AppData/Local/Temp/codex-clipboard-c945190d-1847-4dcd-9595-333623f8d757.png', + 'C:/Users/LBLC/AppData/Local/Temp/codex-clipboard-c37ac867-c3a5-4f49-bf2d-d66def143131.png'] +frames=[(p,cv2.resize(cv2.imread(str(p)),(480,270)).astype(float)) for p in Path('reference/motion').glob('[0-9]*.jpg')] +for source in sources: + im=cv2.imread(source)[:1080,:1920] + im=cv2.resize(im,(480,270)).astype(float) + mask=~((im[:,:,2]>170)&(im[:,:,1]>130)&(im[:,:,0]<160)) + mask[:55,:90]=False + scores=sorted((np.mean(np.abs(im-f)[mask]),p.stem) for p,f in frames) + print(Path(source).name,[(int(n)/25,round(float(s),2)) for s,n in scores[:4]]) diff --git a/reference/measure-boot-logo.py b/reference/measure-boot-logo.py new file mode 100644 index 0000000..2f7492b --- /dev/null +++ b/reference/measure-boot-logo.py @@ -0,0 +1,53 @@ +"""Sample the continuous infinity contour against the reference's black ink.""" +import sys,json +from pathlib import Path +root=Path(__file__).resolve().parent +sys.path.insert(0,str(root.parent/'.tools')) +import cv2 +import numpy as np +from PIL import Image,ImageDraw +out=root/'boot-frames' +# Continuous version of the shared Bezier, with the printed logo's small gap filled. +curves=[[(295,73),(295,41),(273,15),(240,15)],[(240,15),(221,15),(207,23),(192,38)],[(192,38),(186,43),(181,47),(176,52)],[(176,52),(127,96),(103,128),(70,128)],[(70,128),(38,128),(15,101),(15,70)],[(15,70),(15,39),(37,15),(70,15)],[(70,15),(103,15),(127,48),(156,75)],[(156,75),(182,99),(208,128),(240,128)],[(240,128),(273,128),(295,105),(295,73)]] +p=[] +for c in curves: + t=np.linspace(0,1,300)[:,None];a,b,d,e=np.array(c) + p.extend((1-t)**3*a+3*(1-t)**2*t*b+3*(1-t)*t*t*d+t**3*e) +p=np.array(p);dist=np.r_[0,np.cumsum(np.linalg.norm(np.diff(p,axis=0),axis=1))] +params=np.linspace(0,1,2000,endpoint=False) +points=np.c_[np.interp(params*dist[-1],dist,p[:,0]),np.interp(params*dist[-1],dist,p[:,1])] +results=[] +for f in range(229,487): + im=cv2.imread(str(out/f'{f:04}.jpg'),0) + # Fit the final-size contour to its source bounding box; translation is + # measured from stable leftmost ink once the left loop has been drawn. + crop=im[464:608,490:1130] + yy,xx=np.where(crop<90) + left=float(xx.min()+490) if len(xx) and f>=238 else 815 + x=(points[:,0]-2)*(.955)+left + y=(points[:,1]-2)*(.955)+465 + gray=cv2.remap(im,x.astype(np.float32)[None,:],y.astype(np.float32)[None,:],cv2.INTER_LINEAR)[0] + mask=(gray<130).astype(np.uint8) + # Remove tiny raster interruptions; do not fill a real moving cut. + mask=cv2.morphologyEx(np.tile(mask,3)[None,:],cv2.MORPH_CLOSE,np.ones((1,8),np.uint8))[0,2000:4000] + changes=np.diff(np.r_[0,mask,0].astype(int)) + runs=[(int(a),int(b)) for a,b in zip(np.where(changes==1)[0],np.where(changes==-1)[0]) if b-a>4] + results.append({'frame':f,'left':left,'runs':runs,'coverage':round(float(mask.mean()),4)}) + if 247<=f<=268: + mark=im[475:593,int(left):int(left)+294] + n,labels,stats,centers=cv2.connectedComponentsWithStats((mark<110).astype(np.uint8)) + symbols=[] + for s,(cx,cy) in zip(stats[1:],centers[1:]): + if s[4]<4 or s[4]>2500 or not 3012], key=lambda x:-x[1]) + +rows=[] +for f in range(487,569): + im=cv2.imread(str(out/f'{f:04}.jpg')) + polar=cv2.remap(im,mx,my,cv2.INTER_LINEAR,borderMode=cv2.BORDER_CONSTANT,borderValue=(128,128,128)) + black=polar.max(axis=2)<100 + white=polar.min(axis=2)>248 + row={'frame':f} + # Outer arcs occupy much more of a concentric radius than text/background. + for name,mask,lo,hi in [('outer',black,240,2200),('white',white,220,1500),('inner',black,85,140)]: + valid=(radii>=lo)&(radii<=hi) + counts=mask.sum(axis=1)*valid + idx=int(np.argmax(counts)) + row[name]=[float(radii[idx]),segments(mask[max(0,idx-1):idx+2].any(axis=0))[:2]] + orange=(im[:,:,2]>190)&(im[:,:,1]>65)&(im[:,:,1]<190)&(im[:,:,0]<100) + n,labels,stats,centers=cv2.connectedComponentsWithStats(orange.astype(np.uint8)) + dots=[(float(x),float(y),int(s[4])) for s,(x,y) in zip(stats[1:],centers[1:]) if s[4]>8 and 50033)&(radius<44) + if f<547: + # Permission lettering occupies these rows and is not part of the arc. + mask&=(y<526)|(y>554) + xx,yy=x[mask],y[mask] + if len(xx)<8:row['sides'].append(None);continue + a,b,c=np.linalg.lstsq(np.c_[2*xx,2*yy,np.ones(len(xx))],xx*xx+yy*yy,rcond=None)[0] + r=np.sqrt(c+a*a+b*b) + angles=np.sort(np.mod(np.degrees(np.arctan2(yy-b,xx-a)),360)) + gaps=np.diff(np.r_[angles,angles[0]+360]);i=int(np.argmax(gaps)) + start=angles[(i+1)%len(angles)];sweep=360-gaps[i] + row['sides'].append([round(float(v),2) for v in [a,b,r,start,sweep]]) + roi=im[478:603,898:1023] + n,labels,stats,centers=cv2.connectedComponentsWithStats((roi<120).astype(np.uint8)) + row['dots']=[[round(float(x+898),2),round(float(y+478),2),int(s[4]),round(float(np.sqrt(s[4]/np.pi)),2)] for s,(x,y) in zip(stats[1:],centers[1:]) if s[4]>=3 and s[2]<90 and s[3]<90] + rows.append(row) +(out/'scan-detail-measurements.json').write_text(json.dumps(rows,indent=2)) +for start,end in [(540,554),(554,569)]: + w,h=760,440;cols=3 + sheet=Image.new('RGB',(w*cols,((end-start+cols-1)//cols)*(h+24)),'#242424');d=ImageDraw.Draw(sheet) + for i,f in enumerate(range(start,end)): + x=i%cols*w;y=i//cols*(h+24) + sheet.paste(Image.open(out/f'{f:04}.jpg').crop((770,430,1150,650)).resize((w,h)),(x,y+24)) + d.text((x+8,y+5),f'{f} / {f/25:.2f}s',fill='white') + sheet.save(out/f'scan-center-{start}.jpg',quality=96) +print(json.dumps(rows,indent=2)) diff --git a/reference/micro-transition-review.html b/reference/micro-transition-review.html new file mode 100644 index 0000000..a3efe4c --- /dev/null +++ b/reference/micro-transition-review.html @@ -0,0 +1,184 @@ + + + + 标题与查看器过渡检查 + + +
正在检查…
+ + diff --git a/reference/mobile-review.html b/reference/mobile-review.html new file mode 100644 index 0000000..fe17278 --- /dev/null +++ b/reference/mobile-review.html @@ -0,0 +1,40 @@ + + + + +Rhine Lab · 手机实测 + + +
+
手机实测 · 点击收起以体验完整画面 +

iPhone / Safari 校准

+

先竖屏运行一次,再横屏运行一次。每次约 45 秒,期间保持页面在前台,测试会依次浏览档案、解密和拆解模型。结果只包含屏幕、画质与帧间隔信息,会回传到这台电脑。

+
打开完整体验
+

手动体验:左右滑动切列,上下滑动切档;详情正文可滚动;360° 查看器支持单指旋转、双指缩放和平移。请检查搜索键盘弹出后能否输入和关闭窗口。

+等待档案载入…
+ + diff --git a/reference/model-review.html b/reference/model-review.html new file mode 100644 index 0000000..4ce9816 --- /dev/null +++ b/reference/model-review.html @@ -0,0 +1,230 @@ + + + + + + Rhine Lab · 模型细节对照 + + + + + +
+
+
+ RHINE LAB / MODEL STUDY +

模型细节对照

+
+ 体验当前版本 ↗ +
+

+ 放大比较原片、细化前与细化后。两份实时场景使用相同镜头、基线光照和运动。 +

+
+ + +
+
+
+
+

原视频

+ 同一关键帧 +
+
+
+ 原视频第 787 帧 +
+
+
+
+

细化后

+ 相同模型与镜头 +
+
+
+
+

正在加载两份实时场景…

+
+ 可切换左侧为细化前;放大观察顶部与圆环。原片仅用于本地审阅。运动版本对比 ↗ +
+
+ + + diff --git a/reference/model-study/archive-before.glb b/reference/model-study/archive-before.glb new file mode 100644 index 0000000..a23e79b Binary files /dev/null and b/reference/model-study/archive-before.glb differ diff --git a/reference/navigation-transition-check.html b/reference/navigation-transition-check.html new file mode 100644 index 0000000..5c33310 --- /dev/null +++ b/reference/navigation-transition-check.html @@ -0,0 +1,228 @@ + + + + 档案导航文字与刻度过渡检查 + + +
正在检查…
+ + diff --git a/reference/opening-layout-review.html b/reference/opening-layout-review.html new file mode 100644 index 0000000..86a5ed5 --- /dev/null +++ b/reference/opening-layout-review.html @@ -0,0 +1,10 @@ +入场比例验证 +
+ diff --git a/reference/playground-review.html b/reference/playground-review.html new file mode 100644 index 0000000..0f66138 --- /dev/null +++ b/reference/playground-review.html @@ -0,0 +1,19 @@ +律动与接力验证 + +
+ + diff --git a/reference/review.html b/reference/review.html new file mode 100644 index 0000000..8924f8d --- /dev/null +++ b/reference/review.html @@ -0,0 +1,40 @@ + + + + +Rhine Lab · 运动与材质对照 + +
+

运动与材质对照

+

左侧为原片,右侧为实时三维场景。拖动时间轴或逐帧前后移动,两侧会同步;原片每秒 25 帧。

+
+
原视频 · 26.00–39.96 秒
原视频当前帧
+
实时场景 · Blender 模型 / Three.js
+
+ + +
+此页仅用于本地复核,产品入口没有播放或嵌入参考视频。两侧仍存在几何、光照与景深差异;时间轴为逐帧观察后的运动重建,没有从原片恢复相机或三维场景数据。 +
+ + + diff --git a/reference/ring-study/assembly-before.glb b/reference/ring-study/assembly-before.glb new file mode 100644 index 0000000..4bb08b7 Binary files /dev/null and b/reference/ring-study/assembly-before.glb differ diff --git a/reference/rise-review.html b/reference/rise-review.html new file mode 100644 index 0000000..2548bca --- /dev/null +++ b/reference/rise-review.html @@ -0,0 +1,51 @@ + +档案切换起伏检查 + +
正在记录实际场景…
+ diff --git a/reference/sequence-review.html b/reference/sequence-review.html new file mode 100644 index 0000000..8ead4e0 --- /dev/null +++ b/reference/sequence-review.html @@ -0,0 +1,164 @@ + + + 先抬起后波浪检查 + +
+
正在检查动画先后关系…
+ + diff --git a/reference/shell-review.html b/reference/shell-review.html new file mode 100644 index 0000000..0843767 --- /dev/null +++ b/reference/shell-review.html @@ -0,0 +1,46 @@ + +外壳 · 刻线与解密过渡 + +

外壳 · 刻线与解密过渡

检查顶边嵌片、两处螺丝及盖板内刻线。播放或拖动进度,观察清晰区域从顶部向下推进。

+ +

打磨前

外壳打磨版

正在加载…
+ diff --git a/reference/shell-study/assembly-before.glb b/reference/shell-study/assembly-before.glb new file mode 100644 index 0000000..f9595c6 Binary files /dev/null and b/reference/shell-study/assembly-before.glb differ diff --git a/reference/theme-review.html b/reference/theme-review.html new file mode 100644 index 0000000..fedfbdb --- /dev/null +++ b/reference/theme-review.html @@ -0,0 +1,7 @@ +石墨灰配色验证
diff --git a/reference/typing-original.wav b/reference/typing-original.wav new file mode 100644 index 0000000..82a8ceb Binary files /dev/null and b/reference/typing-original.wav differ diff --git a/reference/ui-transition-check.html b/reference/ui-transition-check.html new file mode 100644 index 0000000..9a8ee41 --- /dev/null +++ b/reference/ui-transition-check.html @@ -0,0 +1,251 @@ + + + + 详情与弹窗过渡检查 + +

详情与弹窗过渡检查

+
正在检查…
+ + + diff --git a/reference/viewer-motion-check.html b/reference/viewer-motion-check.html new file mode 100644 index 0000000..aa2239b --- /dev/null +++ b/reference/viewer-motion-check.html @@ -0,0 +1,55 @@ +360° 导航过渡检查 + +

360° 导航过渡检查

正在检查…
+ diff --git a/reference/visual-effects-review.html b/reference/visual-effects-review.html new file mode 100644 index 0000000..d709bbd --- /dev/null +++ b/reference/visual-effects-review.html @@ -0,0 +1,23 @@ +曲面 HUD 与全局屏幕效果验证 + +
+ + + + + + +
等待载入 + diff --git a/reference/wallpaper-engine/settings.png b/reference/wallpaper-engine/settings.png new file mode 100644 index 0000000..7188142 Binary files /dev/null and b/reference/wallpaper-engine/settings.png differ diff --git a/reference/wave-compare.css b/reference/wave-compare.css new file mode 100644 index 0000000..9748ef8 --- /dev/null +++ b/reference/wave-compare.css @@ -0,0 +1,294 @@ +@import url("/src/fonts.css"); +* { + box-sizing: border-box; +} +body { + margin: 0; + background: #eae5e1; + color: #272921; + font: + 14px "MiSans", + "Microsoft YaHei", + sans-serif; +} +main { + max-width: 1800px; + margin: auto; + padding: 28px 32px; +} +header { + display: flex; + align-items: center; + justify-content: space-between; + gap: 20px; +} +h1 { + font-size: 25px; + margin: 6px 0 0; + font-weight: 600; +} +h2 { + font-size: 14px; + font-weight: 600; + margin: 0; +} +.eyebrow { + font-size: 10px; + letter-spacing: 0.14em; + color: #68695f; +} +a { + color: inherit; + text-decoration: none; + border-bottom: 1px solid #96958b; + padding: 8px 0; +} +.intro { + color: #68695f; + line-height: 1.8; + margin: 12px 0 22px; +} +.toolbar { + display: flex; + gap: 24px; + align-items: center; + flex-wrap: wrap; + border-top: 1px solid #c9c4ba; + padding: 14px 0; +} +.toolbar label { + display: flex; + align-items: center; + gap: 8px; + font-size: 12px; +} +select, +button { + font: inherit; + color: inherit; + background: transparent; + border: 1px solid #bdb9ae; + padding: 8px 12px; + cursor: pointer; +} +button:hover, +select:hover { + background: #dfd9cf; +} +button:disabled { + opacity: 0.45; + cursor: wait; +} +button:focus-visible, +a:focus-visible, +input:focus-visible, +select:focus-visible { + outline: 2px solid #6c7054; + outline-offset: 3px; +} +input { + accent-color: #707a53; +} +.views { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 18px; +} +.caption { + display: flex; + justify-content: space-between; + align-items: center; + margin: 6px 0 10px; + gap: 12px; +} +.caption h2 { + display: flex; + align-items: center; + gap: 8px; +} +.caption span { + font-size: 11px; + color: #77776b; +} +i { + display: inline-block; + width: 18px; + height: 3px; +} +.baseline { + background: #a66044; +} +.current { + background: #577356; +} +.view { + position: relative; + aspect-ratio: 16/9; + overflow: hidden; + background: #eae5e1; + border: 1px solid #d4cec2; +} +.scene { + width: 100%; + height: 100%; + pointer-events: none; +} +.scene canvas { + display: block; + width: 100%; + height: 100%; + transform-origin: 34% 30%; +} +.view.zoomed canvas { + transform: scale(1.7); +} +.marker { + position: absolute; + width: 14px; + height: 14px; + border-left: 2px solid #6e725e; + border-top: 2px solid #6e725e; + pointer-events: none; +} +.marker span { + position: absolute; + font-size: 10px; + white-space: nowrap; + top: -20px; + left: 0; + padding: 2px 4px; + background: #eae5e1c9; + color: #575e49; +} +.transport { + display: flex; + align-items: center; + gap: 8px; + flex-wrap: wrap; + margin-top: 18px; +} +.transport button { + font-size: 12px; +} +#play { + background: #353b30; + color: #f4f0e9; + border-color: #353b30; + min-width: 98px; +} +output { + margin-left: auto; + font: + 12px ui-monospace, + monospace; + color: #68695f; +} +#timeline { + width: 100%; + margin: 16px 0 8px; + cursor: pointer; +} +.readout { + display: flex; + gap: 32px; + align-items: center; + min-height: 80px; + padding: 15px 0; + border-bottom: 1px solid #c9c4ba; +} +.readout > div { + min-width: 240px; +} +strong { + display: block; + font: + 24px ui-monospace, + monospace; + margin-top: 6px; + font-weight: 400; +} +.readout p { + color: #64685b; + font-size: 12px; + line-height: 1.8; + margin: 0; +} +.chart { + margin-top: 18px; +} +.chart-heading { + display: flex; + justify-content: space-between; + gap: 20px; + align-items: center; +} +.chart-heading span, +.chart-note { + font-size: 11px; + color: #737469; +} +svg { + width: 100%; + display: block; + max-height: 200px; + margin-top: 4px; +} +.chart-note { + margin: 0 0 20px; +} +footer { + font-size: 10px; + color: #828176; + line-height: 1.8; + display: flex; + justify-content: space-between; + gap: 14px; +} +#check { + white-space: nowrap; +} +@media (min-width: 1600px) { + main { + padding-top: 36px; + } + .view { + aspect-ratio: 16/10; + } +} +@media (max-width: 760px) { + main { + padding: 20px 16px; + } + .views { + grid-template-columns: 1fr; + } + .toolbar { + gap: 12px; + } + .readout { + gap: 12px; + align-items: flex-start; + flex-direction: column; + } + .chart-heading { + align-items: flex-start; + flex-direction: column; + gap: 6px; + } + header { + align-items: flex-start; + } + header a { + font-size: 11px; + } + h1 { + font-size: 23px; + } + footer { + display: block; + } + .transport output { + width: 100%; + margin-top: 6px; + } +} diff --git a/reference/wave-compare.html b/reference/wave-compare.html new file mode 100644 index 0000000..25d313b --- /dev/null +++ b/reference/wave-compare.html @@ -0,0 +1,125 @@ + + + + + + Rhine Lab · 切换波浪对比 + + + + +
+
+
+ RHINE LAB / MOTION STUDY +

切换波浪对比

+
+ 打开当前交互 ↗ +
+

+ 当前主界面已恢复原始基线。左侧选中与波浪同时开始;右侧保留先抬起后波浪的实验方案,便于回看。 +

+
+ + + + + +
+
+
+
+

同时播放

+ 就位 +
+
+
+ +
+
+
+
+

实验 · 先抬起,再波动

+ 就位 +
+
+
+ +
+
+
+
+ 0.00 / 3.20 秒 +
+ +
+
+ 当前选中档案 · 高度差— +
+

正在加载两份相同的三维场景…

+
+
+
+

选中档案的实际高度

+ 横轴为切换后的时间;纵轴为相对起始高度(场景单位) +
+ +

+ 虚线标记切换时刻。连续切换时,曲线会接续新选中档案的高度。 +

+
+
+ 原始基线 aeddd8f · 上一版 2048536 · + 两侧共用相同模型、材质、相机与固定时间步长。 +
+
+ + + diff --git a/reference/wave-compare.js b/reference/wave-compare.js new file mode 100644 index 0000000..a00e1dd --- /dev/null +++ b/reference/wave-compare.js @@ -0,0 +1,355 @@ +import { ArchiveScene } from "/src/scene.ts"; +import { selectionWave as baselineWave } from "./baseline-motion.ts"; +import { selectionWave as currentWave } from "/src/motion.ts"; +import { columnFiles } from "/src/data.ts"; +import { wrap } from "/src/archive-loop.ts"; + +const $ = (s) => document.querySelector(s), + duration = 192, + dt = 1 / 60, + startTime = 8; +const scenes = [ + new ArchiveScene($("#before .scene"), currentWave, false), + new ArchiveScene($("#after .scene"), currentWave, true), +]; +const draws = scenes.map((s) => s.composer.render.bind(s.composer)); +// Seek by simulating at 60 Hz; only the requested frame is drawn. +scenes.forEach((s) => { + s.composer.render = () => {}; +}); +const fields = [ + "lift", + "rail", + "shoulder", + "laneFocus", + "columnCamera", + "selectedCell", + "coordinateOrigin", + "pulses", + "pendingPulse", + "rotation", + "targetRotation", + "returnY", + "detail", + "targetDetail", + "reveal", + "targetReveal", + "last", + "clock", + "idleGain", + "pulseGain", + "lastInteraction", + "scanTime", + "scanBlend", + "selectedSlot", + "looping", + "canInspect", + "clearance", +]; +let saved = [], + frame = 0, + index = 0, + events = [], + samples = [], + maxFrame = 0, + playing = false, + ready = false, + lastWall = 0, + accumulator = 0; +const files = columnFiles(2); +const schedules = { + next: [[1, 1]], + previous: [[1, -1]], + repeat: [ + [1, 1], + [19, 1], + [37, 1], + [55, 1], + ], + reverse: [ + [1, 1], + [25, -1], + ], +}; + +function snapshot(s) { + return { + state: Object.fromEntries(fields.map((k) => [k, structuredClone(s[k])])), + camera: s.camera.position.clone(), + aim: s.cameraAim.clone(), + fov: s.camera.fov, + }; +} +function reset() { + scenes.forEach((s, i) => { + s.setMode("hidden"); + for (const key of fields) s[key] = structuredClone(saved[i].state[key]); + s.camera.position.copy(saved[i].camera); + s.cameraAim.copy(saved[i].aim); + s.camera.fov = saved[i].fov; + s.camera.lookAt(s.cameraAim); + s.camera.updateProjectionMatrix(); + s.camera.updateMatrixWorld(); + s.drawLabel(0); + s.pointer.set(0, 0); + }); + frame = 0; + index = 0; +} +function advance() { + frame++; + const action = events.find(([f]) => f === frame); + if (action) { + const direction = action[1]; + index = files[wrap(files.indexOf(index) + direction, files.length)]; + scenes.forEach((s) => s.select(index, { axis: "row", direction })); + } + scenes.forEach((s) => s.update(startTime + frame * dt)); +} +function setPlaying(value) { + playing = value; + accumulator = 0; + lastWall = 0; + $("#play").textContent = value ? "暂停" : "同步播放"; +} +function seek(target) { + target = Math.max(0, Math.min(duration, target)); + if (target <= frame) reset(); + while (frame < target) advance(); + // The first frame is the checkpoint, prior to the first input. + if (target === 0) { + scenes.forEach((s) => s.update(startTime)); + reset(); + } + render(); +} +function render() { + scenes.forEach((s, i) => { + draws[i](); + const [x, y] = s.projectCard(-2.5, 3.7), + view = $(["#before", "#after"][i]); + const zoom = $("#zoom").checked ? 1.7 : 1; + const px = (x / 1920 - 0.34) * zoom + 0.34, + py = (y / 1080 - 0.3) * zoom + 0.3; + const marker = view.querySelector(".marker"); + marker.style.left = `${px * 100}%`; + marker.style.top = `${py * 100}%`; + $(["#before-phase", "#after-phase"][i]).textContent = s.pendingPulse + ? "档案抬起中" + : s.pulses.length + ? "波浪传播中" + : "就位"; + }); + $("#timeline").value = String(frame); + $("#stamp").textContent = + `${(frame * dt).toFixed(2)} / ${(duration * dt).toFixed(2)} 秒`; + const delta = scenes[1].model.position.y - scenes[0].model.position.y; + const replayError = Math.max( + Math.abs(scenes[0].model.position.y - samples[frame].before), + Math.abs(scenes[1].model.position.y - samples[frame].after), + ); + $("#check").textContent = + replayError < 1e-7 ? "同步回放校验通过" : "回放校验异常"; + $("#check").dataset.replayError = String(replayError); + $("#difference").textContent = + `${delta >= 0 ? "+" : ""}${delta.toFixed(3)} · ${((Math.abs(delta) / 3.7) * 100).toFixed(1)}%`; + const max = samples[maxFrame].after - samples[maxFrame].before; + $("#explanation").textContent = + Math.abs(delta) < 0.001 + ? `此刻两版几乎重合。最大高度差出现在 ${(maxFrame * dt).toFixed(2)} 秒,为卡片高度的 ${((Math.abs(max) / 3.7) * 100).toFixed(1)}%。` + : `右侧选中档案比左侧${delta >= 0 ? "高" : "低"} ${Math.abs(delta).toFixed(3)} 个场景单位,占卡片高度的 ${((Math.abs(delta) / 3.7) * 100).toFixed(1)}%。观察右侧先抬起档案,再带动周围起伏的顺序。`; + const x = 56 + (frame / duration) * 1044; + $("#cursor").setAttribute("x1", x); + $("#cursor").setAttribute("x2", x); +} +function chart() { + // The zero checkpoint still owns the old file. Plot from the first input so + // changing ownership is not misrepresented as a downward motion of one file. + const origin = samples[1].before, + values = samples + .slice(1) + .flatMap((p) => [p.before - origin, p.after - origin]); + const min = Math.floor((Math.min(...values) - 0.03) * 10) / 10, + max = Math.ceil((Math.max(...values) + 0.03) * 10) / 10; + const x = (f) => 56 + (f / duration) * 1044, + y = (v) => 154 - ((v - origin - min) / (max - min)) * 130; + const path = (key) => + samples + .slice(1) + .map( + (p, f) => + `${f ? "L" : "M"}${x(f + 1).toFixed(2)},${y(p[key]).toFixed(2)}`, + ) + .join(" "); + let markup = ""; + for (let i = 0; i <= 4; i++) { + const v = min + ((max - min) * i) / 4, + py = y(v + origin); + markup += `${v.toFixed(2)}`; + } + for (let i = 0; i <= duration; i += 30) + markup += `${(i * dt).toFixed(1)}s`; + for (const [f] of events) + markup += ``; + markup += ``; + $("#chart").innerHTML = markup; +} +function prepare() { + setPlaying(false); + events = schedules[$("#scenario").value]; + scenes[0].selectionPulse = + $("#baseline").value === "original" ? baselineWave : currentWave; + reset(); + // The checkpoint has already been rendered into the selected model. + scenes.forEach((s) => s.update(startTime)); + reset(); + samples = [ + { before: scenes[0].model.position.y, after: scenes[1].model.position.y }, + ]; + while (frame < duration) { + advance(); + samples.push({ + before: scenes[0].model.position.y, + after: scenes[1].model.position.y, + }); + } + maxFrame = samples.reduce( + (best, p, f) => + Math.abs(p.after - p.before) > + Math.abs(samples[best].after - samples[best].before) + ? f + : best, + 0, + ); + chart(); + seek(0); +} +$("#play").onclick = () => { + if (frame === duration) seek(0); + setPlaying(!playing); +}; +$("#restart").onclick = () => { + seek(0); + setPlaying(true); +}; +$("#prev").onclick = () => { + setPlaying(false); + seek(frame - 1); +}; +$("#next").onclick = () => { + setPlaying(false); + seek(frame + 1); +}; +$("#peak").onclick = () => { + setPlaying(false); + seek(maxFrame); +}; +$("#timeline").oninput = (e) => { + setPlaying(false); + seek(Number(e.target.value)); +}; +$("#scenario").onchange = () => { + if (ready) { + prepare(); + setPlaying(true); + } +}; +$("#baseline").onchange = () => { + if (ready) { + prepare(); + setPlaying(true); + } +}; +$("#zoom").onchange = () => { + document + .querySelectorAll(".view") + .forEach((v) => v.classList.toggle("zoomed", $("#zoom").checked)); + if (ready) render(); +}; +$("#chart").onclick = (e) => { + if (!ready) return; + const rect = $("#chart").getBoundingClientRect(); + setPlaying(false); + seek( + Math.round( + ((((e.clientX - rect.left) / rect.width) * 1120 - 56) / 1044) * duration, + ), + ); +}; +function resize() { + scenes.forEach((s) => { + s.resize(); + // These are real-size comparison panels, not the scaled 1920px app stage. + const ratio = Math.min(devicePixelRatio, 1.5); + s.renderer.setPixelRatio(ratio); + s.composer.setPixelRatio(ratio); + }); + if (ready) render(); +} +addEventListener("resize", resize); +document.addEventListener("keydown", (e) => { + if (!ready || ["INPUT", "SELECT", "BUTTON", "A"].includes(e.target.tagName)) + return; + if (e.code === "Space") { + e.preventDefault(); + $("#play").click(); + } + if (e.code === "ArrowLeft" || e.code === "ArrowRight") { + e.preventDefault(); + setPlaying(false); + seek(frame + (e.code === "ArrowLeft" ? -1 : 1)); + } +}); +function tick(wall) { + if (playing && ready && !document.hidden) { + if (lastWall) + accumulator += + Math.min((wall - lastWall) / 1000, 0.1) * Number($("#speed").value); + const count = Math.floor(accumulator / dt); + if (count) { + accumulator -= count * dt; + let target = frame + count; + if (target > duration) { + if ($("#loop").checked) { + reset(); + target %= duration; + } else { + target = duration; + setPlaying(false); + } + } + seek(target); + } + } + lastWall = wall; + requestAnimationFrame(tick); +} +try { + await document.fonts.ready; + await Promise.all(scenes.map((s) => s.load())); + scenes.forEach((s) => { + s.setMode("archive"); + s.select(0); + for (let f = 1; f <= 480; f++) s.update(f * dt); + }); + // Start both runs with the exact same resting state and no residual pulses. + scenes.forEach((s) => { + s.pulses = []; + s.idleGain = 0; + s.lastInteraction = startTime; + s.update(startTime); + }); + saved = scenes.map(snapshot); + resize(); + ready = true; + document + .querySelectorAll(".transport button,#timeline") + .forEach((el) => (el.disabled = false)); + prepare(); + setPlaying(true); + requestAnimationFrame(tick); +} catch (error) { + $("#explanation").textContent = `场景加载失败:${error.message}`; + $("#play").textContent = "加载失败"; + console.error(error); +} diff --git a/reference/workbench-review.html b/reference/workbench-review.html new file mode 100644 index 0000000..9de1d9d --- /dev/null +++ b/reference/workbench-review.html @@ -0,0 +1,11 @@ +工作台宿主回调验证 + +
仅验证页使用模拟数据;不随壁纸发布。
+ + diff --git a/reference/workbench-revision.html b/reference/workbench-revision.html new file mode 100644 index 0000000..09707e6 --- /dev/null +++ b/reference/workbench-revision.html @@ -0,0 +1,59 @@ +工作台切换与开场复核 + +
+
+ diff --git a/scripts/analyze-reference-audio.mjs b/scripts/analyze-reference-audio.mjs new file mode 100644 index 0000000..e887a13 --- /dev/null +++ b/scripts/analyze-reference-audio.mjs @@ -0,0 +1,66 @@ +// Read the local source only; emit measured levels, not a subjective listening report. +// node scripts/analyze-reference-audio.mjs path/to/ffmpeg.exe +import fs from "node:fs"; +import { execFileSync } from "node:child_process"; +const video = fs.readdirSync(".").find((x) => x.endsWith(".mp4")); +if (!video || !process.argv[2]) + throw Error("Local reference video and ffmpeg path required"); +const rate = 24000; +const pcm = execFileSync( + process.argv[2], + [ + "-v", + "error", + "-ss", + "6.76", + "-i", + video, + "-t", + "33.24", + "-vn", + "-af", + "pan=mono|c0=0.5*c0+0.5*c1", + "-ac", + "1", + "-ar", + String(rate), + "-f", + "f32le", + "pipe:1", + ], + { maxBuffer: 16 * 1024 * 1024 }, +); +const samples = new Float32Array(pcm.buffer, pcm.byteOffset, pcm.length / 4); +const bounds = [6.76, 9.16, 11.12, 19.48, 22.76, 26.92, 30.68, 33.3, 40]; +const stages = bounds.slice(0, -1).map((start, i) => { + const data = samples.subarray( + Math.round((start - 6.76) * rate), + Math.round((bounds[i + 1] - 6.76) * rate), + ); + let peak = 0, + sum = 0; + for (const x of data) { + peak = Math.max(peak, Math.abs(x)); + sum += x * x; + } + return { + start, + end: bounds[i + 1], + peakDb: +(20 * Math.log10(peak)).toFixed(2), + rmsDb: +(10 * Math.log10(sum / data.length)).toFixed(2), + }; +}); +fs.writeFileSync( + "reference/audio-analysis.json", + JSON.stringify( + { + video, + scope: + "6.76–40 s, mono downmix; not a transcription or melody identification", + stages, + }, + null, + 2, + ) + "\n", +); +console.log(stages); diff --git a/scripts/archive-content.mjs b/scripts/archive-content.mjs new file mode 100644 index 0000000..0f2b440 --- /dev/null +++ b/scripts/archive-content.mjs @@ -0,0 +1,100 @@ +import fs from "node:fs/promises"; + +const requiredFields = [ + "id", + "title", + "en", + "department", + "category", + "date", + "lead", + "clearance", + "abstract", + "source", +]; +const isText = (value) => typeof value === "string" && value.trim().length > 0; + +export function validateContent(content) { + const errors = []; + if (!content || typeof content !== "object" || Array.isArray(content)) { + throw new Error("档案数据必须是 JSON 对象。"); + } + for (const key of ["categories", "columns"]) { + const names = content[key]; + if (!Array.isArray(names) || names.length !== 5 || !names.every(isText)) { + errors.push(`${key}:必须包含五个非空分类名称`); + } else if (new Set(names).size !== 5 || names.includes("全部档案")) { + errors.push(`${key}:分类名称不能重复,也不能使用“全部档案”`); + } + } + const categories = Array.isArray(content.categories) + ? content.categories + : []; + const columns = Array.isArray(content.columns) ? content.columns : []; + if ( + categories.some((name) => !columns.includes(name)) || + columns.some((name) => !categories.includes(name)) + ) { + errors.push("categories 与 columns 必须包含相同的五个分类(顺序可以不同)"); + } + const records = Array.isArray(content.records) ? content.records : []; + // 每列条数不设上限:列内不足八条时阵列会循环重复,超过八条就向外扩充。 + // 唯一的下限是每列至少一条,否则那一列无可显示。 + if (records.length < 5) errors.push("records:至少需要每个分类一条档案"); + // 编号宽度随总量自适应(≤999 时仍为三位,与原版一致)。 + const idWidth = Math.max(3, String(records.length).length); + const ids = new Set(); + records.forEach((record, index) => { + const label = `records[${index}]`; + if (!record || typeof record !== "object" || Array.isArray(record)) { + errors.push(`${label}:必须是档案对象`); + return; + } + for (const key of requiredFields) { + if (!isText(record[key])) errors.push(`${label}.${key}:必须是非空文本`); + } + const expectedId = `X-${String(index + 1).padStart(idWidth, "0")}`; + if (record.id !== expectedId) + errors.push(`${label}.id:应为 ${expectedId},编号须按顺序保持稳定`); + if (ids.has(record.id)) errors.push(`${label}.id:重复编号 ${record.id}`); + ids.add(record.id); + if (!categories.includes(record.category)) + errors.push(`${label}.category:未知分类 ${record.category}`); + if ( + !Array.isArray(record.findings) || + record.findings.length === 0 || + !record.findings.every(isText) + ) { + errors.push(`${label}.findings:必须包含至少一条非空研究记录`); + } + try { + const url = new URL(record.source); + // 本地归档条目用 file: 指向真实目录;保留 http(s) 以兼容公开设定类条目。 + if (!["https:", "http:", "file:"].includes(url.protocol)) throw new Error(); + } catch { + errors.push(`${label}.source:必须是有效的 HTTP、HTTPS 或 file 链接`); + } + }); + for (const name of columns) { + const count = records.filter((record) => record?.category === name).length; + if (count < 1) errors.push(`分类“${name}”:至少要有一条档案`); + } + if (errors.length) + throw new Error(`档案数据校验失败:\n- ${errors.join("\n- ")}`); + return content; +} + +export async function loadContent() { + return validateContent( + JSON.parse( + await fs.readFile( + new URL("../content/archives.json", import.meta.url), + "utf8", + ), + ), + ); +} + +export function archiveText(r) { + return `\uFEFFRHINE LAB · LOCAL ARCHIVE INDEX\nFILE ${r.id} / ${r.title}\n${r.en}\n\n归档位置:${r.department}\n编目范围:${r.date}\n来源:${r.lead}\n访问范围:${r.clearance}\n\n${r.abstract}\n\n研究记录\n${r.findings.map((f, i) => `${i + 1}. ${f}`).join("\n")}\n\n归档参考:${r.source}\n本文件是本地归档的编目摘要,由 scripts/archive-source.mjs 依据实际目录生成。\n`; +} diff --git a/scripts/archive-dev.mjs b/scripts/archive-dev.mjs new file mode 100644 index 0000000..e94aa88 --- /dev/null +++ b/scripts/archive-dev.mjs @@ -0,0 +1,55 @@ +// 一条命令同时拉起本地归档服务与 Vite 开发服务器。 +// 服务先起(生成令牌与快照),随后 Vite 通过 /archive-api 代理访问它。 + +import { spawn } from "node:child_process"; +import fs from "node:fs"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +const projectRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); +const tokenFile = path.join(projectRoot, ".archive-token"); +const npm = process.platform === "win32" ? "npm.cmd" : "npm"; + +// 令牌是每次启动新生成的,先删掉旧值,避免 Vite 代理到已经退出的旧服务。 +fs.rmSync(tokenFile, { force: true }); + +const children = []; +const shutdown = (code = 0) => { + for (const child of children) { + if (!child.killed) child.kill(); + } + process.exit(code); +}; +process.on("SIGINT", () => shutdown(0)); +process.on("SIGTERM", () => shutdown(0)); + +const service = spawn(process.execPath, ["scripts/archive-service.mjs"], { + cwd: projectRoot, + stdio: "inherit", +}); +children.push(service); +service.on("exit", (code) => { + if (code !== 0 && code !== null) { + console.error(`归档服务已退出(代码 ${code}),同时停止开发服务器。`); + shutdown(code); + } +}); + +// 等令牌文件出现,确保 Vite 启动时代理已能取到凭据。 +const deadline = Date.now() + 30_000; +while (!fs.existsSync(tokenFile)) { + if (Date.now() > deadline) { + console.error("等待归档服务启动超时。"); + shutdown(1); + } + await new Promise((resolve) => setTimeout(resolve, 200)); +} + +console.log("归档服务就绪,启动 Vite 开发服务器……"); +const vite = spawn(npm, ["run", "dev", "--", ...process.argv.slice(2)], { + cwd: projectRoot, + stdio: "inherit", + shell: process.platform === "win32", +}); +children.push(vite); +vite.on("exit", (code) => shutdown(code ?? 0)); diff --git a/scripts/archive-service-hidden.vbs b/scripts/archive-service-hidden.vbs new file mode 100644 index 0000000..58154ec --- /dev/null +++ b/scripts/archive-service-hidden.vbs @@ -0,0 +1,8 @@ +' Generated by archive-service.ps1 - do not edit by hand. +' Purpose: start the archive backend with a fully hidden window (style 0), so +' that logging on does not flash a black console. Also wraps it so the backend +' keeps its own log file. NOTE: this file is ASCII-only on purpose - wscript +' reads .vbs in the system ANSI code page, so non-ASCII here would garble. +Set sh = CreateObject("WScript.Shell") +sh.CurrentDirectory = "E:\deepseek\RhineLabUI" +sh.Run "powershell.exe -NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -File " & Chr(34) & "E:\deepseek\RhineLabUI\scripts\archive-service.ps1" & Chr(34) & " -Action run", 0, False diff --git a/scripts/archive-service.mjs b/scripts/archive-service.mjs new file mode 100644 index 0000000..5fe4d0e --- /dev/null +++ b/scripts/archive-service.mjs @@ -0,0 +1,447 @@ +// 本地归档服务:只监听 127.0.0.1,用每次启动生成的令牌限制网页访问。 +// +// 职责: +// 1. 监视归档根目录,变动后自动重新生成快照(对应"有新文件自动更新") +// 2. 提供检索索引(gzip) +// 3. 提供读操作:打开文件、在资源管理器中定位 +// 4. 提供安全写操作:新建分类目录、移动、复制、解压;一律先 plan 预览再 apply +// —— 不提供删除与重命名 +// +// 所有写操作都校验解析后的绝对路径必须落在归档根目录内,并追加到操作日志。 + +import fs from "node:fs"; +import fsp from "node:fs/promises"; +import http from "node:http"; +import path from "node:path"; +import zlib from "node:zlib"; +import crypto from "node:crypto"; +import { spawn } from "node:child_process"; +import { fileURLToPath, pathToFileURL } from "node:url"; +import { loadConfig, regenerate } from "./archive-source.mjs"; + +const here = path.dirname(fileURLToPath(import.meta.url)); +const projectRoot = path.resolve(here, ".."); +const SEVEN_ZIP = "C:\\Program Files\\7-Zip\\7z.exe"; + +const config = await loadConfig(); +const PORT = config.port ?? 43117; +const TOKEN = crypto.randomBytes(24).toString("hex"); +const TOKEN_FILE = path.join(projectRoot, ".archive-token"); +await fsp.writeFile(TOKEN_FILE, TOKEN, "utf8"); + +const state = { + watching: false, + lastScan: null, + lastError: null, + pendingRescan: null, +}; + +// ---------- 归档根目录围栏 ---------- +// 客户端一律传相对路径(/ 分隔)。解析后必须仍在根目录内,且真实路径也不得越界。 +function resolveInside(relPath) { + if (typeof relPath !== "string" || relPath.includes("\0")) + throw httpError(400, "路径不合法"); + const cleaned = relPath.replace(/^[/\\]+/, "").replace(/\//g, path.sep); + if (!cleaned) throw httpError(400, "路径为空"); + const abs = path.resolve(config.root, cleaned); + const rel = path.relative(config.root, abs); + if (rel === "" || rel.startsWith("..") || path.isAbsolute(rel)) + throw httpError(403, "只允许访问归档目录内的路径"); + return abs; +} + +function realInside(abs) { + let real; + try { + real = fs.realpathSync(abs); + } catch { + return abs; // 还不存在(例如待新建目录);上一级已校验 + } + const rel = path.relative(fs.realpathSync(config.root), real); + if (rel.startsWith("..") || path.isAbsolute(rel)) + throw httpError(403, "解析后的真实路径越出归档目录"); + return real; +} + +function httpError(status, message) { + const error = new Error(message); + error.status = status; + return error; +} + +// ---------- 统计与日志 ---------- +function statsOf(abs) { + const out = { exists: false, isDir: false, bytes: 0, files: 0 }; + let stat; + try { + stat = fs.lstatSync(abs); + } catch { + return out; + } + out.exists = true; + out.isDir = stat.isDirectory(); + if (!out.isDir) { + out.bytes = stat.size; + out.files = 1; + return out; + } + const walk = (dir) => { + let items; + try { + items = fs.readdirSync(dir, { withFileTypes: true }); + } catch { + return; + } + for (const item of items) { + const child = path.join(dir, item.name); + let s; + try { + s = fs.lstatSync(child); + } catch { + continue; + } + if (s.isDirectory()) walk(child); + else if (s.isFile()) { + out.files += 1; + out.bytes += s.size; + } + } + }; + walk(abs); + return out; +} + +async function writeLog(entry) { + const logRel = config.logFile ?? "00_索引\\操作日志.jsonl"; + const logAbs = path.join(config.root, logRel); + await fsp.mkdir(path.dirname(logAbs), { recursive: true }); + await fsp.appendFile( + logAbs, + `${JSON.stringify({ at: new Date().toISOString(), ...entry })}\n`, + "utf8", + ); +} + +const sameVolume = (a, b) => + path.parse(a).root.toLowerCase() === path.parse(b).root.toLowerCase(); + +// ---------- 操作计划(读操作也能生成计划) ---------- +function buildPlan({ op, from, to, folder }) { + switch (op) { + case "mkdir": { + const abs = resolveInside(folder); + const exists = fs.existsSync(abs); + return { + op, + from: null, + to: path.relative(config.root, abs).replace(/\\/g, "/"), + targetExists: exists, + blocked: exists ? "目标目录已存在" : null, + note: exists ? null : "将新建一个分类目录", + bytes: 0, + files: 0, + }; + } + case "move": + case "copy": { + const srcAbs = realInside(resolveInside(from)); + const dstAbs = resolveInside(to); + const src = statsOf(srcAbs); + if (!src.exists) return { op, from, to, blocked: "源不存在", bytes: 0, files: 0 }; + const targetExists = fs.existsSync(dstAbs); + const crossVolume = !sameVolume(srcAbs, dstAbs); + const blocked = + op === "move" && crossVolume + ? "跨盘移动不被允许(请改用复制)" + : targetExists + ? "目标已存在,请换一个名字" + : null; + return { + op, + from: path.relative(config.root, srcAbs).replace(/\\/g, "/"), + to: path.relative(config.root, dstAbs).replace(/\\/g, "/"), + bytes: src.bytes, + files: src.files, + isDir: src.isDir, + targetExists, + crossVolume, + blocked, + note: src.isDir + ? `目录,含 ${src.files} 个文件` + : `文件,${src.bytes} 字节`, + }; + } + case "extract": { + const srcAbs = realInside(resolveInside(from)); + if (!fs.existsSync(srcAbs)) return { op, from, to, blocked: "压缩包不存在", bytes: 0, files: 0 }; + const dstAbs = resolveInside(to); + const dst = statsOf(dstAbs); + const targetExists = dst.exists && dst.files > 0; + return { + op, + from: path.relative(config.root, srcAbs).replace(/\\/g, "/"), + to: path.relative(config.root, dstAbs).replace(/\\/g, "/"), + bytes: fs.statSync(srcAbs).size, + files: 0, + targetExists, + blocked: targetExists ? "目标目录已存在且非空" : null, + note: `解压到 ${path.relative(config.root, dstAbs).replace(/\\/g, "/") || "."}`, + }; + } + default: + throw httpError(400, `不支持的操作:${op}`); + } +} + +async function applyPlan({ op, from, to, folder }) { + const plan = buildPlan({ op, from, to, folder }); + if (plan.blocked) throw httpError(409, plan.blocked); + + if (op === "mkdir") { + const abs = resolveInside(folder); + await fsp.mkdir(abs, { recursive: true }); + } else if (op === "move") { + const abs = resolveInside(to); + await fsp.mkdir(path.dirname(abs), { recursive: true }); + await fsp.rename(resolveInside(from), abs); + } else if (op === "copy") { + const abs = resolveInside(to); + await fsp.mkdir(path.dirname(abs), { recursive: true }); + await fsp.cp(resolveInside(from), abs, { recursive: true, errorOnExist: true }); + } else if (op === "extract") { + if (!fs.existsSync(SEVEN_ZIP)) throw httpError(500, `找不到 7-Zip:${SEVEN_ZIP}`); + const abs = resolveInside(to); + await fsp.mkdir(abs, { recursive: true }); + const code = await new Promise((resolve) => { + const child = spawn( + SEVEN_ZIP, + ["x", resolveInside(from), `-o${abs}`, "-y", "-bso0", "-bsp0"], + { stdio: "ignore", windowsHide: true }, + ); + child.on("close", resolve); + child.on("error", () => resolve(-1)); + }); + if (code !== 0) throw httpError(500, `7-Zip 解压失败(退出码 ${code})`); + } + + await writeLog({ op, from: plan.from, to: plan.to, result: "ok" }); + scheduleRescan(); + return plan; +} + +// ---------- 读操作:交给系统 ---------- +function reveal(abs) { + spawn("explorer.exe", [`/select,${abs}`], { stdio: "ignore", windowsHide: false }).unref(); +} +function openWith(abs) { + spawn("cmd.exe", ["/c", "start", "", abs], { stdio: "ignore", windowsHide: true }).unref(); +} + +// ---------- 快照刷新与自动更新 ---------- +let timer = null; +function scheduleRescan(delay = 2500) { + clearTimeout(timer); + timer = setTimeout(async () => { + try { + const result = await regenerate({ quiet: true }); + state.lastScan = result.generated; + state.lastError = null; + broadcast({ type: "rescan", generated: result.generated, totals: result.totals }); + } catch (error) { + state.lastError = error.message; + } + }, delay); +} + +const clients = new Set(); +function broadcast(message) { + const line = `data: ${JSON.stringify(message)}\n\n`; + for (const res of clients) { + try { + res.write(line); + } catch { + clients.delete(res); + } + } +} + +function startWatcher() { + try { + fs.watch(config.root, { recursive: true }, (_event, filename) => { + if (filename && String(filename).includes("操作日志.jsonl")) return; + scheduleRescan(); + }); + state.watching = true; + } catch (error) { + state.watching = false; + state.lastError = `无法监视归档目录:${error.message}`; + } +} + +// ---------- HTTP ---------- +const indexAbs = path.join(projectRoot, config.indexFile ?? "public/archive-index.json"); +let indexCache = { mtime: 0, gzip: null, raw: null }; + +function readIndex() { + const mtime = fs.existsSync(indexAbs) ? fs.statSync(indexAbs).mtimeMs : 0; + if (indexCache.mtime !== mtime) { + const raw = mtime ? fs.readFileSync(indexAbs) : Buffer.from("{}"); + indexCache = { mtime, raw, gzip: zlib.gzipSync(raw) }; + } + return indexCache; +} + +function searchIndex(query, limit) { + const { raw } = readIndex(); + const payload = JSON.parse(raw.toString("utf8")); + const needle = query.trim().toLowerCase(); + if (!needle) return { total: payload.entries.length, hits: [] }; + const hits = []; + for (const entry of payload.entries) { + if (entry[0].toLowerCase().includes(needle)) { + hits.push(entry); + if (hits.length >= limit) break; + } + } + return { total: hits.length, hits }; +} + +async function readBody(req) { + const chunks = []; + let size = 0; + for await (const chunk of req) { + size += chunk.length; + if (size > 1_000_000) throw httpError(413, "请求体过大"); + chunks.push(chunk); + } + if (!chunks.length) return {}; + try { + return JSON.parse(Buffer.concat(chunks).toString("utf8")); + } catch { + throw httpError(400, "请求体不是合法 JSON"); + } +} + +const send = (res, status, body, headers = {}) => { + const isBuffer = Buffer.isBuffer(body); + const payload = isBuffer ? body : Buffer.from(JSON.stringify(body)); + res.writeHead(status, { + "content-type": isBuffer ? "application/octet-stream" : "application/json; charset=utf-8", + "cache-control": "no-store", + ...headers, + }); + res.end(payload); +}; + +const server = http.createServer(async (req, res) => { + const url = new URL(req.url ?? "/", `http://127.0.0.1:${PORT}`); + const route = url.pathname; + + // 事件流不需要令牌以外的额外处理,但同样要求令牌 + if (req.headers["x-archive-token"] !== TOKEN) { + return send(res, 401, { error: "访问令牌无效" }); + } + + if (route === "/events") { + res.writeHead(200, { + "content-type": "text/event-stream", + "cache-control": "no-store", + connection: "keep-alive", + }); + res.write(": connected\n\n"); + clients.add(res); + req.on("close", () => clients.delete(res)); + return; + } + + try { + if (route === "/status" && req.method === "GET") { + const { raw } = readIndex(); + const payload = JSON.parse(raw.toString("utf8")); + return send(res, 200, { + root: config.root, + watching: state.watching, + lastScan: state.lastScan ?? payload.generated ?? null, + lastError: state.lastError, + totals: payload.totals ?? null, + port: PORT, + sevenZip: fs.existsSync(SEVEN_ZIP), + }); + } + + if (route === "/index" && req.method === "GET") { + const { raw, gzip } = readIndex(); + if (String(req.headers["accept-encoding"] ?? "").includes("gzip")) + return send(res, 200, gzip, { + "content-type": "application/json; charset=utf-8", + "content-encoding": "gzip", + }); + return send(res, 200, raw, { "content-type": "application/json; charset=utf-8" }); + } + + if (route === "/search" && req.method === "GET") { + const q = url.searchParams.get("q") ?? ""; + const limit = Math.min(Number(url.searchParams.get("limit") ?? 200) || 200, 2000); + return send(res, 200, searchIndex(q, limit)); + } + + if (route === "/plan" && req.method === "POST") { + const body = await readBody(req); + return send(res, 200, buildPlan(body)); + } + + if (route === "/apply" && req.method === "POST") { + const body = await readBody(req); + if (body.confirm !== true) throw httpError(400, "写操作需要 confirm: true"); + const plan = await applyPlan(body); + return send(res, 200, { ok: true, plan }); + } + + if (route === "/reveal" && req.method === "POST") { + const body = await readBody(req); + const abs = realInside(resolveInside(body.path)); + if (!fs.existsSync(abs)) throw httpError(404, "路径不存在"); + reveal(abs); + return send(res, 200, { ok: true, action: "reveal", path: body.path }); + } + + if (route === "/open" && req.method === "POST") { + const body = await readBody(req); + const abs = realInside(resolveInside(body.path)); + if (!fs.existsSync(abs)) throw httpError(404, "路径不存在"); + openWith(abs); + return send(res, 200, { ok: true, action: "open", path: body.path }); + } + + if (route === "/refresh" && req.method === "POST") { + const result = await regenerate({ quiet: true }); + state.lastScan = result.generated; + state.lastError = null; + broadcast({ type: "rescan", generated: result.generated, totals: result.totals }); + return send(res, 200, { ok: true, generated: result.generated, totals: result.totals }); + } + + return send(res, 404, { error: `未知接口:${route}` }); + } catch (error) { + return send(res, error.status ?? 500, { error: error.message }); + } +}); + +server.listen(PORT, "127.0.0.1", async () => { + console.log(`归档服务已启动:http://127.0.0.1:${PORT}`); + console.log(`归档根目录:${config.root}`); + console.log(`访问令牌:已写入 ${path.relative(projectRoot, TOKEN_FILE)}`); + try { + const result = await regenerate({ quiet: true }); + state.lastScan = result.generated; + console.log( + `快照就绪:${result.records.length} 条精选档案,` + + `${result.totals.files} 个文件 / ${result.totals.dirs} 个目录`, + ); + } catch (error) { + state.lastError = error.message; + console.error(`首次生成快照失败:${error.message}`); + } + startWatcher(); + console.log(state.watching ? "已开始监视归档变动(自动更新快照)" : "监视未启用"); +}); diff --git a/scripts/archive-service.ps1 b/scripts/archive-service.ps1 new file mode 100644 index 0000000..939bc2d --- /dev/null +++ b/scripts/archive-service.ps1 @@ -0,0 +1,418 @@ +<# + Rhine Lab UI · 本地归档后端(「数据库」)服务管理 + + 这个后端就是 RhineLabUI 的数据层:它索引 E:\归档(当前 4.5 万文件 / 110 GB), + 生成 content/archives.json、public/archive-index.json 与 47 份摘要,并在归档变动时 + 自动重建快照;界面上的 LOCAL ARCHIVE 面板与「文件位置」都靠它。 + 它只监听 127.0.0.1,且每次启动生成一枚访问令牌写入 .archive-token。 + + 常用: + install 安装开机自启(登录时触发)+ 立即启动 ← 一次性 + ensure 没在跑就拉起,在跑就什么都不做 ← 一键脚本会调 + status 看状态(任务 / 端口 / 进程 / 快照 / 归档根) + stop 停掉后端 + uninstall 取消开机自启并停掉后端 + logs 看后端最近的输出 + + 自启的两种装法(install 会自己挑): + · 管理员会话 → 计划任务 WpywArchiveService(登录触发、失败自动重试 3 次) + · 普通会话 → 「启动」文件夹里的快捷方式(不需要提权,效果相同) + 实测本机普通会话注册计划任务会被「拒绝访问」,所以默认走「启动」文件夹这条。 + + 为什么用「登录时」而不是「开机 + SYSTEM」: + 1) 服务要用 explorer.exe /select 打开资源管理器定位文件(SHOW IN FOLDER)。 + 以 SYSTEM 跑会落在会话 0,用户屏幕上什么都不会出现 —— 那功能会静默失效。 + 2) 以当前用户跑,它写出的 archives.json / archive-index.json 归属正常, + 用户随后自己跑 npm run build 不会被权限挡住。 + 3) 开机触发要求把账户口令存进计划任务,本机没有这个必要。 + 结论:本机单人使用,登录即等于开机可用,且功能无损。窗口由 VBS 隐藏,不弹黑框。 +#> +[CmdletBinding()] +param( + [ValidateSet('ensure', 'start', 'stop', 'restart', 'status', 'install', 'uninstall', 'logs', 'run')] + [string]$Action = 'status' +) + +$ErrorActionPreference = 'Stop' + +$TaskName = 'WpywArchiveService' +$ProjectRoot = Split-Path -Parent $PSScriptRoot +$ConfigPath = Join-Path $ProjectRoot 'archive.config.json' +$ServiceJs = Join-Path $ProjectRoot 'scripts\archive-service.mjs' +$TokenFile = Join-Path $ProjectRoot '.archive-token' +$LogDir = Join-Path $ProjectRoot 'logs' +$LogFile = Join-Path $LogDir 'archive-service.log' +$HiddenVbs = Join-Path $ProjectRoot 'scripts\archive-service-hidden.vbs' +$WScript = Join-Path $env:SystemRoot 'System32\wscript.exe' +$StartupLnk = Join-Path ([Environment]::GetFolderPath([Environment+SpecialFolder]::Startup)) '归档后端 (Rhine Lab).lnk' + +function Write-Head($text) { Write-Host ''; Write-Host " $text" -ForegroundColor Cyan } +function Write-Item($label, $value) { + Write-Host (' {0,-12} {1}' -f $label, $value) +} +function Write-Ok($text) { Write-Host " $text" -ForegroundColor Green } +function Write-Info2($text) { Write-Host " $text" -ForegroundColor Gray } +function Write-Warn2($text) { Write-Host " $text" -ForegroundColor Yellow } + +function Get-NodeExe { + $cmd = Get-Command node.exe -ErrorAction SilentlyContinue + if ($cmd -and $cmd.Source) { return $cmd.Source } + $fallback = 'C:\Program Files\nodejs\node.exe' + if (Test-Path -LiteralPath $fallback) { return $fallback } + throw '找不到 node.exe,请先安装 Node.js。' +} + +function Get-ArchiveConfig { + if (-not (Test-Path -LiteralPath $ConfigPath)) { throw "缺少配置文件:$ConfigPath" } + return (Get-Content -LiteralPath $ConfigPath -Raw -Encoding UTF8 | ConvertFrom-Json) +} + +function Get-Port { + $cfg = Get-ArchiveConfig + if ($cfg.port) { return [int]$cfg.port } + return 43117 +} + +function Get-Token { + if (-not (Test-Path -LiteralPath $TokenFile)) { return '' } + return (Get-Content -LiteralPath $TokenFile -Raw).Trim() +} + +function Get-ListenerProcessId { + param([int]$Port) + $conn = Get-NetTCPConnection -LocalPort $Port -State Listen -ErrorAction SilentlyContinue | + Select-Object -First 1 + if ($conn) { return [int]$conn.OwningProcess } + return 0 +} + +function Get-CommandLineOf { + param([int]$ProcessId) + $p = Get-CimInstance Win32_Process -Filter "ProcessId=$ProcessId" -ErrorAction SilentlyContinue + if ($p) { return [string]$p.CommandLine } + return '' +} + +function Test-BackendListening { + param([int]$Port) + $procId = Get-ListenerProcessId -Port $Port + if ($procId -le 0) { return $false } + return ((Get-CommandLineOf -ProcessId $procId) -like '*archive-service.mjs*') +} + +function Get-BackendState { + param([int]$Port) + $state = [ordered]@{ + listening = $false; pid = 0; healthy = $false + watching = $false; lastScan = $null; totals = $null; root = $null; lastError = $null; httpError = $null + } + $procId = Get-ListenerProcessId -Port $Port + if ($procId -le 0) { return $state } + if ((Get-CommandLineOf -ProcessId $procId) -notlike '*archive-service.mjs*') { return $state } + $state.listening = $true + $state.pid = $procId + try { + $headers = @{} + $token = Get-Token + if ($token) { $headers['x-archive-token'] = $token } + $r = Invoke-RestMethod -Uri "http://127.0.0.1:$Port/status" -Headers $headers -TimeoutSec 5 + $state.healthy = $true + $state.watching = [bool]$r.watching + $state.lastScan = $r.lastScan + $state.totals = $r.totals + $state.root = $r.root + $state.lastError = $r.lastError + } catch { + $state.httpError = $_.Exception.Message + } + return $state +} + +function Wait-BackendReady { + param([int]$Port, [int]$TimeoutSec = 150) + $deadline = (Get-Date).AddSeconds($TimeoutSec) + while ((Get-Date) -lt $deadline) { + $s = Get-BackendState -Port $Port + # watching 只有首轮扫描 + 快照写完之后才会为真 + if ($s.healthy -and $s.watching) { return $s } + Start-Sleep -Milliseconds 800 + } + return (Get-BackendState -Port $Port) +} + +function Write-HiddenLauncher { + $node = Get-NodeExe + $ps1 = $PSCommandPath + $lines = @( + "' Generated by archive-service.ps1 - do not edit by hand." + "' Purpose: start the archive backend with a fully hidden window (style 0), so" + "' that logging on does not flash a black console. Also wraps it so the backend" + "' keeps its own log file. NOTE: this file is ASCII-only on purpose - wscript" + "' reads .vbs in the system ANSI code page, so non-ASCII here would garble." + 'Set sh = CreateObject("WScript.Shell")' + ('sh.CurrentDirectory = "' + $ProjectRoot + '"') + ('sh.Run "powershell.exe -NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -File " & Chr(34) & "' + $ps1 + '" & Chr(34) & " -Action run", 0, False') + ) + Set-Content -LiteralPath $HiddenVbs -Value $lines -Encoding ASCII + return $HiddenVbs +} + +function Test-Elevated { + $identity = [Security.Principal.WindowsIdentity]::GetCurrent() + return (New-Object Security.Principal.WindowsPrincipal($identity)).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) +} + +function Install-ScheduledTaskAutostart { + $vbs = Write-HiddenLauncher + $userId = "$env:USERDOMAIN\$env:USERNAME" + + $action = New-ScheduledTaskAction -Execute $WScript -Argument ('"{0}"' -f $vbs) -WorkingDirectory $ProjectRoot + $trigger = New-ScheduledTaskTrigger -AtLogOn -User $userId + $taskPrincipal = New-ScheduledTaskPrincipal -UserId $userId -LogonType Interactive -RunLevel Highest + $settings = New-ScheduledTaskSettingsSet ` + -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries ` + -ExecutionTimeLimit ([TimeSpan]::Zero) ` + -RestartCount 3 -RestartInterval (New-TimeSpan -Minutes 2) ` + -MultipleInstances IgnoreNew -StartWhenAvailable + + Register-ScheduledTask -TaskName $TaskName -Action $action -Trigger $trigger ` + -Principal $taskPrincipal -Settings $settings -Force -ErrorAction Stop ` + -Description '莱茵生命档案 UI 的本地归档后端(127.0.0.1:43117)。登录时后台静默启动,随归档变动自动重建索引。' | Out-Null + + Write-Ok "已注册计划任务 $TaskName(登录触发 / 后台静默 / 失败自动重试 3 次)" +} + +function Install-StartupShortcut { + $vbs = Write-HiddenLauncher + $dir = [Environment]::GetFolderPath([Environment+SpecialFolder]::Startup) + if (-not (Test-Path -LiteralPath $dir)) { New-Item -ItemType Directory -Path $dir -Force | Out-Null } + + $shell = New-Object -ComObject WScript.Shell + $lnk = $shell.CreateShortcut($StartupLnk) + $lnk.TargetPath = $WScript + $lnk.Arguments = '"' + $vbs + '"' + $lnk.WorkingDirectory = $ProjectRoot + $lnk.WindowStyle = 7 + $lnk.Description = '莱茵生命档案 UI 的本地归档后端(登录时后台静默启动)' + $lnk.Save() + [void][Runtime.InteropServices.Marshal]::ReleaseComObject($shell) + + Write-Ok "已在「启动」文件夹放置快捷方式:$StartupLnk" + Write-Info2 '登录后自动在后台静默启动,不弹窗口;删掉这个快捷方式即取消自启。' +} + +function Install-Autostart { + $vbs = Write-HiddenLauncher + + if (Test-Elevated) { + try { + Install-ScheduledTaskAutostart + } catch { + Write-Warn2 "计划任务注册失败($($_.Exception.Message)),改用「启动」文件夹。" + Install-StartupShortcut + } + } else { + Write-Info2 '当前不是管理员会话 —— 计划任务注册会被拒绝,改用「启动」文件夹实现登录自启。' + Write-Info2 '两者效果相同;想要带「失败自动重试」的计划任务版,请用管理员身份再跑一次 -Action install。' + Install-StartupShortcut + } + + if (-not (Test-BackendListening -Port (Get-Port))) { + $task = Get-ScheduledTask -TaskName $TaskName -ErrorAction SilentlyContinue + if ($task) { Start-ScheduledTask -TaskName $TaskName } + else { Start-Process -FilePath $WScript -ArgumentList ('"{0}"' -f $vbs) -WorkingDirectory $ProjectRoot | Out-Null } + Write-Host ' 已立即启动后端,等待首轮扫描(4.5 万条目,十几秒到一分钟)……' + $state = Wait-BackendReady -Port (Get-Port) -TimeoutSec 300 + if ($state.healthy -and $state.watching) { + Write-Ok "后端就绪:端口 $(Get-Port),PID $($state.pid)" + } else { + Write-Warn2 "后端尚未就绪,稍后用 -Action status 复查;日志:$LogFile" + } + } +} + +function Uninstall-Autostart { + Stop-Backend + $removed = $false + + $task = Get-ScheduledTask -TaskName $TaskName -ErrorAction SilentlyContinue + if ($task) { + Unregister-ScheduledTask -TaskName $TaskName -Confirm:$false -ErrorAction SilentlyContinue + Write-Ok "已删除计划任务 $TaskName" + $removed = $true + } + if (Test-Path -LiteralPath $StartupLnk) { + Remove-Item -LiteralPath $StartupLnk -Force + Write-Ok "已删除「启动」文件夹里的快捷方式" + $removed = $true + } + if (-not $removed) { Write-Warn2 '没有找到任何自启项,跳过。' } + Remove-Item -LiteralPath $HiddenVbs -Force -ErrorAction SilentlyContinue +} + +function Start-Backend { + $port = Get-Port + if (Test-BackendListening -Port $port) { + Write-Ok "后端已在运行(端口 $port)" + return $true + } + $other = Get-ListenerProcessId -Port $port + if ($other -gt 0) { + throw "端口 $port 已被 PID $other 占用,而它不是归档后端。请先处理冲突。" + } + + $node = Get-NodeExe + if (-not (Test-Path -LiteralPath $ServiceJs)) { throw "找不到后端脚本:$ServiceJs" } + if (-not (Test-Path -LiteralPath $LogDir)) { New-Item -ItemType Directory -Path $LogDir -Force | Out-Null } + + $task = Get-ScheduledTask -TaskName $TaskName -ErrorAction SilentlyContinue + if ($task) { + Start-ScheduledTask -TaskName $TaskName + } else { + $vbs = Write-HiddenLauncher + Start-Process -FilePath $WScript -ArgumentList ('"{0}"' -f $vbs) -WorkingDirectory $ProjectRoot | Out-Null + } + + Write-Host ' 已拉起后端,等待首轮扫描(4.5 万条目大约十几秒)……' + $state = Wait-BackendReady -Port $port -TimeoutSec 180 + if ($state.healthy -and $state.watching) { + Write-Ok "后端就绪:端口 $port,PID $($state.pid)" + return $true + } + Write-Warn2 "后端没有在预期时间内就绪。最近日志:$LogFile" + if ($state.httpError) { Write-Warn2 " HTTP:$($state.httpError)" } + if ($state.lastError) { Write-Warn2 " 快照:$($state.lastError)" } + return $false +} + +function Stop-Backend { + $port = Get-Port + $task = Get-ScheduledTask -TaskName $TaskName -ErrorAction SilentlyContinue + if ($task) { Stop-ScheduledTask -TaskName $TaskName -ErrorAction SilentlyContinue } + + $targets = New-Object System.Collections.Generic.List[int] + + $procId = Get-ListenerProcessId -Port $port + if ($procId -gt 0 -and (Get-CommandLineOf -ProcessId $procId) -like '*archive-service.mjs*') { + $targets.Add($procId) + } + # 兜底:按命令行找漏网的 node / 宿主 + foreach ($p in (Get-CimInstance Win32_Process -ErrorAction SilentlyContinue)) { + $cl = [string]$p.CommandLine + if (-not $cl) { continue } + if ($cl -like '*archive-service.mjs*') { $targets.Add([int]$p.ProcessId) } + elseif ($cl -like '*-Action run*' -and $cl -like '*archive-service.ps1*') { $targets.Add([int]$p.ProcessId) } + elseif ($cl -like '*archive-service-hidden.vbs*') { $targets.Add([int]$p.ProcessId) } + } + + $unique = $targets | Sort-Object -Unique + if ($unique.Count -eq 0) { + Write-Warn2 '后端本来就没在运行。' + return + } + foreach ($t in $unique) { Stop-Process -Id $t -Force -ErrorAction SilentlyContinue } + Start-Sleep -Milliseconds 600 + if (Get-ListenerProcessId -Port $port) { + Write-Warn2 "停止后端口 $port 仍被占用,请手动检查。" + } else { + Write-Ok "已停止后端(结束 $($unique.Count) 个进程)" + } +} + +function Show-Status { + $port = Get-Port + $cfg = Get-ArchiveConfig + $state = Get-BackendState -Port $port + + Write-Head '归档后端(数据库)' + Write-Item '归档根' "$($cfg.root)" + Write-Item '端口' "$port(仅本机 127.0.0.1)" + if ($state.listening) { + Write-Item '进程' "PID $($state.pid) 运行中" + } else { + Write-Item '进程' '未运行' + } + if ($state.healthy) { + Write-Item '快照' "已就绪 watching=$($state.watching)" + if ($state.totals) { + $gb = [math]::Round($state.totals.bytes / 1GB, 2) + Write-Item '索引' "$($state.totals.files) 文件 / $($state.totals.dirs) 目录 / $gb GB" + } + Write-Item '上次扫描' "$($state.lastScan)" + if ($state.lastError) { Write-Item '最近错误' "$($state.lastError)" } + } elseif ($state.listening) { + Write-Item '接口' "端口在听但 /status 不通:$($state.httpError)" + Write-Item '提示' '令牌可能是旧的,重启一次后端即可' + } else { + Write-Item '接口' '不可达(后端没在跑)' + } + + $tokenAge = '无令牌文件' + if (Test-Path -LiteralPath $TokenFile) { + $t = Get-Item -LiteralPath $TokenFile + $tokenAge = "$($t.LastWriteTime)($(($t.Length)) 字节)" + } + Write-Item '令牌' $tokenAge + + Write-Head '开机自启' + $installed = $false + + $task = Get-ScheduledTask -TaskName $TaskName -ErrorAction SilentlyContinue + if ($task) { + $info = Get-ScheduledTaskInfo -TaskName $TaskName -ErrorAction SilentlyContinue + Write-Item '计划任务' "$TaskName 状态=$($task.State)" + if ($info) { Write-Item '上次结果' "$($info.LastRunTime) code=$($info.LastTaskResult)" } + $installed = $true + } + + if (Test-Path -LiteralPath $StartupLnk) { + Write-Item '启动快捷方式' $StartupLnk + $installed = $true + } + + if ($installed) { + Write-Ok '已启用:登录时后台静默启动(不弹窗口)' + } else { + Write-Warn2 "未安装。执行 `"$($PSCommandPath) -Action install`" 可装上。" + } + Write-Host '' +} + +function Show-Logs { + if (-not (Test-Path -LiteralPath $LogFile)) { + Write-Warn2 "还没有日志:$LogFile" + return + } + Write-Head "后端日志尾部($LogFile)" + Get-Content -LiteralPath $LogFile -Tail 30 -Encoding UTF8 | ForEach-Object { Write-Host " $_" } + Write-Host '' +} + +function Invoke-BackendRun { + # 计划任务的真正入口:已经在跑就直接退出,避免重复实例抢端口 + $port = Get-Port + if (Test-BackendListening -Port $port) { + Write-Host "归档后端已在运行(端口 $port),本次启动跳过。" + return + } + if (-not (Test-Path -LiteralPath $LogDir)) { New-Item -ItemType Directory -Path $LogDir -Force | Out-Null } + $node = Get-NodeExe + Set-Location -LiteralPath $ProjectRoot + & $node $ServiceJs *> $LogFile +} + +switch ($Action) { + 'status' { Show-Status } + 'logs' { Show-Logs } + 'install' { Install-Autostart; Write-Host ''; Show-Status } + 'uninstall' { Uninstall-Autostart } + 'start' { Start-Backend | Out-Null } + 'stop' { Stop-Backend } + 'restart' { Stop-Backend; Start-Sleep -Seconds 1; Start-Backend | Out-Null } + 'ensure' { + # 明确给出跨进程可依赖的退出码:0 = 就绪,1 = 没能就绪 + $ok = Start-Backend + if ($ok) { exit 0 } else { exit 1 } + } + 'run' { Invoke-BackendRun } +} diff --git a/scripts/archive-source.mjs b/scripts/archive-source.mjs new file mode 100644 index 0000000..6e87096 --- /dev/null +++ b/scripts/archive-source.mjs @@ -0,0 +1,251 @@ +// 依据 archive.config.json 扫描真实归档,生成三份快照: +// content/archives.json 阵列用的四十条精选档案(简介来自 content/curated.mjs) +// public/archive-index.json 全量检索索引(供前端检索面板使用) +// public/archives/*.txt 每条档案的可下载摘要 +// +// 只读归档,不修改任何归档文件。服务与 watch 模式都调用这里的 regenerate()。 + +import fs from "node:fs"; +import fsp from "node:fs/promises"; +import path from "node:path"; +import { pathToFileURL, fileURLToPath } from "node:url"; +import { curated, categories, columns } from "../content/curated.mjs"; +import { validateContent, archiveText } from "./archive-content.mjs"; + +const here = path.dirname(fileURLToPath(import.meta.url)); +const projectRoot = path.resolve(here, ".."); + +export async function loadConfig() { + const raw = await fsp.readFile(path.join(projectRoot, "archive.config.json"), "utf8"); + const config = JSON.parse(raw); + if (typeof config.root !== "string" || !config.root.trim()) + throw new Error("archive.config.json:root 必须是非空字符串"); + return { ...config, root: path.resolve(config.root) }; +} + +const formatSize = (bytes) => { + if (bytes >= 1024 ** 3) return `${(bytes / 1024 ** 3).toFixed(2)} GB`; + if (bytes >= 1024 ** 2) return `${(bytes / 1024 ** 2).toFixed(1)} MB`; + if (bytes >= 1024) return `${(bytes / 1024).toFixed(0)} KB`; + return `${bytes} B`; +}; + +const formatDay = (ms) => { + const d = new Date(ms); + const p = (n) => String(n).padStart(2, "0"); + return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())}`; +}; + +// 递归扫描归档,返回条目数组与统计。 +// 条目为紧凑数组:[相对路径(/分隔), 'f'|'d', 字节数, mtime 毫秒] +export function scanArchive(root, exclude = []) { + const skip = new Set(exclude.map((name) => path.resolve(root, name))); + const entries = []; + const totals = { files: 0, dirs: 0, bytes: 0 }; + + const walk = (absDir, relDir) => { + let items; + try { + items = fs.readdirSync(absDir, { withFileTypes: true }); + } catch { + return; + } + for (const item of items) { + const abs = path.join(absDir, item.name); + const rel = relDir ? `${relDir}/${item.name}` : item.name; + let stat; + try { + stat = fs.lstatSync(abs); + } catch { + continue; + } + if (stat.isSymbolicLink()) { + // 只记录链接本身,不跟随,避免越出归档根目录。 + entries.push([rel, "l", 0, Math.round(stat.mtimeMs)]); + totals.files += 1; + continue; + } + if (stat.isDirectory()) { + entries.push([rel, "d", 0, Math.round(stat.mtimeMs)]); + totals.dirs += 1; + if (!skip.has(abs)) walk(abs, rel); + } else { + entries.push([rel, "f", stat.size, Math.round(stat.mtimeMs)]); + totals.files += 1; + totals.bytes += stat.size; + } + } + }; + + walk(root, ""); + return { entries, totals }; +} + +// 取某个归档条目的实时统计,并顺带收集其内部文件的 mtime 范围。 +function statEntry(root, relPath) { + const abs = path.join(root, relPath); + const result = { exists: false, bytes: 0, files: 0, min: Infinity, max: 0 }; + let stat; + try { + stat = fs.statSync(abs); + } catch { + return result; + } + result.exists = true; + if (stat.isFile()) { + result.bytes = stat.size; + result.files = 1; + result.min = result.max = stat.mtimeMs; + return result; + } + const walk = (dir) => { + let items; + try { + items = fs.readdirSync(dir, { withFileTypes: true }); + } catch { + return; + } + for (const item of items) { + const child = path.join(dir, item.name); + let s; + try { + s = fs.lstatSync(child); + } catch { + continue; + } + if (s.isDirectory()) walk(child); + else if (s.isFile()) { + result.files += 1; + result.bytes += s.size; + if (s.mtimeMs < result.min) result.min = s.mtimeMs; + if (s.mtimeMs > result.max) result.max = s.mtimeMs; + } + } + }; + walk(abs); + return result; +} + +function fill(text, stats) { + const range = + stats.min === Infinity + ? "无文件" + : formatDay(stats.min) === formatDay(stats.max) + ? formatDay(stats.min) + : `${formatDay(stats.min)} ~ ${formatDay(stats.max)}`; + return String(text) + .replaceAll("{size}", formatSize(stats.bytes)) + .replaceAll("{files}", String(stats.files)) + .replaceAll("{date}", range); +} + +export async function exportDownloads(records) { + const downloadDir = path.join(projectRoot, "public", "archives"); + await fsp.mkdir(downloadDir, { recursive: true }); + for (const record of records) { + await fsp.writeFile( + path.join(downloadDir, `RHINE-LAB-${record.id}.txt`), + archiveText(record), + "utf8", + ); + } +} + +export async function regenerate({ quiet = false } = {}) { + const config = await loadConfig(); + const log = (message) => { + if (!quiet) console.log(message); + }; + + const archivesJson = path.join(projectRoot, "content", "archives.json"); + + // 归档不在本机时(例如异地构建)沿用既有快照,只重新导出下载文件, + // 以免 build 因缺少本地归档而整个失败。 + if (!fs.existsSync(config.root)) { + const existing = JSON.parse(await fsp.readFile(archivesJson, "utf8")); + validateContent(existing); + await exportDownloads(existing.records); + log( + `归档根目录不可用(${config.root}),沿用既有 ${existing.records.length} 条档案快照。`, + ); + return { + records: existing.records, + totals: null, + generated: new Date().toISOString(), + fallback: true, + }; + } + + // 1) 四十条精选档案:用真实统计填充占位符 + const missing = []; + const records = curated.map((entry) => { + const stats = statEntry(config.root, entry.path); + if (!stats.exists) missing.push(`${entry.id} ${entry.path}`); + const source = pathToFileURL(path.join(config.root, entry.path)).href; + return { + id: entry.id, + title: entry.title, + en: entry.en, + category: entry.category, + department: entry.department, + date: entry.date, + lead: entry.lead, + clearance: entry.clearance, + abstract: fill(entry.abstract, stats), + findings: entry.findings.map((line) => fill(line, stats)), + source, + // 供前端与操作面板使用的真实路径信息(校验器允许附加字段) + path: entry.path, + stats: { + bytes: stats.bytes, + files: stats.files, + modified: stats.max ? new Date(stats.max).toISOString() : null, + }, + }; + }); + + if (missing.length) + throw new Error( + `以下精选条目在归档中不存在,已中止以免写出错误数据:\n- ${missing.join("\n- ")}`, + ); + + const content = { categories, columns, records }; + validateContent(content); // 校验不过就不写盘 + + // 2) 全量检索索引 + const { entries, totals } = scanArchive(config.root, config.exclude); + + const payload = { + generated: new Date().toISOString(), + root: config.root, + totals, + fields: ["path", "kind", "size", "mtime"], + entries, + }; + + const archivesJsonOut = path.join(projectRoot, "content", "archives.json"); + const indexJson = path.join(projectRoot, config.indexFile ?? "public/archive-index.json"); + await fsp.mkdir(path.dirname(indexJson), { recursive: true }); + await fsp.writeFile(archivesJsonOut, `${JSON.stringify(content, null, 2)}\n`, "utf8"); + await fsp.writeFile(indexJson, JSON.stringify(payload), "utf8"); + + // 3) 可下载摘要 + await exportDownloads(records); + + log( + `归档快照已更新:${records.length} 条精选档案,` + + `${totals.files} 个文件 / ${totals.dirs} 个目录 / ${formatSize(totals.bytes)}`, + ); + return { records, totals, generated: payload.generated }; +} + +const invokedDirectly = + process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href; +if (invokedDirectly) { + try { + await regenerate(); + } catch (error) { + console.error(`生成归档快照失败:${error.message}`); + process.exitCode = 1; + } +} diff --git a/scripts/build-icons.mjs b/scripts/build-icons.mjs new file mode 100644 index 0000000..d1c410d --- /dev/null +++ b/scripts/build-icons.mjs @@ -0,0 +1,13 @@ +// Rasterize the existing shared vector mark; no alternate logo or generated art. +import { mkdir, writeFile } from "node:fs/promises"; +import { resolve } from "node:path"; +import { pathToFileURL } from "node:url"; +import { labelMarkSvg } from "../src/brand.ts"; +const {default:sharp}=await import(process.env.SHARP_MODULE ? pathToFileURL(resolve(process.env.SHARP_MODULE)).href : 'sharp'); +const mark=labelMarkSvg.replace(/^]*>/,'').replace(/<\/svg>$/,''); +const svg=`${mark}`; +await mkdir('public/icons',{recursive:true}); +await writeFile('public/icons/app-icon.svg',svg); +for(const [name,size] of [['apple-touch-icon',180],['icon-192',192],['icon-512',512],['icon-maskable-512',512]]) + await sharp(Buffer.from(svg)).resize(size,size).png().toFile(`public/icons/${name}.png`); +console.log('Shared Rhine Lab mark exported to four home-screen icons.'); diff --git a/scripts/build-pwa.mjs b/scripts/build-pwa.mjs new file mode 100644 index 0000000..96588c9 --- /dev/null +++ b/scripts/build-pwa.mjs @@ -0,0 +1,19 @@ +import { readdir, readFile, writeFile } from "node:fs/promises"; +import { createHash } from "node:crypto"; +import { resolve } from "node:path"; +const root=resolve('dist'); +const all=await readdir(root,{recursive:true}); +const files=all.map(path=>path.replaceAll('\\','/')).filter(path=> + path==='index.html'||path==='manifest.webmanifest'||path==='favicon.svg'|| + /^(assets|icons|archives|licenses)\/[^/]+\.[^/]+$/.test(path)|| + /^fonts\/.*\.(woff2|pdf|txt|json|md)$/.test(path)|| + /^audio\/(atmosphere|motif|pulse)\.ogg$/.test(path) +).filter(path=>!/^assets\/archive-(cassette|assembly)\.glb$/.test(path)).sort(); +if(!files.some(path=>/^assets\/index-.*\.js$/.test(path)))throw Error('Build the application before generating the offline cache.'); +const worker=await readFile('scripts/pwa-worker.js','utf8'); +const hash=createHash('sha256').update(worker);let bytes=0; +for(const file of files){const content=await readFile(resolve(root,file));hash.update(file).update(content);bytes+=content.length} +const version=hash.digest('hex').slice(0,16); +await writeFile(resolve(root,'sw.js'),worker.replace('__CACHE_VERSION__',JSON.stringify(version)).replace('__PRECACHE_FILES__',JSON.stringify(files))); +await writeFile(resolve(root,'pwa-build.json'),JSON.stringify({version,bytes,files},null,2)); +console.log(`Offline release ${version}: ${files.length} files, ${(bytes/1024/1024).toFixed(1)} MiB.`); diff --git a/scripts/build-wallpaper.mjs b/scripts/build-wallpaper.mjs new file mode 100644 index 0000000..6f45a96 --- /dev/null +++ b/scripts/build-wallpaper.mjs @@ -0,0 +1,30 @@ +import { copyFile, readFile, readdir, rm, stat, writeFile } from "node:fs/promises"; +import { resolve, relative, sep } from "node:path"; + +const root = resolve("release/wallpaper"); +const project = JSON.parse(await readFile("wallpaper/project.json", "utf8")); +if (project.general?.supportsaudioprocessing !== true || Object.hasOwn(project, "supportsaudioprocessing")) + throw new Error("Wallpaper audio requires general.supportsaudioprocessing=true"); +// Only trim generated output, never the source public directory. +for (const name of ["update.html", "update.js", "manifest.webmanifest", "audio/observatory-preview.mp3", "assets/archive-cassette.glb", "assets/archive-assembly.glb"]) { + const target = resolve(root, name); + if (!target.startsWith(root + sep)) throw new Error("Output path escapes wallpaper directory"); + await rm(target, { force: true }); +} +await copyFile("wallpaper/project.json", resolve(root, "project.json")); +await copyFile("docs/media/archive.jpg", resolve(root, "preview.jpg")); +if (project.preview === "preview.gif") await copyFile("wallpaper/preview.gif", resolve(root, "preview.gif")); +await copyFile("LICENSE", resolve(root, "LICENSE")); +const html = await readFile(resolve(root, "index.html"), "utf8"); +if (/\b(?:src|href)=["']\//.test(html)) throw new Error("Wallpaper HTML contains root-relative URLs"); +const files = []; +async function walk(dir) { + for (const entry of await readdir(dir, { withFileTypes: true })) { + const path = resolve(dir, entry.name); + if (entry.isDirectory()) await walk(path); + else files.push({ path: relative(root, path).replaceAll("\\", "/"), bytes: (await stat(path)).size }); + } +} +await walk(root); +await writeFile(resolve(root, "build-files.json"), JSON.stringify(files, null, 2) + "\n"); +console.log(`Wallpaper ready: ${root} (${files.length} files, ${(files.reduce((n, f) => n + f.bytes, 0) / 1048576).toFixed(1)} MiB)`); diff --git a/scripts/capture-readme.mjs b/scripts/capture-readme.mjs new file mode 100644 index 0000000..8398ec2 --- /dev/null +++ b/scripts/capture-readme.mjs @@ -0,0 +1,250 @@ +// Run against a local dev server. Requires Playwright, Chrome and FFmpeg. +// PLAYWRIGHT_MODULE may point to an existing Playwright index.mjs. +// FFMPEG may point to an existing ffmpeg executable. +import { mkdir, writeFile } from "node:fs/promises"; +import { spawnSync } from "node:child_process"; +import { resolve } from "node:path"; +import { pathToFileURL } from "node:url"; + +const { chromium } = await import( + process.env.PLAYWRIGHT_MODULE + ? pathToFileURL(resolve(process.env.PLAYWRIGHT_MODULE)).href + : "playwright" +); +const ffmpeg = process.env.FFMPEG || "ffmpeg"; +const base = process.env.CAPTURE_URL || "http://127.0.0.1:5186"; +const output = resolve("docs/media"); +const temporary = resolve(".tools/readme-capture"); +await mkdir(output, { recursive: true }); +await mkdir(temporary, { recursive: true }); +const browser = await chromium.launch({ + channel: "chrome", + headless: true, + args: + process.platform === "win32" + ? ["--use-angle=d3d11", "--enable-gpu", "--ignore-gpu-blocklist"] + : ["--enable-gpu"], +}); +const context = await browser.newContext({ + viewport: { width: 1600, height: 900 }, + deviceScaleFactor: 1, +}); +const page = await context.newPage(); +const errors = []; +page.on("pageerror", (error) => errors.push(error.message)); +page.on("console", (message) => { + if (message.type() === "error") errors.push(message.text()); +}); +const session = await context.newCDPSession(page); +const manifest = { + commit: spawnSync("git", ["rev-parse", "HEAD"], { + encoding: "utf8", + }).stdout.trim(), + capturedAt: new Date().toISOString(), + viewport: "1600x900", + quality: "original", + gifs: [], + screenshots: [], + errors, +}; +const wait = (ms) => page.waitForTimeout(ms); +const click = (selector) => page.locator(selector).click(); +async function shot(name) { + await page.mouse.move(1580, 880); + await page.screenshot({ + path: resolve(output, `${name}.jpg`), + type: "jpeg", + quality: 93, + }); + manifest.screenshots.push(name); +} +function encode(args) { + const result = spawnSync( + ffmpeg, + ["-y", "-hide_banner", "-loglevel", "error", ...args], + { encoding: "utf8" }, + ); + if (result.status !== 0) + throw new Error(result.stderr || `FFmpeg failed: ${result.error}`); +} +async function record(name, actions, width = 800) { + const fps = name === "browse" ? 8 : 12; + if (name === "browse") width = 640; + const folder = resolve(temporary, `${name}-${Date.now()}`); + await mkdir(folder, { recursive: true }); + const frames = []; + const writes = []; + const onFrame = (event) => { + const filename = `frame-${String(frames.length).padStart(5, "0")}.jpg`; + frames.push({ filename, timestamp: event.metadata.timestamp }); + writes.push( + writeFile(resolve(folder, filename), Buffer.from(event.data, "base64")), + ); + void session + .send("Page.screencastFrameAck", { sessionId: event.sessionId }) + .catch(() => {}); + }; + session.on("Page.screencastFrame", onFrame); + await session.send("Page.startScreencast", { + format: "jpeg", + quality: 92, + maxWidth: 1600, + maxHeight: 900, + everyNthFrame: 3, + }); + try { + await wait(250); + await actions(); + } finally { + await session.send("Page.stopScreencast"); + session.off("Page.screencastFrame", onFrame); + await Promise.all(writes); + } + if (frames.length < 10) throw new Error(`Too few frames for ${name}`); + const intervals = frames + .slice(1) + .map((frame, i) => frame.timestamp - frames[i].timestamp); + const duration = frames.at(-1).timestamp - frames[0].timestamp; + await writeFile( + resolve(folder, "frames.txt"), + frames + .map( + (frame, i) => + `file '${frame.filename}'\nduration ${intervals[i] || 1 / 12}\n`, + ) + .join("") + `file '${frames.at(-1).filename}'\n`, + ); + encode([ + "-f", + "concat", + "-safe", + "0", + "-i", + resolve(folder, "frames.txt"), + "-vf", + `fps=${fps},scale=${width}:-2:flags=lanczos,split[a][b];[a]palettegen=max_colors=96:stats_mode=full[p];[b][p]paletteuse=dither=bayer:bayer_scale=5:diff_mode=rectangle`, + "-loop", + "0", + resolve(output, `${name}.gif`), + ]); + manifest.gifs.push({ + name, + frames: frames.length, + duration, + width, + fps, + averageCaptureFps: frames.length / duration, + maximumFrameGap: Math.max(...intervals), + }); + console.log( + `Captured ${name}: ${duration.toFixed(2)}s, ${frames.length} source frames`, + ); +} +try { + await page.goto(`${base}/?scene=archive`); + await page.waitForFunction( + () => window.rhine?.stats().ready && !document.querySelector("#loading"), + null, + { timeout: 60000 }, + ); + await page.evaluate(() => document.fonts.ready); + await wait(6000); + await shot("archive"); + await record("browse", async () => { + for (const key of [ + "ArrowDown", + "ArrowDown", + "ArrowRight", + "ArrowDown", + "ArrowRight", + "ArrowLeft", + "ArrowLeft", + ]) { + await page.keyboard.press(key); + await wait(700); + } + await wait(1000); + }); + await page.evaluate(() => window.rhine.select(0)); + await wait(1600); + await record( + "decryption", + async () => { + await page.keyboard.press("Enter"); + await wait(7500); + }, + 960, + ); + await page.waitForFunction( + () => window.rhine.stats().decryption.phase === "clear", + ); + await shot("detail"); + await click('[data-tab="notes"]'); + await wait(450); + await shot("research"); + await click('[data-action="model-viewer"]'); + await page.waitForFunction( + () => document.querySelector('[data-viewer="explode"]')?.disabled === false, + ); + await wait(1700); + await shot("viewer-clear"); + await record("glass-motion", async () => { + await click('[data-viewer="frosted"]'); + await wait(1700); + await shot("viewer-frosted"); + await click('[data-viewer="clear"]'); + await wait(1700); + }); + await record("assembly-motion", async () => { + await click('[data-viewer="explode"]'); + await wait(1800); + await shot("assembly"); + await page.mouse.move(850, 460); + await page.mouse.down(); + for (let i = 1; i <= 30; i++) { + await page.mouse.move(850 + i * 6, 460 + i); + await wait(25); + } + await page.mouse.up(); + await wait(600); + await click('[data-viewer="reset"]'); + await wait(850); + await click('[data-viewer="assemble"]'); + await wait(1800); + }); + await click('[data-viewer="close"]'); + await wait(450); + await page.keyboard.press("Escape"); + await wait(1800); + await click('[data-action="search"]'); + await wait(400); + await page.locator("#archive-search").fill("莱茵"); + await wait(500); + await shot("search"); + await page.keyboard.press("Escape"); + await wait(350); + await click('[data-action="settings"]'); + await wait(400); + await shot("settings"); + await page.keyboard.press("Escape"); + await wait(350); + await record("boot-motion", async () => { + await click('[data-action="replay"]'); + await wait(6500); + await shot("boot"); + await wait(13000); + }); + manifest.stats = await page.evaluate(() => window.rhine.stats()); + await writeFile( + resolve(temporary, "manifest.json"), + JSON.stringify(manifest, null, 2), + ); + if (errors.length) throw new Error(errors.join("\n")); + const { stats, ...publicManifest } = manifest; + await writeFile( + resolve(output, "capture.json"), + JSON.stringify(publicManifest, null, 2) + "\n", + ); +} finally { + await browser.close(); +} diff --git a/scripts/check-appearance.mjs b/scripts/check-appearance.mjs new file mode 100644 index 0000000..af0c8bc --- /dev/null +++ b/scripts/check-appearance.mjs @@ -0,0 +1,101 @@ +import assert from "node:assert/strict"; +import * as THREE from "three"; +import { CardAppearance } from "../src/appearance.ts"; + +const appearance = new CardAppearance(); +const high = new THREE.MeshPhysicalMaterial({ + color: "#fffdfa", + transmission: 0.9, + roughness: 0.21, + attenuationColor: "#eee6df", + attenuationDistance: 2, +}); +const low = new THREE.MeshPhysicalMaterial({ + color: "#fff7ed", + transmission: 0.78, + roughness: 0.28, + attenuationColor: "#d4c7b4", + attenuationDistance: 1.2, +}); +appearance.register("Frosted_Polymer", high, low); +const group = new THREE.Group(); +const body = new THREE.Mesh(new THREE.BoxGeometry(), high); +body.userData.surface = "Frosted_Polymer"; +group.add(body); +appearance.prepare(group); +appearance.apply(group, 0); +assert.ok(body.material.color.equals(low.color)); +assert.equal(body.material.transmission, low.transmission); +assert.ok(body.material.attenuationColor.equals(low.attenuationColor)); +assert.equal(body.material.attenuationDistance, low.attenuationDistance); +let last = body.material.transmission; +for (let i = 1; i <= 100; i++) { + appearance.apply(group, i / 100); + assert.ok( + Math.abs(body.material.transmission - last) < 0.00121, + "Surface changes continuously", + ); + last = body.material.transmission; + assert.ok( + body.material.attenuationColor.equals( + low.attenuationColor.clone().lerp(high.attenuationColor, i / 100), + ), + ); + assert.equal( + body.material.attenuationDistance, + THREE.MathUtils.lerp(1.2, 2, i / 100), + ); +} +assert.ok(body.material.color.equals(high.color)); +assert.equal(body.material.transmission, high.transmission); +assert.ok(body.material.attenuationColor.equals(high.attenuationColor)); + +// Opaque glTF surfaces may be Standard materials without absorption properties. +const opaque = new THREE.MeshStandardMaterial({ color: "#c7beb6" }); +appearance.register("Opaque", opaque, opaque.clone()); +const solid = new THREE.Mesh(new THREE.BoxGeometry(), opaque); +solid.userData.surface = "Opaque"; +const solids = new THREE.Group(); +solids.add(solid); +appearance.prepare(solids); +assert.doesNotThrow(() => appearance.apply(solids, 0.5)); + +// Default unbounded absorption must not turn into NaN during interpolation. +const clear = new THREE.MeshPhysicalMaterial(); +appearance.register("Clear", clear, clear.clone()); +const glass = new THREE.Mesh(new THREE.BoxGeometry(), clear); +glass.userData.surface = "Clear"; +solids.add(glass); +appearance.prepare(solids); +appearance.apply(solids, 0.5); +assert.equal(glass.material.attenuationDistance, Infinity); +appearance.dispose(solids); + +// A file selected again while descending must keep its own material state. +const returning = group.clone(true); +appearance.prepare(returning); +appearance.apply(returning, 0.37); +appearance.apply(group, 0.81); +assert.notEqual(returning.children[0].material, body.material); +assert.equal(returning.children[0].userData.appearance.value, 0.37); +assert.equal(body.userData.appearance.value, 0.81); +appearance.apply(returning, 1e-8); +assert.ok( + Math.abs(returning.children[0].material.transmission - low.transmission) < + 1e-8, + "Array handoff has the same surface", +); + +const shader = { + uniforms: {}, + vertexShader: "#include ", + fragmentShader: "#include \n#include ", +}; +body.material.onBeforeCompile(shader, null); +assert.equal(shader.uniforms.archiveQuality, body.userData.appearance); +assert.ok(shader.fragmentShader.includes("roughnessFactor = mix(0.28")); +appearance.dispose(returning); +assert.ok(body.material.color.r > 0); +console.log( + "Appearance interpolation, independent returning materials, shader uniform and array handoff: passed", +); diff --git a/scripts/check-archive-diagonal.mjs b/scripts/check-archive-diagonal.mjs new file mode 100644 index 0000000..035d1cd --- /dev/null +++ b/scripts/check-archive-diagonal.mjs @@ -0,0 +1,208 @@ +import assert from "node:assert/strict"; +import { mkdir, writeFile } from "node:fs/promises"; +import { resolve } from "node:path"; +import { pathToFileURL } from "node:url"; +const { chromium } = await import( + process.env.PLAYWRIGHT_MODULE + ? pathToFileURL(resolve(process.env.PLAYWRIGHT_MODULE)).href + : "playwright" +); +const browser = await chromium.launch({ + channel: "chrome", + headless: true, + args: ["--use-angle=d3d11", "--enable-gpu", "--ignore-gpu-blocklist"], +}); +const report = []; +await mkdir(".tools/array-input", { recursive: true }); +const stats = (page) => page.evaluate(() => rhine.stats()); +try { + for (const [width, height, mobile] of [ + [1920, 1080, false], + [390, 844, true], + [844, 390, true], + ].filter( + ([w, h]) => + !process.env.REVIEW_CASES || + process.env.REVIEW_CASES.split(",").includes(`${w}x${h}`), + )) { + const context = await browser.newContext({ + viewport: { width, height }, + hasTouch: mobile, + isMobile: mobile, + }); + const page = await context.newPage(), + errors = []; + page.on("pageerror", (e) => errors.push(e.message)); + await page.goto( + `${process.env.REVIEW_URL || "http://127.0.0.1:5204"}/?scene=archive`, + ); + await page.waitForFunction( + () => window.rhine?.stats().extraction >= 0.399, + null, + { timeout: 60000 }, + ); + await page.locator('[data-action="next"]').click(); + await page.waitForTimeout(2200); + const cdp = mobile ? await context.newCDPSession(page) : null; + const landscape = mobile && width > height; + const x = width * (landscape ? 0.4 : 0.65), + y = height * (landscape ? 0.52 : 0.3); + const down = async () => + mobile + ? cdp.send("Input.dispatchTouchEvent", { + type: "touchStart", + touchPoints: [{ x, y, id: 1 }], + }) + : (await page.mouse.move(x, y), page.mouse.down()); + const move = async (px, py) => + mobile + ? cdp.send("Input.dispatchTouchEvent", { + type: "touchMove", + touchPoints: [{ x: px, y: py, id: 1 }], + }) + : page.mouse.move(px, py); + const up = async () => + mobile + ? cdp.send("Input.dispatchTouchEvent", { + type: "touchEnd", + touchPoints: [], + }) + : page.mouse.up(); + const results = []; + for (const axis of ["lane", "row"]) + for (const sign of [1, -1]) { + const before = await stats(page); + const vector = before.dragProjection[axis]; + assert.ok( + Math.abs(vector.x) > 1 && Math.abs(vector.y) > 1, + "The actual camera produces diagonal axes", + ); + const amount = (axis === "lane" ? 0.8 : 1.8) * sign; + await down(); + const captured = await stats(page); + assert.ok( + captured.holdingArchive, + "Gesture starts in the canvas, clear of the document controls", + ); + // Use the camera at pointer-down; mouse parallax can slightly change it. + const direction = captured.dragProjection[axis]; + await move(x + direction.x * amount, y + direction.y * amount); + await page.waitForTimeout(160); + const during = await stats(page); + assert.equal( + during.dragMapping, + "free", + `${width}x${height}: ${axis} uses the camera projection`, + ); + assert.ok(during.dragTrack); + const track = axis === "lane" ? "columnCamera" : "rail", + spacing = axis === "lane" ? 5.2 : -0.62; + assert.ok( + Math.abs((during[track] - captured[track]) / spacing - amount) < 0.04, + "Projected travel follows the requested physical distance", + ); + assert.equal( + during.selectedCell[axis], + before.selectedCell[axis] + Math.round(amount), + ); + if (axis === "row") + assert.equal( + during.selectedCell.lane, + before.selectedCell.lane, + "Depth drag keeps its column", + ); + await up(); + await page.waitForFunction(() => !rhine.stats().archiveMomentum, null, { + timeout: 12000, + }); + results.push({ axis, sign, direction, mapping: during.dragMapping }); + } + // One held gesture moves freely in screen space and turns without relocking. + await down(); + const anchor = await stats(page), + basis = anchor.dragProjection; + const paths = [ + [45, 0], + [45, 50], + [-35, 50], + [-35, -30], + [0, 0], + ]; + for (const [dx, dy] of paths) { + await move(x + dx, y + dy); + await page.waitForTimeout(100); + const state = await stats(page); + const lane = (state.columnCamera - anchor.columnCamera) / 5.2; + const row = (state.rail - anchor.rail) / -0.62; + assert.ok( + Math.abs(lane * basis.lane.x + row * basis.row.x - dx) < 2, + "Screen X follows the pointer", + ); + assert.ok( + Math.abs(lane * basis.lane.y + row * basis.row.y - dy) < 2, + "Screen Y follows the pointer", + ); + assert.equal(state.dragMapping, "free"); + } + await up(); + await page.waitForFunction(() => !rhine.stats().archiveMomentum); + results.push({ screenPath: paths, checks: "free turns passed" }); + if (!mobile) { + await down(); + const releaseBasis = (await stats(page)).dragProjection; + for (let i = 1; i <= 4; i++) { + await move( + x + ((releaseBasis.lane.x * 0.4 + releaseBasis.row.x * 1.8) * i) / 4, + y + ((releaseBasis.lane.y * 0.4 + releaseBasis.row.y * 1.8) * i) / 4, + ); + await page.waitForTimeout(8); + } + await up(); + const release = (await stats(page)).archiveMomentum; + assert.equal(release?.phase, "coasting"); + assert.ok(release.velocity.lane > 0 && release.velocity.row > 0); + await page.waitForTimeout(250); + const coast = (await stats(page)).archiveMomentum; + assert.equal(coast?.phase, "coasting"); + assert.ok( + coast.value.lane > release.value.lane && + coast.value.row > release.value.row, + ); + assert.ok( + Math.abs( + coast.velocity.lane / coast.velocity.row - + release.velocity.lane / release.velocity.row, + ) < 0.001, + ); + await down(); + const caught = await stats(page); + assert.equal(caught.archiveMomentum, null); + await page.waitForTimeout(120); + const held = await stats(page); + assert.equal(held.columnCamera, caught.columnCamera); + assert.equal(held.rail, caught.rail); + await up(); + await page.waitForTimeout(1500); + results.push({ + checks: "free coast and two-track catch passed", + release, + }); + } + await page.screenshot({ + path: resolve(`.tools/array-input/diagonal-${width}x${height}.png`), + }); + assert.deepEqual(errors, []); + report.push({ width, height, mobile, results, checks: "passed" }); + console.log( + `${width}x${height}: projected axes, free screen paths and turns passed`, + ); + await context.close(); + } +} finally { + await mkdir(".tools/array-input", { recursive: true }); + await writeFile( + ".tools/array-input/diagonal.json", + JSON.stringify(report, null, 2), + ); + await browser.close(); +} diff --git a/scripts/check-archive-drag.mjs b/scripts/check-archive-drag.mjs new file mode 100644 index 0000000..ce0db08 --- /dev/null +++ b/scripts/check-archive-drag.mjs @@ -0,0 +1,154 @@ +import assert from "node:assert/strict"; +import { + ArchiveDrag, + ArchiveMomentum, + ArchivePlaneMomentum, +} from "../src/archive-drag.ts"; +const projection = { lane: { x: -240, y: 120 }, row: { x: -50, y: -15 } }; +const close = (a, b, message) => assert.ok(Math.abs(a - b) < 1e-9, message); +const drag = new ArchiveDrag(); +drag.start(500, 500, projection); +drag.move(504, 502, 10); +assert.equal(drag.active, false); +assert.equal(drag.moved, false); +// Every screen direction decomposes back into the same displayed displacement. +for (const [dx, dy] of [ + [-120, 60], + [-100, -30], + [100, 0], + [0, -100], + [50, 90], + [-30, 20], +]) { + drag.move(500 + dx, 500 + dy, 40); + assert.equal(drag.active, true); + close( + drag.value.lane * projection.lane.x + drag.value.row * projection.row.x, + dx, + ); + close( + drag.value.lane * projection.lane.y + drag.value.row * projection.row.y, + dy, + ); +} +// A single held gesture can turn from one track into the other. +drag.move(380, 560, 60); +close(drag.value.lane, 0.5); +close(drag.value.row, 0); +drag.move(400, 470, 80); +close(drag.value.lane, 0); +close(drag.value.row, 2); +drag.move(500, 500, 100); +assert.equal(drag.moved, true, "Out-and-back cannot become a click"); +close(drag.value.lane, 0); +close(drag.value.row, 0); +assert.deepEqual(drag.releaseVelocity(200, false), { lane: 0, row: 0 }); +assert.deepEqual(drag.releaseVelocity(100, true), { lane: 0, row: 0 }); +const alternate = { lane: { x: 180, y: 100 }, row: { x: 40, y: -12 } }; +drag.start(500, 500, alternate); +alternate.lane.x = 0; +drag.move(590, 550, 40); +close(drag.value.lane, 0.5, "Freeze camera mapping for the gesture"); +close(drag.value.row, 0); +for (const invalid of [ + { lane: { x: 1, y: 1 }, row: { x: 2, y: 2 } }, + { lane: { x: NaN, y: 1 }, row: { x: 0, y: 1 } }, +]) { + drag.start(0, 0, invalid); + drag.move(100, 100, 50); + assert.equal( + drag.active, + false, + "Degenerate projection must not generate unbounded motion", + ); +} +// Reverse the full screen path: momentum follows the final direction. +drag.start(0, 0, projection); +drag.move(-100, -30, 20); +drag.move(-100, -30, 30); +drag.move(-75, -22.5, 45); +assert.ok(drag.releaseVelocity(45, false).row < 0); +console.log( + "Free projection, direction changes, jitter, reversal and reduced motion passed.", +); +const fling = (duration) => { + const input = new ArchiveDrag(); + input.start(0, 0, projection); + for (let i = 1; i <= 12; i++) + input.move( + (projection.row.x * 1.6 * i) / 12, + (projection.row.y * 1.6 * i) / 12, + (duration * i) / 12, + ); + return new ArchiveMomentum( + input.value.row, + input.releaseVelocity(duration, false).row, + ); +}; +const fast = fling(80), + slow = fling(800); +assert.ok(fast.velocity > slow.velocity * 8); +assert.equal(fast.value, slow.value); +const fastStart = fast.value; +const fastVelocity = fast.velocity; +fast.step(1 / 60); +assert.ok( + fast.value > fastStart, + "The first released frame continues from the pointer", +); +assert.ok(fast.velocity < fastVelocity && fast.velocity > fastVelocity * 0.9); +for (let i = 0; i < 600; i++) { + fast.step(1 / 120); + slow.step(1 / 120); +} +assert.equal(fast.phase, "idle"); +assert.equal(slow.phase, "idle"); +assert.ok(fast.value > slow.value + 5, "A fast flick crosses many more files"); +assert.ok( + fast.value - fastStart > 3, + "Travel is no longer limited to three files", +); +assert.equal(fast.value, Math.round(fast.value)); + +const simulate = (fps) => { + const motion = new ArchiveMomentum(12.3, 24); + for (let i = 0; i < fps; i++) motion.step(1 / fps); + return motion; +}; +const at30 = simulate(30), + at60 = simulate(60), + at120 = simulate(120); +assert.ok(Math.abs(at30.value - at120.value) < 1e-10); +assert.ok(Math.abs(at60.velocity - at120.velocity) < 1e-10); +const backward = new ArchiveMomentum(-3.2, -35); +for (let i = 0; i < 600; i++) backward.step(1 / 120); +assert.equal(backward.phase, "idle"); +assert.ok(backward.value < -15, "Negative positions continue through the loop"); +console.log( + JSON.stringify({ + fastRest: fast.value, + slowRest: slow.value, + frameRateIndependent: true, + }), +); + +const plane = new ArchivePlaneMomentum( + { lane: 2.3, row: 12.8 }, + { lane: 2, row: 24 }, +); +let previous = plane.value; +for (let i = 0; i < 90; i++) { + if (plane.phase !== "coasting") break; + plane.step(1 / 60); + close( + (plane.value.lane - previous.lane) * 12, + plane.value.row - previous.row, + "Both tracks keep the screen direction, even when the smaller component is slow", + ); + previous = plane.value; +} +for (let i = 0; i < 600; i++) plane.step(1 / 120); +assert.equal(plane.phase, "idle"); +assert.equal(plane.value.lane, Math.round(plane.value.lane)); +assert.equal(plane.value.row, Math.round(plane.value.row)); +console.log("Two-dimensional coasting and final grid snap passed."); diff --git a/scripts/check-archive-momentum.mjs b/scripts/check-archive-momentum.mjs new file mode 100644 index 0000000..1d356c7 --- /dev/null +++ b/scripts/check-archive-momentum.mjs @@ -0,0 +1,205 @@ +import assert from "node:assert/strict"; +import { mkdir, writeFile } from "node:fs/promises"; +import { resolve } from "node:path"; +import { pathToFileURL } from "node:url"; +const { chromium } = await import( + process.env.PLAYWRIGHT_MODULE + ? pathToFileURL(resolve(process.env.PLAYWRIGHT_MODULE)).href + : "playwright" +); +const browser = await chromium.launch({ + channel: "chrome", + headless: true, + args: ["--use-angle=d3d11", "--enable-gpu", "--ignore-gpu-blocklist"], +}); +const report = []; +const stats = (page) => page.evaluate(() => window.rhine.stats()); +const rest = (page) => + page.waitForFunction(() => !window.rhine.stats().archiveMomentum, null, { + timeout: 12000, + }); +try { + for (const mobile of [false, true].filter( + (mobile) => + !process.env.REVIEW_CASES || + process.env.REVIEW_CASES === (mobile ? "mobile" : "desktop"), + )) { + const width = mobile ? 390 : 1920, + height = mobile ? 844 : 1080; + const context = await browser.newContext({ + viewport: { width, height }, + hasTouch: mobile, + isMobile: mobile, + }); + const page = await context.newPage(), + errors = []; + page.on("pageerror", (e) => errors.push(e.message)); + await page.goto( + `${process.env.REVIEW_URL || "http://127.0.0.1:5204"}/?scene=archive`, + ); + await page.waitForFunction( + () => window.rhine?.stats().extraction >= 0.399, + null, + { timeout: 60000 }, + ); + await page.locator('[data-action="next"]').click(); + await page.waitForTimeout(2200); + const cdp = mobile ? await context.newCDPSession(page) : null; + const x = width * 0.6, + y = height * 0.38; + const down = async () => + mobile + ? cdp.send("Input.dispatchTouchEvent", { + type: "touchStart", + touchPoints: [{ x, y, id: 1 }], + }) + : (await page.mouse.move(x, y), page.mouse.down()); + const move = async (px, py) => + mobile + ? cdp.send("Input.dispatchTouchEvent", { + type: "touchMove", + touchPoints: [{ x: px, y: py, id: 1 }], + }) + : page.mouse.move(px, py); + const up = async () => + mobile + ? cdp.send("Input.dispatchTouchEvent", { + type: "touchEnd", + touchPoints: [], + }) + : page.mouse.up(); + const fling = async (delay, axis = "row") => { + await down(); + const vector = (await stats(page)).dragProjection[axis]; + const travel = axis === "row" ? 3.2 : 0.8; + const segments = delay < 30 ? 4 : 8; + for (let i = 1; i <= segments; i++) { + await page.waitForTimeout(delay); + await move( + x + (vector.x * travel * i) / segments, + y + (vector.y * travel * i) / segments, + ); + } + await up(); + }; + const startSlow = await stats(page); + await fling(90); + await rest(page); + const slow = await stats(page); + const slowDistance = slow.selectedCell.row - startSlow.selectedCell.row; + await fling(8); + const released = await stats(page); + assert.equal(released.archiveMomentum?.phase, "coasting"); + assert.ok(released.archiveMomentum.velocity.row > 2); + await page.waitForTimeout(450); + const moving = await stats(page); + assert.ok( + moving.selectedCell.row > released.selectedCell.row, + "Files change as the released array passes them", + ); + assert.ok( + moving.rail < released.rail - 0.3, + "Released array keeps travelling", + ); + assert.ok( + moving.archiveMomentum.velocity.row < + released.archiveMomentum.velocity.row, + "Speed decays continuously", + ); + await rest(page); + const fast = await stats(page); + const fastDistance = fast.selectedCell.row - slow.selectedCell.row; + console.log({ + mobile, + slowDistance, + fastDistance, + releaseVelocity: released.archiveMomentum.velocity.row, + }); + assert.ok( + fastDistance > slowDistance, + "Fast travel goes farther than identical slow travel", + ); + assert.ok( + fastDistance > 3, + "Fast scrolling is no longer capped at three files", + ); + + // Catch the array without moving the pointer, then take over on another axis. + await fling(8); + await page.waitForTimeout(120); + await down(); + const caught = await stats(page); + assert.equal(caught.archiveMomentum, null); + assert.equal(caught.holdingArchive, true); + await page.waitForTimeout(220); + const held = await stats(page); + assert.equal( + held.rail, + caught.rail, + "Pressing catches the current rail before direction lock", + ); + assert.deepEqual(held.selectedCell, caught.selectedCell); + const capturedVector = caught.dragProjection.lane; + await move(x + capturedVector.x * 0.8, y + capturedVector.y * 0.8); + await page.waitForTimeout(160); + await up(); + await rest(page); + assert.equal( + (await stats(page)).selectedCell.lane, + caught.selectedCell.lane + 1, + ); + + await fling(8, "lane"); + const lateral = await stats(page); + assert.ok( + lateral.archiveMomentum?.velocity.lane > 0, + JSON.stringify(lateral.archiveMomentum), + ); + await page.waitForTimeout(250); + assert.ok((await stats(page)).columnCamera > lateral.columnCamera + 0.5); + await rest(page); + + await fling(8); + await page.keyboard.press("ArrowDown"); + const keyboard = await stats(page); + assert.equal( + keyboard.archiveMomentum, + null, + "Keyboard selection stops the previous coast", + ); + await page.waitForTimeout(500); + assert.deepEqual((await stats(page)).selectedCell, keyboard.selectedCell); + + await page.locator('[data-action="settings"]').click(); + await page.locator('[data-pref="reduced"]').check({ force: true }); + await page.locator('[data-action="close-modal"]').click(); + await page.waitForFunction( + () => !document.querySelector(".modal-backdrop"), + ); + await fling(8); + assert.equal( + (await stats(page)).archiveMomentum, + null, + "Reduced motion skips continued scrolling", + ); + assert.deepEqual(errors, []); + report.push({ + mobile, + slowDistance, + fastDistance, + releaseVelocity: released.archiveMomentum.velocity.row, + releaseRow: released.selectedCell.row, + movingRow: moving.selectedCell.row, + checks: "passed", + }); + console.log(report.at(-1)); + await context.close(); + } +} finally { + await mkdir(".tools/array-input", { recursive: true }); + await writeFile( + resolve(".tools/array-input/momentum.json"), + JSON.stringify(report, null, 2), + ); + await browser.close(); +} diff --git a/scripts/check-archive-visibility.mjs b/scripts/check-archive-visibility.mjs new file mode 100644 index 0000000..0ecf1e8 --- /dev/null +++ b/scripts/check-archive-visibility.mjs @@ -0,0 +1,35 @@ +import assert from 'node:assert/strict'; +import * as THREE from 'three'; +import { ArchiveVisibility } from '../src/archive-visibility.ts'; +const visibility=new ArchiveVisibility(); +const summaries=[]; +for(const [width,height] of [[1920,1080],[2560,1080],[3840,1080],[1400,1050],[1080,1920]])for(const detail of [false,true]) { + const camera=new THREE.PerspectiveCamera(3.0,width/height,5,300); + const direction=new THREE.Vector3(...(detail?[-.277,.238,.931]:[-.81,.326,.487])).normalize(); + const distance=detail?72:140; + const span=detail?5.9:widthvisibility.intersects((c.lane-2)*5.2,-4.6,(c.row-15.5)*.62)); + const keys=new Set(selected.map(c=>`${c.lane}:${c.row}`));assert.equal(keys.size,selected.length); + // Independently scan a much larger reference lattice. Every intersecting card + // must have been generated by the projected footprint, at both extreme lifts. + const candidateKeys=new Set(candidates.map(c=>`${c.lane}:${c.row}`)); + const viewProjection=new THREE.Matrix4().multiplyMatrices(camera.projectionMatrix,camera.matrixWorldInverse); + for(const y of [-6,1.6])for(let lane=-30;lane<=30;lane++)for(let row=-180;row<=180;row++) { + for(const dx of [-2.5,0,2.5])for(const dy of [0,1.85,3.7])for(const dz of [-.3,.3]) { + const point=new THREE.Vector3((lane-2)*5.2+dx,y+dy,(row-15.5)*.62+dz); + const depth=-point.clone().applyMatrix4(camera.matrixWorldInverse).z; + point.applyMatrix4(viewProjection); + if(Math.abs(point.x)<=1.18&&Math.abs(point.y)<=1.18&&depth>=5&&depth<=distance+33)assert.ok(candidateKeys.has(`${lane}:${row}`),`Missing ${width}x${height} ${lane}:${row} y=${y}`); + } + } + const extra=visibility.update(camera,distance+25,0,0,true).filter(c=>visibility.intersects((c.lane-2)*5.2,-4.6,(c.row-15.5)*.62)); + assert.ok(extra.length>=selected.length); + const shifted=visibility.update(camera,distance+25,52,-6.2,false).filter(c=>visibility.intersects((c.lane-2)*5.2-52,-4.6,(c.row-15.5)*.62-6.2)); + assert.equal(shifted.length,selected.length,'Whole-cell track movement preserves view coverage'); + summaries.push({width,height,detail,drawn:selected.length,candidates:candidates.length,extra:extra.length}); +} +assert.ok(summaries[4].drawn>summaries[0].drawn,'Wider view increases archive count'); +console.table(summaries);console.log('Projected coverage, extreme heights, unique picking cells, overscan and track translation passed.'); diff --git a/scripts/check-archive.mjs b/scripts/check-archive.mjs new file mode 100644 index 0000000..8281a27 --- /dev/null +++ b/scripts/check-archive.mjs @@ -0,0 +1,143 @@ +import assert from "node:assert/strict"; +import { readFile } from "node:fs/promises"; +import * as THREE from "three"; +import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js"; +import { + records, + archiveColumns, + columnFiles, + fileLocation, + fileAtSlot, +} from "../src/data.ts"; +import { + cinematicField, + columnStrength, + INSPECTION_LIFT, + returnStep, + damp, +} from "../src/motion.ts"; + +// 条数不再有上限(校验器只要求每列 ≥1、总数 ≥5),因此这里不写死 40 / 8, +// 而是按数据推导并检查真正的契约:每列非空、列内槽位互不冲突。 +assert.ok(records.length >= 5, "Archive must carry at least five documents"); +const slots = new Set(); +const perColumn = []; +let maxPerColumn = 0; +for (let lane = 0; lane < archiveColumns.length; lane++) { + const files = columnFiles(lane); + assert.ok(files.length >= 1, "Every column has at least one readable file"); + perColumn.push(files.length); + maxPerColumn = Math.max(maxPerColumn, files.length); + const laneSlots = new Set(); + for (const index of files) { + const location = fileLocation(index); + assert.equal(location.lane, lane); + assert.equal(fileAtSlot(location.slot), index); + assert.ok(location.row >= 0 && location.row < 32); + // slot = lane * 32 + (row % 32):同一列里超过 32 条才会开始复用槽位, + // 届时 fileAtSlot(截断语义)本就不是用来寻址这些条目的,所以只断言列内不冲突。 + assert.ok( + !laneSlots.has(location.slot) || files.length > 32, + `Slots within one column must stay distinct (lane ${lane})`, + ); + laneSlots.add(location.slot); + slots.add(location.slot); + const record = records[index]; + // 归档内容取代原 demo 之后,字数与条数不再固定: + // 契约以 scripts/archive-content.mjs 的校验器为准——摘要非空、研究记录至少一条。 + // 这里保留一个极低的下限,只防止空壳内容混入。 + assert.ok( + record.abstract.trim().length > 20, + `Record abstract must carry real text (got ${record.abstract.trim().length} chars)`, + ); + assert.ok( + record.findings.length >= 1 && + record.findings.every((line) => String(line).trim().length > 0), + "Every record carries at least one non-empty research note", + ); + // 本地归档条目的参考链接是 file:(浏览器不允许网页跳转,仅供溯源); + // 公开设定类条目仍用 https:。 + assert.ok( + ["https:", "file:"].includes(new URL(record.source).protocol), + `Record source must be https: or file: (got ${new URL(record.source).protocol})`, + ); + } +} +// 每列不超过 32 条时,所有条目的槽位应当恰好铺满、互不重叠。 +if (maxPerColumn <= 32) { + assert.equal(slots.size, records.length, "No two documents occupy the same slot"); +} +const crests = Array.from({ length: 5 }, (_, lane) => + Math.max( + ...Array.from({ length: 32 }, (_, row) => cinematicField(row, lane, 25.4)), + ), +); +assert.ok( + Math.max(...crests) - Math.min(...crests) < 1e-9, + "Frame 760 crests share the same height", +); +assert.ok(columnStrength(0, 2) >= 0.25, "Other columns retain a visible wave"); +assert.ok(columnStrength(2, 2) > columnStrength(1, 2)); +let maxDelta = 0; +for (let f = 750; f < 786; f++) { + for (let row = 0; row < 32; row++) + for (let lane = 0; lane < 5; lane++) { + maxDelta = Math.max( + maxDelta, + Math.abs( + cinematicField(row, lane, (f + 1) / 25 - 5) - + cinematicField(row, lane, f / 25 - 5), + ), + ); + } +} +assert.ok( + maxDelta < 0.65, + "The equal-crest to selected-column handoff is continuous", +); + +// Validate against the delivered Blender model, not a duplicate nominal box. +const bytes = await readFile( + new URL("../public/assets/archive-cassette.glb", import.meta.url), +); +const gltf = await new GLTFLoader().parseAsync( + bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength), + "", +); +const box = new THREE.Box3().setFromObject(gltf.scene); +const height = box.max.y - box.min.y; +assert.ok( + INSPECTION_LIFT - height > 0.25, + "Inspection clears the adjacent card while staying near the array", +); +assert.ok(INSPECTION_LIFT <= 4.1, "Inspection lift remains modest"); +let angle = 0.8, + elapsed = 0; +while (angle !== 0 && elapsed < 2) { + angle = returnStep(angle, 1 / 60); + elapsed += 1 / 60; +} +assert.equal(angle, 0, "Alignment finishes exactly before insertion"); +assert.ok(elapsed > 0.5 && elapsed < 1.2); +const coarse = { value: 2, velocity: 0 }, + fine = { ...coarse }; +for (let i = 0; i < 30; i++) damp(coarse, 3, 4, 1 / 30); +for (let i = 0; i < 120; i++) damp(fine, 3, 4, 1 / 120); +assert.ok(Math.abs(coarse.value - fine.value) < 1e-9); +console.log( + JSON.stringify( + { + documents: records.length, + perColumn, + maxPerColumn, + crestsAt760: crests, + maxFrameDelta: maxDelta, + modelHeight: height, + inspectionLift: INSPECTION_LIFT, + alignmentSeconds: elapsed, + checks: "passed", + }, + null, + 2, + ), +); diff --git a/scripts/check-array-input.mjs b/scripts/check-array-input.mjs new file mode 100644 index 0000000..7b7ed54 --- /dev/null +++ b/scripts/check-array-input.mjs @@ -0,0 +1,294 @@ +import assert from "node:assert/strict"; +import { mkdir, writeFile } from "node:fs/promises"; +import { resolve } from "node:path"; +import { pathToFileURL } from "node:url"; +const { chromium } = await import( + process.env.PLAYWRIGHT_MODULE + ? pathToFileURL(resolve(process.env.PLAYWRIGHT_MODULE)).href + : "playwright" +); +const browser = await chromium.launch({ + channel: "chrome", + headless: true, + args: ["--use-angle=d3d11", "--enable-gpu", "--ignore-gpu-blocklist"], +}); +const output = resolve(".tools/array-input"); +await mkdir(output, { recursive: true }); +const report = []; +const stats = (page) => page.evaluate(() => window.rhine.stats()); +const settle = (page) => page.waitForTimeout(2200); +try { + for (const mobile of [false, true].filter( + (mobile) => + !process.env.REVIEW_CASES || + process.env.REVIEW_CASES === (mobile ? "mobile" : "desktop"), + )) { + const width = mobile ? 390 : 1920, + height = mobile ? 844 : 1080; + const context = await browser.newContext({ + viewport: { width, height }, + hasTouch: mobile, + isMobile: mobile, + }); + const page = await context.newPage(), + errors = []; + page.on("pageerror", (e) => errors.push(e.message)); + await page.goto( + `${process.env.REVIEW_URL || "http://127.0.0.1:5204"}/?scene=archive`, + ); + await page.waitForFunction( + () => window.rhine?.stats().ready && !document.querySelector("#loading"), + null, + { timeout: 60000 }, + ); + await page.waitForFunction( + () => window.rhine.stats().extraction >= 0.399, + null, + { timeout: 60000 }, + ); + await settle(page); + const start = await stats(page); + const x = width * 0.55, + y = height * 0.3; + const laneStep = Math.max(100, Math.min(280, width * 0.24)); + const rowStep = Math.max(72, Math.min(150, height * 0.14)); + const cdp = mobile ? await context.newCDPSession(page) : null; + let origin, projection, inspecting, pointer; + const downRaw = async (x, y) => + mobile + ? cdp.send("Input.dispatchTouchEvent", { + type: "touchStart", + touchPoints: [{ x, y, id: 1 }], + }) + : (await page.mouse.move(x, y), page.mouse.down()); + const moveRaw = async (x, y) => + mobile + ? cdp.send("Input.dispatchTouchEvent", { + type: "touchMove", + touchPoints: [{ x, y, id: 1 }], + }) + : page.mouse.move(x, y); + const down = async (px, py) => { + await downRaw(px, py); + const state = await stats(page); + origin = { x: px, y: py }; + pointer = origin; + projection = state.dragProjection; + inspecting = state.canInspect; + }; + // Existing behavioral cases use logical column/file distances; project them. + const move = async (px, py) => { + if (inspecting) return moveRaw(px, py); + const lane = (origin.x - px) / laneStep, + row = (origin.y - py) / rowStep; + pointer = { + x: origin.x + lane * projection.lane.x + row * projection.row.x, + y: origin.y + lane * projection.lane.y + row * projection.row.y, + }; + return moveRaw(pointer.x, pointer.y); + }; + const up = async () => + mobile + ? cdp.send("Input.dispatchTouchEvent", { + type: "touchEnd", + touchPoints: [], + }) + : page.mouse.up(); + await down(x, y); + await move(x - laneStep * 0.3, y + 2); + await page.waitForTimeout(150); + const partial = await stats(page); + assert.ok(partial.dragTrack); + assert.equal(partial.selectedCell.lane, start.selectedCell.lane); + assert.ok( + partial.columnCamera > start.columnCamera + 1, + "Array follows before selecting the next cell", + ); + await move(x - laneStep * 0.8, y + 3); + await page.waitForTimeout(150); + assert.equal( + (await stats(page)).selectedCell.lane, + start.selectedCell.lane + 1, + "Selection changes before release", + ); + await page.screenshot({ + path: resolve(output, `${mobile ? "touch" : "mouse"}-drag.png`), + }); + await up(); + await settle(page); + const lane = await stats(page); + assert.equal(lane.selectedCell.lane, start.selectedCell.lane + 1); + assert.equal(lane.dragTrack, null); + assert.ok( + Math.abs(lane.columnCamera - (lane.selectedCell.lane - 2) * 5.2) < 0.03, + ); + await down(x, y); + await move(x + 2, y - rowStep * 0.8); + await page.waitForTimeout(150); + assert.equal( + (await stats(page)).selectedCell.row, + lane.selectedCell.row + 1, + ); + await move(x + 4, y + rowStep * 0.8); + await page.waitForTimeout(150); + const reverse = await stats(page); + assert.equal(reverse.selectedCell.lane, lane.selectedCell.lane); + assert.equal( + reverse.selectedCell.row, + lane.selectedCell.row - 1, + "Reversing mid-drag switches to previous file", + ); + await up(); + await settle(page); + if (!mobile) { + // Find a visible cover using actual scene hit-testing, then hold the pointer still. + let hit = false; + for (const point of [ + [960, 450], + [800, 440], + [1100, 400], + [600, 500], + [1300, 400], + ]) { + await page.mouse.move(...point); + await page.waitForTimeout(50); + if ((await stats(page)).hoverCell) { + hit = true; + break; + } + } + assert.ok(hit, "A visible card can be hovered"); + await page.waitForTimeout(450); + const hovered = await stats(page); + assert.ok( + Object.values(hovered.hoverLifts).some((v) => v > 0.27), + "Hover raises the card", + ); + assert.equal( + hovered.extraction, + 0.4, + "Hover does not change extraction or camera progress", + ); + await page.screenshot({ path: resolve(output, "mouse-hover.png") }); + await page.mouse.down(); + await page.mouse.up(); + assert.deepEqual( + (await stats(page)).selectedCell, + hovered.hoverCell, + "Click still selects the hovered physical cell", + ); + await page.mouse.move(5, 5); + await page.waitForTimeout(700); + assert.equal((await stats(page)).hoverCell, null); + assert.deepEqual((await stats(page)).hoverLifts, {}); + await page.mouse.move(x, y); + const beforeWheel = await stats(page); + await page.mouse.wheel(0, 100); + await page.waitForTimeout(100); + assert.equal( + (await stats(page)).selectedCell.row, + beforeWheel.selectedCell.row + 1, + ); + await page.mouse.wheel(0, -100); + await settle(page); + assert.equal( + (await stats(page)).selectedCell.row, + beforeWheel.selectedCell.row, + ); + await page.locator('[data-action="settings"]').click(); + await page.waitForTimeout(350); + const modal = await stats(page); + await page.mouse.move(x, y); + await page.mouse.wheel(0, 400); + await page.waitForTimeout(200); + assert.deepEqual( + (await stats(page)).selectedCell, + modal.selectedCell, + "Modal blocks array wheel navigation", + ); + await page.locator('[data-action="close-modal"]').click(); + await page.waitForFunction( + () => !document.querySelector(".modal-backdrop"), + ); + } + // Run across a complete row cycle, checking physical direction at the seam. + const loop = await stats(page); + for (let i = 0; i < 8; i++) + await page.locator('[data-action="next"]').click(); + assert.equal((await stats(page)).selected, loop.selected); + assert.equal( + (await stats(page)).selectedCell.row, + loop.selectedCell.row + 8, + ); + await settle(page); + const interrupted = await stats(page); + await down(x, y); + await move(x - laneStep * 0.3, y); + await page.waitForTimeout(100); + if (mobile) { + await cdp.send("Input.dispatchTouchEvent", { + type: "touchStart", + touchPoints: [ + { ...pointer, id: 1 }, + { x: x + 30, y: y + 30, id: 2 }, + ], + }); + } else { + await page.evaluate(() => + document.querySelector("#three-scene canvas").releasePointerCapture(1), + ); + } + await up(); + await settle(page); + assert.equal( + (await stats(page)).dragTrack, + null, + "Interrupted drag clears capture state", + ); + assert.deepEqual( + (await stats(page)).selectedCell, + interrupted.selectedCell, + "Interrupted partial drag does not select", + ); + // A subsequent gesture must work after cancellation / multi-touch. + await down(x, y); + await move(x, y - rowStep * 0.8); + await page.waitForTimeout(150); + await up(); + await settle(page); + assert.equal( + (await stats(page)).selectedCell.row, + interrupted.selectedCell.row + 1, + ); + await page.locator(".read-file").click(); + await page.waitForFunction(() => window.rhine.stats().canInspect, null, { + timeout: 30000, + }); + const detail = await stats(page); + await down(x, y); + await move(x + 60, y + 5); + await up(); + await page.waitForTimeout(500); + assert.deepEqual( + (await stats(page)).selectedCell, + detail.selectedCell, + "Detail drag only rotates", + ); + assert.ok((await stats(page)).rotation > 0.05); + assert.deepEqual(errors, []); + report.push({ + mobile, + start: start.selectedCell, + after: reverse.selectedCell, + checks: "passed", + }); + console.log(`${mobile ? "Touch" : "Mouse"} input passed`); + await context.close(); + } +} finally { + await writeFile( + resolve(output, "report.json"), + JSON.stringify(report, null, 2), + ); + await browser.close(); +} diff --git a/scripts/check-assembly.mjs b/scripts/check-assembly.mjs new file mode 100644 index 0000000..0b026fa --- /dev/null +++ b/scripts/check-assembly.mjs @@ -0,0 +1,111 @@ +import assert from "node:assert/strict"; +import { readFile } from "node:fs/promises"; +import * as THREE from "three"; +import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js"; +import { damp } from "../src/motion.ts"; +async function load(name) { + const b = await readFile( + new URL("../public/assets/" + name, import.meta.url), + ); + return ( + await new GLTFLoader().parseAsync( + b.buffer.slice(b.byteOffset, b.byteOffset + b.byteLength), + "", + ) + ).scene; +} +const [original, assembly] = await Promise.all([ + load("archive-cassette.glb"), + load("archive-assembly.glb"), +]); +const parts = new Map(); +const vertices = (scene, collect = false) => { + const points = new Map(); + scene.updateMatrixWorld(true); + scene.traverse((mesh) => { + if (!mesh.isMesh) return; + const surface = mesh.material.name.replace(/\.\d+$/, ""); + if (surface === "Carbon_Ink") return; + if (collect) { + assert.ok( + mesh.userData.assemblyPart, + "Every mesh belongs to a physical assembly", + ); + parts.set( + mesh.userData.assemblyPart, + (parts.get(mesh.userData.assemblyPart) || 0) + 1, + ); + } + const position = mesh.geometry.attributes.position; + for (let i = 0; i < position.count; i++) { + const point = new THREE.Vector3() + .fromBufferAttribute(position, i) + .applyMatrix4(mesh.matrixWorld); + points.set(surface + ":" + point.toArray().join(","), { surface, point }); + } + }); + return points; +}; +const a = vertices(original), + b = vertices(assembly, true); +assert.deepEqual([...parts.keys()].sort(), [ + "carrier", + "cover", + "fasteners", + "optical-core", + "optical-lenses", + "substrate", +]); +// Compare actual distances: rounding to a fixed grid can split equivalent +// float32 coordinates on either side of a rounding boundary after Blender joins. +function maxVertexError(from, to) { + const surfaces = new Map(); + for (const { surface, point } of to.values()) { + if (!surfaces.has(surface)) surfaces.set(surface, []); + surfaces.get(surface).push(point); + } + let max = 0; + for (const { surface, point } of from.values()) { + const candidates = surfaces.get(surface) || []; + let nearest = Infinity; + for (const candidate of candidates) + nearest = Math.min(nearest, point.distanceToSquared(candidate)); + max = Math.max(max, Math.sqrt(nearest)); + } + return max; +} +const vertexError = Math.max(maxVertexError(a, b), maxVertexError(b, a)); +assert.ok( + vertexError < 1e-5, + `Regrouping must retain assembled geometry within float32 tolerance: ${vertexError}`, +); +const height = new THREE.Box3() + .setFromObject(assembly) + .getSize(new THREE.Vector3()).y; +assert.ok(Math.abs(height - 3.7) < 1e-5); +// Interrupted motion retains position and velocity, then converges exactly enough +// for the viewer to snap to the original assembly pose. +const spread = { value: 0, velocity: 0 }; +for (let i = 0; i < 25; i++) damp(spread, 1, 5.5, 1 / 60); +const interrupted = spread.value; +damp(spread, 0, 5.5, 1 / 60); +assert.ok( + Math.abs(spread.value - interrupted) < 0.05, + "Reassembly must not jump on reversal", +); +for (let i = 0; i < 180; i++) damp(spread, 0, 5.5, 1 / 60); +assert.ok(Math.abs(spread.value) < 0.0001 && Math.abs(spread.velocity) < 0.001); +console.log( + JSON.stringify( + { + parts: Object.fromEntries(parts), + uniqueSurfaceVertices: a.size, + vertexError, + modelHeight: height, + reassembly: spread.value, + checks: "passed", + }, + null, + 2, + ), +); diff --git a/scripts/check-boot-hud.mjs b/scripts/check-boot-hud.mjs new file mode 100644 index 0000000..2025707 --- /dev/null +++ b/scripts/check-boot-hud.mjs @@ -0,0 +1,33 @@ +import assert from 'node:assert/strict'; +import {createRequire} from 'node:module'; +import {createServer} from 'node:http'; +import {readFile,mkdir,writeFile} from 'node:fs/promises'; +import {resolve,extname} from 'node:path'; +const {chromium}=createRequire(import.meta.url)(process.env.PLAYWRIGHT_MODULE||'playwright'); +const server=createServer(async(req,res)=>{try{let path=new URL(req.url,'http://localhost').pathname;if(path==='/')path='/index.html';let data=await readFile(resolve('release/wallpaper'+path));if(path.endsWith('.html'))data=Buffer.from(data.toString().replace('',``));res.setHeader('Content-Type',({'.js':'text/javascript','.css':'text/css','.html':'text/html','.woff2':'font/woff2'})[extname(path)]||'application/octet-stream');res.end(data)}catch{res.statusCode=404;res.end()}}); +await new Promise(r=>server.listen(5189,'127.0.0.1',r)); +const browser=await chromium.launch({channel:'msedge',headless:true}); +try { + const page=await browser.newPage({viewport:{width:1600,height:900}}),errors=[],results=[]; + page.on('pageerror',e=>errors.push(e.message)); + const apply=values=>page.evaluate(values=>wallpaperPropertyListener.applyUserProperties(Object.fromEntries(Object.entries(values).map(([k,value])=>[k,{value}]))),values); + const shot=()=>page.evaluate(()=>({mode:rhine.stats().mode,hud:document.querySelector('#stage').dataset.hudDepth,tracking:document.querySelector('#stage').dataset.hudTracking,panels:[...document.querySelectorAll('.boot .hud-surface')].map(n=>({name:n.className,base:n.style.transform,projection:n.style.getPropertyValue('--hud-projection'),transform:getComputedStyle(n).transform}))})); + await page.goto('http://127.0.0.1:5189/');await page.waitForFunction(()=>window.rhine?.stats().ready);await page.waitForTimeout(600); + await page.evaluate(()=>rhine.seek(7));await page.waitForTimeout(150); + const glyphs=await page.evaluate(async()=>{const text=document.querySelector('.boot-logo text'),first=text.firstChild;let mutations=0;const observer=new MutationObserver(records=>mutations+=records.length);observer.observe(text,{childList:true,characterData:true,subtree:true});await new Promise(r=>setTimeout(r,650));observer.disconnect();return {mutations,sameNode:first===text.firstChild,value:text.textContent}}); + assert.equal(glyphs.mutations,0,'Completed logo lettering must not be rebuilt each frame');assert.equal(glyphs.sameNode,true);assert.equal(glyphs.value,'RHINE·LAB'); + await mkdir('verification/boot-hud',{recursive:true}); + for(const [name,time] of [['logo',6.5],['scan',15.3],['welcome',19.6]]) { + await page.evaluate(time=>rhine.seek(time),time);await page.mouse.move(1450,120);await page.waitForTimeout(300); + const state=await shot();assert.equal(state.mode,'boot');assert.equal(state.hud,'true');assert.equal(state.tracking,'true');assert.equal(state.panels.length,5); + assert.ok(state.panels.every(p=>p.projection.startsWith('matrix3d(')&&!p.projection.includes('NaN'))); + results.push({name,...state});await page.screenshot({path:`verification/boot-hud/${name}.png`}); + } + await apply({hudtracking:false});await page.waitForTimeout(1800);const fixed=await shot();await page.mouse.move(80,800);await page.waitForTimeout(400);assert.equal((await shot()).tracking,'false');assert.equal(fixed.hud,'true'); + await apply({hudparallax:false});await page.waitForTimeout(2000);assert.equal((await shot()).hud,'false'); + await apply({hudparallax:true,reduced:true});await page.evaluate(()=>rhine.seek(12));await page.waitForTimeout(100);assert.equal((await shot()).hud,'true');assert.equal((await shot()).tracking,'false'); + await page.evaluate(()=>rhine.archive());await page.waitForTimeout(200);assert.equal((await shot()).hud,'true');assert.equal((await shot()).mode,'archive'); + await apply({reduced:false});await page.evaluate(()=>rhine.seek(12));await page.setViewportSize({width:2560,height:1080});await page.waitForTimeout(400);assert.equal((await shot()).hud,'true');await page.screenshot({path:'verification/boot-hud/ultrawide.png'}); + assert.deepEqual(errors,[]);await writeFile('verification/boot-hud/results.json',JSON.stringify({glyphs,results,errors,off:true,static:true,reduced:true,modeTransition:true,resize:true},null,2)); + console.log('Boot HUD: all five panels, authored transforms, tracking/static/off, reduced motion, mode transition and ultrawide passed.'); +} finally {await browser.close();server.close();} diff --git a/scripts/check-bottom-insets.mjs b/scripts/check-bottom-insets.mjs new file mode 100644 index 0000000..28aac2b --- /dev/null +++ b/scripts/check-bottom-insets.mjs @@ -0,0 +1,34 @@ +import {createRequire} from 'node:module'; +import assert from 'node:assert/strict'; +import {mkdir,writeFile} from 'node:fs/promises'; +const require=createRequire(import.meta.url); +const {chromium}=require(process.env.PLAYWRIGHT_MODULE || 'playwright'); +const browser=await chromium.launch({channel:'msedge',headless:true}); +const page=await browser.newPage({viewport:{width:1920,height:1080}}); +const errors=[];page.on('pageerror',e=>errors.push(String(e))); +await page.goto('http://127.0.0.1:5176/?scene=archive'); +await page.waitForFunction(()=>window.wallpaperPropertyListener&&document.querySelector('.wb-clock')?.textContent); +async function apply(values){await page.evaluate(values=>window.wallpaperPropertyListener.applyUserProperties(Object.fromEntries(Object.entries(values).map(([k,value])=>[k,{value}]))),values);await page.waitForTimeout(650)} +await apply({desktopmode:'workbench',boot:false,music:false,sound:false,reduced:true,hudtracking:false,uifrost:true,screenfinish:true,screengrain:20,screenfringe:20,screenvignette:20,task1:'记录本次实验结果',task2:'整理观测资料',task3:'完成今日工作'}); +await page.waitForTimeout(1500); +const selectors=['.wb-overview','.wb-module','.brand','.wb-nav button','.relay-entry','.system-footer > span','.system-footer > button']; +async function measure(){return page.evaluate(selectors=>selectors.map(s=>{const n=document.querySelector(s),r=n.getBoundingClientRect(),c=getComputedStyle(n);return {s,x:r.x,y:r.y,w:r.width,h:r.height,overflowX:c.overflowX,overflowY:c.overflowY}}),selectors)} +const result=[]; +for(const [width,height] of [[1920,1080],[2560,1440],[1600,900]]){ + await page.setViewportSize({width,height}); + for(const depth of [0,20,80]){ + await apply({hudparallax:depth>0,huddepth:depth,uimarginbottom:0});await page.waitForTimeout(5000);const baseline=await measure(); + for(const bottom of [60,192,300,-80,0]){ + await apply({uimarginbottom:bottom});const current=await measure(); + current.forEach((a,i)=>{const b=baseline[i],shift=i>=3?bottom:0;assert.ok(Math.abs(a.y-b.y+shift)<1.1,JSON.stringify({width,depth,bottom,a,b,shift}));for(const k of ['x','w','h'])assert.ok(Math.abs(a[k]-b[k])<1.1,`${a.s} ${k} changed`);if(i<2){assert.equal(a.overflowX,'visible');assert.equal(a.overflowY,'visible')}}); + result.push({width,height,depth,bottom,upperShift:current[3].y-baseline[3].y,lowerShift:current[5].y-baseline[5].y,middleShift:current[0].y-baseline[0].y}); + } + } +} +await page.setViewportSize({width:1920,height:1080});await apply({hudparallax:true,huddepth:20,uimarginbottom:0}); +await mkdir('verification/ui-insets',{recursive:true});await page.screenshot({path:'verification/ui-insets/bottom-0.png'}); +await apply({uimarginbottom:192});await page.screenshot({path:'verification/ui-insets/bottom-192.png'}); +assert.deepEqual(errors,[]);await writeFile('verification/ui-insets/results.json',JSON.stringify(result,null,2)); +console.log(`Passed ${result.length} viewport/depth/margin combinations; fixed middle geometry, no overflow, identical lower-row translation.`); +await browser.close(); + diff --git a/scripts/check-content.mjs b/scripts/check-content.mjs new file mode 100644 index 0000000..c9e9bbe --- /dev/null +++ b/scripts/check-content.mjs @@ -0,0 +1,193 @@ +import assert from "node:assert/strict"; +import { readFile } from "node:fs/promises"; +import { test } from "node:test"; +import { + loadContent, + validateContent, + archiveText, +} from "./archive-content.mjs"; +import { escapeHtml } from "../src/html.ts"; + +const content = await loadContent(); +test("all forty downloads match the shared content, including the UTF-8 BOM", async () => { + for (const record of content.records) { + assert.equal( + ( + await readFile( + new URL( + `../public/archives/RHINE-LAB-${record.id}.txt`, + import.meta.url, + ), + "utf8", + ) + ).replace(/\r\n/g, "\n"), + archiveText(record), + ); + } +}); + +const invalidCases = [ + [ + "missing title", + (c) => { + delete c.records[0].title; + }, + /records\[0\].title/, + ], + [ + "blank abstract", + (c) => { + c.records[0].abstract = " "; + }, + /abstract/, + ], + [ + "duplicate ID", + (c) => { + c.records[1].id = "X-001"; + }, + /重复编号/, + ], + [ + "reordered ID", + (c) => { + [c.records[0], c.records[1]] = [c.records[1], c.records[0]]; + }, + /X-001/, + ], + [ + "unknown category", + (c) => { + c.records[0].category = "未知"; + }, + /未知分类/, + ], + [ + "empty column", + (c) => { + // 把某一列的所有档案都挪到另一列 → 该列一条不剩,必须被拒。 + const victim = c.columns[0]; + const donor = c.columns[1]; + for (const record of c.records) if (record.category === victim) record.category = donor; + }, + /至少要有一条档案/, + ], + [ + "too few records", + (c) => { + c.records.length = 4; + }, + /至少需要每个分类一条档案/, + ], + [ + "null record", + (c) => { + c.records[0] = null; + }, + /必须是档案对象/, + ], + [ + "empty findings", + (c) => { + c.records[0].findings = []; + }, + /findings/, + ], + [ + "non-text findings", + (c) => { + c.records[0].findings = [42]; + }, + /findings/, + ], + [ + "unsafe URL", + (c) => { + c.records[0].source = "javascript:alert(1)"; + }, + /HTTPS/, + ], + [ + "invalid URL", + (c) => { + c.records[0].source = "example.com"; + }, + /HTTPS/, + ], + [ + "duplicate categories", + (c) => { + c.categories[1] = c.categories[0]; + }, + /不能重复/, + ], + [ + "reserved category", + (c) => { + c.categories[0] = "全部档案"; + }, + /全部档案/, + ], + [ + "mismatched columns", + (c) => { + c.columns[0] = "其他"; + }, + /相同的五个分类/, + ], +]; +for (const [name, mutate, error] of invalidCases) { + test(`rejects ${name}`, () => { + const invalid = structuredClone(content); + mutate(invalid); + assert.throws(() => validateContent(invalid), error); + }); +} +test("accepts independent filter and column order", () => { + const edited = structuredClone(content); + edited.categories.reverse(); + assert.equal(validateContent(edited), edited); +}); +test("accepts unequal column counts instead of forcing eight each", () => { + const edited = structuredClone(content); + const want = [1, 12, 5, 2, 9]; // 合计 29,且每列都不同 + const pool = [...edited.records]; + edited.records = []; + edited.columns.forEach((name, lane) => { + for (let k = 0; k < want[lane]; k += 1) { + const source = pool[lane * 3 + (k % 3)] ?? pool[0]; + edited.records.push({ ...source, category: name }); + } + }); + edited.records.forEach((record, i) => { + record.id = `X-${String(i + 1).padStart(3, "0")}`; + }); + assert.equal(validateContent(edited), edited); + assert.equal(edited.records.length, 29); + edited.columns.forEach((name, lane) => { + assert.equal(edited.records.filter((r) => r.category === name).length, want[lane]); + }); +}); + +test("pads archive ids to the total once it exceeds 999", () => { + const edited = structuredClone(content); + const base = edited.records[0]; + edited.records = Array.from({ length: 1200 }, (_, i) => ({ + ...structuredClone(base), + category: edited.columns[i % edited.columns.length], + id: `X-${String(i + 1).padStart(4, "0")}`, + })); + assert.equal(validateContent(edited), edited); +}); + +test("plain-text punctuation stays literal in HTML and downloadable text", () => { + const title = `<玻璃> & "实验" 'A'`; + const edited = structuredClone(content); + edited.records[0].title = title; + validateContent(edited); + assert.equal( + escapeHtml(title), + "<玻璃> & "实验" 'A'", + ); + assert.ok(archiveText(edited.records[0]).includes(title)); +}); diff --git a/scripts/check-dark-index.mjs b/scripts/check-dark-index.mjs new file mode 100644 index 0000000..d6553c8 --- /dev/null +++ b/scripts/check-dark-index.mjs @@ -0,0 +1,22 @@ +import {createRequire} from 'node:module'; +import assert from 'node:assert/strict'; +import {mkdir,writeFile} from 'node:fs/promises'; +const require=createRequire(import.meta.url); +const {chromium}=require(process.env.PLAYWRIGHT_MODULE || 'playwright'); +const browser=await chromium.launch({channel:'msedge',headless:true}); +const page=await browser.newPage({viewport:{width:1920,height:1080}}); +const errors=[];page.on('pageerror',e=>errors.push(String(e))); +await page.goto('http://127.0.0.1:5176/?scene=archive'); +await page.waitForFunction(()=>window.wallpaperPropertyListener&&document.querySelector('.wb-clock')?.textContent); +async function apply(values){await page.evaluate(values=>window.wallpaperPropertyListener.applyUserProperties(Object.fromEntries(Object.entries(values).map(([k,value])=>[k,{value}]))),values);await page.waitForTimeout(650)} +await apply({desktopmode:'workbench',boot:false,music:false,sound:false,reduced:true,hudtracking:false,uifrost:true,screenfinish:true,screengrain:20,screenfringe:20,screenvignette:20,task1:'记录本次实验结果',task2:'整理观测资料',task3:'完成今日工作'}); +await page.waitForTimeout(1500); +const selectors=['.wb-overview','.wb-module','.brand','.wb-nav button','.relay-entry','.system-footer > span','.system-footer > button']; +async function measure(){return page.evaluate(selectors=>selectors.map(s=>{const n=document.querySelector(s),r=n.getBoundingClientRect(),c=getComputedStyle(n);return {s,x:r.x,y:r.y,w:r.width,h:r.height,overflowX:c.overflowX,overflowY:c.overflowY}}),selectors)} + +await mkdir('verification/ui-insets',{recursive:true}); +await apply({colortheme:'dark',hudparallax:false,selectionstyle:'original',audioreactive:false,idlebreathing:false});await page.waitForTimeout(2500); +await apply({selectedindexaccent:false});await page.screenshot({path:'verification/ui-insets/dark-all-accent.png'}); +await apply({selectedindexaccent:true});await page.screenshot({path:'verification/ui-insets/dark-selected-accent.png'}); +await apply({colortheme:'light'});await page.waitForTimeout(2500);await page.screenshot({path:'verification/ui-insets/light-selected-accent.png'}); +assert.deepEqual(errors,[]);console.log('Dark label toggle rendered without page exceptions.');await browser.close(); \ No newline at end of file diff --git a/scripts/check-decryption.mjs b/scripts/check-decryption.mjs new file mode 100644 index 0000000..df3fa73 --- /dev/null +++ b/scripts/check-decryption.mjs @@ -0,0 +1,154 @@ +import assert from "node:assert/strict"; +import { readFile } from "node:fs/promises"; +import * as THREE from "three"; +import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js"; +import { decryptionFrame, DecryptionController } from "../src/decryption.ts"; +import { CardAppearance } from "../src/appearance.ts"; +import { frostedTransmissionLod, FROSTED_ROUGHNESS, CLEAR_ROUGHNESS } from "../src/glass-reveal.ts"; + +// Frost occupies the same fraction of a cover across viewport sizes. Clearing +// must never briefly increase its blur, and both original endpoints survive. +for (const scale of [0.5, 1, 2]) { + const pixels = 400 * scale, width = 1920 * scale; + assert.ok(Math.abs(2 ** frostedTransmissionLod(pixels, width, FROSTED_ROUGHNESS) / pixels - 0.016) < 1e-10); + let prior = -Infinity; + for (let i = 0; i <= 100; i++) { + const roughness = CLEAR_ROUGHNESS + (FROSTED_ROUGHNESS - CLEAR_ROUGHNESS) * i / 100; + const lod = frostedTransmissionLod(pixels, width, roughness); + assert.ok(lod >= prior - 1e-10, "Clearing monotonically reduces blur"); + prior = lod; + assert.equal(frostedTransmissionLod(pixels, width, roughness, 0), Math.log2(width) * roughness * (1.46 * 2 - 2)); + } + assert.ok(Math.abs(frostedTransmissionLod(pixels, width, CLEAR_ROUGHNESS) - Math.log2(width) * CLEAR_ROUGHNESS * (1.46 * 2 - 2)) < 1e-10); +} + +const length = (frame) => frame.intervals.reduce((n, [a, b]) => n + b - a, 0); +let previous = 0; +for (let t = 34.24; t < 36.04; t += 0.001) { + const f = decryptionFrame(t), + current = length(f); + assert.ok(current >= previous - 1e-10 && current <= 1); + if (f.intervals.length) + assert.ok(Math.abs(f.intervals[0][1] + f.intervals[1][0] - 1) < 1e-10); + previous = current; +} +assert.deepEqual(decryptionFrame(36.04).intervals, [[0, 1]]); +assert.equal(length(decryptionFrame(37.71)), 1); +previous = 1; +for (let t = 37.72; t < 38.84; t += 0.001) { + const f = decryptionFrame(t), + current = length(f); + assert.ok(current <= previous + 1e-10 && current >= 0); + assert.ok(Math.abs(f.intervals[0][0] + f.intervals[0][1] - 1) < 1e-10); + assert.equal(f.clarity, 0); + previous = current; +} +assert.equal(length(decryptionFrame(38.84)), 0); +assert.equal(decryptionFrame(38.84).clarity, 0); +assert.equal(decryptionFrame(39.56).clarity, 1); +assert.ok(length(decryptionFrame(34.64)) > 0.5, "Joining is eased, not linear"); +const a = new DecryptionController(); +a.enter(); +a.update(3, false, false); +assert.equal(a.frame.phase, "waiting"); +a.update(0, true, false); +for (let i = 0; i < 120; i++) a.update(1 / 30, true, false); +assert.equal(a.clarity, 1); +a.leave(); +assert.equal(a.frame.intervals.length, 0); +a.update(0.1, false, false); +const returning = a.clarity; +a.enter(); +a.update(0, true, false); +assert.equal(a.clarity, returning); +a.update(0.1, true, true); +assert.equal(a.clarity, 1); +assert.equal(a.frame.intervals.length, 0); +a.select(); +assert.equal(a.clarity, 0); +a.update(0, false, false, 35); +assert.equal(a.frame.phase, "joining"); +a.update(0, false, false, 39.56); +assert.equal(a.clarity, 1); +a.update(0, false, false, 34); +assert.equal(a.clarity, 0, "Reference seeking is reversible"); + +const appearance = new CardAppearance(); +const high = new THREE.MeshPhysicalMaterial({ + transmission: 0.9, + thickness: 0.12, + attenuationDistance: 2, +}); +const low = high.clone(); +low.thickness = 0.25; +appearance.register("Frosted_Polymer", high, low); +const group = new THREE.Group(), + mesh = new THREE.Mesh(new THREE.BoxGeometry(), high); +mesh.userData.surface = "Frosted_Polymer"; +group.add(mesh); +appearance.prepare(group); +appearance.apply(group, 1); +const shader = { + uniforms: {}, + vertexShader: "#include \n#include ", + fragmentShader: "#include \n#include \n#include ", +}; +mesh.material.onBeforeCompile(shader); +assert.ok(shader.vertexShader.includes("vArchiveProjectedAxis = 1.85")); +assert.ok(shader.fragmentShader.includes("float lod = archiveTransmissionLod(roughness, ior, transmissionSamplerSize);")); +const part = new THREE.Group(); +group.add(part); +part.add(mesh); +appearance.setClarity(group, 1); +assert.equal(shader.uniforms.archiveClarity.value, 1); +assert.ok(Math.abs(mesh.material.thickness - 0.018) < 1e-10); +appearance.setClarity(group, 0); +assert.equal(shader.uniforms.archiveClarity.value, 0); +assert.equal(mesh.material.thickness, 0.12); + +const bytes = await readFile( + new URL("../public/assets/archive-assembly.glb", import.meta.url), +); +const asset = ( + await new GLTFLoader().parseAsync( + bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength), + "", + ) +).scene; +asset.updateMatrixWorld(true); +let interiors = 0, + maxDepth = -Infinity, + minDepth = Infinity; +asset.traverse((mesh) => { + if ( + !mesh.isMesh || + !["optical-core", "optical-lenses"].includes(mesh.userData.assemblyPart) + ) + return; + const box = new THREE.Box3().setFromObject(mesh); + minDepth = Math.min(minDepth, box.min.z); + maxDepth = Math.max(maxDepth, box.max.z); + interiors++; + assert.ok( + box.min.z > -0.038 && box.max.z < 0.174, + "Interior stays between substrate and cover", + ); +}); +assert.ok(interiors >= 6); +// Perspective depth resolution, at the actual detail distance and 24-bit depth. +const step = (near, distance) => + (distance * distance * (300 - near)) / (300 * near * (2 ** 24 - 1)); +assert.ok(step(5, 72) < step(0.1, 72) / 50); +console.log( + JSON.stringify( + { + passed: true, + interiorMeshes: interiors, + interiorDepth: [minDepth, maxDepth], + depthStepBefore: step(0.1, 72), + depthStepAfter: step(5, 72), + }, + null, + 2, + ), +); diff --git a/scripts/check-font-loading.mjs b/scripts/check-font-loading.mjs new file mode 100644 index 0000000..d7ccad3 --- /dev/null +++ b/scripts/check-font-loading.mjs @@ -0,0 +1,55 @@ +// Compare the previous production build with the current one on local HTTP. +// BASELINE_DIST defaults to .tools/issues-before; neither build is modified. +import assert from 'node:assert/strict'; +import { createServer } from 'node:http'; +import { readFile, mkdir, writeFile } from 'node:fs/promises'; +import { resolve, extname, sep } from 'node:path'; +import { pathToFileURL } from 'node:url'; +const { chromium } = await import(process.env.PLAYWRIGHT_MODULE ? pathToFileURL(resolve(process.env.PLAYWRIGHT_MODULE)).href : 'playwright'); +const baseline = resolve(process.env.BASELINE_DIST || '.tools/issues-before'); +const current = resolve('dist'); +const out = resolve('.tools/issues');await mkdir(out,{recursive:true}); +const mime={'.html':'text/html','.js':'text/javascript','.css':'text/css','.woff2':'font/woff2','.svg':'image/svg+xml','.glb':'model/gltf-binary','.ogg':'audio/ogg','.json':'application/json'}; +const serve = async(root,port)=>{ + const server=createServer(async(req,res)=>{try { + const url=new URL(req.url,'http://localhost');const file=resolve(root,'.'+(url.pathname==='/'?'/index.html':decodeURIComponent(url.pathname))); + if(!file.startsWith(root+sep))throw Error('path');const body=await readFile(file); + res.writeHead(200,{'Content-Type':mime[extname(file)]||'application/octet-stream'}).end(body); + }catch{res.writeHead(404).end()}}); + await new Promise(r=>server.listen(port,'127.0.0.1',r));return server; +}; +const servers=await Promise.all([serve(baseline,0),serve(current,0)]); +const browser=await chromium.launch({channel:'chrome',headless:true,args:['--use-angle=d3d11','--enable-gpu','--ignore-gpu-blocklist']}); +const report={cases:[],errors:[]}; +try { + for(const [name,port] of [['baseline',servers[0].address().port],['current',servers[1].address().port]]) { + const context=await browser.newContext({viewport:{width:1920,height:1080},serviceWorkers:'block',reducedMotion:'no-preference'}); + const page=await context.newPage();page.on('pageerror',e=>report.errors.push(e.message)); + await page.goto(`http://127.0.0.1:${port}/`); + await page.waitForFunction(()=>window.rhine?.stats().ready); + await page.evaluate(()=>document.fonts.ready); + const fonts=await page.evaluate(()=>performance.getEntriesByType('resource').filter(e=>e.name.endsWith('.woff2')).map(e=>({url:new URL(e.name).pathname,bytes:e.decodedBodySize}))); + const result={name,entryFontBytes:fonts.reduce((n,f)=>n+f.bytes,0),entryFontRequests:fonts.length};report.cases.push(result); + await page.goto(`http://127.0.0.1:${port}/?time=8.48&freeze=1`); + await page.waitForFunction(()=>window.rhine?.stats().ready&&!document.querySelector('#loading')); + await page.evaluate(()=>document.fonts.ready); + result.brand=await page.evaluate(()=>['.brand h1','.brand > div','.brand p'].map(selector=>{ + const el=document.querySelector(selector),r=document.createRange();r.selectNodeContents(el);const rect=r.getBoundingClientRect(); + return {selector,text:el.textContent,width:rect.width,height:rect.height,x:rect.x,y:rect.y,font:getComputedStyle(el).font}; + })); + await page.screenshot({path:resolve(out,`brand-${name}.png`),clip:{x:45,y:100,width:300,height:135}}); + await page.goto(`http://127.0.0.1:${port}/?scene=detail`); + await page.waitForFunction(()=>window.rhine?.stats().ready&&!document.querySelector('#loading')); + await page.waitForFunction(()=>window.rhine.stats().decryption.clarity===1); + await page.evaluate(()=>document.fonts.ready); + await page.waitForFunction(()=>!document.querySelector('.document-redaction-window')); + await page.screenshot({path:resolve(out,`detail-${name}.png`)}); + result.document=await page.locator('.detail-content').evaluate(el=>({width:el.clientWidth,height:el.clientHeight,scrollWidth:el.scrollWidth,scrollHeight:el.scrollHeight})); + await context.close(); + } + report.fontByteReduction=1-report.cases[1].entryFontBytes/report.cases[0].entryFontBytes; + assert.ok(report.fontByteReduction>.9); + for(let i=0;i<3;i++)assert.ok(Math.abs(report.cases[1].brand[i].width-report.cases[0].brand[i].width)<.1,'Calibrated brand width must survive the font update'); + assert.equal(report.cases[1].document.scrollWidth,report.cases[1].document.width); + assert.deepEqual(report.errors,[]);console.log(JSON.stringify(report,null,2)); +} finally {await writeFile(resolve(out,'font-loading.json'),JSON.stringify(report,null,2));await browser.close();await Promise.all(servers.map(s=>new Promise(r=>s.close(r))));} diff --git a/scripts/check-font-update.mjs b/scripts/check-font-update.mjs new file mode 100644 index 0000000..6dbe137 --- /dev/null +++ b/scripts/check-font-update.mjs @@ -0,0 +1,50 @@ +// Migrate an installed pre-split-font release to this build, then test offline. +import assert from 'node:assert/strict'; +import { createServer } from 'node:http'; +import { readFile, mkdir, writeFile } from 'node:fs/promises'; +import { resolve, extname, sep } from 'node:path'; +import { pathToFileURL } from 'node:url'; +const { chromium } = await import(process.env.PLAYWRIGHT_MODULE ? pathToFileURL(resolve(process.env.PLAYWRIGHT_MODULE)).href : 'playwright'); +const oldRoot=resolve(process.env.BASELINE_DIST || '.tools/issues-before'),newRoot=resolve('dist'); +const metadata=JSON.parse(await readFile(resolve(newRoot,'pwa-build.json'),'utf8')); +const report={release:metadata.version,checks:[],errors:[]};let deployed=false; +const mime={'.html':'text/html','.js':'text/javascript','.css':'text/css','.json':'application/json','.woff2':'font/woff2','.svg':'image/svg+xml','.glb':'model/gltf-binary','.ogg':'audio/ogg','.webmanifest':'application/manifest+json'}; +const server=createServer(async(req,res)=>{try { + const root=deployed?newRoot:oldRoot,url=new URL(req.url,'http://localhost'); + const file=resolve(root,'.'+(url.pathname==='/'?'/index.html':decodeURIComponent(url.pathname))); + if(!file.startsWith(root+sep))throw Error('path');const body=await readFile(file); + res.writeHead(200,{'Content-Type':mime[extname(file)]||'application/octet-stream','Cache-Control':'no-cache'}).end(body); +}catch{res.writeHead(404).end()}}); +await new Promise(r=>server.listen(5198,'127.0.0.1',r)); +const browser=await chromium.launch({channel:process.env.REVIEW_CHANNEL||'chrome',headless:true,args:['--use-angle=d3d11','--enable-gpu','--ignore-gpu-blocklist']}); +try { + const context=await browser.newContext({viewport:{width:1440,height:900}}); + await context.addInitScript(()=>{if(!localStorage.getItem('rhine-settings'))localStorage.setItem('rhine-settings',JSON.stringify({sound:false,music:false,reduced:true}))}); + const page=await context.newPage();page.on('pageerror',e=>report.errors.push(e.message)); + const ready=()=>page.waitForFunction(()=>window.rhine?.stats().ready&&!document.querySelector('#loading')&&navigator.serviceWorker.controller&&document.documentElement.dataset.offlineReady==='true',null,{timeout:120000}); + await page.goto('http://127.0.0.1:5198/');await ready(); + await page.evaluate(()=>localStorage.setItem('rhine-saved','["X-001","X-009"]')); + const oldKeys=await page.evaluate(()=>caches.keys()); + assert.ok(await page.evaluate(()=>caches.match('/fonts/MiSans-Regular.woff2').then(Boolean))); + deployed=true; + await page.evaluate(async()=>{await(await navigator.serviceWorker.getRegistration()).update()}); + await page.waitForFunction(async()=>Boolean((await navigator.serviceWorker.getRegistration())?.waiting),null,{timeout:120000}); + assert.equal(await page.locator('.entry-start').count(),0); + await page.locator('#pwa-update-notice [data-pwa-action="update"]').click();await page.waitForLoadState('load');await ready(); + assert.equal(await page.evaluate(()=>window.rhine.stats().startup),'started'); + assert.equal(await page.evaluate(()=>localStorage.getItem('rhine-saved')),'["X-001","X-009"]'); + assert.equal(await page.evaluate(()=>JSON.parse(localStorage.getItem('rhine-settings')).sound),false); + const newKeys=await page.evaluate(()=>caches.keys());assert.ok(newKeys.some(k=>k.endsWith(metadata.version)));assert.ok(oldKeys.every(k=>!newKeys.includes(k))); + assert.equal(await page.evaluate(()=>caches.match('/fonts/MiSans-Regular.woff2').then(Boolean)),false); + report.checks.push('Previous complete release updates atomically; obsolete whole fonts removed; bookmarks and preferences retained'); + // Enable audio only for the next entry, then prove its cached resources work. + await page.evaluate(()=>{const p=JSON.parse(localStorage.getItem('rhine-settings'));p.sound=true;p.music=true;localStorage.setItem('rhine-settings',JSON.stringify(p))}); + await context.setOffline(true);await page.reload();await page.waitForFunction(()=>window.rhine?.stats().startup==='waiting'); + await page.locator('.entry-start').click();await ready(); + assert.equal(await page.evaluate(()=>window.rhine.stats().audio.tracks),3); + await page.locator('.read-file').click();await page.waitForFunction(()=>window.rhine.stats().decryption.clarity===1); + await page.locator('.viewer-open').click();await page.waitForFunction(()=>JSON.parse(document.querySelector('.model-viewer')?.dataset.stats||'{}').ready); + await page.locator('[data-viewer="explode"]').click();await page.waitForFunction(()=>JSON.parse(document.querySelector('.model-viewer').dataset.stats).spread===1); + report.checks.push('Updated release enters offline with all three music tracks, split fonts, hashed main/viewer models and explosion'); + assert.deepEqual(report.errors,[]);console.log(JSON.stringify(report,null,2)); +} finally {await mkdir('.tools/issues',{recursive:true});await writeFile('.tools/issues/font-update.json',JSON.stringify(report,null,2));await browser.close();await new Promise(r=>server.close(r));} diff --git a/scripts/check-internal-optics.mjs b/scripts/check-internal-optics.mjs new file mode 100644 index 0000000..d4fbf33 --- /dev/null +++ b/scripts/check-internal-optics.mjs @@ -0,0 +1,32 @@ +import assert from "node:assert/strict"; +import {readFile} from "node:fs/promises"; +import * as T from "three"; +import {GLTFLoader} from "three/addons/loaders/GLTFLoader.js"; +async function load(path) { + const b=await readFile(new URL(path,import.meta.url)); + const s=(await new GLTFLoader().parseAsync(b.buffer.slice(b.byteOffset,b.byteOffset+b.byteLength),"")).scene; + s.updateMatrixWorld(true);return s; +} +const [chosen,previous,current]=await Promise.all([ + load("../reference/internal-study/assembly-before.glb"), + load("../reference/ring-study/assembly-before.glb"), + load("../public/assets/archive-assembly.glb"), +]); +function collect(scene,interior) { + const result=new Map(); + scene.traverse(m=>{ + if(!m.isMesh)return; + const isInterior=["optical-core","optical-lenses"].includes(m.userData.assemblyPart); + if(isInterior!==interior)return; + const p=m.geometry.attributes.position,n=m.geometry.attributes.normal,nm=new T.Matrix3().getNormalMatrix(m.matrixWorld),rows=[]; + for(let i=0;ix.toFixed(5)).join(",")); + } + result.set(m.userData.assemblyPart+":"+m.material.name.replace(/\.\d+$/,""),{rows:rows.sort(),color:m.material.color.toArray().map(x=>x.toFixed(6)),roughness:m.material.roughness,metalness:m.material.metalness}); + });return result; +} +assert.deepEqual(collect(current,true),collect(chosen,true),"Restore the chosen first-version interior geometry, normals and source materials"); +assert.deepEqual(collect(current,false),collect(previous,false),"Keep the refined outer case unchanged"); +console.log(JSON.stringify({passed:true,interior:"matches chosen first version",outerCase:"unchanged",precision:1e-5},null,2)); diff --git a/scripts/check-local-sound.mjs b/scripts/check-local-sound.mjs new file mode 100644 index 0000000..ca96cde --- /dev/null +++ b/scripts/check-local-sound.mjs @@ -0,0 +1,14 @@ +import assert from 'node:assert/strict'; +import {SpectrumEnvelope} from '../src/archive-play-motion.ts'; +const ordinary=new SpectrumEnvelope(),filtered=new SpectrumEnvelope(); +const tone=Array(128).fill(.16); +for(let i=0;i<120;i++){const t=i/60;filtered.ignoreLocalSound(t+.4);ordinary.ingest(tone,t);filtered.ingest(tone,t);ordinary.update(1/60,t,true);filtered.update(1/60,t,true)} +assert.ok(ordinary.bands.activity>.99,'System-mixed interaction audio reproduces the false music onset'); +assert.equal(filtered.bands.activity,0);assert.equal(filtered.bands.low,0); +for(let i=180;i<360;i++){const t=i/60;filtered.ingest(tone,t);filtered.update(1/60,t,true)} +assert.ok(filtered.bands.activity>.99,'External sustained music still starts'); +for(let i=360;i<480;i++){const t=i/60;filtered.ignoreLocalSound(t+.4);filtered.ingest(tone,t);filtered.update(1/60,t,true)} +assert.ok(filtered.bands.activity>.99,'Dragging during established music does not disable music'); +for(let i=480;i<1080;i++)filtered.update(1/60,i/60,true); +assert.ok(filtered.bands.activity<.001,'Missing audio returns to silence'); +console.log('Reproduced false onset; own sounds ignored from silence, sustained music and release passed.'); diff --git a/scripts/check-loop.mjs b/scripts/check-loop.mjs new file mode 100644 index 0000000..7847b3b --- /dev/null +++ b/scripts/check-loop.mjs @@ -0,0 +1,86 @@ +import assert from "node:assert/strict"; +import { columnFiles, fileLocation } from "../src/data.ts"; +import { + fileAtCell, + selectionCell, + visibleCell, + poolCell, + cellKey, + LOOP_COLUMNS, + LOOP_ROWS, + wrap, +} from "../src/archive-loop.ts"; + +// 每列的档案在零点两侧循环复用。列内条数不再固定为八条, +// 所以用该列自己的 files.length 取模,而不是写死的 8。 +for (let lane = -23; lane <= 23; lane++) { + const files = columnFiles(wrap(lane, 5)); + for (let row = -35; row <= 35; row++) { + assert.equal(fileAtCell({ lane, row }), files[wrap(row - 12, files.length)]); + } +} +let checks = 0; +for (const direction of [-1, 1]) { + let cell = { lane: 2, row: 12 }; + let index = fileAtCell(cell); + const memory = Array.from({ length: 5 }, (_, lane) => columnFiles(lane)[0]); + for (let step = 0; step < 10000; step++) { + const axis = step % 17 < 10 ? "row" : "lane"; + const lane = fileLocation(index).lane; + if (axis === "row") { + const files = columnFiles(lane); + index = files[wrap(files.indexOf(index) + direction, files.length)]; + } else index = memory[wrap(lane + direction, 5)]; + const next = selectionCell(index, cell, { axis, direction }); + assert.equal( + next[axis] - cell[axis], + direction, + "Crossing a seam must move exactly one cell in the requested direction", + ); + assert.equal( + fileAtCell(next), + index, + "Selected physical cell must contain the requested document", + ); + memory[fileLocation(index).lane] = index; + cell = next; + checks++; + } +} +for (const center of [ + { lane: 2, row: 12 }, + { lane: -8.3, row: -19.2 }, + { lane: 10002.49, row: -32001.49 }, +]) { + const cells = Array.from({ length: LOOP_COLUMNS * LOOP_ROWS }, (_, i) => + visibleCell(i, center), + ); + assert.equal(new Set(cells.map(cellKey)).size, LOOP_COLUMNS * LOOP_ROWS); + const lanes = [...new Set(cells.map((c) => c.lane))].sort((a, b) => a - b); + const rows = [...new Set(cells.map((c) => c.row))].sort((a, b) => a - b); + assert.equal(lanes.length, LOOP_COLUMNS); + assert.equal(rows.length, LOOP_ROWS); + assert.equal(lanes.at(-1) - lanes[0], LOOP_COLUMNS - 1); + assert.equal(rows.at(-1) - rows[0], LOOP_ROWS - 1); + assert.ok(lanes[0] < center.lane - 3 && lanes.at(-1) > center.lane + 3); + assert.ok(rows[0] < center.row - 14 && rows.at(-1) > center.row + 14); +} +for (let i = 0; i < 160; i++) { + assert.deepEqual( + poolCell(i), + { lane: Math.floor(i / 32), row: i % 32 }, + "Reference-animation instance order is preserved", + ); +} +console.log( + JSON.stringify( + { + directionalMoves: checks, + poolSize: LOOP_COLUMNS * LOOP_ROWS, + referenceInstances: 160, + checks: "passed", + }, + null, + 2, + ), +); diff --git a/scripts/check-motion.mjs b/scripts/check-motion.mjs new file mode 100644 index 0000000..284a6fe --- /dev/null +++ b/scripts/check-motion.mjs @@ -0,0 +1,147 @@ +import assert from "node:assert/strict"; +import { + archiveWave, + extraction, + selectionWave, + baselineSelectionWave, + rippleEnvelope, + settlingWave, + damp, + idleWave, +} from "../src/motion.ts"; +import { selectionWave as historicalWave } from "../reference/baseline-motion.ts"; + +// The selected production motion is the original signed wave, including troughs. +let baselineTrough = false; +for (let frame = -1; frame <= 200; frame++) { + for (let distance = 0; distance <= 32; distance += 0.5) { + const restored = baselineSelectionWave(distance, frame / 60); + assert.equal(restored, historicalWave(distance, frame / 60)); + baselineTrough ||= restored < -0.01; + } +} +assert.ok(baselineTrough, "The original negative trough is restored"); + +let idleRange = 0; +for (let lane = 0; lane < 5; lane++) { + for (let row = 0; row < 32; row++) { + for (let frame = 0; frame < 60 * 13; frame++) { + const a = idleWave(row, lane, frame / 60); + const b = idleWave(row, lane, (frame + 1) / 60); + idleRange = Math.max(idleRange, Math.abs(a)); + assert.ok( + Math.abs(a) < 3.7 * 0.03, + "Idle lift stays below 3% of card height", + ); + assert.ok( + Math.abs(b - a) * (1080 / 7.33) < 0.21, + "Idle motion remains subpixel per frame", + ); + } + } +} +assert.ok(idleRange > 0.075, "Idle field remains perceptible without input"); + +const peak = (t) => + Array.from({ length: 32 }, (_, row) => archiveWave(row, 2, t)).reduce( + (best, y, row, values) => (y > values[best] ? row : best), + 0, + ); +assert.ok(peak(23.3) > peak(22.7) + 6, "First crest must travel across rows"); +assert.ok(peak(24.8) < peak(24.2) - 8, "Second crest must return across rows"); +let maxFrameDelta = 0; +for (let frame = 550; frame < 800; frame++) { + for (let row = 0; row < 32; row++) + for (let lane = 0; lane < 5; lane++) { + const a = archiveWave(row, lane, frame / 25); + const b = archiveWave(row, lane, (frame + 1) / 25); + assert.ok(Number.isFinite(a)); + maxFrameDelta = Math.max(maxFrameDelta, Math.abs(b - a)); + } +} +assert.ok(maxFrameDelta < 0.7, "25 fps samples must not teleport"); +assert.ok( + Math.abs(extraction(26.6) - extraction(27.2)) < 0.01, + "Pause between extraction phases", +); +assert.ok(extraction(29) > 3 && extraction(25.1) === 0); +assert.ok( + Math.abs(settlingWave(2, 26.1) - settlingWave(2, 26.5)) > 0.01, + "Neighbors keep moving during the first extraction hold", +); +assert.ok(selectionWave(8, 1) > 0.1, "Click ripple reaches neighboring rows"); +for (let frame = 0; frame <= 200; frame++) { + for (let distance = 0; distance <= 32; distance += 0.5) { + const y = selectionWave(distance, frame / 60); + const age = frame / 60; + const ramp = Math.max(0, Math.min(1, age / 0.2)); + const original = + 0.8 * + ramp ** 3 * + (10 + ramp * (-15 + 6 * ramp)) * + Math.exp(-age * 1.15) * + Math.cos((distance - age * 8) * 0.58) * + Math.exp(-0.5 * ((distance - age * 8) / 3.4) ** 2); + assert.ok( + y >= 0 && y <= 0.8, + "Selection pulse cannot create a negative trough", + ); + if (age <= 3.2 && original > 0) + assert.ok( + Math.abs(y - original) < 1e-12, + "Positive crests retain the baseline amplitude and timing", + ); + } +} +const ripple = (distance, age) => + selectionWave(distance, age) * rippleEnvelope(distance, age); +for (let frame = 0; frame <= 200; frame++) { + assert.equal( + ripple(0, frame / 60), + 0, + "The selected source cannot bounce on its own ripple", + ); +} +for (const distance of [3, 5, 8, 12]) { + const crestTime = distance / 8; + assert.equal( + ripple(distance, crestTime), + selectionWave(distance, crestTime), + "The outward crest keeps its strength away from the source", + ); + const edge = (distance - Math.PI / (2 * 0.58)) / 8; + const epsilon = 1e-5; + const velocity = + (ripple(distance, edge + epsilon) - ripple(distance, edge - epsilon)) / + (2 * epsilon); + assert.ok( + Math.abs(velocity) < 0.001, + "Ripple edges approach rest without a velocity snap", + ); +} +const coarse = { value: 5, velocity: -2 }, + fine = { ...coarse }; +for (let i = 0; i < 30; i++) damp(coarse, -3, 4, 1 / 30); +for (let i = 0; i < 120; i++) damp(fine, -3, 4, 1 / 120); +assert.ok( + Math.abs(coarse.value - fine.value) < 1e-9, + "Spring must be frame-rate independent", +); +const before = coarse.value; +damp(coarse, 6, 4, 1 / 120); +assert.ok( + Math.abs(coarse.value - before) < 0.05, + "Retargeting must preserve position continuity", +); +console.log( + JSON.stringify( + { + forwardPeaks: [peak(22.7), peak(23.3)], + returnPeaks: [peak(24.2), peak(24.8)], + maxFrameDelta, + checks: "passed", + }, + null, + 2, + ), +); diff --git a/scripts/check-playground.mjs b/scripts/check-playground.mjs new file mode 100644 index 0000000..bc4df1c --- /dev/null +++ b/scripts/check-playground.mjs @@ -0,0 +1,52 @@ +import assert from 'node:assert/strict'; +import {readFileSync} from 'node:fs'; +import ts from 'typescript'; +import vm from 'node:vm'; +async function load(file) {const source=ts.transpileModule(readFileSync(file,'utf8'),{compilerOptions:{module:ts.ModuleKind.ESNext,target:ts.ScriptTarget.ES2022}}).outputText;return import(`data:text/javascript;base64,${Buffer.from(source).toString('base64')}`)} +const {SpectrumEnvelope,RelayRound,musicDisplacement,quietBands}=await load('src/archive-play-motion.ts'); +const e=new SpectrumEnvelope(), samples=Array(128).fill(0);samples.fill(1,64,72);e.ingest(samples,0); +let bands=e.update(.1,.1,true);assert.ok(bands.low>.5);assert.equal(bands.mid,0);assert.equal(bands.high,0); +for(let i=1;i<=150;i++) bands=e.update(.05,i*.05,true); +assert.ok(bands.low<.0001&&bands.activity<.001,'Missing callbacks fade to original motion'); +e.ingest(Array(128).fill(NaN),8);bands=e.update(.1,8,true);assert.ok(Number.isFinite(bands.low)); +assert.equal(musicDisplacement(1,2,3,quietBands(),2),0); +for(let row=-20;row<20;row++) {const n=musicDisplacement(row,3,1,{low:1,mid:1,high:1,activity:1},20);assert.ok(n>=0&&n<=1.8)} +const r=new RelayRound();r.start();r.aim('1:2','normal');r.tick(100,true);assert.equal(r.remaining,6);assert.equal(r.hit('1:2'),true);assert.equal(r.score,1);assert.equal(r.hit('1:2'),false,'Duplicate hit cannot score twice');r.aim('1:3','normal');r.hit('wrong');assert.equal(r.status,'over');r.start();assert.equal(r.score,0);r.aim('1:2','quick');for(let i=0;i<50;i++)r.tick(.1,false);assert.equal(r.status,'over');r.stop();assert.equal(r.target,null); +let callback;const window={dispatchEvent(){},wallpaperRegisterAudioListener:fn=>callback=fn};vm.runInNewContext(readFileSync('wallpaper/host.js','utf8'),{window,Event,CustomEvent,performance:{now:()=>2000}});assert.ok(callback);callback(Array(128).fill(4));assert.equal(window.rhineWallpaperSpectrum.samples[0],1);assert.equal(window.rhineWallpaperSpectrum.time,2); +const {openingShowsDetail,ARRAY_OPENING_END}=await load('src/wallpaper-opening.ts');assert.equal(openingShowsDetail('auto',true),false);assert.equal(openingShowsDetail('auto',false),true);assert.equal(openingShowsDetail('show',true),true);assert.equal(openingShowsDetail('skip',false),false);assert.ok(ARRAY_OPENING_END<26); +console.log('Spectrum stereo/clamping/decay, relay pause/scoring/retry/timeout, host callback and opening policy passed.'); +const project=JSON.parse(readFileSync('wallpaper/project.json','utf8')),props=project.general.properties; +assert.equal(project.general.supportsaudioprocessing,true,'WE reads audio support from general'); +assert.equal(Object.hasOwn(project,'supportsaudioprocessing'),false,'Root-level flag is not recognized by the host'); +assert.equal(Object.values(props).filter(p=>p.type==='group').length,9); +function visible(key,override={}){const context=structuredClone(props);for(const [k,v] of Object.entries(override))context[k].value=v;return !props[key].condition||vm.runInNewContext(props[key].condition,context)} +assert.equal(visible('groupworkbench',{desktopmode:'archive'}),false); +assert.equal(visible('reactiveintensity',{audioreactive:false}),false); +assert.equal(visible('gamepace',{showgame:false}),false); +assert.equal(visible('openingdetail',{boot:false}),false); +for(const key of Object.keys(props))visible(key); +assert.equal(visible('customwallpaperfile',{customwallpaper:false}),false); +assert.equal(visible('customwallpaperfile',{customwallpaper:true}),true); +console.log('Nine native groups and all display conditions passed.'); + +const {RhythmMotion,rhythmDisplacement}=await load('src/archive-play-motion.ts'); +const rhythm=new RhythmMotion(); let motion; +const tone={low:0,mid:.5,high:0,activity:1}; +for(let i=0;i<300;i++)motion=rhythm.update(tone,i/60,1/60,'wave'); +const displacement=(x,b=tone,t=5)=>rhythmDisplacement(10,2,t,b,1,motion,x); +assert.ok(displacement(.5)>.15,'Sustained midrange remains visible after five seconds'); +assert.ok(displacement(.5)>displacement(0)*10&&displacement(.5)>displacement(1)*10,'Midrange lives in the screen center'); +assert.ok(displacement(0,{...tone,low:.5,mid:0})>.15,'Bass lives on the left'); +assert.ok(displacement(1,{...tone,high:.5,mid:0})>.15,'Treble lives on the right'); +assert.ok(Math.abs(displacement(.499)-displacement(.501))<.001,'Band boundary is continuous'); +assert.equal(displacement(.5,quietBands()),0,'Silence has no new motion'); +for(let i=300;i<600;i++)motion=rhythm.update(tone,i/60,1/60,'lift'); +for(const key of ['low','mid','high']) { + const b={...quietBands(),[key]:.5,activity:1}; + const samples=Array.from({length:60},(_,i)=>rhythmDisplacement(10,2,10+i/60,b,1,motion)); + assert.ok(Math.min(...samples)>0,'All sustained bands drive B continuously'); + assert.ok(Math.max(...samples)-Math.min(...samples)>.01,'B keeps travelling during sustained music'); +} +assert.equal(rhythmDisplacement(10,2,20,quietBands(),1,motion),0); +for(let i=0;i<200;i++){motion=rhythm.update(tone,i/60,1/60,i%2?'wave':'legacy');assert.ok(Math.abs(Object.values(motion.style).reduce((a,b)=>a+b,0)-1)<1e-10)} +console.log('Continuous spectrum position, sustained notes, smooth bands, travelling layers, silence and interrupted style blend passed.'); diff --git a/scripts/check-pwa-recovery.mjs b/scripts/check-pwa-recovery.mjs new file mode 100644 index 0000000..5da8726 --- /dev/null +++ b/scripts/check-pwa-recovery.mjs @@ -0,0 +1,58 @@ +// Preserve an earlier production dist, then set PWA_PREVIOUS_DIST to its path. +import assert from 'node:assert/strict'; +import {createServer} from 'node:http'; +import {readFile, writeFile, mkdir} from 'node:fs/promises'; +import {resolve, extname, sep} from 'node:path'; +import {pathToFileURL} from 'node:url'; +const {chromium}=await import(process.env.PLAYWRIGHT_MODULE?pathToFileURL(resolve(process.env.PLAYWRIGHT_MODULE)).href:'playwright'); +if(!process.env.PWA_PREVIOUS_DIST) throw Error('Set PWA_PREVIOUS_DIST to an earlier built release.'); +const oldRoot=resolve(process.env.PWA_PREVIOUS_DIST),newRoot=resolve('dist'); +const metadata=JSON.parse(await readFile(resolve(newRoot,'pwa-build.json'),'utf8')); +let deployed=false,broken=false; +const mime={'.html':'text/html','.js':'text/javascript','.css':'text/css','.json':'application/json','.webmanifest':'application/manifest+json','.svg':'image/svg+xml','.png':'image/png','.woff2':'font/woff2','.txt':'text/plain','.glb':'model/gltf-binary','.ogg':'audio/ogg'}; +const server=createServer(async(req,res)=>{try { + const root=deployed?newRoot:oldRoot,url=new URL(req.url,'http://localhost'); + const path=decodeURIComponent(url.pathname==='/'?'/index.html':url.pathname),file=resolve(root,'.'+path); + if(!file.startsWith(root+sep))throw Error('path'); + if(broken&&path==='/icons/icon-192.png'){res.writeHead(503).end();return} + let body=await readFile(file); + if(broken&&path==='/sw.js')body=Buffer.from(body.toString().replace(metadata.version,metadata.version+'-broken')); + res.writeHead(200,{'Content-Type':mime[extname(file)]||'application/octet-stream','Cache-Control':'no-cache'}).end(body); +}catch{res.writeHead(404).end()}}); +await new Promise(r=>server.listen(5192,'127.0.0.1',r)); +const channel=process.env.REVIEW_CHANNEL||'chrome'; +const browser=await chromium.launch({channel,headless:true,args:['--use-angle=d3d11','--enable-gpu','--ignore-gpu-blocklist']}); +const report={channel,version:browser.version(),release:metadata.version,checks:[],errors:[]}; +try { + const context=await browser.newContext({viewport:{width:1440,height:900}}); + await context.addInitScript(()=>{if(!localStorage.getItem('rhine-settings'))localStorage.setItem('rhine-settings',JSON.stringify({reduced:true,sound:false,music:false}))}); + const page=await context.newPage();page.on('pageerror',e=>report.errors.push(e.message)); + const ready=()=>page.waitForFunction(()=>window.rhine?.stats().ready&&document.documentElement.dataset.offlineReady==='true'&&navigator.serviceWorker.controller&&!document.querySelector('#loading'),null,{timeout:90000}); + const base='http://127.0.0.1:5192/'; + await page.goto(base);await ready();assert.equal(await page.locator('.settings-label').count(),0); + await page.evaluate(()=>localStorage.setItem('rhine-saved','["X-001"]')); + deployed=true; + const cdp=await context.newCDPSession(page);await cdp.send('Network.clearBrowserCache');await cdp.detach(); + await page.reload();await ready(); + await page.waitForFunction(async()=>Boolean((await navigator.serviceWorker.getRegistration())?.waiting),null,{timeout:90000}); + assert.equal(await page.locator('.settings-label').count(),0); + report.checks.push('clearing HTTP cache and reloading still serves the previous service-worker release'); + await page.goto(base+'update.html');await page.getByRole('button',{name:'更新并返回'}).click(); + await page.waitForURL(base);await ready();assert.equal(await page.locator('.settings-label').textContent(),'设置'); + assert.ok((await page.evaluate(()=>caches.keys())).some(k=>k.endsWith(metadata.version))); + assert.equal(await page.evaluate(()=>localStorage.getItem('rhine-saved')),'["X-001"]'); + assert.equal(await page.evaluate(()=>JSON.parse(localStorage.getItem('rhine-settings')).reduced),true); + report.checks.push('network recovery replaces the old page and preserves bookmarks and motion preference'); + broken=true; + await page.goto(base+'update.html');await page.getByRole('button',{name:'更新并返回'}).click(); + await page.waitForFunction(()=>document.querySelector('#status').textContent.includes('更新未完成')); + assert.equal(new URL(page.url()).pathname,'/update.html');assert.equal(await page.locator('#update').isEnabled(),true); + report.checks.push('failed recovery download reports failure and retains the previous release');broken=false; + await page.goto(base);await ready(); + await context.setOffline(true);await page.reload();await ready();assert.equal(await page.locator('.settings-label').textContent(),'设置'); + report.checks.push('recovered release works after offline reload');await context.close(); + const fresh=await browser.newContext();const freshPage=await fresh.newPage(); + await freshPage.goto(base+'update.html');await freshPage.getByRole('button',{name:'更新并返回'}).click();await freshPage.waitForURL(base,{timeout:90000}); + report.checks.push('recovery also works without a previous service worker');await fresh.close(); + assert.deepEqual(report.errors,[]);console.log(JSON.stringify(report,null,2)); +}finally{await mkdir('.tools/responsive',{recursive:true});await writeFile(`.tools/responsive/recovery-${channel}.json`,JSON.stringify(report,null,2));await browser.close();await new Promise(r=>server.close(r))} diff --git a/scripts/check-pwa.mjs b/scripts/check-pwa.mjs new file mode 100644 index 0000000..b49aedb --- /dev/null +++ b/scripts/check-pwa.mjs @@ -0,0 +1,60 @@ +// Uses a disposable HTTP server to exercise real service-worker updates/failures. +import assert from 'node:assert/strict'; +import { createServer } from 'node:http'; +import { readFile, writeFile, mkdir } from 'node:fs/promises'; +import { resolve, extname } from 'node:path'; +import { pathToFileURL } from 'node:url'; +const {chromium}=await import(process.env.PLAYWRIGHT_MODULE?pathToFileURL(resolve(process.env.PLAYWRIGHT_MODULE)).href:'playwright'); +const metadata=JSON.parse(await readFile('dist/pwa-build.json','utf8')); +let revision=1,fail=false; +const root=resolve('dist'); +const mime={'.html':'text/html','.js':'text/javascript','.css':'text/css','.json':'application/json','.webmanifest':'application/manifest+json','.svg':'image/svg+xml','.png':'image/png','.woff2':'font/woff2','.txt':'text/plain','.glb':'model/gltf-binary','.ogg':'audio/ogg'}; +const server=createServer(async(req,res)=>{try{ + const url=new URL(req.url,'http://localhost');const path=decodeURIComponent(url.pathname==='/'?'/index.html':url.pathname);const file=resolve(root,`.${path}`); + if(!file.startsWith(root+ '/'.replace('/',process.platform==='win32'?'\\':'/')))throw Error(); + if(fail&&path==='/icons/icon-192.png'){res.writeHead(503).end();return} + let body=await readFile(file); + if(path==='/sw.js'&&revision>1)body=Buffer.from(body.toString().replace(metadata.version,`${metadata.version}-test-${revision}`)); + res.writeHead(200,{'Content-Type':mime[extname(file)]||'application/octet-stream','Cache-Control':'no-cache'}).end(body); +}catch{res.writeHead(404).end()}}); +await new Promise(resolve=>server.listen(5191,'127.0.0.1',resolve)); +const browser=await chromium.launch({channel:'chrome',headless:true,args:['--use-angle=d3d11','--enable-gpu','--ignore-gpu-blocklist']}); +const context=await browser.newContext({viewport:{width:390,height:844},hasTouch:true,isMobile:true}); +await context.addInitScript(()=>{if(!localStorage.getItem('rhine-settings'))localStorage.setItem('rhine-settings',JSON.stringify({reduced:true,sound:false,music:false}))}); +const page=await context.newPage();const errors=[];page.on('pageerror',e=>errors.push(e.message)); +const ready=()=>page.waitForFunction(()=>window.rhine?.stats().ready&&document.documentElement.dataset.offlineReady==='true'&&navigator.serviceWorker.controller&&!document.querySelector('#loading'),null,{timeout:90000}); +const report={version:metadata.version,bytes:metadata.bytes,files:metadata.files.length,checks:[],errors}; +try{ + await page.goto('http://127.0.0.1:5191/?scene=archive');await ready(); + const manifest=await page.evaluate(async()=>await(await fetch(document.querySelector('link[rel="manifest"]').href)).json()); + assert.equal(manifest.display,'standalone');assert.equal(manifest.scope,'./');assert.equal(manifest.icons.length,3); + await page.evaluate(async()=>{await caches.open('unrelated-app');localStorage.setItem('rhine-saved','["X-001"]')}); + report.checks.push('manifest, installation, atomic full-resource cache'); + await context.setOffline(true);await page.reload();await ready(); + await page.locator('.read-file').click();await page.waitForFunction(()=>window.rhine.stats().decryption.clarity===1); + const exported=await page.locator('.export-button').evaluate(async a=>{const r=await fetch(a.href);return {ok:r.ok,text:await r.text()}});assert.ok(exported.ok&&exported.text.includes('X-001')); + await page.locator('.viewer-open').click();await page.waitForFunction(()=>JSON.parse(document.querySelector('.model-viewer')?.dataset.stats||'{}').ready); + await page.locator('[data-viewer="explode"]').click();await page.waitForFunction(()=>JSON.parse(document.querySelector('.model-viewer').dataset.stats).spread===1); + await mkdir('.tools/responsive',{recursive:true});await page.screenshot({path:'.tools/responsive/pwa-offline.png'}); + assert.ok(await page.evaluate(async()=>{const r=await fetch('/audio/motif.ogg');return r.ok&&(await r.arrayBuffer()).byteLength>100000})); + report.checks.push('offline reload, fonts, document export, model viewer, explosion and audio resource'); + await context.setOffline(false);revision=2; + await page.evaluate(async()=>{const r=await navigator.serviceWorker.getRegistration();await r.update()}); + await page.waitForFunction(async()=>Boolean((await navigator.serviceWorker.getRegistration())?.waiting),null,{timeout:90000}); + assert.equal(await page.locator('#pwa-update-notice').isVisible(),false,'Viewer must isolate the outside update action'); + await page.locator('[data-viewer="close"]').click(); + await page.waitForSelector('#pwa-update-notice:not([hidden])'); + assert.equal(await page.evaluate(()=>document.querySelector('#stage').dataset.mode),'detail','Waiting update must not interrupt the page'); + await Promise.all([page.waitForNavigation(),page.locator('#pwa-update-notice [data-pwa-action="update"]').click()]);await ready(); + assert.equal(await page.evaluate(()=>localStorage.getItem('rhine-saved')),'["X-001"]'); + const keys=await page.evaluate(()=>caches.keys());assert.ok(keys.includes('unrelated-app'));assert.equal(keys.filter(k=>k.startsWith('rhine-lab:')).length,1);assert.ok(keys.some(k=>k.endsWith('-test-2'))); + report.checks.push('visible update action without opening settings, explicit restart, old-cache cleanup and preserved preferences/bookmarks'); + revision=3;fail=true; + await page.evaluate(async()=>{const r=await navigator.serviceWorker.getRegistration();await r.update()}); + await page.waitForFunction(async()=>{const r=await navigator.serviceWorker.getRegistration();return !r.installing&&!r.waiting},null,{timeout:90000}); + await context.setOffline(true);await page.reload();await ready(); + assert.ok((await page.evaluate(()=>caches.keys())).some(k=>k.endsWith('-test-2'))); + assert.ok(!(await page.evaluate(()=>caches.keys())).some(k=>k.endsWith('-test-3'))); + report.checks.push('failed update leaves the previous complete offline release usable'); + assert.deepEqual(errors,[]);console.log(JSON.stringify(report,null,2)); +}finally{await writeFile('.tools/responsive/pwa-report.json',JSON.stringify(report,null,2));await browser.close();await new Promise(r=>server.close(r))} diff --git a/scripts/check-quality.mjs b/scripts/check-quality.mjs new file mode 100644 index 0000000..cd6ccfe --- /dev/null +++ b/scripts/check-quality.mjs @@ -0,0 +1,78 @@ +import assert from "node:assert/strict"; +import { + normalizeQuality, + qualityPresets, + matchingPreset, + renderDimensions, +} from "../src/render-quality.ts"; + +assert.deepEqual(normalizeQuality(null), qualityPresets.original); +const legacy = normalizeQuality(undefined, false); +assert.equal(legacy.pixelRatio, 1); +assert.equal(legacy.aoSamples, 0); +assert.equal(legacy.depthOfField, 0); +assert.equal(legacy.shadows, 2048, "Migration preserves old low-mode shadows"); +assert.equal(legacy.transmission, 1, "Migration preserves old low-mode glass"); +for (const bad of [ + null, + false, + "ultra", + [], + { + scale: NaN, + pixelRatio: 99, + antialias: "injected", + shadows: -1, + aoSamples: Infinity, + }, +]) { + assert.deepEqual(normalizeQuality(bad), qualityPresets.original); +} +assert.equal(normalizeQuality({ scale: 99999 }).scale, 200); +assert.equal(normalizeQuality({ scale: -1 }).scale, 50); +assert.equal(normalizeQuality({ depthOfField: 99999 }).depthOfField, 150); +for (const [name, quality] of Object.entries(qualityPresets)) { + assert.equal(matchingPreset(normalizeQuality(quality)), name); + const reloaded = normalizeQuality(JSON.parse(JSON.stringify(quality))); + assert.deepEqual(reloaded, quality); +} +assert.equal(matchingPreset({ ...qualityPresets.ultra, scale: 145 }), "custom"); +for (const width of [640, 1920, 3840, 7680]) { + for (const dpr of [1, 1.5, 2, 3]) { + for (const max of [2048, 4096, 16384]) { + const dimensions = renderDimensions( + normalizeQuality({ scale: 200, pixelRatio: 3 }), + 1920, + 1080, + width / 1920, + dpr, + max, + ); + assert.ok(dimensions.width * dimensions.height <= 8_294_400); + assert.ok(dimensions.width <= max && dimensions.height <= max); + assert.ok(dimensions.ratio > 0); + } + } +} +const native = renderDimensions( + qualityPresets.original, + 1920, + 1080, + 1, + 1, + 16384, +); +const ultra = renderDimensions(qualityPresets.ultra, 1920, 1080, 1, 1, 16384); +assert.equal(native.width, 1920); +assert.equal(ultra.width, 2880); +assert.equal( + (ultra.width * ultra.height) / (native.width * native.height), + 2.25, +); +assert.equal( + renderDimensions(qualityPresets.ultra, 1920, 1080, 2, 2, 16384).limited, + true, +); +console.log( + "Quality checks passed: migration, invalid storage, presets, supersampling and 48 device-limit combinations.", +); diff --git a/scripts/check-responsive.mjs b/scripts/check-responsive.mjs new file mode 100644 index 0000000..4cc08f8 --- /dev/null +++ b/scripts/check-responsive.mjs @@ -0,0 +1,111 @@ +// Browser regression against the actual WebGL application. Run with local Vite. +import assert from "node:assert/strict"; +import { mkdir, writeFile } from "node:fs/promises"; +import { resolve } from "node:path"; +import { pathToFileURL } from "node:url"; +const { chromium, webkit } = await import(process.env.PLAYWRIGHT_MODULE ? pathToFileURL(resolve(process.env.PLAYWRIGHT_MODULE)).href : "playwright"); +const base=process.env.REVIEW_URL || "http://127.0.0.1:5204"; +const output=resolve(".tools/responsive");await mkdir(output,{recursive:true}); +const engine=process.env.REVIEW_ENGINE || "chromium"; +const browser=engine==='webkit'?await webkit.launch({headless:true}):await chromium.launch({channel:'chrome',headless:true,args:['--use-angle=d3d11','--enable-gpu','--ignore-gpu-blocklist']}); +const cases=engine==='webkit' ? [['safari-portrait',390,844,true],['safari-landscape',844,390,true]] : + [['desktop',1920,1080,false],['laptop',1440,900,false],['wide',2560,1080,false],['ultrawide',3840,1080,false],['tablet',1280,1024,false],['landscape',844,390,true],['portrait',390,844,true],['small',320,568,true],['short-landscape',568,320,true]]; +const report=[]; +const stats=page=>page.evaluate(()=>window.rhine.stats()); +async function bounds(page,selectors){return page.evaluate(selectors=>Object.fromEntries(selectors.map(s=>{const el=document.querySelector(s),r=el.getBoundingClientRect();return [s,{x:r.x,y:r.y,width:r.width,height:r.height,right:r.right,bottom:r.bottom}]})),selectors)} +async function inside(page,selectors,w,h){const rects=await bounds(page,selectors);for(const [s,r] of Object.entries(rects))assert.ok(r.x>=-1&&r.y>=-1&&r.right<=w+1&&r.bottom<=h+1,`${s} outside ${w}x${h}: ${JSON.stringify(r)}`);return rects} +async function touch(page,points){ + if(engine==='webkit') { + // Real WebKit pointer handlers; real multi-touch requires an iPhone. + await page.evaluate(points=>{const el=document.querySelector('#three-scene canvas');el.setPointerCapture=()=>{};for(const [i,[x,y]] of points.entries())el.dispatchEvent(new PointerEvent(i===0?'pointerdown':i===points.length-1?'pointerup':'pointermove',{pointerId:77,pointerType:'touch',isPrimary:true,clientX:x,clientY:y,bubbles:true}));},points);return; + } + const session=await page.context().newCDPSession(page); + await session.send('Input.dispatchTouchEvent',{type:'touchStart',touchPoints:[{x:points[0][0],y:points[0][1],id:1}]}); + for(const [x,y] of points.slice(1)){await session.send('Input.dispatchTouchEvent',{type:'touchMove',touchPoints:[{x,y,id:1}]});await page.waitForTimeout(16)} + // These are precise single-cell gestures; free flicks have their own momentum checks. + await page.waitForTimeout(160); + await session.send('Input.dispatchTouchEvent',{type:'touchEnd',touchPoints:[]});await session.detach(); +} +try{ +for(const [name,width,height,mobile] of cases.filter(([name])=>!process.env.REVIEW_CASES||process.env.REVIEW_CASES.split(',').includes(name))){ + const context=await browser.newContext({viewport:{width,height},hasTouch:mobile,isMobile:mobile,deviceScaleFactor:mobile?2:1}); + const page=await context.newPage(),errors=[];page.on('pageerror',e=>errors.push(e.message)); + await page.goto(`${base}/?scene=archive`); + await page.waitForFunction(()=>window.rhine?.stats().ready&&!document.querySelector('#loading'),null,{timeout:60000}); + await page.waitForFunction(()=>window.rhine.stats().extraction>=.395,null,{timeout:60000});await page.waitForTimeout(300); + const entry={name,viewport:{width,height},errors};report.push(entry); + entry.archive=await inside(page,['.brand','.system-nav','.read-file','.archive-navigation','.column-navigation','.archive-counter'],width,height); + await page.screenshot({path:resolve(output,`${name}-archive-final.png`)}); + const before=await stats(page); + if(mobile){ + const y=Math.round(height*(width>height?.4:.25)),x=Math.round(width*.4); + let vector=(await stats(page)).dragProjection.lane; + await touch(page,[[x,y],[x+vector.x*.4,y+vector.y*.4],[x+vector.x*.8,y+vector.y*.8]]); + await page.waitForFunction(()=>!rhine.stats().archiveMomentum); + assert.equal((await stats(page)).selectedCell.lane,before.selectedCell.lane+1,'Projected column travel advances one column'); + const row=(await stats(page)).selectedCell.row; + vector=(await stats(page)).dragProjection.row; + await touch(page,[[x,y],[x+vector.x*.4,y+vector.y*.4],[x+vector.x*.8,y+vector.y*.8]]); + await page.waitForFunction(()=>!rhine.stats().archiveMomentum); + assert.equal((await stats(page)).selectedCell.row,row+1,'Projected depth travel advances one file'); + } + // Eight steps traverse the seam without changing the remembered content. + const loop=await stats(page); + for(let i=0;i<8;i++){await page.locator('[data-action="next"]').click();await page.waitForTimeout(65)} + assert.equal((await stats(page)).selected,loop.selected); + assert.equal((await stats(page)).selectedCell.row,loop.selectedCell.row+8); + await page.waitForTimeout(2000); + await page.locator('.read-file').click(); + await page.waitForFunction(()=>window.rhine.stats().decryption.clarity===1,null,{timeout:60000});await page.waitForTimeout(1400); + entry.detail=await inside(page,['.back-button','.viewer-open','.detail-content'],width,height); + entry.detailStats=await stats(page); + await page.screenshot({path:resolve(output,`${name}-detail-final.png`)}); + assert.equal(entry.detailStats.extraction,4.05); + assert.ok(entry.detailStats.canInspect); + // The document can reach actions on short displays; bookmarking preserves scroll. + await page.locator('[data-action="bookmark"]').scrollIntoViewIfNeeded(); + const scroll=await page.locator('.detail-content').evaluate(el=>el.scrollTop); + await page.locator('[data-action="bookmark"]').click(); + assert.equal(await page.locator('.detail-content').evaluate(el=>el.scrollTop),scroll); + await page.locator('.viewer-open').click(); + await page.waitForFunction(()=>JSON.parse(document.querySelector('.model-viewer')?.dataset.stats||'{}').ready,null,{timeout:60000}); + await page.waitForTimeout(550); + await page.locator('[data-viewer="explode"]').click();await page.waitForFunction(()=>JSON.parse(document.querySelector('.model-viewer').dataset.stats).spread>.999); + await inside(page,['.viewer-back','.viewer-actions','.viewer-reset','.viewer-surface'],width,height); + await page.screenshot({path:resolve(output,`${name}-viewer-final.png`)}); + const viewerBefore=await page.locator('.model-viewer').evaluate(el=>JSON.parse(el.dataset.stats)); + if(mobile&&engine==='chromium') { + const host=await page.locator('.viewer-canvas').boundingBox();const x=host.x+host.width*.5,y=host.y+host.height*.5; + const session=await context.newCDPSession(page); + const points=(dx,dy=0)=>[{x:x-dx,y:y+dy,id:1},{x:x+dx,y:y+dy,id:2}]; + await session.send('Input.dispatchTouchEvent',{type:'touchStart',touchPoints:points(35)}); + for(const dx of [42,50,60]){await session.send('Input.dispatchTouchEvent',{type:'touchMove',touchPoints:points(dx,15)});await page.waitForTimeout(30)} + await session.send('Input.dispatchTouchEvent',{type:'touchEnd',touchPoints:[]});await page.waitForTimeout(500); + const after=await page.locator('.model-viewer').evaluate(el=>JSON.parse(el.dataset.stats)); + assert.ok(after.requestedDistance0.01,'Two fingers pan the view'); + await session.detach(); + // Orientation changes keep the requested pose, selection, and exploded state. + await page.setViewportSize({width:height,height:width});await page.waitForTimeout(400); + const rotated=await page.locator('.model-viewer').evaluate(el=>JSON.parse(el.dataset.stats)); + assert.equal(rotated.target,1);assert.ok(Math.abs(rotated.requestedDistance-after.requestedDistance)<1e-5); + assert.equal((await stats(page)).selected,entry.detailStats.selected); + await page.setViewportSize({width,height});await page.waitForTimeout(350); + } + await page.locator('[data-viewer="assemble"]').click();await page.locator('[data-viewer="close"]').click(); + await page.waitForFunction(()=>document.querySelector('.model-viewer').hidden); + assert.equal(await page.evaluate(()=>document.activeElement?.className),'viewer-open'); + await page.locator('[data-action="back"]').click();await page.waitForTimeout(350); + for(const action of ['search','saved','settings']){ + await page.locator(`[data-action="${action}"]`).click();await page.waitForTimeout(350); + await inside(page,['.terminal-modal','[data-action="close-modal"]'],width,height); + if(action==='search'){ + await page.locator('#archive-search').fill('X-001');assert.equal(await page.locator('.result-row').count(),1); + assert.ok(await page.locator('#archive-search').evaluate(el=>parseFloat(getComputedStyle(el).fontSize)>=16)); + } + if(action==='settings')await page.screenshot({path:resolve(output,`${name}-settings-final.png`)}); + await page.locator('[data-action="close-modal"]').click();await page.waitForFunction(()=>!document.querySelector('.modal-backdrop')); + } + assert.deepEqual(errors,[]);console.log(`${engine} ${name}: passed`);await context.close(); +} +}finally{await writeFile(resolve(output,`regression-${engine}.json`),JSON.stringify(report,null,2));await browser.close()} diff --git a/scripts/check-selection-state.mjs b/scripts/check-selection-state.mjs new file mode 100644 index 0000000..b819c6c --- /dev/null +++ b/scripts/check-selection-state.mjs @@ -0,0 +1,33 @@ +import {createRequire} from 'node:module'; +import {mkdir,writeFile} from 'node:fs/promises'; +import assert from 'node:assert/strict'; +const require=createRequire(import.meta.url),{chromium}=require(process.env.PLAYWRIGHT_MODULE||'playwright'); +const browser=await chromium.launch({channel:'msedge',headless:true}); +try { +const page=await browser.newPage({viewport:{width:1600,height:900}}),errors=[]; +page.on('pageerror',e=>errors.push(String(e)));page.on('console',m=>{if(m.type()==='error'&&/THREE|shader|WebGL/.test(m.text()))errors.push(m.text())}); +await page.goto('http://127.0.0.1:5176/?scene=archive');await page.waitForFunction(()=>window.rhine?.stats().ready); +const apply=async(values)=>{await page.evaluate(values=>window.wallpaperPropertyListener.applyUserProperties(Object.fromEntries(Object.entries(values).map(([k,value])=>[k,{value}]))),values);await page.waitForTimeout(300)}; +const stats=()=>page.evaluate(()=>window.rhine.stats()); +await apply({desktopmode:'workbench',boot:false,superperformance:true,screenfinish:false,hudparallax:false,uifrost:false,sound:false,music:false,reduced:false,colortheme:'dark',selectedindexaccent:true,selectionstyle:'music-flat',audioreactive:true}); +await page.waitForFunction(()=>window.rhine.stats().selectedIndexDim<.02&&window.rhine.stats().extraction>.38); +const before=await stats(); +// Simulate the host capturing the wallpaper's own sounds during a real drag. +await page.evaluate(()=>{window.localNoise=setInterval(()=>{const t=performance.now()/1000;window.dispatchEvent(new CustomEvent('rhine-local-sound',{detail:{until:t+.5}}));window.rhineWallpaperSpectrum={samples:Array(128).fill(.2),time:t}},35)}); +await page.mouse.move(860,480);await page.mouse.down();await page.mouse.move(1120,570,{steps:18});await page.mouse.up(); +await page.waitForTimeout(1200);const dragged=await stats(); +await page.evaluate(()=>{clearInterval(window.localNoise);window.rhineWallpaperSpectrum={samples:Array(128).fill(0),time:performance.now()/1000}}); +assert.equal(dragged.flatten,0);assert.equal(dragged.spectrumActivity,0); +await page.waitForFunction(()=>window.rhine.stats().selectedIndexDim<.02&&window.rhine.stats().extraction>.38&&!window.rhine.stats().archiveMomentum); +await page.evaluate(()=>{const n=Number(window.rhine.stats().selected.slice(2))-1;window.rhine.select((n+1)%40)}); +const frames=[];for(let i=0;i<14;i++){await page.waitForTimeout(120);frames.push(await stats())} +assert.ok(frames.some(f=>f.selectedIndexDim>.03&&f.selectedIndexDim<.97),'New selected accent fades in'); +assert.ok(frames.some(f=>f.returningIndexDims.some(x=>x.dim>.03&&x.dim<.97)),'Outgoing accent fades out'); +await apply({selectionstyle:'flat'});await page.waitForFunction(()=>window.rhine.stats().flatten>.995); +const flat=await stats();assert.ok(flat.selectedIndexDim>.99); +await apply({selectedindexaccent:false});assert.ok((await stats()).selectedIndexDim>.99,'Flat disables accent even when all-label style is selected'); +await mkdir('verification/selection-state',{recursive:true});await page.screenshot({path:'verification/selection-state/flat.png'}); +await apply({selectedindexaccent:true,selectionstyle:'music-flat'});await page.waitForFunction(()=>window.rhine.stats().flatten<.01&&window.rhine.stats().selectedIndexDim<.02&&window.rhine.stats().extraction>.38);await page.screenshot({path:'verification/selection-state/silent-selected.png'}); +assert.deepEqual(errors,[]);await writeFile('verification/selection-state/results.json',JSON.stringify({before,dragged,frames:frames.map(f=>({dim:f.selectedIndexDim,returning:f.returningIndexDims,lift:f.extraction})),flat,errors},null,2)); +console.log('Silent drag retains unflattened array; both label directions interpolate; flat mode has no accent; restore passed.'); +} finally {await browser.close()} diff --git a/scripts/check-shell.mjs b/scripts/check-shell.mjs new file mode 100644 index 0000000..028432f --- /dev/null +++ b/scripts/check-shell.mjs @@ -0,0 +1,85 @@ +import assert from "node:assert/strict"; +import { readFile } from "node:fs/promises"; +import * as T from "three"; +import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js"; +import { glassRevealAtHeight as reveal } from "../src/glass-reveal.ts"; +import { decryptionFrame } from "../src/decryption.ts"; +async function load(path) { + const b = await readFile(new URL(path, import.meta.url)); + const s = ( + await new GLTFLoader().parseAsync( + b.buffer.slice(b.byteOffset, b.byteOffset + b.byteLength), + "", + ) + ).scene; + s.updateMatrixWorld(true); + return s; +} +// Subsequent ring changes are covered by check-internal-optics.mjs. +const after = await load("../public/assets/archive-assembly.glb"); +let patch, + metal, + engravingDepth = -Infinity; +after.traverse((m) => { + if (!m.isMesh) return; + const name = m.material.name.replace(/\.\d+$/, ""); + assert.notEqual(name, "Amber_Lightguide"); + const box = new T.Box3().setFromObject(m); + if (name === "Index_Inlay") patch = box; + if (name === "Titanium_Fasteners") metal = m; + if (name.startsWith("Case_") && m.userData.assemblyPart === "cover") + engravingDepth = Math.max(engravingDepth, box.max.z); +}); +assert.ok(patch && metal); +assert.ok(Math.abs(patch.max.y - 3.7) < 1e-5, "Patch flush with top"); +assert.ok(Math.abs(patch.max.z - 0.206) < 1e-5, "Patch flush with front"); +assert.ok(Math.abs(patch.max.x - patch.min.x - 0.25) < 1e-5); +assert.ok( + engravingDepth < 0.174, + "Every frame/boss engraving lies behind front cover", +); +const positions = metal.geometry.attributes.position; +let tr = 0, + bl = 0; +for (let i = 0; i < positions.count; i++) { + const v = new T.Vector3() + .fromBufferAttribute(positions, i) + .applyMatrix4(metal.matrixWorld); + if (v.x > 2.25 && v.y > 3.4) tr++; + else if (v.x < -2.2 && v.y < 0.23) bl++; + else assert.fail("Extra screw outside top-right / bottom-left"); +} +assert.ok(tr > 0 && bl > 0); +for (let h = 0; h <= 1; h += 0.01) { + assert.equal(reveal(0, h), 0); + assert.equal(reveal(1, h), 1); + let last = 0; + for (let p = 0; p <= 1; p += 0.01) { + const next = reveal(p, h); + assert.ok(next >= last); + last = next; + } +} +assert.equal(reveal(0.5, 0.9), 1); +assert.equal(reveal(0.5, 0.1), 0); +let last = 0; +for (let t = 38.84; t <= 39.56; t += 0.001) { + const p = decryptionFrame(t).clarity; + assert.ok(p >= last); + last = p; +} +assert.equal(decryptionFrame(38.84).clarity, 0); +assert.equal(decryptionFrame(39.56).clarity, 1); +console.log( + JSON.stringify( + { + passed: true, + screwRegions: 2, + patchSize: patch.getSize(new T.Vector3()).toArray(), + engravingDepth, + reveal: "fully frosted → top to bottom → fully clear", + }, + null, + 2, + ), +); diff --git a/scripts/check-startup-2d-host.mjs b/scripts/check-startup-2d-host.mjs new file mode 100644 index 0000000..eea7231 --- /dev/null +++ b/scripts/check-startup-2d-host.mjs @@ -0,0 +1,12 @@ +import {createServer} from 'node:http'; +import {spawn} from 'node:child_process'; +import {readFile,writeFile,mkdir,cp} from 'node:fs/promises'; +import {resolve} from 'node:path'; +import assert from 'node:assert/strict'; +const dir=resolve('verification/startup-2d/host');await mkdir(dir,{recursive:true});await cp('release/wallpaper',dir,{recursive:true}); +const project=JSON.parse(await readFile(dir+'/project.json','utf8'));delete project.workshopid;delete project.workshopurl;project.general.properties.load3donstartup.value=false;project.general.properties.boot.value=true;await writeFile(dir+'/project.json',JSON.stringify(project)); +const html=await readFile(dir+'/index.html','utf8'); +await writeFile(dir+'/index.html',html.replace('',``)); +let finish;const result=new Promise(r=>finish=r);const server=createServer((req,res)=>{let body='';req.on('data',b=>body+=b);req.on('end',()=>{res.setHeader('Access-Control-Allow-Origin','*');res.end('ok');try{finish(JSON.parse(body))}catch{}})});await new Promise(r=>server.listen(5185,'127.0.0.1',r)); +const exe='D:/Game/Steam/steamapps/common/wallpaper_engine/wallpaper64.exe',location='Rhine Lab startup diagnostic';const run=args=>new Promise((ok,no)=>{const child=spawn(exe,args,{windowsHide:true,stdio:'ignore'});child.once('error',no);child.once('exit',ok)});let timeout; +try{await run(['-control','openWallpaper','-file',dir+'/project.json','-playInWindow',location,'-width','640','-height','360','-x','-30000','-y','-30000']);const data=await Promise.race([result,new Promise((_,no)=>timeout=setTimeout(()=>no(Error('Host timeout')),30000))]);console.log(data);await writeFile('verification/startup-2d/host-results.json',JSON.stringify(data,null,2));assert.deepEqual(data,{state:'off',mode:'archive',property:false,canvas:0,models:0})}finally{clearTimeout(timeout);server.close();await run(['-control','closeWallpaper','-location',location])} diff --git a/scripts/check-startup-2d.mjs b/scripts/check-startup-2d.mjs new file mode 100644 index 0000000..84003ef --- /dev/null +++ b/scripts/check-startup-2d.mjs @@ -0,0 +1,23 @@ +import assert from 'node:assert/strict'; +import {createRequire} from 'node:module'; +import {createServer} from 'node:http'; +import {readFile,mkdir,writeFile} from 'node:fs/promises'; +import {resolve,extname} from 'node:path'; +const {chromium}=createRequire(import.meta.url)(process.env.PLAYWRIGHT_MODULE||'playwright'); +let enabled=false; +const server=createServer(async(req,res)=>{try{let path=decodeURIComponent(new URL(req.url,'http://localhost').pathname);if(path==='/')path='/index.html';let data=await readFile(resolve('release/wallpaper'+path));if(path.endsWith('.html'))data=Buffer.from(data.toString().replace('',``));res.setHeader('Content-Type',({'.js':'text/javascript','.css':'text/css','.html':'text/html','.json':'application/json','.woff2':'font/woff2'})[extname(path)]||'application/octet-stream');res.end(data)}catch{res.statusCode=404;res.end()}}); +await new Promise(r=>server.listen(5184,'127.0.0.1',r)); +const browser=await chromium.launch({channel:'msedge',headless:true}); +try{const page=await browser.newPage({viewport:{width:1280,height:720}}),models=[],errors=[]; +await page.addInitScript(()=>{window.wallpaperRegisterAudioListener=()=>{}}); +page.on('request',r=>{if(r.url().includes('.glb'))models.push(r.url())});page.on('pageerror',e=>errors.push(e.message)); +await page.goto('http://127.0.0.1:5184/');await page.waitForTimeout(600);assert.equal(await page.locator('canvas').count(),0);assert.equal(models.length,0);await page.waitForFunction(()=>window.rhine?.stats().startup==='started'); +assert.equal(await page.locator('canvas').count(),0);assert.equal(models.length,0);assert.equal(await page.evaluate(()=>rhine.stats().mode),'boot'); +await page.evaluate(()=>rhine.seek(21.8));await page.waitForFunction(()=>rhine.stats().mode==='archive');assert.equal(await page.locator('canvas').count(),0); +await page.locator('[data-action="toggle-three"]').click();await page.waitForFunction(()=>rhine.stats().threeState==='on',{},{timeout:60000});assert.ok(models.length>0);assert.equal(await page.locator('#three-scene canvas').count(),1); +await page.evaluate(()=>wallpaperPropertyListener.applyUserProperties({load3donstartup:{value:false}}));assert.equal(await page.evaluate(()=>rhine.stats().threeState),'on'); +await mkdir('verification/startup-2d',{recursive:true});await page.screenshot({path:'verification/startup-2d/manual-on.png'}); +enabled=true;await page.reload();await page.waitForFunction(()=>rhine.stats().startup==='started');assert.equal(await page.locator('#three-scene canvas').count(),1);assert.equal(await page.evaluate(()=>rhine.stats().threeState),'on');assert.deepEqual(errors,[]); +await writeFile('verification/startup-2d/results.json',JSON.stringify({delayedHostCallbackMs:1800,noInitialCanvas:true,noInitialModelRequests:true,bootTo2d:true,manualLoad:true,startupOnly:true,default3d:true,errors},null,2));console.log('2D startup, no canvas/model requests, boot completion, manual load, startup-only property and default 3D passed.'); +}finally{await browser.close();server.close()} + diff --git a/scripts/check-startup-entry.mjs b/scripts/check-startup-entry.mjs new file mode 100644 index 0000000..b140563 --- /dev/null +++ b/scripts/check-startup-entry.mjs @@ -0,0 +1,104 @@ +// Real browser checks for entry audio, first-load fonts and failure recovery. +import assert from 'node:assert/strict'; +import { mkdir, writeFile } from 'node:fs/promises'; +import { resolve } from 'node:path'; +import { pathToFileURL } from 'node:url'; +const { chromium, webkit } = await import(process.env.PLAYWRIGHT_MODULE ? pathToFileURL(resolve(process.env.PLAYWRIGHT_MODULE)).href : 'playwright'); +const base = process.env.REVIEW_URL || 'http://127.0.0.1:5190/'; +const engine = process.env.REVIEW_ENGINE || 'chromium'; +const browser = engine === 'webkit' ? await webkit.launch({headless:true}) : await chromium.launch({channel:process.env.REVIEW_CHANNEL || 'chrome',headless:true,args:['--use-angle=d3d11','--enable-gpu','--ignore-gpu-blocklist']}); +const report = {engine,version:browser.version(),checks:[],errors:[]}; +const output = resolve('.tools/issues');await mkdir(output,{recursive:true}); +const waitEntry = page => page.waitForFunction(()=>window.rhine?.stats().startup==='waiting',null,{timeout:60000}); +const waitStart = page => page.waitForFunction(()=>window.rhine?.stats().startup==='started'&&!document.querySelector('#loading'),null,{timeout:60000}); +async function fresh(options={},prefs) { + const context=await browser.newContext({viewport:{width:1440,height:900},serviceWorkers:'block',reducedMotion:'no-preference',...options}); + if(prefs)await context.addInitScript(prefs=>localStorage.setItem('rhine-settings',JSON.stringify(prefs)),prefs); + const page=await context.newPage();page.on('pageerror',e=>report.errors.push(e.message));return {context,page}; +} +try { + if(engine==='webkit') { + // Windows WebKit lacks the required audio decoder. Exercise the entry + // viewport and its real error path; never label this an iPhone audio test. + const {context,page}=await fresh({viewport:{width:390,height:844},hasTouch:true,isMobile:true}); + await page.goto(base);await waitEntry(page); + await page.screenshot({path:resolve(output,'entry-webkit.png')}); + await page.locator('.entry-start').tap(); + await page.waitForFunction(()=>['started','error'].includes(window.rhine.stats().startup),null,{timeout:25000}); + const state=await page.evaluate(()=>window.rhine.stats().startup); + if(state==='error')await page.locator('.entry-silent').tap(); + await waitStart(page);report.checks.push({name:'WebKit portrait entry and decoder fallback',audioResult:state});await context.close(); + } else { + for(const [name,options,input,prefs] of [ + ['desktop',{},'click'], + ['portrait',{viewport:{width:390,height:844},hasTouch:true,isMobile:true},'tap'], + ['keyboard',{},'keyboard'], + ['reduced',{reducedMotion:'reduce'},'click'], + ['sound-only',{},'click',{sound:true,music:false}], + ['music-only',{},'click',{sound:false,music:true}], + ]) { + const {context,page}=await fresh(options,prefs); + await page.goto(base);await waitEntry(page); + const before=await page.evaluate(()=>window.rhine.stats()); + assert.equal(before.audio.state,'locked');assert.equal(before.audio.tracks,0); + assert.equal(await page.locator('#stage').evaluate(el=>el.inert),true); + await page.waitForTimeout(700); + assert.equal(await page.evaluate(()=>window.rhine.stats().bootTime),6.76); + assert.equal(await page.evaluate(()=>document.documentElement.dataset.offlineReady),undefined); + const fonts=await page.evaluate(()=>performance.getEntriesByType('resource').filter(e=>e.name.endsWith('.woff2')).map(e=>({url:new URL(e.name).pathname,bytes:e.decodedBodySize}))); + assert.ok(fonts.length>0&&fonts.every(f=>f.url.includes('/fonts/misans-webfont-4.3.1/'))); + assert.ok(fonts.reduce((n,f)=>n+f.bytes,0)<2*1024*1024,'Entry must not load entire font families'); + const rect=await page.locator('.entry-start').boundingBox(),vp=page.viewportSize(); + assert.ok(rect.x>=0&&rect.y>=0&&rect.x+rect.width<=vp.width&&rect.y+rect.height<=vp.height); + if(name==='desktop'||name==='portrait')await page.screenshot({path:resolve(output,`entry-${name}.png`)}); + if(input==='keyboard')await page.keyboard.press('Enter'); + else if(input==='tap')await page.locator('.entry-start').tap(); + else await page.locator('.entry-start').click(); + await waitStart(page); + const after=await page.evaluate(()=>window.rhine.stats()); + assert.equal(after.mode,name==='reduced'?'archive':'boot','Entry activation must not leak into skip/open'); + assert.equal(after.audio.state,'running');assert.equal(after.audio.loaded,name!=='sound-only');assert.equal(after.audio.tracks,name==='sound-only'?0:3); + if(name!=='reduced') { + assert.ok(after.bootTime<9,'Waiting time must not advance the animation'); + if(name!=='music-only')await page.waitForFunction(()=>window.rhine.stats().audio.playedKeys>0,null,{timeout:10000}); + } + report.checks.push({name,entryFontBytes:fonts.reduce((n,f)=>n+f.bytes,0),fontRequests:fonts.length,audio:after.audio.state,tracks:after.audio.tracks}); + await context.close(); + } + { + const {context,page}=await fresh({}, {sound:false,music:false,reduced:true}); + await page.goto(base);await waitStart(page);assert.equal(await page.locator('.entry-start').count(),0); + assert.equal(await page.evaluate(()=>window.rhine.stats().audio.state),'locked'); + report.checks.push({name:'Existing silent preference enters without opening audio'});await context.close(); + } + { + const {context,page}=await fresh();let fail=true; + await page.route('**/audio/*.ogg',route=>fail?route.fulfill({status:503,body:'Unavailable'}):route.continue()); + await page.goto(base);await waitEntry(page);await page.locator('.entry-start').click(); + await page.waitForFunction(()=>window.rhine.stats().startup==='error'); + assert.equal(await page.evaluate(()=>window.rhine.stats().bootTime),6.76); + assert.equal(await page.evaluate(()=>window.rhine.stats().audio.tracks),0); + fail=false;await page.locator('.entry-start').click();await waitStart(page); + assert.equal(await page.evaluate(()=>window.rhine.stats().audio.tracks),3); + report.checks.push({name:'Failed music leaves opening paused; retry downloads and starts all tracks'});await context.close(); + } + { + const {context,page}=await fresh();let unblock; + const blocked=new Promise(r=>unblock=r); + await page.route('**/audio/*.ogg',async route=>{await blocked;await route.continue().catch(()=>{});}); + await page.goto(base);await waitEntry(page);await page.locator('.entry-start').click(); + await page.waitForFunction(()=>window.rhine.stats().startup==='starting'); + await page.locator('.entry-start').click({force:true});assert.equal(await page.evaluate(()=>window.rhine.stats().startup),'starting'); + await page.locator('.entry-silent').click();unblock();await waitStart(page);await page.waitForTimeout(500); + const state=await page.evaluate(()=>window.rhine.stats());assert.equal(state.audio.tracks,0);assert.equal(state.audio.preferences.sound,false);assert.equal(state.audio.preferences.music,false); + report.checks.push({name:'Repeated entry is ignored; silent entry cancels pending music and does not start late'});await context.close(); + } + { + const {context,page}=await fresh();await page.goto(base+'?time=6.2&freeze=1');await waitStart(page); + assert.equal(await page.evaluate(()=>window.rhine.stats().bootTime),11.2); + await page.waitForTimeout(500);assert.equal(await page.evaluate(()=>window.rhine.stats().bootTime),11.2); + report.checks.push({name:'Frame review bypasses entry and preserves reference time'});await context.close(); + } + } + assert.deepEqual(report.errors,[]);console.log(JSON.stringify(report,null,2)); +} finally {await writeFile(resolve(output,`startup-${engine}-${process.env.REVIEW_CHANNEL||'chrome'}.json`),JSON.stringify(report,null,2));await browser.close();} diff --git a/scripts/check-startup-motion.mjs b/scripts/check-startup-motion.mjs new file mode 100644 index 0000000..a70f0fa --- /dev/null +++ b/scripts/check-startup-motion.mjs @@ -0,0 +1,45 @@ +// Run against a built preview; optionally set REVIEW_CHANNEL=msedge. +import assert from 'node:assert/strict'; +import {mkdir, writeFile} from 'node:fs/promises'; +import {resolve} from 'node:path'; +import {pathToFileURL} from 'node:url'; +const {chromium}=await import(process.env.PLAYWRIGHT_MODULE?pathToFileURL(resolve(process.env.PLAYWRIGHT_MODULE)).href:'playwright'); +const channel=process.env.REVIEW_CHANNEL || 'chrome'; +const browser=await chromium.launch({channel,headless:true,args:['--use-angle=d3d11','--enable-gpu','--ignore-gpu-blocklist']}); +const report={channel,version:browser.version(),checks:[]}; +try {for(const reducedMotion of ['no-preference','reduce']) { + const context=await browser.newContext({viewport:{width:1440,height:900},reducedMotion,serviceWorkers:'block'}); + const page=await context.newPage(),errors=[];page.on('pageerror',error=>errors.push(error.message)); + const loaded=async()=>{ + await page.waitForFunction(()=>window.rhine?.stats().ready); + if(await page.locator('.entry-start').count())await page.locator('.entry-start').click(); + await page.waitForFunction(()=>!document.querySelector('#loading')); + }; + await page.goto(process.env.REVIEW_URL || 'http://127.0.0.1:5190/');await loaded(); + let state=await page.evaluate(()=>window.rhine.stats()); + assert.equal(state.mode,reducedMotion==='reduce'?'archive':'boot'); + assert.equal(state.motion.reduced,reducedMotion==='reduce'); + await page.evaluate(()=>window.rhine.archive()); + await page.getByRole('button',{name:'系统设置',exact:true}).click(); + assert.equal(await page.locator('.settings-label').textContent(),'设置'); + const checkbox=page.locator('[data-pref="reduced"]'); + assert.equal(await checkbox.isChecked(),reducedMotion==='reduce'); + if(reducedMotion==='reduce') { + await page.getByRole('button',{name:'启用完整动效并重播'}).click(); + await page.waitForFunction(()=>window.rhine.stats().mode==='boot'); + assert.equal(await page.locator('.modal-backdrop').count(),0); + state=await page.evaluate(()=>window.rhine.stats()); + assert.equal(state.motion.reduced,false);assert.equal(state.motion.systemReduced,true); + await page.evaluate(()=>window.rhine.archive());await page.waitForTimeout(1000); + await page.locator('[data-action="next"]').click();await page.waitForTimeout(150); + assert.equal(await page.locator('#stage').evaluate(el=>el.classList.contains('reduce-motion')),false); + assert.ok((await page.evaluate(()=>window.rhine.stats().pulses.length))>0); + await page.locator('.read-file').click(); + // The app override also restores document reveals under an OS reduce preference. + await page.waitForSelector('.document-redaction-window',{state:'attached'}); + assert.equal(await page.locator('.document-redaction-window').first().evaluate(el=>getComputedStyle(el).display),'block'); + await page.reload();await loaded();assert.equal((await page.evaluate(()=>window.rhine.stats())).mode,'boot'); + } + assert.deepEqual(errors,[]);report.checks.push({reducedMotion,passed:true});await context.close(); +}}finally{await browser.close()} +await mkdir('.tools/responsive',{recursive:true});await writeFile(`.tools/responsive/startup-${channel}.json`,JSON.stringify(report,null,2));console.log(JSON.stringify(report,null,2)); diff --git a/scripts/check-super-performance.mjs b/scripts/check-super-performance.mjs new file mode 100644 index 0000000..d3ec80f --- /dev/null +++ b/scripts/check-super-performance.mjs @@ -0,0 +1,26 @@ +import {createRequire} from 'node:module'; +import {mkdir,writeFile} from 'node:fs/promises'; +import assert from 'node:assert/strict'; +const require=createRequire(import.meta.url),{chromium}=require(process.env.PLAYWRIGHT_MODULE || 'playwright'); +const browser=await chromium.launch({channel:'msedge',headless:true}); +const page=await browser.newPage({viewport:{width:1920,height:1080},deviceScaleFactor:1}); +const errors=[];page.on('pageerror',e=>errors.push(String(e)));page.on('console',m=>{if(m.type()==='error'&&/THREE|shader|WebGL/.test(m.text()))errors.push(m.text())}); +await page.goto('http://127.0.0.1:5176/?scene=archive');await page.waitForFunction(()=>window.rhine?.stats().ready); +const apply=async(values,wait=1500)=>{await page.evaluate(values=>window.wallpaperPropertyListener.applyUserProperties(Object.fromEntries(Object.entries(values).map(([k,value])=>[k,{value}]))),values);await page.waitForTimeout(wait)}; +await apply({boot:false,desktopmode:'workbench',sound:false,music:false,reduced:false,renderquality:'original',hudparallax:true,huddepth:20,hudtracking:false,uifrost:true,screenfinish:true,screengrain:20,screenfringe:20,screenvignette:20,audioreactive:true,selectionstyle:'original'}); +await page.evaluate(()=>window.wallpaperPropertyListener.applyGeneralProperties({fps:60})); +const measure=()=>page.evaluate(()=>{const host=document.querySelector('#three-scene'),canvas=host.querySelector('canvas'),gl=canvas.getContext('webgl2'),ext=gl.getExtension('WEBGL_debug_renderer_info');return {quality:JSON.parse(host.dataset.renderQuality),stats:window.rhine.stats(),font:getComputedStyle(document.querySelector('.wb-clock')).fontSize,screen:document.querySelector('#stage').dataset.screenFinish,gpu:ext?gl.getParameter(ext.UNMASKED_RENDERER_WEBGL):gl.getParameter(gl.RENDERER)}}); +await mkdir('verification/super-performance',{recursive:true}); +await page.waitForTimeout(6000);const original=await measure();await page.screenshot({path:'verification/super-performance/original.png'}); +await apply({superperformance:true},6000);const fast=await measure();await page.screenshot({path:'verification/super-performance/super.png'}); +assert.equal(fast.stats.superPerformance,true);assert.equal(fast.stats.motion.reduced,false);assert.equal(fast.quality.shadows,0);assert.equal(fast.quality.aoSamples,0);assert.equal(fast.quality.depthOfField,0);assert.equal(fast.screen,'false');assert.equal(fast.font,original.font);assert.ok(fast.quality.width*fast.quality.height<=921600);assert.ok(fast.stats.drawCallswindow.rhine.select(9));await page.waitForTimeout(180);assert.equal((await measure()).stats.selected,'X-010'); +await apply({desktopmode:'archive'});await page.waitForTimeout(1000);await page.evaluate(()=>window.rhine.detail());await page.waitForTimeout(3500);assert.equal((await measure()).stats.mode,'detail');await page.screenshot({path:'verification/super-performance/detail.png'}); +await page.evaluate(()=>window.rhine.archive());await page.waitForTimeout(1200); +await apply({superperformance:false},4000);const restored=await measure();assert.equal(restored.quality.width,original.quality.width);assert.equal(restored.quality.shadows,original.quality.shadows);assert.equal(restored.screen,'true');assert.equal(restored.stats.motion.reduced,false); +await apply({renderquality:'custom',qualityscale:75,qualityshadows:'0',qualityaosamples:'0',qualitydepthoffield:0,qualitytransmission:'0.5',qualitypixelratio:'1'});const custom=await measure();assert.equal(custom.quality.shadows,0);assert.equal(custom.quality.transmission,.5);assert.equal(custom.quality.width,1440); +await apply({qualityscale:65});const partial=await measure();assert.equal(partial.quality.width,1248);assert.equal(partial.quality.transmission,.5); +await apply({superperformance:true});await apply({qualityscale:85});assert.ok((await measure()).quality.width<=1280); +await apply({superperformance:false});const customRestored=await measure();assert.equal(customRestored.quality.width,1632);assert.equal(customRestored.quality.transmission,.5); +await page.setViewportSize({width:3840,height:2160});await apply({superperformance:true});const uhd=await measure();assert.ok(uhd.quality.width*uhd.quality.height<=921600); +assert.deepEqual(errors,[]);await writeFile('verification/super-performance/results.json',JSON.stringify({original,fast,restored,custom,partial,customRestored,uhd,errors},null,2));console.log(JSON.stringify({original:{pixels:original.quality.width*original.quality.height,calls:original.stats.drawCalls,triangles:original.stats.triangles,fps:original.stats.fps},fast:{pixels:fast.quality.width*fast.quality.height,calls:fast.stats.drawCalls,triangles:fast.stats.triangles,fps:fast.stats.fps},gpu:fast.gpu,checks:'motion, detail, restore, custom partial callbacks, override recovery, 4K pixel budget passed'}));await browser.close(); diff --git a/scripts/check-theme.mjs b/scripts/check-theme.mjs new file mode 100644 index 0000000..ca296a1 --- /dev/null +++ b/scripts/check-theme.mjs @@ -0,0 +1,12 @@ +import assert from 'node:assert/strict'; +import {readFileSync} from 'node:fs'; +import ts from 'typescript'; +const source=ts.transpileModule(readFileSync('src/theme-motion.ts','utf8'),{compilerOptions:{module:ts.ModuleKind.ESNext,target:ts.ScriptTarget.ES2022}}).outputText; +const {ThemeWave}=await import(`data:text/javascript;base64,${Buffer.from(source).toString('base64')}`); +const wave=new ThemeWave(),center={lane:2,row:12},far={lane:5,row:20}; +wave.set(true,0,center);wave.beginFrame();const a=wave.sample(center,.3),b=wave.sample(far,.3);assert.ok(a>b&&a>0&&a<1,'Origin changes first'); +wave.set(false,.3,center);assert.equal(wave.sample(center,.3),a);assert.equal(wave.sample(far,.3),b,'Reversal preserves each current card'); +assert.equal(wave.sample(center,2),0);assert.equal(wave.sample(far,2),0);assert.equal(wave.background(2),0); +wave.set(true,3,center,true);assert.equal(wave.sample(far,3),1);assert.equal(wave.background(3),1,'Reduced motion goes directly to target'); +wave.set(false,4,center);wave.beginFrame();for(let i=-100;i<100;i++){const n=wave.sample({lane:i,row:i},4.2);assert.ok(n>=0&&n<=1)} +console.log('Theme cascade, reversal continuity, endpoints, reduced motion and bounded values passed.'); diff --git a/scripts/check-three-release.mjs b/scripts/check-three-release.mjs new file mode 100644 index 0000000..21ece55 --- /dev/null +++ b/scripts/check-three-release.mjs @@ -0,0 +1,52 @@ +import {createRequire} from 'node:module'; +import {mkdir,writeFile} from 'node:fs/promises'; +import assert from 'node:assert/strict'; +const require=createRequire(import.meta.url),{chromium}=require(process.env.PLAYWRIGHT_MODULE||'playwright'); +const browser=await chromium.launch({channel:'msedge',headless:true}); +try { +const page=await browser.newPage({viewport:{width:1600,height:900}}),errors=[]; +page.on('pageerror',e=>errors.push(String(e)));page.on('console',m=>{if(m.type()==='error'&&/THREE|shader|WebGL/.test(m.text()))errors.push(m.text())}); +await page.goto('http://127.0.0.1:5176/?scene=archive');await page.waitForFunction(()=>window.rhine?.stats().ready); +const apply=async(values)=>{await page.evaluate(values=>window.wallpaperPropertyListener.applyUserProperties(Object.fromEntries(Object.entries(values).map(([k,value])=>[k,{value}]))),values);await page.waitForTimeout(300)}; +const stats=()=>page.evaluate(()=>window.rhine.stats()); +const click=()=>page.locator('[data-action="toggle-three"]').click(); +await apply({desktopmode:'workbench',boot:false,superperformance:true,screenfinish:false,hudparallax:false,uifrost:false,sound:false,music:false,reduced:false}); +await page.waitForFunction(()=>window.rhine.stats().mode==='archive'); +await page.evaluate(()=>{window.contextLosses=0;const canvas=document.querySelector('#three-scene canvas');canvas.addEventListener('webglcontextlost',()=>window.contextLosses++);window.oldCanvas=canvas}); +const before=await stats(); +await click();await page.waitForTimeout(150);const closing=await stats();assert.equal(closing.threeState,'closing');assert.ok(closing.presentation<1&&closing.presentation>0); +await click();await page.waitForFunction(()=>window.rhine.stats().presentation===1);assert.equal(await page.evaluate(()=>window.contextLosses),0); +const picture='data:image/svg+xml,'+encodeURIComponent(''); +await apply({customwallpaper:true,customwallpaperfile:picture}); +await page.waitForFunction(()=>document.querySelector('.wallpaper-background').dataset.ready==='true'); +assert.equal(await page.locator('.wallpaper-background').evaluate(el=>getComputedStyle(el).opacity),'0'); +await click();await page.waitForFunction(()=>window.rhine.stats().threeState==='off');await page.waitForTimeout(800); +assert.equal(await page.locator('#three-scene canvas').count(),0);assert.equal(await page.evaluate(()=>window.contextLosses),1); +assert.equal(await page.locator('.relay-entry').isVisible(),false); +assert.equal(await page.locator('.wallpaper-background').evaluate(el=>getComputedStyle(el).opacity),'1'); +await mkdir('verification/three-release',{recursive:true});await page.screenshot({path:'verification/three-release/custom-wallpaper.png'}); +await click();await page.waitForFunction(()=>window.rhine.stats().threeState==='on');const reloading=await stats();assert.ok(reloading.presentation<1); +await page.waitForFunction(()=>window.rhine.stats().presentation===1);await page.waitForTimeout(800); +assert.equal(await page.locator('.wallpaper-background').evaluate(el=>getComputedStyle(el).opacity),'0');assert.equal(await page.locator('#three-scene canvas').count(),1); +assert.equal(await page.evaluate(()=>window.oldCanvas===document.querySelector('#three-scene canvas')),false); +assert.equal((await stats()).selected,before.selected); +await page.waitForFunction(()=>!document.querySelector('.relay-entry').hidden); +assert.equal(await page.locator('.relay-entry').isVisible(),true); +// Reduced motion also disposes; toggling the optional picture never recreates WebGL. +await apply({reduced:true});await click();await page.waitForFunction(()=>window.rhine.stats().threeState==='off'); +await apply({customwallpaper:false});assert.equal(await page.locator('.wallpaper-background').evaluate(el=>getComputedStyle(el).opacity),'0'); +await apply({customwallpaper:true});assert.equal(await page.locator('.wallpaper-background').evaluate(el=>getComputedStyle(el).opacity),'1'); +await apply({customwallpaperfile:''});assert.equal(await page.locator('.wallpaper-background img').count(),0); +assert.equal(await page.locator('canvas').count(),0); +await click();await page.waitForFunction(()=>window.rhine.stats().threeState==='on'&&window.rhine.stats().presentation===1); +await apply({desktopmode:'archive'});await page.evaluate(()=>window.rhine.detail());await page.waitForFunction(()=>window.rhine.stats().mode==='detail'); +await page.locator('[data-action="model-viewer"]').click();await page.waitForTimeout(1800); +assert.equal(await page.locator('canvas').count(),2);await page.keyboard.press('Escape');await page.waitForTimeout(400); +await click();await page.waitForFunction(()=>window.rhine.stats().threeState==='off');assert.equal(await page.locator('canvas').count(),0); +// Details stay readable with no renderer. +assert.equal(await page.locator('#detail-content').evaluate(el=>el.inert),false); +await click();await page.waitForFunction(()=>window.rhine.stats().threeState==='on'); +await page.screenshot({path:'verification/three-release/restored.png'}); +assert.deepEqual(errors,[]);await writeFile('verification/three-release/results.json',JSON.stringify({before,closing,reloading,after:await stats(),errors},null,2)); +console.log('Exit reversal, actual context loss, zero canvases, custom image, fresh context, reduced motion, viewer disposal and detail recovery passed.'); +} finally {await browser.close()} diff --git a/scripts/check-viewport.mjs b/scripts/check-viewport.mjs new file mode 100644 index 0000000..6cd8472 --- /dev/null +++ b/scripts/check-viewport.mjs @@ -0,0 +1,29 @@ +import assert from "node:assert/strict"; +import { viewportLayout, openingLayout, archiveFraming } from "../src/viewport-layout.ts"; +import { renderDimensions, qualityPresets } from "../src/render-quality.ts"; + +for (const [w,h,touch] of [[1920,1080,false],[2560,1080,false],[1280,1024,false],[844,390,true],[390,844,true],[320,568,true]]) { + const layout=viewportLayout(w,h,touch); + assert.ok(Math.abs(layout.width*layout.scale-w)<1e-8); + assert.ok(Math.abs(layout.height*layout.scale-h)<1e-8); + const film=viewportLayout(w,h,touch,true); + const opening=openingLayout(w,h); + assert.ok(Math.abs(opening.width*opening.scale-w)<1e-8 && Math.abs(opening.height*opening.scale-h)<1e-8,'Opening fills the viewport'); + assert.ok(opening.width>=1279.99 && opening.height>=1079.99,'Central login content fits without stretching'); + assert.equal(film.width/film.height,16/9); + assert.ok(film.width*film.scale<=w+.001&&film.height*film.scale<=h+.001); + const shot=archiveFraming(layout.width,layout.height,7.33,1,layout.kind==='compact'); + assert.ok(shot.span>=5.9&&shot.detailX>0&&shot.detailX<1&&shot.detailY>0&&shot.detailY<1); + if(layout.kind==='portrait') { + const coverHeight=3.7*h/shot.span; + assert.ok(shot.detailY*h-coverHeight/2>=110, 'Cover clears the return control on short phones'); + assert.ok(shot.detailY*h+coverHeight/2<=h*.54-40, 'Cover clears the document header'); + } + const render=renderDimensions(qualityPresets.original,layout.width,layout.height,layout.scale,3,16384); + assert.ok(render.width>=w, 'CSS-sized phones must not retain the old 1920px scale factor'); + assert.ok(render.width*render.height<=8294400+5000); +} +assert.deepEqual(viewportLayout(1920,1080,false),{width:1920,height:1080,scale:1,kind:'desktop'}); +assert.equal(archiveFraming(1920,1080,7.33,1,false).span,5.9); +assert.equal(archiveFraming(1920,1080,7.33,1,false).detailX,550/1920); +console.log('Viewport, reference framing and render resolution checks passed.'); diff --git a/scripts/check-visual-effects.mjs b/scripts/check-visual-effects.mjs new file mode 100644 index 0000000..56b70a9 --- /dev/null +++ b/scripts/check-visual-effects.mjs @@ -0,0 +1,51 @@ +import assert from 'node:assert/strict'; +import {readFileSync} from 'node:fs'; +import ts from 'typescript'; +const moduleFrom = async source => import('data:text/javascript;base64,'+Buffer.from(ts.transpileModule(source,{compilerOptions:{module:ts.ModuleKind.ESNext,target:ts.ScriptTarget.ES2022}}).outputText).toString('base64')); +const {projectHudPoint,hudQuadMatrix}=await moduleFrom(readFileSync('src/hud-projection.ts','utf8')); +const zero={x:0,y:0};const close=(a,b)=>assert.ok(Math.abs(a-b)<1e-6,`${a} != ${b}`); +for(const [width,height] of [[1920,1080],[3440,1440],[390,844]]) { + for(const point of [{x:0,y:0},{x:width,y:height},{x:width*.12,y:height*.3}]) { + const original=projectHudPoint(point,width,height,0,zero);close(original.x,point.x);close(original.y,point.y); + const a=projectHudPoint(point,width,height,.7,zero),b=projectHudPoint({x:width-point.x,y:point.y},width,height,.7,zero); + close(a.x+b.x,width);close(a.y,b.y); + } +} +const tl=projectHudPoint({x:60,y:114},1920,1080,.7,zero),tr=projectHudPoint({x:330,y:114},1920,1080,.7,zero); +assert.ok(tr.y>tl.y,'Top-left line slopes inward/down'); +const rtl=projectHudPoint({x:1590,y:114},1920,1080,.7,zero),rtr=projectHudPoint({x:1860,y:114},1920,1080,.7,zero); +assert.ok(rtr.yevents[key]=fn};globalThis.document={addEventListener(){}}; +globalThis.effectProbe={}; +const mocks=`const wallpaperHost=()=>undefined;class HudProjection{constructor(stage){this.stage=stage}invalidate(){}update(depth,pointer){globalThis.effectProbe.depth=depth;globalThis.effectProbe.pointer={...pointer};this.stage.dataset.hudDepth=String(depth>.00001)}}class ScreenFinish{update(...args){globalThis.effectProbe.screen=args}}`; +const input=readFileSync('src/wallpaper-effects.ts','utf8').replace(/^import .*;\r?\n/gm,''); +const {WallpaperEffects,effectOptions,wallpaperInsets}=await moduleFrom(mocks+input); +assert.deepEqual(wallpaperInsets({}),{top:0,right:0,bottom:0,left:0}); +assert.deepEqual(wallpaperInsets({uimargintop:{value:-40},uimarginright:{value:25},uimarginbottom:{value:60},uimarginleft:{value:-10}}),{top:-40,right:25,bottom:60,left:-10}); +assert.deepEqual(wallpaperInsets({uimargintop:{value:-999},uimarginright:{value:Infinity},uimarginbottom:{value:999},uimarginleft:{value:'20'}}),{top:-300,right:0,bottom:300,left:0}); +assert.equal(effectOptions({}).parallax,false);assert.equal(effectOptions({}).tracking,true); +assert.equal(effectOptions({huddepth:{value:Infinity}}).depth,.2);assert.equal(effectOptions({huddepth:{value:999}}).depth,1);assert.equal(effectOptions({uifroststrength:{value:-3}}).frostStrength,0); +const pointer={},css={};let modal=0; +const stage={dataset:{mode:'archive'},style:{setProperty:(k,v)=>css[k]=v},addEventListener:(k,fn)=>pointer[k]=fn,getBoundingClientRect:()=>({left:0,top:0,width:1000,height:800}),querySelectorAll:()=>[],querySelector:()=>({childElementCount:modal})}; +const scene={setSelectedIndexAccent(){}};const fx=new WallpaperEffects(stage,()=>scene),probe=globalThis.effectProbe; +const props=p=>events['rhine-wallpaper-properties']({detail:Object.fromEntries(Object.entries(p).map(([k,value])=>[k,{value}]))}); +props({hudparallax:true,uifrost:true,screenfinish:true});pointer.pointermove({pointerType:'mouse',clientX:1000,clientY:0}); +for(let i=1;i<120;i++)fx.update(i/60,false); +assert.equal(scene.uiOnlyParallax,true);assert.equal(stage.dataset.uiFrost,'true');assert.equal(probe.screen[0],true);assert.ok(probe.pointer.x>.99); +props({hudtracking:false});for(let i=120;i<240;i++)fx.update(i/60,false); +assert.ok(probe.depth>.19,'Tracking off retains static curved HUD');assert.ok(Math.abs(probe.pointer.x)<.001,'Tracking off returns to centered lens'); +props({hudtracking:true});modal=1;for(let i=240;i<360;i++)fx.update(i/60,false);assert.ok(Math.abs(probe.pointer.x)<.001); +modal=0;fx.update(6,true);assert.equal(probe.pointer.x,0);assert.ok(probe.depth>.19,'Reduced motion keeps stationary projection'); +stage.dataset.mode='boot';fx.update(6.1,false);assert.ok(probe.depth>.19,'Opening shares the enabled HUD depth');assert.equal(stage.dataset.uiFrost,'false');assert.equal(probe.screen[0],true,'Global finish also covers opening'); +props({hudparallax:false,screenfinish:false});fx.update(6.2,false);assert.equal(scene.uiOnlyParallax,true,'Wallpaper camera never follows passive cursor input');assert.equal(probe.screen[0],false); +props({uimarginbottom:60,uimarginleft:-20});fx.update(6.3,false); +assert.equal(stage.dataset.uiInsets,'true');assert.equal(css['--ui-bottom'],'calc(60px / var(--stage-scale, 1))');assert.equal(css['--ui-left'],'calc(-20px / var(--stage-scale, 1))'); +props({uimargintop:30});fx.update(6.4,false);assert.equal(css['--ui-bottom'],'calc(60px / var(--stage-scale, 1))','Partial side change retains the other sides'); +props({uimargintop:0,uimarginbottom:0,uimarginleft:0});fx.update(6.5,false);assert.equal(stage.dataset.uiInsets,'false','All zero restores baseline layout'); +console.log('HUD radial symmetry, opposite slopes, projective corners, tracking independence, modal/reduced motion and global screen scope passed.'); diff --git a/scripts/check-wallpaper-image-host.mjs b/scripts/check-wallpaper-image-host.mjs new file mode 100644 index 0000000..9696253 --- /dev/null +++ b/scripts/check-wallpaper-image-host.mjs @@ -0,0 +1,26 @@ +import {createServer} from 'node:http'; +import {spawn} from 'node:child_process'; +import {readFile,writeFile,mkdir} from 'node:fs/promises'; +import {resolve} from 'node:path'; +import assert from 'node:assert/strict'; +const dir=resolve('verification/wallpaper-image/host');await mkdir(dir,{recursive:true}); +const source=await readFile('verification/wallpaper-image/test.html','utf8'); +const raw='E:/AIProject/RhineLabUI/reference/extracted-3088099655/wallpaper-2.jpg'; +const exe='D:/Game/Steam/steamapps/common/wallpaper_engine/wallpaper64.exe'; +const location='Rhine Lab image diagnostic'; +const run=args=>new Promise((ok,no)=>{const child=spawn(exe,args,{windowsHide:true,stdio:'ignore'});child.once('error',no);child.once('exit',ok)}); +let finish;const result=new Promise(resolve=>finish=resolve); +const server=createServer((req,res)=>{let body='';req.on('data',b=>body+=b);req.on('end',()=>{res.setHeader('Access-Control-Allow-Origin','*');res.end('ok');try{finish(JSON.parse(body))}catch{}})}); +await new Promise(resolve=>server.listen(5183,'127.0.0.1',resolve)); +const probe=`window.wallpaperPropertyListener={applyUserProperties(props){if(!props.customwallpaperfile)return;const raw=props.customwallpaperfile.value,url=wallpaperImageUrl(raw),image=new Image();const report=ok=>fetch('http://127.0.0.1:5183/',{method:'POST',body:JSON.stringify({ok,raw,url,width:image.naturalWidth,height:image.naturalHeight})});image.onload=()=>report(true);image.onerror=()=>report(false);image.src=url;document.body.append(image)}};`; +await writeFile(dir+'/index.html',source.replace('',probe+'')); +await writeFile(dir+'/project.json',JSON.stringify({title:'Rhine Lab image diagnostic',type:'web',file:'index.html',general:{properties:{customwallpaperfile:{type:'file',value:raw,text:'image'}}}})); +let timeout; +try { + await run(['-control','openWallpaper','-file',dir+'/project.json','-playInWindow',location,'-width','320','-height','200','-x','-30000','-y','-30000']); + const data=await Promise.race([result,new Promise((_,reject)=>{timeout=setTimeout(()=>reject(Error('No host result within 30 seconds')),30000)})]); + await writeFile('verification/wallpaper-image/host-results.json',JSON.stringify(data,null,2));console.log(data); + assert.equal(data.ok,true);assert.equal(data.width,3200);assert.equal(data.height,2000); +}finally{ + clearTimeout(timeout);server.close();await run(['-control','closeWallpaper','-location',location]); +} diff --git a/scripts/check-wallpaper-image.mjs b/scripts/check-wallpaper-image.mjs new file mode 100644 index 0000000..e3139d9 --- /dev/null +++ b/scripts/check-wallpaper-image.mjs @@ -0,0 +1,34 @@ +import assert from 'node:assert/strict'; +import {createRequire} from 'node:module'; +import {readFile,writeFile,mkdir,copyFile,rm} from 'node:fs/promises'; +import {resolve} from 'node:path'; +import {pathToFileURL} from 'node:url'; +import {wallpaperImageUrl} from '../src/wallpaper-image-url.ts'; +const raw='E:/AIProject/RhineLabUI/reference/extracted-3088099655/wallpaper-2.jpg'; +const expected='file:///'+raw; +for(const path of [raw,raw.replaceAll('/','\\'),'E%3A'+raw.slice(2),encodeURIComponent(raw),'file:///E%3A'+raw.slice(2),expected]) + assert.equal(wallpaperImageUrl(path),expected,path); +assert.equal(wallpaperImageUrl('C:/图片/100% #1.jpg'),'file:///C:/%E5%9B%BE%E7%89%87/100%25%20%231.jpg'); +assert.equal(wallpaperImageUrl('file:///C:/folder/a%20b.jpg'),'file:///C:/folder/a%20b.jpg'); +const require=createRequire(import.meta.url),ts=require('typescript'); +const {chromium}=require(process.env.PLAYWRIGHT_MODULE||'playwright'); +const dir=resolve('verification/wallpaper-image');await mkdir(dir,{recursive:true}); +const sources=await Promise.all(['src/wallpaper-image-url.ts','src/wallpaper-background.ts'].map(p=>readFile(p,'utf8'))); +const js=ts.transpileModule(sources.join('\n').replace(/^import .*;\s*$/gm,'').replace(/^export /gm,''),{compilerOptions:{target:ts.ScriptTarget.ES2022}}).outputText; +await writeFile(dir+'/test.html',`
`); +const browser=await chromium.launch({channel:'msedge',headless:true}); +try { + const page=await browser.newPage();await page.goto(pathToFileURL(dir+'/test.html').href); + const apply=(path,retry=false)=>page.evaluate(({path,retry})=>background.update({customwallpaper:{value:true},customwallpaperfile:{value:path}},true,true,retry),{path,retry}); + await apply('E%3A'+raw.slice(2));await page.waitForFunction(()=>document.querySelector('.wallpaper-background').dataset.ready==='true'); + assert.deepEqual(await page.locator('.wallpaper-background img').evaluate(el=>[el.naturalWidth,el.naturalHeight]),[3200,2000]); + // Fail, create the same file, then reselect exactly the same path. + const retryPath=dir+'/retry-'+Date.now()+'.jpg'; + await apply(retryPath);await page.waitForFunction(()=>window.messages?.length===1); + await copyFile(raw,retryPath); + await apply(retryPath,true);await page.waitForFunction(()=>document.querySelector('.wallpaper-background').dataset.ready==='true'); + assert.equal(await page.locator('.wallpaper-background img').last().evaluate(el=>el.naturalWidth),3200); + await rm(retryPath); + await writeFile(dir+'/results.json',JSON.stringify({raw,encoded:'E%3A'+raw.slice(2),url:expected,dimensions:[3200,2000],samePathRetry:true},null,2)); + console.log('Raw/escaped drive, whole escaped path, special characters, real local JPEG and same-path retry passed.'); +}finally{await browser.close()} diff --git a/scripts/check-wallpaper-mask.mjs b/scripts/check-wallpaper-mask.mjs new file mode 100644 index 0000000..ce09f8e --- /dev/null +++ b/scripts/check-wallpaper-mask.mjs @@ -0,0 +1,10 @@ +import {createRequire} from 'node:module'; +import {readFile,mkdir,writeFile} from 'node:fs/promises'; +import {pathToFileURL} from 'node:url'; +import {resolve} from 'node:path'; +import assert from 'node:assert/strict'; +const require=createRequire(import.meta.url),{chromium}=require(process.env.PLAYWRIGHT_MODULE||'playwright'); +const browser=await chromium.launch({channel:'msedge',headless:true}); +try{const page=await browser.newPage({viewport:{width:1280,height:720}});await page.goto(pathToFileURL(resolve('verification/wallpaper-image/test.html')).href);for(const file of ['src/style.css','src/theme.css','src/wallpaper-effects.css'])await page.evaluate(css=>{const style=document.createElement('style');style.textContent=css;document.head.append(style)},await readFile(file,'utf8'));await page.evaluate(()=>{document.querySelector('#stage').style.cssText='position:fixed;inset:0';document.querySelector('#stage').insertAdjacentHTML('beforeend','
')}); +const results=[];for(const dark of [false,true])for(const value of [100,50,0]){await page.evaluate(({dark,value})=>{document.documentElement.dataset.darkSurface=String(dark);document.documentElement.style.setProperty('--theme-paper-rgb',dark?'40,44,48':'234,229,225');background.update({customwallpaper:{value:true},customwallpaperfile:{value:'E:/AIProject/RhineLabUI/reference/extracted-3088099655/wallpaper-2.jpg'},customwallpapermask:{value}},true,true)},{dark,value});await page.waitForFunction(()=>document.querySelector('.wallpaper-background').dataset.ready==='true');await page.waitForTimeout(100);const result=await page.locator('.scene-atmosphere').evaluate(el=>({opacity:getComputedStyle(el).opacity,size:getComputedStyle(el).backgroundSize}));assert.equal(result.opacity,value===0?'0':'1');assert.ok(result.size.includes(`${value}%`));results.push({dark,value,...result});await mkdir('verification/wallpaper-mask',{recursive:true});await page.screenshot({path:`verification/wallpaper-mask/${dark?'dark':'light'}-${value}.png`})}await writeFile('verification/wallpaper-mask/results.json',JSON.stringify(results,null,2));console.log('Light/dark mask ranges 100, 50, 0 passed.')}finally{await browser.close()} + diff --git a/scripts/check-wallpaper.mjs b/scripts/check-wallpaper.mjs new file mode 100644 index 0000000..a313420 --- /dev/null +++ b/scripts/check-wallpaper.mjs @@ -0,0 +1,61 @@ +import assert from "node:assert/strict"; +import { readFileSync } from "node:fs"; +import vm from "node:vm"; +import ts from "typescript"; + +const events = []; +const hostWindow = { dispatchEvent: event => events.push(event) }; +vm.runInNewContext(readFileSync("wallpaper/host.js", "utf8"), { + window: hostWindow, Event, CustomEvent, +}); +const listener = hostWindow.wallpaperPropertyListener; +listener.applyUserProperties({ sound: { value: false } }); +listener.applyUserProperties({ musicvolume: { value: 15 } }); +assert.equal(hostWindow.rhineWallpaperHost.properties.sound.value, false); +assert.equal(hostWindow.rhineWallpaperHost.properties.musicvolume.value, 15); +assert.equal(events.length, 2, "Early and partial callbacks are retained and dispatched"); +listener.applyGeneralProperties({ fps: 30 }); +listener.applyGeneralProperties({ fps: NaN }); +assert.equal(hostWindow.rhineWallpaperHost.fps, 30); +globalThis.window = hostWindow; +const documentListeners = {}; +globalThis.document = { documentElement: { dataset: {} }, addEventListener: (type, handler) => { documentListeners[type] = handler; } }; +const source = readFileSync("src/wallpaper.ts", "utf8").replace('import.meta.env.MODE', '"wallpaper"'); +const { outputText } = ts.transpileModule(source, { compilerOptions: { target: ts.ScriptTarget.ES2022, module: ts.ModuleKind.ESNext } }); +const wallpaperModule = `data:text/javascript;base64,${Buffer.from(outputText).toString("base64")}`; +const { wallpaperFrame } = await import(wallpaperModule); +let rendered = 0; +for (let i = 0; i < 120; i++) if (wallpaperFrame(i * 1000 / 60)) rendered++; +assert.ok(rendered >= 59 && rendered <= 61, `30 FPS host limit at 60 Hz: ${rendered} / 2 s`); +listener.setPaused(true); +assert.equal(wallpaperFrame(10000), false); +listener.setPaused(false); +assert.equal(wallpaperFrame(10001), true, "Resume renders immediately without catch-up frames"); +listener.applyGeneralProperties({ fps: 15 }); +rendered = 0; +for (let i = 1; i <= 120; i++) if (wallpaperFrame(10001 + i * 1000 / 60)) rendered++; +assert.ok(rendered >= 29 && rendered <= 31, `Live 15 FPS limit: ${rendered} / 2 s`); +function moduleUrl(file) { + const { outputText } = ts.transpileModule(readFileSync(file, "utf8"), { compilerOptions: { target: ts.ScriptTarget.ES2022, module: ts.ModuleKind.ESNext } }); + return `data:text/javascript;base64,${Buffer.from(outputText).toString("base64")}`; +} +const qualityModule = moduleUrl("src/render-quality.ts"); +let controls = ts.transpileModule(readFileSync("src/quality-settings.ts", "utf8"), { compilerOptions: { target: ts.ScriptTarget.ES2022, module: ts.ModuleKind.ESNext } }).outputText; +controls = controls.replace('"./wallpaper"', JSON.stringify(wallpaperModule)).replace('"./render-quality"', JSON.stringify(qualityModule)).replace('"./html"', JSON.stringify(moduleUrl("src/html.ts"))); +const { qualityMarkup } = await import(`data:text/javascript;base64,${Buffer.from(controls).toString("base64")}`); +const { qualityPresets } = await import(qualityModule); +const markup = qualityMarkup(qualityPresets.original); +assert.ok(!markup.includes(" selectedLabel, dispatchEvent: event => { assert.equal(event.type, "change"); assert.ok(event.bubbles); } }; +documentListeners.click({ target: { closest: () => button } }); +assert.equal(button.value, "high"); +assert.equal(selectedLabel.textContent, "高"); +documentListeners.click({ target: { closest: () => button } }); +assert.equal(button.value, "original", "All settings wrap to their first option"); +const webModule = `data:text/javascript;base64,${Buffer.from(outputText.replace('"wallpaper" === "wallpaper"', '"production" === "wallpaper"')).toString("base64")}`; +const webControls = controls.replace(JSON.stringify(wallpaperModule), JSON.stringify(webModule)); +const { qualityMarkup: webQualityMarkup } = await import(`data:text/javascript;base64,${Buffer.from(webControls).toString("base64")}`); +assert.equal((webQualityMarkup(qualityPresets.original).match(/ + + +
+
+ +
+ +`; + } + + private bind() { + this.root.addEventListener("click", (event) => { + const target = (event.target as Element).closest("[data-la]"); + if (!target) return; + const action = target.dataset.la!; + if (action === "close") return this.close(); + if (action === "open") return void this.doOpen(target.dataset.path!); + if (action === "reveal") return void this.doReveal(target.dataset.path!); + if (action === "pick") return this.pick(target.dataset.path!); + if (action === "preview") return void this.doPreview(); + if (action === "apply") return void this.doApply(); + if (action === "reset") return this.resetOps(); + if (action === "refresh") return void this.doRefresh(); + if (action === "top") { + this.fillDestination(target.dataset.top!); + return; + } + }); + + this.root.addEventListener("input", (event) => { + const target = event.target as HTMLInputElement; + if (target.id === "la-query") { + this.query = target.value.trim().toLowerCase(); + clearTimeout(this.searchTimer); + this.searchTimer = setTimeout(() => this.render(), 110); + } else if (target.id === "la-dest") { + this.plan = null; + this.renderOps(); + } + }); + + this.root.addEventListener("change", (event) => { + const target = event.target as HTMLSelectElement; + if (target.id === "la-op") { + this.plan = null; + this.renderOps(); + } + }); + + this.root.addEventListener("keydown", (event) => { + if (event.key === "Escape" && this.opened) { + event.stopPropagation(); + this.close(); + } + }); + } + + private renderStatus() { + const node = this.root.querySelector("#la-status"); + if (!node) return; + if (!this.status) { + node.dataset.state = "offline"; + node.innerHTML = + "归档服务未运行 · 只读检索模式" + + 'npm run dev:archive'; + return; + } + node.dataset.state = "online"; + const scan = this.status.lastScan + ? new Date(this.status.lastScan).toLocaleTimeString() + : "—"; + node.innerHTML = + `服务在线 · 127.0.0.1:${this.status.port} · ` + + `${this.status.watching ? "监视中" : "未监视"} · 快照 ${escapeHtml(scan)}` + + ``; + } + + private render() { + const results = this.root.querySelector("#la-results"); + const count = this.root.querySelector("#la-count"); + if (!results || !count) return; + + if (this.loading || !this.loaded) { + count.textContent = this.loading ? "正在载入索引…" : ""; + results.innerHTML = `
${ + this.loading ? "LOADING" : "NO INDEX" + }${this.loading ? "正在读取归档索引" : "索引尚未载入"}
`; + return; + } + + const totals = this.index?.entries.length ?? 0; + if (!this.query) { + count.textContent = `共 ${totals.toLocaleString()} 条 · 输入关键字开始检索`; + results.innerHTML = `
${totals.toLocaleString()} + 个条目已索引${this.viaService ? "(服务端)" : "(静态快照)"} +

按文件名、扩展名或日期片段检索,例如 .7z、2026-02、吉他谱。
+ 选中结果后可在右侧执行移动、复制、解压或新建分类目录。

`; + return; + } + + const total = this.countMatches(this.query); + const hits = this.matches(this.query, MAX_ROWS); + count.textContent = `${total.toLocaleString()} 条命中${ + total > hits.length ? ` · 显示前 ${hits.length} 条` : "" + }`; + + if (!hits.length) { + results.innerHTML = `
NO RESULT没有匹配的条目 +

试试更短的关键字,或按扩展名检索。

`; + return; + } + + results.innerHTML = hits + .map((entry) => { + const cut = entry.path.lastIndexOf("/"); + const dir = cut < 0 ? "" : entry.path.slice(0, cut + 1); + const name = cut < 0 ? entry.path : entry.path.slice(cut + 1); + const selected = this.source?.path === entry.path ? " selected" : ""; + const serviceOnly = this.viaService ? "" : " disabled"; + return `
+
+ ${KIND_LABEL[entry.kind] ?? "?"} + ${this.highlight(name)}${escapeHtml(dir)} + ${entry.kind === "d" ? "—" : formatBytes(entry.size)}${formatTime(entry.mtime)} +
+
+ + + +
+
`; + }) + .join(""); + } + + private highlight(text: string): string { + const lower = text.toLowerCase(); + const index = this.query ? lower.indexOf(this.query) : -1; + if (index < 0) return escapeHtml(text); + return ( + escapeHtml(text.slice(0, index)) + + `${escapeHtml(text.slice(index, index + this.query.length))}` + + escapeHtml(text.slice(index + this.query.length)) + ); + } + + private pick(path: string) { + this.source = + this.index?.entries.find((entry) => entry.path === path) ?? null; + this.plan = null; + this.render(); + this.renderOps(); + } + + private resetOps() { + this.source = null; + this.plan = null; + this.renderOps(); + this.render(); + } + + private currentOp(): ArchiveOp { + const select = this.root.querySelector("#la-op"); + return (select?.value as ArchiveOp) ?? "move"; + } + + private destination(): string { + return ( + this.root.querySelector("#la-dest")?.value.trim() ?? "" + ); + } + + private fillDestination(value: string) { + const input = this.root.querySelector("#la-dest"); + if (!input) return; + const op = this.currentOp(); + const base = this.source ? this.source.path.split("/").pop()! : ""; + if (op === "move" || op === "copy") { + input.value = base ? `${value}/${base}` : value; + } else { + input.value = value; + } + this.plan = null; + this.renderOps(); + } + + private renderOps() { + const ops = this.root.querySelector("#la-ops"); + if (!ops) return; + const op = this.currentOp(); + const chips = this.topLevel + .map( + (name) => + ``, + ) + .join(""); + + const planBlock = this.plan ? this.planMarkup(this.plan) : ""; + const canApply = + this.viaService && this.plan && !this.plan.blocked && !this.busy; + + ops.innerHTML = ` +
归档操作
+${ + this.source + ? `
+ ${KIND_LABEL[this.source.kind] ?? "?"} +
${escapeHtml(this.source.path.split("/").pop()!)} + ${escapeHtml(this.source.path)}
+ ${this.source.kind === "d" ? "目录" : formatBytes(this.source.size)} +
` + : `

在左侧检索并点击「归档…」选中一个条目,然后在这里执行操作。

` +} +
+ + +
+
+ + +
+${ + chips + ? `
顶层分类
${chips}
` + : "" +} +
+ + +
+${ + this.viaService + ? "" + : `

写操作需要本地归档服务:在项目目录执行 npm run dev:archive,或单独执行 npm run archive:serve。

` +} +${planBlock}`; + } + + private planMarkup(plan: ArchivePlan): string { + const rows: string[] = []; + if (plan.from) rows.push(`
源${escapeHtml(plan.from)}
`); + rows.push(`
目标${escapeHtml(plan.to)}
`); + if (plan.bytes) rows.push(`
体积${formatBytes(plan.bytes)}
`); + if (plan.files) rows.push(`
文件数${plan.files}
`); + if (plan.crossVolume) rows.push(`
跨盘是
`); + const state = plan.blocked + ? `

不可执行:${escapeHtml(plan.blocked)}

` + : `

可以执行${plan.note ? ` · ${escapeHtml(plan.note)}` : ""}

`; + return `
影响预览
${rows.join("")}${state}
`; + } + + // ---------- 操作 ---------- + private request(): ArchiveRequest { + const op = this.currentOp(); + const dest = this.destination(); + if (op === "mkdir") return { op, folder: dest }; + return { op, from: this.source?.path, to: dest }; + } + + private validate(): string | null { + const op = this.currentOp(); + const dest = this.destination(); + if (!this.viaService) return "本地归档服务未运行"; + if (!dest) return op === "mkdir" ? "请填写新目录路径" : "请填写目标路径"; + if (dest.startsWith("/") || /^[a-zA-Z]:/.test(dest)) + return "目标路径要写成相对归档根目录的形式,例如 01_人工智能/runs.7z"; + if (dest.includes("..")) return "目标路径不能包含 .."; + if (op !== "mkdir" && !this.source) return "请先在左侧选中一个条目"; + if (op !== "mkdir" && this.source?.path === dest) return "目标与源相同"; + return null; + } + + private async doPreview() { + const invalid = this.validate(); + if (invalid) return this.notify(invalid); + this.busy = true; + this.renderOps(); + try { + this.plan = await planOperation(this.request()); + } catch (error) { + this.plan = null; + this.notify(`预览失败:${(error as Error).message}`); + } finally { + this.busy = false; + this.renderOps(); + } + } + + private async doApply() { + const invalid = this.validate(); + if (invalid) return this.notify(invalid); + if (!this.plan) return this.notify("请先预览影响"); + if (this.plan.blocked) return this.notify(this.plan.blocked); + this.busy = true; + this.renderOps(); + try { + await applyOperation(this.request()); + this.notify(`${OP_LABELS[this.plan.op]}成功:${this.plan.to}`); + this.plan = null; + this.source = null; + invalidateArchiveIndex(); // 归档已变,丢弃共享缓存,避免主界面检索看到旧数据 + await this.loadIndex(0); + this.renderOps(); + } catch (error) { + this.notify(`归档失败:${(error as Error).message}`); + } finally { + this.busy = false; + this.renderOps(); + } + } + + private async doOpen(path: string) { + if (!this.viaService) return this.notify("打开文件需要本地归档服务"); + try { + await openEntry(path); + this.notify(`已用系统默认程序打开:${path}`); + } catch (error) { + this.notify(`打开失败:${(error as Error).message}`); + } + } + + private async doReveal(path: string) { + if (!this.viaService) return this.notify("定位需要本地归档服务"); + try { + await revealEntry(path); + this.notify(`已在资源管理器中定位:${path}`); + } catch (error) { + this.notify(`定位失败:${(error as Error).message}`); + } + } + + private async doRefresh() { + if (!this.viaService) return this.notify("重新扫描需要本地归档服务"); + try { + const result = await refreshSnapshot(); + invalidateArchiveIndex(); + await this.loadIndex(0); + this.notify(`已重新扫描:${new Date(result.generated).toLocaleTimeString()}`); + } catch (error) { + this.notify(`重新扫描失败:${(error as Error).message}`); + } + } +} diff --git a/src/archive-play-motion.ts b/src/archive-play-motion.ts new file mode 100644 index 0000000..4d494d3 --- /dev/null +++ b/src/archive-play-motion.ts @@ -0,0 +1,103 @@ +const clamp = (value: number, low = 0, high = 1) => Math.min(high, Math.max(low, value)); +export type MusicBands = { low: number; mid: number; high: number; activity: number }; +export const quietBands = (): MusicBands => ({ low: 0, mid: 0, high: 0, activity: 0 }); +export class SpectrumEnvelope { + bands = quietBands(); + private target = quietBands(); + private received = -Infinity; + private audible = -Infinity; + private localSoundUntil = -Infinity; + ignoreLocalSound(until: number) { + if (Number.isFinite(until)) this.localSoundUntil = Math.max(this.localSoundUntil, until); + } + ingest(samples: ArrayLike, time: number) { + if (samples.length !== 128) return; + // WE captures the wallpaper's own interaction sounds too. They cannot + // start music mode from silence; already-established music keeps flowing. + if (time <= this.localSoundUntil && this.bands.activity < .1) return; + const band = (start: number, end: number) => { + let energy = 0; + for (let i = start; i < end; i++) { + const l = Number.isFinite(samples[i]) ? clamp(samples[i]) : 0; + const r = Number.isFinite(samples[i + 64]) ? clamp(samples[i + 64]) : 0; + energy += (l * l + r * r) / 2; + } + return Math.sqrt(energy / (end - start)); + }; + this.target = { low: band(0, 8), mid: band(8, 32), high: band(32, 64), activity: 0 }; + this.received = time; + if (Math.max(this.target.low, this.target.mid, this.target.high) > .012) this.audible = time; + } + update(dt: number, time: number, enabled: boolean): MusicBands { + const fresh = enabled && time - this.received < .5; + for (const key of ["low", "mid", "high"] as const) { + const target = fresh ? this.target[key] : 0; + const rate = target > this.bands[key] ? 16 : 3.2; + this.bands[key] += (target - this.bands[key]) * (1 - Math.exp(-Math.min(dt, .1) * rate)); + } + const active = fresh && time - this.audible < 1.5 ? 1 : 0; + this.bands.activity += (active - this.bands.activity) * (1 - Math.exp(-Math.min(dt, .1) * (active ? 3 : 1.5))); + return { ...this.bands }; + } +} +export function musicDisplacement(row: number, lane: number, time: number, bands: MusicBands, strength: number) { + const bass = bands.low * .8 * (.5 + .5 * Math.sin(row * .29 - lane * .5 - time * 2.7)); + const middle = bands.mid * .48 * (.5 + .5 * Math.sin(row * .72 + lane * .9 - time * 4.3)); + const treble = bands.high * .18 * Math.pow(Math.max(0, Math.sin(row * 1.7 - lane * 2.2 - time * 6.4)), 4); + return clamp((bass + middle + treble) * clamp(strength, 0, 2), 0, 1.8); +} +export type RelayStatus = "idle" | "preparing" | "playing" | "over"; +export class RelayRound { + status: RelayStatus = "idle"; + score = 0; + target: string | null = null; + remaining = 0; + total = 0; + reason = ""; + start() { this.status = "preparing"; this.score = 0; this.target = null; this.remaining = .8; this.reason = ""; } + stop() { this.status = "idle"; this.target = null; this.remaining = 0; } + aim(target: string, pace: "gentle" | "normal" | "quick") { + this.target = target; this.status = "playing"; + const base = pace === "gentle" ? 8 : pace === "quick" ? 4 : 6; + this.total = this.remaining = Math.max(base * .55, base - this.score * .12); + } + tick(dt: number, paused: boolean) { + if (paused || (this.status !== "playing" && this.status !== "preparing")) return; + this.remaining = Math.max(0, this.remaining - Math.max(0, dt)); + if (this.status === "playing" && this.remaining === 0) this.finish("这道波纹停下了"); + } + hit(target: string | null) { + if (this.status !== "playing") return false; + if (target !== this.target) { this.finish("接力结束"); return false; } + this.score++; this.status = "preparing"; this.target = null; this.remaining = .28; return true; + } + finish(reason: string) { this.status = "over"; this.target = null; this.reason = reason; } +} + +export type RhythmStyle = 'legacy' | 'wave' | 'lift'; +export type RhythmFrame = { style: Record }; +/** Crossfade styles without a beat gate: sustained notes remain visible. */ +export class RhythmMotion { + private weights = { legacy: 1, wave: 0, lift: 0 }; + update(_bands: MusicBands, _time: number, dt: number, style: RhythmStyle): RhythmFrame { + for (const key of ['legacy', 'wave', 'lift'] as const) this.weights[key] += ((style === key ? 1 : 0) - this.weights[key]) * (1 - Math.exp(-clamp(dt, 0, .1) * 5)); + return { style: { ...this.weights } }; + } +} +/** x is the card's projected horizontal position, left=0, right=1. */ +export function rhythmDisplacement(row: number, lane: number, time: number, bands: MusicBands, strength: number, frame: RhythmFrame, x = .5) { + x = clamp(x); + const smooth = (v: number) => v * v * (3 - 2 * v); + const right = smooth(clamp((x - .5) * 2)); + const left = 1 - smooth(clamp(x * 2)); + const middle = 1 - left - right; + const spectrum = bands.low * left + bands.mid * middle + bands.high * right; + // A: broad, screen-aligned spectrum ridges, continuous across band boundaries. + const wave = spectrum * (.72 + .28 * Math.sin(row * .32 - time * 2.2)) * .95; + // B: layered travelling currents; vocals and sustained treble also carry motion. + const flow = bands.low * .62 * (.55 + .45 * Math.sin(row * .22 + lane * .18 - time * 1.8)) + + bands.mid * .42 * (.55 + .45 * Math.sin(row * .38 - lane * .27 - time * 2.8)) + + bands.high * .28 * (.55 + .45 * Math.sin(row * .62 + lane * .4 - time * 4.1)); + return musicDisplacement(row, lane, time, bands, strength) * frame.style.legacy + + (frame.style.wave * wave + frame.style.lift * flow) * clamp(strength, 0, 2); +} diff --git a/src/archive-playground.css b/src/archive-playground.css new file mode 100644 index 0000000..1762d49 --- /dev/null +++ b/src/archive-playground.css @@ -0,0 +1,20 @@ +.relay-entry, .relay-hud, .relay-target { position: absolute; z-index: 7; color: #171713; } +.relay-entry { left: var(--edge); bottom: 9%; padding: 12px 0; border-top: 1px solid #aaa59a; background: transparent; font-size: 13px; letter-spacing: 1px; } +.relay-entry span { margin-right: 22px; } +.relay-hud { top: 15%; left: var(--edge); right: var(--edge); pointer-events: none; } +.relay-heading { display: flex; align-items: baseline; gap: 24px; } +.relay-heading small { font-size: 11px; letter-spacing: 2px; } +.relay-score { font-size: 56px; font-weight: 300; font-variant-numeric: tabular-nums; } +.relay-message { font-size: 13px; } +.relay-time { height: 2px; background: #b7b3a944; margin-top: 12px; } +.relay-time i { display: block; height: 100%; background: #8c744b; transform-origin: left; } +.relay-actions { position: relative; display: flex; justify-content: flex-end; gap: 16px; margin-top: 16px; } +.relay-actions button { pointer-events: auto; padding: 12px 18px; border: 1px solid #928c7b; background: #e8e5deee; font-size: 13px; } +.relay-target { width: 46px; height: 46px; transform: translate(-50%, -50%); border: 1px solid #7b6035; background: #eee9dfe8; box-shadow: 0 0 0 5px #eee9df44; font-size: 15px; font-variant-numeric: tabular-nums; } +.relay-target:hover { background: #1d1e19; color: #eee9df; } +.relay-entry[hidden], .relay-hud[hidden], .relay-target[hidden], .relay-retry[hidden] { display: none; } +[data-relay]:not([data-mode="boot"]) > :is(.workbench, .brand, .system-footer) { transition: opacity 300ms ease, visibility 0s; } +[data-relay="true"] > :is(.workbench, .brand, .system-footer, #archive-ui, .powered, .inspection-overlay) { opacity: 0; visibility: hidden; pointer-events: none; transition: opacity 220ms ease-out, visibility 0s 220ms; } +.reduce-motion[data-relay] > :is(.workbench, .brand, .system-footer, #archive-ui, .powered, .inspection-overlay) { transition: none; } +.relay-entry:focus-visible, .relay-actions button:focus-visible, .relay-target:focus-visible { outline: 2px solid #9c7642; outline-offset: 5px; } +@media(max-width: 700px) { .relay-heading { gap: 12px; flex-wrap: wrap; }.relay-message { flex-basis: 100%; }.relay-hud { top: max(120px, 18%); } } diff --git a/src/archive-playground.ts b/src/archive-playground.ts new file mode 100644 index 0000000..8aeff9f --- /dev/null +++ b/src/archive-playground.ts @@ -0,0 +1,190 @@ +import { RelayRound, SpectrumEnvelope } from "./archive-play-motion"; +import type { ArchiveScene } from "./scene"; +import { wallpaperHost, type WallpaperProperties } from "./wallpaper"; +import { SurfaceTransition } from "./ui-transitions"; +import "./archive-playground.css"; + +declare global { interface Window { rhineWallpaperSpectrum?: { samples: number[]; time: number }; } } +type Context = { enabled: boolean; paused: boolean; reduced: boolean }; +export class ArchivePlayground { + private props: WallpaperProperties = {}; + private envelope = new SpectrumEnvelope(); + private round = new RelayRound(); + private last = 0; + private sampleTime = -1; + private previousTarget = ""; + private musicGain = 1; + private muted = false; + private entry: HTMLButtonElement; + private hud: HTMLElement; + private marker: HTMLButtonElement; + private score: HTMLElement; + private message: HTMLElement; + private retry: HTMLButtonElement; + private bar: HTMLElement; + private hudTransition: SurfaceTransition; + private entryTransition: SurfaceTransition; + private markerTransition: SurfaceTransition; + private closing = false; + private entryVisible = false; + private markerVisible = false; + get active() { return this.round.status !== "idle"; } + constructor(private stage: HTMLElement, private scene: () => ArchiveScene | undefined, private context: () => Context, private mute: (value: boolean) => void, private hitSound: () => void) { + stage.insertAdjacentHTML("beforeend", ``); + this.entry = stage.querySelector(".relay-entry")!; + this.hud = stage.querySelector(".relay-hud")!; + this.marker = stage.querySelector(".relay-target")!; + this.score = this.hud.querySelector(".relay-score")!; + this.message = this.hud.querySelector(".relay-message")!; + this.retry = this.hud.querySelector(".relay-retry")!; + this.bar = this.hud.querySelector(".relay-time i")!; + this.hudTransition = new SurfaceTransition(this.hud, this.hud, 300, 220); + this.entryTransition = new SurfaceTransition(this.entry, undefined, 260, 180); + this.markerTransition = new SurfaceTransition(this.marker, undefined, 160, 140); + this.entry.onclick = this.retry.onclick = () => this.start(); + this.hud.querySelector(".relay-exit")!.onclick = () => this.stop(); + this.marker.onclick = () => this.hit(this.round.target); + window.addEventListener("rhine-wallpaper-properties", e => Object.assign(this.props, (e as CustomEvent).detail)); + Object.assign(this.props, wallpaperHost()?.properties ?? {}); + window.addEventListener("rhine-local-sound", event => { + this.envelope.ignoreLocalSound((event as CustomEvent<{ until: number }>).detail.until); + }); + window.addEventListener("rhine-wallpaper-pause", () => { this.last = 0; }); + document.addEventListener("visibilitychange", () => { this.last = 0; }); + } + private bool(key: string, fallback: boolean) { const v = this.props[key]?.value; return typeof v === "boolean" ? v : fallback; } + private start() { + if (!this.context().enabled || !this.scene() || (this.stage.dataset.threeState && this.stage.dataset.threeState !== "on")) return; + this.closing = false; + this.round.start(); this.previousTarget = ""; + this.score.textContent = "00"; + this.message.textContent = "点击抬起的档案,接住下一道波纹"; + this.retry.hidden = true; + this.bar.style.transform = "scaleX(0)"; + this.showMarker(false); + this.scene()!.setRelayActive(true); + this.stage.dataset.relay = "true"; + this.hud.inert = false; + this.hud.setAttribute("aria-hidden", "false"); + this.hudTransition.show(this.context().reduced); + this.updateEntry(); + this.syncInputIsolation(); + this.hud.querySelector(".relay-exit")!.focus({ preventScroll: true }); + } + stop() { + if (!this.active && !this.closing) return; + if (this.closing) return; + this.round.stop(); this.closing = true; + this.stage.dataset.relay = "false"; + this.hud.inert = true; + this.hud.setAttribute("aria-hidden", "true"); + this.showMarker(false); + this.hudTransition.hide(this.context().reduced, () => { + this.closing = false; + this.scene()?.setRelayActive(false); + this.updateEntry(); + this.syncInputIsolation(); + if (this.context().enabled && !this.context().paused) { + const target = this.entryVisible ? this.entry : this.stage.querySelector(".settings-button"); + target?.focus({ preventScroll: true }); + } + }); + } + private updateEntry() { + const visible = this.context().enabled && (!this.stage.dataset.threeState || this.stage.dataset.threeState === "on") && !this.active && !this.closing && this.bool("showgame", true); + if (visible === this.entryVisible) return; + this.entryVisible = visible; + this.entry.inert = !visible; + this.entry.setAttribute("aria-hidden", String(!visible)); + if (visible) this.entryTransition.show(this.context().reduced); + else this.entryTransition.hide(this.context().reduced); + } + private showMarker(visible: boolean) { + if (visible === this.markerVisible) return; + this.markerVisible = visible; + this.marker.inert = !visible; + this.marker.setAttribute("aria-hidden", String(!visible)); + if (visible) this.markerTransition.show(this.context().reduced); + else this.markerTransition.hide(this.context().reduced); + } + private syncInputIsolation() { + const mode = this.stage.dataset.mode; + const blocked = this.active || this.closing || Boolean(this.stage.querySelector("#modal-root")?.childElementCount); + const workbench = this.stage.querySelector(".workbench"); + if (workbench) workbench.inert = blocked || !this.context().enabled; + const archive = this.stage.querySelector("#archive-ui"); + if (archive) archive.inert = blocked || mode !== "archive" || this.stage.dataset.workbench === "true"; + const footer = this.stage.querySelector(".system-footer"); + if (footer) footer.inert = blocked || mode === "boot"; + } + private hit(key: string | null) { + if (this.context().paused) return; + if (this.round.hit(key)) { this.scene()?.relayPulse(key!); this.hitSound(); } + } + tick(time: number) { + const gap = time - this.last; + const elapsed = this.last ? Math.max(0, gap) : 0; + const dt = Math.min(.1, elapsed); + this.last = time; + const context = this.context(), scene = this.scene(); + if (!context.enabled && this.active) this.stop(); + const reactive = context.enabled && this.bool("audioreactive", true) && !context.reduced; + const mute = reactive && this.bool("reactivemute", true); + if (mute !== this.muted) { this.muted = mute; this.mute(mute); } + const sample = window.rhineWallpaperSpectrum; + if (sample && sample.time !== this.sampleTime) { this.sampleTime = sample.time; this.envelope.ingest(sample.samples, sample.time); } + const bands = this.envelope.update(dt, time, reactive); + const playing = this.round.status === "playing" || this.round.status === "preparing"; + this.musicGain += ((playing ? 0 : 1) - this.musicGain) * (1 - Math.exp(-dt * 6)); + const intensity = this.props.reactiveintensity?.value; + const strength = typeof intensity === "number" && Number.isFinite(intensity) ? Math.max(0, Math.min(2, intensity / 100)) : 1; + const style = this.props.selectionstyle?.value ?? "music-flat"; + const rhythm = this.props.rhythmstyle?.value; + scene?.setRhythmStyle(rhythm === "wave" || rhythm === "lift" ? rhythm : "legacy"); + const flatten = playing || style === "flat" ? 1 : style === "music-flat" && reactive && strength > 0 ? bands.activity : 0; + scene?.setPlayfield(context.enabled, { ...bands, low: bands.low * this.musicGain, mid: bands.mid * this.musicGain, high: bands.high * this.musicGain }, strength, flatten, this.round.target, this.bool("idlebreathing", true)); + if (scene) scene.onRelayPick = key => this.hit(key); + this.round.tick(elapsed, context.paused); + if (context.paused) this.last = 0; + if (this.round.status === "preparing" && this.round.remaining === 0 && !context.paused && scene) { + const candidates = scene.relayCandidates().filter(key => key !== this.previousTarget); + if (candidates.length) { + const key = candidates[Math.floor(Math.random() * candidates.length)]; + this.previousTarget = key; + const pace = this.props.gamepace?.value; + this.round.aim(key, pace === "gentle" || pace === "quick" ? pace : "normal"); + } else this.round.finish("这一侧没有可接力的档案,请退出后移动阵列再试"); + } + const over = this.round.status === "over"; + // Keep the game isolated until the user exits, including its result screen. + scene?.setRelayActive(this.active || this.closing); + this.stage.dataset.relay = String(this.active); + this.stage.dataset.relayStatus = this.round.status; + this.stage.dataset.relayScore = String(this.round.score); + this.stage.dataset.spectrumLevel = bands.activity.toFixed(3); + this.updateEntry(); + this.syncInputIsolation(); + if (context.reduced) { + this.hudTransition.finish(); this.entryTransition.finish(); this.markerTransition.finish(); + } + if (this.closing) return; + this.retry.hidden = !over; + const score = String(this.round.score).padStart(2, "0"); + if (this.score.textContent !== score) this.score.textContent = score; + const message = over ? this.round.reason : context.paused ? "已暂停" : "点击抬起的档案,接住下一道波纹"; + if (this.message.textContent !== message) this.message.textContent = message; + this.bar.style.transform = `scaleX(${this.round.status === "playing" ? this.round.remaining / this.round.total : 0})`; + this.position(); + } + position() { + const point = this.round.target ? this.scene()?.projectRelay(this.round.target) : null; + this.showMarker(Boolean(point) && !this.context().paused && this.active && !this.closing); + if (point) { + const rect = this.stage.getBoundingClientRect(); + this.marker.style.left = `${(point.x - rect.left) / rect.width * 100}%`; + this.marker.style.top = `${(point.y - rect.top) / rect.height * 100}%`; + const label = String(this.round.score + 1).padStart(2, "0"); + if (this.marker.textContent !== label) this.marker.textContent = label; + } + } +} diff --git a/src/archive-service.ts b/src/archive-service.ts new file mode 100644 index 0000000..8d1bd36 --- /dev/null +++ b/src/archive-service.ts @@ -0,0 +1,285 @@ +// 本地归档服务的浏览器端客户端。 +// +// 开发与预览下 Vite 把 /archive-api 代理到 127.0.0.1 的归档服务,并在代理层注入 +// 每次启动生成的访问令牌,所以浏览器里不需要也不应该持有令牌。 +// 服务不可用时退回静态快照:检索仍可用,写操作与打开文件会明确报错。 + +export type EntryKind = "f" | "d" | "l"; + +export interface ArchiveEntry { + path: string; + kind: EntryKind; + size: number; + mtime: number; +} + +export interface ArchiveTotals { + files: number; + dirs: number; + bytes: number; +} + +export interface ArchiveIndex { + generated: string; + root: string; + totals: ArchiveTotals; + entries: ArchiveEntry[]; +} + +export interface ArchiveStatus { + root: string; + watching: boolean; + lastScan: string | null; + lastError: string | null; + totals: ArchiveTotals | null; + port: number; + sevenZip: boolean; +} + +export type ArchiveOp = "move" | "copy" | "mkdir" | "extract"; + +export interface ArchivePlan { + op: ArchiveOp; + from: string | null; + to: string; + bytes: number; + files: number; + isDir?: boolean; + targetExists?: boolean; + crossVolume?: boolean; + blocked?: string | null; + note?: string | null; +} + +export interface ArchiveRequest { + op: ArchiveOp; + from?: string; + to?: string; + folder?: string; +} + +const API = "archive-api"; + +async function request( + path: string, + init: RequestInit = {}, + timeout = 8000, +): Promise { + const controller = new AbortController(); + const timer = setTimeout(() => controller.abort(), timeout); + try { + const headers = new Headers(init.headers); + if (!headers.has("content-type")) headers.set("content-type", "application/json"); + const response = await fetch(`${API}${path}`, { + ...init, + signal: controller.signal, + headers, + }); + const text = await response.text(); + let payload: unknown = null; + try { + payload = text ? JSON.parse(text) : null; + } catch { + payload = null; + } + if (!response.ok) { + const message = + (payload as { error?: string } | null)?.error ?? + `服务返回 ${response.status}`; + throw new Error(message); + } + return payload as T; + } finally { + clearTimeout(timer); + } +} + +/** 服务不可用时返回 null,而不是抛错——面板据此降级到静态快照。 */ +export async function fetchStatus(): Promise { + try { + return await request("/status", { method: "GET" }, 2500); + } catch { + return null; + } +} + +/** + * 优先取服务端 gzip 的索引;服务不在时退回 public/archive-index.json。 + * 两种情况返回的条目结构一致。 + */ +export async function fetchIndex(): Promise<{ + index: ArchiveIndex; + viaService: boolean; +}> { + try { + const raw = await request<{ + generated: string; + root: string; + totals: ArchiveTotals; + entries: [string, string, number, number][]; + }>("/index", { method: "GET" }, 20000); + return { + index: { + generated: raw.generated, + root: raw.root, + totals: raw.totals, + entries: raw.entries.map(([path, kind, size, mtime]) => ({ + path, + kind: kind as EntryKind, + size, + mtime, + })), + }, + viaService: true, + }; + } catch { + const response = await fetch("archive-index.json", { cache: "no-cache" }); + if (!response.ok) throw new Error("无法读取归档索引快照"); + const raw = (await response.json()) as { + generated: string; + root: string; + totals: ArchiveTotals; + entries: [string, string, number, number][]; + }; + return { + index: { + generated: raw.generated, + root: raw.root, + totals: raw.totals, + entries: raw.entries.map(([path, kind, size, mtime]) => ({ + path, + kind: kind as EntryKind, + size, + mtime, + })), + }, + viaService: false, + }; + } +} + +export function planOperation(body: ArchiveRequest): Promise { + return request("/plan", { + method: "POST", + body: JSON.stringify(body), + }); +} + +export function applyOperation(body: ArchiveRequest): Promise { + return request("/apply", { + method: "POST", + body: JSON.stringify({ ...body, confirm: true }), + }); +} + +export function openEntry(path: string): Promise { + return request("/open", { method: "POST", body: JSON.stringify({ path }) }); +} + +export function revealEntry(path: string): Promise { + return request("/reveal", { method: "POST", body: JSON.stringify({ path }) }); +} + +export function refreshSnapshot(): Promise<{ generated: string }> { + return request<{ generated: string }>("/refresh", { method: "POST" }); +} + +export function subscribeRescan(onRescan: (generated: string) => void): () => void { + if (typeof EventSource === "undefined") return () => {}; + let source: EventSource; + try { + source = new EventSource(`${API}/events`); + } catch { + return () => {}; + } + source.onmessage = (event) => { + try { + const data = JSON.parse(event.data) as { type?: string; generated?: string }; + if (data.type === "rescan" && data.generated) onRescan(data.generated); + } catch { + /* 忽略无法解析的事件 */ + } + }; + return () => source.close(); +} + +export function formatBytes(bytes: number): string { + if (bytes >= 1024 ** 3) return `${(bytes / 1024 ** 3).toFixed(2)} GB`; + if (bytes >= 1024 ** 2) return `${(bytes / 1024 ** 2).toFixed(1)} MB`; + if (bytes >= 1024) return `${(bytes / 1024).toFixed(0)} KB`; + return `${bytes} B`; +} + +export function formatTime(ms: number): string { + if (!ms) return "—"; + const d = new Date(ms); + const p = (n: number) => String(n).padStart(2, "0"); + return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}`; +} + +// ---------- 共享索引缓存 ---------- +// 面板与主界面检索弹窗共用同一份索引,避免各拉一次 7.4 MB。 +// maxAgeMs 为 0 表示"总是重新取",用于面板每次打开时确保看到最新归档。 + +export interface LoadedIndex { + index: ArchiveIndex; + viaService: boolean; + loadedAt: number; +} + +let cachedIndex: LoadedIndex | null = null; +const loweredPaths = new WeakMap(); + +export function invalidateArchiveIndex() { + cachedIndex = null; +} + +export async function loadArchiveIndex( + options: { maxAgeMs?: number } = {}, +): Promise { + const maxAge = options.maxAgeMs ?? 0; + if (cachedIndex && Date.now() - cachedIndex.loadedAt <= maxAge) { + return cachedIndex; + } + const result = await fetchIndex(); + cachedIndex = { ...result, loadedAt: Date.now() }; + return cachedIndex; +} + +function pathsOf(index: ArchiveIndex): string[] { + let list = loweredPaths.get(index); + if (!list) { + list = index.entries.map((entry) => entry.path.toLowerCase()); + loweredPaths.set(index, list); + } + return list; +} + +/** 在索引中按相对路径的子串匹配,返回前 limit 条与命中总数。 */ +export function searchArchive( + index: ArchiveIndex, + query: string, + limit = 200, +): { hits: ArchiveEntry[]; total: number } { + const needle = query.trim().toLowerCase(); + if (!needle) return { hits: [], total: 0 }; + const lower = pathsOf(index); + const hits: ArchiveEntry[] = []; + let total = 0; + for (let i = 0; i < lower.length; i += 1) { + if (lower[i].includes(needle)) { + total += 1; + if (hits.length < limit) hits.push(index.entries[i]); + } + } + return { hits, total }; +} + +/** 索引中的顶层目录名,用于归档操作的目标路径快捷选择。 */ +export function topLevelNames(index: ArchiveIndex): string[] { + return index.entries + .filter((entry) => entry.kind === "d" && !entry.path.includes("/")) + .map((entry) => entry.path) + .sort(); +} + diff --git a/src/archive-visibility.ts b/src/archive-visibility.ts new file mode 100644 index 0000000..d261b57 --- /dev/null +++ b/src/archive-visibility.ts @@ -0,0 +1,57 @@ +import * as THREE from 'three'; +import { COLUMN_SPACING, ROW_SPACING, type ArchiveCell } from './archive-loop.ts'; + +const edges = [[0,1],[0,2],[0,4],[1,3],[1,5],[2,3],[2,6],[3,7],[4,5],[4,6],[5,7],[6,7]]; +/** A view-aligned pool, clipped to the height slab the array can occupy. */ +export class ArchiveVisibility { + private camera = new THREE.PerspectiveCamera(); + private frustum = new THREE.Frustum(); + private matrix = new THREE.Matrix4(); + private box = new THREE.Box3(); + candidates = 0; + update(source: THREE.PerspectiveCamera, far: number, trackX: number, trackZ: number, extra: boolean): ArchiveCell[] { + this.camera.copy(source, false); + this.camera.far = Math.min(source.far, Math.max(source.near + 1, far + 8)); + this.camera.updateProjectionMatrix(); + // Screen-space overscan plus the complete card bounds keep silhouettes and + // off-screen shadow casters alive before they reach an edge. + const margin = extra ? 1.5 : 1.18; + this.camera.projectionMatrix.elements[0] /= margin; + this.camera.projectionMatrix.elements[5] /= margin; + this.camera.projectionMatrixInverse.copy(this.camera.projectionMatrix).invert(); + this.matrix.multiplyMatrices(this.camera.projectionMatrix, source.matrixWorldInverse); + this.frustum.setFromProjectionMatrix(this.matrix); + const inverse = this.matrix.clone().invert(); + const vertices = Array.from({length:8},(_,i)=>new THREE.Vector3(i&1?1:-1,i&2?1:-1,i&4?1:-1).applyMatrix4(inverse)); + const slab = new THREE.Box3(); + const bottom = -6.5, top = 6.5; + for (const point of vertices) if(point.y >= bottom && point.y <= top) slab.expandByPoint(point); + for (const [a,b] of edges) for (const y of [bottom,top]) { + const start=vertices[a],end=vertices[b],dy=end.y-start.y; + if(Math.abs(dy)<1e-9)continue; + const t=(y-start.y)/dy; + if(t>=0&&t<=1)slab.expandByPoint(start.clone().lerp(end,t)); + } + if(slab.isEmpty()) { this.candidates=0; return []; } + const minLane=Math.floor((slab.min.x-2.8+trackX)/COLUMN_SPACING+2)-1; + const maxLane=Math.ceil((slab.max.x+2.8+trackX)/COLUMN_SPACING+2)+1; + const minRow=Math.floor((slab.min.z-.6-trackZ)/ROW_SPACING+15.5)-2; + const maxRow=Math.ceil((slab.max.z+.6-trackZ)/ROW_SPACING+15.5)+2; + const cells: ArchiveCell[]=[]; + for(let lane=minLane;lane<=maxLane;lane++)for(let row=minRow;row<=maxRow;row++) { + const x=(lane-2)*COLUMN_SPACING-trackX, z=(row-15.5)*ROW_SPACING+trackZ; + this.box.min.set(x-2.8,bottom,z-1.2); + this.box.max.set(x+2.8,top,z+1.2); + if(this.frustum.intersectsBox(this.box))cells.push({lane,row}); + } + this.candidates=cells.length; + return cells; + } + intersects(x: number, y: number, z: number) { + // Conservative over the subtle x-axis lean and normal wave amplitude. + // Additional depth keeps adjacent offscreen shadow casters in the set. + this.box.min.set(x-2.8,y-.3,z-1.2); + this.box.max.set(x+2.8,y+4.1,z+1.2); + return this.frustum.intersectsBox(this.box); + } +} diff --git a/src/asset-url.ts b/src/asset-url.ts new file mode 100644 index 0000000..cbd046d --- /dev/null +++ b/src/asset-url.ts @@ -0,0 +1,7 @@ +/** Public resources work at the origin root and at a static-hosting subpath. */ +declare const __RHINE_MODELS__: Record; +export const assetUrl = (path: string) => { + const key = path.replace(/^\//, ""); + const versioned = import.meta.env.PROD ? __RHINE_MODELS__[key] ?? key : key; + return `${import.meta.env.BASE_URL}${versioned}`; +}; diff --git a/src/audio-settings.ts b/src/audio-settings.ts new file mode 100644 index 0000000..ac496c1 --- /dev/null +++ b/src/audio-settings.ts @@ -0,0 +1,17 @@ +import type { AudioPreferences } from "./audio"; + +export function audioSettingsMarkup(prefs: AudioPreferences) { + return `
${( + [ + ["sound", "soundVolume", "INTERFACE SOUND", "操作与启动音效"], + ["music", "musicVolume", "BACKGROUND MUSIC", "观测室 · 背景音乐"], + ] as const + ) + .map( + ([toggle, volume, title, description]) => `
+ + +
`, + ) + .join("")}
`; +} diff --git a/src/audio.ts b/src/audio.ts new file mode 100644 index 0000000..0451307 --- /dev/null +++ b/src/audio.ts @@ -0,0 +1,664 @@ +import { hasTypingBetween } from "./typing-rhythm"; +import { TYPING_PCM, TYPING_SAMPLE_RATE } from "./typing-samples"; +import { assetUrl } from "./asset-url"; +export type Sound = + | "page-open" + | "page-close" + | "ui-tick" + | "brand" + | "text-reveal" + | "key" + | "tick" + | "column" + | "open" + | "confirm" + | "back" + | "scan" + | "welcome" + | "array" + | "inspect" + | "explode" + | "assemble"; +export type SoundScene = "boot" | "archive" | "detail" | "viewer"; +export type AudioPreferences = { + sound: boolean; + music: boolean; + soundVolume: number; + musicVolume: number; +}; +const STEMS = ["atmosphere", "motif", "pulse"] as const; +const LOOP_SECONDS = 160 / 3; +const noiseBuffers = new WeakMap(); +const typingBuffers = new WeakMap< + BaseAudioContext, + { buffers: AudioBuffer[]; next: number } +>(); +function typingSample(c: BaseAudioContext) { + let bank = typingBuffers.get(c); + if (!bank) { + bank = { + buffers: TYPING_PCM.map((encoded) => { + const bytes = atob(encoded); + const buffer = c.createBuffer(1, bytes.length / 2, TYPING_SAMPLE_RATE); + const data = buffer.getChannelData(0); + for (let i = 0; i < data.length; i++) { + const word = + bytes.charCodeAt(i * 2) | (bytes.charCodeAt(i * 2 + 1) << 8); + data[i] = (word > 32767 ? word - 65536 : word) / 32768; + } + return buffer; + }), + next: 0, + }; + typingBuffers.set(c, bank); + } + return bank.buffers[bank.next++ % bank.buffers.length]; +} +const clamp = (x: number) => + Math.max(0, Math.min(1, Number.isFinite(x) ? x : 0)); +const level = ( + param: AudioParam, + value: number, + now: number, + seconds = 0.05, +) => { + param.cancelAndHoldAtTime(now); + param.linearRampToValueAtTime(value, now + seconds); +}; +export const BOOT_CUES: readonly { time: number; sound: Sound }[] = [ + { time: 9.16, sound: "brand" }, + { time: 11.84, sound: "confirm" }, + { time: 19.48, sound: "scan" }, + { time: 21.84, sound: "confirm" }, + { time: 22.76, sound: "welcome" }, + { time: 23.52, sound: "text-reveal" }, + { time: 25.04, sound: "text-reveal" }, + { time: 26.92, sound: "array" }, + { time: 30.68, sound: "open" }, + { time: 34.3, sound: "inspect" }, +]; + +/** Shared by live playback and OfflineAudioContext verification. */ +export function synthesizeSound( + c: BaseAudioContext, + destination: AudioNode, + type: Sound, + at: number, + pan = 0, +) { + const output = c.createGain(), + stereo = c.createStereoPanner(); + stereo.pan.value = Math.max(-0.65, Math.min(0.65, pan)); + output.connect(stereo); + stereo.connect(destination); + const sources: AudioScheduledSourceNode[] = []; + let remaining = 0, + end = at; + const connect = ( + source: AudioScheduledSourceNode, + node: AudioNode, + gain: number, + delay: number, + duration: number, + attack = 0.006, + preserveEnvelope = false, + ) => { + const env = c.createGain(), + start = at + delay; + if (preserveEnvelope) { + // The reference excerpt already contains the impact envelope and edge fades. + env.gain.setValueAtTime(gain, start); + } else { + env.gain.setValueAtTime(0, start); + env.gain.linearRampToValueAtTime( + gain, + start + Math.min(attack, duration * 0.3), + ); + env.gain.exponentialRampToValueAtTime(0.00001, start + duration); + env.gain.linearRampToValueAtTime(0, start + duration + 0.012); + } + node.connect(env); + env.connect(output); + sources.push(source); + remaining++; + source.onended = () => { + source.disconnect(); + node.disconnect(); + env.disconnect(); + if (--remaining === 0) { + output.disconnect(); + stereo.disconnect(); + } + }; + source.start(start); + source.stop(start + duration + 0.015); + end = Math.max(end, start + duration + 0.015); + }; + const tone = ( + f: number, + to: number, + gain: number, + duration: number, + delay = 0, + attack = 0.006, + ) => { + const osc = c.createOscillator(); + osc.frequency.setValueAtTime(f, at + delay); + osc.frequency.exponentialRampToValueAtTime(to, at + delay + duration); + connect(osc, osc, gain, delay, duration, attack); + }; + const air = ( + f: number, + to: number, + gain: number, + duration: number, + delay = 0, + attack = 0.008, + ) => { + let buffer = noiseBuffers.get(c); + if (!buffer) { + buffer = c.createBuffer(1, c.sampleRate * 2, c.sampleRate); + const data = buffer.getChannelData(0); + let seed = 773; + for (let i = 0; i < data.length; i++) { + seed = (Math.imul(seed, 1664525) + 1013904223) >>> 0; + data[i] = seed / 2147483648 - 1; + } + noiseBuffers.set(c, buffer); + } + const src = c.createBufferSource(), + filter = c.createBiquadFilter(); + src.buffer = buffer; + filter.type = "bandpass"; + filter.Q.value = 0.8; + filter.frequency.setValueAtTime(f, at + delay); + filter.frequency.exponentialRampToValueAtTime(to, at + delay + duration); + src.connect(filter); + connect(src, filter, gain, delay, duration, attack); + }; + // A thin glass plate: a fast contact transient excites unequal modes. + // Upper modes fade first, leaving a small, clear body instead of a long bell. + const glass = ( + fundamental: number, + gain: number, + decay: number, + delay = 0, + ) => { + const modes = [ + [1, 1, 1], + [1.47, 0.39, 0.66], + [2.09, 0.21, 0.4], + [2.73, 0.095, 0.25], + [3.86, 0.035, 0.15], + ]; + for (const [ratio, amplitude, damping] of modes) { + const frequency = fundamental * ratio; + if (frequency > Math.min(8500, c.sampleRate * 0.42)) continue; + tone( + frequency, + frequency, + gain * amplitude, + decay * damping, + delay, + 0.0012, + ); + } + air(4800, 3600, gain * 0.24, 0.013, delay, 0.0008); + }; + switch (type) { + case "page-open": + air(700, 1800, 0.065, 0.18, 0, 0.025); + tone(360, 480, 0.032, 0.16, 0, 0.014); + tone(960, 960, 0.009, 0.075, 0.06, 0.01); + break; + case "page-close": + air(1300, 600, 0.05, 0.13, 0, 0.014); + tone(420, 280, 0.027, 0.13, 0, 0.01); + break; + case "ui-tick": + air(1500, 1200, 0.042, 0.036, 0, 0.003); + tone(820, 820, 0.022, 0.052, 0, 0.003); + break; + case "brand": + tone(146.83, 146.83, 0.039, 0.72, 0, 0.08); + tone(293.66, 293.66, 0.03, 0.62, 0.07, 0.07); + tone(440, 440, 0.022, 0.54, 0.17, 0.055); + air(420, 1750, 0.036, 0.7, 0, 0.13); + break; + case "text-reveal": + air(2100, 1300, 0.033, 0.064, 0, 0.005); + tone(1050, 1050, 0.012, 0.06, 0, 0.005); + break; + case "key": { + const source = c.createBufferSource(); + source.buffer = typingSample(c); + connect(source, source, 0.2, 0, source.buffer.duration, 0, true); + break; + } + case "tick": + glass(1680, 0.064, 0.24); + break; + case "column": + glass(1280, 0.065, 0.32); + glass(2050, 0.016, 0.18, 0.045); + break; + case "open": + glass(1150, 0.071, 0.58); + glass(2180, 0.025, 0.36, 0.16); + air(3100, 4400, 0.014, 0.25, 0.035, 0.025); + break; + case "confirm": + tone(640, 640, 0.039, 0.095, 0, 0.008); + tone(960, 960, 0.026, 0.15, 0.095, 0.009); + break; + case "back": + glass(1120, 0.066, 0.22); + tone(560, 560, 0.012, 0.1, 0.025, 0.002); + break; + case "scan": + air(1800, 3400, 0.025, 0.8, 0, 0.12); + for (let i = 0; i < 4; i++) + tone(760, 760, 0.025, 0.064, i * 0.19 + 0.15, 0.007); + break; + case "welcome": + [293.66, 440, 659.25, 739.99].forEach((f, i) => + tone(f, f, 0.034, 1.6, i * 0.095, 0.05), + ); + air(600, 1800, 0.065, 0.9, 0, 0.15); + break; + case "array": + air(1600, 3300, 0.025, 0.8, 0, 0.12); + for (let i = 0; i < 5; i++) + glass(1180 + i * 170, 0.043 - i * 0.005, 0.31, 0.05 + i * 0.105); + break; + case "inspect": + tone(1120, 1120, 0.026, 0.055, 0, 0.005); + tone(1120, 1120, 0.018, 0.055, 0.11, 0.005); + break; + case "explode": + [1220, 1680, 2260].forEach((f, i) => + glass(f, 0.054 - i * 0.01, 0.4 - i * 0.055, i * 0.115), + ); + break; + case "assemble": + [2260, 1680, 1220].forEach((f, i) => + glass(f, 0.035 + i * 0.008, 0.2, i * 0.095), + ); + break; + } + return { + end, + stop(now: number) { + level(output.gain, 0, now, 0.018); + for (const source of sources) { + try { + source.stop(now + 0.02); + } catch { + /* already ended */ + } + } + }, + }; +} + +export class TerminalAudio { + private prefs: AudioPreferences = { + sound: false, + music: false, + soundVolume: 0.55, + musicVolume: 0.5, + }; + private context?: AudioContext; + private effects?: GainNode; + private musicBus?: GainNode; + private duck?: GainNode; + private stemGains: GainNode[] = []; + private buffers?: AudioBuffer[]; + private loading?: Promise; + private fetching?: Promise; + private musicData?: ArrayBuffer[]; + private tracks: AudioBufferSourceNode[] = []; + private voices: ReturnType[] = []; + private lastSound = new Map(); + private scene: SoundScene = "boot"; + private offset = 0; + private startedAt = 0; + private unlocked = false; + private disposed = false; + private bootTime: number | null = null; + private error = ""; + private requestId = 0; + private suspension: Promise = Promise.resolve(); + private bootMix = -1; + private playedKeys = 0; + private entryPending = false; + private hostPaused = false; + setHostPaused(paused: boolean) { + this.hostPaused = paused; + if (paused) this.hide(); + else this.visibility(); + } + constructor() { + document.addEventListener("pointerdown", this.gesture, { capture: true }); + document.addEventListener("keydown", this.gesture, { capture: true }); + document.addEventListener("visibilitychange", this.visibility); + window.addEventListener("pagehide", this.hide); + window.addEventListener("pageshow", this.visibility); + } + private gesture = () => { + if (this.entryPending) return; + this.unlocked = true; + void this.activate(); + }; + holdForEntry() { + this.entryPending = true; + } + releaseEntry() { + this.entryPending = false; + } + cancelEntry() { + this.hide(); + } + async unlock() { + this.unlocked = true; + await this.activate(); + return this.context?.state === "running" && (!this.prefs.music || Boolean(this.buffers)); + } + // Fetch compressed tracks while the entry screen is visible; create/resume + // the audio device only from a real click or keyboard activation. + prepareMusic() { + if (this.musicData) return Promise.resolve(this.musicData); + this.fetching ??= Promise.all(STEMS.map(async name => { + const controller = new AbortController(); + const timeout = setTimeout(() => controller.abort(), 15000); + try { + const response = await fetch(assetUrl(`audio/${name}.ogg`), { signal: controller.signal }); + if (!response.ok) throw new Error(`Music ${name}: ${response.status}`); + return await response.arrayBuffer(); + } finally { clearTimeout(timeout); } + })).then(data => this.musicData = data).finally(() => { this.fetching = undefined; }); + return this.fetching; + } + restartBoot() { + this.stopEffects(); + this.bootTime = 6.76; + this.bootMix = -1; + } + private hide = () => { + this.requestId++; + this.stopMusic(); + this.stopEffects(); + this.suspension = + this.context?.suspend().catch(() => {}) ?? Promise.resolve(); + }; + private visibility = () => { + this.bootTime = null; + if (document.hidden) this.hide(); + else if (this.unlocked && !this.entryPending) void this.activate(); + }; + configure(prefs: AudioPreferences) { + this.prefs = { + sound: !!prefs.sound, + music: !!prefs.music, + soundVolume: clamp(prefs.soundVolume), + musicVolume: clamp(prefs.musicVolume), + }; + if (this.context) { + level( + this.effects!.gain, + this.prefs.sound ? this.prefs.soundVolume : 0, + this.context.currentTime, + ); + level( + this.musicBus!.gain, + this.prefs.music ? this.prefs.musicVolume : 0, + this.context.currentTime, + 0.2, + ); + } + if (!this.prefs.sound) this.stopEffects(); + if (!this.prefs.music) this.stopMusic(); + if (!this.prefs.sound && !this.prefs.music) this.hide(); + else if (this.unlocked && !this.entryPending) void this.activate(); + } + private createContext() { + const c = (this.context = new AudioContext()), + master = c.createGain(), + limiter = c.createDynamicsCompressor(); + master.gain.value = 0.8; + limiter.threshold.value = -8; + limiter.knee.value = 8; + limiter.ratio.value = 6; + limiter.attack.value = 0.003; + limiter.release.value = 0.18; + this.effects = c.createGain(); + this.musicBus = c.createGain(); + this.duck = c.createGain(); + this.effects.gain.value = this.prefs.sound ? this.prefs.soundVolume : 0; + this.musicBus.gain.value = this.prefs.music ? this.prefs.musicVolume : 0; + this.effects.connect(master); + this.musicBus.connect(this.duck); + this.duck.connect(master); + master.connect(limiter); + limiter.connect(c.destination); + this.stemGains = STEMS.map(() => { + const gain = c.createGain(); + gain.gain.value = 0; + gain.connect(this.musicBus!); + return gain; + }); + this.mixScene(); + return c; + } + private async activate() { + if ( + this.disposed || + this.hostPaused || + document.hidden || + !this.unlocked || + (!this.prefs.sound && !this.prefs.music) + ) + return; + const id = ++this.requestId; + try { + const c = this.context ?? this.createContext(); + // Call resume before awaiting network or an earlier suspension so the + // browser observes this call in the user's activation handler. + const resume = c.state === "running" ? Promise.resolve() : c.resume(); + await Promise.all([this.suspension, resume]); + if (id !== this.requestId || this.disposed || document.hidden) return; + if (c.state !== "running") return; + if (id !== this.requestId || document.hidden || this.disposed) return; + if (this.prefs.music) { + await this.loadMusic(c); + if (id === this.requestId) this.startMusic(); + } + } catch (e) { + this.error = e instanceof Error ? e.message : "Audio unavailable"; + } + } + private loadMusic(c: AudioContext) { + if (this.buffers) return Promise.resolve(); + this.loading ??= this.prepareMusic() + .then(data => Promise.all(data.map(bytes => c.decodeAudioData(bytes.slice(0))))) + .then((buffers) => { + this.buffers = buffers; + this.error = ""; + }) + .finally(() => { + this.loading = undefined; + }); + return this.loading; + } + private startMusic() { + const c = this.context; + if ( + !c || + c.state !== "running" || + !this.buffers || + this.hostPaused || + this.tracks.length || + !this.prefs.music || + this.disposed || + document.hidden + ) + return; + this.startedAt = c.currentTime + 0.04; + this.tracks = this.buffers.map((buffer, i) => { + const src = c.createBufferSource(); + src.buffer = buffer; + src.loop = true; + src.loopStart = 0; + src.loopEnd = Math.min(LOOP_SECONDS, buffer.duration); + src.connect(this.stemGains[i]); + src.start(this.startedAt, this.offset % src.loopEnd); + return src; + }); + this.musicBus!.gain.cancelScheduledValues(c.currentTime); + this.musicBus!.gain.setValueAtTime(0, c.currentTime); + this.musicBus!.gain.linearRampToValueAtTime( + this.prefs.musicVolume, + c.currentTime + 1.2, + ); + } + private stopMusic() { + const c = this.context; + if (!c || !this.tracks.length) return; + this.offset = + (this.offset + Math.max(0, c.currentTime - this.startedAt)) % + LOOP_SECONDS; + this.tracks.forEach((track, i) => { + const fade = c.createGain(); + track.disconnect(); + track.connect(fade); + fade.connect(this.stemGains[i]); + fade.gain.setValueAtTime(1, c.currentTime); + fade.gain.linearRampToValueAtTime(0, c.currentTime + 0.06); + track.stop(c.currentTime + 0.07); + track.onended = () => { + track.disconnect(); + fade.disconnect(); + }; + }); + this.tracks = []; + } + private stopEffects() { + if (this.context) + this.voices.forEach((v) => v.stop(this.context!.currentTime)); + this.voices = []; + this.lastSound.clear(); + } + setScene(scene: SoundScene) { + if (this.scene === scene) return; + this.scene = scene; + this.bootTime = null; + this.bootMix = -1; + this.stopEffects(); + this.mixScene(); + } + private mixScene() { + if (!this.context) return; + const gains = { + boot: [0.48, 0.32, 0.18], + archive: [0.9, 0.72, 0.65], + detail: [0.72, 0.36, 0.12], + viewer: [0.8, 0.24, 0.28], + }[this.scene]; + this.stemGains.forEach((g, i) => + level(g.gain, gains[i], this.context!.currentTime, 1.1), + ); + } + play(type: Sound = "tick", pan = 0) { + const c = this.context; + if ( + !this.prefs.sound || + this.hostPaused || + !c || + c.state !== "running" || + document.hidden || + this.disposed + ) + return; + const now = c.currentTime, + interval = + type === "key" + ? 0.024 + : type === "tick" || type === "column" + ? 0.055 + : 0.12; + if (now - (this.lastSound.get(type) ?? -Infinity) < interval) return; + this.lastSound.set(type, now); + this.voices = this.voices.filter((v) => v.end > now); + if (this.voices.length >= 10) this.voices.shift()!.stop(now); + const voice = synthesizeSound(c, this.effects!, type, now + 0.004, pan); + this.voices.push(voice); + if (this.prefs.soundVolume > 0) window.dispatchEvent(new CustomEvent("rhine-local-sound", { + detail: { until: performance.now() / 1000 + Math.max(0, voice.end - now) + .2 }, + })); + if (type === "key") this.playedKeys++; + if ( + ["open", "brand", "welcome", "array", "explode", "assemble"].includes( + type, + ) + ) { + level(this.duck!.gain, 0.65, now, 0.035); + this.duck!.gain.linearRampToValueAtTime(1, now + 0.9); + } + } + updateBoot(appTime: number, frozen = false) { + const time = appTime + 5; + const previous = this.bootTime; + this.bootTime = time; + const phase = time < 22.76 ? 0 : time < 26.92 ? 1 : time < 34.3 ? 2 : 3; + if (phase !== this.bootMix && this.context) { + this.bootMix = phase; + const gains = [ + [0.48, 0.32, 0.18], + [0.68, 0.55, 0.32], + [0.9, 0.72, 0.65], + [0.72, 0.36, 0.12], + ][phase]; + this.stemGains.forEach((g, i) => + level(g.gain, gains[i], this.context!.currentTime, 0.9), + ); + } + if ( + frozen || + previous === null || + time < previous || + time - previous > 0.3 + ) { + this.stopEffects(); + return; + } + for (const cue of BOOT_CUES) + if (cue.time > previous && cue.time <= time) this.play(cue.sound); + if (hasTypingBetween(previous, time)) this.play("key"); + } + stats() { + return { + state: this.context?.state ?? "locked", + scene: this.scene, + tracks: this.tracks.length, + voices: this.voices.filter( + (v) => v.end > (this.context?.currentTime ?? 0), + ).length, + loaded: !!this.buffers, + playedKeys: this.playedKeys, + error: this.error, + preferences: { ...this.prefs }, + }; + } + dispose() { + this.disposed = true; + this.requestId++; + this.stopMusic(); + this.stopEffects(); + document.removeEventListener("pointerdown", this.gesture, true); + document.removeEventListener("keydown", this.gesture, true); + document.removeEventListener("visibilitychange", this.visibility); + window.removeEventListener("pagehide", this.hide); + window.removeEventListener("pageshow", this.visibility); + void this.context?.close(); + } +} diff --git a/src/boot-lettering-art.json b/src/boot-lettering-art.json new file mode 100644 index 0000000..b7b2239 --- /dev/null +++ b/src/boot-lettering-art.json @@ -0,0 +1 @@ +{"access":{"text":"ACCESS PERMISSION REQUIRED","weight":"Normal","units":1000,"letters":[{"width":0.732,"path":"M543 614.0 621 800.0H708L410 100.0H321L23 800.0H110L188 614.0ZM512 540.0H219L296 356.0C322 293.0 363 194.0 364 193.0H366C367 194.0 408 293.0 435 357.0Z"},{"width":0.769,"path":"M646 582.0C602 676.0 508 735.0 394 735.0C240 735.0 120 626.0 120 449.0C120 273.0 234 163.0 396 163.0C542 163.0 611 251.0 637 299.0L716 277.0C672 174.0 560 86.0 398 86.0C193 86.0 38 228.0 38 448.0C38 669.0 194 813.0 398 813.0C546 813.0 671 735.0 725 603.0Z"},{"width":0.769,"path":"M646 582.0C602 676.0 508 735.0 394 735.0C240 735.0 120 626.0 120 449.0C120 273.0 234 163.0 396 163.0C542 163.0 611 251.0 637 299.0L716 277.0C672 174.0 560 86.0 398 86.0C193 86.0 38 228.0 38 448.0C38 669.0 194 813.0 398 813.0C546 813.0 671 735.0 725 603.0Z"},{"width":0.6,"path":"M77 800.0H550V724.0H158V472.0H462V396.0H158V176.0H548V100.0H77Z"},{"width":0.593,"path":"M35 616.0C57 774.0 192 813.0 310 813.0C466 813.0 558 746.0 558 614.0C558 460.0 430 427.0 326 407.0C226 387.0 138 367.0 138 274.0C138 203.0 189 159.0 291 159.0C394 159.0 451 204.0 470 281.0L548 260.0C518 140.0 422 86.0 292 86.0C145 86.0 55 155.0 55 277.0C55 419.0 177 453.0 272 475.0C359 494.0 475 500.0 475 615.0C475 699.0 413 740.0 311 740.0C204 740.0 121 695.0 113 595.0Z"},{"width":0.593,"path":"M35 616.0C57 774.0 192 813.0 310 813.0C466 813.0 558 746.0 558 614.0C558 460.0 430 427.0 326 407.0C226 387.0 138 367.0 138 274.0C138 203.0 189 159.0 291 159.0C394 159.0 451 204.0 470 281.0L548 260.0C518 140.0 422 86.0 292 86.0C145 86.0 55 155.0 55 277.0C55 419.0 177 453.0 272 475.0C359 494.0 475 500.0 475 615.0C475 699.0 413 740.0 311 740.0C204 740.0 121 695.0 113 595.0Z"},{"width":0.25,"path":""},{"width":0.576,"path":"M77 100.0V800.0H158V535.0H289C452 535.0 550 457.0 550 318.0C550 175.0 452 100.0 289 100.0ZM158 463.0V175.0H291C402 175.0 468 220.0 468 319.0C468 412.0 408 463.0 291 463.0Z"},{"width":0.6,"path":"M77 800.0H550V724.0H158V472.0H462V396.0H158V176.0H548V100.0H77Z"},{"width":0.602,"path":"M467 800.0H565L392 510.0C491 489.0 552 426.0 552 318.0C552 181.0 455 100.0 291 100.0H77V800.0H158V526.0H244C259 526.0 293 526.0 308 525.0ZM158 458.0V175.0H294C403 175.0 469 222.0 469 321.0C469 422.0 401 458.0 299 458.0Z"},{"width":0.802,"path":"M644 800.0H726V100.0H642L403 526.0H401L163 100.0H77V800.0H158V485.0C158 377.0 156 233.0 156 232.0H158C159 233.0 185 289.0 250 404.0L377 628.0H426L554 401.0C617 289.0 644 233.0 645 232.0H647C647 233.0 644 377.0 644 485.0Z"},{"width":0.235,"path":"M77 800.0H158V100.0H77Z"},{"width":0.593,"path":"M35 616.0C57 774.0 192 813.0 310 813.0C466 813.0 558 746.0 558 614.0C558 460.0 430 427.0 326 407.0C226 387.0 138 367.0 138 274.0C138 203.0 189 159.0 291 159.0C394 159.0 451 204.0 470 281.0L548 260.0C518 140.0 422 86.0 292 86.0C145 86.0 55 155.0 55 277.0C55 419.0 177 453.0 272 475.0C359 494.0 475 500.0 475 615.0C475 699.0 413 740.0 311 740.0C204 740.0 121 695.0 113 595.0Z"},{"width":0.593,"path":"M35 616.0C57 774.0 192 813.0 310 813.0C466 813.0 558 746.0 558 614.0C558 460.0 430 427.0 326 407.0C226 387.0 138 367.0 138 274.0C138 203.0 189 159.0 291 159.0C394 159.0 451 204.0 470 281.0L548 260.0C518 140.0 422 86.0 292 86.0C145 86.0 55 155.0 55 277.0C55 419.0 177 453.0 272 475.0C359 494.0 475 500.0 475 615.0C475 699.0 413 740.0 311 740.0C204 740.0 121 695.0 113 595.0Z"},{"width":0.235,"path":"M77 800.0H158V100.0H77Z"},{"width":0.802,"path":"M38 449.0C38 667.0 196 813.0 401 813.0C605 813.0 764 667.0 764 449.0C764 232.0 605 86.0 401 86.0C196 86.0 38 232.0 38 449.0ZM120 449.0C120 272.0 243 163.0 401 163.0C558 163.0 682 271.0 682 449.0C682 627.0 558 735.0 401 735.0C243 735.0 120 627.0 120 449.0Z"},{"width":0.727,"path":"M568 800.0H651V100.0H569V500.0C569 581.0 571 663.0 571 664.0H569L159 100.0H77V800.0H158V399.0C158 312.0 156 232.0 156 231.0H158Z"},{"width":0.25,"path":""},{"width":0.602,"path":"M467 800.0H565L392 510.0C491 489.0 552 426.0 552 318.0C552 181.0 455 100.0 291 100.0H77V800.0H158V526.0H244C259 526.0 293 526.0 308 525.0ZM158 458.0V175.0H294C403 175.0 469 222.0 469 321.0C469 422.0 401 458.0 299 458.0Z"},{"width":0.6,"path":"M77 800.0H550V724.0H158V472.0H462V396.0H158V176.0H548V100.0H77Z"},{"width":0.802,"path":"M764 449.0C764 232.0 605 86.0 401 86.0C196 86.0 38 232.0 38 449.0C38 652.0 175 792.0 359 811.0V935.0H442V811.0C626 792.0 764 652.0 764 449.0ZM401 735.0C243 735.0 120 627.0 120 449.0C120 272.0 243 163.0 401 163.0C558 163.0 682 271.0 682 449.0C682 627.0 558 735.0 401 735.0Z"},{"width":0.696,"path":"M628 100.0H547V508.0C547 660.0 484 735.0 348 735.0C213 735.0 151 660.0 151 508.0V100.0H68V508.0C68 706.0 166 813.0 348 813.0C529 813.0 628 706.0 628 508.0Z"},{"width":0.235,"path":"M77 800.0H158V100.0H77Z"},{"width":0.602,"path":"M467 800.0H565L392 510.0C491 489.0 552 426.0 552 318.0C552 181.0 455 100.0 291 100.0H77V800.0H158V526.0H244C259 526.0 293 526.0 308 525.0ZM158 458.0V175.0H294C403 175.0 469 222.0 469 321.0C469 422.0 401 458.0 299 458.0Z"},{"width":0.6,"path":"M77 800.0H550V724.0H158V472.0H462V396.0H158V176.0H548V100.0H77Z"},{"width":0.729,"path":"M77 100.0V800.0H326C535 800.0 691 669.0 691 453.0C691 237.0 536 100.0 319 100.0ZM158 724.0V176.0H316C479 176.0 609 272.0 609 453.0C609 635.0 478 724.0 322 724.0Z"}]},"identity":{"text":"ID CONFIRMED : JOYCE MOORE","weight":"Normal","units":1000,"letters":[{"width":0.235,"path":"M77 800.0H158V100.0H77Z"},{"width":0.729,"path":"M77 100.0V800.0H326C535 800.0 691 669.0 691 453.0C691 237.0 536 100.0 319 100.0ZM158 724.0V176.0H316C479 176.0 609 272.0 609 453.0C609 635.0 478 724.0 322 724.0Z"},{"width":0.25,"path":""},{"width":0.769,"path":"M646 582.0C602 676.0 508 735.0 394 735.0C240 735.0 120 626.0 120 449.0C120 273.0 234 163.0 396 163.0C542 163.0 611 251.0 637 299.0L716 277.0C672 174.0 560 86.0 398 86.0C193 86.0 38 228.0 38 448.0C38 669.0 194 813.0 398 813.0C546 813.0 671 735.0 725 603.0Z"},{"width":0.802,"path":"M38 449.0C38 667.0 196 813.0 401 813.0C605 813.0 764 667.0 764 449.0C764 232.0 605 86.0 401 86.0C196 86.0 38 232.0 38 449.0ZM120 449.0C120 272.0 243 163.0 401 163.0C558 163.0 682 271.0 682 449.0C682 627.0 558 735.0 401 735.0C243 735.0 120 627.0 120 449.0Z"},{"width":0.727,"path":"M568 800.0H651V100.0H569V500.0C569 581.0 571 663.0 571 664.0H569L159 100.0H77V800.0H158V399.0C158 312.0 156 232.0 156 231.0H158Z"},{"width":0.583,"path":"M544 176.0V100.0H77V800.0H158V486.0H465V410.0H158V176.0Z"},{"width":0.235,"path":"M77 800.0H158V100.0H77Z"},{"width":0.602,"path":"M467 800.0H565L392 510.0C491 489.0 552 426.0 552 318.0C552 181.0 455 100.0 291 100.0H77V800.0H158V526.0H244C259 526.0 293 526.0 308 525.0ZM158 458.0V175.0H294C403 175.0 469 222.0 469 321.0C469 422.0 401 458.0 299 458.0Z"},{"width":0.802,"path":"M644 800.0H726V100.0H642L403 526.0H401L163 100.0H77V800.0H158V485.0C158 377.0 156 233.0 156 232.0H158C159 233.0 185 289.0 250 404.0L377 628.0H426L554 401.0C617 289.0 644 233.0 645 232.0H647C647 233.0 644 377.0 644 485.0Z"},{"width":0.6,"path":"M77 800.0H550V724.0H158V472.0H462V396.0H158V176.0H548V100.0H77Z"},{"width":0.729,"path":"M77 100.0V800.0H326C535 800.0 691 669.0 691 453.0C691 237.0 536 100.0 319 100.0ZM158 724.0V176.0H316C479 176.0 609 272.0 609 453.0C609 635.0 478 724.0 322 724.0Z"},{"width":0.25,"path":""},{"width":0.224,"path":"M112 462.0C147 462.0 166 440.0 166 411.0C166 384.0 147 361.0 112 361.0C77 361.0 58 383.0 58 411.0C58 439.0 76 462.0 112 462.0ZM112 810.0C147 810.0 166 787.0 166 759.0C166 731.0 147 709.0 112 709.0C77 709.0 58 731.0 58 759.0C58 787.0 76 810.0 112 810.0Z"},{"width":0.25,"path":""},{"width":0.488,"path":"M416 100.0H334V516.0C334 660.0 288 735.0 176 735.0C122 735.0 81 714.0 46 669.0L0 733.0C44 786.0 102 813.0 176 813.0C334 813.0 416 708.0 416 520.0Z"},{"width":0.802,"path":"M38 449.0C38 667.0 196 813.0 401 813.0C605 813.0 764 667.0 764 449.0C764 232.0 605 86.0 401 86.0C196 86.0 38 232.0 38 449.0ZM120 449.0C120 272.0 243 163.0 401 163.0C558 163.0 682 271.0 682 449.0C682 627.0 558 735.0 401 735.0C243 735.0 120 627.0 120 449.0Z"},{"width":0.657,"path":"M369 496.0 640 100.0H551L392 332.0C356 383.0 333 420.0 332 421.0H330C329 420.0 304 382.0 268 330.0L111 100.0H17L287 496.0V800.0H369Z"},{"width":0.769,"path":"M646 582.0C602 676.0 508 735.0 394 735.0C240 735.0 120 626.0 120 449.0C120 273.0 234 163.0 396 163.0C542 163.0 611 251.0 637 299.0L716 277.0C672 174.0 560 86.0 398 86.0C193 86.0 38 228.0 38 448.0C38 669.0 194 813.0 398 813.0C546 813.0 671 735.0 725 603.0Z"},{"width":0.6,"path":"M77 800.0H550V724.0H158V472.0H462V396.0H158V176.0H548V100.0H77Z"},{"width":0.25,"path":""},{"width":0.802,"path":"M644 800.0H726V100.0H642L403 526.0H401L163 100.0H77V800.0H158V485.0C158 377.0 156 233.0 156 232.0H158C159 233.0 185 289.0 250 404.0L377 628.0H426L554 401.0C617 289.0 644 233.0 645 232.0H647C647 233.0 644 377.0 644 485.0Z"},{"width":0.802,"path":"M38 449.0C38 667.0 196 813.0 401 813.0C605 813.0 764 667.0 764 449.0C764 232.0 605 86.0 401 86.0C196 86.0 38 232.0 38 449.0ZM120 449.0C120 272.0 243 163.0 401 163.0C558 163.0 682 271.0 682 449.0C682 627.0 558 735.0 401 735.0C243 735.0 120 627.0 120 449.0Z"},{"width":0.802,"path":"M38 449.0C38 667.0 196 813.0 401 813.0C605 813.0 764 667.0 764 449.0C764 232.0 605 86.0 401 86.0C196 86.0 38 232.0 38 449.0ZM120 449.0C120 272.0 243 163.0 401 163.0C558 163.0 682 271.0 682 449.0C682 627.0 558 735.0 401 735.0C243 735.0 120 627.0 120 449.0Z"},{"width":0.602,"path":"M467 800.0H565L392 510.0C491 489.0 552 426.0 552 318.0C552 181.0 455 100.0 291 100.0H77V800.0H158V526.0H244C259 526.0 293 526.0 308 525.0ZM158 458.0V175.0H294C403 175.0 469 222.0 469 321.0C469 422.0 401 458.0 299 458.0Z"},{"width":0.6,"path":"M77 800.0H550V724.0H158V472.0H462V396.0H158V176.0H548V100.0H77Z"}]},"request":{"text":"REQUEST RECEIVED","weight":"Normal","units":1000,"letters":[{"width":0.602,"path":"M467 800.0H565L392 510.0C491 489.0 552 426.0 552 318.0C552 181.0 455 100.0 291 100.0H77V800.0H158V526.0H244C259 526.0 293 526.0 308 525.0ZM158 458.0V175.0H294C403 175.0 469 222.0 469 321.0C469 422.0 401 458.0 299 458.0Z"},{"width":0.6,"path":"M77 800.0H550V724.0H158V472.0H462V396.0H158V176.0H548V100.0H77Z"},{"width":0.802,"path":"M764 449.0C764 232.0 605 86.0 401 86.0C196 86.0 38 232.0 38 449.0C38 652.0 175 792.0 359 811.0V935.0H442V811.0C626 792.0 764 652.0 764 449.0ZM401 735.0C243 735.0 120 627.0 120 449.0C120 272.0 243 163.0 401 163.0C558 163.0 682 271.0 682 449.0C682 627.0 558 735.0 401 735.0Z"},{"width":0.696,"path":"M628 100.0H547V508.0C547 660.0 484 735.0 348 735.0C213 735.0 151 660.0 151 508.0V100.0H68V508.0C68 706.0 166 813.0 348 813.0C529 813.0 628 706.0 628 508.0Z"},{"width":0.6,"path":"M77 800.0H550V724.0H158V472.0H462V396.0H158V176.0H548V100.0H77Z"},{"width":0.593,"path":"M35 616.0C57 774.0 192 813.0 310 813.0C466 813.0 558 746.0 558 614.0C558 460.0 430 427.0 326 407.0C226 387.0 138 367.0 138 274.0C138 203.0 189 159.0 291 159.0C394 159.0 451 204.0 470 281.0L548 260.0C518 140.0 422 86.0 292 86.0C145 86.0 55 155.0 55 277.0C55 419.0 177 453.0 272 475.0C359 494.0 475 500.0 475 615.0C475 699.0 413 740.0 311 740.0C204 740.0 121 695.0 113 595.0Z"},{"width":0.637,"path":"M600 176.0V100.0H37V176.0H277V800.0H359V176.0Z"},{"width":0.25,"path":""},{"width":0.602,"path":"M467 800.0H565L392 510.0C491 489.0 552 426.0 552 318.0C552 181.0 455 100.0 291 100.0H77V800.0H158V526.0H244C259 526.0 293 526.0 308 525.0ZM158 458.0V175.0H294C403 175.0 469 222.0 469 321.0C469 422.0 401 458.0 299 458.0Z"},{"width":0.6,"path":"M77 800.0H550V724.0H158V472.0H462V396.0H158V176.0H548V100.0H77Z"},{"width":0.769,"path":"M646 582.0C602 676.0 508 735.0 394 735.0C240 735.0 120 626.0 120 449.0C120 273.0 234 163.0 396 163.0C542 163.0 611 251.0 637 299.0L716 277.0C672 174.0 560 86.0 398 86.0C193 86.0 38 228.0 38 448.0C38 669.0 194 813.0 398 813.0C546 813.0 671 735.0 725 603.0Z"},{"width":0.6,"path":"M77 800.0H550V724.0H158V472.0H462V396.0H158V176.0H548V100.0H77Z"},{"width":0.235,"path":"M77 800.0H158V100.0H77Z"},{"width":0.686,"path":"M392 800.0 670 100.0H582L437 472.0C381 614.0 347 706.0 346 709.0H344C343 706.0 308 613.0 251 467.0L108 100.0H17L295 800.0Z"},{"width":0.6,"path":"M77 800.0H550V724.0H158V472.0H462V396.0H158V176.0H548V100.0H77Z"},{"width":0.729,"path":"M77 100.0V800.0H326C535 800.0 691 669.0 691 453.0C691 237.0 536 100.0 319 100.0ZM158 724.0V176.0H316C479 176.0 609 272.0 609 453.0C609 635.0 478 724.0 322 724.0Z"}]},"processing":{"text":"START PROCESSING...","weight":"Normal","units":1000,"letters":[{"width":0.593,"path":"M35 616.0C57 774.0 192 813.0 310 813.0C466 813.0 558 746.0 558 614.0C558 460.0 430 427.0 326 407.0C226 387.0 138 367.0 138 274.0C138 203.0 189 159.0 291 159.0C394 159.0 451 204.0 470 281.0L548 260.0C518 140.0 422 86.0 292 86.0C145 86.0 55 155.0 55 277.0C55 419.0 177 453.0 272 475.0C359 494.0 475 500.0 475 615.0C475 699.0 413 740.0 311 740.0C204 740.0 121 695.0 113 595.0Z"},{"width":0.637,"path":"M600 176.0V100.0H37V176.0H277V800.0H359V176.0Z"},{"width":0.732,"path":"M543 614.0 621 800.0H708L410 100.0H321L23 800.0H110L188 614.0ZM512 540.0H219L296 356.0C322 293.0 363 194.0 364 193.0H366C367 194.0 408 293.0 435 357.0Z"},{"width":0.602,"path":"M467 800.0H565L392 510.0C491 489.0 552 426.0 552 318.0C552 181.0 455 100.0 291 100.0H77V800.0H158V526.0H244C259 526.0 293 526.0 308 525.0ZM158 458.0V175.0H294C403 175.0 469 222.0 469 321.0C469 422.0 401 458.0 299 458.0Z"},{"width":0.637,"path":"M600 176.0V100.0H37V176.0H277V800.0H359V176.0Z"},{"width":0.25,"path":""},{"width":0.576,"path":"M77 100.0V800.0H158V535.0H289C452 535.0 550 457.0 550 318.0C550 175.0 452 100.0 289 100.0ZM158 463.0V175.0H291C402 175.0 468 220.0 468 319.0C468 412.0 408 463.0 291 463.0Z"},{"width":0.602,"path":"M467 800.0H565L392 510.0C491 489.0 552 426.0 552 318.0C552 181.0 455 100.0 291 100.0H77V800.0H158V526.0H244C259 526.0 293 526.0 308 525.0ZM158 458.0V175.0H294C403 175.0 469 222.0 469 321.0C469 422.0 401 458.0 299 458.0Z"},{"width":0.802,"path":"M38 449.0C38 667.0 196 813.0 401 813.0C605 813.0 764 667.0 764 449.0C764 232.0 605 86.0 401 86.0C196 86.0 38 232.0 38 449.0ZM120 449.0C120 272.0 243 163.0 401 163.0C558 163.0 682 271.0 682 449.0C682 627.0 558 735.0 401 735.0C243 735.0 120 627.0 120 449.0Z"},{"width":0.769,"path":"M646 582.0C602 676.0 508 735.0 394 735.0C240 735.0 120 626.0 120 449.0C120 273.0 234 163.0 396 163.0C542 163.0 611 251.0 637 299.0L716 277.0C672 174.0 560 86.0 398 86.0C193 86.0 38 228.0 38 448.0C38 669.0 194 813.0 398 813.0C546 813.0 671 735.0 725 603.0Z"},{"width":0.6,"path":"M77 800.0H550V724.0H158V472.0H462V396.0H158V176.0H548V100.0H77Z"},{"width":0.593,"path":"M35 616.0C57 774.0 192 813.0 310 813.0C466 813.0 558 746.0 558 614.0C558 460.0 430 427.0 326 407.0C226 387.0 138 367.0 138 274.0C138 203.0 189 159.0 291 159.0C394 159.0 451 204.0 470 281.0L548 260.0C518 140.0 422 86.0 292 86.0C145 86.0 55 155.0 55 277.0C55 419.0 177 453.0 272 475.0C359 494.0 475 500.0 475 615.0C475 699.0 413 740.0 311 740.0C204 740.0 121 695.0 113 595.0Z"},{"width":0.593,"path":"M35 616.0C57 774.0 192 813.0 310 813.0C466 813.0 558 746.0 558 614.0C558 460.0 430 427.0 326 407.0C226 387.0 138 367.0 138 274.0C138 203.0 189 159.0 291 159.0C394 159.0 451 204.0 470 281.0L548 260.0C518 140.0 422 86.0 292 86.0C145 86.0 55 155.0 55 277.0C55 419.0 177 453.0 272 475.0C359 494.0 475 500.0 475 615.0C475 699.0 413 740.0 311 740.0C204 740.0 121 695.0 113 595.0Z"},{"width":0.235,"path":"M77 800.0H158V100.0H77Z"},{"width":0.727,"path":"M568 800.0H651V100.0H569V500.0C569 581.0 571 663.0 571 664.0H569L159 100.0H77V800.0H158V399.0C158 312.0 156 232.0 156 231.0H158Z"},{"width":0.79,"path":"M730 800.0V434.0H446V510.0H654V610.0C626 662.0 534 735.0 402 735.0C242 735.0 120 626.0 120 448.0C120 273.0 236 163.0 398 163.0C549 163.0 615 256.0 638 299.0L718 277.0C674 173.0 560 86.0 400 86.0C194 86.0 38 228.0 38 449.0C38 669.0 198 813.0 397 813.0C535 813.0 622 738.0 656 699.0H658C658 708.0 657 720.0 657 800.0Z"},{"width":0.224,"path":"M112 810.0C147 810.0 166 787.0 166 759.0C166 731.0 147 709.0 112 709.0C77 709.0 58 731.0 58 759.0C58 787.0 76 810.0 112 810.0Z"},{"width":0.224,"path":"M112 810.0C147 810.0 166 787.0 166 759.0C166 731.0 147 709.0 112 709.0C77 709.0 58 731.0 58 759.0C58 787.0 76 810.0 112 810.0Z"},{"width":0.224,"path":"M112 810.0C147 810.0 166 787.0 166 759.0C166 731.0 147 709.0 112 709.0C77 709.0 58 731.0 58 759.0C58 787.0 76 810.0 112 810.0Z"}]},"processingGlitch":{"text":" SING...","weight":"Normal","units":1000,"letters":[{"width":0.25,"path":""},{"width":0.25,"path":""},{"width":0.25,"path":""},{"width":0.25,"path":""},{"width":0.25,"path":""},{"width":0.25,"path":""},{"width":0.25,"path":""},{"width":0.25,"path":""},{"width":0.25,"path":""},{"width":0.25,"path":""},{"width":0.25,"path":""},{"width":0.25,"path":""},{"width":0.25,"path":""},{"width":0.25,"path":""},{"width":0.593,"path":"M35 616.0C57 774.0 192 813.0 310 813.0C466 813.0 558 746.0 558 614.0C558 460.0 430 427.0 326 407.0C226 387.0 138 367.0 138 274.0C138 203.0 189 159.0 291 159.0C394 159.0 451 204.0 470 281.0L548 260.0C518 140.0 422 86.0 292 86.0C145 86.0 55 155.0 55 277.0C55 419.0 177 453.0 272 475.0C359 494.0 475 500.0 475 615.0C475 699.0 413 740.0 311 740.0C204 740.0 121 695.0 113 595.0Z"},{"width":0.235,"path":"M77 800.0H158V100.0H77Z"},{"width":0.727,"path":"M568 800.0H651V100.0H569V500.0C569 581.0 571 663.0 571 664.0H569L159 100.0H77V800.0H158V399.0C158 312.0 156 232.0 156 231.0H158Z"},{"width":0.79,"path":"M730 800.0V434.0H446V510.0H654V610.0C626 662.0 534 735.0 402 735.0C242 735.0 120 626.0 120 448.0C120 273.0 236 163.0 398 163.0C549 163.0 615 256.0 638 299.0L718 277.0C674 173.0 560 86.0 400 86.0C194 86.0 38 228.0 38 449.0C38 669.0 198 813.0 397 813.0C535 813.0 622 738.0 656 699.0H658C658 708.0 657 720.0 657 800.0Z"},{"width":0.224,"path":"M112 810.0C147 810.0 166 787.0 166 759.0C166 731.0 147 709.0 112 709.0C77 709.0 58 731.0 58 759.0C58 787.0 76 810.0 112 810.0Z"},{"width":0.224,"path":"M112 810.0C147 810.0 166 787.0 166 759.0C166 731.0 147 709.0 112 709.0C77 709.0 58 731.0 58 759.0C58 787.0 76 810.0 112 810.0Z"},{"width":0.224,"path":"M112 810.0C147 810.0 166 787.0 166 759.0C166 731.0 147 709.0 112 709.0C77 709.0 58 731.0 58 759.0C58 787.0 76 810.0 112 810.0Z"}]},"permission":{"text":"PERMISSION AUTHORIZED","weight":"Normal","units":1000,"letters":[{"width":0.576,"path":"M77 100.0V800.0H158V535.0H289C452 535.0 550 457.0 550 318.0C550 175.0 452 100.0 289 100.0ZM158 463.0V175.0H291C402 175.0 468 220.0 468 319.0C468 412.0 408 463.0 291 463.0Z"},{"width":0.6,"path":"M77 800.0H550V724.0H158V472.0H462V396.0H158V176.0H548V100.0H77Z"},{"width":0.602,"path":"M467 800.0H565L392 510.0C491 489.0 552 426.0 552 318.0C552 181.0 455 100.0 291 100.0H77V800.0H158V526.0H244C259 526.0 293 526.0 308 525.0ZM158 458.0V175.0H294C403 175.0 469 222.0 469 321.0C469 422.0 401 458.0 299 458.0Z"},{"width":0.802,"path":"M644 800.0H726V100.0H642L403 526.0H401L163 100.0H77V800.0H158V485.0C158 377.0 156 233.0 156 232.0H158C159 233.0 185 289.0 250 404.0L377 628.0H426L554 401.0C617 289.0 644 233.0 645 232.0H647C647 233.0 644 377.0 644 485.0Z"},{"width":0.235,"path":"M77 800.0H158V100.0H77Z"},{"width":0.593,"path":"M35 616.0C57 774.0 192 813.0 310 813.0C466 813.0 558 746.0 558 614.0C558 460.0 430 427.0 326 407.0C226 387.0 138 367.0 138 274.0C138 203.0 189 159.0 291 159.0C394 159.0 451 204.0 470 281.0L548 260.0C518 140.0 422 86.0 292 86.0C145 86.0 55 155.0 55 277.0C55 419.0 177 453.0 272 475.0C359 494.0 475 500.0 475 615.0C475 699.0 413 740.0 311 740.0C204 740.0 121 695.0 113 595.0Z"},{"width":0.593,"path":"M35 616.0C57 774.0 192 813.0 310 813.0C466 813.0 558 746.0 558 614.0C558 460.0 430 427.0 326 407.0C226 387.0 138 367.0 138 274.0C138 203.0 189 159.0 291 159.0C394 159.0 451 204.0 470 281.0L548 260.0C518 140.0 422 86.0 292 86.0C145 86.0 55 155.0 55 277.0C55 419.0 177 453.0 272 475.0C359 494.0 475 500.0 475 615.0C475 699.0 413 740.0 311 740.0C204 740.0 121 695.0 113 595.0Z"},{"width":0.235,"path":"M77 800.0H158V100.0H77Z"},{"width":0.802,"path":"M38 449.0C38 667.0 196 813.0 401 813.0C605 813.0 764 667.0 764 449.0C764 232.0 605 86.0 401 86.0C196 86.0 38 232.0 38 449.0ZM120 449.0C120 272.0 243 163.0 401 163.0C558 163.0 682 271.0 682 449.0C682 627.0 558 735.0 401 735.0C243 735.0 120 627.0 120 449.0Z"},{"width":0.727,"path":"M568 800.0H651V100.0H569V500.0C569 581.0 571 663.0 571 664.0H569L159 100.0H77V800.0H158V399.0C158 312.0 156 232.0 156 231.0H158Z"},{"width":0.25,"path":""},{"width":0.732,"path":"M543 614.0 621 800.0H708L410 100.0H321L23 800.0H110L188 614.0ZM512 540.0H219L296 356.0C322 293.0 363 194.0 364 193.0H366C367 194.0 408 293.0 435 357.0Z"},{"width":0.696,"path":"M628 100.0H547V508.0C547 660.0 484 735.0 348 735.0C213 735.0 151 660.0 151 508.0V100.0H68V508.0C68 706.0 166 813.0 348 813.0C529 813.0 628 706.0 628 508.0Z"},{"width":0.637,"path":"M600 176.0V100.0H37V176.0H277V800.0H359V176.0Z"},{"width":0.702,"path":"M544 800.0H626V100.0H544V395.0H158V100.0H77V800.0H158V473.0H544Z"},{"width":0.802,"path":"M38 449.0C38 667.0 196 813.0 401 813.0C605 813.0 764 667.0 764 449.0C764 232.0 605 86.0 401 86.0C196 86.0 38 232.0 38 449.0ZM120 449.0C120 272.0 243 163.0 401 163.0C558 163.0 682 271.0 682 449.0C682 627.0 558 735.0 401 735.0C243 735.0 120 627.0 120 449.0Z"},{"width":0.602,"path":"M467 800.0H565L392 510.0C491 489.0 552 426.0 552 318.0C552 181.0 455 100.0 291 100.0H77V800.0H158V526.0H244C259 526.0 293 526.0 308 525.0ZM158 458.0V175.0H294C403 175.0 469 222.0 469 321.0C469 422.0 401 458.0 299 458.0Z"},{"width":0.235,"path":"M77 800.0H158V100.0H77Z"},{"width":0.654,"path":"M612 178.0V100.0H52V176.0H425C477 176.0 509 175.0 510 175.0L511 178.0C511 179.0 485 204.0 434 263.0L42 725.0V800.0H612V724.0H235C184 724.0 149 725.0 148 725.0L146 723.0C146 722.0 169 701.0 228 631.0Z"},{"width":0.6,"path":"M77 800.0H550V724.0H158V472.0H462V396.0H158V176.0H548V100.0H77Z"},{"width":0.729,"path":"M77 100.0V800.0H326C535 800.0 691 669.0 691 453.0C691 237.0 536 100.0 319 100.0ZM158 724.0V176.0H316C479 176.0 609 272.0 609 453.0C609 635.0 478 724.0 322 724.0Z"}]},"brand":{"text":"RHINE LAB","weight":"DemiBold","units":1000,"letters":[{"width":0.631,"path":"M443 800.0H608L443 527.0C533 499.0 585 431.0 585 330.0C585 188.0 484 100.0 311 100.0H63V800.0H202V554.0H257H302ZM202 442.0V227.0H319C399 227.0 443 265.0 443 337.0C443 410.0 397 442.0 321 442.0Z"},{"width":0.712,"path":"M510 800.0H649V100.0H510V374.0H202V100.0H63V800.0H202V508.0H510Z"},{"width":0.265,"path":"M63 800.0H202V100.0H63Z"},{"width":0.742,"path":"M538 800.0H679V100.0H540V396.0C540 473.0 542 560.0 542 561.0H539L204 100.0H63V800.0H202V506.0C202 417.0 200 333.0 200 332.0H203Z"},{"width":0.609,"path":"M63 800.0H564V671.0H202V506.0H471V376.0H202V229.0H560V100.0H63Z"},{"width":0.25,"path":""},{"width":0.567,"path":"M63 800.0H534V671.0H202V100.0H63Z"},{"width":0.749,"path":"M531 660.0 584 800.0H733L453 100.0H296L16 800.0H163L216 660.0ZM482 534.0H264L323 380.0C347 319.0 371 258.0 372 257.0H375C376 258.0 400 320.0 424 380.0Z"},{"width":0.629,"path":"M464 423.0C505 409.0 560 362.0 560 279.0C560 181.0 475 100.0 348 100.0H63V800.0H354C521 800.0 601 710.0 601 599.0C601 497.0 530 444.0 464 427.0ZM202 384.0V216.0H321C383 216.0 420 251.0 420 300.0C420 353.0 381 384.0 324 384.0ZM202 684.0V489.0H342C407 489.0 455 525.0 455 588.0C455 648.0 413 684.0 342 684.0Z"}]},"welcome":{"text":"WELCOME TO","weight":"Bold","units":1000,"letters":[{"width":1.065,"path":"M662 800.0H846L1050 100.0H883L800 405.0C774 504.0 756 571.0 755 573.0H752C751 571.0 734 508.0 713 428.0L644 166.0H440L370 426.0C349 506.0 332 571.0 331 573.0H327C326 571.0 309 502.0 282 399.0L203 100.0H16L220 800.0H412L489 518.0C516 418.0 529 361.0 530 359.0H534C535 361.0 548 418.0 576 511.0Z"},{"width":0.613,"path":"M56 800.0H571V644.0H224V516.0H476V376.0H224V256.0H566V100.0H56Z"},{"width":0.569,"path":"M56 800.0H538V644.0H224V100.0H56Z"},{"width":0.749,"path":"M559 549.0C531 607.0 474 655.0 382 655.0C274 655.0 197 574.0 197 450.0C197 328.0 269 244.0 383 244.0C458 244.0 515 277.0 551 333.0L717 288.0C670 175.0 552 86.0 391 86.0C182 86.0 28 235.0 28 448.0C28 667.0 182 814.0 391 814.0C543 814.0 672 729.0 724 593.0Z"},{"width":0.794,"path":"M28 450.0C28 664.0 183 814.0 397 814.0C611 814.0 766 664.0 766 450.0C766 236.0 611 86.0 397 86.0C183 86.0 28 236.0 28 450.0ZM197 450.0C197 324.0 281 244.0 397 244.0C513 244.0 597 324.0 597 450.0C597 575.0 513 655.0 397 655.0C281 655.0 197 575.0 197 450.0Z"},{"width":0.817,"path":"M593 800.0H761V100.0H590L413 413.0H410L234 100.0H56V800.0H224V629.0C224 463.0 222 381.0 222 380.0H225C226 381.0 259 444.0 294 506.0L362 627.0H456L524 504.0C558 444.0 592 381.0 593 380.0H595C595 381.0 593 463.0 593 629.0Z"},{"width":0.613,"path":"M56 800.0H571V644.0H224V516.0H476V376.0H224V256.0H566V100.0H56Z"},{"width":0.25,"path":""},{"width":0.658,"path":"M627 256.0V100.0H31V256.0H245V800.0H413V256.0Z"},{"width":0.794,"path":"M28 450.0C28 664.0 183 814.0 397 814.0C611 814.0 766 664.0 766 450.0C766 236.0 611 86.0 397 86.0C183 86.0 28 236.0 28 450.0ZM197 450.0C197 324.0 281 244.0 397 244.0C513 244.0 597 324.0 597 450.0C597 575.0 513 655.0 397 655.0C281 655.0 197 575.0 197 450.0Z"}]},"company":{"text":"RHINE LAB.LLC.","weight":"Bold","units":1000,"letters":[{"width":0.645,"path":"M430 800.0H629L469 536.0C554 504.0 601 433.0 601 337.0C601 192.0 498 100.0 322 100.0H56V800.0H224V569.0H264H299ZM224 435.0V252.0H332C397 252.0 429 287.0 429 345.0C429 404.0 395 435.0 333 435.0Z"},{"width":0.717,"path":"M493 800.0H661V100.0H493V364.0H224V100.0H56V800.0H224V525.0H493Z"},{"width":0.28,"path":"M56 800.0H224V100.0H56Z"},{"width":0.75,"path":"M523 800.0H694V100.0H526V343.0C526 419.0 527 509.0 527 510.0H524L226 100.0H56V800.0H224V560.0C224 469.0 222 383.0 222 382.0H225Z"},{"width":0.613,"path":"M56 800.0H571V644.0H224V516.0H476V376.0H224V256.0H566V100.0H56Z"},{"width":0.25,"path":""},{"width":0.569,"path":"M56 800.0H538V644.0H224V100.0H56Z"},{"width":0.758,"path":"M523 684.0 565 800.0H745L474 100.0H283L13 800.0H190L232 684.0ZM468 531.0H287L337 391.0C359 333.0 375 290.0 376 289.0H379C380 290.0 396 334.0 418 392.0Z"},{"width":0.632,"path":"M481 423.0C519 409.0 571 360.0 571 282.0C571 185.0 484 100.0 358 100.0H56V800.0H357C531 800.0 608 704.0 608 596.0C608 503.0 549 447.0 481 427.0ZM224 377.0V239.0H325C373 239.0 402 269.0 402 308.0C402 349.0 372 377.0 326 377.0ZM224 661.0V501.0H342C397 501.0 431 531.0 431 581.0C431 628.0 400 661.0 342 661.0Z"},{"width":0.283,"path":"M142 809.0C199 809.0 227 774.0 227 730.0C227 686.0 198 652.0 142 652.0C85 652.0 56 686.0 56 730.0C56 774.0 85 809.0 142 809.0Z"},{"width":0.569,"path":"M56 800.0H538V644.0H224V100.0H56Z"},{"width":0.569,"path":"M56 800.0H538V644.0H224V100.0H56Z"},{"width":0.749,"path":"M559 549.0C531 607.0 474 655.0 382 655.0C274 655.0 197 574.0 197 450.0C197 328.0 269 244.0 383 244.0C458 244.0 515 277.0 551 333.0L717 288.0C670 175.0 552 86.0 391 86.0C182 86.0 28 235.0 28 448.0C28 667.0 182 814.0 391 814.0C543 814.0 672 729.0 724 593.0Z"},{"width":0.283,"path":"M142 809.0C199 809.0 227 774.0 227 730.0C227 686.0 198 652.0 142 652.0C85 652.0 56 686.0 56 730.0C56 774.0 85 809.0 142 809.0Z"}]},"database":{"text":"INTERNAL DATABASE","weight":"Bold","units":1000,"letters":[{"width":0.28,"path":"M56 800.0H224V100.0H56Z"},{"width":0.75,"path":"M523 800.0H694V100.0H526V343.0C526 419.0 527 509.0 527 510.0H524L226 100.0H56V800.0H224V560.0C224 469.0 222 383.0 222 382.0H225Z"},{"width":0.658,"path":"M627 256.0V100.0H31V256.0H245V800.0H413V256.0Z"},{"width":0.613,"path":"M56 800.0H571V644.0H224V516.0H476V376.0H224V256.0H566V100.0H56Z"},{"width":0.645,"path":"M430 800.0H629L469 536.0C554 504.0 601 433.0 601 337.0C601 192.0 498 100.0 322 100.0H56V800.0H224V569.0H264H299ZM224 435.0V252.0H332C397 252.0 429 287.0 429 345.0C429 404.0 395 435.0 333 435.0Z"},{"width":0.75,"path":"M523 800.0H694V100.0H526V343.0C526 419.0 527 509.0 527 510.0H524L226 100.0H56V800.0H224V560.0C224 469.0 222 383.0 222 382.0H225Z"},{"width":0.758,"path":"M523 684.0 565 800.0H745L474 100.0H283L13 800.0H190L232 684.0ZM468 531.0H287L337 391.0C359 333.0 375 290.0 376 289.0H379C380 290.0 396 334.0 418 392.0Z"},{"width":0.569,"path":"M56 800.0H538V644.0H224V100.0H56Z"},{"width":0.25,"path":""},{"width":0.751,"path":"M56 100.0V800.0H355C562 800.0 723 665.0 723 453.0C723 242.0 563 100.0 354 100.0ZM224 644.0V256.0H345C449 256.0 554 316.0 554 453.0C554 591.0 449 644.0 345 644.0Z"},{"width":0.758,"path":"M523 684.0 565 800.0H745L474 100.0H283L13 800.0H190L232 684.0ZM468 531.0H287L337 391.0C359 333.0 375 290.0 376 289.0H379C380 290.0 396 334.0 418 392.0Z"},{"width":0.658,"path":"M627 256.0V100.0H31V256.0H245V800.0H413V256.0Z"},{"width":0.758,"path":"M523 684.0 565 800.0H745L474 100.0H283L13 800.0H190L232 684.0ZM468 531.0H287L337 391.0C359 333.0 375 290.0 376 289.0H379C380 290.0 396 334.0 418 392.0Z"},{"width":0.632,"path":"M481 423.0C519 409.0 571 360.0 571 282.0C571 185.0 484 100.0 358 100.0H56V800.0H357C531 800.0 608 704.0 608 596.0C608 503.0 549 447.0 481 427.0ZM224 377.0V239.0H325C373 239.0 402 269.0 402 308.0C402 349.0 372 377.0 326 377.0ZM224 661.0V501.0H342C397 501.0 431 531.0 431 581.0C431 628.0 400 661.0 342 661.0Z"},{"width":0.758,"path":"M523 684.0 565 800.0H745L474 100.0H283L13 800.0H190L232 684.0ZM468 531.0H287L337 391.0C359 333.0 375 290.0 376 289.0H379C380 290.0 396 334.0 418 392.0Z"},{"width":0.627,"path":"M27 625.0C67 777.0 205 814.0 327 814.0C498 814.0 599 742.0 599 598.0C599 447.0 485 403.0 382 383.0C284 363.0 217 354.0 217 295.0C217 258.0 244 233.0 308 233.0C378 233.0 418 261.0 435 312.0L595 269.0C556 144.0 454 86.0 311 86.0C154 86.0 46 157.0 46 300.0C46 448.0 164 492.0 259 515.0C345 536.0 429 532.0 429 599.0C429 644.0 390 666.0 326 666.0C250 666.0 201 635.0 187 582.0Z"},{"width":0.613,"path":"M56 800.0H571V644.0H224V516.0H476V376.0H224V256.0H566V100.0H56Z"}]}} diff --git a/src/boot-lettering.css b/src/boot-lettering.css new file mode 100644 index 0000000..2efec6b --- /dev/null +++ b/src/boot-lettering.css @@ -0,0 +1,75 @@ +/* Fixed Novecento phrase drawings; the licensed font files are not embedded. */ +.boot-phrase { + display: inline-flex; + height: 1em; + align-items: stretch; + vertical-align: -0.16em; + line-height: 1; + letter-spacing: 0; + white-space: nowrap; +} +.boot-phrase-letter { + position: relative; + display: block; + flex: 0 0 auto; + height: 1em; + margin-inline-end: var(--boot-phrase-tracking, 0px); +} +.boot-font-letter::before { + content: attr(data-letter); + position: absolute; + left: 0; + /* The official font's 1021/-179 metrics place its 1em line baseline at + .921em. Move it to the same .8em baseline as the authored artwork. */ + top: -0.121em; + font-family: var(--boot-webfont-family); + font-size: inherit; + font-weight: 400; + font-style: normal; + font-synthesis: none; + font-kerning: none; + line-height: 1; + white-space: pre; +} +.boot-phrase-letter:last-child { margin-inline-end: 0; } +.boot-phrase[hidden], .boot-phrase-letter[hidden] { display: none; } +.boot-phrase svg.boot-letter-art { + display: block; + width: 100%; + height: 100%; + overflow: visible; + fill: currentColor; +} +.boot-phrase-label { + position: absolute; + width: 1px; + height: 1px; + overflow: hidden; + clip-path: inset(50%); + white-space: pre; +} +.boot-lettering-fallback > .boot-phrase-label { + position: static; + width: auto; + height: auto; + overflow: visible; + clip-path: none; +} +#auth-message { --boot-phrase-tracking: -0.00909em; } +.auth-status { top: 527px; gap: 10px; font-size: 21.35px; } +.welcome-company { --boot-phrase-tracking: -0.5px; } +.brand h1 { + position: relative; + height: 48px; + font-size: 50.75px; + letter-spacing: 0; + --boot-phrase-tracking: 1px; +} +.brand h1 > .boot-phrase { + position: absolute; + left: 0; + top: 1.425px; +} +[data-layout="opening"][data-opening-portrait="true"] .scan > span { + --boot-phrase-tracking: 1px !important; +} diff --git a/src/boot-lettering.ts b/src/boot-lettering.ts new file mode 100644 index 0000000..1237e56 --- /dev/null +++ b/src/boot-lettering.ts @@ -0,0 +1,111 @@ +import artwork from "./boot-lettering-art.json"; +import "./boot-lettering.css"; +import { assetUrl } from "./asset-url"; + +declare const __RHINE_NOVECENTO__: boolean; +const letterings = new Set(); + +/** Only a locally installed, licensed kit enables native webfont rendering. */ +export async function loadBootWebfonts() { + if (!__RHINE_NOVECENTO__) return false; + const faces = ["Normal", "DemiBold", "Bold"].map(weight => new FontFace( + `Rhine Novecento ${weight}`, + `url("${assetUrl(`fonts/novecento/webFonts/NovecentoSansWide${weight}/font.woff2`)}") format("woff2")`, + { weight: "400", style: "normal", display: "swap" }, + )); + let timeout: ReturnType | undefined; + try { + await Promise.race([ + Promise.all(faces.map(face => face.load())), + new Promise((_, reject) => { timeout = setTimeout(() => reject(new Error("Novecento load timeout")), 8000); }), + ]); + faces.forEach(face => document.fonts.add(face)); + letterings.forEach(lettering => lettering.useWebfonts()); + return true; + } catch (error) { + console.warn("Novecento kit unavailable; retaining authored phrase graphics.", error); + return false; + } finally { clearTimeout(timeout); } +} + +type PhraseKey = keyof typeof artwork; +const ns = "http://www.w3.org/2000/svg"; + +/** Fixed phrase reveal cells, backed by licensed webfonts or authored artwork. */ +export class BootLettering { + private label = document.createElement("span"); + private phrases: { + text: string; + node: HTMLSpanElement; + letters: HTMLSpanElement[]; + weight: string; + }[]; + private value: string | undefined; + + constructor(private host: HTMLElement, keys: PhraseKey[]) { + this.label.className = "boot-phrase-label"; + this.phrases = keys.map((key) => { + const art = artwork[key]; + const node = document.createElement("span"); + node.className = "boot-phrase"; + node.dataset.phrase = key; + node.dataset.weight = art.weight; + node.setAttribute("aria-hidden", "true"); + node.hidden = true; + const letters = art.letters.map((letter) => { + const cell = document.createElement("span"); + cell.className = "boot-phrase-letter"; + cell.style.width = `${letter.width}em`; + if (letter.path) { + const svg = document.createElementNS(ns, "svg"); + svg.classList.add("boot-letter-art"); + svg.setAttribute("viewBox", `0 0 ${letter.width * art.units} ${art.units}`); + svg.setAttribute("focusable", "false"); + const path = document.createElementNS(ns, "path"); + path.setAttribute("d", letter.path); + svg.append(path); + cell.append(svg); + } + node.append(cell); + return cell; + }); + return { text: art.text, node, letters, weight: art.weight }; + }); + host.classList.add("has-boot-lettering"); + host.replaceChildren(this.label, ...this.phrases.map((p) => p.node)); + host.dataset.letteringRenderer = "artwork"; + letterings.add(this); + } + + useWebfonts() { + // Retain the measured cells and the reveal timeline. Only the glyph source + // changes: actual WOFF2 text replaces each pre-authored SVG drawing. + for (const phrase of this.phrases) { + phrase.node.style.setProperty("--boot-webfont-family", `"Rhine Novecento ${phrase.weight}"`); + phrase.letters.forEach((letter, i) => { + letter.replaceChildren(); + letter.dataset.letter = phrase.text[i]; + letter.classList.add("boot-font-letter"); + }); + } + this.host.dataset.letteringRenderer = "webfont"; + } + + setText(value: string) { + if (this.value === value) return; + this.value = value; + this.label.textContent = value; + const active = value ? this.phrases.find((p) => p.text.startsWith(value)) : undefined; + // A new, unauthored phrase remains readable until its artwork is exported. + this.host.classList.toggle("boot-lettering-fallback", Boolean(value && !active)); + for (const phrase of this.phrases) { + const visible = phrase === active; + if (phrase.node.hidden === visible) phrase.node.hidden = !visible; + if (!visible) continue; + phrase.letters.forEach((letter, i) => { + const hidden = i >= value.length; + if (letter.hidden !== hidden) letter.hidden = hidden; + }); + } + } +} diff --git a/src/boot-logo-tracks.ts b/src/boot-logo-tracks.ts new file mode 100644 index 0000000..b3c514a --- /dev/null +++ b/src/boot-logo-tracks.ts @@ -0,0 +1,188 @@ +import { track } from "./boot-tracks"; + +// Position along the single shared contour, unwrapped across its closing point. +// These are the visible stroke's trailing/leading ends, not whole-logo opacity. +const draw = [ + [229, -0.02, -0.0198], + [230, -0.0195, -0.011], + [231, -0.016, 0.009], + [232, -0.0095, 0.047], + [233, 0.0025, 0.1135], + [234, 0.0215, 0.2205], + [235, 0.048, 0.365], + [236, 0.0735, 0.504], + [237, 0.094, 0.6155], + [238, 0.109, 0.7045], + [239, 0.122, 0.7755], + [240, 0.133, 0.835], + [241, 0.1425, 0.884], + [242, 0.1505, 0.927], + [243, 0.1575, 0.964], + [244, 0.164, 0.996], + [245, 0.1695, 1.023], + [246, 0.1745, 1.0475], + [247, 0.1785, 1.0685], + [248, 0.183, 1.087], + [249, 0.187, 1.103], + [250, 0.19, 1.1175], + [251, 0.1935, 1.13], + [252, 0.196, 1.1405], + [253, 0.1985, 1.15], + [254, 0.201, 1.1575], + [255, 0.203, 1.1645], + [256, 0.205, 1.17], + [257, 0.2065, 1.1745], + [258, 0.208, 1.1785], + [259, 0.2095, 1.181], + [260, 0.2105, 1.183], + [261, 0.2115, 1.184], + [264, 0.211, 1.1835], +] as const; +const drawStart = draw.map(([f, start]) => [f, start] as const); +const drawEnd = draw.map(([f, , end]) => [f, end] as const); +const movingCut = [ + [420, 0.1835], + [425, 0.184], + [430, 0.1885], + [435, 0.2], + [440, 0.2205], + [450, 0.2925], + [455, 0.3485], + [460, 0.421], + [465, 0.5095], + [470, 0.614], + [475, 0.722], + [480, 0.827], + [485, 0.9195], + [486, 0.9365], +] as const; +const leftEdge = [ + [260, 814], + [261, 813], + [262, 812], + [263, 808], + [264, 803], + [265, 794], + [266, 781], + [267, 763], + [268, 740], + [269, 716], + [270, 691], + [271, 668], + [272, 648], + [273, 630], + [274, 615], + [275, 601], + [276, 590], + [277, 580], + [278, 571], + [279, 563], + [280, 556], + [281, 550], + [282, 545], + [283, 540], + [284, 536], + [285, 533], + [286, 530], + [287, 527], + [288, 525], + [289, 523], + [290, 521], + [291, 520], + [292, 519], + [294, 518], +] as const; + +export function bootLogoTrack(frame: number) { + const cut = track(movingCut, frame); + const start = frame < 420 ? track(drawStart, frame) : cut + 0.0275; + const end = frame < 420 ? track(drawEnd, frame) : cut + 1; + return { + offsetX: track(leftEdge, frame) - 520, + start, + length: Math.max(0, Math.min(1, end - start)), + strokeWidth: track( + [ + [229, 0.25], + [230, 2.8], + [231, 8], + [232, 16], + [233, 23.5], + [234, 26], + ], + frame, + ), + symbolScale: track( + [ + [246, 0], + [247, 0.4], + [248, 0.63], + [249, 0.75], + [250, 0.83], + [251, 0.89], + [252, 0.94], + [254, 0.985], + [256, 1], + ], + frame, + ), + plusX: track( + [ + [246, 155], + [247, 123.3], + [248, 104], + [249, 93.53], + [250, 86.52], + [251, 81.78], + [252, 78.48], + [254, 74.26], + [256, 72.96], + [260, 72.6], + ], + frame, + ), + minusX: track( + [ + [246, 155], + [247, 186.82], + [248, 206.14], + [249, 216.66], + [250, 223.47], + [251, 228.21], + [252, 231.77], + [254, 236.06], + [256, 237.57], + [260, 237.1], + ], + frame, + ), + minusWidth: track( + [ + [246, 15], + [256, 15], + [257, 23], + [258, 31], + [259, 35.5], + [260, 38], + [262, 42], + [264, 44], + [268, 46], + ], + frame, + ), + plusAngle: track( + [ + [255, 0], + [256, -3], + [257, -22], + [258, -45], + [259, -62], + [260, -70], + [262, -80], + [265, -87], + [270, -90], + ], + frame, + ), + }; +} diff --git a/src/boot-motion.ts b/src/boot-motion.ts new file mode 100644 index 0000000..d78aa4e --- /dev/null +++ b/src/boot-motion.ts @@ -0,0 +1,140 @@ +// Original footage is 25 fps. App time zero corresponds to video time 5 s. +// Discrete editorial cuts use frame numbers; spatial motion uses continuous time. +import { brandTrack, companyTrack, scanTrack, track } from "./boot-tracks"; +import { scanOrbitTrack } from "./boot-orbit-tracks"; +import { bootLogoTrack } from "./boot-logo-tracks"; +export const progress = (t: number, a: number, b: number) => + Math.max(0, Math.min(1, (t - a) / (b - a))); +export const smooth = (p: number) => p * p * (3 - 2 * p); +const typed = (text: string, f: number, start: number, end: number) => + text.slice( + 0, + f < start + ? 0 + : Math.min( + text.length, + 1 + Math.floor(((f - start) * (text.length - 1)) / (end - start)), + ), + ); +const at = (f: number, frames: number[]) => frames.includes(f); +const accessCounts = [ + 1, 1, 3, 4, 5, 6, 9, 11, 12, 14, 17, 18, 19, 20, 22, 23, 25, 26, +]; + +export function bootMotion(appTime: number) { + const t = appTime + 5; + const f = Math.floor(t * 25 + 0.00001); + const step = + t < 9.12 + ? "access" + : t < 11.12 + ? "logo" + : t < 19.48 + ? "auth" + : t < 22.76 + ? "scan" + : "welcome"; + let auth = ""; + if (f < 363) { + auth = typed("ID CONFIRMED", f, 282, 295); + if (f >= 320) auth += " : " + typed("JOYCE MOORE", f, 321, 339); + } else if (f < 421) auth = typed("REQUEST RECEIVED", f, 367, 389); + else { + auth = typed("START PROCESSING", f, 423, 440); + if (f >= 449) + auth += ".".repeat(Math.min(3, 1 + Math.floor((f - 449) / 4))); + if (at(f, [479, 485, 486])) auth = " SING..."; + } + const frame = t * 25; + const scan = scanTrack(frame); + const scanOrbit = scanOrbitTrack(frame); + const scanGlitch = at(f, [525, 526, 528, 529]); + const welcomeIntro = [1, 0, 0.28, 0, 1, 0, 0]; + const flashIndex = f - 569; + const exit = smooth(progress(t, 26.56, 26.92)); + return { + t, + f, + step, + auth, + access: "ACCESS PERMISSION REQUIRED".slice( + 0, + f < 170 ? 0 : accessCounts[Math.min(17, f - 170)], + ), + accessOpacity: f >= 170 && f < 227 ? (f === 226 ? 0.25 : 1) : 0, + logoOpacity: t >= 9.16 && t < 19.48 ? 1 : 0, + logo: bootLogoTrack(frame), + logoLetters: typed("RHINE·LAB", f, 232, 255), + authOpacity: f >= 281 && f < 487 ? 1 : 0, + brand: [0, 1, 2].map((line) => brandTrack(frame, line)), + poweredLetters: typed("POWERED BY RHINE LAB", f, 279, 295).length, + scanVisible: t >= 19.48 && t < 22.76, + scan, + scanOrbit, + scanRadius: scan.radius, + ringScale: scanGlitch ? 1.94 : 1, + ringOpacity: scanGlitch + ? 0.32 + : track( + [ + [487, 0], + [488, 0.18], + [490, 0.6], + [493, 1], + ], + frame, + ), + ringBlur: scanGlitch ? 2.2 : 0, + scanTracking: track( + [ + [487, 40], + [492, 28], + [497, 18], + [500, 14], + [505, 8], + [510, 4], + [515, 1.7], + [520, 0.5], + [527, 0], + [568, 0], + ], + frame, + ), + scanFont: 26.5, + permissionOpacity: + t < 21.8 + ? progress(t, 19.48, 19.88) + : track( + [ + [545, 1], + [546, 0.4], + [547, 0.3], + [548, 0.25], + [549, 0.1], + [550, 0.04], + [551, 0], + ], + frame, + ), + ornament: t >= 21.84, + coreRadius: scanOrbit.coreRadius, + welcomeVisible: t >= 22.76 && t < 26.92, + welcomePanel: + flashIndex >= 0 && flashIndex < 7 ? welcomeIntro[flashIndex] : 0, + welcomeInk: + flashIndex >= 0 && flashIndex < 7 + ? [0, 0, 0.2, 1, 0, 0, 0.25][flashIndex] + : 1, + companyVisible: f >= 588 && !at(f, [590, 591]), + companyMask: at(f, [594, 595]), + highlight: companyTrack(frame), + databaseOpacity: f < 626 || at(f, [628, 629, 631, 634]) ? 0 : 1, + welcomeLogo: f >= 588, + welcomeScale: 1 - 0.46 * exit, + welcomeOpacity: 1 - Math.pow(exit, 3), + exitBlur: 8 * exit, + exit, + backgroundOpacity: t < 26.92 ? 1 : 0, + white: smooth(progress(t, 26.16, 26.88)), + }; +} diff --git a/src/boot-orbit-tracks.ts b/src/boot-orbit-tracks.ts new file mode 100644 index 0000000..980136a --- /dev/null +++ b/src/boot-orbit-tracks.ts @@ -0,0 +1,149 @@ +import { track } from "./boot-tracks"; + +// Source frames 543–568. Angles are unwrapped: the left arc turns clockwise, +// the right arc counterclockwise. Each grows while its angular speed decays. +// Columns: frame, left x/y, right x/y, radius, left/right start, left/right sweep. +// Early short strokes are manually fitted; longer arcs are measured by +// reference/measure-scan-detail.py. Centers also settle towards y=539.5. +const sides = [ + [543, 827.5, 561, 1091, 518, 38.7, 260, 250, 0, 0], + [544, 827.5, 561, 1091, 518, 38.7, 260, 250, 3, 3], + [545, 827.5, 560.5, 1091, 518.5, 38.6, 265, 236, 17, 17], + [546, 827.5, 559.5, 1091, 519.5, 38.5, 274, 190, 44, 44], + [547, 827.6, 558.2, 1090.6, 520.8, 38.5, 296, 121, 79, 81], + [548, 828.06, 556.76, 1090.35, 522.58, 38.38, 332.38, 50.99, 111.73, 113.42], + [549, 827.84, 555.13, 1090.42, 524.39, 38.34, 367.27, -14.03, 141.56, 145.66], + [550, 827.89, 553.28, 1090.2, 525.89, 38.44, 396.14, -68.97, 168.88, 172.89], + [551, 828, 551.68, 1090.21, 527.44, 38.28, 421.62, -113.84, 191.68, 195.3], + [552, 828.19, 550.24, 1089.94, 528.85, 38.25, 444.24, -152.25, 208.55, 215.7], + [553, 828.42, 548.83, 1089.7, 530.24, 38.18, 462.76, -184.19, 225.92, 230.73], + [554, 828.61, 547.64, 1089.53, 531.44, 38.12, 479.15, -212.29, 239.9, 244.63], + [555, 828.8, 546.47, 1089.37, 532.55, 38.03, 494.23, -237.06, 251.56, 257.75], + [ + 556, 829.06, 545.41, 1089.16, 533.53, 37.91, 508.57, -259.19, 261.49, + 267.63, + ], + [557, 829.25, 544.49, 1088.93, 534.52, 37.87, 520.46, -279.2, 271.23, 278.42], + [558, 829.47, 543.66, 1088.7, 535.37, 37.79, 530.39, -295.86, 280.34, 286.17], + [559, 829.75, 542.88, 1088.46, 536.12, 37.72, 541.33, -312.5, 287.17, 295.12], + [ + 560, 829.95, 542.16, 1088.26, 536.88, 37.65, 549.47, -326.37, 295.98, + 301.16, + ], + [ + 561, 830.16, 541.63, 1088.02, 537.41, 37.57, 557.82, -338.78, 302.29, + 307.52, + ], + [562, 830.33, 541.08, 1087.81, 537.94, 37.52, 565.1, -350.74, 307.99, 312.58], + [ + 563, 830.59, 540.65, 1087.61, 538.36, 37.43, 571.89, -362.08, 313.78, + 319.28, + ], + [ + 564, 830.83, 540.28, 1087.38, 538.74, 37.37, 578.93, -371.93, 316.97, + 323.53, + ], + [ + 565, 831.04, 539.97, 1087.14, 539.04, 37.35, 583.85, -380.51, 322.27, + 327.78, + ], + [ + 566, 831.25, 539.74, 1086.93, 539.28, 37.27, 589.84, -388.93, 325.61, + 331.62, + ], + [567, 831.48, 539.56, 1086.73, 539.4, 37.19, 594, -396.5, 330.93, 335.92], + [568, 831.61, 539.51, 1086.55, 539.48, 37.15, 599.44, -403.94, 332.25, 338.7], +] as const; +const columns = Array.from({ length: 9 }, (_, i) => + sides.map((row) => [row[0], row[i + 1]] as const), +); +const orbit = [ + [548, 926.33, 554.33], + [551, 944.38, 572.15], + [553, 954.92, 575.05], + [555, 963, 575], + [558, 971.63, 572.85], + [561, 977.53, 569.78], + [564, 981.5, 566.86], + [568, 984.71, 563.5], +] as const; +const orbitAngle = orbit.map( + ([f, x, y]) => [f, Math.atan2(y - 539.5, x - 959.5)] as const, +); +const orbitRadius = orbit.map( + ([f, x, y]) => [f, Math.hypot(x - 959.5, y - 539.5)] as const, +); +const dotGrowth = [ + [-1, 0], + [0, 0.8], + [1, 1.7], + [2, 2.35], + [3, 2.85], + [4, 3.15], + [5, 3.45], + [7, 3.8], + [10, 4.1], + [15, 4.25], + [20, 4.25], +] as const; + +export function scanOrbitTrack(frame: number) { + const values = columns.map((keys) => track(keys, frame)); + const [lx, ly, rx, ry, radius, leftStart, rightStart, leftSweep, rightSweep] = + values; + const angle = track(orbitAngle, frame), + distance = track(orbitRadius, frame); + const sourceFrame = Math.floor(frame + 0.00001); + // Large core flashes are editorial cuts. The small core then grows gently + // as six satellites appear in three-frame steps, with continuing orbit phase. + const flash = [546, 547, 549, 550].includes(sourceFrame); + const coreRadius = flash + ? track( + [ + [546, 42.93], + [550, 42.43], + ], + frame, + ) + : track( + [ + [545, 0], + [548, 7.96], + [551, 9.89], + [553, 10.63], + [555, 11.08], + [559, 11.45], + [564, 11.45], + [568, 11.27], + ], + frame, + ); + return { + sides: [ + { + x: lx, + y: ly, + radius, + start: (leftStart * Math.PI) / 180, + sweep: (leftSweep * Math.PI) / 180, + }, + { + x: rx, + y: ry, + radius, + start: (rightStart * Math.PI) / 180, + sweep: (rightSweep * Math.PI) / 180, + }, + ], + sideVisible: frame >= 544, + coreRadius, + satellites: Array.from({ length: 6 }, (_, i) => { + const phase = angle + (i * Math.PI) / 3; + return { + x: 959.5 + Math.cos(phase) * distance, + y: 539.5 + Math.sin(phase) * distance, + radius: track(dotGrowth, frame - 548 - i * 3), + }; + }), + }; +} diff --git a/src/boot-tracks.ts b/src/boot-tracks.ts new file mode 100644 index 0000000..d4b4739 --- /dev/null +++ b/src/boot-tracks.ts @@ -0,0 +1,347 @@ +// Reference-space measurements (1920×1080, 25 fps). See reference/measure-boot.py. +// Monotone cubic interpolation preserves measured speeds between source frames; +// unlike easing each interval separately, it does not stop at every keyframe. +type Key = readonly [number, number]; +export function track(keys: readonly Key[], frame: number): number { + if (frame <= keys[0][0]) return keys[0][1]; + const last = keys.length - 1; + if (frame >= keys[last][0]) return keys[last][1]; + const slope = (i: number) => + (keys[i + 1][1] - keys[i][1]) / (keys[i + 1][0] - keys[i][0]); + const tangent = (i: number) => { + if (i === 0) return slope(0); + if (i === last) return slope(last - 1); + const a = slope(i - 1), + b = slope(i); + if (a * b <= 0) return 0; + const h0 = keys[i][0] - keys[i - 1][0], + h1 = keys[i + 1][0] - keys[i][0]; + const w0 = 2 * h1 + h0, + w1 = h1 + 2 * h0; + return (w0 + w1) / (w0 / a + w1 / b); + }; + let i = 0; + while (frame > keys[i + 1][0]) i++; + const h = keys[i + 1][0] - keys[i][0], + p = (frame - keys[i][0]) / h; + return ( + (2 * p ** 3 - 3 * p ** 2 + 1) * keys[i][1] + + (p ** 3 - 2 * p ** 2 + p) * h * tangent(i) + + (-2 * p ** 3 + 3 * p ** 2) * keys[i + 1][1] + + (p ** 3 - p ** 2) * h * tangent(i + 1) + ); +} + +// All three lines repeat this trajectory with a two-frame offset. Ink starts +// arriving before the fastest movement, then the translation has a long tail. +const brandX = [ + 234, 216, 181, 143, 115, 94, 78, 65, 54, 45, 38, 31, 26, 21, 17, 13, 10, 8, 6, + 4, 3, 2, 1, 0, +]; +const brandKeys = brandX.map((x, i) => [i, x] as const); +export const brandTrack = (frame: number, line: number) => { + const local = frame - 278 - line * 2; + return { + x: track(brandKeys, local), + opacity: track( + [ + [-1, 0], + [0, 0.4], + [1, 0.65], + [2, 0.88], + [3, 1], + ], + local, + ), + }; +}; + +// Frames 590–591 cut the layer out; its spatial trajectory keeps advancing. +const highlightKeys: readonly Key[] = [ + [588, 4], + [589, 39], + [592, 225], + [593, 261], + [594, 288], + [595, 310], + [596, 328], + [597, 343], + [598, 356], + [599, 366], + [600, 375], + [601, 382], + [602, 389], + [603, 394], + [604, 398], + [605, 402], + [606, 405], + [607, 407], + [608, 408], + [609, 409], + [610, 410], + [611, 410], +]; +export const companyTrack = (frame: number) => + track(highlightKeys, frame) / 410; + +export function scanTrack(frame: number) { + const value = (keys: readonly Key[]) => track(keys, frame); + const rad = Math.PI / 180; + return { + radius: value([ + [487, 2000], + [492, 1540], + [497, 1095], + [500, 895.5], + [505, 650], + [510, 492], + [515, 385.5], + [520, 320.5], + [524, 287.5], + [527, 275], + [530, 270], + [535, 265.5], + [540, 262], + [545, 258], + [550, 255], + [555, 252.5], + [560, 250], + [568, 246], + ]), + whiteRadius: value([ + [487, 1500], + [492, 1011], + [493, 955], + [494, 893], + [495, 834], + [496, 783], + [497, 737], + [498, 693.5], + [499, 655], + [500, 619], + [505, 470.5], + [510, 374], + [515, 310.5], + [520, 272], + [524, 253.5], + [527, 248], + [530, 246], + [535, 242], + [540, 238], + [545, 235], + [550, 232.5], + [555, 230], + [560, 227.5], + [568, 224], + ]), + outerStart: + rad * + value([ + [487, 470], + [492, 364], + [497, 276], + [500, 226], + [505, 159.75], + [510, 98.75], + [515, 49.25], + [520, 13.25], + [524, -9], + [527, -23], + [530, -36.25], + [535, -53], + [540, -66.25], + [545, -75.75], + [550, -82.75], + [560, -89], + [568, -90], + ]), + outerSweep: + rad * + value([ + [487, 30], + [492, 105], + [497, 170], + [500, 199], + [505, 241.5], + [510, 270.25], + [515, 295.75], + [520, 313.25], + [527, 331], + [530, 336.75], + [535, 344.5], + [540, 350.75], + [545, 355], + [550, 358], + [560, 360], + [568, 360], + ]), + whiteStart: + rad * + value([ + [487, -100], + [492, -30], + [497, 72], + [500, 107], + [505, 140], + [510, 172], + [515, 196], + [520, 213.75], + [527, 234.5], + [530, 241], + [535, 249.75], + [540, 256.75], + [545, 262.25], + [550, 265.75], + [560, 269.5], + [568, 270], + ]), + whiteSweep: + rad * + value([ + [487, 30], + [492, 105], + [497, 170], + [500, 200], + [505, 241.25], + [510, 272.75], + [515, 295.5], + [520, 313.75], + [527, 330.5], + [530, 335.5], + [535, 343.5], + [540, 349.5], + [545, 354], + [550, 357], + [560, 360], + [568, 360], + ]), + innerRadius: value([ + [487, 124.5], + [492, 123], + [495, 121], + [500, 117.5], + [505, 112], + [510, 107.5], + [515, 104], + [520, 101.5], + [530, 97], + [540, 94], + [550, 91.5], + [560, 90], + [568, 88.5], + ]), + innerStart: + rad * + value([ + [487, -20], + [490, -4.25], + [492, 14], + [495, 48.75], + [497, 69.25], + [500, 93.25], + [505, 121.25], + [510, 141], + [515, 155.5], + [520, 166.5], + [527, 178], + [530, 181], + [535, 187.75], + [540, 192], + [545, 195.25], + [550, 197.75], + [555, 199.75], + [560, 200.75], + [568, 201.5], + ]), + innerSweep: + rad * + value([ + [487, 0], + [492, 22.5], + [497, 55.75], + [500, 70.25], + [505, 87.25], + [510, 99], + [515, 108], + [520, 115], + [527, 123.5], + [530, 127], + [535, 128], + [540, 131], + [550, 134], + [568, 136], + ]), + orbit: + rad * + value([ + [487, 70], + [492, 33], + [493, 13.2], + [494, -5.8], + [495, -24.2], + [496, -41.4], + [497, -57.2], + [498, -71.4], + [499, -84.2], + [500, -96], + [505, -140.8], + [510, -172.3], + [515, -195.5], + [520, -213.5], + [527, -234], + [530, -238.8], + [535, -247.7], + [540, -254.7], + [545, -260.1], + [550, -264], + [555, -266.9], + [560, -268.8], + [568, -270], + ]), + orbitRadius: value([ + [487, 270], + [495, 236], + [500, 224], + [505, 214], + [510, 206], + [515, 199], + [520, 193], + [530, 185], + [540, 179.5], + [550, 174.5], + [560, 171.5], + [568, 169], + ]), + dotRadius: value([ + [487, 0], + [492, 1], + [500, 5.6], + [510, 7], + [520, 7.8], + [530, 8], + [568, 8], + ]), + blackCap: value([ + [487, 45], + [504, 35], + [510, 20], + [515, 10], + [520, 5], + [525, 2.5], + [535, 2], + [550, 0], + [568, 0], + ]), + whiteCap: value([ + [487, 40], + [492, 30], + [497, 22], + [502, 16], + [507, 10], + [512, 5], + [520, 1.5], + [540, 0], + [568, 0], + ]), + }; +} diff --git a/src/boot.ts b/src/boot.ts new file mode 100644 index 0000000..b9f7911 --- /dev/null +++ b/src/boot.ts @@ -0,0 +1,274 @@ +import { bootMotion } from "./boot-motion"; +import { bootMarkContour } from "./brand"; +import { themeAmount } from "./theme-ui"; +import { BootLettering } from "./boot-lettering"; + +const ns = "http://www.w3.org/2000/svg"; +const arc = (r: number, start: number, sweep: number, x = 960, y = 540) => { + const point = (a: number) => `${x + Math.cos(a) * r},${y + Math.sin(a) * r}`; + if (sweep >= Math.PI * 1.999) + return `M${point(start)}A${r},${r} 0 1 1 ${point(start + Math.PI)}A${r},${r} 0 1 1 ${point(start + Math.PI * 2)}`; + return `M${point(start)}A${r},${r} 0 ${sweep > Math.PI ? 1 : 0} 1 ${point(start + sweep)}`; +}; + +export class BootSequence { + private nodes: Map = new Map(); + private contour: SVGPathElement; + private letters: SVGTextElement; + private plus: SVGPathElement; + private minus: SVGPathElement; + private brandLines: HTMLElement[]; + private scanPaths: SVGPathElement[]; + private orbitDots: SVGCircleElement[]; + private core: SVGCircleElement; + private satellites: SVGCircleElement[]; + private caps: SVGCircleElement[]; + private companyInk: HTMLElement[]; + private poweredHTML: string; + private accessLettering: BootLettering; + private authLettering: BootLettering; + constructor(private stage: HTMLElement) { + [ + ".access-text", + ".boot-logo", + ".auth-status", + "#auth-message", + ".scan", + ".scan > span", + ".welcome", + ".welcome-heading", + ".welcome-panel", + ".welcome-company", + ".welcome-highlight", + ".welcome-database", + ".welcome-logo", + ".brand", + ".powered", + "#boot-background", + ".boot-background svg", + ".boot-white", + ].forEach((s) => this.nodes.set(s, stage.querySelector(s)!)); + const mark = stage.querySelector(".boot-logo svg")!; + const original = mark.querySelector("path")!; + this.contour = original; + this.contour.setAttribute("d", bootMarkContour); + this.contour.setAttribute("pathLength", "1"); + const symbols = mark.querySelector("path:not([pathLength])")!; + this.plus = document.createElementNS(ns, "path"); + this.plus.setAttribute("d", "M44 70h50M69 45v50"); + this.minus = document.createElementNS(ns, "path"); + this.minus.setAttribute("d", "M219 70h44"); + [this.plus, this.minus].forEach((p) => { + p.setAttribute("stroke", "currentColor"); + p.setAttribute("stroke-width", "15"); + mark.insertBefore(p, symbols); + }); + symbols.remove(); + this.letters = mark.querySelector("text")!; + this.letters.setAttribute("text-anchor", "start"); + this.letters.setAttribute("x", "20"); + this.brandLines = Array.from( + stage.querySelector(".brand")!.children, + ) as HTMLElement[]; + this.scanPaths = Array.from( + stage.querySelectorAll(".scan path"), + ); + this.orbitDots = Array.from( + stage.querySelectorAll(".scan .orbit-dot"), + ); + this.core = stage.querySelector(".scan .scan-core")!; + this.core.setAttribute("cx", "959.5"); + this.core.setAttribute("cy", "539.5"); + this.satellites = Array.from({ length: 6 }, () => { + const dot = document.createElementNS(ns, "circle"); + dot.classList.add("satellite-dot"); + dot.setAttribute("fill", "#080a08"); + dot.setAttribute("stroke", "none"); + // Draw beneath the core so its large flashes naturally cover the orbit. + this.core.parentElement!.insertBefore(dot, this.core); + return dot; + }); + this.caps = ["#080a08", "#fff"].map((fill) => { + const cap = document.createElementNS(ns, "circle"); + cap.setAttribute("fill", fill); + cap.setAttribute("stroke", "none"); + this.core.parentElement!.appendChild(cap); + return cap; + }); + this.companyInk = Array.from( + stage.querySelectorAll(".welcome-company strong"), + ); + this.companyInk.forEach((el) => { + const ink = document.createElement("span"); + ink.textContent = el.textContent; + el.replaceChildren(ink); + }); + this.poweredHTML = this.el(".powered").innerHTML; + new BootLettering(this.brandLines[0], ["brand"]).setText("RHINE LAB"); + // Bind after collecting the original ring paths. Phrase artwork also has + // SVG paths, and must never be included in the scan's animated geometry. + this.accessLettering = new BootLettering(this.el(".access-text"), ["access"]); + this.authLettering = new BootLettering(this.el("#auth-message"), [ + "identity", "request", "processing", "processingGlitch", + ]); + for (const [selector, key, text] of [ + [".scan > span", "permission", "PERMISSION AUTHORIZED"], + [".welcome-heading", "welcome", "WELCOME TO"], + [".welcome-database", "database", "INTERNAL DATABASE"], + ] as const) new BootLettering(this.el(selector), [key]).setText(text); + this.companyInk.forEach((el) => + new BootLettering(el.querySelector("span")!, ["company"]).setText("RHINE LAB.LLC."), + ); + } + private el(selector: string) { + return this.nodes.get(selector)!; + } + private opacity(selector: string, value: number | boolean) { + this.el(selector).style.opacity = String(Number(value)); + } + update(time: number) { + const s = bootMotion(time), + t = s.t; + this.stage.dataset.bootFrame = String(s.f); + this.accessLettering.setText(s.access); + this.opacity(".access-text", s.accessOpacity); + this.opacity(".boot-logo", s.logoOpacity); + this.el(".boot-logo").style.transform = + `translate(${s.logo.offsetX}px, 1px)`; + this.contour.style.strokeDasharray = `${s.logo.length} ${1 - s.logo.length}`; + this.contour.style.strokeDashoffset = String(-s.logo.start); + this.contour.setAttribute("stroke-width", String(s.logo.strokeWidth)); + // Preserve the SVG text node once each revealed letter is in place. Replacing + // it every frame invalidates glyph rasterization under the moving HUD. + if (this.letters.textContent !== s.logoLetters) + this.letters.textContent = s.logoLetters; + this.plus.style.opacity = this.minus.style.opacity = + s.logo.symbolScale > 0 ? "1" : "0"; + this.plus.setAttribute( + "transform", + `translate(${s.logo.plusX} 70) rotate(${s.logo.plusAngle}) scale(${s.logo.symbolScale}) translate(-69 -70)`, + ); + this.minus.setAttribute( + "d", + `M${-s.logo.minusWidth / 2} 0h${s.logo.minusWidth}`, + ); + this.minus.setAttribute( + "transform", + `translate(${s.logo.minusX} 70) scale(${s.logo.symbolScale})`, + ); + this.opacity(".auth-status", s.authOpacity); + this.authLettering.setText(s.auth); + this.opacity(".brand", 1); + this.el(".brand").style.transform = "none"; + this.brandLines.forEach((node, i) => { + node.style.opacity = String(s.brand[i].opacity); + node.style.transform = `translateX(${s.brand[i].x}px)`; + }); + this.opacity(".powered", s.poweredLetters > 0); + this.el(".powered").style.clipPath = + `inset(0 ${100 * (1 - s.poweredLetters / 19)}% 0 0)`; + this.opacity(".scan", s.scanVisible); + if (s.scanVisible) this.renderScan(s); + this.opacity(".welcome", s.welcomeVisible ? s.welcomeOpacity : 0); + this.el(".welcome").style.transform = `scale(${s.welcomeScale})`; + this.el(".welcome").style.filter = + `blur(${s.exitBlur}px) invert(${s.exit * 0.22}) sepia(${s.exit}) saturate(${1 + s.exit * 5}) hue-rotate(${s.exit * 115}deg)`; + this.opacity(".welcome-panel", s.welcomePanel); + this.opacity(".welcome-heading", 1); + this.el(".welcome-heading").style.color = + themeAmount > .0001 ? "var(--theme-ink)" : `rgb(${255 * (1 - s.welcomeInk)} ${255 * (1 - s.welcomeInk)} ${255 * (1 - s.welcomeInk)})`; + this.opacity(".welcome-company", s.companyVisible); + this.el(".welcome-company").style.opacity = String( + s.companyVisible ? (s.companyMask ? 0.65 : 1) : 0, + ); + this.companyInk[1].querySelector("span")!.style.opacity = s.companyMask + ? ".06" + : "1"; + this.el(".welcome-highlight").style.clipPath = + `inset(0 ${100 * (1 - s.highlight)}% 0 0)`; + this.opacity(".welcome-database", s.databaseOpacity); + this.opacity(".welcome-logo", s.welcomeLogo); + this.opacity("#boot-background", s.backgroundOpacity); + this.opacity(".boot-white", s.white); + this.el(".boot-background svg").style.transform = + `translate(${Math.sin(t * 0.16) * 18}px, ${-(t - 6) * 5}px) scale(1.08)`; + return s; + } + private renderScan(s: ReturnType) { + const { scan } = s, + r = scan.radius; + const group = this.scanPaths[0].parentElement!; + group.setAttribute( + "transform", + `translate(960 540) scale(${s.ringScale}) translate(-960 -540)`, + ); + group.style.opacity = String(s.ringOpacity); + group.style.filter = `blur(${s.ringBlur}px)`; + this.scanPaths[0].setAttribute( + "d", + arc(r, scan.outerStart, scan.outerSweep), + ); + this.scanPaths[0].setAttribute("stroke-width", "2.4"); + this.scanPaths[1].setAttribute( + "d", + arc(scan.whiteRadius, scan.whiteStart, scan.whiteSweep), + ); + this.scanPaths[1].setAttribute("stroke-width", "4"); + const inner = scan.innerStart; + this.scanPaths[2].setAttribute( + "d", + arc(scan.innerRadius, inner, scan.innerSweep), + ); + this.scanPaths[3].setAttribute( + "d", + arc(scan.innerRadius, inner + Math.PI, scan.innerSweep), + ); + s.scanOrbit.sides.forEach((side, i) => { + this.scanPaths[i + 4].setAttribute( + "d", + arc(side.radius, side.start, side.sweep, side.x, side.y), + ); + this.scanPaths[i + 4].style.opacity = s.scanOrbit.sideVisible ? "1" : "0"; + }); + s.scanOrbit.satellites.forEach((point, i) => { + const dot = this.satellites[i]; + dot.setAttribute("cx", String(point.x)); + dot.setAttribute("cy", String(point.y)); + dot.setAttribute("r", String(point.radius)); + }); + this.core.style.opacity = s.ornament ? "1" : "0"; + this.core.setAttribute("r", String(s.coreRadius)); + const orbit = scan.orbit; + this.orbitDots.forEach((dot, i) => { + dot.setAttribute( + "cx", + String(960 + Math.cos(orbit + i * Math.PI) * scan.orbitRadius), + ); + dot.setAttribute( + "cy", + String(540 + Math.sin(orbit + i * Math.PI) * scan.orbitRadius), + ); + dot.setAttribute("r", String(scan.dotRadius)); + }); + this.caps.forEach((cap, i) => { + const angle = i ? scan.whiteStart : scan.outerStart + scan.outerSweep; + const radius = i ? scan.whiteRadius : r; + cap.setAttribute("cx", String(960 + Math.cos(angle) * radius)); + cap.setAttribute("cy", String(540 + Math.sin(angle) * radius)); + cap.setAttribute("r", String(i ? scan.whiteCap : scan.blackCap)); + }); + this.opacity(".scan > span", s.permissionOpacity); + this.el(".scan > span").style.letterSpacing = `${s.scanTracking}px`; + this.el(".scan > span").style.setProperty("--boot-phrase-tracking", `${s.scanTracking}px`); + this.el(".scan > span").style.fontSize = `${s.scanFont}px`; + } + reset() { + // Restore shared corner branding when skipping at any intermediate frame. + [".brand", ".powered"].forEach((key) => + this.el(key).removeAttribute("style"), + ); + this.brandLines.forEach((node) => node.removeAttribute("style")); + this.el(".powered").innerHTML = this.poweredHTML; + this.opacity("#boot-background", 0); + } +} diff --git a/src/brand.ts b/src/brand.ts new file mode 100644 index 0000000..f115ec2 --- /dev/null +++ b/src/brand.ts @@ -0,0 +1,12 @@ +// One shared mark for the interface and the printed archive label. +const paths = ``; +export const labelMarkSvg = `${paths}`; +export const logo = `${paths}RHINE·LAB`; +// The same Bezier contour, continuous for the opening's moving draw/erase ends. +// Its small printed gap is animated with stroke dashes, not baked into the path. +export const bootMarkContour = + "M295 73C295 41 273 15 240 15C221 15 207 23 192 38C186 43 181 47 176 52C127 96 103 128 70 128C38 128 15 101 15 70C15 39 37 15 70 15C103 15 127 48 156 75C182 99 208 128 240 128C273 128 295 105 295 73Z"; + +// Optical spacing for this fixed wordmark, measured from the reference glyphs. +const analysisPositions = [2, 28, 55, 81, 103, 129, 154, 166]; +export const brandHeading = `

RHINE LAB

SYNTHESIZE INFORMATION

${[..."ANALYSIS"].map((letter, i) => ``).join("")} OS

`; diff --git a/src/data.ts b/src/data.ts new file mode 100644 index 0000000..a1a585b --- /dev/null +++ b/src/data.ts @@ -0,0 +1,41 @@ +import content from "../content/archives.json" with { type: "json" }; + +export interface ArchiveRecord { + id: string; + title: string; + en: string; + department: string; + category: string; + date: string; + lead: string; + clearance: string; + abstract: string; + findings: string[]; + source: string; + /** 相对归档根目录的真实路径,由 scripts/archive-source.mjs 写入;用于在资源管理器中定位。 */ + path?: string; + stats?: { bytes: number; files: number; modified: string | null }; +} + +export const records: ArchiveRecord[] = content.records; +export const categories = ["全部档案", ...content.categories]; +export const archiveColumns = content.columns; + +export function columnFiles(lane: number) { + return records + .map((record, index) => ({ record, index })) + .filter(({ record }) => record.category === archiveColumns[lane]) + .map(({ index }) => index); +} +export function fileLocation(index: number) { + const lane = archiveColumns.indexOf(records[index].category); + const row = 12 + columnFiles(lane).indexOf(index); + // slot 是 32 行窗口内的归一化坐标。列内超过 20 条时 row 会超过 32, + // 若直接相加会串到下一列的行上,所以这里取模。 + // 交互路径用的是 {lane,row},行空间无上限;slot 只服务 boot 的固定 160 布局。 + return { lane, row, slot: lane * 32 + (row % 32) }; +} +export function fileAtSlot(slot: number) { + const files = columnFiles(Math.floor(slot / 32)); + return files[Math.max(0, Math.min(files.length - 1, (slot % 32) - 12))]; +} diff --git a/src/decryption.css b/src/decryption.css new file mode 100644 index 0000000..f39a1ed --- /dev/null +++ b/src/decryption.css @@ -0,0 +1,35 @@ +#inspection-lines { + stroke-linecap: butt; + stroke-linejoin: miter; + filter: none; +} +#inspection-point { + fill: #24221f; +} +.viewer-surface { + position: absolute; + top: 144px; + right: 62px; + z-index: 3; + display: flex; + border: 1px solid #bcb6ac; +} +.viewer-surface button { + padding: 10px 22px; + font-size: 14px; + color: #626158; + background: transparent; +} +.viewer-surface button + button { + border-left: 1px solid #bcb6ac; +} +.viewer-surface button[aria-pressed="true"] { + color: #f5f1e9; + background: #353b30; +} +.viewer-surface button:disabled { + opacity: 0.45; +} +.viewer-surface button:focus-visible { + outline-offset: 4px; +} diff --git a/src/decryption.ts b/src/decryption.ts new file mode 100644 index 0000000..4e18540 --- /dev/null +++ b/src/decryption.ts @@ -0,0 +1,185 @@ +// Measured against the local reference at 25 fps. Times here are ORIGINAL video +// seconds, unlike the camera's historical (original minus five) time convention. +export const DECRYPTION_START = 34.12; +export const DECRYPTION_END = 39.56; +const INTERACTIVE_RATE = 1.5; +const clamp = (value: number) => Math.max(0, Math.min(1, value)); +const smooth = (value: number) => { + const x = clamp(value); + return x * x * x * (10 + x * (-15 + x * 6)); +}; + +type Knot = readonly [number, number]; +// Monotone cubic Hermite interpolation preserves the measured easing without +// overshoot or velocity jumps at a frame sample. End velocities are zero. +export function sampleCurve(knots: readonly Knot[], time: number) { + if (time <= knots[0][0]) return knots[0][1]; + if (time >= knots[knots.length - 1][0]) return knots[knots.length - 1][1]; + const secant = (i: number) => + (knots[i + 1][1] - knots[i][1]) / (knots[i + 1][0] - knots[i][0]); + const slope = (i: number) => { + if (i === 0 || i === knots.length - 1) return 0; + const a = secant(i - 1), + b = secant(i); + if (a * b <= 0) return 0; + const left = knots[i][0] - knots[i - 1][0], + right = knots[i + 1][0] - knots[i][0]; + const w1 = 2 * right + left, + w2 = right + 2 * left; + return (w1 + w2) / (w1 / a + w2 / b); + }; + let i = 0; + while (time > knots[i + 1][0]) i++; + const span = knots[i + 1][0] - knots[i][0], + t = (time - knots[i][0]) / span, + t2 = t * t, + t3 = t2 * t; + return ( + (2 * t3 - 3 * t2 + 1) * knots[i][1] + + (t3 - 2 * t2 + t) * span * slope(i) + + (-2 * t3 + 3 * t2) * knots[i + 1][1] + + (t3 - t2) * span * slope(i + 1) + ); +} + +const GROW: readonly Knot[] = [ + [34.24, 0], + [34.4, 0.19], + [34.64, 0.57], + [34.96, 0.79], + [35.28, 0.92], + [35.6, 0.973], + [36.04, 1], +]; +// The clearing front starts when the line finishes retracting. At 39.0 s +// the upper half is already readable; height-dependent roughness moves with it. +const REVEAL: readonly Knot[] = [ + [38.84, 0], + [38.92, 0.28], + [39.0, 0.51], + [39.16, 0.74], + [39.32, 0.94], + [39.56, 1], +]; +const RETRACT: readonly Knot[] = [ + [37.72, 1], + [37.88, 0.72], + [38.0, 0.38], + [38.12, 0.22], + [38.24, 0.14], + [38.4, 0.075], + [38.64, 0.024], + [38.84, 0], +]; + +// Endpoints measured on the face at 37 seconds. Every moving endpoint is on +// this ONE diagonal in model space; projection carries it with the camera. +export const SCAN_FROM = [-1.6, 0.5] as const; +export const SCAN_TO = [1.98, 3.24] as const; +export const SCAN_CORNERS = [ + [-1.88, 3.2], + [2.14, 3.36], + [-1.77, 0.36], + [2.17, 0.65], +] as const; + +export function decryptionFrame(time: number) { + const grow = sampleCurve(GROW, time); + const remaining = sampleCurve(RETRACT, time); + const intervals: [number, number][] = []; + if (time >= 34.24 && time < 36.04 && grow > 0) + intervals.push([0, grow * 0.5], [1 - grow * 0.5, 1]); + else if (time >= 36.04 && time < 38.84) + intervals.push([0.5 - remaining * 0.5, 0.5 + remaining * 0.5]); + const markers = + smooth((time - 34.2) / 0.12) * (1 - smooth((time - 37.76) / 0.56)); + return { + time, + intervals, + markers, + point: smooth((time - 38.58) / 0.2) * (1 - smooth((time - 39.08) / 0.22)), + label: smooth((time - 34.32) / 0.36) * (1 - smooth((time - 37.68) / 0.24)), + labelValue: smooth((time - 35.64) / 0.56), + clarity: sampleCurve(REVEAL, time), + phase: + time < 34.24 + ? "waiting" + : time < 36.04 + ? "joining" + : time < 37.72 + ? "connected" + : time < 38.84 + ? "retracting" + : time < DECRYPTION_END + ? "revealing" + : "clear", + }; +} +export type DecryptionFrame = ReturnType; + +export class DecryptionController { + clarity = 0; + private active = false; + private elapsed: number | null = null; + frame: DecryptionFrame = decryptionFrame(-1); + + enter(alreadyClear = false) { + if (this.active) return; + this.active = true; + this.elapsed = alreadyClear + ? (DECRYPTION_END - DECRYPTION_START) / INTERACTIVE_RATE + : null; + if (alreadyClear) this.finish(); + } + leave() { + this.active = false; + this.elapsed = null; + this.frame = decryptionFrame(-1); + } + select(clarity = 0) { + this.leave(); + this.clarity = clarity; + } + finish() { + this.elapsed = (DECRYPTION_END - DECRYPTION_START) / INTERACTIVE_RATE; + this.clarity = 1; + this.frame = decryptionFrame(DECRYPTION_END); + } + update(dt: number, ready: boolean, reduced: boolean, referenceTime?: number) { + if (referenceTime !== undefined) { + this.frame = decryptionFrame(referenceTime); + this.clarity = this.frame.clarity; + return; + } + if (!this.active) { + this.clarity = reduced + ? 0 + : this.clarity * Math.exp(-Math.max(0, dt) * 9); + if (this.clarity < 0.0001) this.clarity = 0; + this.frame = decryptionFrame(-1); + return; + } + if (reduced) { + this.finish(); + return; + } + if (this.elapsed === null && ready) this.elapsed = 0; + else if (this.elapsed !== null) + this.elapsed = Math.min( + this.elapsed + Math.max(0, dt), + (DECRYPTION_END - DECRYPTION_START) / INTERACTIVE_RATE, + ); + if (this.elapsed !== null) { + this.frame = decryptionFrame( + DECRYPTION_START + this.elapsed * INTERACTIVE_RATE, + ); + // Re-entry during refrosting starts from the displayed material state. + this.clarity = + this.frame.clarity > this.clarity + ? this.frame.clarity + : this.frame.phase === "clear" + ? 1 + : this.clarity * Math.exp(-Math.max(0, dt) * 9); + } + } +} diff --git a/src/document-decryption.css b/src/document-decryption.css new file mode 100644 index 0000000..7c6ee4b --- /dev/null +++ b/src/document-decryption.css @@ -0,0 +1,24 @@ +.document-redacted { + position: relative; +} + +.document-redaction-window { + position: absolute; + display: block; + overflow: hidden; + pointer-events: none; + user-select: none; + z-index: 1; +} + +.document-redaction-ink { + position: absolute; + inset: 0; + display: block; + background: #20221d; + will-change: transform; +} + +.reduce-motion .document-redaction-window { + display: none; +} diff --git a/src/document-decryption.ts b/src/document-decryption.ts new file mode 100644 index 0000000..96f22cd --- /dev/null +++ b/src/document-decryption.ts @@ -0,0 +1,103 @@ +import type { DecryptionFrame } from "./decryption"; + +type Cover = { window: HTMLElement; ink: HTMLElement; order: number }; + +/** Decorative redaction, driven by the physical archive's reveal cue. */ +export class DocumentDecryption { + private root: HTMLElement | null = null; + private covers: Cover[] = []; + private started: number | null = null; + private progress = 0; + + reset(root: HTMLElement, clear: boolean) { + this.remove(); + this.root = root; + this.started = null; + this.progress = clear ? 1 : 0; + this.refresh(); + } + + refresh() { + this.remove(); + if (!this.root || this.progress === 1) return; + // Measure text fragments, including wrapped lines, without splitting or + // replacing the actual text. Stage scaling cancels out in local coordinates. + const targets = this.root.querySelectorAll( + "h2, .detail-title-cn, .metadata dd, .tab-panel p, .research-notes li, .log-row", + ); + targets.forEach((target) => { + target.classList.add("document-redacted"); + const bounds = target.getBoundingClientRect(); + const scale = bounds.width / target.offsetWidth; + if (!scale || !Number.isFinite(scale)) return; + const walker = document.createTreeWalker(target, NodeFilter.SHOW_TEXT); + const lines: { x: number; y: number; right: number; bottom: number }[] = []; + let node: Node | null; + while ((node = walker.nextNode())) { + if (!node.textContent?.trim()) continue; + const range = document.createRange(); + range.selectNodeContents(node); + for (const rect of range.getClientRects()) { + if (!rect.width || !rect.height) continue; + const x = (rect.left - bounds.left) / scale; + const y = (rect.top - bounds.top) / scale; + const right = (rect.right - bounds.left) / scale; + const bottom = (rect.bottom - bounds.top) / scale; + const line = lines.find((entry) => Math.abs(entry.y - y) < 6); + if (line) { + line.x = Math.min(line.x, x); + line.y = Math.min(line.y, y); + line.right = Math.max(line.right, right); + line.bottom = Math.max(line.bottom, bottom); + } else lines.push({ x, y, right, bottom }); + } + } + for (const line of lines) { + const window = document.createElement("span"); + window.className = "document-redaction-window"; + window.setAttribute("aria-hidden", "true"); + const left = Math.max(0, line.x - 1); + const right = Math.min(target.clientWidth, line.right + 1); + window.style.cssText = `left:${left}px;top:${line.y - 1}px;width:${right - left}px;height:${line.bottom - line.y + 2}px`; + const ink = document.createElement("span"); + ink.className = "document-redaction-ink"; + window.append(ink); + target.append(window); + this.covers.push({ window, ink, order: this.covers.length }); + } + }); + this.paint(); + } + + update(now: number, frame: DecryptionFrame, reduced: boolean) { + if (!this.root || this.progress === 1) return; + if (reduced) this.progress = 1; + else { + // Keep covered through joining / holding / retraction. The text starts + // opening with the glass, and its easing tail lasts a little longer. + if (this.started === null && frame.clarity > 0) this.started = now; + if (this.started !== null) + this.progress = Math.min(1, Math.max(0, (now - this.started) / 0.95)); + } + if (this.progress === 1) this.remove(); + else if (this.started !== null) this.paint(); + } + + private paint() { + const count = Math.max(1, this.covers.length - 1); + for (const cover of this.covers) { + const delay = (cover.order / count) * 0.22; + const t = Math.min(1, Math.max(0, (this.progress - delay) / 0.78)); + // Brief acceleration, decisive departure, long deceleration; no bounce. + const eased = t < 0.2 + ? 0.4 * (t / 0.2) ** 2 + : 1 - 0.6 * ((1 - t) / 0.8) ** (16 / 3); + cover.ink.style.transform = `translateX(${eased * 101}%)`; + } + } + + private remove() { + for (const cover of this.covers) cover.window.remove(); + this.covers = []; + } +} diff --git a/src/fonts.css b/src/fonts.css new file mode 100644 index 0000000..e193f5d --- /dev/null +++ b/src/fonts.css @@ -0,0 +1,754 @@ +/* Generated by scripts/vendor-fonts.mjs from misans-webfont@4.3.1. */ +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/187.woff2") format("woff2");unicode-range:U+2C361,U+2C364,U+2C488,U+2C494,U+2C497,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+2C8D9,U+2C8DE,U+2C8E1,U+2C8F3,U+2C907,U+2C90A,U+2C91D,U+2CA02,U+2CA0E,U+2CA7D,U+2CAA9,U+2CB29,U+2CB2D-2CB2E,U+2CB31,U+2CB38-2CB39,U+2CB3B,U+2CB3F,U+2CB41,U+2CB4A,U+2CB4E,U+2CB5A-2CB5B,U+2CB64,U+2CB69,U+2CB6C,U+2CB6F,U+2CB73,U+2CB76,U+2CB78,U+2CB7C,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+2CE18,U+2CE1A,U+2CE23,U+2CE26,U+2CE2A,U+2CE7C,U+2CE88,U+2CE93} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/186.woff2") format("woff2");unicode-range:U+9FC6-9FEF,U+F92C,U+F979,U+F995,U+F9E7,U+F9F1,U+FA0C-FA0F,U+FA11,U+FA13-FA14,U+FA18,U+FA1F-FA21,U+FA23-FA24,U+FA27-FA29,U+FE10-FE19,U+FE30-FE31,U+FE33-FE44,U+FE49-FE52,U+FE54-FE57,U+FE59-FE66,U+FE68-FE6B,U+20087,U+20089,U+200CC,U+20164,U+20676,U+20CD0,U+2139A,U+21413,U+215D7,U+2298F,U+235CB,U+23C97-23C98,U+23E23,U+241FE,U+249DB,U+24A7D,U+24AC9,U+25532,U+25562,U+255A8,U+25ED7,U+26221,U+2648D,U+26676,U+2677C,U+26B5C,U+26C21,U+27FF9,U+28408,U+28678,U+28695,U+287E0,U+28B49,U+28C47,U+28C4F,U+28C51,U+28C54,U+28E99,U+29F7E,U+29F83,U+29F8C,U+2A7DD,U+2A8FB,U+2A917,U+2AA30,U+2AA36,U+2AA58,U+2AFA2,U+2B127-2B128,U+2B137-2B138,U+2B1ED,U+2B300,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+2B626-2B628,U+2B62A,U+2B62C,U+2B695-2B696,U+2B6AD,U+2B6ED,U+2B7A9,U+2B7C5,U+2B7E6,U+2B7F9,U+2B7FC,U+2B806,U+2B80A,U+2B81C,U+2B8B8,U+2BAC7,U+2BB5F,U+2BB62,U+2BB7C,U+2BB83,U+2BC1B,U+2BD77,U+2BD87,U+2BDF7,U+2BE29,U+2C029-2C02A,U+2C0A9,U+2C0CA,U+2C1D5,U+2C1D9,U+2C1F9,U+2C27C,U+2C288,U+2C2A4,U+2C317,U+2C35B} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/185.woff2") format("woff2");unicode-range: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-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+9ED2-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-9F16,U+9F18,U+9F1A-9F1F,U+9F21,U+9F23-9F2B,U+9F2D-9F2E,U+9F30-9F33,U+9F35-9F36,U+9F38,U+9F3A,U+9F3C,U+9F3F-9F43,U+9F45-9F49,U+9F4C-9F4D,U+9F53,U+9F55-9F58,U+9F5A-9F5B,U+9F5D-9F5E,U+9F62-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-9FC5} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/184.woff2") format("woff2");unicode-range:U+9CFA-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-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} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/183.woff2") format("woff2");unicode-range:U+9B7A-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-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-9CF9} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/182.woff2") format("woff2");unicode-range:U+99F5-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-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-9B07,U+9B09-9B0E,U+9B10-9B12,U+9B14-9B1E,U+9B20-9B21,U+9B24,U+9B26,U+9B28,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-9B79} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/181.woff2") format("woff2");unicode-range:U+983E-9845,U+9847-984B,U+9850-9852,U+9854-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-98B5,U+98B7-98BB,U+98BD-98C3,U+98C5,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-98F1,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-992D,U+992F-9930,U+9932,U+9934-9936,U+9938-993B,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-99F4} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/180.woff2") format("woff2");unicode-range:U+96C8,U+96CA,U+96D0-96D1,U+96D3-96D4,U+96D7-96D8,U+96DA,U+96DD,U+96DF,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-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-97AA,U+97AC,U+97AE,U+97B0-97B1,U+97B3,U+97B5-97BC,U+97BE-97C2,U+97C4-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-983D} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/179.woff2") format("woff2");unicode-range:U+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+95A4,U+95A6-95A7,U+95AA,U+95AE-95B0,U+95B2-95B5,U+95B7-95B8,U+95BA,U+95C0-95C2,U+95C4-95C7,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-96BB,U+96BF,U+96C2-96C3} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/178.woff2") format("woff2");unicode-range:U+92D8-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-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-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} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/177.woff2") format("woff2");unicode-range:U+913F-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-91E6,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-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-92D7} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/176.woff2") format("woff2");unicode-range: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+8FF4-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-902C,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+905D,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-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-913E} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/175.woff2") format("woff2");unicode-range: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+8E30,U+8E32-8E34,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+8F13,U+8F16-8F1A,U+8F20-8F24,U+8F27-8F28,U+8F2B-8F2E,U+8F30-8F32,U+8F34-8F37,U+8F39-8F3A} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/174.woff2") format("woff2");unicode-range:U+8B7A-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+8C8D-8C92,U+8C95-8C97,U+8C99-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,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} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/173.woff2") format("woff2");unicode-range: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+8A3B-8A3D,U+8A3F-8A40,U+8A42-8A45,U+8A47,U+8A49-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-8A8C,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-8B65,U+8B67-8B6A,U+8B6D-8B6E,U+8B71-8B73,U+8B75-8B76,U+8B78-8B79} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/172.woff2") format("woff2");unicode-range:U+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+88FD,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-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-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-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} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/171.woff2") format("woff2");unicode-range:U+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-883F,U+8841-8843,U+8847-884B,U+884E-8852,U+8855-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-889A} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/170.woff2") format("woff2");unicode-range: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-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+8716,U+8719,U+871B,U+871D,U+871F-8720,U+8724,U+8726-8728,U+872A-872C} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/169.woff2") format("woff2");unicode-range:U+844D-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,U+848D,U+848F-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-84C8,U+84CC,U+84CE-84CF,U+84D2,U+84D4-84D5,U+84D7-84DC,U+84DE,U+84E1-84E2,U+84E4,U+84E7-84EB,U+84ED,U+84F1-84F3,U+84F5-84FB,U+84FE,U+8500-850B,U+850D-8510,U+8512,U+8514-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+8553-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-8593,U+8595-859A,U+859D-859E,U+85A0-85A3,U+85A5,U+85A7,U+85AB-85AD,U+85B1-85B6,U+85B8} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/168.woff2") format("woff2");unicode-range: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-83D3,U+83D5,U+83D7,U+83D9-83DB,U+83DE,U+83E2-83E4,U+83E6-83E8,U+83EB-83EE,U+83F3-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-844C} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/167.woff2") format("woff2");unicode-range:U+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-812A,U+812D-812E,U+8130,U+8133-8135,U+8137,U+813A-813D,U+813F-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+8181,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-81B2,U+81B4-81B9,U+81BC,U+81C4-81C5,U+81C7-81C8,U+81CB,U+81CE,U+81D0-81D7,U+81D9,U+81DB-81DF,U+81E1-81E2,U+81E4,U+81E6,U+81E9,U+81EB,U+81EE-81F2,U+81F5-81F9,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-8278,U+827B-827C,U+8280-8281,U+8283,U+8285-8287} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/166.woff2") format("woff2");unicode-range: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-7FED,U+7FEF,U+7FF2,U+7FF4-7FF8,U+7FFA,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-80D4} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/165.woff2") format("woff2");unicode-range: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-7D92,U+7D94-7D9B,U+7D9D,U+7D9F,U+7DA1,U+7DA4-7DA5,U+7DA7-7DAB,U+7DAF,U+7DB3,U+7DB5-7DB7,U+7DBC,U+7DC0-7DC3,U+7DC5-7DC6,U+7DC8-7DC9,U+7DCC-7DD1,U+7DD3-7DD6,U+7DDB-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-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+7E48-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,U+7E8B,U+7E8D-7E8E,U+7E90-7E92,U+7E94-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} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/164.woff2") format("woff2");unicode-range: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-7C15,U+7C17-7C1D,U+7C20,U+7C22,U+7C24-7C25,U+7C28-7C29,U+7C2C-7C37,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-7C71,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,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-7D2E,U+7D34-7D38,U+7D3B,U+7D3D-7D3E,U+7D41,U+7D43,U+7D45,U+7D47-7D4D} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/163.woff2") format("woff2");unicode-range: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-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} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/162.woff2") format("woff2");unicode-range:U+7918,U+791A-791D,U+791F-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-79A6,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-7A1D,U+7A21-7A22,U+7A24-7A2D,U+7A2F-7A30,U+7A32,U+7A34-7A36,U+7A38,U+7A3A,U+7A3E,U+7A40-7A45,U+7A47-7A4B,U+7A4F-7A50,U+7A52-7A56,U+7A58-7A60,U+7A63-7A68,U+7A6A-7A6C} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/161.woff2") format("woff2");unicode-range:U+77B7-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-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,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-7917} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/160.woff2") format("woff2");unicode-range:U+7641,U+7644-7645,U+7648,U+764A-764B,U+764E-7653,U+7655,U+7657,U+7659-765B,U+765D,U+7660,U+7665-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+76C3-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-7700,U+7702-7703,U+7705-7706,U+770A,U+770C,U+770E-7718,U+771B-771E,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-774F,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-778B,U+778F-7790,U+7793-7797,U+7799-779D,U+77A1,U+77A3-77A4,U+77A6,U+77A8,U+77AB,U+77AD-77AF,U+77B1-77B2,U+77B4,U+77B6} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/159.woff2") format("woff2");unicode-range:U+74B1-74B9,U+74BB-74BC,U+74BE-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-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-75E1,U+75E5,U+75E9,U+75EC-75EF,U+75F2-75F3,U+75F5-75F8,U+75FA-75FB,U+75FD,U+7604,U+7606-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} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/158.woff2") format("woff2");unicode-range:U+7366-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-739E,U+73A0-73A1,U+73A3-73A8,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-744A,U+744C-7454,U+7456,U+7458,U+745D,U+7460-7462,U+7465-7468,U+746B-746C,U+746E-746F,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} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/157.woff2") format("woff2");unicode-range: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+7260,U+7263-7265,U+7268,U+726A-726D,U+7270-7271,U+7273-7274,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-7343,U+7346-734C,U+734F,U+7351,U+7353-7356,U+7358-735F,U+7361-7365} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/156.woff2") format("woff2");unicode-range: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+7146-7148,U+714B,U+714D,U+714F-7151,U+7153-7158,U+715A-715B,U+715D,U+715F-7161,U+7163,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} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/155.woff2") format("woff2");unicode-range: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-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} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/154.woff2") format("woff2");unicode-range:U+6E01-6E04,U+6E06-6E09,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-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-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-6F6C,U+6F71} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/153.woff2") format("woff2");unicode-range:U+6C51-6C53,U+6C56,U+6C58-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-6CDD,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-6D29,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+6DE9,U+6DED,U+6DEF-6DF0,U+6DF2,U+6DF4,U+6DF8,U+6DFD-6E00} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/152.woff2") format("woff2");unicode-range:U+6AE3-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-6BAD,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+6C39-6C3C,U+6C3E-6C3F,U+6C43-6C45,U+6C48,U+6C4B-6C4F} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/151.woff2") format("woff2");unicode-range:U+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-69EC,U+69EE-69F1,U+69F4-69FC,U+69FE,U+6A00,U+6A03-6A04,U+6A06-6A09,U+6A0B-6A12,U+6A14-6A16,U+6A1A-6A1D,U+6A20,U+6A22,U+6A24-6A27,U+6A29,U+6A2B-6A2E,U+6A30,U+6A32-6A34,U+6A36-6A37,U+6A3B-6A3C,U+6A3F-6A43,U+6A45-6A46,U+6A49-6A4A,U+6A4C-6A4F,U+6A51-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-6AC2,U+6AC4-6AD2,U+6AD4-6AD9,U+6ADC,U+6AE0-6AE2} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/150.woff2") format("woff2");unicode-range:U+687F-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+68CA,U+68CC,U+68CE-68D1,U+68D3-68D4,U+68D9,U+68DB-68DE,U+68E1-68E2,U+68E4-68E6,U+68E8-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-69B9} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/149.woff2") format("woff2");unicode-range: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+6736-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-67FC,U+67FE,U+6801-6804,U+6806,U+680D,U+6810,U+6812,U+6814-6815,U+6818-681C,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} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/148.woff2") format("woff2");unicode-range:U+65AE,U+65B1-65B6,U+65B8,U+65BA-65BB,U+65BE-65C0,U+65C2,U+65C7-65CA,U+65CD,U+65D0-65D1,U+65D3-65D5,U+65D8-65DF,U+65E1,U+65E3-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} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/147.woff2") format("woff2");unicode-range:U+6433,U+6435,U+6437-6439,U+643B-643C,U+643E,U+6440,U+6442-6443,U+6449,U+644B-6450,U+6453,U+6455-6457,U+6459-645B,U+645D,U+6460-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+649A-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+64E1,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,U+653A,U+653C-653D,U+6540-6544,U+6546-6547,U+654A-654B,U+654D-654E,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} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/146.woff2") format("woff2");unicode-range: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+62DD-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-6331,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+6368,U+636A,U+636C,U+636F-6370,U+6372-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+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+63F9-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-6425,U+6427-6429,U+642B,U+642E-6432} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/145.woff2") format("woff2");unicode-range:U+6130-6133,U+6135-6136,U+6138-613D,U+6140-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-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,U+6235,U+6238-623C,U+6242,U+6244-6246,U+624A,U+624F-6250,U+6255-6257,U+6259-625A,U+625C-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} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/144.woff2") format("woff2");unicode-range:U+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-612F} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/143.woff2") format("woff2");unicode-range:U+5E21-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+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+5F14,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+5F5E-5F60,U+5F63,U+5F67-5F68,U+5F6B,U+5F6E-5F6F,U+5F72,U+5F74-5F76,U+5F78,U+5F7A,U+5F7D-5F7F,U+5F83,U+5F86,U+5F8D-5F8F,U+5F93-5F94,U+5F96,U+5F9A-5F9B,U+5F9D,U+5F9F,U+5FA2-5FA6} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/142.woff2") format("woff2");unicode-range:U+5CF3,U+5CF5,U+5CF7-5CFA,U+5CFC,U+5CFE-5D01,U+5D04-5D05,U+5D08-5D0C,U+5D0F-5D13,U+5D15,U+5D18-5D1A,U+5D1C-5D1D,U+5D1F-5D21,U+5D23,U+5D25,U+5D28,U+5D2A-5D2B,U+5D2F-5D33,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-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+5DE3-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-5E20} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/141.woff2") format("woff2");unicode-range:U+5B61,U+5B67-5B68,U+5B6D-5B6F,U+5B72,U+5B74,U+5B76-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-5C5B,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-5CE0,U+5CE2-5CE3,U+5CE7,U+5CE9,U+5CEB-5CEC,U+5CEE,U+5CF1-5CF2} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/140.woff2") format("woff2");unicode-range:U+5A4D-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-5B4B,U+5B4D-5B4F,U+5B52,U+5B56,U+5B5E,U+5B60} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/139.woff2") format("woff2");unicode-range:U+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+59B3-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+59E6-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,U+5A4B-5A4C} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/138.woff2") format("woff2");unicode-range:U+57C6-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-5856,U+5859-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} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/137.woff2") format("woff2");unicode-range:U+5677,U+5679-567A,U+567D-567F,U+5681-5684,U+5688-568B,U+568D,U+5690-5692,U+5694,U+5696-56A2,U+56A4-56A5,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-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+5770-5772,U+5774-5775,U+5778-577A,U+577D-5781,U+5787-578A,U+578D-5791,U+5794-579A,U+579C-579F,U+57A5,U+57A8,U+57AA,U+57AC,U+57AF-57B1,U+57B3,U+57B5-57B7,U+57B9-57C1,U+57C4-57C5} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/136.woff2") format("woff2");unicode-range:U+54F9,U+54FB,U+54FE,U+5500,U+5502-5503,U+5505,U+5508,U+550A-550E,U+5512-5513,U+5515-551A,U+551C-551F,U+5521,U+5525-5526,U+5528-5529,U+552B,U+552D,U+5532,U+5534-5536,U+5538-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-5586,U+558C-558E,U+5590,U+5592-5593,U+5595-5597,U+559B,U+559E,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-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} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/135.woff2") format("woff2");unicode-range: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+5379,U+537C-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+5451,U+545A,U+545D-5461,U+5463,U+5465,U+5467,U+5469-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+54E2-54E4,U+54EB-54EC,U+54EF-54F1,U+54F4-54F8} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/134.woff2") format("woff2");unicode-range:U+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+524B,U+524F,U+5252-5253,U+5255,U+5257-525A,U+525F-5260,U+5262-5264,U+5266,U+5268,U+526B-526D,U+5270-5271,U+5273,U+5276-527C,U+527E,U+5280,U+5284-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} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/133.woff2") format("woff2");unicode-range:U+5063-5064,U+5066-506B,U+506D-5073,U+5078-507A,U+507C-507D,U+5081-5084,U+5086-5087,U+5089-508C,U+508E-5090,U+5092-5095,U+5097,U+509A-50A2,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-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+5101,U+5103,U+5105,U+510A,U+510C-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+516A,U+516F,U+5172,U+517A,U+517E-517F,U+5183-5184,U+5186-5187,U+518B,U+518E-5191,U+5193-5194,U+5198,U+519A,U+519D-519F,U+51A1,U+51A3,U+51A6-51A9,U+51AD} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/132.woff2") format("woff2");unicode-range:U+4EFE,U+4F00,U+4F02-4F08,U+4F0B-4F0C,U+4F12-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+4F48-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+4FB7-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-4FEC,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-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-5062} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/131.woff2") format("woff2");unicode-range:U+4D5C-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} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/130.woff2") format("woff2");unicode-range:U+4CA0-4D5B} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/129.woff2") format("woff2");unicode-range:U+4BE5-4C9F} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/128.woff2") format("woff2");unicode-range:U+4B2B-4BE4} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/127.woff2") format("woff2");unicode-range:U+4A72-4B2A} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/126.woff2") format("woff2");unicode-range:U+49BB-4A71} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/125.woff2") format("woff2");unicode-range:U+4905-49BA} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/124.woff2") format("woff2");unicode-range:U+4850-4904} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/123.woff2") format("woff2");unicode-range:U+479D-484F} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/122.woff2") format("woff2");unicode-range:U+46EB-479C} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/121.woff2") format("woff2");unicode-range:U+463B-46EA} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/120.woff2") format("woff2");unicode-range:U+458C-463A} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/119.woff2") format("woff2");unicode-range:U+44DF-458B} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/118.woff2") format("woff2");unicode-range:U+4433-44DE} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/117.woff2") format("woff2");unicode-range:U+4389-4432} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/116.woff2") format("woff2");unicode-range:U+42E0-4388} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/115.woff2") format("woff2");unicode-range:U+4239-42DF} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/114.woff2") format("woff2");unicode-range:U+4193-4238} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/113.woff2") format("woff2");unicode-range:U+40EF-4192} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/112.woff2") format("woff2");unicode-range:U+404D-40EE} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/111.woff2") format("woff2");unicode-range:U+3FAC-404C} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/110.woff2") format("woff2");unicode-range:U+3F0D-3FAB} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/109.woff2") format("woff2");unicode-range:U+3E70-3F0C} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/108.woff2") format("woff2");unicode-range:U+3DD5-3E6F} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/107.woff2") format("woff2");unicode-range:U+3D3C-3DD4} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/106.woff2") format("woff2");unicode-range:U+3CA4-3D3B} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/105.woff2") format("woff2");unicode-range:U+3C0E-3CA3} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/104.woff2") format("woff2");unicode-range:U+3B7A-3C0D} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/103.woff2") format("woff2");unicode-range:U+3AE8-3B79} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/102.woff2") format("woff2");unicode-range:U+3A59-3AE7} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/101.woff2") format("woff2");unicode-range:U+39CC-3A58} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/100.woff2") format("woff2");unicode-range:U+3941-39CB} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/99.woff2") format("woff2");unicode-range:U+38B8-3940} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/98.woff2") format("woff2");unicode-range:U+3832-38B7} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/97.woff2") format("woff2");unicode-range:U+37AE-3831} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/96.woff2") format("woff2");unicode-range:U+372D-37AD} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/95.woff2") format("woff2");unicode-range:U+36AF-372C} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/94.woff2") format("woff2");unicode-range:U+3634-36AE} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/93.woff2") format("woff2");unicode-range:U+35BC-3633} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/92.woff2") format("woff2");unicode-range:U+3547-35BB} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/91.woff2") format("woff2");unicode-range:U+34D5-3546} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/90.woff2") format("woff2");unicode-range:U+3467-34D4} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/89.woff2") format("woff2");unicode-range:U+33CE,U+33D1-33D2,U+33D5,U+3400-3466} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/88.woff2") format("woff2");unicode-range:U+2EB7,U+2EBB,U+2ECA,U+2FF0-2FFB,U+3000,U+3003,U+3005-3009,U+300C-3017,U+301D-301E,U+3021-3029,U+303E,U+3105-3129,U+3220-3229,U+3231,U+32A3,U+338E-338F,U+339C-339E,U+33A1,U+33C4} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/87.woff2") format("woff2");unicode-range:U+255E-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+2713,U+2715,U+275B-275E,U+2780-2793,U+2E81,U+2E84,U+2E88,U+2E8B-2E8C,U+2E97,U+2EA7,U+2EAA,U+2EAE,U+2EB3,U+2EB6} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/86.woff2") format("woff2");unicode-range:U+2496-249B,U+2501,U+2503-251B,U+251D-254B,U+2550-255D} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/85.woff2") format("woff2");unicode-range:U+2199,U+21C4,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+223C-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-2495} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/84.woff2") format("woff2");unicode-range:U+207A-207B,U+207F,U+2081,U+20A3-20A4,U+20A9,U+20AB-20AC,U+20AE,U+20B1,U+20B8-20BA,U+20BD,U+20BF,U+2103,U+2105,U+2109,U+2113-2114,U+2116-2117,U+211E,U+2120-2122,U+2126,U+212E,U+2153-2154,U+215B-215E,U+2160-2179,U+2180-2182,U+2190-2193,U+2196-2198} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/83.woff2") format("woff2");unicode-range:U+2004-2006,U+2009-200A,U+2010,U+2015-2016,U+201A,U+201E,U+2020-2022,U+2025,U+2028-2029,U+2030-2037,U+2039-203C,U+203E,U+2042-2043,U+2047-2049,U+204E,U+2051,U+2055,U+2060-2064,U+2066-2069,U+2071} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/82.woff2") format("woff2");unicode-range:U+300-304,U+306-30C,U+312,U+323,U+326-328,U+1D43,U+1D47-1D49,U+1D4D,U+1D4F-1D50,U+1D52,U+1D56-1D58,U+1D5B,U+1D9C,U+1DA0,U+1DBB} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/81.woff2") format("woff2");unicode-range:U+FF3C-FF5B,U+FF5D,U+FFE0-FFE5} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/80.woff2") format("woff2");unicode-range:U+FF02-FF07,U+FF0A-FF0B,U+FF0E-FF19,U+FF1C-FF1E,U+FF20-FF3B} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/79.woff2") format("woff2");unicode-range:U+E3F} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/78.woff2") format("woff2");unicode-range:U+30EC-30FA,U+30FC-30FE} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/77.woff2") format("woff2");unicode-range:U+308A-3094,U+3099-309E,U+30A1-30EB} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/76.woff2") format("woff2");unicode-range:U+3041-3089} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/75.woff2") format("woff2");unicode-range:U+5244,U+5666,U+568C,U+58DA,U+5C68,U+5E58,U+5F33,U+61CD,U+6214,U+6516,U+69E7,U+6BFF,U+7020,U+7044,U+7798,U+78FD,U+7E6F-7E70,U+802E,U+8079,U+8183,U+8464,U+861E,U+8811,U+8A92,U+8B56,U+8CB0,U+8CD5,U+8CE7,U+8E93,U+8EA6,U+8EE4,U+8F62,U+9233,U+92DD,U+9301,U+9307,U+9346,U+9370,U+93AA,U+93F7,U+940B,U+9412,U+9426,U+958C,U+95AB,U+95BD,U+95BF,U+986C,U+9A2D,U+9A38,U+9B9A,U+9BEB,U+9BF4,U+9C12,U+9C20,U+9C35,U+9C37,U+9C5D,U+9D1D,U+9DA5,U+9F60} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/74.woff2") format("woff2");unicode-range:U+50E8,U+532D,U+53B4,U+54BC,U+5672,U+56C5,U+5852,U+5B24,U+5B4C,U+5D2C,U+5DF9,U+5E6C,U+5F12,U+649F,U+6504,U+68D6,U+69BF,U+6AEB,U+6AF3,U+6E5E,U+736B,U+7380,U+7649,U+79B0,U+7A6D,U+7AB6,U+7C5C,U+7D06,U+7D32,U+7DE6,U+7DF6,U+7DF9,U+7E0B,U+7E36,U+7E52,U+7E62,U+7F8B,U+8156,U+8264,U+826B,U+8526,U+8546,U+8558,U+8577,U+85F6,U+87EF,U+8806,U+89AF,U+89BF,U+8A01,U+8A75,U+8A84,U+8AD7,U+8B7E,U+8B9E,U+8DB2,U+8E60,U+8EFA,U+8F05,U+8F07,U+91D9,U+923D,U+9255,U+929A,U+92B1,U+92DF,U+92E3,U+92E6,U+92F6,U+9306,U+9308,U+9364,U+9369,U+9384,U+93BF,U+93C7,U+9414,U+9428,U+9479,U+95E5,U+9689,U+97BD,U+97DD,U+9859,U+98EB,U+9937,U+993C,U+99D4,U+99D8,U+9A0D,U+9A36,U+9A42,U+9B9E,U+9BAA,U+9BDD,U+9C08,U+9C29,U+9C3E,U+9D1F,U+9D2F,U+9D42,U+9D6F,U+9D93,U+9DE5,U+9E0C,U+9E7A,U+9EF2,U+9F09} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/73.woff2") format("woff2");unicode-range:U+50B4,U+5110,U+51AA,U+528C,U+52F1,U+56B3,U+5969,U+5ABC,U+6192,U+61AE,U+61DF,U+6207,U+6451,U+69AA,U+6A48,U+6AE7,U+6F59,U+6F77,U+6FA9,U+6FFC,U+700B,U+7027,U+7197,U+736A,U+7602,U+761E,U+7667,U+76B8,U+7C69-7C6A,U+7CDD,U+7CF4,U+7CF6,U+7D07,U+7D86,U+7DC4,U+7DD9,U+7E11,U+7E2D,U+7E32,U+7E8A,U+7F86,U+8161,U+81DA,U+81E0,U+8452,U+851E,U+8552,U+8562,U+859F,U+86FA,U+87E3,U+8949,U+89A1,U+89F6,U+8AD1,U+8B6B,U+8B8E,U+8D10,U+8EF9,U+8F0A,U+91C3,U+9201,U+9225,U+92A9,U+92CF,U+9312,U+931F,U+9343,U+9387,U+93F9,U+9420,U+944A,U+9465,U+980A,U+980F,U+9862,U+9931,U+9933,U+994A,U+995C,U+9A4F,U+9B83,U+9B92,U+9BC1,U+9C39,U+9C5F,U+9C67,U+9C6D,U+9C7A,U+9D30,U+9D53,U+9D5C,U+9D87,U+9D98,U+9DBC,U+9DD9-9DDA,U+9DE6,U+9DEF,U+9ECC,U+9EF7,U+9F34,U+9F4E-9F4F,U+9F54,U+9F59} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/72.woff2") format("woff2");unicode-range:U+50C9,U+5274,U+5635,U+5638,U+56A6,U+5B00,U+5B19,U+5B2A,U+5D81,U+5DF0,U+5E43,U+5EE1,U+6128,U+616A,U+6227,U+651B,U+6898,U+6ADE,U+6AE8,U+6C0C,U+7026,U+733B,U+74A6,U+74D4,U+766D,U+7725,U+7868,U+78B8,U+7BCB,U+7CF2,U+7CF9,U+7D83,U+7D88,U+7DB0,U+7DC7,U+7DE1,U+7DF1,U+84EF,U+84F4,U+84FD,U+863A,U+8810,U+889E,U+8933,U+895D,U+8A4E,U+8A58,U+8A7F,U+8A86,U+8A91,U+8AC2,U+8ACF,U+8ADE,U+8AE1,U+8AFC,U+8B92,U+8B96,U+8CB2,U+8CFE,U+8E92,U+8EDB,U+8F1C,U+8F61,U+90DF,U+9227,U+9230,U+9248,U+927F,U+9283,U+92A0,U+92A3,U+92A6,U+92AA,U+92F1,U+9338,U+9340,U+93CD,U+93DD,U+9413,U+943F,U+944C,U+9454,U+946D,U+95BC,U+95C8,U+979D,U+97C9,U+9807,U+9826,U+98AE,U+98BC,U+98E0,U+9A05,U+9AD5,U+9B4E,U+9BC0,U+9BC7,U+9BD7,U+9BE1,U+9BEA,U+9C23,U+9C28,U+9C33,U+9D52,U+9DF3,U+9F5C} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/71.woff2") format("woff2");unicode-range:U+5000,U+5114-5115,U+526E,U+5399,U+53C4,U+55F6,U+5665,U+566F,U+581D,U+584F,U+58E2,U+6173,U+61E8,U+645C,U+6476,U+6894,U+6A89,U+6ADD,U+6AF8,U+6B0F,U+6B5F,U+6B7F,U+6FAE,U+7341,U+7658,U+7670,U+7906,U+7C00,U+7C1E,U+7D3C,U+7D4E,U+7D5D,U+7D9E,U+7DA3,U+7E35,U+7E7E,U+7E98,U+802C,U+8407,U+8494,U+8541,U+8555,U+87BB,U+8A6C,U+8A9A,U+8AE2,U+8B05,U+8B59,U+8B9C,U+8CBA,U+8CFB,U+8D04,U+8E55,U+8E91,U+8F1E,U+8F54,U+9134,U+9136,U+91D3-91D5,U+91E4,U+91F7,U+9204,U+9266,U+926D,U+92E8,U+92EE,U+931B,U+93A6,U+93A9,U+93C3,U+93CC,U+93E8,U+9403,U+9419,U+9582,U+95C3,U+95CB,U+95D0,U+984E,U+9853,U+9873,U+9908,U+99D1,U+99D9,U+9A43-9A44,U+9B29,U+9B2E,U+9B74,U+9B90,U+9BD6,U+9BFF,U+9C13,U+9C45,U+9D07,U+9DC0,U+9DC2,U+9DF8,U+9EFD,U+9EFF,U+9F76} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/70.woff2") format("woff2");unicode-range:U+4FC1,U+5096,U+5108,U+565D,U+56B6,U+56C0-56C1,U+56C8,U+5816,U+58D9,U+5B21,U+5D0D,U+5EE9,U+614D,U+64FC,U+6A05,U+6A81,U+6AA3,U+6AEC,U+6B1E,U+6BA4,U+6BAE,U+6DE5,U+6F35,U+6F7F,U+7032,U+7162,U+716C,U+743A,U+74DA,U+7646,U+7669,U+7864,U+78E7,U+7A61,U+7B67,U+7BF3,U+7D08,U+7D3A,U+7D3F,U+7D5B,U+7DB9,U+7E0A,U+7E1E-7E1F,U+7FB6,U+8435,U+85CE,U+85EA,U+861A,U+8784,U+87F6,U+8A0C,U+8A10,U+8AB6,U+8ADB,U+8AE4,U+8B04,U+8B16,U+8B95,U+8CB3,U+8E95,U+8ED4,U+8EEB,U+8F26,U+8F33,U+8F64,U+9015,U+9090,U+91C5,U+91E7,U+9200,U+9239,U+923F,U+927A,U+92AB,U+9319,U+9477,U+958E,U+95B6,U+95B9,U+9768,U+97D9,U+980E,U+9874,U+98ED,U+9949,U+994C,U+99E2,U+9ACF,U+9B58,U+9B8E,U+9BAB,U+9BE2,U+9C09,U+9C48,U+9CE7,U+9DBB,U+9E15,U+9E1D,U+9F66,U+9F6A,U+9F77} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/69.woff2") format("woff2");unicode-range:U+50AF,U+513B,U+528A,U+561C,U+5707,U+584B,U+58CE,U+5950,U+5AA7,U+5B0B,U+5CF4,U+5DA0,U+5EC4,U+613E,U+6229,U+64A3,U+64B3,U+64C4,U+66D6,U+690F,U+69E8,U+6A9C,U+6AD3,U+6AF1,U+6B9E,U+6C2C,U+6C33,U+7028,U+7043,U+7069,U+7296,U+7319,U+74A3,U+75FE,U+7664,U+76B0,U+77BC,U+78E3,U+7D02,U+7D13,U+7D15,U+7DBE,U+7E88,U+81CF,U+82BB,U+858C,U+8604,U+875F,U+87C8,U+8960,U+8998,U+89B2,U+89F4,U+8A15,U+8A41,U+8AA5,U+8B2B,U+8CDA,U+8E89,U+8E9A,U+8EA1,U+8EA5,U+8F42,U+9106,U+91F9,U+9210-9211,U+922E,U+9238,U+924D,U+925E,U+9278,U+92BC,U+92C3,U+9358,U+93A7,U+93B0,U+93B5,U+93D1,U+97C3,U+9865,U+993F,U+9948,U+9962,U+9A16,U+9A41,U+9BAD,U+9BDB,U+9BE4,U+9BE7,U+9C25,U+9C56,U+9C78,U+9CF6,U+9D06,U+9D9A,U+9DD3,U+9E1B,U+9F5F,U+9F67,U+9F6C} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/68.woff2") format("woff2");unicode-range:U+50C2,U+5137,U+57E1,U+58E0,U+5AD7,U+5B08,U+5DA7,U+5DCB,U+60F2,U+60FB,U+615F,U+616B,U+61CC,U+61E3,U+636B,U+64EF,U+689F,U+6ADF,U+6AEA,U+6F1A,U+71DC,U+71F4,U+7370,U+763B,U+7931,U+7A4C,U+7C23,U+7D31,U+7DCB,U+7DD7-7DD8,U+7DF2,U+7E39,U+7FA5,U+83A2,U+83A7,U+866F,U+8706,U+87C4,U+87CE,U+8814,U+8831,U+8938,U+893B,U+8964,U+89A6,U+89AC,U+8A03,U+8A16,U+8A46,U+8A52,U+8A5B,U+8A85,U+8ACD,U+8AED,U+8B14,U+8B3E,U+8B4E,U+8E4C,U+8EAA,U+8EF2,U+8EFB,U+9148,U+91F5,U+9245,U+9315,U+9347,U+93DC,U+9410,U+9427,U+9463,U+9481,U+95AC,U+95DE,U+9658,U+973D,U+9744,U+97DE,U+981C,U+9821,U+9837,U+9870,U+98B6,U+98F4,U+9909,U+9911,U+9A0F,U+9A2E,U+9A64,U+9BF0,U+9C0D,U+9C2D,U+9C31,U+9C52,U+9CE9,U+9D23,U+9D6A,U+9DF2,U+9EA9,U+9F95} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/67.woff2") format("woff2");unicode-range:U+513A,U+5504,U+55C7,U+560D,U+5660,U+5680,U+5695,U+56A8,U+580A,U+597C,U+5B7F,U+6134,U+61AB,U+61FA,U+64BB,U+6506,U+6523,U+66B1,U+66C7,U+6D79,U+6DF6,U+6FA0,U+7005,U+71FC,U+7258,U+72FD,U+75D9,U+765F,U+78AD,U+79C8,U+7A1F,U+7C2B,U+7C5F,U+7C6E,U+7CC9,U+7D40,U+7DAC,U+7E09,U+7E10,U+8075,U+8594,U+85BA,U+8768,U+87A2,U+8823,U+89B7,U+8A1B,U+8A36,U+8A54,U+8A61,U+8A70,U+8AEB,U+8AF6,U+8CC5,U+8CE1,U+8D17,U+8EFE,U+91D2,U+91E9,U+9249,U+9262,U+92BB,U+92C7,U+92E5,U+92EF-92F0,U+9341,U+9354,U+9376,U+937A,U+9398,U+93A3,U+93E4,U+93F5,U+943A,U+9460,U+947E,U+95A1,U+95AD,U+95D4,U+9742,U+975A,U+9766,U+98AF,U+98C6,U+98C8,U+98E9,U+991B,U+991E,U+9A4D,U+9AD6,U+9B81,U+9BCA,U+9BFD,U+9D61,U+9D89,U+9E1A,U+9F72} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/66.woff2") format("woff2");unicode-range:U+4F47,U+5102,U+5157,U+55E9,U+562E,U+5630,U+5879,U+5ACB,U+5AF5,U+5B38,U+5C5C,U+5D97,U+5DB8,U+5EDD,U+60B5,U+611C,U+619A,U+6384,U+64FB,U+66C4,U+6A62,U+6ADA,U+6B12,U+6BAB,U+6ECE,U+6F6F,U+705D,U+70F4,U+71C9,U+71FE,U+729B,U+72A2,U+7489,U+750C,U+7515,U+766C,U+769A,U+792B-792C,U+7B8B,U+7C0D,U+7D73,U+7E1D,U+7E6E,U+7F4C,U+81BE,U+8477,U+849E,U+84C0,U+854E,U+85FA,U+8622,U+865C,U+86FB,U+8778,U+8956,U+8A6D,U+8B01,U+8B10,U+8B28,U+8CC1,U+8D05,U+8E7A,U+8F45-8F46,U+8FAE,U+913A,U+91C1,U+91FA,U+9208,U+920D,U+9251,U+925A,U+926C,U+9291,U+92A5,U+92CC,U+942E,U+9432,U+9586,U+958F,U+95CC,U+95D5,U+9830,U+9839,U+9957,U+995E,U+99DF,U+99ED,U+9A2B,U+9A3E,U+9AAF,U+9C54,U+9D1B,U+9D26,U+9D60,U+9DA9} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/65.woff2") format("woff2");unicode-range:U+4E99,U+50E5,U+513C,U+55C6,U+5616,U+5653,U+56EA,U+59CD,U+5D22,U+5D87,U+6137,U+615A,U+645F,U+6473,U+64F7,U+6519,U+6595,U+67F5,U+6AB3,U+6AB8,U+6ABB,U+6EF7-6EF8,U+6F41,U+6F97,U+6FD8,U+6FFA,U+7377,U+737A,U+737C,U+744B,U+74BD,U+74CF,U+7627,U+792A,U+79B1,U+7D46,U+7E43,U+7E45,U+7E93,U+7F88,U+811B,U+81BF,U+81CD,U+8396,U+858A,U+880D,U+8A25,U+8A6B,U+8A6E,U+8A7C,U+8AB9,U+8AC9,U+8AFA,U+8B17,U+8B2C,U+8B4F,U+8CD2,U+8D16,U+8E63,U+8E8A,U+8EFC,U+8F25,U+9114,U+9116,U+9183,U+9226,U+9237,U+923E,U+927B,U+9296,U+92AC,U+9310,U+932E,U+93A2,U+93D7-93D8,U+93DE,U+93E2,U+990C,U+9912,U+993E,U+9943,U+99B1,U+9A40,U+9A5B,U+9A6A,U+9B22,U+9B77,U+9BC9,U+9C58,U+9D15,U+9D51,U+9DFA,U+9E1E} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/64.woff2") format("woff2");unicode-range:U+55B2,U+55DA,U+58AE,U+5A6D,U+5AFA,U+5BE2,U+5DD2,U+5DD4,U+5FA0,U+60BD,U+618A,U+633E,U+64F0,U+6582-6583,U+6727,U+69F3,U+6ADB,U+6AE5,U+6C08,U+6D87,U+6E19,U+6E26,U+6F2C,U+7006,U+7018,U+71BE,U+743F,U+760D,U+7647,U+7662,U+7672,U+76DE,U+798E,U+79BF,U+7ABA,U+7BE4,U+7C6C,U+7D1C,U+7D33,U+7D79,U+7DBB,U+7DBD,U+7DDE,U+7E08,U+7E55,U+82E7,U+85F9,U+87EC,U+8805,U+8A63,U+8AA3,U+8AA8,U+8AC4,U+8ADC,U+8AE6,U+8AF3,U+8B33,U+8CD1,U+8CE4,U+8D0D,U+8F12,U+8F3E,U+923A,U+9257,U+9293,U+92A8,U+92E4,U+9333,U+9365,U+936C,U+93AC,U+93B3,U+942B,U+9433,U+9594,U+95A8,U+9695,U+96CB,U+9824,U+98EA,U+9903,U+9921,U+99B4,U+9A01,U+9A62,U+9A65,U+9BE8,U+9C32,U+9C77,U+9D72,U+9DAF,U+9DD7} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/63.woff2") format("woff2");unicode-range:U+4FB6,U+51DC,U+52FB,U+5331,U+5436,U+5614,U+562F,U+56C2,U+5862,U+5A41,U+5B0C,U+5C37,U+5E57,U+61FE,U+63C0,U+6417,U+64BF,U+64F2,U+6522,U+652A,U+65AC,U+66E0,U+6968,U+6A3A,U+6AFB,U+6B16,U+6BAF,U+6BB2,U+6D36,U+6DEA,U+6F23,U+6F80,U+6FC1,U+7009,U+7064,U+7152,U+71C1,U+71ED,U+7463,U+7469,U+7621,U+7681,U+76BA,U+77EF,U+786F,U+7A08,U+7AC4-7AC5,U+7AC7,U+7D09,U+7D5E,U+7D62,U+7DBA,U+7E5A,U+7E7D,U+7FF9,U+8123,U+81A9,U+8466,U+879E,U+883B,U+896A,U+8A1D,U+8AA6,U+8AF7,U+8B0A,U+8CBD,U+8CCA,U+8CDC,U+8F1F,U+8F4D,U+9087,U+9209,U+92F8,U+9328,U+9382,U+93DF,U+9438,U+97DC,U+9871,U+991A,U+9945,U+99AD,U+99D2,U+99FF,U+9A4A,U+9BD4,U+9C3B,U+9C57,U+9D09,U+9F9C} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/62.woff2") format("woff2");unicode-range:U+4FE0,U+50AD,U+53AD,U+555E,U+589C,U+58B3,U+5DBC,U+6065,U+6190,U+61F6,U+61FC,U+6493,U+64BE,U+64F1,U+650F,U+6688-6689,U+66EC,U+68F2,U+6BC6,U+6C2B,U+6FF0,U+701D,U+7192,U+71D9,U+71FB,U+720D,U+7587,U+760B,U+7613,U+7661,U+766E,U+775C,U+779E,U+78EF,U+79AA,U+7A62,U+7AAA,U+7B8F,U+7C3E,U+7CDE,U+7D8F,U+7DDD,U+7DEF,U+7E37,U+7E46,U+7E6D,U+7E79,U+8073,U+807E,U+8588,U+863F,U+87FB,U+881F,U+8993,U+8A23,U+8A60,U+8AA1,U+8AF1,U+8B20,U+8B41,U+8B74,U+8C4E,U+8C9E,U+8EC0,U+9132,U+919E,U+91D7-91D8,U+9223,U+9240,U+92C5,U+9318,U+931A,U+9320,U+934D,U+93FD,U+9472,U+947F,U+95A5,U+96B4,U+96DB,U+97CC,U+985B,U+986B,U+99DD,U+99F1,U+9C49,U+9D3F} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/61.woff2") format("woff2");unicode-range:U+4F96,U+5098,U+50D5,U+524E,U+5687,U+56D1,U+582F,U+588A,U+58D8,U+58FA,U+5BF5,U+5C4D,U+5EC2,U+5EDA,U+5EEC,U+60B6,U+60F1,U+61C7,U+646F,U+647B,U+64CB,U+665D,U+6953,U+69D3,U+6A01,U+6DF5,U+6E4A,U+6ECC,U+6F86-6F87,U+7015,U+701F,U+703E,U+72F9,U+7378,U+7464,U+7671,U+797F,U+798D,U+7AC8,U+7B4D,U+7DB8,U+7E1B,U+7E8F,U+7F75,U+7FA8,U+814E,U+81D8,U+8259,U+834A,U+8525,U+852D,U+856A,U+8606,U+8766,U+8AD2,U+8AE7,U+8B0E,U+8C48,U+8CAF,U+8CB6,U+8CC2,U+8D13,U+8E8B,U+8ED2,U+919C,U+91AC,U+921E,U+925B,U+938A,U+947C,U+9598,U+95A9,U+95BB,U+96B8,U+980C,U+98E2,U+9913,U+9952,U+99C1,U+9A37,U+9B91,U+9D5D,U+9DF9,U+9EF4,U+9F4B,U+9F52} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/60.woff2") format("woff2");unicode-range:U+5006,U+5109,U+5147,U+525D,U+52F3,U+58DF,U+599D,U+5B30,U+5CA1,U+5D84,U+5E25,U+5E5F,U+5EC1,U+5F4E,U+5F65,U+6085,U+6158,U+61A4,U+6200,U+6488,U+652C,U+68D7,U+68DF,U+6A1E,U+6BBC,U+6E3E,U+6EC4,U+6EF2,U+6F3F,U+6F51,U+6FB1,U+71E6,U+7344-7345,U+7375,U+775E,U+7AAF,U+7ACA,U+7C60,U+7CB5,U+7D0B,U+7D17,U+7DBF,U+7DEC,U+7E61,U+7E69,U+7F77,U+8070,U+8076,U+8178,U+819A,U+81E5,U+8332,U+84BC,U+860A,U+8755,U+8836,U+8932,U+896F,U+8A50,U+8A87,U+8B19,U+8B39,U+8B5A,U+8C93,U+8CED,U+8D1B,U+8ECB,U+91D0,U+91E3,U+9214,U+9298,U+95E1,U+9727,U+97CB,U+97FB,U+9811,U+9905,U+9A0E,U+9A55,U+9B31,U+9DB4,U+9E79,U+9E7C,U+9F94} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/59.woff2") format("woff2");unicode-range:U+5075,U+50F1,U+51CD,U+51F1,U+528D,U+52F8,U+53E2,U+5442,U+559A,U+55AA,U+5606,U+5674,U+5857,U+5875,U+58BE,U+58E9,U+5C62,U+5D50,U+5DBD,U+5EDF,U+5F4C,U+6046,U+61F8,U+6399,U+64AB,U+64B2,U+651C,U+6558,U+6DBC,U+6E6F,U+6EBC,U+6EEC,U+6FEB,U+7051,U+7165,U+721B,U+723A,U+72A7,U+7336,U+746A,U+74CA,U+77DA,U+78A9,U+78DA,U+7AA9,U+7D68,U+7DA2,U+7DE0,U+7E2B,U+7E9C,U+8105,U+8108,U+816B,U+8266,U+84EE,U+856D,U+860B,U+8A1F,U+8A98,U+8C54,U+8CC3-8CC4,U+8F3B,U+8F5F,U+8FAF,U+9059,U+91C0,U+9234,U+9264,U+929C,U+92B3,U+92C1,U+935B,U+9444,U+9583,U+95A3,U+9803,U+98C4,U+99B3,U+9AD2,U+9D28,U+9D3B,U+9F90} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/58.woff2") format("woff2");unicode-range:U+514C,U+53B2,U+58C7,U+58FD,U+5A1B,U+5ABD,U+5DBA,U+5DD6,U+61F2,U+6416,U+64E0,U+6575,U+69CD,U+6A38,U+6AC3,U+6B3D,U+6BC0,U+6DDA,U+6EC5,U+6EEF,U+6FC3,U+6FE4,U+6FF1,U+7058,U+7210,U+7246,U+727D,U+76DC,U+7919,U+7A4E,U+7C43,U+7C72,U+7D10,U+7DB1,U+7DE3,U+7E31,U+7E96,U+8056,U+8139,U+81A0,U+81C9,U+840A,U+8523,U+8569,U+8972,U+8A3A,U+8ABC,U+8AEE,U+8CA9-8CAA,U+8CC8,U+8CDE,U+8CE2,U+8CFA,U+8D08,U+8D0F,U+8F29,U+8F3F,U+8F44,U+8F4E,U+8FAD,U+9112,U+9285,U+92D2,U+934B,U+947D,U+95D6,U+95E2,U+9670,U+978F,U+9817,U+9846,U+98FC-98FD,U+99AE,U+99D5,U+99DB,U+9A19,U+9B27,U+9CE5,U+9CF4} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/57.woff2") format("woff2");unicode-range:U+502B,U+5091,U+50D1,U+5100,U+511F,U+5291,U+52C1,U+55AC,U+58EF,U+5922,U+596A,U+5B6B,U+5BAE,U+5CFD,U+5E33,U+5EC8,U+5EE2,U+6182,U+6191,U+61B2,U+61F7,U+64A5,U+64FA,U+6524,U+66C9,U+6B98,U+6DE8,U+6E67,U+6F01,U+6F32,U+6F64,U+70CF,U+71D2,U+7240,U+76E7,U+7955,U+79AE,U+7AAE,U+7CFE,U+7D21,U+7E73,U+8085,U+81BD,U+838A,U+85A9,U+8AA0,U+8AFE,U+8B00,U+8C6C,U+8CC0,U+8CD3,U+8CE0,U+8D95,U+8D99,U+8DE1,U+8ECC,U+8F1B,U+8F1D,U+907C,U+9081,U+912D,U+92EA,U+9326,U+932B,U+95CA,U+965D,U+96DE,U+9812,U+98F2,U+99D0,U+9B5A,U+9B6F,U+9CF3,U+9D6C,U+9E7D,U+9E97,U+9EA5,U+9F61} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/56.woff2") format("woff2");unicode-range:U+5049,U+50B5,U+5287,U+5289,U+52DE,U+5433,U+5678,U+5712,U+5967,U+596E,U+5A66,U+5BE7,U+5C46,U+5CF6,U+5EF3,U+6176,U+63DA,U+6514,U+694A,U+69AE,U+6A13,U+6A4B,U+6B50,U+6B72,U+6DFA,U+6EAB,U+6F22,U+6FA4,U+7063,U+707D,U+7159,U+755D,U+7642,U+7926,U+7A05,U+7BC9,U+7BE9,U+7C4C,U+7CE7,U+7D00,U+7D19,U+7D1B,U+7D72,U+7DA0,U+7E23,U+7F70,U+8271,U+85E5,U+8607,U+862D,U+8667,U+885B,U+8B7D,U+8CA7,U+8CAB,U+8CB8,U+8CBF,U+8FB2,U+9055,U+9109,U+9127,U+91AB,U+92FC,U+93AE,U+9435,U+9673,U+9678,U+967D,U+97D3,U+98EF,U+9928,U+9A5A,U+9B25,U+9EE8,U+9F8D} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/55.woff2") format("woff2");unicode-range:U+4E9E,U+50B7,U+50BE,U+50DE,U+5104,U+52DD,U+5713,U+593E,U+5CEF,U+5D17,U+5FB9,U+60E1,U+62CB,U+6436,U+6490,U+64FE,U+6649,U+66A2,U+6A6B,U+6F54,U+6F5B,U+6F70,U+6FDF,U+7149,U+7169,U+71C8,U+734E,U+737B,U+758A,U+7D0D,U+7D9C,U+7DB4,U+7DD2,U+7F85,U+805E,U+812B,U+83EF,U+8449,U+85CD,U+85DD,U+89AA,U+8A0A,U+8A17,U+8A69,U+8A95,U+8AF8,U+8B5C,U+8CA1-8CA2,U+8CB4,U+8CBC,U+8CFD,U+8D0A,U+8E8D,U+8ECD,U+8EF8,U+8F14,U+905C,U+90F5,U+9130,U+9215,U+9280,U+9451,U+9592,U+95BE,U+9813,U+98DB,U+98FE,U+9A30,U+9BAE,U+9EC3,U+9F4A} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/54.woff2") format("woff2");unicode-range:U+4E1F,U+4E82,U+5009,U+5074,U+52F5,U+55AB,U+5805,U+58DE,U+5BE9,U+5BEC,U+5BF6,U+5C0B,U+5E40,U+5E63,U+5F48,U+611B,U+6163,U+61B6,U+6383,U+639B,U+63EE,U+640D,U+64C1,U+64D4,U+651D,U+66AB,U+68C4,U+6BBA,U+6C23,U+6EFE,U+6F38,U+6FFE,U+723E,U+7B46,U+7D05,U+7D14,U+7D81,U+7DCA,U+7E3E,U+7E5E,U+7E6A,U+820A,U+84CB,U+87F2,U+8846,U+8AB0,U+8B02,U+8B1D,U+8C50,U+8C9D,U+8CA8,U+8CE3,U+8CE6,U+8CEC,U+8DA8,U+8E64,U+8F2A,U+9072,U+9077,U+907A,U+93E1,U+9470,U+9663,U+96B1,U+9838,U+984F,U+9858,U+990A,U+994B} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/53.woff2") format("woff2");unicode-range:U+4F54,U+5152,U+518A,U+522A,U+532F,U+537B,U+5617,U+56B4,U+5E79,U+5EE0,U+5F91,U+6232,U+6557,U+6771,U+6975,U+6A02,U+6B04,U+6E9D,U+700F,U+71B1,U+71DF,U+722D,U+7562,U+7A4D,U+7A69,U+7AF6,U+7C64,U+7D04,U+7D55,U+7E2E,U+7E54,U+8166,U+8173,U+81E8,U+8208,U+842C,U+885D,U+88DC,U+89BD,U+89F8,U+8A02,U+8A34,U+8A73,U+8AC7,U+8CB7,U+8CFC,U+8ECA,U+8F15,U+904A,U+905E,U+92B7,U+9322,U+937E,U+9589,U+95B1,U+96AA,U+96D9,U+9748,U+975C,U+9802,U+984D,U+9867,U+9918,U+99AC,U+9A45,U+9A5F} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/52.woff2") format("woff2");unicode-range:U+50C5,U+525B,U+5283,U+52E2,U+55CE,U+570B,U+570D,U+5831,U+58D3,U+5EE3,U+5F35,U+616E,U+6230,U+64CA,U+64EC,U+64F4,U+66F8,U+68E7,U+6A39,U+6A94,U+6AA2,U+6B77-6B78,U+6E1B,U+6EFF,U+7368,U+756B,U+76E1,U+76E3-76E4,U+7A31,U+7BC4,U+7DF4,U+7E7C,U+7FA3,U+8072,U+8077,U+807D,U+8209,U+85A6,U+865B,U+8A0E,U+8A13,U+8A31,U+8A55,U+8AA4,U+8B77,U+8CAC,U+8CF4,U+8E10,U+8FA6,U+9060,U+908A,U+91CB,U+91DD,U+9396,U+96D6,U+96F2,U+96FB,U+9806,U+9808,U+98A8} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/51.woff2") format("woff2");unicode-range:U+50F9,U+5354,U+5553,U+5C08,U+5C6C,U+5E2B,U+5E6B,U+5F37,U+63A1,U+64C7,U+6B0A,U+6B61,U+72C0,U+7570,U+790E,U+7D30,U+7D39,U+7D42,U+7D61,U+7DE9,U+806F,U+865F,U+88DD,U+898B,U+89BA,U+89C0,U+8A2A,U+8A5E,U+8A62,U+8B6F-8B70,U+8CA0,U+8CBB,U+8CEA,U+8EDF,U+8F09,U+8F2F,U+9023,U+9054,U+9069,U+908F,U+932F,U+9375,U+93C8,U+9580,U+968E,U+969B,U+96A8,U+96DC,U+96E2-96E3,U+97FF,U+9801,U+9810,U+9818,U+982D,U+983B,U+986F} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/50.woff2") format("woff2");unicode-range:U+5099,U+50B3,U+512A,U+5132,U+5247,U+5275,U+5340,U+54E1,U+5718,U+5834,U+584A,U+5920,U+5C0E,U+5E36,U+5E7E,U+63DB,U+65B7,U+689D,U+6CC1,U+6E96,U+7121,U+7372,U+74B0,U+78BA,U+7C21,U+7D1A,U+7DAD,U+7E3D,U+7E8C,U+7FA9,U+81FA,U+898F,U+8996,U+8A18,U+8A71-8A72,U+8A8D,U+8AB2,U+8ACB,U+8AD6,U+8B49,U+8B58,U+8B80,U+8CC7,U+8F03,U+8F38,U+8F49,U+9078,U+9304,U+9577,U+968A,U+9A57} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/49.woff2") format("woff2");unicode-range:U+4E26,U+5169,U+5225,U+52D5,U+53C3,U+55AE,U+5716,U+57F7,U+5B78,U+5BEB,U+5C07,U+5C64,U+5EAB,U+5F9E,U+5FA9,U+614B,U+6236,U+69CB,U+6A19,U+6A5F,U+6C7A,U+6C92,U+6E2C,U+7522,U+7576,U+7BC0,U+7D44,U+7D66,U+7D93,U+7DB2,U+7DDA,U+7DE8,U+7FD2,U+8207,U+8655,U+8853,U+8A2D,U+8A66,U+8A9E,U+8ABF,U+8B1B,U+8B8A,U+8B93,U+904B,U+9805,U+9AD4} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/48.woff2") format("woff2");unicode-range:U+4F86,U+500B,U+5011,U+5167,U+52D9,U+554F,U+5BE6,U+5C0D,U+61C9,U+64DA,U+6578,U+6642,U+6703,U+696D,U+6A23,U+7232,U+73FE,U+767C,U+78BC,U+7A2E,U+7D50,U+7D71,U+88CF,U+8A08,U+8AAA,U+9019,U+9032,U+904E,U+9084,U+958B,U+9593,U+95DC,U+984C,U+985E,U+9EBC,U+9EDE} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/47.woff2") format("woff2");unicode-range:U+4E2C,U+4E3F,U+5107,U+5155,U+5216,U+5281,U+5293,U+53B6,U+54F3,U+5523,U+553C,U+570A,U+5776,U+5819,U+5ADC,U+5B32,U+5B65,U+5C88,U+5C8D,U+5E3B,U+5F61,U+5FEE,U+6067,U+614A,U+615D,U+62F6,U+63AD,U+63BE,U+6420,U+6434,U+6484,U+6499,U+652E,U+67D9,U+67DD,U+6920,U+6A8E,U+6AA0,U+6B43,U+6BF5,U+6C15,U+6D5E,U+6EE0,U+6F46,U+717A,U+71E0,U+72C1,U+72C3,U+72E8,U+730A,U+73E7,U+746D,U+752F,U+754E,U+75B0,U+7603,U+7722,U+778D,U+77E7,U+7809,U+7811,U+7857,U+78C9,U+78F2,U+7946,U+7967,U+799A,U+7B45,U+7B9D,U+7BA2,U+7CD7,U+7F32,U+8014,U+8028,U+804D,U+8080,U+80C2,U+80D9,U+80EC,U+810E,U+8159,U+817D,U+81A3,U+81AA,U+8201,U+833C,U+836E,U+83E5,U+8459,U+848E,U+84CD,U+84D0,U+84F0,U+85B7,U+871E,U+8723,U+8729,U+8753,U+877E,U+878B,U+8793,U+87D3,U+88D2,U+8966,U+89DC,U+89EB,U+8B26,U+8BF6,U+8C2E,U+8D33,U+8D47,U+8D55,U+8E2C,U+8E9C,U+8E9E,U+8ECE,U+8FEE,U+9139,U+914F,U+9174,U+9191,U+928E,U+94F4,U+952B,U+953C,U+9564,U+960C,U+9622,U+9674,U+98A5,U+9A98,U+9A9F,U+9B48,U+9CA6,U+9CB0,U+9CBA,U+9CCB,U+9E32,U+9E88,U+9EB4,U+9F3D,U+9F44,U+9F86} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/46.woff2") format("woff2");unicode-range:U+4E0C,U+4E28,U+4E36,U+4EA0,U+4EC2,U+4F91,U+4F94,U+4FC5,U+502E,U+50BA,U+5181,U+51F5,U+522D,U+5290,U+52F9,U+5369,U+53A3,U+5423,U+54D5,U+54DC,U+54FF,U+552A,U+5588,U+55B5,U+55C9,U+5686,U+5786,U+57A4,U+57F4,U+580B,U+5820,U+5865,U+58BC,U+5902,U+5AB5,U+5BE4,U+5C1C,U+5C22,U+5C66,U+5C6E,U+5D3E,U+5DDB,U+5E80,U+5EE8,U+5F2A,U+5F50,U+5FED,U+609B,U+619D,U+61D4,U+620B,U+6224-6225,U+6343,U+63CE,U+63F2,U+640B-640C,U+6445,U+6496,U+64D0,U+64D7,U+6509,U+6534,U+6549,U+6603,U+674C,U+67A8,U+6833,U+6844,U+684A,U+6864,U+6957,U+6971,U+6A65,U+6A91,U+6BEA,U+6C5C,U+6CD0,U+6D48,U+6E2B,U+6EB2,U+6F24,U+6F2D,U+706C,U+70B1,U+70C0,U+721D,U+728B,U+72F3,U+7303,U+7338-7339,U+734D,U+736C,U+736F,U+74A9,U+750D,U+7513,U+7592,U+75C4,U+762D,U+7707,U+770D,U+7735,U+77A2,U+7839,U+784E,U+786D,U+791E,U+7924,U+793B,U+7953,U+798A,U+7A78,U+7A80,U+7B58,U+7BA8,U+7BDA,U+7CC8,U+7EC1,U+7F0F,U+7F12,U+7F33,U+7F68,U+8022,U+8025,U+8029,U+8035,U+809C,U+80AD,U+80B7,U+80E9,U+80F2,U+8204,U+8221,U+8223,U+822D,U+824B,U+825A,U+8274,U+827F,U+82B0,U+82E0,U+8307,U+8347,U+837D,U+839B,U+83B0,U+83F9,U+8487-8488,U+8539,U+8579-857A,U+85A8,U+867C,U+86B0,U+86D8,U+86F2,U+8764,U+8788,U+8797,U+87B5,U+8803,U+881B,U+8844,U+88FC,U+8930,U+89CC,U+8BA0,U+8C02,U+8C85,U+8C8A,U+8C98,U+8DBC,U+8E23,U+8E40,U+8E94,U+8F77,U+8F79,U+8F81,U+8FD5,U+9058,U+90B6,U+914E,U+91A2,U+91B5,U+943E,U+9494,U+94B6-94B8,U+94DE,U+94EB,U+94F9,U+950A,U+950E,U+9516,U+9518,U+951E,U+9538,U+953E-953F,U+9544,U+954E,U+955F,U+9561,U+9569,U+95F6,U+95FC,U+9603,U+960D,U+963D,U+9667,U+9792,U+9794,U+97AB,U+98A1,U+9969,U+9A80,U+9A96,U+9AB1,U+9AD1,U+9B0F,U+9B2F,U+9C92,U+9C95,U+9CBC,U+9CC6,U+9CD0,U+9CD8,U+9E38,U+9E3A,U+9E5B,U+9E7E,U+9EE5,U+9EEA,U+9EF9,U+9EFB-9EFC,U+9FA0} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/45.woff2") format("woff2");unicode-range:U+4EF3,U+4F74,U+507E,U+50D6,U+50EC-50ED,U+5182,U+51B1,U+525E,U+5282,U+52D6,U+5326,U+537A,U+5459,U+54A9,U+54B4,U+54D9,U+55BE,U+55CD,U+564D,U+572F,U+574C,U+57D8,U+57FD,U+5844,U+5925,U+59D2,U+5A08,U+5AE0,U+5B16,U+5B37,U+5B80,U+5B84,U+5CB5,U+5CBD,U+5E31,U+5E5E,U+5F11,U+5F56,U+5F82,U+5F8C,U+5FC4,U+5FC9,U+5FE4,U+600A,U+605D,U+609D,U+60DD,U+6106,U+610D,U+6243,U+6322,U+63DE,U+63F8,U+641B,U+643F,U+64E4,U+656B,U+6634,U+676A,U+67B5,U+680A,U+681D,U+6883,U+69D4,U+6B37,U+6B9B,U+6C2A,U+6C46,U+6D0E,U+6D7C,U+6EB7,U+6F36,U+6F72,U+6F89,U+6F8C,U+7039,U+7113,U+7173,U+71D4,U+727F,U+728F,U+72EF,U+72F4,U+7321,U+7331,U+74DE,U+74E0,U+75C3,U+75E6,U+7605,U+760A,U+761B,U+7625,U+763C,U+7643,U+764D,U+7704,U+7743,U+7747,U+7780,U+77B5,U+7847,U+786A,U+78A5,U+78B9,U+7962,U+7A02-7A03,U+7A06,U+7AAC-7AAD,U+7AB3,U+7B0A,U+7B4C,U+7BE5,U+7C0F,U+7C40,U+7CA2,U+7CC7,U+7CF8,U+7D77,U+7E3B,U+7EA1,U+7EA9,U+7F02,U+7F07,U+7F0B-7F0C,U+7F23,U+7F2F,U+7F9D,U+8020,U+80E8,U+811E,U+812C,U+8136,U+8182,U+822F,U+8233,U+823B,U+8268,U+827D,U+8284,U+8288,U+828F,U+8291,U+82C8,U+8311,U+831B,U+835B,U+83A9,U+83DD,U+8406,U+845C,U+84B4,U+84B9,U+84E3,U+850C,U+863C,U+864D,U+867F,U+86BA,U+86D1,U+86DE,U+86F1,U+8770,U+87AC-87AD,U+87BD,U+87DB,U+880A,U+883C,U+887F,U+88BC,U+895E,U+89CF,U+89D6,U+8A48,U+8BD4,U+8BDC,U+8C2B,U+8C33,U+8C94,U+8DB1,U+8DCF,U+8DD6,U+8DE3,U+8E90,U+8F7A,U+8F82,U+902D,U+90DB,U+9164,U+91AD,U+91AF,U+936A,U+948B,U+94D8,U+950D,U+9513-9514,U+9529,U+956C,U+963C,U+966C,U+9688,U+96B3,U+97B2,U+996B,U+9987,U+9998,U+9A75,U+9A7A,U+9A92,U+9ABA,U+9ADF,U+9B08,U+9B43,U+9C8E,U+9C94,U+9C9A,U+9CB4,U+9CBD,U+9CD4,U+9E31,U+9E36,U+9E4B,U+9E4E,U+9E55,U+9E63,U+9E68-9E69,U+9E71,U+9EBD,U+9EC9,U+9F0D,U+9F37,U+9F51} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/44.woff2") format("woff2");unicode-range:U+4F1B,U+4F67,U+4F7E,U+4FDC,U+50E6,U+5196,U+51AB,U+5202,U+5233,U+523F,U+52A2,U+536E,U+5476,U+54CF,U+54DA,U+5537,U+561E,U+56DD,U+56DF,U+5709,U+572C,U+576B,U+57CF,U+57F8,U+580D,U+5881,U+589A,U+5941,U+59B2,U+5B5B,U+5C25,U+5D1E,U+5D24,U+5D6B,U+5E42,U+5E8B,U+5EB3,U+5ED2,U+5EFE,U+5F58,U+5F73,U+5FAD,U+6003,U+602B,U+603C,U+6083,U+6100,U+6126,U+6206,U+62CA,U+638E,U+63B4,U+6426,U+646D,U+6535,U+65C4,U+66DB,U+6715,U+6769,U+6798,U+67C3,U+6861,U+698D,U+69B1,U+69CA,U+69E0,U+69ED,U+69F2,U+69FF,U+6A18,U+6B39,U+6BB3,U+6C0D,U+6CB2,U+6CD6,U+6CF7,U+6CFA,U+6D33,U+6E16,U+6E53-6E54,U+6EBB,U+6ED7,U+6FB6,U+7035,U+709D,U+7178,U+7228,U+72AD,U+72B4,U+72F7,U+72FB,U+7313,U+739F,U+7481,U+74BA,U+7540,U+754B,U+755B,U+758B,U+75AC,U+75D6,U+7617,U+7635,U+7640,U+76A4,U+76B2,U+76CD,U+775A,U+77BD,U+781F,U+79B3,U+7B2B,U+7B31,U+7B3E,U+7B6E,U+7B7B,U+7B9C,U+7BFC,U+7C0B,U+7C16,U+7C9E,U+7CC1,U+7CE8,U+7EA5,U+7EF2,U+7F21,U+7F27,U+7F74,U+7FB0,U+7FBC,U+8016,U+8031,U+8071,U+80EA,U+8114,U+8160,U+81A6,U+81C1,U+825F,U+829F,U+82A4,U+82FB,U+8308,U+831A,U+8333,U+836C-836D,U+83B6,U+83F8,U+840F,U+8411,U+841C,U+8489,U+848C,U+855E,U+85A4,U+8627,U+8629,U+866E,U+86B5,U+86C4,U+872E,U+8731,U+873E,U+877B,U+877D,U+87EA,U+8813,U+8864,U+88CE,U+88E5,U+88F0,U+890A,U+892B,U+897B,U+89CB,U+89EF,U+89F3,U+8BFC,U+8C18,U+8C35,U+8D46,U+8DBA,U+8E3A,U+8E6F,U+8F75,U+8F7E,U+8FB6,U+8FD3,U+90BE,U+9161,U+9179,U+917E,U+91A3,U+94AC,U+94D7,U+94E5,U+952C,U+953A,U+9545,U+9565,U+9568,U+956A,U+961D,U+9654,U+96E0,U+972A,U+9730,U+97B4,U+989F,U+98E7,U+990D,U+9967,U+9993,U+9AA3,U+9AC0,U+9AE1,U+9AEB,U+9AED,U+9AF9,U+9C86,U+9C8B,U+9CA0-9CA1,U+9CA3,U+9CE2,U+9E2B,U+9E48,U+9E6A,U+9E87,U+9EE2,U+9EE9,U+9F17,U+9F19,U+9F2C,U+9F80} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/43.woff2") format("woff2");unicode-range:U+4E69,U+4F65,U+50A7,U+5240,U+5261,U+52AC,U+531A,U+5363,U+5416,U+5432,U+5452,U+5456,U+5472,U+5478,U+54AD,U+54BB,U+553F,U+5575,U+5581,U+55CC,U+55FE,U+5601,U+572E,U+57D2,U+57EF,U+581E,U+5924,U+5981,U+5997,U+59A3,U+5AAA,U+5AB8,U+5B34,U+5D5B,U+5D5D,U+5D74,U+5DEF,U+5E11,U+5E91,U+5ED1,U+5EF4,U+5F40,U+600D,U+6019,U+601B,U+605A,U+6092,U+60AB,U+6217,U+623D,U+624C,U+6369,U+65D2,U+6661,U+670A,U+6753,U+67A7,U+6855,U+686B,U+68F9,U+6939,U+696E,U+6980,U+6A7C,U+6AAB,U+6B82,U+6B84,U+6BF3,U+6BF9,U+6C05-6C06,U+6C19-6C1A,U+6CA9,U+6CF6,U+6D07,U+6D1A,U+6D27,U+6D2B,U+6DAB,U+6E8F,U+6F74,U+7085,U+7198,U+71B5,U+7256,U+725D,U+727E,U+72F2,U+72FA,U+7322,U+738E,U+73E5,U+7477,U+750F,U+755A,U+7594,U+75B3,U+760C,U+7615,U+7630,U+763F,U+7654,U+7726,U+77EC,U+7817,U+78A1,U+78D4,U+78D9,U+7905,U+7B2A,U+7B2E,U+7B62,U+7B85,U+7BA7,U+7BCC,U+7BEA,U+7C26,U+7C74,U+7C9C-7C9D,U+7E47,U+7E9B,U+7E9F,U+7EE0-7EE1,U+7EE8,U+7EF1,U+7F01,U+7F11,U+7F17,U+7F36,U+7F7E,U+7FEE,U+802A,U+809F,U+80AB,U+80CD,U+80D7,U+8112,U+8118,U+8169,U+8234,U+8279,U+8298,U+82CA,U+82D8,U+82E1,U+83C0,U+83D4,U+83DF,U+8401,U+8451,U+845A,U+8476,U+8478,U+84BA,U+84BD,U+84E0,U+851F,U+8548,U+8556,U+8585,U+868B,U+868D,U+86E9,U+86F4,U+86F8,U+8765,U+8785,U+87AB,U+87EE,U+8816,U+8832,U+8872,U+88B7,U+88E2-88E3,U+89DA,U+8BCE,U+8BD3,U+8BD6,U+8BF9,U+8C16,U+8C73,U+8D40,U+8D4D,U+8D5C,U+8DDE,U+8DEB,U+8F6D,U+8F94,U+8FE8,U+9011,U+915E,U+9185,U+918C,U+94AB,U+94D1,U+94F3,U+94FD,U+9515,U+951D,U+952A,U+9558,U+9567,U+96CE,U+96E9,U+9785,U+9878,U+987C,U+9883,U+98D1,U+9954,U+9963,U+9A93,U+9AC1,U+9ACC,U+9B1F,U+9B49,U+9B4D,U+9B51,U+9CA5,U+9CA7,U+9CAE,U+9CCE,U+9CD3,U+9E37,U+9E39,U+9E41,U+9E46,U+9F22,U+9F2F,U+9F39,U+9F85} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/42.woff2") format("woff2");unicode-range:U+4E47,U+4E5C,U+4E8D,U+4EDF,U+4F89,U+4FAA,U+50EE,U+520E,U+5412,U+5443,U+5454,U+54C2,U+54D3,U+54DE,U+5577,U+5591,U+55E5,U+560C,U+560F,U+562C,U+566B,U+56AF,U+5769,U+577C,U+578C,U+5793,U+57DA,U+57E4,U+5889,U+593C,U+59AB,U+5AD4,U+5AD8,U+5AF1,U+5B53,U+5BA5,U+5C59,U+5C63,U+5CA3,U+5E0F,U+5E14,U+5EDB,U+5F01,U+5FBC,U+6004,U+6029,U+6041,U+60AD,U+610E,U+61B7,U+634C,U+647A,U+64BA,U+65C3,U+65C6,U+65F0,U+6600,U+668C,U+66F7,U+67E2,U+67F0,U+680C,U+6874,U+691F,U+6989,U+69A7,U+6A17,U+6A3E,U+6B81,U+6C07,U+6C10,U+6C3D,U+6D91,U+6E6B,U+6FDE,U+70BB,U+71B3,U+723B,U+726E,U+72B0,U+72CE,U+7301,U+731E,U+7360,U+737E,U+73A2,U+748E,U+74FF,U+7633,U+771A,U+7765,U+7768,U+781C,U+7829,U+7913,U+7957,U+79D5,U+79EB,U+7A70,U+7A86,U+7AA8,U+7B04,U+7B25,U+7B38,U+7B47,U+7B72,U+7BA6,U+7CCD,U+7DAE,U+7ECB,U+7ED7,U+7EFE,U+7F26,U+7F31,U+7F35,U+7F5F,U+7FAF,U+801C,U+8043,U+80BC,U+8188,U+81CC,U+823E,U+8244,U+824F,U+82A1,U+82B4,U+82C1,U+82E4,U+82F4,U+8306,U+833A,U+835C,U+839C,U+83B3,U+83BC,U+83F0,U+846D,U+8497,U+85FF,U+867A,U+86A8-86A9,U+870A,U+8734,U+87CA,U+886E,U+887E,U+8888,U+88A2,U+88C9,U+8921,U+8BB5,U+8BF3,U+8C04,U+8C17,U+8C1D,U+8C25,U+8C2F,U+8C36,U+8C55,U+8C78,U+8D3D,U+8D59,U+8D67,U+8D91,U+8DBF,U+8DEC,U+8DFD,U+8E05,U+8E14,U+8E41,U+8F8E,U+900B,U+9044,U+9062,U+90CF,U+9123,U+9146,U+9162,U+9172,U+918D,U+9190,U+92C8,U+933E,U+93CA,U+9486,U+948C,U+94AA,U+94B2,U+94C8,U+94CA,U+94D5,U+94DF,U+94E9-94EA,U+94F7,U+94FC,U+951B,U+9546,U+954F,U+9554,U+9559,U+9566,U+9571-9572,U+95F1,U+960B,U+960F,U+97AF,U+988F,U+98D5,U+992E,U+9955,U+9A7D,U+9AA2,U+9AB0,U+9AC2,U+9B23,U+9B32,U+9C90,U+9C9E,U+9CA9,U+9CAD,U+9CB1,U+9CC3,U+9E47,U+9E6C,U+9EE7,U+9EFE,U+9F0B,U+9F87} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/41.woff2") format("woff2");unicode-range:U+4F25,U+4F32,U+4F5E,U+4F76,U+4F7B,U+4FE6,U+5028,U+5048,U+5250,U+535F,U+538D,U+53C1,U+54A6,U+54D4,U+54DD,U+5533,U+5541,U+5550,U+5556,U+55DD,U+55F3,U+55F5,U+5627,U+5657-5658,U+5664,U+567B,U+56D4,U+575C,U+57B2,U+5800,U+59A4,U+5A62,U+5AEB,U+5C3B,U+5CAC,U+5D26,U+5D9D,U+5DB7,U+5F2D,U+5FB5,U+5FDD,U+5FF8,U+6079,U+60B1,U+6222,U+623E,U+629F,U+6332,U+638A,U+63BC,U+63E0,U+6485,U+64B8,U+64E2,U+65AB,U+669D,U+66BE,U+66E9,U+67C1,U+67D2,U+67FD,U+6800,U+6877,U+68FC,U+690B,U+6924,U+6978,U+69C1,U+6A50,U+6A5B,U+6A97,U+6B24,U+6B8D,U+6BAA,U+6C18,U+6C54,U+6CEB,U+6CEE,U+6D04,U+6D4D,U+6DE0,U+6EB1,U+6EBD,U+7110,U+7168,U+71F9,U+7230,U+728D,U+7292,U+72B8,U+72D2,U+73B3,U+73D0,U+7511,U+75A0,U+75A5,U+75C8,U+75E7,U+7610,U+7619,U+772D,U+779F,U+7826,U+782C,U+7856,U+7877,U+7A39,U+7AE6,U+7B0F,U+7BAA,U+7BAC,U+7C1F,U+7ED0,U+7ED4,U+7EFB,U+7F0D,U+7F1F,U+7F45,U+7FBF,U+7FD5,U+7FE5,U+8027,U+8052,U+80DB,U+80DD,U+80FC,U+8132,U+815A,U+8167,U+816D,U+81CA,U+8228,U+8249,U+82A9,U+82AB,U+82CC,U+8315,U+8351,U+8368,U+8378,U+83AA,U+83B4,U+83B8,U+83D8,U+83EA,U+84C1,U+8538,U+858F,U+859C,U+85AE,U+867B,U+86B4,U+86CF,U+8722,U+876E,U+877C,U+87E5,U+88DF,U+8919,U+8BA7,U+8BCC,U+8BDF,U+8BE8,U+8BEE,U+8C20,U+8C30,U+8D36,U+8DB5,U+8DF8,U+8E1F,U+8E2F,U+8E70,U+8F78,U+8F8B,U+8F8F,U+8F9A,U+9021,U+908B,U+90B4,U+90BA,U+90C4,U+90D0,U+90EB,U+90FE,U+917D,U+91AA,U+9487,U+948D,U+9490,U+94AD,U+94BD,U+94CD,U+94D6,U+94D9,U+94EF,U+9507,U+954A,U+955E,U+956B,U+95E9,U+9604,U+9612,U+9615,U+9617,U+96B9,U+96D2,U+9708,U+989A-989B,U+989E,U+98A7,U+9A78,U+9A88,U+9AA0,U+9AB6,U+9B3B,U+9B47,U+9C82,U+9C9B,U+9CCA,U+9CD9,U+9E28,U+9E2C,U+9E5A,U+9E5E,U+9ECD,U+9ECF,U+9F8A,U+9F8C} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/40.woff2") format("woff2");unicode-range:U+4E93,U+4EBB,U+4EC4,U+4EF5,U+4F27,U+4FA9,U+4FE3,U+502C,U+5080,U+5121,U+51C7,U+51EB,U+5208,U+523D,U+52F0,U+530F,U+53F5,U+5453,U+5464,U+5466,U+549D,U+54A3,U+54BF,U+54D0,U+5527,U+5549,U+556D,U+558F,U+55B1,U+55EB,U+55F2,U+5624,U+5685,U+56E1,U+571C,U+5739,U+57B4,U+5807,U+58C5,U+59AF,U+59D8,U+5A09,U+5A0C,U+5A4A,U+5A67,U+5AD2,U+5B6C,U+5C50,U+5CA2,U+5D03,U+5D27,U+5D6C,U+5DF3,U+5E19,U+5EA0,U+5EBE,U+5EEA,U+5FEA,U+5FFE,U+6042,U+6120,U+6175,U+6215,U+6221,U+622C,U+6266,U+6308,U+6339,U+643D,U+659B,U+6677,U+679E,U+67B3,U+6832,U+6840,U+68C2,U+68F0,U+6934,U+6966,U+6987,U+6998,U+69CE,U+6A2F,U+6A3D,U+6A84,U+6AA9,U+6B87,U+6B93,U+6BD6,U+6BFD,U+6C16,U+6CD4,U+6D0C,U+6DA0,U+6E0C,U+6E86,U+6ECF,U+6F4B,U+7023,U+7080,U+70B7,U+70CA,U+72D9,U+7352,U+740A,U+7441,U+7457,U+74D2,U+7596,U+75A3,U+75D8,U+75E4,U+75E8,U+7622,U+765E,U+76B4,U+7812,U+7818,U+784C,U+7850,U+7887,U+789A-789B,U+78F4,U+7A51,U+7B15,U+7B1E,U+7B24,U+7B5A,U+7BB8,U+7BC1,U+7BD9,U+7CCC,U+7EA8,U+7EC0,U+7EE6,U+7EFA,U+7F1B,U+7F22,U+7F71,U+7FA7,U+7FF3,U+8084,U+8093,U+80B1,U+80F4,U+81BB,U+81EC,U+8202,U+8210,U+828A,U+828E,U+82A8,U+82E3,U+8366,U+8369,U+83A8,U+83E1,U+84D1,U+84FC,U+853B,U+857B,U+85D3,U+8605,U+8662,U+86B1,U+86C9,U+86D4,U+86F3,U+8725,U+8748,U+874C,U+8763,U+879F,U+87B3,U+887D,U+88FE,U+89DE,U+8A8A,U+8BA6,U+8C00,U+8C07,U+8C14,U+8C21,U+8C47,U+8C7A,U+8D30,U+8DD7,U+8E59,U+8E69,U+8E85,U+8F6B,U+8F73,U+8F87,U+8FF3,U+9026,U+902F,U+9099,U+90C7,U+9143,U+91AE,U+91BA,U+948F,U+94AF,U+94BF,U+94D2,U+9531,U+95F3,U+9609-960A,U+9765,U+97EA,U+9880,U+996C,U+9980,U+9991,U+9994,U+9AB7,U+9AC5,U+9AFB,U+9C87,U+9CB5,U+9CC7,U+9CD5,U+9E42,U+9E58,U+9F88} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/39.woff2") format("woff2");unicode-range:U+4EDD-4EDE,U+4EE1,U+4FFE,U+5025,U+506C,U+50A5,U+51FC,U+5241,U+525C,U+539D,U+53FC,U+54E7,U+5514,U+555C,U+556E,U+5576,U+557E,U+5599,U+55B9,U+55D4,U+5623,U+564C,U+5654,U+5671,U+568F,U+56D7,U+57A1,U+57A7,U+57D9,U+584D,U+58EC,U+5942,U+59DD,U+5A32,U+5A55,U+5A75,U+5B71,U+5B73,U+5C7A,U+5C98,U+5D06,U+5D6F,U+5DFD,U+5E5B,U+5EB9,U+5FD2,U+600F,U+606B,U+62BB,U+62BF,U+634B,U+636D,U+6387,U+63B3,U+63B8,U+63C4,U+63C6,U+63F6,U+6441,U+6555,U+661D,U+6657,U+66A7,U+6775,U+678B,U+6849,U+6860,U+6867,U+6901,U+6910,U+699B,U+6A28,U+6A79,U+6B46,U+6B92,U+6BC2,U+6C21,U+6C24,U+6C29,U+6C32,U+6C35,U+6C4A,U+6C86,U+6CA3,U+6CC5,U+6D39,U+6D60,U+6D63,U+6D6F,U+6DDD,U+6ED3,U+6EDF,U+6FB9,U+6FD1,U+6FEF,U+712F,U+7145,U+71E7,U+7284,U+72CD,U+72F0,U+7325,U+732C,U+7350,U+7391,U+73C8,U+73D9,U+741A,U+757F,U+7583,U+759D,U+75B4,U+75E3,U+75F1,U+75FF,U+7688,U+7699,U+76E5,U+7751,U+789C,U+78B2,U+7A17,U+7B08,U+7B33,U+7BE6,U+7C2A,U+7EA3,U+7EB0,U+7F2C,U+7FE6,U+8012,U+80E4,U+80EF,U+8171,U+821B,U+826E,U+8297,U+82BE,U+82EB,U+830C,U+8314,U+8331,U+83C5,U+83D6,U+83FD,U+843C,U+8473,U+84A1,U+84CA,U+8564,U+8572,U+85C1,U+86A3,U+86AA,U+86C6,U+86ED,U+8708-8709,U+870D,U+8869,U+89C7,U+89D0,U+89E5,U+8A3E,U+8B07,U+8BC2,U+8BF0,U+8C2A,U+8C49,U+8C62,U+8C89,U+8D49,U+8D6D,U+8D94,U+8DF9,U+8E2E,U+8E3D,U+8E47,U+8E51,U+8E7F,U+9005,U+9016,U+907D,U+9082,U+9088,U+909B,U+90B0,U+914A,U+9495,U+94A3-94A4,U+94B9-94BA,U+94CB-94CC,U+94E1,U+94F0,U+9509,U+9512,U+9552-9553,U+955D,U+9697,U+96B0,U+96BC,U+975B,U+977C,U+98A2,U+998A,U+9990,U+9995,U+9A9B,U+9C91,U+9CB3,U+9CB7,U+9CCF,U+9E29,U+9E57,U+9E82,U+9EDC,U+9EDF-9EE0,U+9F83,U+9F89} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/38.woff2") format("woff2");unicode-range:U+4EE8,U+4EEB,U+4F22,U+4F43,U+4F5D,U+4FDA,U+502D,U+50F3,U+5106,U+520D,U+52AD,U+530D,U+5310,U+53A9,U+53DF,U+5421,U+544B,U+5477,U+5482,U+551B,U+5530,U+5555,U+55C4,U+55D1,U+55DF,U+55E4,U+55EA,U+55EC,U+55EF,U+562D,U+56EB,U+56F5,U+56F9,U+5704,U+576D,U+57D5,U+57DD,U+5880,U+5958,U+599E,U+59BE,U+5ABE,U+5B51,U+5BEE,U+5CBF,U+5CC4,U+5CD2,U+5CE4,U+5E96,U+603F,U+607B,U+609A,U+61D1,U+61FF,U+622E,U+626A,U+6371,U+63AE,U+63CD,U+63D6,U+63FF,U+6410,U+6414,U+6421,U+6448,U+64C0,U+66DC,U+67A5,U+680E,U+6841,U+6954,U+6A47,U+6C94,U+6CA4,U+6D94,U+6E4E,U+705E,U+70E9,U+7116,U+726F,U+72DE,U+74E4,U+753E,U+7548,U+75B1,U+75B8,U+75BD,U+75C2,U+75CD,U+7618,U+766F,U+76F1,U+7719,U+777E,U+7791,U+7823,U+7827,U+7889,U+7893,U+795C,U+79E3,U+7A88,U+7A95,U+7AA0,U+7B90,U+7BD1,U+7BFE,U+7CB2,U+7DA6,U+7EBE,U+7EC2,U+7EEB,U+7EEF,U+7F03,U+7F08,U+7F25,U+7F58,U+7F61,U+7F9F,U+7FB8,U+8037,U+8153,U+8174,U+8191,U+8214,U+8222,U+82D5,U+82DC,U+8334,U+835A,U+8418,U+84FF,U+852B,U+8616,U+866C,U+8693,U+869D,U+86A7,U+86AC,U+86AF,U+86B6,U+86CA,U+86D0,U+8759,U+8760,U+87A8,U+87AF,U+87C6,U+87D1,U+8839,U+88C6,U+8934,U+89CA,U+89CE,U+8BA3,U+8BAA-8BAB,U+8BC5,U+8BDB,U+8BE4,U+8C15,U+8D84,U+8DB8,U+8DC4,U+8DC6,U+8DCE,U+8DDB,U+8E09,U+8E1D,U+8E39,U+8E42,U+8E49,U+8E4B,U+8E8F,U+8F71-8F72,U+9051,U+9097,U+90D3,U+90E2,U+90E6,U+90EF,U+9104,U+9150,U+919A,U+91B4,U+938F,U+9497,U+949C,U+94E0,U+951F,U+9534,U+9557,U+9562-9563,U+9606,U+965F,U+973E,U+9791,U+9889,U+9894,U+9974,U+9A90,U+9A9C-9A9D,U+9AA7,U+9AEF,U+9C88,U+9CA2,U+9CB2,U+9CB6,U+9CC5,U+9CCD,U+9CDC,U+9CDF,U+9E22,U+9E2A,U+9E67,U+9E6B,U+9E73,U+9EB8,U+9F9B} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/37.woff2") format("woff2");unicode-range:U+4EB5,U+4F09,U+4F57,U+4F6F,U+4FCE,U+4FEA,U+4FF3,U+4FF8,U+500C,U+500F,U+504E,U+507B,U+5088,U+5345,U+53FB,U+5457,U+549B,U+54A7,U+54CC,U+54D2,U+550F,U+5544,U+558B,U+5594,U+559F,U+55D6,U+55E8,U+564E,U+565C,U+5785,U+57A9,U+57AD,U+57ED,U+5914,U+59AA,U+5A06,U+5A40,U+5AE1,U+5AE6,U+5B40,U+5B6A,U+5B93,U+5C99,U+5C9C,U+5CC1,U+5CCB,U+5D2E,U+5D47,U+5D4B,U+5D99,U+5E54,U+5E61,U+6002,U+6014,U+6078,U+607D,U+607F,U+608C,U+60C6,U+60F4,U+60FA,U+621B,U+621F,U+62C8,U+631B,U+631E,U+64D8,U+6635,U+6641,U+6710,U+6748,U+6772,U+67AD,U+67B0,U+67B7,U+695D,U+69AB,U+6A58,U+6C68,U+6C8F,U+6D2E,U+6D35,U+6D9E,U+6DD9,U+6DEC,U+6E11,U+6E98,U+6EC2,U+6EE2,U+6F09,U+6F66,U+6F8D,U+6FC9,U+6FE0,U+7094,U+7119,U+723F,U+729F,U+73C9,U+73DE,U+7430,U+75C9,U+7634,U+76C5,U+7708,U+778C,U+77B3,U+77DC,U+7800,U+781D,U+782D,U+7830,U+783B-783C,U+78A3,U+78EC,U+7949,U+7980,U+7A14,U+7A23,U+7AFD,U+7B95,U+7C0C,U+7C41,U+7C91,U+7CC5,U+7ECC,U+7F04,U+7FCA,U+8006,U+8069,U+807F,U+80BD,U+814B,U+8198,U+8200,U+828D,U+82C4,U+82CB,U+82D2,U+82F7,U+832D,U+834F,U+840B,U+8438,U+847A,U+84D6,U+84E5,U+8537,U+85D0,U+8671,U+86A4,U+86F0,U+86F9,U+8703,U+8707,U+8737,U+873B,U+8782,U+87D2,U+87E0,U+8815,U+8913,U+891B,U+8941,U+8A07,U+8BC3,U+8BCB,U+8BCF,U+8BD2,U+8C0C,U+8C11,U+8C29,U+8C32,U+8C4C,U+8D45,U+8DFA,U+8E31,U+8E6D,U+8E76,U+9004,U+9036,U+90D7,U+90DC,U+9169,U+9485,U+94DB,U+9506,U+950F,U+9528,U+9542,U+9549,U+9573,U+95FE,U+961A,U+9649,U+9701,U+970E,U+972D,U+9798,U+97A3,U+97EB,U+988C,U+98A6,U+98D3,U+98D9,U+9968,U+996F,U+9977,U+997D,U+9984,U+9997,U+9ACB,U+9E20,U+9E49,U+9E4C,U+9E51,U+9E66,U+9F3E} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/36.woff2") format("woff2");unicode-range:U+4EC9,U+4F5A,U+4F8F,U+4FDF,U+501C,U+50A9,U+510B,U+51A2,U+527D,U+52BE,U+5420,U+542E,U+5486,U+5499,U+54C6,U+5506,U+5511,U+5522,U+552C,U+556C,U+55B3,U+55BD,U+55FD,U+567C,U+568E,U+5693,U+572A,U+5768,U+577B,U+5892,U+59F9,U+5A11,U+5A20,U+5B17,U+5BB8,U+5C15,U+5D3D,U+5D4A,U+5D82,U+5E1A-5E1B,U+5EA5,U+5FCF-5FD1,U+5FD6,U+5FFF,U+6006,U+6035,U+60AF,U+60B4,U+60BB,U+60DA,U+612B,U+6194,U+61CA,U+61E6,U+61F5,U+620A,U+62A1,U+62D7,U+637B,U+63E9,U+64B5,U+655D,U+65CC,U+6619,U+664C,U+665F,U+66B9,U+6773,U+6777,U+6787,U+67E9,U+6845,U+684E,U+688F,U+693D,U+696B,U+6994,U+69B7,U+6A35,U+6C26,U+6D43,U+6D5C,U+6D8E,U+6E6E,U+6E9F,U+6EF9,U+6F2A,U+6F78,U+6FC2,U+704F,U+70EC,U+7118,U+714A,U+724D,U+72C8,U+7337,U+733E,U+7428,U+74F4,U+7566,U+7572,U+7579,U+75E2,U+75FC,U+762A,U+7638,U+7678,U+76C2,U+76F9,U+7762,U+77CD,U+77FD,U+7837,U+78B4,U+795F,U+7A1E,U+7B20,U+7BA9,U+7BAB,U+7BDD,U+7C7C,U+7CB3,U+7CBD,U+7CDC,U+7EC9,U+7EF6,U+7F19,U+7F44,U+7F54,U+7F94,U+8004,U+800B,U+8019,U+809B,U+80C4,U+80ED,U+80F1,U+8146,U+816E,U+817C,U+81C0,U+81FC,U+81FE,U+830F,U+8360,U+8365,U+8385,U+8392,U+83A0,U+8424,U+84AF,U+869C,U+86CE,U+8713,U+8718,U+87C0,U+87CB,U+8821,U+8936,U+89D1,U+8BB9,U+8BD8,U+8BE9,U+8C0F,U+8D53,U+8D5D,U+8D73,U+8DF7,U+8F7C,U+8FE4,U+9041,U+9052,U+905B,U+9095,U+909D,U+90C5,U+911E,U+9122,U+916A,U+919B,U+92AE,U+9492,U+94A1,U+94B5,U+94BC,U+94C4,U+94C9,U+94E7,U+9502-9503,U+9511,U+9517,U+9536-9537,U+9602,U+9616,U+9621,U+9631,U+96C9,U+96F3,U+9739,U+9753,U+98D2,U+98DA,U+998B,U+9A81,U+9A9E,U+9B03,U+9C85,U+9CA8,U+9CAB,U+9EBE,U+9F10,U+9F8B} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/35.woff2") format("woff2");unicode-range:U+4E15,U+4E1E,U+4E2B,U+4EB3,U+4F0E,U+4F3D,U+4F64,U+5043,U+5140,U+51BC,U+51CB,U+52D0,U+531D,U+536F,U+53A5,U+53FD,U+541D,U+5429,U+5494,U+54A4,U+54AA,U+54AF,U+54CE,U+5520,U+553E,U+557B,U+55C5,U+55D2,U+55E1,U+55E6,U+55F7,U+5608,U+561F,U+5636,U+5639,U+563B,U+5659,U+5662,U+5773,U+5811,U+587E,U+58D5,U+598A,U+5992,U+59A9,U+5A6A,U+5A76,U+5A7A,U+5AC9,U+5AE3,U+5B7D,U+5B95,U+5C8C,U+5CAB,U+5CB7,U+5D02,U+5D34,U+5D58,U+5E7A,U+5EFF,U+5F0B,U+5F29,U+5F77,U+5F89,U+5F9C,U+5FA8,U+6005,U+6026,U+6063,U+60D8,U+60EC,U+60EE,U+6115,U+618E,U+6248,U+6376,U+637A,U+6390,U+63B0,U+64DE,U+652B,U+6538,U+65BC,U+65CE,U+65D6,U+6631,U+6636,U+6654,U+6666,U+6684,U+677C,U+67D8,U+67DE,U+692D,U+6979,U+6B59,U+6C69,U+6C74,U+6CAE,U+6CE0,U+6D54,U+6DD6,U+6DDE,U+6DE6,U+6EB4,U+6F15,U+6F29,U+70C3,U+715C,U+7172,U+71B9,U+7266,U+728A,U+7329,U+7396,U+73B7,U+73CF,U+73E9,U+7425,U+742C,U+742E,U+7455,U+74EE,U+7519,U+7525,U+753A,U+75B5,U+75B9,U+75D4,U+75DE,U+7656,U+765C,U+768E,U+7691,U+7729,U+777D,U+7825,U+783E,U+787C,U+795A,U+79ED,U+7A79,U+7B06,U+7B3A,U+7B8D,U+7BB4,U+7BD3,U+7BE1,U+7CBC,U+7F30,U+7F42,U+7F8C,U+7FCE,U+7FF1,U+80AE,U+810D,U+81C6,U+822B,U+82A5,U+82AA,U+82DE,U+832F,U+8340,U+8343,U+835E,U+8364,U+836A,U+8543,U+854A,U+8559,U+8568,U+85B0,U+86E4,U+8717,U+87FE,U+8845,U+88F1,U+8902,U+8C12,U+8C82,U+8D32,U+8D58,U+8DBE,U+8E7C,U+8F95,U+8FAB,U+9035,U+90A1,U+9149,U+9157,U+948E,U+949A,U+94C2,U+94C6,U+94F1,U+956F-9570,U+95EB,U+95F0,U+9611,U+9642,U+968D,U+9706,U+970F,U+97ED,U+988A,U+98E8,U+99A5,U+9A77,U+9CDD,U+9E2F,U+9E33,U+9E44,U+9EDD} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/34.woff2") format("woff2");unicode-range:U+4F2B,U+4F58,U+4FAC,U+4FD0,U+5055,U+5156,U+51BD,U+5243,U+5323,U+535E,U+53AE,U+53F1,U+540F,U+542D,U+5431,U+545B,U+547B,U+548E,U+5492,U+54AB,U+54FD,U+5509,U+5567,U+5600,U+5618,U+563F,U+56F1,U+56FF,U+573B,U+574D,U+579B,U+57A0,U+57B8,U+57C2,U+586C,U+58F9,U+5993,U+5996,U+59E3,U+5A29,U+5B62,U+5B7A,U+5BA6,U+5C49,U+5C4E,U+5C96,U+5CE5,U+5E44,U+5F99,U+6043,U+6059,U+606C,U+60B8,U+60ED,U+60F0,U+61A9,U+62A8,U+62FD,U+630E,U+6342,U+63A3,U+63AC,U+6402,U+642A,U+64A9,U+64AE,U+64B7,U+6525,U+659F,U+65EE-65EF,U+66F3,U+67B8,U+67E0,U+683E,U+6886,U+68B5,U+6963,U+6988,U+69DF,U+6AAC,U+6B86,U+6B9A,U+6C50,U+6C76,U+6C85,U+6C8C,U+6CEF,U+6D19,U+6D52,U+6D95,U+6DA7,U+6DB8,U+6DC5,U+6DFC,U+6E1A,U+6E25,U+6E4D,U+6EA7,U+6EC1,U+6F31,U+6F7A,U+6FA7,U+6FE1,U+7096,U+70F7,U+7131,U+7166,U+72E1,U+7317,U+731D,U+736D,U+73C2,U+740F,U+7490,U+74EF,U+7663,U+76BF,U+76D4,U+7728,U+776C,U+77A0,U+77F8,U+77FE,U+78D0,U+7A37,U+7A92,U+7ABF,U+7AFA,U+7B4F,U+7B60,U+7B75,U+7CEF,U+7EDB,U+7F1C,U+7FB2,U+7FB9,U+7FE1,U+808B,U+80E5,U+8113,U+81C3,U+82D4,U+8309,U+8317,U+8431,U+8469,U+84BF,U+84DF,U+8511,U+853A,U+85B9,U+8638,U+864F,U+86C0,U+8715,U+8822,U+8882,U+8884-8885,U+8892,U+88E8,U+892A,U+8983,U+8BA5,U+8BD9,U+8BE0-8BE1,U+8BE7,U+8C1A,U+8C1F,U+8C27,U+8C5A,U+8E0C,U+8E52,U+8E74,U+8E87,U+8F98,U+8FC2,U+8FE6,U+900D,U+9068,U+9074,U+90B3,U+90E7,U+915A,U+916E,U+93D6,U+949D,U+94B4,U+94D0,U+94E3-94E4,U+94EC,U+9522,U+9556,U+9619,U+9893,U+9981,U+9A6E,U+9A87,U+9A8A,U+9AA1,U+9ABC,U+9B13,U+9E3E,U+9E43,U+9E5C,U+9E6D,U+9E9D,U+9EDB} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/33.woff2") format("woff2");unicode-range:U+4E56,U+4E98,U+4F3A,U+4F5F,U+4F6C,U+4F70,U+4FA5,U+4FE8,U+5014,U+524C,U+5315,U+5364,U+53E8,U+5406,U+549A,U+54E6,U+54EE,U+54FC,U+556A,U+5583,U+55E3,U+5669,U+592F,U+5944,U+595A,U+598D,U+59CA,U+59D7,U+5A7F,U+5B09,U+5E4C,U+5EB6,U+5F08,U+5F1B,U+602F,U+607A,U+608D,U+60CB,U+620C,U+6249,U+62A0,U+62C4,U+62ED,U+62F4,U+6345,U+6382,U+6396,U+63C9,U+63EA,U+6400,U+6413,U+645E,U+6487,U+64AC,U+6556,U+6593,U+65A1,U+6615,U+667E,U+6805,U+68A2,U+69DB,U+6A80,U+6B83,U+6C30,U+6C5E,U+6CAD,U+6CDE,U+6CF1,U+6D31,U+6D93,U+6DAE,U+6DBF,U+6E5F,U+6E85,U+6F5E,U+6F7C,U+701B,U+70AB,U+717D,U+71A8,U+7252,U+72B7,U+72C4,U+72E9,U+7315,U+73AE,U+73BA,U+73C0,U+73D1,U+7435-7436,U+745A,U+74E2,U+7504,U+752C-752D,U+7599,U+759F,U+75A4,U+75F0,U+7600-7601,U+761F,U+7629,U+768B,U+772F,U+77A5,U+77B0,U+783A,U+78D5,U+78FA,U+7934,U+7940,U+7977,U+7B19,U+7B71,U+7B94,U+7CB1,U+7CE0,U+7ECA,U+7EF7,U+7F28,U+7F2B,U+7F81,U+80EB,U+8110,U+812F,U+8148,U+814C,U+8165,U+819B,U+81BA,U+8235,U+828B,U+82AE,U+82B7,U+830E,U+831C,U+8338,U+837C,U+8393,U+83BA,U+83C1,U+84D3,U+84E6,U+853C,U+8654,U+86DB,U+86FE,U+8712,U+873F,U+874E,U+8757,U+8783,U+8859,U+88A4,U+88F3,U+8925,U+8BB7,U+8BFD,U+8BFF,U+8C1B,U+8C24,U+8C2C,U+8D4A,U+8D4E,U+8D61,U+8D66,U+8DDA,U+8F76,U+8F8A,U+8FE5,U+8FF8,U+90AC,U+90B8,U+9119,U+912F,U+9131,U+9163,U+9170,U+949B,U+94A8,U+94C0,U+94E2,U+94FF,U+9525,U+9535,U+9539,U+9550,U+955B,U+965B,U+96CC,U+96F9,U+9774,U+988D,U+9975,U+998D,U+998F,U+9A7F,U+9AB8,U+9C7F,U+9C9F,U+9CA4,U+9E35,U+9E8B,U+9E92,U+9EEF} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/32.woff2") format("woff2");unicode-range:U+4E10,U+4EA5,U+4EC3,U+4F36,U+4F84,U+4F88,U+4F8D,U+4F97,U+501A,U+504C,U+5201,U+52FA,U+5366,U+543C,U+545C,U+5471,U+5480,U+5495,U+54B3,U+54C7,U+54DF,U+557C,U+55DC,U+5632,U+566C,U+5676,U+56B7,U+56DA,U+56E4,U+5742,U+57A2-57A3,U+5815,U+58D1,U+5919,U+592D,U+5955,U+5983,U+59E5,U+5A13,U+5A34,U+5B70,U+5BD0,U+5BF0,U+5C41,U+5C94,U+5CE6,U+5D69,U+5DC5,U+5E16,U+5EB5,U+5F3C,U+5F95,U+604D,U+60E6,U+618B,U+61A7,U+61AC,U+61CB,U+62CE,U+62E7,U+62EE,U+631F,U+634E,U+6479,U+6482,U+655B,U+660A,U+664F,U+66E6,U+672D,U+675E,U+67C4,U+67DA,U+6809,U+6868,U+68F1,U+68FA,U+693F,U+695E,U+69AD,U+6A71,U+6A90,U+6B89,U+6BD9,U+6BE1,U+6C40,U+6CB1,U+6D85,U+6DA3,U+6DAA,U+6DC6-6DC7,U+6E0D,U+6E3A,U+6E43,U+6EBA,U+6F3E,U+6F88,U+6FEE,U+70D9,U+7109,U+71EE,U+722A,U+733F,U+73F2,U+7409,U+7459,U+747E,U+7487,U+748B,U+749E,U+759A,U+75A1,U+75CA,U+75D2,U+75EB,U+766B,U+7738,U+773A,U+777F,U+7785,U+77AA,U+77BF,U+78BE,U+795B,U+796F,U+79A7,U+7A20,U+7A96,U+7AA5,U+7B03,U+7B28,U+7B50,U+7B77,U+7BF1,U+7C38,U+7C95,U+7EAD,U+7EC5,U+7EF0,U+7EFD,U+7F0E,U+7F2E,U+7F9A,U+7FCC,U+8046,U+8098,U+80DA,U+80E7,U+80F0,U+8237-8238,U+8299,U+82CE,U+82D3,U+8301,U+8398,U+83E0,U+83E9,U+8426,U+8475,U+85E9,U+86DF,U+871A,U+8910,U+891A,U+8BB4,U+8BE3,U+8BEC,U+8BF2,U+8C06,U+8C0D,U+8C19,U+8DB4,U+8DE4,U+8E1E,U+8E66,U+8E6C,U+8F84,U+901E,U+9083,U+90E1,U+90F4,U+9165,U+91C9,U+91DC,U+94B3,U+94E8,U+9504,U+9530,U+9532,U+954C-954D,U+95FA,U+962A,U+9685,U+9704,U+9776,U+9985,U+9A6F,U+9AA5,U+9CB8,U+9E25,U+9E4A} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/31.woff2") format("woff2");unicode-range:U+4EA2,U+4FA3,U+4FAE,U+4FD8,U+5029,U+516E,U+5195,U+51A5,U+51C4,U+51DB,U+51F8,U+5228,U+527F,U+5288,U+5321,U+5352,U+53E9,U+5450,U+5484,U+5490,U+54C9,U+5501,U+56A3,U+56BC,U+5703,U+5729,U+575E,U+589F,U+5984,U+5A04-5A05,U+5A25,U+5A36,U+5A77,U+5A9B,U+5AB2,U+5B75,U+5BC7,U+5BDD,U+5C51,U+5C79,U+5C91,U+5CD9,U+5CED,U+5E87,U+5F13,U+5F27,U+5F64,U+5FFB,U+6020,U+6055,U+60EB,U+60F6,U+6123,U+6151,U+61A8,U+620D-620E,U+6241,U+6273,U+6289,U+62C7,U+62CC,U+62D9,U+634D,U+6363,U+6452,U+64D2,U+6726,U+6756,U+6789,U+6829,U+6853,U+6862,U+6866,U+6897,U+68E3,U+6942,U+6995,U+69A8,U+69B4,U+69CC,U+6A1F,U+6A44,U+6A59,U+6BCB,U+6BD3,U+6CBD,U+6CF8,U+6CFE,U+6DA1,U+6E0E,U+6E44,U+6EA5,U+6EE6,U+6F2F,U+6F8E,U+7078,U+7095,U+7099,U+709C,U+70AF,U+70BD,U+70DB,U+70E8,U+70FD,U+714E,U+715E,U+7184,U+71CE,U+7235,U+7239,U+72F8,U+749C,U+74A8,U+74E3,U+75AE,U+75F9,U+7620,U+7693,U+76CF,U+776B,U+778E,U+7852,U+7948,U+7960,U+797A,U+79BA,U+79BE,U+79C3,U+79C6,U+79F8,U+7AA6,U+7BC6,U+7C27,U+7CD9,U+7D0A,U+7D6E,U+7EE2,U+7EEE,U+7F24,U+7F2D,U+7F79,U+800D,U+80F3,U+80FA,U+8151,U+818A,U+81B3,U+81E7,U+82B8,U+8339,U+837B,U+8386,U+83BD,U+83CF,U+85C9,U+85D5,U+85DC,U+8747,U+8749,U+8774,U+888D,U+88F4,U+8912,U+8B6C,U+8C0E,U+8D31,U+8D48,U+8E4A,U+8F97,U+8F99,U+8FE9,U+914C,U+94A0,U+94B0,U+94F5,U+951A,U+952F,U+956D,U+95F5,U+9640,U+9668,U+968B,U+9698,U+96BD,U+96CD,U+96EF,U+9713,U+9773,U+97EC,U+9885,U+9890,U+996A,U+997A,U+9992,U+9A74,U+9A79,U+9A8B,U+9CBB,U+9CC4,U+9CCC} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/30.woff2") format("woff2");unicode-range:U+4E5E,U+4FEF,U+50A3,U+5189,U+51F3,U+51F9,U+52FF-5300,U+532E,U+533F,U+5351,U+53DB,U+53ED,U+543B,U+5455,U+54E9,U+5507,U+5543,U+5578,U+55D3,U+560E,U+5830,U+5962,U+59E8,U+59EC,U+5A07,U+5A23,U+5A3C,U+5A9A,U+5AC2,U+5AD6,U+5C2C,U+5C34,U+5CB1,U+5CE8,U+5D0E,U+5DEB,U+5E3C,U+5ED3,U+5F87,U+5FE1,U+5FF1,U+6064,U+606A,U+6096,U+60DF,U+60F9,U+6252,U+627C,U+62C2,U+62E3,U+62EF,U+634F,U+6361,U+63B7,U+63E3,U+6405,U+6495,U+6512,U+6518,U+65A9,U+65F7,U+6627,U+66AE,U+6795,U+6813,U+6893,U+68D5,U+690E,U+6977,U+6984,U+69BB,U+6A31,U+6B7C,U+6BA1,U+6C13,U+6C72,U+6C79,U+6C81,U+6C90,U+6C93,U+6CA6,U+6CBC,U+6CCC,U+6CD3,U+6CD7,U+6CFB,U+6D4A,U+6D5A,U+6D9F,U+6DA9,U+6DCC,U+6E2D,U+6EAF,U+6F13,U+6FA1,U+7011,U+701A,U+707C,U+70D8,U+71A0,U+7280-7281,U+72D0,U+7357,U+73AB,U+7410,U+745B-745C,U+7480,U+74A7,U+7578,U+7682,U+76B1,U+76CE,U+7736,U+77D7,U+77E2-77E3,U+77EB,U+780C,U+781A,U+789F,U+7941,U+7A8D,U+7A98,U+7A9C,U+7AFF,U+7B1B,U+7BF7,U+7C07,U+7CA5,U+7E82,U+7EAB,U+7EDA,U+7EDE,U+7FD8,U+7FDF,U+7FE9,U+803D,U+80AA,U+8116,U+8155,U+8205,U+821C,U+82C7,U+82DB,U+82EF,U+8304,U+832C,U+8335,U+8446,U+846B,U+8587,U+85AF,U+85FB,U+8611,U+8682,U+868A,U+868C,U+86D9,U+86EE,U+889C,U+88D8,U+88F8,U+8BBD,U+8BF5,U+8C41,U+8C79,U+8D3B-8D3C,U+8D50,U+8DEA,U+8E29,U+8E35,U+8E44,U+8EAC,U+8F8D,U+8FE2,U+9017,U+916F,U+9175-9176,U+918B,U+94CE,U+94F2,U+9541,U+9672,U+9699,U+9761,U+97A0,U+9A6D,U+9A85,U+9A8F,U+9CD7,U+9CDE,U+9E26,U+9E93,U+9F9F} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/29.woff2") format("woff2");unicode-range:U+4E4D,U+4EC7,U+4ED5,U+4F51,U+4F63,U+5018,U+5026,U+508D,U+50DA,U+50E7,U+515C,U+51A4,U+51FF,U+5203,U+5254,U+533E,U+5375,U+53EE,U+5435,U+548B,U+548F,U+54D7,U+54FA,U+5587,U+5598,U+55A7,U+575F-5760,U+576F,U+5777,U+57AE,U+58A9,U+5978,U+5A49,U+5B5A,U+5BB5,U+5BC5,U+5BDE,U+5BE1,U+5C7F,U+5CEA,U+5E18,U+5F57,U+5F5D,U+6016,U+601C,U+6021,U+606D,U+60E7,U+6177,U+61D2,U+625B,U+6296,U+62F1,U+631D,U+63A0,U+6401,U+6467,U+64BC,U+64C2,U+6577,U+6590,U+65A7,U+6643,U+6655-6656,U+6687,U+66DD,U+6714,U+67D1,U+67FF,U+6816,U+68CD,U+68D8,U+68E0,U+6930,U+6960,U+69D0,U+6BB4,U+6BD7,U+6C22,U+6C2E-6C2F,U+6C7E,U+6CA5,U+6CE3,U+6D3C,U+6DE4,U+6DF3,U+6EC7,U+6ED5,U+6F33,U+6F62,U+708A,U+70C1,U+70EB,U+70F9,U+711A,U+716E,U+7194,U+71AC,U+71E5,U+7316,U+7384,U+7405,U+7422,U+742A,U+745F,U+7574,U+75AF,U+75EA,U+763E,U+7741,U+7792,U+77A7,U+77EE,U+77F6,U+785D,U+78C5,U+7985,U+79E4,U+7A1A,U+7AD6,U+7B5D,U+7C3F,U+7C9F,U+7CDF,U+7ECE,U+7EE5,U+7F09,U+7F15,U+7F2A,U+7F38,U+7F9E,U+803B,U+804B,U+80B4,U+813E,U+817A-817B,U+819D,U+81FB,U+82AD,U+82B9,U+82DF,U+8354,U+835F,U+8367,U+83B9,U+841D,U+853D,U+8650,U+865E,U+8700,U+8862,U+88D4,U+895F,U+89C5,U+8A79,U+8BB3,U+8BB6,U+8BC0,U+8BEB,U+8C23,U+8D1E,U+8DCB,U+8E72,U+8E81,U+8EAF,U+9050,U+9091,U+90F8,U+914B,U+948A,U+9499,U+94BE,U+94EE,U+950C,U+9524,U+952D,U+9540,U+9576,U+9600,U+962E,U+9647,U+9661,U+96CF,U+9716,U+97E7,U+98A4,U+9A7C,U+9A86,U+9AE6,U+9CD6,U+9E3D} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/28.woff2") format("woff2");unicode-range:U+4E19,U+4E38,U+4EA8,U+4ED1,U+4F1E,U+4F7C,U+4F83,U+4FA0,U+4FD1,U+4FDE,U+4FFA,U+50BB,U+50F5,U+50FB,U+5162,U+5256,U+5319,U+5320,U+538C,U+5413,U+541F,U+5475,U+54BD,U+54D1,U+5589,U+561B,U+57E0,U+5937,U+5974,U+59AE,U+5A1F,U+5AB3,U+5B99,U+5B9B,U+5BA0,U+5BE5,U+5C09,U+5C27,U+5C38,U+5D16,U+5DCD,U+5DE2,U+5E90,U+5E9A,U+5F26,U+5F8A,U+5F98,U+607C,U+60BC,U+60D5,U+611A,U+614C,U+6254,U+626F,U+6292,U+62B9,U+62E2,U+62FE,U+631A,U+6320,U+6346,U+63BA,U+658B,U+663C,U+6652,U+6674,U+66D9,U+66F0,U+673D,U+67AB,U+6817,U+68A7,U+68AD,U+6986,U+6B47,U+6B49,U+6B6A,U+6C1F,U+6CBE,U+6E1D,U+6E23,U+6E9C,U+6ED4,U+6F4D,U+6F84,U+7130,U+718F,U+72FC,U+7334,U+73CA,U+7426,U+7470,U+7529,U+75D5,U+75F4,U+762B,U+7696,U+7737,U+7766,U+77BB,U+7845,U+788C,U+78CA,U+7901,U+796D,U+79B9,U+79FD,U+7A3C,U+7A57,U+7A74,U+7A9F,U+7B0B,U+7C7D,U+7CAA,U+7CB9,U+7CD5,U+7EAC,U+7EB6,U+7F20,U+7F69,U+7FA1,U+7FC5,U+8018,U+8038,U+803F,U+80A2,U+80BE,U+80D6,U+81ED,U+820C,U+829C,U+82AF,U+82BD,U+82D1,U+8327,U+836B,U+839E,U+8403,U+840C,U+840E,U+8471,U+849C,U+84B2,U+84C9,U+8517,U+851A,U+8549,U+8681,U+8721,U+8776,U+87F9,U+88D9,U+88F9,U+8C1C,U+8C34,U+8D2E,U+8D43,U+8D81,U+8D9F,U+8E0A,U+8E22,U+8EB2,U+8F69,U+901B,U+902E,U+9038,U+906E,U+90AA,U+90AF,U+90DD,U+915D,U+9171,U+946B,U+9489,U+94A7,U+9508,U+9523,U+9551,U+95F7,U+964C,U+96B6,U+96C0,U+9756,U+97F6,U+98A0,U+9965,U+997F,U+99A8,U+9A73,U+9A82,U+9AD3,U+9ED4} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/27.woff2") format("woff2");unicode-range:U+4E53,U+4E7E,U+4E9F,U+4EC6,U+4F50,U+502A,U+5154,U+517D,U+51D1,U+51F0,U+5239,U+52C9,U+52FE,U+5308,U+532A,U+535C,U+5384,U+53A2,U+53A8,U+5496,U+54AC,U+54C0,U+54C4,U+54E8,U+5561,U+5566,U+5582,U+5631,U+5764,U+576A,U+5792,U+57AB,U+584C,U+5885,U+58F6,U+5ACC,U+5BC2,U+5C39,U+5C60,U+5C6F,U+5C82,U+5C90,U+5E37,U+5EB8,U+5F6A,U+5FCC,U+6012,U+6068,U+6073,U+6094,U+6109,U+621A,U+626E,U+6284,U+62D0,U+6321,U+6328,U+632B,U+6349,U+6454,U+6591,U+65ED,U+660F,U+66A8,U+6749,U+674F,U+6760,U+67AF,U+67EF,U+6850,U+6854,U+6869,U+68A8,U+68D2,U+6912,U+6BEF,U+6C28,U+6C5D,U+6C82,U+6CAB,U+6CB8,U+6CF5,U+6D47,U+6D74,U+6D78,U+6D9D,U+6DA4,U+6DC4,U+6DCB,U+6DF9,U+6E0A,U+6E5B,U+6E89,U+6EB6,U+6F06,U+6F47,U+6F9C,U+6FD2,U+7076,U+70AC,U+70EF,U+7199,U+723D,U+72AC,U+72ED,U+7476,U+752B,U+754F,U+7554,U+7626,U+76EF,U+7720,U+7784,U+77AC,U+780D,U+7838,U+786B,U+78B3,U+7978,U+7984,U+79C9,U+79E7,U+7A3D,U+7A84,U+7B52,U+7F1A,U+7FF0,U+8086-8087,U+808C,U+809A,U+80BA,U+810A,U+8180,U+818F,U+81C2,U+8231,U+8292,U+82A6,U+8346,U+838E,U+83C7,U+83F1,U+8513,U+857E,U+859B,U+85E4,U+867E,U+871C,U+87BA,U+884D,U+8944,U+8A93,U+8C05,U+8C10,U+8D2C,U+8D42,U+8DFB,U+8EBA,U+8F9C,U+8FA3,U+8FA8,U+8FB1,U+9042,U+904F,U+90B5,U+9102,U+9187,U+94C5,U+95F8,U+95FD,U+960E,U+964B,U+96C1,U+9709,U+971C,U+97AD,U+9882,U+9976,U+9A9A,U+9B3C,U+9B41,U+9C8D,U+9E45,U+9E70,U+9E9F,U+9F3B,U+9F7F} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/26.woff2") format("woff2");unicode-range:U+4E11,U+4ED7,U+4FE9,U+4FED,U+50B2,U+5112,U+5180,U+5188,U+51F6,U+522E,U+5265,U+52CB,U+52DF,U+5349,U+5367,U+5378,U+5395,U+5398,U+53D4,U+543E,U+5446,U+54B8,U+5580,U+55BB,U+56CA,U+574E,U+5782,U+5784,U+58F3,U+5938,U+5948,U+594E,U+5A74,U+5AE9,U+5B55,U+5B5C,U+5BB0,U+5BD3,U+5D14,U+5D2D,U+5DF7,U+5DFE,U+5E05-5E06,U+5E1C,U+5E62,U+5E7B,U+5E7D,U+5ED6,U+5F2F,U+5F66,U+5F6C,U+5FA1,U+604B,U+60A6,U+60E8,U+6101,U+6124,U+6127,U+6148,U+61BE,U+6247,U+62D8,U+633D,U+635E,U+6367,U+6380,U+638F,U+63A9,U+64E6,U+655E,U+6691,U+6735,U+67A2-67A3,U+6905,U+6A0A,U+6A61,U+6B79,U+6BB7,U+6BBF,U+6C41,U+6C55,U+6C83,U+6C9B,U+6CA7,U+6CFC,U+6D46,U+6D51,U+6DAF,U+6DC0,U+6DEB,U+6E17,U+6E24,U+6EA2,U+6EF4,U+6F6D,U+70B3,U+70E4,U+710A,U+725F,U+7261,U+72EE,U+72F1,U+730E,U+732B,U+7433,U+7538,U+75BC,U+7624,U+7750,U+7779,U+7802,U+7898,U+78A7,U+78B1,U+78CB,U+78F7,U+7AED,U+7B3C,U+7B4B,U+7C98,U+7CA4,U+7EB9,U+7EE3,U+7EF3,U+7EF5,U+7F05,U+7F55,U+7FC1,U+7FD4,U+7FE0,U+8042,U+806A,U+80A0,U+80C3,U+8102,U+8106,U+814A,U+8154,U+8247,U+8258,U+8328,U+832B,U+8389,U+845B,U+846C,U+84B8,U+8574,U+8680,U+8695,U+86C7,U+8702,U+886C,U+8896,U+88B1,U+88E4,U+8BC8,U+8C26,U+8C2D,U+8D4C,U+8D63,U+8F67,U+8FC4,U+9006,U+9063,U+90A2,U+9177,U+9189,U+9493,U+949E,U+9610,U+961C,U+96A7,U+96FE,U+978D,U+97F5,U+997C,U+9A84,U+9B44-9B45,U+9B54,U+9E64,U+9F9A} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/25.woff2") format("woff2");unicode-range:U+4E18,U+4E1B,U+4E52,U+4EAD,U+4EF0,U+4FA6,U+4FCF,U+5076,U+50AC,U+5179,U+51EF,U+526A,U+529D,U+5306,U+537F,U+53D9,U+540A,U+5439,U+5440,U+54ED,U+558A,U+55B7,U+5634,U+572D,U+574A,U+57A6,U+57CB,U+5835,U+58E4,U+5951,U+5986,U+59DA,U+59FB,U+59FF,U+5A03,U+5A1C,U+5A46,U+5AC1,U+5B5D,U+5BF8,U+5C3F,U+5C48,U+5C61,U+5CB3,U+5E15,U+5E3D,U+5E99,U+5E9E,U+5F0A,U+5F17-5F18,U+5F25,U+5F7C,U+5FCD,U+6028,U+609F-60A0,U+60AC,U+614E,U+6155,U+6168,U+61C8,U+6212,U+6251,U+62AB-62AC,U+62DA,U+6323,U+63FD,U+6492,U+64B0,U+64C5,U+6572,U+6602,U+6614,U+662D,U+6664,U+6746,U+67D4,U+680B,U+682A,U+68DA,U+6B3A,U+6B67,U+6C5B,U+6C70,U+6CAA,U+6CE1,U+6D12,U+6DD1,U+6E34,U+6E7F,U+6F02,U+707F,U+7092,U+7115,U+7237,U+727A,U+72C2,U+739B,U+73B2,U+743C,U+751C,U+758F,U+75B2,U+7686,U+76C6,U+76D2,U+76FC,U+7709,U+77A9,U+788E,U+7897,U+79BD,U+7A83,U+7A91,U+7A9D,U+7AE3,U+7BAD,U+7D2B,U+7EB1,U+7F06,U+7F14,U+7F1D,U+7F50,U+7F62,U+7FFC,U+8036,U+80A4,U+80BF,U+80CE,U+80F8,U+810F,U+8179,U+819C,U+821F,U+829D,U+82CD,U+82F9,U+8305,U+83B2,U+83CA,U+8404,U+840D,U+8427,U+8679,U+8854,U+886B,U+8BBC,U+8C28,U+8C6B,U+8E48,U+8F90,U+8FB0,U+9022,U+903C,U+90B1,U+90C1,U+916C,U+917F,U+94A9,U+94C3,U+94ED,U+9510,U+953B,U+96C7,U+970D,U+9738,U+97E6,U+9877,U+987D,U+98D8,U+9A70,U+9A91,U+9B42,U+9B4F,U+9E2D,U+9E3F,U+9E7F,U+9F0E,U+9F20} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/24.woff2") format("woff2");unicode-range:U+4E08,U+4E27,U+4E59,U+4E73,U+4ED9,U+4F0F,U+4FAF,U+5085,U+5144,U+5146,U+5151,U+51AF,U+51BB,U+51C9,U+51CC,U+524A,U+5251,U+52AB,U+5353,U+5389,U+53F9,U+5415,U+541B,U+54F2,U+5524,U+5564,U+575D,U+57D4,U+5821,U+5824,U+582A,U+5858,U+5893,U+5915,U+5960,U+5999,U+59A5,U+59B9,U+59C6,U+59D1,U+59DC,U+5B64,U+5B87,U+5BB4,U+5BFA,U+5C16,U+5C18,U+5C9A,U+5CFB,U+5D1B,U+5DE1,U+5DE9,U+5ECA,U+5EF7,U+5F79,U+6052,U+6084,U+60B2,U+60DC,U+60E9,U+6208,U+6291,U+629A,U+62F3,U+632A,U+640F,U+642C,U+643A,U+6458,U+6478,U+649E,U+6500,U+654C,U+658C,U+659C,U+6676,U+6697,U+679D,U+67CF,U+67DC,U+683D,U+6843,U+684C,U+699C,U+6C0F,U+6C1B,U+6C27,U+6C64,U+6C6A,U+6CCA,U+6D3D,U+6D82,U+6DB5,U+6DEE,U+6E58,U+6ECB,U+6EE5,U+6F20,U+6F58,U+70C2,U+718A,U+7238,U+7262,U+7272,U+72B9,U+72D7,U+72E0,U+741B,U+7434,U+74F7,U+764C,U+775B,U+7761,U+7816,U+7855,U+7891,U+7A00,U+7ED2,U+7EF8,U+7FBD,U+8000,U+8096,U+809D,U+80A9,U+80C1,U+8109,U+8170,U+817F,U+81E3,U+8212,U+8230,U+8236,U+8273,U+82AC,U+8302,U+8361,U+8377,U+83CC,U+83F2,U+8461,U+8482,U+84EC,U+8521,U+8870,U+8877,U+8881,U+88AD,U+88C2,U+8BF1,U+8D24,U+8D2A,U+8D3E-8D3F,U+8D41,U+8D64,U+8DCC,U+8E0F,U+8F70,U+8FA9,U+9003,U+901D,U+903E,U+9065,U+90CE,U+94A6,U+94DD,U+94F8,U+95EA,U+95EF,U+9601,U+96D5,U+971E,U+9896-9897,U+9A7E,U+9E1F} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/23.woff2") format("woff2");unicode-range:U+4E43,U+4EA6,U+4EF2,U+4EFF,U+4F69,U+4FD7,U+4FF1,U+5021,U+5077,U+51B6,U+51DD,U+51E4,U+5200,U+523A,U+52D8,U+5401,U+54A8,U+559D,U+5609,U+5723,U+575B,U+57C3,U+5899,U+58C1,U+5976,U+5988,U+59D0,U+5A18,U+5A31,U+5B54,U+5B5F,U+5B85,U+5BD2,U+5BE8,U+5BFF,U+5C65,U+5CA9,U+5CAD,U+5D07,U+5E1D,U+5E7C,U+5F6D,U+5F70,U+5F92,U+6050,U+6108,U+6167,U+6170,U+6234,U+626D,U+62B1,U+62BC,U+62D2,U+62DC,U+6324,U+63ED,U+6447,U+64A4,U+6566,U+65C1,U+65E8,U+65EC,U+65F1,U+6606,U+6628,U+6668,U+6696,U+66F9,U+66FC,U+6717,U+6734,U+675C,U+676D,U+679A,U+67AA,U+67EC,U+67F3-67F4,U+6842,U+6851,U+6B32,U+6BC1,U+6BC5,U+6C57,U+6CEA,U+6CF3,U+6D53,U+6D66,U+6D69,U+6D9B,U+6DB2,U+6DE1,U+6EAA,U+6EDE,U+6EE8-6EE9,U+6F2B,U+704C,U+706D,U+7089,U+708E,U+70AE,U+70B8,U+71C3,U+71D5,U+7275,U+73BB,U+7483,U+74DC,U+7532,U+75AB,U+75C7,U+7687,U+76D7,U+788D,U+78E8,U+7A3B,U+7A46,U+7AF9,U+7BEE,U+7CD6,U+7EA4,U+7EB2,U+7EB5,U+7EBD,U+7F18,U+8010,U+8033,U+80C0,U+80F6,U+8138,U+81A8,U+8206,U+827E,U+82B3,U+83B1,U+848B,U+864E,U+888B,U+8BCA,U+8C0A,U+8C46,U+8D29,U+8D4F,U+8D5A,U+8D60,U+8D62,U+8D6B,U+8F7F,U+8F88,U+8F96,U+8F9E-8F9F,U+90B9,U+90CA,U+9178,U+94BB,U+94DC,U+94FA,U+9505,U+950B,U+9521,U+95F9,U+9634,U+966A,U+9675,U+9887,U+9971-9972,U+9A76,U+9A97,U+9E23} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/22.woff2") format("woff2");unicode-range:U+4E01,U+4E39,U+4E4C,U+4E54,U+4E95,U+4EA1,U+4EC1,U+4F10,U+4F26,U+4F2F,U+4F5B,U+4FA8,U+4FB5,U+4FCA,U+5192,U+51AC,U+51B0,U+51C0,U+51E1,U+51ED,U+5211,U+5242,U+52A3,U+52B2,U+52C3,U+52C7,U+52D2,U+5362,U+53A6,U+54E5,U+5510,U+5761,U+5851,U+5854,U+58A8,U+58EE,U+593A,U+5949,U+5954,U+59BB,U+59D3,U+5A5A,U+5B8B,U+5BAA-5BAB,U+5C0A,U+5C4B,U+5CE1,U+5E10,U+5E8A,U+5E9F,U+5EC9,U+5F1F,U+5FBD,U+5FE0,U+5FE7,U+6069,U+60A3,U+60A8,U+624E,U+6263,U+62D4,U+62E8,U+6350,U+63F4,U+6446,U+644A,U+6469,U+6562,U+656C,U+65D7,U+65FA,U+6653,U+676F-6770,U+67F1,U+6881,U+6885,U+68A6,U+68CB,U+68EE,U+6B20,U+6B23,U+6B96,U+6C88,U+6CC9,U+6D17,U+6D1B,U+6D59,U+6D8C,U+6E14,U+6E21,U+70AD,U+70E7,U+714C,U+7259,U+7267,U+731B,U+732A,U+73CD,U+7518,U+755C,U+756A,U+7586,U+76C8,U+76D0,U+76DF,U+76F2,U+77DB,U+795D,U+7965,U+79E6,U+79E9,U+7A3F,U+7A77,U+7B11,U+7C89,U+7EA0,U+7F34,U+7F8A,U+8015,U+8083,U+80C6,U+80DE,U+8150,U+820D,U+82D7,U+8352,U+83AB,U+8428,U+8463,U+84C4,U+852C,U+8584,U+86CB,U+88D5,U+8BDA,U+8BFA,U+8C0B,U+8C6A,U+8C8C,U+8D3A,U+8D54,U+8D74,U+8D76,U+8F9B,U+8FBD,U+8FC8,U+8FEA-8FEB,U+8FF9,U+906D,U+90A6,U+90ED,U+91CE,U+9526,U+9614,U+9655,U+9676,U+9686,U+96EA,U+9707,U+978B,U+9881,U+9E21,U+9EA6,U+9ECE,U+9F84} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/21.woff2") format("woff2");unicode-range:U+4E3D,U+4E8F,U+4EA9,U+4EEA,U+4F0A,U+4F0D,U+4F11,U+4F1F,U+4FC4,U+503A,U+507F,U+5170,U+51A0,U+520A,U+5238,U+5267,U+52E4,U+5348,U+5371,U+5385,U+539A,U+5409,U+5428,U+5434,U+547C,U+5531,U+56ED,U+5733,U+5766,U+5802,U+590F,U+591C,U+594B,U+5965,U+5987,U+5B59,U+5B63,U+5B81,U+5B97,U+5B9C,U+5BBE,U+5C1A,U+5C3C,U+5C81,U+5C9B,U+5CB8,U+5DDD,U+5E84,U+5E86,U+5EA7,U+5EAD,U+5F90,U+6000,U+60E0,U+626C,U+6276,U+6297,U+62B5,U+632F,U+6551,U+65A4,U+660C,U+671D,U+6728,U+6731,U+6768,U+6865,U+68B0,U+68C9,U+690D,U+697C,U+6B27,U+6B8B,U+6BD2,U+6BDB,U+6C49,U+6C61,U+6C7D,U+6C99,U+6CE5,U+6CF0,U+6CFD,U+6D0B,U+6D25,U+6D2A,U+6DA6,U+6DA8,U+6E29,U+6E56,U+6E7E,U+6F6E,U+6FB3,U+707E,U+70DF,U+7164,U+7389,U+73E0,U+745E,U+74E6,U+7530,U+7537,U+7597,U+75BE,U+76AE,U+76DB,U+76FE,U+7763,U+77FF,U+793C,U+7956,U+7981,U+798F,U+79CB,U+79D8,U+79DF,U+7AE5,U+7B51,U+7B79,U+7EB7-7EB8,U+7EBA,U+7F5A,U+7F6A,U+8089,U+80A5,U+80E1,U+821E,U+8239,U+8270,U+82CF,U+8336,U+8349,U+8499,U+8840,U+8857,U+8863,U+8A89,U+8D2B,U+8D2F,U+8D37,U+8D75,U+8F68,U+8F86,U+8F89,U+8FDD,U+9093,U+90D1,U+9152,U+94A2,U+9646,U+96C4,U+96E8,U+96F7,U+97E9,U+996D-996E,U+9986,U+9A7B,U+9AA8,U+9C7C,U+9C81,U+9E4F,U+9F99} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/20.woff2") format("woff2");unicode-range:U+4E03,U+4E1D,U+4E4F,U+4E5D,U+4E61,U+4E88,U+4EAC,U+4F19,U+4F24,U+4F2A,U+4FC3,U+515A,U+516B,U+5175,U+5197,U+519B-519C,U+51B7,U+5218,U+52B3,U+533B,U+5357,U+536B,U+53BF,U+53E0,U+53E4,U+53EC,U+5565,U+566A,U+5706,U+571F,U+592E,U+5939,U+594F,U+59A8,U+59D4,U+5A01,U+5B88,U+5BA3-5BA4,U+5BBF,U+5C3A,U+5C45,U+5C4A,U+5DDE,U+5E2D,U+5E78,U+5E9C,U+5EB7,U+5F04,U+5F7B,U+5FB7,U+5FD9,U+602A,U+6076,U+60CA,U+6233,U+6258,U+6270,U+62A2,U+62D6,U+62E6,U+633A,U+6355,U+63AA,U+653F,U+6597,U+65A5,U+65C5,U+65CB,U+65CF,U+6625,U+664B,U+665A,U+672B,U+6751,U+6797,U+68AF,U+68F5,U+69FD,U+6A2A,U+6B66,U+6C5F,U+6C89,U+6CB3,U+6CB9,U+6CBF,U+6CC4,U+6D01,U+6D32,U+6D45,U+6D4E,U+6D6E,U+6E20,U+6E2F,U+6E83,U+6ED1,U+706F-7070,U+70C8,U+7206,U+72AF,U+731C,U+7545,U+75C5,U+77F3,U+7A0E,U+7B5B,U+7C4D,U+7C73,U+7C97,U+7CAE,U+7CCA,U+7EAA,U+7EFF-7F00,U+7F57,U+80B2,U+80DC,U+8131,U+822A,U+8363,U+836F,U+83DC,U+85AA,U+88C1,U+8BDE,U+8BF8,U+8C31,U+8D21,U+8D35,U+8D38,U+8D5B,U+8D5E,U+8F74,U+8FC5,U+8FF7,U+900A,U+9080,U+90BB,U+94C1,U+9547,U+95FB,U+9608,U+9633,U+9644,U+9648,U+9662,U+9752,U+9769,U+98DF,U+9910,U+9970,U+9999,U+9C9C,U+9EC4,U+9F13,U+FF5E} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/19.woff2") format("woff2");unicode-range:U+251C,U+4E30,U+4E58,U+4E71,U+4E9A,U+4EAE,U+4EB2,U+4EBF,U+4ED3-4ED4,U+4F34,U+4F38,U+4F73,U+5012,U+503E,U+5065,U+516D,U+517B,U+5237,U+5269,U+5272,U+52AA,U+5317,U+534E,U+5377,U+53C9,U+53F6,U+5403,U+5410,U+541E,U+5438,U+5448,U+54B1,U+554A,U+5708,U+573E,U+5750-5751,U+575A,U+5783,U+57CE,U+57F9,U+586B,U+58EB,U+592B,U+5956,U+5973,U+5979,U+5A92,U+5B69,U+5B8F,U+5BB3,U+5C71,U+5C97,U+5CF0,U+5D29,U+5DF4,U+5E55,U+5E97,U+5F31,U+5F39,U+5F69,U+5FC6,U+5FD8,U+5FFD,U+6015,U+6025,U+6070,U+60D1,U+6298,U+629B,U+62D3,U+62F7,U+62FC,U+6316,U+6398,U+6491,U+65E7,U+661F,U+6682,U+66F2,U+6740,U+674E,U+6750,U+6876,U+6B4C,U+6BCD,U+6BEB,U+6C11,U+6C38,U+6CBB,U+6CE2,U+6D3E,U+6D6A,U+6DD8,U+6EDA,U+6F5C,U+70BC,U+70E6,U+725B,U+732E,U+738B,U+73ED,U+75DB,U+78B0,U+7A0D,U+7A7F,U+7B14,U+7BB1,U+7C92,U+7EB3,U+7ED1,U+7EEA,U+7EFC,U+8026,U+8058,U+80A1,U+817E,U+826F,U+827A,U+82E5-82E6,U+8457,U+84DD,U+85CF,U+866B,U+8B66,U+8BAF,U+8BD7,U+8C37,U+8D22,U+8D27,U+8D34,U+8D8B,U+8DC3,U+8F85,U+9014,U+9057,U+9075,U+90AE,U+9192,U+9274,U+94AE,U+94F6,U+95F2,U+96C5,U+987F,U+9888,U+989C,U+98DE,U+9EBB,U+9F50} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/18.woff2") format("woff2");unicode-range:U+4E22,U+4E94,U+4ECD,U+4F17,U+4FA7,U+500D,U+5141,U+514B,U+517C,U+526F,U+52B1,U+5343,U+5356,U+535A,U+5382,U+53F2,U+552E-552F,U+5584,U+56F0,U+56FA,U+574F,U+5947,U+5B9D,U+5BA1,U+5BBD,U+5BC4,U+5BCC,U+5BFB,U+5C14,U+5C24,U+5C3E,U+5C4F,U+5D4C,U+5DE7-5DE8,U+5E01,U+5E27,U+5E45,U+5F03,U+5F8B,U+6062,U+6089,U+60EF,U+613F,U+61C2,U+622A,U+626B,U+6279,U+6293,U+62C5,U+62C9,U+62DB,U+62E5,U+6302,U+6325,U+635F,U+6377,U+6388,U+641E,U+6444,U+653B,U+654F,U+6563,U+65AF,U+65E6,U+6670,U+66B4,U+66FE,U+677E,U+6821,U+68B3,U+6B3E,U+6B7B,U+6B8A,U+6C14,U+6C60,U+6D1E,U+6D77,U+6DF7,U+6E10,U+6EE4,U+6F0F,U+6FC0,U+706B,U+7126,U+722C,U+7231,U+7236,U+724C,U+73A9,U+7403,U+74F6,U+7533,U+767B,U+76D6,U+7701,U+773C,U+77E9,U+7834,U+78C1,U+7968,U+79C0-79C1,U+7A97,U+7ADE-7ADF,U+7D27,U+7D2F,U+7E41,U+7EA2,U+7EAF,U+7ED5,U+7ED8,U+7EE9,U+7FFB,U+82F1,U+878D,U+8986,U+8C01,U+8C13,U+8C22,U+8D1D,U+8D26,U+8D4B,U+8DD1,U+8DDD,U+8DE8,U+8E2A,U+8F6E,U+8FC1,U+8FDF,U+8FFD,U+9000,U+900F,U+94A5,U+955C,U+9632,U+9635,U+963B,U+963F,U+9677,U+9690,U+96F6,U+9732,U+9988} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/17.woff2") format("woff2");unicode-range:U+2500,U+2502,U+4E16,U+4E1C,U+4E25,U+4E34,U+4E45,U+4E4E,U+4E50,U+4E70,U+4E89,U+4F30,U+4F4F,U+4F59,U+501F,U+504F,U+505C,U+513F,U+5145,U+5149,U+5174,U+518C,U+51B2,U+5220,U+523B,U+5339,U+534A,U+5360-5361,U+5370,U+5374,U+53CC,U+53F3,U+5427,U+5473,U+54C8,U+559C,U+5806,U+585E,U+5B98,U+5BDF,U+5C01,U+5C04,U+5C1D,U+5DE6,U+5E72,U+5EF6,U+5F84,U+6162,U+620F,U+623F,U+627F,U+62BD,U+62C6,U+62CD,U+62FF,U+6311,U+6389,U+63A2,U+641C,U+642D,U+64AD,U+64CE,U+6559,U+6599,U+65BD,U+65E2,U+65E9,U+6620,U+666E,U+66FF,U+6708,U+67D3,U+6B62,U+6BD5,U+6C47,U+6C9F,U+6CDB,U+6D4F,U+6D89,U+6DFB,U+6E32,U+6F14,U+7075,U+70ED,U+7591,U+767D-767E,U+76CA,U+793E,U+795E,U+79D1-79D2,U+7A33,U+7A81,U+7B7E,U+7EC7,U+7EDD,U+7F29,U+7F8E,U+8017,U+805A,U+80AF,U+8111,U+811A,U+8425,U+843D,U+8861,U+8865,U+897F,U+89C8,U+89E6,U+8BA2,U+8BC9,U+8C08,U+8D25,U+8D2D,U+8D70,U+8DA3,U+8DF3,U+8F66,U+8F7B,U+8FED,U+9010,U+904D,U+949F,U+94B1,U+95ED,U+9669,U+9694,U+9759,U+9760,U+9876,U+987E,U+989D,U+9A6C,U+9A71,U+9AA4,U+9ED1,U+9ED8} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/16.woff2") format("woff2");unicode-range:U+4E07,U+4E32,U+4E3E,U+4E66,U+4E91,U+4ED8,U+4EFD,U+4F01,U+5047,U+514D,U+5178,U+51CF,U+51FB,U+5207,U+5212,U+521A,U+5224,U+529E,U+52BF,U+5341,U+5386,U+538B,U+53CB,U+53D7,U+53EB,U+5417,U+542C,U+5468,U+56DB,U+56F4,U+56FD,U+5740,U+5747,U+58F0,U+592A,U+5957,U+5BC6,U+5C3D,U+5C40,U+5E02,U+5F20,U+5F52,U+5F81,U+5F85,U+5FAA,U+5FD7,U+5FF5,U+6218,U+6269,U+6295,U+62A4-62A5,U+62DF,U+62EC,U+638C,U+63CF,U+63D2,U+63E1,U+6545,U+667A,U+670B,U+671B,U+672A,U+675F,U+677F,U+6781,U+6808,U+680F,U+6811,U+6863,U+695A,U+6C34,U+6E38,U+6EE1,U+719F,U+72EC,U+751A,U+7535,U+753B,U+76D1,U+76D8,U+77ED,U+7814,U+786C,U+79EF-79F0,U+79FB,U+7A76,U+7AD9,U+7CBE,U+7EA6,U+7EC3,U+7EE7,U+7F3A,U+7F72,U+7FA4,U+8001,U+804A,U+804C,U+80CC,U+822C,U+82B1,U+8303,U+8350,U+8651,U+865A,U+867D,U+8BA8,U+8BAD,U+8BB8,U+8BC4,U+8BE6,U+8D23,U+8D56,U+8D85,U+8DF5,U+8FB9,U+8FD1,U+8FDC,U+9012,U+9047,U+907F,U+91CA,U+91D1,U+9500-9501,U+9605,U+964D,U+969C,U+97F3,U+987A-987B,U+98CE} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/15.woff2") format("woff2");unicode-range:U+4E13,U+4E92,U+4EC5,U+4ECB,U+4EF7,U+4F3C,U+4F4E,U+4F9D,U+4FBF,U+4FEE,U+5171,U+521D,U+5229,U+52A9,U+5347,U+5373,U+53C8,U+53CD,U+53E5-53E6,U+53F7,U+542B,U+542F,U+544A,U+5546,U+5883,U+5931,U+5934,U+5B89,U+5C5E,U+5DEE,U+5E08,U+5E0C,U+5E2E,U+5E7F,U+5E95,U+5F02,U+5F3A,U+5F62,U+5F71,U+5F80,U+5FAE,U+5FC5,U+5FEB,U+62E9,U+6309,U+6392,U+65E5,U+6613,U+663E,U+6742-6743,U+67D0,U+6846,U+68C0,U+6982,U+6B22,U+6D3B,U+6DF1,U+6E05,U+7167,U+7248,U+7269,U+754C,U+7559,U+7565,U+771F,U+7840,U+79BB,U+7A7A,U+7ACB,U+7AE0,U+7B54,U+7B56,U+7BC7,U+7D20,U+7EC6,U+7EC8,U+7ECD,U+7F13,U+8054,U+81F3,U+8272,U+88C5,U+89C1-89C2,U+89C9,U+89D2,U+8BBF,U+8BCD,U+8BD1,U+8BE2,U+8BEF,U+8D1F,U+8D28,U+8D39,U+8D8A,U+8DB3,U+8DDF,U+8EAB,U+8F6F,U+8F7D,U+8FCE,U+8FD4,U+8FDE,U+8FF0,U+9001-9002,U+901F-9020,U+903B,U+91C7,U+9488,U+94FE,U+9519,U+95E8,U+9636,U+9650,U+968F,U+96BE,U+9875,U+9884,U+9886,U+9891,U+9996} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/14.woff2") format("woff2");unicode-range:U+4E09,U+4E14,U+4E8C,U+4EA4,U+4EAB,U+4ECA,U+4EE4,U+4EFB,U+4F20,U+4F4D,U+4F9B,U+4FDD,U+50A8,U+50CF,U+5143,U+51B5,U+51C6,U+51E0,U+5217,U+521B,U+534F,U+53E3,U+53F0,U+53F8,U+5404,U+540D,U+5426,U+5458,U+547D,U+54CD,U+54EA,U+56E2,U+573A,U+5757,U+57DF,U+589E,U+5907,U+591F,U+59CB,U+5BA2,U+5BB6,U+5BFC,U+5C11,U+5C55,U+5DF2,U+5E03,U+5E26,U+5E73-5E74,U+5F15,U+5F55,U+600E,U+603B,U+611F,U+624B,U+624D,U+6253,U+627E,U+6301,U+6362,U+63A7-63A8,U+652F,U+6536,U+6539,U+653E,U+65AD,U+65E0,U+660E,U+666F,U+671F,U+6761,U+6790,U+6838-6839,U+683C,U+6848,U+6D88,U+7247,U+72B6,U+7387,U+73AF,U+7531,U+7740,U+7B26,U+7B80,U+7D22,U+7EA7,U+7EDC,U+7EED,U+7EF4,U+81F4,U+83B7,U+89C4,U+89C6,U+8BA4,U+8BAE,U+8BB0,U+8BBA,U+8BC1,U+8BC6,U+8BDD,U+8BF7,U+8BFB,U+8D44,U+8DEF,U+8F6C,U+8F83,U+8F91,U+8F93,U+8FBE,U+952E,U+957F,U+9645,U+9664,U+9A8C} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/13.woff2") format("woff2");unicode-range:U+4E0E,U+4E3B,U+4E49,U+4E60,U+4EA7,U+4EC0,U+4ED6,U+4F18,U+4F55,U+4FE1,U+5019,U+5148,U+5168,U+516C,U+5177,U+518D,U+5199,U+51B3,U+51FD,U+5219,U+522B,U+5236,U+529B,U+529F,U+5305,U+533A,U+539F,U+53BB,U+53C2,U+53CA,U+53D6,U+5411,U+5462,U+54C1,U+56DE,U+57FA,U+5904,U+590D,U+5916,U+5929,U+5B50,U+5B57,U+5B8C,U+5C06,U+5C0F,U+5C42,U+5DF1,U+5E93,U+5FC3,U+6001,U+601D,U+606F,U+60C5,U+60F3,U+610F,U+6216,U+6267,U+6280,U+6307,U+64CD,U+6548,U+6574,U+672F,U+67B6,U+67E5,U+6B21,U+6B63-6B65,U+6BB5,U+6BCF,U+6CE8,U+6D41,U+6D4B,U+6E90,U+7279,U+76F4,U+77E5,U+786E,U+793A,U+7AEF,U+7BA1,U+7EBF,U+7EC4,U+7F16,U+7F51,U+7F6E,U+8003,U+88AB,U+8A00,U+8BA9,U+8BB2,U+8BE5,U+8BED,U+8BFE,U+8C61,U+8D77,U+8FD0,U+9009,U+9053,U+914D,U+961F,U+96C6,U+975E,U+9879,U+9AD8} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/12.woff2") format("woff2");unicode-range:U+4E1A,U+4E24,U+4E4B,U+4E8B,U+4EBA,U+4ECE,U+4F46,U+4F53,U+4F7F,U+4F8B,U+503C,U+505A,U+5165,U+5173,U+5176,U+5185,U+524D,U+52A0,U+52A8,U+5316,U+5355,U+53D8,U+53EA,U+5408,U+5668,U+56E0,U+56FE,U+5730,U+578B,U+597D,U+5B58,U+5B66,U+5B83,U+5BB9,U+5DE5,U+5E38,U+5E76,U+5E8F,U+5E94,U+5EA6,U+5EFA,U+5F00,U+5F53,U+5F88,U+5F97,U+6237,U+628A,U+63D0,U+6587,U+65B0,U+66F4,U+6700,U+670D,U+672C,U+673A,U+6784,U+6807,U+6837,U+6A21,U+6BD4,U+6C42,U+6CA1,U+7136,U+751F,U+76EE,U+76F8,U+79CD,U+7B2C,U+7B49,U+7B97,U+7C7B,U+7CFB,U+7ECF,U+7ED3,U+7ED9,U+7EDF,U+8005,U+81EA,U+8282,U+8868,U+8BA1,U+8BBE,U+8BD5,U+8BF4,U+8C03,U+8FD8,U+90A3,U+90E8,U+91CC-91CD,U+91CF,U+95F4} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/11.woff2") format("woff2");unicode-range:U+4E00,U+4E0A-4E0B,U+4E0D,U+4E2A,U+4E2D,U+4E3A,U+4E48,U+4E5F,U+4E86,U+4E8E,U+4E9B,U+4EE3,U+4EE5,U+4EEC,U+4EF6,U+4F1A,U+4F5C,U+4F60,U+51FA,U+5206,U+5230,U+52A1,U+53D1,U+53EF,U+540C,U+540E,U+548C,U+5728,U+591A,U+5927,U+5982,U+5B9A,U+5B9E,U+5BF9,U+5C31,U+5F0F,U+6027,U+6210-6211,U+6240,U+636E,U+63A5,U+6570,U+65B9,U+65F6,U+662F,U+6709,U+6765,U+679C,U+6CD5,U+70B9,U+73B0,U+7406,U+7528,U+7684,U+770B,U+7801,U+7A0B,U+800C,U+80FD,U+884C,U+8981,U+89E3,U+8FC7,U+8FD9,U+8FDB,U+901A,U+90FD,U+95EE,U+9700,U+9762,U+9898} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/10.woff2") format("woff2");unicode-range:U+2026,U+3001-3002,U+300A-300B,U+FF01,U+FF08-FF09,U+FF0D,U+FF1A-FF1B,U+FF1F,U+FF5C} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/9.woff2") format("woff2");unicode-range:U+409,U+40F,U+418,U+41F,U+427-428,U+42B} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/8.woff2") format("woff2");unicode-range:U+3CD,U+1F45} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/7.woff2") format("woff2");unicode-range:U+374-375,U+37E,U+384-385,U+387,U+389-38A,U+38F-390,U+393-394,U+39E,U+3A3,U+3A6,U+3A8-3A9,U+3AC,U+3B0-3B1,U+3B3,U+3B6,U+3B8,U+3BA,U+3BD-3BE,U+3C0,U+3C2-3C4,U+3C7,U+3CB,U+3D1,U+3D6,U+402-403,U+40A,U+40E,U+411,U+413-414,U+423-424,U+426,U+429,U+42C,U+431,U+433-434,U+436-437,U+43A,U+442,U+446,U+449-44A,U+44C,U+453,U+459-45A,U+45C,U+462-463,U+46A-46B,U+474-475,U+490-491,U+493,U+497,U+49B,U+49D,U+4A2-4A3,U+4B0-4B1,U+4B6-4B7,U+4BA,U+4CA,U+4EE} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/6.woff2") format("woff2");unicode-range:U+259,U+2B0,U+2B2-2B3,U+2B7-2B8,U+2BB-2BC,U+2C6-2C7,U+2C9-2CB,U+2D8-2DD,U+2E1-2E3} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/5.woff2") format("woff2");unicode-range:U+18A,U+192} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/4.woff2") format("woff2");unicode-range:U+135,U+140} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/3.woff2") format("woff2");unicode-range:U+F0,U+F7} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/2.woff2") format("woff2");unicode-range:U+A0-AA,U+AC-B1,U+B4-BF,U+C3,U+D7} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/1.woff2") format("woff2");unicode-range:U+7B-7F} +@font-face{font-family:"MiSans";font-style:normal;font-weight:300;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/light/0.woff2") format("woff2");unicode-range:U+0-1,U+A,U+D,U+20-7A,U+AB,U+B2-B3,U+C0-C2,U+C4-D6,U+D8-EF,U+F1-F6,U+F8-134,U+136-13F,U+141-17F,U+181,U+18F,U+198-199,U+1A0-1A1,U+1AF-1B0,U+1B3-1B4,U+1B7,U+1CD-1DC,U+1E4-1E9,U+1EE-1F0,U+1F9-1FF,U+218-21B,U+21E-21F,U+237,U+251,U+253,U+257,U+261,U+292,U+386,U+388,U+38C,U+38E,U+391-392,U+395-39D,U+39F-3A1,U+3A4-3A5,U+3A7,U+3AA-3AB,U+3AD-3AF,U+3B2,U+3B4-3B5,U+3B7,U+3B9,U+3BB-3BC,U+3BF,U+3C1,U+3C5-3C6,U+3C8-3CA,U+3CC,U+3CE,U+3D2-3D4,U+400-401,U+404-408,U+40B-40D,U+410,U+412,U+415-417,U+419-41E,U+420-422,U+425,U+42A,U+42D-430,U+432,U+435,U+438-439,U+43B-441,U+443-445,U+447-448,U+44B,U+44D-452,U+454-458,U+45B,U+45D-45F,U+472-473,U+492,U+49A,U+49C,U+4AE-4AF,U+4B2-4B3,U+4B8-4B9,U+4BB,U+4D8-4D9,U+4E2-4E3,U+4E8-4E9,U+4EF,U+1E00-1E01,U+1E3E-1E3F,U+1E80-1E85,U+1E9E,U+1EA0-1EF9,U+2013-2014,U+2018-2019,U+201C-201D,U+2044,U+2070,U+2074-2079,U+2080,U+2082-2089,U+20A8,U+FF0C} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/187.woff2") format("woff2");unicode-range:U+2C361,U+2C364,U+2C488,U+2C494,U+2C497,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+2C8D9,U+2C8DE,U+2C8E1,U+2C8F3,U+2C907,U+2C90A,U+2C91D,U+2CA02,U+2CA0E,U+2CA7D,U+2CAA9,U+2CB29,U+2CB2D-2CB2E,U+2CB31,U+2CB38-2CB39,U+2CB3B,U+2CB3F,U+2CB41,U+2CB4A,U+2CB4E,U+2CB5A-2CB5B,U+2CB64,U+2CB69,U+2CB6C,U+2CB6F,U+2CB73,U+2CB76,U+2CB78,U+2CB7C,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+2CE18,U+2CE1A,U+2CE23,U+2CE26,U+2CE2A,U+2CE7C,U+2CE88,U+2CE93} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/186.woff2") format("woff2");unicode-range:U+9FC6-9FEF,U+F92C,U+F979,U+F995,U+F9E7,U+F9F1,U+FA0C-FA0F,U+FA11,U+FA13-FA14,U+FA18,U+FA1F-FA21,U+FA23-FA24,U+FA27-FA29,U+FE10-FE19,U+FE30-FE31,U+FE33-FE44,U+FE49-FE52,U+FE54-FE57,U+FE59-FE66,U+FE68-FE6B,U+20087,U+20089,U+200CC,U+20164,U+20676,U+20CD0,U+2139A,U+21413,U+215D7,U+2298F,U+235CB,U+23C97-23C98,U+23E23,U+241FE,U+249DB,U+24A7D,U+24AC9,U+25532,U+25562,U+255A8,U+25ED7,U+26221,U+2648D,U+26676,U+2677C,U+26B5C,U+26C21,U+27FF9,U+28408,U+28678,U+28695,U+287E0,U+28B49,U+28C47,U+28C4F,U+28C51,U+28C54,U+28E99,U+29F7E,U+29F83,U+29F8C,U+2A7DD,U+2A8FB,U+2A917,U+2AA30,U+2AA36,U+2AA58,U+2AFA2,U+2B127-2B128,U+2B137-2B138,U+2B1ED,U+2B300,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+2B626-2B628,U+2B62A,U+2B62C,U+2B695-2B696,U+2B6AD,U+2B6ED,U+2B7A9,U+2B7C5,U+2B7E6,U+2B7F9,U+2B7FC,U+2B806,U+2B80A,U+2B81C,U+2B8B8,U+2BAC7,U+2BB5F,U+2BB62,U+2BB7C,U+2BB83,U+2BC1B,U+2BD77,U+2BD87,U+2BDF7,U+2BE29,U+2C029-2C02A,U+2C0A9,U+2C0CA,U+2C1D5,U+2C1D9,U+2C1F9,U+2C27C,U+2C288,U+2C2A4,U+2C317,U+2C35B} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/185.woff2") format("woff2");unicode-range: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-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+9ED2-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-9F16,U+9F18,U+9F1A-9F1F,U+9F21,U+9F23-9F2B,U+9F2D-9F2E,U+9F30-9F33,U+9F35-9F36,U+9F38,U+9F3A,U+9F3C,U+9F3F-9F43,U+9F45-9F49,U+9F4C-9F4D,U+9F53,U+9F55-9F58,U+9F5A-9F5B,U+9F5D-9F5E,U+9F62-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-9FC5} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/184.woff2") format("woff2");unicode-range:U+9CFA-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-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} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/183.woff2") format("woff2");unicode-range:U+9B7A-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-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-9CF9} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/182.woff2") format("woff2");unicode-range:U+99F5-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-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-9B07,U+9B09-9B0E,U+9B10-9B12,U+9B14-9B1E,U+9B20-9B21,U+9B24,U+9B26,U+9B28,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-9B79} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/181.woff2") format("woff2");unicode-range:U+983E-9845,U+9847-984B,U+9850-9852,U+9854-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-98B5,U+98B7-98BB,U+98BD-98C3,U+98C5,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-98F1,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-992D,U+992F-9930,U+9932,U+9934-9936,U+9938-993B,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-99F4} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/180.woff2") format("woff2");unicode-range:U+96C8,U+96CA,U+96D0-96D1,U+96D3-96D4,U+96D7-96D8,U+96DA,U+96DD,U+96DF,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-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-97AA,U+97AC,U+97AE,U+97B0-97B1,U+97B3,U+97B5-97BC,U+97BE-97C2,U+97C4-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-983D} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/179.woff2") format("woff2");unicode-range:U+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+95A4,U+95A6-95A7,U+95AA,U+95AE-95B0,U+95B2-95B5,U+95B7-95B8,U+95BA,U+95C0-95C2,U+95C4-95C7,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-96BB,U+96BF,U+96C2-96C3} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/178.woff2") format("woff2");unicode-range:U+92D8-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-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-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} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/177.woff2") format("woff2");unicode-range:U+913F-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-91E6,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-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-92D7} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/176.woff2") format("woff2");unicode-range: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+8FF4-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-902C,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+905D,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-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-913E} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/175.woff2") format("woff2");unicode-range: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+8E30,U+8E32-8E34,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+8F13,U+8F16-8F1A,U+8F20-8F24,U+8F27-8F28,U+8F2B-8F2E,U+8F30-8F32,U+8F34-8F37,U+8F39-8F3A} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/174.woff2") format("woff2");unicode-range:U+8B7A-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+8C8D-8C92,U+8C95-8C97,U+8C99-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,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} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/173.woff2") format("woff2");unicode-range: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+8A3B-8A3D,U+8A3F-8A40,U+8A42-8A45,U+8A47,U+8A49-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-8A8C,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-8B65,U+8B67-8B6A,U+8B6D-8B6E,U+8B71-8B73,U+8B75-8B76,U+8B78-8B79} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/172.woff2") format("woff2");unicode-range:U+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+88FD,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-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-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-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} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/171.woff2") format("woff2");unicode-range:U+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-883F,U+8841-8843,U+8847-884B,U+884E-8852,U+8855-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-889A} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/170.woff2") format("woff2");unicode-range: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-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+8716,U+8719,U+871B,U+871D,U+871F-8720,U+8724,U+8726-8728,U+872A-872C} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/169.woff2") format("woff2");unicode-range:U+844D-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,U+848D,U+848F-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-84C8,U+84CC,U+84CE-84CF,U+84D2,U+84D4-84D5,U+84D7-84DC,U+84DE,U+84E1-84E2,U+84E4,U+84E7-84EB,U+84ED,U+84F1-84F3,U+84F5-84FB,U+84FE,U+8500-850B,U+850D-8510,U+8512,U+8514-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+8553-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-8593,U+8595-859A,U+859D-859E,U+85A0-85A3,U+85A5,U+85A7,U+85AB-85AD,U+85B1-85B6,U+85B8} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/168.woff2") format("woff2");unicode-range: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-83D3,U+83D5,U+83D7,U+83D9-83DB,U+83DE,U+83E2-83E4,U+83E6-83E8,U+83EB-83EE,U+83F3-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-844C} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/167.woff2") format("woff2");unicode-range:U+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-812A,U+812D-812E,U+8130,U+8133-8135,U+8137,U+813A-813D,U+813F-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+8181,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-81B2,U+81B4-81B9,U+81BC,U+81C4-81C5,U+81C7-81C8,U+81CB,U+81CE,U+81D0-81D7,U+81D9,U+81DB-81DF,U+81E1-81E2,U+81E4,U+81E6,U+81E9,U+81EB,U+81EE-81F2,U+81F5-81F9,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-8278,U+827B-827C,U+8280-8281,U+8283,U+8285-8287} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/166.woff2") format("woff2");unicode-range: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-7FED,U+7FEF,U+7FF2,U+7FF4-7FF8,U+7FFA,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-80D4} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/165.woff2") format("woff2");unicode-range: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-7D92,U+7D94-7D9B,U+7D9D,U+7D9F,U+7DA1,U+7DA4-7DA5,U+7DA7-7DAB,U+7DAF,U+7DB3,U+7DB5-7DB7,U+7DBC,U+7DC0-7DC3,U+7DC5-7DC6,U+7DC8-7DC9,U+7DCC-7DD1,U+7DD3-7DD6,U+7DDB-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-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+7E48-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,U+7E8B,U+7E8D-7E8E,U+7E90-7E92,U+7E94-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} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/164.woff2") format("woff2");unicode-range: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-7C15,U+7C17-7C1D,U+7C20,U+7C22,U+7C24-7C25,U+7C28-7C29,U+7C2C-7C37,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-7C71,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,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-7D2E,U+7D34-7D38,U+7D3B,U+7D3D-7D3E,U+7D41,U+7D43,U+7D45,U+7D47-7D4D} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/163.woff2") format("woff2");unicode-range: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-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} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/162.woff2") format("woff2");unicode-range:U+7918,U+791A-791D,U+791F-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-79A6,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-7A1D,U+7A21-7A22,U+7A24-7A2D,U+7A2F-7A30,U+7A32,U+7A34-7A36,U+7A38,U+7A3A,U+7A3E,U+7A40-7A45,U+7A47-7A4B,U+7A4F-7A50,U+7A52-7A56,U+7A58-7A60,U+7A63-7A68,U+7A6A-7A6C} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/161.woff2") format("woff2");unicode-range:U+77B7-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-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,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-7917} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/160.woff2") format("woff2");unicode-range:U+7641,U+7644-7645,U+7648,U+764A-764B,U+764E-7653,U+7655,U+7657,U+7659-765B,U+765D,U+7660,U+7665-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+76C3-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-7700,U+7702-7703,U+7705-7706,U+770A,U+770C,U+770E-7718,U+771B-771E,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-774F,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-778B,U+778F-7790,U+7793-7797,U+7799-779D,U+77A1,U+77A3-77A4,U+77A6,U+77A8,U+77AB,U+77AD-77AF,U+77B1-77B2,U+77B4,U+77B6} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/159.woff2") format("woff2");unicode-range:U+74B1-74B9,U+74BB-74BC,U+74BE-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-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-75E1,U+75E5,U+75E9,U+75EC-75EF,U+75F2-75F3,U+75F5-75F8,U+75FA-75FB,U+75FD,U+7604,U+7606-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} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/158.woff2") format("woff2");unicode-range:U+7366-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-739E,U+73A0-73A1,U+73A3-73A8,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-744A,U+744C-7454,U+7456,U+7458,U+745D,U+7460-7462,U+7465-7468,U+746B-746C,U+746E-746F,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} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/157.woff2") format("woff2");unicode-range: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+7260,U+7263-7265,U+7268,U+726A-726D,U+7270-7271,U+7273-7274,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-7343,U+7346-734C,U+734F,U+7351,U+7353-7356,U+7358-735F,U+7361-7365} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/156.woff2") format("woff2");unicode-range: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+7146-7148,U+714B,U+714D,U+714F-7151,U+7153-7158,U+715A-715B,U+715D,U+715F-7161,U+7163,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} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/155.woff2") format("woff2");unicode-range: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-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} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/154.woff2") format("woff2");unicode-range:U+6E01-6E04,U+6E06-6E09,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-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-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-6F6C,U+6F71} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/153.woff2") format("woff2");unicode-range:U+6C51-6C53,U+6C56,U+6C58-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-6CDD,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-6D29,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+6DE9,U+6DED,U+6DEF-6DF0,U+6DF2,U+6DF4,U+6DF8,U+6DFD-6E00} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/152.woff2") format("woff2");unicode-range:U+6AE3-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-6BAD,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+6C39-6C3C,U+6C3E-6C3F,U+6C43-6C45,U+6C48,U+6C4B-6C4F} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/151.woff2") format("woff2");unicode-range:U+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-69EC,U+69EE-69F1,U+69F4-69FC,U+69FE,U+6A00,U+6A03-6A04,U+6A06-6A09,U+6A0B-6A12,U+6A14-6A16,U+6A1A-6A1D,U+6A20,U+6A22,U+6A24-6A27,U+6A29,U+6A2B-6A2E,U+6A30,U+6A32-6A34,U+6A36-6A37,U+6A3B-6A3C,U+6A3F-6A43,U+6A45-6A46,U+6A49-6A4A,U+6A4C-6A4F,U+6A51-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-6AC2,U+6AC4-6AD2,U+6AD4-6AD9,U+6ADC,U+6AE0-6AE2} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/150.woff2") format("woff2");unicode-range:U+687F-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+68CA,U+68CC,U+68CE-68D1,U+68D3-68D4,U+68D9,U+68DB-68DE,U+68E1-68E2,U+68E4-68E6,U+68E8-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-69B9} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/149.woff2") format("woff2");unicode-range: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+6736-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-67FC,U+67FE,U+6801-6804,U+6806,U+680D,U+6810,U+6812,U+6814-6815,U+6818-681C,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} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/148.woff2") format("woff2");unicode-range:U+65AE,U+65B1-65B6,U+65B8,U+65BA-65BB,U+65BE-65C0,U+65C2,U+65C7-65CA,U+65CD,U+65D0-65D1,U+65D3-65D5,U+65D8-65DF,U+65E1,U+65E3-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} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/147.woff2") format("woff2");unicode-range:U+6433,U+6435,U+6437-6439,U+643B-643C,U+643E,U+6440,U+6442-6443,U+6449,U+644B-6450,U+6453,U+6455-6457,U+6459-645B,U+645D,U+6460-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+649A-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+64E1,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,U+653A,U+653C-653D,U+6540-6544,U+6546-6547,U+654A-654B,U+654D-654E,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} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/146.woff2") format("woff2");unicode-range: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+62DD-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-6331,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+6368,U+636A,U+636C,U+636F-6370,U+6372-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+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+63F9-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-6425,U+6427-6429,U+642B,U+642E-6432} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/145.woff2") format("woff2");unicode-range:U+6130-6133,U+6135-6136,U+6138-613D,U+6140-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-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,U+6235,U+6238-623C,U+6242,U+6244-6246,U+624A,U+624F-6250,U+6255-6257,U+6259-625A,U+625C-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} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/144.woff2") format("woff2");unicode-range:U+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-612F} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/143.woff2") format("woff2");unicode-range:U+5E21-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+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+5F14,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+5F5E-5F60,U+5F63,U+5F67-5F68,U+5F6B,U+5F6E-5F6F,U+5F72,U+5F74-5F76,U+5F78,U+5F7A,U+5F7D-5F7F,U+5F83,U+5F86,U+5F8D-5F8F,U+5F93-5F94,U+5F96,U+5F9A-5F9B,U+5F9D,U+5F9F,U+5FA2-5FA6} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/142.woff2") format("woff2");unicode-range:U+5CF3,U+5CF5,U+5CF7-5CFA,U+5CFC,U+5CFE-5D01,U+5D04-5D05,U+5D08-5D0C,U+5D0F-5D13,U+5D15,U+5D18-5D1A,U+5D1C-5D1D,U+5D1F-5D21,U+5D23,U+5D25,U+5D28,U+5D2A-5D2B,U+5D2F-5D33,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-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+5DE3-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-5E20} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/141.woff2") format("woff2");unicode-range:U+5B61,U+5B67-5B68,U+5B6D-5B6F,U+5B72,U+5B74,U+5B76-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-5C5B,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-5CE0,U+5CE2-5CE3,U+5CE7,U+5CE9,U+5CEB-5CEC,U+5CEE,U+5CF1-5CF2} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/140.woff2") format("woff2");unicode-range:U+5A4D-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-5B4B,U+5B4D-5B4F,U+5B52,U+5B56,U+5B5E,U+5B60} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/139.woff2") format("woff2");unicode-range:U+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+59B3-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+59E6-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,U+5A4B-5A4C} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/138.woff2") format("woff2");unicode-range:U+57C6-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-5856,U+5859-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} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/137.woff2") format("woff2");unicode-range:U+5677,U+5679-567A,U+567D-567F,U+5681-5684,U+5688-568B,U+568D,U+5690-5692,U+5694,U+5696-56A2,U+56A4-56A5,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-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+5770-5772,U+5774-5775,U+5778-577A,U+577D-5781,U+5787-578A,U+578D-5791,U+5794-579A,U+579C-579F,U+57A5,U+57A8,U+57AA,U+57AC,U+57AF-57B1,U+57B3,U+57B5-57B7,U+57B9-57C1,U+57C4-57C5} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/136.woff2") format("woff2");unicode-range:U+54F9,U+54FB,U+54FE,U+5500,U+5502-5503,U+5505,U+5508,U+550A-550E,U+5512-5513,U+5515-551A,U+551C-551F,U+5521,U+5525-5526,U+5528-5529,U+552B,U+552D,U+5532,U+5534-5536,U+5538-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-5586,U+558C-558E,U+5590,U+5592-5593,U+5595-5597,U+559B,U+559E,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-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} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/135.woff2") format("woff2");unicode-range: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+5379,U+537C-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+5451,U+545A,U+545D-5461,U+5463,U+5465,U+5467,U+5469-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+54E2-54E4,U+54EB-54EC,U+54EF-54F1,U+54F4-54F8} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/134.woff2") format("woff2");unicode-range:U+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+524B,U+524F,U+5252-5253,U+5255,U+5257-525A,U+525F-5260,U+5262-5264,U+5266,U+5268,U+526B-526D,U+5270-5271,U+5273,U+5276-527C,U+527E,U+5280,U+5284-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} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/133.woff2") format("woff2");unicode-range:U+5063-5064,U+5066-506B,U+506D-5073,U+5078-507A,U+507C-507D,U+5081-5084,U+5086-5087,U+5089-508C,U+508E-5090,U+5092-5095,U+5097,U+509A-50A2,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-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+5101,U+5103,U+5105,U+510A,U+510C-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+516A,U+516F,U+5172,U+517A,U+517E-517F,U+5183-5184,U+5186-5187,U+518B,U+518E-5191,U+5193-5194,U+5198,U+519A,U+519D-519F,U+51A1,U+51A3,U+51A6-51A9,U+51AD} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/132.woff2") format("woff2");unicode-range:U+4EFE,U+4F00,U+4F02-4F08,U+4F0B-4F0C,U+4F12-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+4F48-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+4FB7-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-4FEC,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-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-5062} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/131.woff2") format("woff2");unicode-range:U+4D5C-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} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/130.woff2") format("woff2");unicode-range:U+4CA0-4D5B} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/129.woff2") format("woff2");unicode-range:U+4BE5-4C9F} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/128.woff2") format("woff2");unicode-range:U+4B2B-4BE4} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/127.woff2") format("woff2");unicode-range:U+4A72-4B2A} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/126.woff2") format("woff2");unicode-range:U+49BB-4A71} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/125.woff2") format("woff2");unicode-range:U+4905-49BA} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/124.woff2") format("woff2");unicode-range:U+4850-4904} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/123.woff2") format("woff2");unicode-range:U+479D-484F} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/122.woff2") format("woff2");unicode-range:U+46EB-479C} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/121.woff2") format("woff2");unicode-range:U+463B-46EA} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/120.woff2") format("woff2");unicode-range:U+458C-463A} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/119.woff2") format("woff2");unicode-range:U+44DF-458B} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/118.woff2") format("woff2");unicode-range:U+4433-44DE} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/117.woff2") format("woff2");unicode-range:U+4389-4432} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/116.woff2") format("woff2");unicode-range:U+42E0-4388} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/115.woff2") format("woff2");unicode-range:U+4239-42DF} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/114.woff2") format("woff2");unicode-range:U+4193-4238} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/113.woff2") format("woff2");unicode-range:U+40EF-4192} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/112.woff2") format("woff2");unicode-range:U+404D-40EE} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/111.woff2") format("woff2");unicode-range:U+3FAC-404C} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/110.woff2") format("woff2");unicode-range:U+3F0D-3FAB} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/109.woff2") format("woff2");unicode-range:U+3E70-3F0C} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/108.woff2") format("woff2");unicode-range:U+3DD5-3E6F} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/107.woff2") format("woff2");unicode-range:U+3D3C-3DD4} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/106.woff2") format("woff2");unicode-range:U+3CA4-3D3B} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/105.woff2") format("woff2");unicode-range:U+3C0E-3CA3} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/104.woff2") format("woff2");unicode-range:U+3B7A-3C0D} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/103.woff2") format("woff2");unicode-range:U+3AE8-3B79} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/102.woff2") format("woff2");unicode-range:U+3A59-3AE7} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/101.woff2") format("woff2");unicode-range:U+39CC-3A58} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/100.woff2") format("woff2");unicode-range:U+3941-39CB} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/99.woff2") format("woff2");unicode-range:U+38B8-3940} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/98.woff2") format("woff2");unicode-range:U+3832-38B7} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/97.woff2") format("woff2");unicode-range:U+37AE-3831} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/96.woff2") format("woff2");unicode-range:U+372D-37AD} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/95.woff2") format("woff2");unicode-range:U+36AF-372C} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/94.woff2") format("woff2");unicode-range:U+3634-36AE} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/93.woff2") format("woff2");unicode-range:U+35BC-3633} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/92.woff2") format("woff2");unicode-range:U+3547-35BB} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/91.woff2") format("woff2");unicode-range:U+34D5-3546} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/90.woff2") format("woff2");unicode-range:U+3467-34D4} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/89.woff2") format("woff2");unicode-range:U+33CE,U+33D1-33D2,U+33D5,U+3400-3466} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/88.woff2") format("woff2");unicode-range:U+2EB7,U+2EBB,U+2ECA,U+2FF0-2FFB,U+3000,U+3003,U+3005-3009,U+300C-3017,U+301D-301E,U+3021-3029,U+303E,U+3105-3129,U+3220-3229,U+3231,U+32A3,U+338E-338F,U+339C-339E,U+33A1,U+33C4} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/87.woff2") format("woff2");unicode-range:U+255E-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+2713,U+2715,U+275B-275E,U+2780-2793,U+2E81,U+2E84,U+2E88,U+2E8B-2E8C,U+2E97,U+2EA7,U+2EAA,U+2EAE,U+2EB3,U+2EB6} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/86.woff2") format("woff2");unicode-range:U+2496-249B,U+2501,U+2503-251B,U+251D-254B,U+2550-255D} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/85.woff2") format("woff2");unicode-range:U+2199,U+21C4,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+223C-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-2495} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/84.woff2") format("woff2");unicode-range:U+207A-207B,U+207F,U+2081,U+20A3-20A4,U+20A9,U+20AB-20AC,U+20AE,U+20B1,U+20B8-20BA,U+20BD,U+20BF,U+2103,U+2105,U+2109,U+2113-2114,U+2116-2117,U+211E,U+2120-2122,U+2126,U+212E,U+2153-2154,U+215B-215E,U+2160-2179,U+2180-2182,U+2190-2193,U+2196-2198} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/83.woff2") format("woff2");unicode-range:U+2004-2006,U+2009-200A,U+2010,U+2015-2016,U+201A,U+201E,U+2020-2022,U+2025,U+2028-2029,U+2030-2037,U+2039-203C,U+203E,U+2042-2043,U+2047-2049,U+204E,U+2051,U+2055,U+2060-2064,U+2066-2069,U+2071} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/82.woff2") format("woff2");unicode-range:U+300-304,U+306-30C,U+312,U+323,U+326-328,U+1D43,U+1D47-1D49,U+1D4D,U+1D4F-1D50,U+1D52,U+1D56-1D58,U+1D5B,U+1D9C,U+1DA0,U+1DBB} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/81.woff2") format("woff2");unicode-range:U+FF3C-FF5B,U+FF5D,U+FFE0-FFE5} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/80.woff2") format("woff2");unicode-range:U+FF02-FF07,U+FF0A-FF0B,U+FF0E-FF19,U+FF1C-FF1E,U+FF20-FF3B} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/79.woff2") format("woff2");unicode-range:U+E3F} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/78.woff2") format("woff2");unicode-range:U+30EC-30FA,U+30FC-30FE} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/77.woff2") format("woff2");unicode-range:U+308A-3094,U+3099-309E,U+30A1-30EB} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/76.woff2") format("woff2");unicode-range:U+3041-3089} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/75.woff2") format("woff2");unicode-range:U+5244,U+5666,U+568C,U+58DA,U+5C68,U+5E58,U+5F33,U+61CD,U+6214,U+6516,U+69E7,U+6BFF,U+7020,U+7044,U+7798,U+78FD,U+7E6F-7E70,U+802E,U+8079,U+8183,U+8464,U+861E,U+8811,U+8A92,U+8B56,U+8CB0,U+8CD5,U+8CE7,U+8E93,U+8EA6,U+8EE4,U+8F62,U+9233,U+92DD,U+9301,U+9307,U+9346,U+9370,U+93AA,U+93F7,U+940B,U+9412,U+9426,U+958C,U+95AB,U+95BD,U+95BF,U+986C,U+9A2D,U+9A38,U+9B9A,U+9BEB,U+9BF4,U+9C12,U+9C20,U+9C35,U+9C37,U+9C5D,U+9D1D,U+9DA5,U+9F60} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/74.woff2") format("woff2");unicode-range:U+50E8,U+532D,U+53B4,U+54BC,U+5672,U+56C5,U+5852,U+5B24,U+5B4C,U+5D2C,U+5DF9,U+5E6C,U+5F12,U+649F,U+6504,U+68D6,U+69BF,U+6AEB,U+6AF3,U+6E5E,U+736B,U+7380,U+7649,U+79B0,U+7A6D,U+7AB6,U+7C5C,U+7D06,U+7D32,U+7DE6,U+7DF6,U+7DF9,U+7E0B,U+7E36,U+7E52,U+7E62,U+7F8B,U+8156,U+8264,U+826B,U+8526,U+8546,U+8558,U+8577,U+85F6,U+87EF,U+8806,U+89AF,U+89BF,U+8A01,U+8A75,U+8A84,U+8AD7,U+8B7E,U+8B9E,U+8DB2,U+8E60,U+8EFA,U+8F05,U+8F07,U+91D9,U+923D,U+9255,U+929A,U+92B1,U+92DF,U+92E3,U+92E6,U+92F6,U+9306,U+9308,U+9364,U+9369,U+9384,U+93BF,U+93C7,U+9414,U+9428,U+9479,U+95E5,U+9689,U+97BD,U+97DD,U+9859,U+98EB,U+9937,U+993C,U+99D4,U+99D8,U+9A0D,U+9A36,U+9A42,U+9B9E,U+9BAA,U+9BDD,U+9C08,U+9C29,U+9C3E,U+9D1F,U+9D2F,U+9D42,U+9D6F,U+9D93,U+9DE5,U+9E0C,U+9E7A,U+9EF2,U+9F09} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/73.woff2") format("woff2");unicode-range:U+50B4,U+5110,U+51AA,U+528C,U+52F1,U+56B3,U+5969,U+5ABC,U+6192,U+61AE,U+61DF,U+6207,U+6451,U+69AA,U+6A48,U+6AE7,U+6F59,U+6F77,U+6FA9,U+6FFC,U+700B,U+7027,U+7197,U+736A,U+7602,U+761E,U+7667,U+76B8,U+7C69-7C6A,U+7CDD,U+7CF4,U+7CF6,U+7D07,U+7D86,U+7DC4,U+7DD9,U+7E11,U+7E2D,U+7E32,U+7E8A,U+7F86,U+8161,U+81DA,U+81E0,U+8452,U+851E,U+8552,U+8562,U+859F,U+86FA,U+87E3,U+8949,U+89A1,U+89F6,U+8AD1,U+8B6B,U+8B8E,U+8D10,U+8EF9,U+8F0A,U+91C3,U+9201,U+9225,U+92A9,U+92CF,U+9312,U+931F,U+9343,U+9387,U+93F9,U+9420,U+944A,U+9465,U+980A,U+980F,U+9862,U+9931,U+9933,U+994A,U+995C,U+9A4F,U+9B83,U+9B92,U+9BC1,U+9C39,U+9C5F,U+9C67,U+9C6D,U+9C7A,U+9D30,U+9D53,U+9D5C,U+9D87,U+9D98,U+9DBC,U+9DD9-9DDA,U+9DE6,U+9DEF,U+9ECC,U+9EF7,U+9F34,U+9F4E-9F4F,U+9F54,U+9F59} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/72.woff2") format("woff2");unicode-range:U+50C9,U+5274,U+5635,U+5638,U+56A6,U+5B00,U+5B19,U+5B2A,U+5D81,U+5DF0,U+5E43,U+5EE1,U+6128,U+616A,U+6227,U+651B,U+6898,U+6ADE,U+6AE8,U+6C0C,U+7026,U+733B,U+74A6,U+74D4,U+766D,U+7725,U+7868,U+78B8,U+7BCB,U+7CF2,U+7CF9,U+7D83,U+7D88,U+7DB0,U+7DC7,U+7DE1,U+7DF1,U+84EF,U+84F4,U+84FD,U+863A,U+8810,U+889E,U+8933,U+895D,U+8A4E,U+8A58,U+8A7F,U+8A86,U+8A91,U+8AC2,U+8ACF,U+8ADE,U+8AE1,U+8AFC,U+8B92,U+8B96,U+8CB2,U+8CFE,U+8E92,U+8EDB,U+8F1C,U+8F61,U+90DF,U+9227,U+9230,U+9248,U+927F,U+9283,U+92A0,U+92A3,U+92A6,U+92AA,U+92F1,U+9338,U+9340,U+93CD,U+93DD,U+9413,U+943F,U+944C,U+9454,U+946D,U+95BC,U+95C8,U+979D,U+97C9,U+9807,U+9826,U+98AE,U+98BC,U+98E0,U+9A05,U+9AD5,U+9B4E,U+9BC0,U+9BC7,U+9BD7,U+9BE1,U+9BEA,U+9C23,U+9C28,U+9C33,U+9D52,U+9DF3,U+9F5C} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/71.woff2") format("woff2");unicode-range:U+5000,U+5114-5115,U+526E,U+5399,U+53C4,U+55F6,U+5665,U+566F,U+581D,U+584F,U+58E2,U+6173,U+61E8,U+645C,U+6476,U+6894,U+6A89,U+6ADD,U+6AF8,U+6B0F,U+6B5F,U+6B7F,U+6FAE,U+7341,U+7658,U+7670,U+7906,U+7C00,U+7C1E,U+7D3C,U+7D4E,U+7D5D,U+7D9E,U+7DA3,U+7E35,U+7E7E,U+7E98,U+802C,U+8407,U+8494,U+8541,U+8555,U+87BB,U+8A6C,U+8A9A,U+8AE2,U+8B05,U+8B59,U+8B9C,U+8CBA,U+8CFB,U+8D04,U+8E55,U+8E91,U+8F1E,U+8F54,U+9134,U+9136,U+91D3-91D5,U+91E4,U+91F7,U+9204,U+9266,U+926D,U+92E8,U+92EE,U+931B,U+93A6,U+93A9,U+93C3,U+93CC,U+93E8,U+9403,U+9419,U+9582,U+95C3,U+95CB,U+95D0,U+984E,U+9853,U+9873,U+9908,U+99D1,U+99D9,U+9A43-9A44,U+9B29,U+9B2E,U+9B74,U+9B90,U+9BD6,U+9BFF,U+9C13,U+9C45,U+9D07,U+9DC0,U+9DC2,U+9DF8,U+9EFD,U+9EFF,U+9F76} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/70.woff2") format("woff2");unicode-range:U+4FC1,U+5096,U+5108,U+565D,U+56B6,U+56C0-56C1,U+56C8,U+5816,U+58D9,U+5B21,U+5D0D,U+5EE9,U+614D,U+64FC,U+6A05,U+6A81,U+6AA3,U+6AEC,U+6B1E,U+6BA4,U+6BAE,U+6DE5,U+6F35,U+6F7F,U+7032,U+7162,U+716C,U+743A,U+74DA,U+7646,U+7669,U+7864,U+78E7,U+7A61,U+7B67,U+7BF3,U+7D08,U+7D3A,U+7D3F,U+7D5B,U+7DB9,U+7E0A,U+7E1E-7E1F,U+7FB6,U+8435,U+85CE,U+85EA,U+861A,U+8784,U+87F6,U+8A0C,U+8A10,U+8AB6,U+8ADB,U+8AE4,U+8B04,U+8B16,U+8B95,U+8CB3,U+8E95,U+8ED4,U+8EEB,U+8F26,U+8F33,U+8F64,U+9015,U+9090,U+91C5,U+91E7,U+9200,U+9239,U+923F,U+927A,U+92AB,U+9319,U+9477,U+958E,U+95B6,U+95B9,U+9768,U+97D9,U+980E,U+9874,U+98ED,U+9949,U+994C,U+99E2,U+9ACF,U+9B58,U+9B8E,U+9BAB,U+9BE2,U+9C09,U+9C48,U+9CE7,U+9DBB,U+9E15,U+9E1D,U+9F66,U+9F6A,U+9F77} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/69.woff2") format("woff2");unicode-range:U+50AF,U+513B,U+528A,U+561C,U+5707,U+584B,U+58CE,U+5950,U+5AA7,U+5B0B,U+5CF4,U+5DA0,U+5EC4,U+613E,U+6229,U+64A3,U+64B3,U+64C4,U+66D6,U+690F,U+69E8,U+6A9C,U+6AD3,U+6AF1,U+6B9E,U+6C2C,U+6C33,U+7028,U+7043,U+7069,U+7296,U+7319,U+74A3,U+75FE,U+7664,U+76B0,U+77BC,U+78E3,U+7D02,U+7D13,U+7D15,U+7DBE,U+7E88,U+81CF,U+82BB,U+858C,U+8604,U+875F,U+87C8,U+8960,U+8998,U+89B2,U+89F4,U+8A15,U+8A41,U+8AA5,U+8B2B,U+8CDA,U+8E89,U+8E9A,U+8EA1,U+8EA5,U+8F42,U+9106,U+91F9,U+9210-9211,U+922E,U+9238,U+924D,U+925E,U+9278,U+92BC,U+92C3,U+9358,U+93A7,U+93B0,U+93B5,U+93D1,U+97C3,U+9865,U+993F,U+9948,U+9962,U+9A16,U+9A41,U+9BAD,U+9BDB,U+9BE4,U+9BE7,U+9C25,U+9C56,U+9C78,U+9CF6,U+9D06,U+9D9A,U+9DD3,U+9E1B,U+9F5F,U+9F67,U+9F6C} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/68.woff2") format("woff2");unicode-range:U+50C2,U+5137,U+57E1,U+58E0,U+5AD7,U+5B08,U+5DA7,U+5DCB,U+60F2,U+60FB,U+615F,U+616B,U+61CC,U+61E3,U+636B,U+64EF,U+689F,U+6ADF,U+6AEA,U+6F1A,U+71DC,U+71F4,U+7370,U+763B,U+7931,U+7A4C,U+7C23,U+7D31,U+7DCB,U+7DD7-7DD8,U+7DF2,U+7E39,U+7FA5,U+83A2,U+83A7,U+866F,U+8706,U+87C4,U+87CE,U+8814,U+8831,U+8938,U+893B,U+8964,U+89A6,U+89AC,U+8A03,U+8A16,U+8A46,U+8A52,U+8A5B,U+8A85,U+8ACD,U+8AED,U+8B14,U+8B3E,U+8B4E,U+8E4C,U+8EAA,U+8EF2,U+8EFB,U+9148,U+91F5,U+9245,U+9315,U+9347,U+93DC,U+9410,U+9427,U+9463,U+9481,U+95AC,U+95DE,U+9658,U+973D,U+9744,U+97DE,U+981C,U+9821,U+9837,U+9870,U+98B6,U+98F4,U+9909,U+9911,U+9A0F,U+9A2E,U+9A64,U+9BF0,U+9C0D,U+9C2D,U+9C31,U+9C52,U+9CE9,U+9D23,U+9D6A,U+9DF2,U+9EA9,U+9F95} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/67.woff2") format("woff2");unicode-range:U+513A,U+5504,U+55C7,U+560D,U+5660,U+5680,U+5695,U+56A8,U+580A,U+597C,U+5B7F,U+6134,U+61AB,U+61FA,U+64BB,U+6506,U+6523,U+66B1,U+66C7,U+6D79,U+6DF6,U+6FA0,U+7005,U+71FC,U+7258,U+72FD,U+75D9,U+765F,U+78AD,U+79C8,U+7A1F,U+7C2B,U+7C5F,U+7C6E,U+7CC9,U+7D40,U+7DAC,U+7E09,U+7E10,U+8075,U+8594,U+85BA,U+8768,U+87A2,U+8823,U+89B7,U+8A1B,U+8A36,U+8A54,U+8A61,U+8A70,U+8AEB,U+8AF6,U+8CC5,U+8CE1,U+8D17,U+8EFE,U+91D2,U+91E9,U+9249,U+9262,U+92BB,U+92C7,U+92E5,U+92EF-92F0,U+9341,U+9354,U+9376,U+937A,U+9398,U+93A3,U+93E4,U+93F5,U+943A,U+9460,U+947E,U+95A1,U+95AD,U+95D4,U+9742,U+975A,U+9766,U+98AF,U+98C6,U+98C8,U+98E9,U+991B,U+991E,U+9A4D,U+9AD6,U+9B81,U+9BCA,U+9BFD,U+9D61,U+9D89,U+9E1A,U+9F72} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/66.woff2") format("woff2");unicode-range:U+4F47,U+5102,U+5157,U+55E9,U+562E,U+5630,U+5879,U+5ACB,U+5AF5,U+5B38,U+5C5C,U+5D97,U+5DB8,U+5EDD,U+60B5,U+611C,U+619A,U+6384,U+64FB,U+66C4,U+6A62,U+6ADA,U+6B12,U+6BAB,U+6ECE,U+6F6F,U+705D,U+70F4,U+71C9,U+71FE,U+729B,U+72A2,U+7489,U+750C,U+7515,U+766C,U+769A,U+792B-792C,U+7B8B,U+7C0D,U+7D73,U+7E1D,U+7E6E,U+7F4C,U+81BE,U+8477,U+849E,U+84C0,U+854E,U+85FA,U+8622,U+865C,U+86FB,U+8778,U+8956,U+8A6D,U+8B01,U+8B10,U+8B28,U+8CC1,U+8D05,U+8E7A,U+8F45-8F46,U+8FAE,U+913A,U+91C1,U+91FA,U+9208,U+920D,U+9251,U+925A,U+926C,U+9291,U+92A5,U+92CC,U+942E,U+9432,U+9586,U+958F,U+95CC,U+95D5,U+9830,U+9839,U+9957,U+995E,U+99DF,U+99ED,U+9A2B,U+9A3E,U+9AAF,U+9C54,U+9D1B,U+9D26,U+9D60,U+9DA9} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/65.woff2") format("woff2");unicode-range:U+4E99,U+50E5,U+513C,U+55C6,U+5616,U+5653,U+56EA,U+59CD,U+5D22,U+5D87,U+6137,U+615A,U+645F,U+6473,U+64F7,U+6519,U+6595,U+67F5,U+6AB3,U+6AB8,U+6ABB,U+6EF7-6EF8,U+6F41,U+6F97,U+6FD8,U+6FFA,U+7377,U+737A,U+737C,U+744B,U+74BD,U+74CF,U+7627,U+792A,U+79B1,U+7D46,U+7E43,U+7E45,U+7E93,U+7F88,U+811B,U+81BF,U+81CD,U+8396,U+858A,U+880D,U+8A25,U+8A6B,U+8A6E,U+8A7C,U+8AB9,U+8AC9,U+8AFA,U+8B17,U+8B2C,U+8B4F,U+8CD2,U+8D16,U+8E63,U+8E8A,U+8EFC,U+8F25,U+9114,U+9116,U+9183,U+9226,U+9237,U+923E,U+927B,U+9296,U+92AC,U+9310,U+932E,U+93A2,U+93D7-93D8,U+93DE,U+93E2,U+990C,U+9912,U+993E,U+9943,U+99B1,U+9A40,U+9A5B,U+9A6A,U+9B22,U+9B77,U+9BC9,U+9C58,U+9D15,U+9D51,U+9DFA,U+9E1E} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/64.woff2") format("woff2");unicode-range:U+55B2,U+55DA,U+58AE,U+5A6D,U+5AFA,U+5BE2,U+5DD2,U+5DD4,U+5FA0,U+60BD,U+618A,U+633E,U+64F0,U+6582-6583,U+6727,U+69F3,U+6ADB,U+6AE5,U+6C08,U+6D87,U+6E19,U+6E26,U+6F2C,U+7006,U+7018,U+71BE,U+743F,U+760D,U+7647,U+7662,U+7672,U+76DE,U+798E,U+79BF,U+7ABA,U+7BE4,U+7C6C,U+7D1C,U+7D33,U+7D79,U+7DBB,U+7DBD,U+7DDE,U+7E08,U+7E55,U+82E7,U+85F9,U+87EC,U+8805,U+8A63,U+8AA3,U+8AA8,U+8AC4,U+8ADC,U+8AE6,U+8AF3,U+8B33,U+8CD1,U+8CE4,U+8D0D,U+8F12,U+8F3E,U+923A,U+9257,U+9293,U+92A8,U+92E4,U+9333,U+9365,U+936C,U+93AC,U+93B3,U+942B,U+9433,U+9594,U+95A8,U+9695,U+96CB,U+9824,U+98EA,U+9903,U+9921,U+99B4,U+9A01,U+9A62,U+9A65,U+9BE8,U+9C32,U+9C77,U+9D72,U+9DAF,U+9DD7} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/63.woff2") format("woff2");unicode-range:U+4FB6,U+51DC,U+52FB,U+5331,U+5436,U+5614,U+562F,U+56C2,U+5862,U+5A41,U+5B0C,U+5C37,U+5E57,U+61FE,U+63C0,U+6417,U+64BF,U+64F2,U+6522,U+652A,U+65AC,U+66E0,U+6968,U+6A3A,U+6AFB,U+6B16,U+6BAF,U+6BB2,U+6D36,U+6DEA,U+6F23,U+6F80,U+6FC1,U+7009,U+7064,U+7152,U+71C1,U+71ED,U+7463,U+7469,U+7621,U+7681,U+76BA,U+77EF,U+786F,U+7A08,U+7AC4-7AC5,U+7AC7,U+7D09,U+7D5E,U+7D62,U+7DBA,U+7E5A,U+7E7D,U+7FF9,U+8123,U+81A9,U+8466,U+879E,U+883B,U+896A,U+8A1D,U+8AA6,U+8AF7,U+8B0A,U+8CBD,U+8CCA,U+8CDC,U+8F1F,U+8F4D,U+9087,U+9209,U+92F8,U+9328,U+9382,U+93DF,U+9438,U+97DC,U+9871,U+991A,U+9945,U+99AD,U+99D2,U+99FF,U+9A4A,U+9BD4,U+9C3B,U+9C57,U+9D09,U+9F9C} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/62.woff2") format("woff2");unicode-range:U+4FE0,U+50AD,U+53AD,U+555E,U+589C,U+58B3,U+5DBC,U+6065,U+6190,U+61F6,U+61FC,U+6493,U+64BE,U+64F1,U+650F,U+6688-6689,U+66EC,U+68F2,U+6BC6,U+6C2B,U+6FF0,U+701D,U+7192,U+71D9,U+71FB,U+720D,U+7587,U+760B,U+7613,U+7661,U+766E,U+775C,U+779E,U+78EF,U+79AA,U+7A62,U+7AAA,U+7B8F,U+7C3E,U+7CDE,U+7D8F,U+7DDD,U+7DEF,U+7E37,U+7E46,U+7E6D,U+7E79,U+8073,U+807E,U+8588,U+863F,U+87FB,U+881F,U+8993,U+8A23,U+8A60,U+8AA1,U+8AF1,U+8B20,U+8B41,U+8B74,U+8C4E,U+8C9E,U+8EC0,U+9132,U+919E,U+91D7-91D8,U+9223,U+9240,U+92C5,U+9318,U+931A,U+9320,U+934D,U+93FD,U+9472,U+947F,U+95A5,U+96B4,U+96DB,U+97CC,U+985B,U+986B,U+99DD,U+99F1,U+9C49,U+9D3F} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/61.woff2") format("woff2");unicode-range:U+4F96,U+5098,U+50D5,U+524E,U+5687,U+56D1,U+582F,U+588A,U+58D8,U+58FA,U+5BF5,U+5C4D,U+5EC2,U+5EDA,U+5EEC,U+60B6,U+60F1,U+61C7,U+646F,U+647B,U+64CB,U+665D,U+6953,U+69D3,U+6A01,U+6DF5,U+6E4A,U+6ECC,U+6F86-6F87,U+7015,U+701F,U+703E,U+72F9,U+7378,U+7464,U+7671,U+797F,U+798D,U+7AC8,U+7B4D,U+7DB8,U+7E1B,U+7E8F,U+7F75,U+7FA8,U+814E,U+81D8,U+8259,U+834A,U+8525,U+852D,U+856A,U+8606,U+8766,U+8AD2,U+8AE7,U+8B0E,U+8C48,U+8CAF,U+8CB6,U+8CC2,U+8D13,U+8E8B,U+8ED2,U+919C,U+91AC,U+921E,U+925B,U+938A,U+947C,U+9598,U+95A9,U+95BB,U+96B8,U+980C,U+98E2,U+9913,U+9952,U+99C1,U+9A37,U+9B91,U+9D5D,U+9DF9,U+9EF4,U+9F4B,U+9F52} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/60.woff2") format("woff2");unicode-range:U+5006,U+5109,U+5147,U+525D,U+52F3,U+58DF,U+599D,U+5B30,U+5CA1,U+5D84,U+5E25,U+5E5F,U+5EC1,U+5F4E,U+5F65,U+6085,U+6158,U+61A4,U+6200,U+6488,U+652C,U+68D7,U+68DF,U+6A1E,U+6BBC,U+6E3E,U+6EC4,U+6EF2,U+6F3F,U+6F51,U+6FB1,U+71E6,U+7344-7345,U+7375,U+775E,U+7AAF,U+7ACA,U+7C60,U+7CB5,U+7D0B,U+7D17,U+7DBF,U+7DEC,U+7E61,U+7E69,U+7F77,U+8070,U+8076,U+8178,U+819A,U+81E5,U+8332,U+84BC,U+860A,U+8755,U+8836,U+8932,U+896F,U+8A50,U+8A87,U+8B19,U+8B39,U+8B5A,U+8C93,U+8CED,U+8D1B,U+8ECB,U+91D0,U+91E3,U+9214,U+9298,U+95E1,U+9727,U+97CB,U+97FB,U+9811,U+9905,U+9A0E,U+9A55,U+9B31,U+9DB4,U+9E79,U+9E7C,U+9F94} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/59.woff2") format("woff2");unicode-range:U+5075,U+50F1,U+51CD,U+51F1,U+528D,U+52F8,U+53E2,U+5442,U+559A,U+55AA,U+5606,U+5674,U+5857,U+5875,U+58BE,U+58E9,U+5C62,U+5D50,U+5DBD,U+5EDF,U+5F4C,U+6046,U+61F8,U+6399,U+64AB,U+64B2,U+651C,U+6558,U+6DBC,U+6E6F,U+6EBC,U+6EEC,U+6FEB,U+7051,U+7165,U+721B,U+723A,U+72A7,U+7336,U+746A,U+74CA,U+77DA,U+78A9,U+78DA,U+7AA9,U+7D68,U+7DA2,U+7DE0,U+7E2B,U+7E9C,U+8105,U+8108,U+816B,U+8266,U+84EE,U+856D,U+860B,U+8A1F,U+8A98,U+8C54,U+8CC3-8CC4,U+8F3B,U+8F5F,U+8FAF,U+9059,U+91C0,U+9234,U+9264,U+929C,U+92B3,U+92C1,U+935B,U+9444,U+9583,U+95A3,U+9803,U+98C4,U+99B3,U+9AD2,U+9D28,U+9D3B,U+9F90} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/58.woff2") format("woff2");unicode-range:U+514C,U+53B2,U+58C7,U+58FD,U+5A1B,U+5ABD,U+5DBA,U+5DD6,U+61F2,U+6416,U+64E0,U+6575,U+69CD,U+6A38,U+6AC3,U+6B3D,U+6BC0,U+6DDA,U+6EC5,U+6EEF,U+6FC3,U+6FE4,U+6FF1,U+7058,U+7210,U+7246,U+727D,U+76DC,U+7919,U+7A4E,U+7C43,U+7C72,U+7D10,U+7DB1,U+7DE3,U+7E31,U+7E96,U+8056,U+8139,U+81A0,U+81C9,U+840A,U+8523,U+8569,U+8972,U+8A3A,U+8ABC,U+8AEE,U+8CA9-8CAA,U+8CC8,U+8CDE,U+8CE2,U+8CFA,U+8D08,U+8D0F,U+8F29,U+8F3F,U+8F44,U+8F4E,U+8FAD,U+9112,U+9285,U+92D2,U+934B,U+947D,U+95D6,U+95E2,U+9670,U+978F,U+9817,U+9846,U+98FC-98FD,U+99AE,U+99D5,U+99DB,U+9A19,U+9B27,U+9CE5,U+9CF4} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/57.woff2") format("woff2");unicode-range:U+502B,U+5091,U+50D1,U+5100,U+511F,U+5291,U+52C1,U+55AC,U+58EF,U+5922,U+596A,U+5B6B,U+5BAE,U+5CFD,U+5E33,U+5EC8,U+5EE2,U+6182,U+6191,U+61B2,U+61F7,U+64A5,U+64FA,U+6524,U+66C9,U+6B98,U+6DE8,U+6E67,U+6F01,U+6F32,U+6F64,U+70CF,U+71D2,U+7240,U+76E7,U+7955,U+79AE,U+7AAE,U+7CFE,U+7D21,U+7E73,U+8085,U+81BD,U+838A,U+85A9,U+8AA0,U+8AFE,U+8B00,U+8C6C,U+8CC0,U+8CD3,U+8CE0,U+8D95,U+8D99,U+8DE1,U+8ECC,U+8F1B,U+8F1D,U+907C,U+9081,U+912D,U+92EA,U+9326,U+932B,U+95CA,U+965D,U+96DE,U+9812,U+98F2,U+99D0,U+9B5A,U+9B6F,U+9CF3,U+9D6C,U+9E7D,U+9E97,U+9EA5,U+9F61} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/56.woff2") format("woff2");unicode-range:U+5049,U+50B5,U+5287,U+5289,U+52DE,U+5433,U+5678,U+5712,U+5967,U+596E,U+5A66,U+5BE7,U+5C46,U+5CF6,U+5EF3,U+6176,U+63DA,U+6514,U+694A,U+69AE,U+6A13,U+6A4B,U+6B50,U+6B72,U+6DFA,U+6EAB,U+6F22,U+6FA4,U+7063,U+707D,U+7159,U+755D,U+7642,U+7926,U+7A05,U+7BC9,U+7BE9,U+7C4C,U+7CE7,U+7D00,U+7D19,U+7D1B,U+7D72,U+7DA0,U+7E23,U+7F70,U+8271,U+85E5,U+8607,U+862D,U+8667,U+885B,U+8B7D,U+8CA7,U+8CAB,U+8CB8,U+8CBF,U+8FB2,U+9055,U+9109,U+9127,U+91AB,U+92FC,U+93AE,U+9435,U+9673,U+9678,U+967D,U+97D3,U+98EF,U+9928,U+9A5A,U+9B25,U+9EE8,U+9F8D} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/55.woff2") format("woff2");unicode-range:U+4E9E,U+50B7,U+50BE,U+50DE,U+5104,U+52DD,U+5713,U+593E,U+5CEF,U+5D17,U+5FB9,U+60E1,U+62CB,U+6436,U+6490,U+64FE,U+6649,U+66A2,U+6A6B,U+6F54,U+6F5B,U+6F70,U+6FDF,U+7149,U+7169,U+71C8,U+734E,U+737B,U+758A,U+7D0D,U+7D9C,U+7DB4,U+7DD2,U+7F85,U+805E,U+812B,U+83EF,U+8449,U+85CD,U+85DD,U+89AA,U+8A0A,U+8A17,U+8A69,U+8A95,U+8AF8,U+8B5C,U+8CA1-8CA2,U+8CB4,U+8CBC,U+8CFD,U+8D0A,U+8E8D,U+8ECD,U+8EF8,U+8F14,U+905C,U+90F5,U+9130,U+9215,U+9280,U+9451,U+9592,U+95BE,U+9813,U+98DB,U+98FE,U+9A30,U+9BAE,U+9EC3,U+9F4A} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/54.woff2") format("woff2");unicode-range:U+4E1F,U+4E82,U+5009,U+5074,U+52F5,U+55AB,U+5805,U+58DE,U+5BE9,U+5BEC,U+5BF6,U+5C0B,U+5E40,U+5E63,U+5F48,U+611B,U+6163,U+61B6,U+6383,U+639B,U+63EE,U+640D,U+64C1,U+64D4,U+651D,U+66AB,U+68C4,U+6BBA,U+6C23,U+6EFE,U+6F38,U+6FFE,U+723E,U+7B46,U+7D05,U+7D14,U+7D81,U+7DCA,U+7E3E,U+7E5E,U+7E6A,U+820A,U+84CB,U+87F2,U+8846,U+8AB0,U+8B02,U+8B1D,U+8C50,U+8C9D,U+8CA8,U+8CE3,U+8CE6,U+8CEC,U+8DA8,U+8E64,U+8F2A,U+9072,U+9077,U+907A,U+93E1,U+9470,U+9663,U+96B1,U+9838,U+984F,U+9858,U+990A,U+994B} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/53.woff2") format("woff2");unicode-range:U+4F54,U+5152,U+518A,U+522A,U+532F,U+537B,U+5617,U+56B4,U+5E79,U+5EE0,U+5F91,U+6232,U+6557,U+6771,U+6975,U+6A02,U+6B04,U+6E9D,U+700F,U+71B1,U+71DF,U+722D,U+7562,U+7A4D,U+7A69,U+7AF6,U+7C64,U+7D04,U+7D55,U+7E2E,U+7E54,U+8166,U+8173,U+81E8,U+8208,U+842C,U+885D,U+88DC,U+89BD,U+89F8,U+8A02,U+8A34,U+8A73,U+8AC7,U+8CB7,U+8CFC,U+8ECA,U+8F15,U+904A,U+905E,U+92B7,U+9322,U+937E,U+9589,U+95B1,U+96AA,U+96D9,U+9748,U+975C,U+9802,U+984D,U+9867,U+9918,U+99AC,U+9A45,U+9A5F} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/52.woff2") format("woff2");unicode-range:U+50C5,U+525B,U+5283,U+52E2,U+55CE,U+570B,U+570D,U+5831,U+58D3,U+5EE3,U+5F35,U+616E,U+6230,U+64CA,U+64EC,U+64F4,U+66F8,U+68E7,U+6A39,U+6A94,U+6AA2,U+6B77-6B78,U+6E1B,U+6EFF,U+7368,U+756B,U+76E1,U+76E3-76E4,U+7A31,U+7BC4,U+7DF4,U+7E7C,U+7FA3,U+8072,U+8077,U+807D,U+8209,U+85A6,U+865B,U+8A0E,U+8A13,U+8A31,U+8A55,U+8AA4,U+8B77,U+8CAC,U+8CF4,U+8E10,U+8FA6,U+9060,U+908A,U+91CB,U+91DD,U+9396,U+96D6,U+96F2,U+96FB,U+9806,U+9808,U+98A8} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/51.woff2") format("woff2");unicode-range:U+50F9,U+5354,U+5553,U+5C08,U+5C6C,U+5E2B,U+5E6B,U+5F37,U+63A1,U+64C7,U+6B0A,U+6B61,U+72C0,U+7570,U+790E,U+7D30,U+7D39,U+7D42,U+7D61,U+7DE9,U+806F,U+865F,U+88DD,U+898B,U+89BA,U+89C0,U+8A2A,U+8A5E,U+8A62,U+8B6F-8B70,U+8CA0,U+8CBB,U+8CEA,U+8EDF,U+8F09,U+8F2F,U+9023,U+9054,U+9069,U+908F,U+932F,U+9375,U+93C8,U+9580,U+968E,U+969B,U+96A8,U+96DC,U+96E2-96E3,U+97FF,U+9801,U+9810,U+9818,U+982D,U+983B,U+986F} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/50.woff2") format("woff2");unicode-range:U+5099,U+50B3,U+512A,U+5132,U+5247,U+5275,U+5340,U+54E1,U+5718,U+5834,U+584A,U+5920,U+5C0E,U+5E36,U+5E7E,U+63DB,U+65B7,U+689D,U+6CC1,U+6E96,U+7121,U+7372,U+74B0,U+78BA,U+7C21,U+7D1A,U+7DAD,U+7E3D,U+7E8C,U+7FA9,U+81FA,U+898F,U+8996,U+8A18,U+8A71-8A72,U+8A8D,U+8AB2,U+8ACB,U+8AD6,U+8B49,U+8B58,U+8B80,U+8CC7,U+8F03,U+8F38,U+8F49,U+9078,U+9304,U+9577,U+968A,U+9A57} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/49.woff2") format("woff2");unicode-range:U+4E26,U+5169,U+5225,U+52D5,U+53C3,U+55AE,U+5716,U+57F7,U+5B78,U+5BEB,U+5C07,U+5C64,U+5EAB,U+5F9E,U+5FA9,U+614B,U+6236,U+69CB,U+6A19,U+6A5F,U+6C7A,U+6C92,U+6E2C,U+7522,U+7576,U+7BC0,U+7D44,U+7D66,U+7D93,U+7DB2,U+7DDA,U+7DE8,U+7FD2,U+8207,U+8655,U+8853,U+8A2D,U+8A66,U+8A9E,U+8ABF,U+8B1B,U+8B8A,U+8B93,U+904B,U+9805,U+9AD4} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/48.woff2") format("woff2");unicode-range:U+4F86,U+500B,U+5011,U+5167,U+52D9,U+554F,U+5BE6,U+5C0D,U+61C9,U+64DA,U+6578,U+6642,U+6703,U+696D,U+6A23,U+7232,U+73FE,U+767C,U+78BC,U+7A2E,U+7D50,U+7D71,U+88CF,U+8A08,U+8AAA,U+9019,U+9032,U+904E,U+9084,U+958B,U+9593,U+95DC,U+984C,U+985E,U+9EBC,U+9EDE} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/47.woff2") format("woff2");unicode-range:U+4E2C,U+4E3F,U+5107,U+5155,U+5216,U+5281,U+5293,U+53B6,U+54F3,U+5523,U+553C,U+570A,U+5776,U+5819,U+5ADC,U+5B32,U+5B65,U+5C88,U+5C8D,U+5E3B,U+5F61,U+5FEE,U+6067,U+614A,U+615D,U+62F6,U+63AD,U+63BE,U+6420,U+6434,U+6484,U+6499,U+652E,U+67D9,U+67DD,U+6920,U+6A8E,U+6AA0,U+6B43,U+6BF5,U+6C15,U+6D5E,U+6EE0,U+6F46,U+717A,U+71E0,U+72C1,U+72C3,U+72E8,U+730A,U+73E7,U+746D,U+752F,U+754E,U+75B0,U+7603,U+7722,U+778D,U+77E7,U+7809,U+7811,U+7857,U+78C9,U+78F2,U+7946,U+7967,U+799A,U+7B45,U+7B9D,U+7BA2,U+7CD7,U+7F32,U+8014,U+8028,U+804D,U+8080,U+80C2,U+80D9,U+80EC,U+810E,U+8159,U+817D,U+81A3,U+81AA,U+8201,U+833C,U+836E,U+83E5,U+8459,U+848E,U+84CD,U+84D0,U+84F0,U+85B7,U+871E,U+8723,U+8729,U+8753,U+877E,U+878B,U+8793,U+87D3,U+88D2,U+8966,U+89DC,U+89EB,U+8B26,U+8BF6,U+8C2E,U+8D33,U+8D47,U+8D55,U+8E2C,U+8E9C,U+8E9E,U+8ECE,U+8FEE,U+9139,U+914F,U+9174,U+9191,U+928E,U+94F4,U+952B,U+953C,U+9564,U+960C,U+9622,U+9674,U+98A5,U+9A98,U+9A9F,U+9B48,U+9CA6,U+9CB0,U+9CBA,U+9CCB,U+9E32,U+9E88,U+9EB4,U+9F3D,U+9F44,U+9F86} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/46.woff2") format("woff2");unicode-range:U+4E0C,U+4E28,U+4E36,U+4EA0,U+4EC2,U+4F91,U+4F94,U+4FC5,U+502E,U+50BA,U+5181,U+51F5,U+522D,U+5290,U+52F9,U+5369,U+53A3,U+5423,U+54D5,U+54DC,U+54FF,U+552A,U+5588,U+55B5,U+55C9,U+5686,U+5786,U+57A4,U+57F4,U+580B,U+5820,U+5865,U+58BC,U+5902,U+5AB5,U+5BE4,U+5C1C,U+5C22,U+5C66,U+5C6E,U+5D3E,U+5DDB,U+5E80,U+5EE8,U+5F2A,U+5F50,U+5FED,U+609B,U+619D,U+61D4,U+620B,U+6224-6225,U+6343,U+63CE,U+63F2,U+640B-640C,U+6445,U+6496,U+64D0,U+64D7,U+6509,U+6534,U+6549,U+6603,U+674C,U+67A8,U+6833,U+6844,U+684A,U+6864,U+6957,U+6971,U+6A65,U+6A91,U+6BEA,U+6C5C,U+6CD0,U+6D48,U+6E2B,U+6EB2,U+6F24,U+6F2D,U+706C,U+70B1,U+70C0,U+721D,U+728B,U+72F3,U+7303,U+7338-7339,U+734D,U+736C,U+736F,U+74A9,U+750D,U+7513,U+7592,U+75C4,U+762D,U+7707,U+770D,U+7735,U+77A2,U+7839,U+784E,U+786D,U+791E,U+7924,U+793B,U+7953,U+798A,U+7A78,U+7A80,U+7B58,U+7BA8,U+7BDA,U+7CC8,U+7EC1,U+7F0F,U+7F12,U+7F33,U+7F68,U+8022,U+8025,U+8029,U+8035,U+809C,U+80AD,U+80B7,U+80E9,U+80F2,U+8204,U+8221,U+8223,U+822D,U+824B,U+825A,U+8274,U+827F,U+82B0,U+82E0,U+8307,U+8347,U+837D,U+839B,U+83B0,U+83F9,U+8487-8488,U+8539,U+8579-857A,U+85A8,U+867C,U+86B0,U+86D8,U+86F2,U+8764,U+8788,U+8797,U+87B5,U+8803,U+881B,U+8844,U+88FC,U+8930,U+89CC,U+8BA0,U+8C02,U+8C85,U+8C8A,U+8C98,U+8DBC,U+8E23,U+8E40,U+8E94,U+8F77,U+8F79,U+8F81,U+8FD5,U+9058,U+90B6,U+914E,U+91A2,U+91B5,U+943E,U+9494,U+94B6-94B8,U+94DE,U+94EB,U+94F9,U+950A,U+950E,U+9516,U+9518,U+951E,U+9538,U+953E-953F,U+9544,U+954E,U+955F,U+9561,U+9569,U+95F6,U+95FC,U+9603,U+960D,U+963D,U+9667,U+9792,U+9794,U+97AB,U+98A1,U+9969,U+9A80,U+9A96,U+9AB1,U+9AD1,U+9B0F,U+9B2F,U+9C92,U+9C95,U+9CBC,U+9CC6,U+9CD0,U+9CD8,U+9E38,U+9E3A,U+9E5B,U+9E7E,U+9EE5,U+9EEA,U+9EF9,U+9EFB-9EFC,U+9FA0} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/45.woff2") format("woff2");unicode-range:U+4EF3,U+4F74,U+507E,U+50D6,U+50EC-50ED,U+5182,U+51B1,U+525E,U+5282,U+52D6,U+5326,U+537A,U+5459,U+54A9,U+54B4,U+54D9,U+55BE,U+55CD,U+564D,U+572F,U+574C,U+57D8,U+57FD,U+5844,U+5925,U+59D2,U+5A08,U+5AE0,U+5B16,U+5B37,U+5B80,U+5B84,U+5CB5,U+5CBD,U+5E31,U+5E5E,U+5F11,U+5F56,U+5F82,U+5F8C,U+5FC4,U+5FC9,U+5FE4,U+600A,U+605D,U+609D,U+60DD,U+6106,U+610D,U+6243,U+6322,U+63DE,U+63F8,U+641B,U+643F,U+64E4,U+656B,U+6634,U+676A,U+67B5,U+680A,U+681D,U+6883,U+69D4,U+6B37,U+6B9B,U+6C2A,U+6C46,U+6D0E,U+6D7C,U+6EB7,U+6F36,U+6F72,U+6F89,U+6F8C,U+7039,U+7113,U+7173,U+71D4,U+727F,U+728F,U+72EF,U+72F4,U+7321,U+7331,U+74DE,U+74E0,U+75C3,U+75E6,U+7605,U+760A,U+761B,U+7625,U+763C,U+7643,U+764D,U+7704,U+7743,U+7747,U+7780,U+77B5,U+7847,U+786A,U+78A5,U+78B9,U+7962,U+7A02-7A03,U+7A06,U+7AAC-7AAD,U+7AB3,U+7B0A,U+7B4C,U+7BE5,U+7C0F,U+7C40,U+7CA2,U+7CC7,U+7CF8,U+7D77,U+7E3B,U+7EA1,U+7EA9,U+7F02,U+7F07,U+7F0B-7F0C,U+7F23,U+7F2F,U+7F9D,U+8020,U+80E8,U+811E,U+812C,U+8136,U+8182,U+822F,U+8233,U+823B,U+8268,U+827D,U+8284,U+8288,U+828F,U+8291,U+82C8,U+8311,U+831B,U+835B,U+83A9,U+83DD,U+8406,U+845C,U+84B4,U+84B9,U+84E3,U+850C,U+863C,U+864D,U+867F,U+86BA,U+86D1,U+86DE,U+86F1,U+8770,U+87AC-87AD,U+87BD,U+87DB,U+880A,U+883C,U+887F,U+88BC,U+895E,U+89CF,U+89D6,U+8A48,U+8BD4,U+8BDC,U+8C2B,U+8C33,U+8C94,U+8DB1,U+8DCF,U+8DD6,U+8DE3,U+8E90,U+8F7A,U+8F82,U+902D,U+90DB,U+9164,U+91AD,U+91AF,U+936A,U+948B,U+94D8,U+950D,U+9513-9514,U+9529,U+956C,U+963C,U+966C,U+9688,U+96B3,U+97B2,U+996B,U+9987,U+9998,U+9A75,U+9A7A,U+9A92,U+9ABA,U+9ADF,U+9B08,U+9B43,U+9C8E,U+9C94,U+9C9A,U+9CB4,U+9CBD,U+9CD4,U+9E31,U+9E36,U+9E4B,U+9E4E,U+9E55,U+9E63,U+9E68-9E69,U+9E71,U+9EBD,U+9EC9,U+9F0D,U+9F37,U+9F51} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/44.woff2") format("woff2");unicode-range:U+4F1B,U+4F67,U+4F7E,U+4FDC,U+50E6,U+5196,U+51AB,U+5202,U+5233,U+523F,U+52A2,U+536E,U+5476,U+54CF,U+54DA,U+5537,U+561E,U+56DD,U+56DF,U+5709,U+572C,U+576B,U+57CF,U+57F8,U+580D,U+5881,U+589A,U+5941,U+59B2,U+5B5B,U+5C25,U+5D1E,U+5D24,U+5D6B,U+5E42,U+5E8B,U+5EB3,U+5ED2,U+5EFE,U+5F58,U+5F73,U+5FAD,U+6003,U+602B,U+603C,U+6083,U+6100,U+6126,U+6206,U+62CA,U+638E,U+63B4,U+6426,U+646D,U+6535,U+65C4,U+66DB,U+6715,U+6769,U+6798,U+67C3,U+6861,U+698D,U+69B1,U+69CA,U+69E0,U+69ED,U+69F2,U+69FF,U+6A18,U+6B39,U+6BB3,U+6C0D,U+6CB2,U+6CD6,U+6CF7,U+6CFA,U+6D33,U+6E16,U+6E53-6E54,U+6EBB,U+6ED7,U+6FB6,U+7035,U+709D,U+7178,U+7228,U+72AD,U+72B4,U+72F7,U+72FB,U+7313,U+739F,U+7481,U+74BA,U+7540,U+754B,U+755B,U+758B,U+75AC,U+75D6,U+7617,U+7635,U+7640,U+76A4,U+76B2,U+76CD,U+775A,U+77BD,U+781F,U+79B3,U+7B2B,U+7B31,U+7B3E,U+7B6E,U+7B7B,U+7B9C,U+7BFC,U+7C0B,U+7C16,U+7C9E,U+7CC1,U+7CE8,U+7EA5,U+7EF2,U+7F21,U+7F27,U+7F74,U+7FB0,U+7FBC,U+8016,U+8031,U+8071,U+80EA,U+8114,U+8160,U+81A6,U+81C1,U+825F,U+829F,U+82A4,U+82FB,U+8308,U+831A,U+8333,U+836C-836D,U+83B6,U+83F8,U+840F,U+8411,U+841C,U+8489,U+848C,U+855E,U+85A4,U+8627,U+8629,U+866E,U+86B5,U+86C4,U+872E,U+8731,U+873E,U+877B,U+877D,U+87EA,U+8813,U+8864,U+88CE,U+88E5,U+88F0,U+890A,U+892B,U+897B,U+89CB,U+89EF,U+89F3,U+8BFC,U+8C18,U+8C35,U+8D46,U+8DBA,U+8E3A,U+8E6F,U+8F75,U+8F7E,U+8FB6,U+8FD3,U+90BE,U+9161,U+9179,U+917E,U+91A3,U+94AC,U+94D7,U+94E5,U+952C,U+953A,U+9545,U+9565,U+9568,U+956A,U+961D,U+9654,U+96E0,U+972A,U+9730,U+97B4,U+989F,U+98E7,U+990D,U+9967,U+9993,U+9AA3,U+9AC0,U+9AE1,U+9AEB,U+9AED,U+9AF9,U+9C86,U+9C8B,U+9CA0-9CA1,U+9CA3,U+9CE2,U+9E2B,U+9E48,U+9E6A,U+9E87,U+9EE2,U+9EE9,U+9F17,U+9F19,U+9F2C,U+9F80} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/43.woff2") format("woff2");unicode-range:U+4E69,U+4F65,U+50A7,U+5240,U+5261,U+52AC,U+531A,U+5363,U+5416,U+5432,U+5452,U+5456,U+5472,U+5478,U+54AD,U+54BB,U+553F,U+5575,U+5581,U+55CC,U+55FE,U+5601,U+572E,U+57D2,U+57EF,U+581E,U+5924,U+5981,U+5997,U+59A3,U+5AAA,U+5AB8,U+5B34,U+5D5B,U+5D5D,U+5D74,U+5DEF,U+5E11,U+5E91,U+5ED1,U+5EF4,U+5F40,U+600D,U+6019,U+601B,U+605A,U+6092,U+60AB,U+6217,U+623D,U+624C,U+6369,U+65D2,U+6661,U+670A,U+6753,U+67A7,U+6855,U+686B,U+68F9,U+6939,U+696E,U+6980,U+6A7C,U+6AAB,U+6B82,U+6B84,U+6BF3,U+6BF9,U+6C05-6C06,U+6C19-6C1A,U+6CA9,U+6CF6,U+6D07,U+6D1A,U+6D27,U+6D2B,U+6DAB,U+6E8F,U+6F74,U+7085,U+7198,U+71B5,U+7256,U+725D,U+727E,U+72F2,U+72FA,U+7322,U+738E,U+73E5,U+7477,U+750F,U+755A,U+7594,U+75B3,U+760C,U+7615,U+7630,U+763F,U+7654,U+7726,U+77EC,U+7817,U+78A1,U+78D4,U+78D9,U+7905,U+7B2A,U+7B2E,U+7B62,U+7B85,U+7BA7,U+7BCC,U+7BEA,U+7C26,U+7C74,U+7C9C-7C9D,U+7E47,U+7E9B,U+7E9F,U+7EE0-7EE1,U+7EE8,U+7EF1,U+7F01,U+7F11,U+7F17,U+7F36,U+7F7E,U+7FEE,U+802A,U+809F,U+80AB,U+80CD,U+80D7,U+8112,U+8118,U+8169,U+8234,U+8279,U+8298,U+82CA,U+82D8,U+82E1,U+83C0,U+83D4,U+83DF,U+8401,U+8451,U+845A,U+8476,U+8478,U+84BA,U+84BD,U+84E0,U+851F,U+8548,U+8556,U+8585,U+868B,U+868D,U+86E9,U+86F4,U+86F8,U+8765,U+8785,U+87AB,U+87EE,U+8816,U+8832,U+8872,U+88B7,U+88E2-88E3,U+89DA,U+8BCE,U+8BD3,U+8BD6,U+8BF9,U+8C16,U+8C73,U+8D40,U+8D4D,U+8D5C,U+8DDE,U+8DEB,U+8F6D,U+8F94,U+8FE8,U+9011,U+915E,U+9185,U+918C,U+94AB,U+94D1,U+94F3,U+94FD,U+9515,U+951D,U+952A,U+9558,U+9567,U+96CE,U+96E9,U+9785,U+9878,U+987C,U+9883,U+98D1,U+9954,U+9963,U+9A93,U+9AC1,U+9ACC,U+9B1F,U+9B49,U+9B4D,U+9B51,U+9CA5,U+9CA7,U+9CAE,U+9CCE,U+9CD3,U+9E37,U+9E39,U+9E41,U+9E46,U+9F22,U+9F2F,U+9F39,U+9F85} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/42.woff2") format("woff2");unicode-range:U+4E47,U+4E5C,U+4E8D,U+4EDF,U+4F89,U+4FAA,U+50EE,U+520E,U+5412,U+5443,U+5454,U+54C2,U+54D3,U+54DE,U+5577,U+5591,U+55E5,U+560C,U+560F,U+562C,U+566B,U+56AF,U+5769,U+577C,U+578C,U+5793,U+57DA,U+57E4,U+5889,U+593C,U+59AB,U+5AD4,U+5AD8,U+5AF1,U+5B53,U+5BA5,U+5C59,U+5C63,U+5CA3,U+5E0F,U+5E14,U+5EDB,U+5F01,U+5FBC,U+6004,U+6029,U+6041,U+60AD,U+610E,U+61B7,U+634C,U+647A,U+64BA,U+65C3,U+65C6,U+65F0,U+6600,U+668C,U+66F7,U+67E2,U+67F0,U+680C,U+6874,U+691F,U+6989,U+69A7,U+6A17,U+6A3E,U+6B81,U+6C07,U+6C10,U+6C3D,U+6D91,U+6E6B,U+6FDE,U+70BB,U+71B3,U+723B,U+726E,U+72B0,U+72CE,U+7301,U+731E,U+7360,U+737E,U+73A2,U+748E,U+74FF,U+7633,U+771A,U+7765,U+7768,U+781C,U+7829,U+7913,U+7957,U+79D5,U+79EB,U+7A70,U+7A86,U+7AA8,U+7B04,U+7B25,U+7B38,U+7B47,U+7B72,U+7BA6,U+7CCD,U+7DAE,U+7ECB,U+7ED7,U+7EFE,U+7F26,U+7F31,U+7F35,U+7F5F,U+7FAF,U+801C,U+8043,U+80BC,U+8188,U+81CC,U+823E,U+8244,U+824F,U+82A1,U+82B4,U+82C1,U+82E4,U+82F4,U+8306,U+833A,U+835C,U+839C,U+83B3,U+83BC,U+83F0,U+846D,U+8497,U+85FF,U+867A,U+86A8-86A9,U+870A,U+8734,U+87CA,U+886E,U+887E,U+8888,U+88A2,U+88C9,U+8921,U+8BB5,U+8BF3,U+8C04,U+8C17,U+8C1D,U+8C25,U+8C2F,U+8C36,U+8C55,U+8C78,U+8D3D,U+8D59,U+8D67,U+8D91,U+8DBF,U+8DEC,U+8DFD,U+8E05,U+8E14,U+8E41,U+8F8E,U+900B,U+9044,U+9062,U+90CF,U+9123,U+9146,U+9162,U+9172,U+918D,U+9190,U+92C8,U+933E,U+93CA,U+9486,U+948C,U+94AA,U+94B2,U+94C8,U+94CA,U+94D5,U+94DF,U+94E9-94EA,U+94F7,U+94FC,U+951B,U+9546,U+954F,U+9554,U+9559,U+9566,U+9571-9572,U+95F1,U+960B,U+960F,U+97AF,U+988F,U+98D5,U+992E,U+9955,U+9A7D,U+9AA2,U+9AB0,U+9AC2,U+9B23,U+9B32,U+9C90,U+9C9E,U+9CA9,U+9CAD,U+9CB1,U+9CC3,U+9E47,U+9E6C,U+9EE7,U+9EFE,U+9F0B,U+9F87} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/41.woff2") format("woff2");unicode-range:U+4F25,U+4F32,U+4F5E,U+4F76,U+4F7B,U+4FE6,U+5028,U+5048,U+5250,U+535F,U+538D,U+53C1,U+54A6,U+54D4,U+54DD,U+5533,U+5541,U+5550,U+5556,U+55DD,U+55F3,U+55F5,U+5627,U+5657-5658,U+5664,U+567B,U+56D4,U+575C,U+57B2,U+5800,U+59A4,U+5A62,U+5AEB,U+5C3B,U+5CAC,U+5D26,U+5D9D,U+5DB7,U+5F2D,U+5FB5,U+5FDD,U+5FF8,U+6079,U+60B1,U+6222,U+623E,U+629F,U+6332,U+638A,U+63BC,U+63E0,U+6485,U+64B8,U+64E2,U+65AB,U+669D,U+66BE,U+66E9,U+67C1,U+67D2,U+67FD,U+6800,U+6877,U+68FC,U+690B,U+6924,U+6978,U+69C1,U+6A50,U+6A5B,U+6A97,U+6B24,U+6B8D,U+6BAA,U+6C18,U+6C54,U+6CEB,U+6CEE,U+6D04,U+6D4D,U+6DE0,U+6EB1,U+6EBD,U+7110,U+7168,U+71F9,U+7230,U+728D,U+7292,U+72B8,U+72D2,U+73B3,U+73D0,U+7511,U+75A0,U+75A5,U+75C8,U+75E7,U+7610,U+7619,U+772D,U+779F,U+7826,U+782C,U+7856,U+7877,U+7A39,U+7AE6,U+7B0F,U+7BAA,U+7BAC,U+7C1F,U+7ED0,U+7ED4,U+7EFB,U+7F0D,U+7F1F,U+7F45,U+7FBF,U+7FD5,U+7FE5,U+8027,U+8052,U+80DB,U+80DD,U+80FC,U+8132,U+815A,U+8167,U+816D,U+81CA,U+8228,U+8249,U+82A9,U+82AB,U+82CC,U+8315,U+8351,U+8368,U+8378,U+83AA,U+83B4,U+83B8,U+83D8,U+83EA,U+84C1,U+8538,U+858F,U+859C,U+85AE,U+867B,U+86B4,U+86CF,U+8722,U+876E,U+877C,U+87E5,U+88DF,U+8919,U+8BA7,U+8BCC,U+8BDF,U+8BE8,U+8BEE,U+8C20,U+8C30,U+8D36,U+8DB5,U+8DF8,U+8E1F,U+8E2F,U+8E70,U+8F78,U+8F8B,U+8F8F,U+8F9A,U+9021,U+908B,U+90B4,U+90BA,U+90C4,U+90D0,U+90EB,U+90FE,U+917D,U+91AA,U+9487,U+948D,U+9490,U+94AD,U+94BD,U+94CD,U+94D6,U+94D9,U+94EF,U+9507,U+954A,U+955E,U+956B,U+95E9,U+9604,U+9612,U+9615,U+9617,U+96B9,U+96D2,U+9708,U+989A-989B,U+989E,U+98A7,U+9A78,U+9A88,U+9AA0,U+9AB6,U+9B3B,U+9B47,U+9C82,U+9C9B,U+9CCA,U+9CD9,U+9E28,U+9E2C,U+9E5A,U+9E5E,U+9ECD,U+9ECF,U+9F8A,U+9F8C} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/40.woff2") format("woff2");unicode-range:U+4E93,U+4EBB,U+4EC4,U+4EF5,U+4F27,U+4FA9,U+4FE3,U+502C,U+5080,U+5121,U+51C7,U+51EB,U+5208,U+523D,U+52F0,U+530F,U+53F5,U+5453,U+5464,U+5466,U+549D,U+54A3,U+54BF,U+54D0,U+5527,U+5549,U+556D,U+558F,U+55B1,U+55EB,U+55F2,U+5624,U+5685,U+56E1,U+571C,U+5739,U+57B4,U+5807,U+58C5,U+59AF,U+59D8,U+5A09,U+5A0C,U+5A4A,U+5A67,U+5AD2,U+5B6C,U+5C50,U+5CA2,U+5D03,U+5D27,U+5D6C,U+5DF3,U+5E19,U+5EA0,U+5EBE,U+5EEA,U+5FEA,U+5FFE,U+6042,U+6120,U+6175,U+6215,U+6221,U+622C,U+6266,U+6308,U+6339,U+643D,U+659B,U+6677,U+679E,U+67B3,U+6832,U+6840,U+68C2,U+68F0,U+6934,U+6966,U+6987,U+6998,U+69CE,U+6A2F,U+6A3D,U+6A84,U+6AA9,U+6B87,U+6B93,U+6BD6,U+6BFD,U+6C16,U+6CD4,U+6D0C,U+6DA0,U+6E0C,U+6E86,U+6ECF,U+6F4B,U+7023,U+7080,U+70B7,U+70CA,U+72D9,U+7352,U+740A,U+7441,U+7457,U+74D2,U+7596,U+75A3,U+75D8,U+75E4,U+75E8,U+7622,U+765E,U+76B4,U+7812,U+7818,U+784C,U+7850,U+7887,U+789A-789B,U+78F4,U+7A51,U+7B15,U+7B1E,U+7B24,U+7B5A,U+7BB8,U+7BC1,U+7BD9,U+7CCC,U+7EA8,U+7EC0,U+7EE6,U+7EFA,U+7F1B,U+7F22,U+7F71,U+7FA7,U+7FF3,U+8084,U+8093,U+80B1,U+80F4,U+81BB,U+81EC,U+8202,U+8210,U+828A,U+828E,U+82A8,U+82E3,U+8366,U+8369,U+83A8,U+83E1,U+84D1,U+84FC,U+853B,U+857B,U+85D3,U+8605,U+8662,U+86B1,U+86C9,U+86D4,U+86F3,U+8725,U+8748,U+874C,U+8763,U+879F,U+87B3,U+887D,U+88FE,U+89DE,U+8A8A,U+8BA6,U+8C00,U+8C07,U+8C14,U+8C21,U+8C47,U+8C7A,U+8D30,U+8DD7,U+8E59,U+8E69,U+8E85,U+8F6B,U+8F73,U+8F87,U+8FF3,U+9026,U+902F,U+9099,U+90C7,U+9143,U+91AE,U+91BA,U+948F,U+94AF,U+94BF,U+94D2,U+9531,U+95F3,U+9609-960A,U+9765,U+97EA,U+9880,U+996C,U+9980,U+9991,U+9994,U+9AB7,U+9AC5,U+9AFB,U+9C87,U+9CB5,U+9CC7,U+9CD5,U+9E42,U+9E58,U+9F88} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/39.woff2") format("woff2");unicode-range:U+4EDD-4EDE,U+4EE1,U+4FFE,U+5025,U+506C,U+50A5,U+51FC,U+5241,U+525C,U+539D,U+53FC,U+54E7,U+5514,U+555C,U+556E,U+5576,U+557E,U+5599,U+55B9,U+55D4,U+5623,U+564C,U+5654,U+5671,U+568F,U+56D7,U+57A1,U+57A7,U+57D9,U+584D,U+58EC,U+5942,U+59DD,U+5A32,U+5A55,U+5A75,U+5B71,U+5B73,U+5C7A,U+5C98,U+5D06,U+5D6F,U+5DFD,U+5E5B,U+5EB9,U+5FD2,U+600F,U+606B,U+62BB,U+62BF,U+634B,U+636D,U+6387,U+63B3,U+63B8,U+63C4,U+63C6,U+63F6,U+6441,U+6555,U+661D,U+6657,U+66A7,U+6775,U+678B,U+6849,U+6860,U+6867,U+6901,U+6910,U+699B,U+6A28,U+6A79,U+6B46,U+6B92,U+6BC2,U+6C21,U+6C24,U+6C29,U+6C32,U+6C35,U+6C4A,U+6C86,U+6CA3,U+6CC5,U+6D39,U+6D60,U+6D63,U+6D6F,U+6DDD,U+6ED3,U+6EDF,U+6FB9,U+6FD1,U+6FEF,U+712F,U+7145,U+71E7,U+7284,U+72CD,U+72F0,U+7325,U+732C,U+7350,U+7391,U+73C8,U+73D9,U+741A,U+757F,U+7583,U+759D,U+75B4,U+75E3,U+75F1,U+75FF,U+7688,U+7699,U+76E5,U+7751,U+789C,U+78B2,U+7A17,U+7B08,U+7B33,U+7BE6,U+7C2A,U+7EA3,U+7EB0,U+7F2C,U+7FE6,U+8012,U+80E4,U+80EF,U+8171,U+821B,U+826E,U+8297,U+82BE,U+82EB,U+830C,U+8314,U+8331,U+83C5,U+83D6,U+83FD,U+843C,U+8473,U+84A1,U+84CA,U+8564,U+8572,U+85C1,U+86A3,U+86AA,U+86C6,U+86ED,U+8708-8709,U+870D,U+8869,U+89C7,U+89D0,U+89E5,U+8A3E,U+8B07,U+8BC2,U+8BF0,U+8C2A,U+8C49,U+8C62,U+8C89,U+8D49,U+8D6D,U+8D94,U+8DF9,U+8E2E,U+8E3D,U+8E47,U+8E51,U+8E7F,U+9005,U+9016,U+907D,U+9082,U+9088,U+909B,U+90B0,U+914A,U+9495,U+94A3-94A4,U+94B9-94BA,U+94CB-94CC,U+94E1,U+94F0,U+9509,U+9512,U+9552-9553,U+955D,U+9697,U+96B0,U+96BC,U+975B,U+977C,U+98A2,U+998A,U+9990,U+9995,U+9A9B,U+9C91,U+9CB3,U+9CB7,U+9CCF,U+9E29,U+9E57,U+9E82,U+9EDC,U+9EDF-9EE0,U+9F83,U+9F89} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/38.woff2") format("woff2");unicode-range:U+4EE8,U+4EEB,U+4F22,U+4F43,U+4F5D,U+4FDA,U+502D,U+50F3,U+5106,U+520D,U+52AD,U+530D,U+5310,U+53A9,U+53DF,U+5421,U+544B,U+5477,U+5482,U+551B,U+5530,U+5555,U+55C4,U+55D1,U+55DF,U+55E4,U+55EA,U+55EC,U+55EF,U+562D,U+56EB,U+56F5,U+56F9,U+5704,U+576D,U+57D5,U+57DD,U+5880,U+5958,U+599E,U+59BE,U+5ABE,U+5B51,U+5BEE,U+5CBF,U+5CC4,U+5CD2,U+5CE4,U+5E96,U+603F,U+607B,U+609A,U+61D1,U+61FF,U+622E,U+626A,U+6371,U+63AE,U+63CD,U+63D6,U+63FF,U+6410,U+6414,U+6421,U+6448,U+64C0,U+66DC,U+67A5,U+680E,U+6841,U+6954,U+6A47,U+6C94,U+6CA4,U+6D94,U+6E4E,U+705E,U+70E9,U+7116,U+726F,U+72DE,U+74E4,U+753E,U+7548,U+75B1,U+75B8,U+75BD,U+75C2,U+75CD,U+7618,U+766F,U+76F1,U+7719,U+777E,U+7791,U+7823,U+7827,U+7889,U+7893,U+795C,U+79E3,U+7A88,U+7A95,U+7AA0,U+7B90,U+7BD1,U+7BFE,U+7CB2,U+7DA6,U+7EBE,U+7EC2,U+7EEB,U+7EEF,U+7F03,U+7F08,U+7F25,U+7F58,U+7F61,U+7F9F,U+7FB8,U+8037,U+8153,U+8174,U+8191,U+8214,U+8222,U+82D5,U+82DC,U+8334,U+835A,U+8418,U+84FF,U+852B,U+8616,U+866C,U+8693,U+869D,U+86A7,U+86AC,U+86AF,U+86B6,U+86CA,U+86D0,U+8759,U+8760,U+87A8,U+87AF,U+87C6,U+87D1,U+8839,U+88C6,U+8934,U+89CA,U+89CE,U+8BA3,U+8BAA-8BAB,U+8BC5,U+8BDB,U+8BE4,U+8C15,U+8D84,U+8DB8,U+8DC4,U+8DC6,U+8DCE,U+8DDB,U+8E09,U+8E1D,U+8E39,U+8E42,U+8E49,U+8E4B,U+8E8F,U+8F71-8F72,U+9051,U+9097,U+90D3,U+90E2,U+90E6,U+90EF,U+9104,U+9150,U+919A,U+91B4,U+938F,U+9497,U+949C,U+94E0,U+951F,U+9534,U+9557,U+9562-9563,U+9606,U+965F,U+973E,U+9791,U+9889,U+9894,U+9974,U+9A90,U+9A9C-9A9D,U+9AA7,U+9AEF,U+9C88,U+9CA2,U+9CB2,U+9CB6,U+9CC5,U+9CCD,U+9CDC,U+9CDF,U+9E22,U+9E2A,U+9E67,U+9E6B,U+9E73,U+9EB8,U+9F9B} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/37.woff2") format("woff2");unicode-range:U+4EB5,U+4F09,U+4F57,U+4F6F,U+4FCE,U+4FEA,U+4FF3,U+4FF8,U+500C,U+500F,U+504E,U+507B,U+5088,U+5345,U+53FB,U+5457,U+549B,U+54A7,U+54CC,U+54D2,U+550F,U+5544,U+558B,U+5594,U+559F,U+55D6,U+55E8,U+564E,U+565C,U+5785,U+57A9,U+57AD,U+57ED,U+5914,U+59AA,U+5A06,U+5A40,U+5AE1,U+5AE6,U+5B40,U+5B6A,U+5B93,U+5C99,U+5C9C,U+5CC1,U+5CCB,U+5D2E,U+5D47,U+5D4B,U+5D99,U+5E54,U+5E61,U+6002,U+6014,U+6078,U+607D,U+607F,U+608C,U+60C6,U+60F4,U+60FA,U+621B,U+621F,U+62C8,U+631B,U+631E,U+64D8,U+6635,U+6641,U+6710,U+6748,U+6772,U+67AD,U+67B0,U+67B7,U+695D,U+69AB,U+6A58,U+6C68,U+6C8F,U+6D2E,U+6D35,U+6D9E,U+6DD9,U+6DEC,U+6E11,U+6E98,U+6EC2,U+6EE2,U+6F09,U+6F66,U+6F8D,U+6FC9,U+6FE0,U+7094,U+7119,U+723F,U+729F,U+73C9,U+73DE,U+7430,U+75C9,U+7634,U+76C5,U+7708,U+778C,U+77B3,U+77DC,U+7800,U+781D,U+782D,U+7830,U+783B-783C,U+78A3,U+78EC,U+7949,U+7980,U+7A14,U+7A23,U+7AFD,U+7B95,U+7C0C,U+7C41,U+7C91,U+7CC5,U+7ECC,U+7F04,U+7FCA,U+8006,U+8069,U+807F,U+80BD,U+814B,U+8198,U+8200,U+828D,U+82C4,U+82CB,U+82D2,U+82F7,U+832D,U+834F,U+840B,U+8438,U+847A,U+84D6,U+84E5,U+8537,U+85D0,U+8671,U+86A4,U+86F0,U+86F9,U+8703,U+8707,U+8737,U+873B,U+8782,U+87D2,U+87E0,U+8815,U+8913,U+891B,U+8941,U+8A07,U+8BC3,U+8BCB,U+8BCF,U+8BD2,U+8C0C,U+8C11,U+8C29,U+8C32,U+8C4C,U+8D45,U+8DFA,U+8E31,U+8E6D,U+8E76,U+9004,U+9036,U+90D7,U+90DC,U+9169,U+9485,U+94DB,U+9506,U+950F,U+9528,U+9542,U+9549,U+9573,U+95FE,U+961A,U+9649,U+9701,U+970E,U+972D,U+9798,U+97A3,U+97EB,U+988C,U+98A6,U+98D3,U+98D9,U+9968,U+996F,U+9977,U+997D,U+9984,U+9997,U+9ACB,U+9E20,U+9E49,U+9E4C,U+9E51,U+9E66,U+9F3E} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/36.woff2") format("woff2");unicode-range:U+4EC9,U+4F5A,U+4F8F,U+4FDF,U+501C,U+50A9,U+510B,U+51A2,U+527D,U+52BE,U+5420,U+542E,U+5486,U+5499,U+54C6,U+5506,U+5511,U+5522,U+552C,U+556C,U+55B3,U+55BD,U+55FD,U+567C,U+568E,U+5693,U+572A,U+5768,U+577B,U+5892,U+59F9,U+5A11,U+5A20,U+5B17,U+5BB8,U+5C15,U+5D3D,U+5D4A,U+5D82,U+5E1A-5E1B,U+5EA5,U+5FCF-5FD1,U+5FD6,U+5FFF,U+6006,U+6035,U+60AF,U+60B4,U+60BB,U+60DA,U+612B,U+6194,U+61CA,U+61E6,U+61F5,U+620A,U+62A1,U+62D7,U+637B,U+63E9,U+64B5,U+655D,U+65CC,U+6619,U+664C,U+665F,U+66B9,U+6773,U+6777,U+6787,U+67E9,U+6845,U+684E,U+688F,U+693D,U+696B,U+6994,U+69B7,U+6A35,U+6C26,U+6D43,U+6D5C,U+6D8E,U+6E6E,U+6E9F,U+6EF9,U+6F2A,U+6F78,U+6FC2,U+704F,U+70EC,U+7118,U+714A,U+724D,U+72C8,U+7337,U+733E,U+7428,U+74F4,U+7566,U+7572,U+7579,U+75E2,U+75FC,U+762A,U+7638,U+7678,U+76C2,U+76F9,U+7762,U+77CD,U+77FD,U+7837,U+78B4,U+795F,U+7A1E,U+7B20,U+7BA9,U+7BAB,U+7BDD,U+7C7C,U+7CB3,U+7CBD,U+7CDC,U+7EC9,U+7EF6,U+7F19,U+7F44,U+7F54,U+7F94,U+8004,U+800B,U+8019,U+809B,U+80C4,U+80ED,U+80F1,U+8146,U+816E,U+817C,U+81C0,U+81FC,U+81FE,U+830F,U+8360,U+8365,U+8385,U+8392,U+83A0,U+8424,U+84AF,U+869C,U+86CE,U+8713,U+8718,U+87C0,U+87CB,U+8821,U+8936,U+89D1,U+8BB9,U+8BD8,U+8BE9,U+8C0F,U+8D53,U+8D5D,U+8D73,U+8DF7,U+8F7C,U+8FE4,U+9041,U+9052,U+905B,U+9095,U+909D,U+90C5,U+911E,U+9122,U+916A,U+919B,U+92AE,U+9492,U+94A1,U+94B5,U+94BC,U+94C4,U+94C9,U+94E7,U+9502-9503,U+9511,U+9517,U+9536-9537,U+9602,U+9616,U+9621,U+9631,U+96C9,U+96F3,U+9739,U+9753,U+98D2,U+98DA,U+998B,U+9A81,U+9A9E,U+9B03,U+9C85,U+9CA8,U+9CAB,U+9EBE,U+9F10,U+9F8B} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/35.woff2") format("woff2");unicode-range:U+4E15,U+4E1E,U+4E2B,U+4EB3,U+4F0E,U+4F3D,U+4F64,U+5043,U+5140,U+51BC,U+51CB,U+52D0,U+531D,U+536F,U+53A5,U+53FD,U+541D,U+5429,U+5494,U+54A4,U+54AA,U+54AF,U+54CE,U+5520,U+553E,U+557B,U+55C5,U+55D2,U+55E1,U+55E6,U+55F7,U+5608,U+561F,U+5636,U+5639,U+563B,U+5659,U+5662,U+5773,U+5811,U+587E,U+58D5,U+598A,U+5992,U+59A9,U+5A6A,U+5A76,U+5A7A,U+5AC9,U+5AE3,U+5B7D,U+5B95,U+5C8C,U+5CAB,U+5CB7,U+5D02,U+5D34,U+5D58,U+5E7A,U+5EFF,U+5F0B,U+5F29,U+5F77,U+5F89,U+5F9C,U+5FA8,U+6005,U+6026,U+6063,U+60D8,U+60EC,U+60EE,U+6115,U+618E,U+6248,U+6376,U+637A,U+6390,U+63B0,U+64DE,U+652B,U+6538,U+65BC,U+65CE,U+65D6,U+6631,U+6636,U+6654,U+6666,U+6684,U+677C,U+67D8,U+67DE,U+692D,U+6979,U+6B59,U+6C69,U+6C74,U+6CAE,U+6CE0,U+6D54,U+6DD6,U+6DDE,U+6DE6,U+6EB4,U+6F15,U+6F29,U+70C3,U+715C,U+7172,U+71B9,U+7266,U+728A,U+7329,U+7396,U+73B7,U+73CF,U+73E9,U+7425,U+742C,U+742E,U+7455,U+74EE,U+7519,U+7525,U+753A,U+75B5,U+75B9,U+75D4,U+75DE,U+7656,U+765C,U+768E,U+7691,U+7729,U+777D,U+7825,U+783E,U+787C,U+795A,U+79ED,U+7A79,U+7B06,U+7B3A,U+7B8D,U+7BB4,U+7BD3,U+7BE1,U+7CBC,U+7F30,U+7F42,U+7F8C,U+7FCE,U+7FF1,U+80AE,U+810D,U+81C6,U+822B,U+82A5,U+82AA,U+82DE,U+832F,U+8340,U+8343,U+835E,U+8364,U+836A,U+8543,U+854A,U+8559,U+8568,U+85B0,U+86E4,U+8717,U+87FE,U+8845,U+88F1,U+8902,U+8C12,U+8C82,U+8D32,U+8D58,U+8DBE,U+8E7C,U+8F95,U+8FAB,U+9035,U+90A1,U+9149,U+9157,U+948E,U+949A,U+94C2,U+94C6,U+94F1,U+956F-9570,U+95EB,U+95F0,U+9611,U+9642,U+968D,U+9706,U+970F,U+97ED,U+988A,U+98E8,U+99A5,U+9A77,U+9CDD,U+9E2F,U+9E33,U+9E44,U+9EDD} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/34.woff2") format("woff2");unicode-range:U+4F2B,U+4F58,U+4FAC,U+4FD0,U+5055,U+5156,U+51BD,U+5243,U+5323,U+535E,U+53AE,U+53F1,U+540F,U+542D,U+5431,U+545B,U+547B,U+548E,U+5492,U+54AB,U+54FD,U+5509,U+5567,U+5600,U+5618,U+563F,U+56F1,U+56FF,U+573B,U+574D,U+579B,U+57A0,U+57B8,U+57C2,U+586C,U+58F9,U+5993,U+5996,U+59E3,U+5A29,U+5B62,U+5B7A,U+5BA6,U+5C49,U+5C4E,U+5C96,U+5CE5,U+5E44,U+5F99,U+6043,U+6059,U+606C,U+60B8,U+60ED,U+60F0,U+61A9,U+62A8,U+62FD,U+630E,U+6342,U+63A3,U+63AC,U+6402,U+642A,U+64A9,U+64AE,U+64B7,U+6525,U+659F,U+65EE-65EF,U+66F3,U+67B8,U+67E0,U+683E,U+6886,U+68B5,U+6963,U+6988,U+69DF,U+6AAC,U+6B86,U+6B9A,U+6C50,U+6C76,U+6C85,U+6C8C,U+6CEF,U+6D19,U+6D52,U+6D95,U+6DA7,U+6DB8,U+6DC5,U+6DFC,U+6E1A,U+6E25,U+6E4D,U+6EA7,U+6EC1,U+6F31,U+6F7A,U+6FA7,U+6FE1,U+7096,U+70F7,U+7131,U+7166,U+72E1,U+7317,U+731D,U+736D,U+73C2,U+740F,U+7490,U+74EF,U+7663,U+76BF,U+76D4,U+7728,U+776C,U+77A0,U+77F8,U+77FE,U+78D0,U+7A37,U+7A92,U+7ABF,U+7AFA,U+7B4F,U+7B60,U+7B75,U+7CEF,U+7EDB,U+7F1C,U+7FB2,U+7FB9,U+7FE1,U+808B,U+80E5,U+8113,U+81C3,U+82D4,U+8309,U+8317,U+8431,U+8469,U+84BF,U+84DF,U+8511,U+853A,U+85B9,U+8638,U+864F,U+86C0,U+8715,U+8822,U+8882,U+8884-8885,U+8892,U+88E8,U+892A,U+8983,U+8BA5,U+8BD9,U+8BE0-8BE1,U+8BE7,U+8C1A,U+8C1F,U+8C27,U+8C5A,U+8E0C,U+8E52,U+8E74,U+8E87,U+8F98,U+8FC2,U+8FE6,U+900D,U+9068,U+9074,U+90B3,U+90E7,U+915A,U+916E,U+93D6,U+949D,U+94B4,U+94D0,U+94E3-94E4,U+94EC,U+9522,U+9556,U+9619,U+9893,U+9981,U+9A6E,U+9A87,U+9A8A,U+9AA1,U+9ABC,U+9B13,U+9E3E,U+9E43,U+9E5C,U+9E6D,U+9E9D,U+9EDB} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/33.woff2") format("woff2");unicode-range:U+4E56,U+4E98,U+4F3A,U+4F5F,U+4F6C,U+4F70,U+4FA5,U+4FE8,U+5014,U+524C,U+5315,U+5364,U+53E8,U+5406,U+549A,U+54E6,U+54EE,U+54FC,U+556A,U+5583,U+55E3,U+5669,U+592F,U+5944,U+595A,U+598D,U+59CA,U+59D7,U+5A7F,U+5B09,U+5E4C,U+5EB6,U+5F08,U+5F1B,U+602F,U+607A,U+608D,U+60CB,U+620C,U+6249,U+62A0,U+62C4,U+62ED,U+62F4,U+6345,U+6382,U+6396,U+63C9,U+63EA,U+6400,U+6413,U+645E,U+6487,U+64AC,U+6556,U+6593,U+65A1,U+6615,U+667E,U+6805,U+68A2,U+69DB,U+6A80,U+6B83,U+6C30,U+6C5E,U+6CAD,U+6CDE,U+6CF1,U+6D31,U+6D93,U+6DAE,U+6DBF,U+6E5F,U+6E85,U+6F5E,U+6F7C,U+701B,U+70AB,U+717D,U+71A8,U+7252,U+72B7,U+72C4,U+72E9,U+7315,U+73AE,U+73BA,U+73C0,U+73D1,U+7435-7436,U+745A,U+74E2,U+7504,U+752C-752D,U+7599,U+759F,U+75A4,U+75F0,U+7600-7601,U+761F,U+7629,U+768B,U+772F,U+77A5,U+77B0,U+783A,U+78D5,U+78FA,U+7934,U+7940,U+7977,U+7B19,U+7B71,U+7B94,U+7CB1,U+7CE0,U+7ECA,U+7EF7,U+7F28,U+7F2B,U+7F81,U+80EB,U+8110,U+812F,U+8148,U+814C,U+8165,U+819B,U+81BA,U+8235,U+828B,U+82AE,U+82B7,U+830E,U+831C,U+8338,U+837C,U+8393,U+83BA,U+83C1,U+84D3,U+84E6,U+853C,U+8654,U+86DB,U+86FE,U+8712,U+873F,U+874E,U+8757,U+8783,U+8859,U+88A4,U+88F3,U+8925,U+8BB7,U+8BFD,U+8BFF,U+8C1B,U+8C24,U+8C2C,U+8D4A,U+8D4E,U+8D61,U+8D66,U+8DDA,U+8F76,U+8F8A,U+8FE5,U+8FF8,U+90AC,U+90B8,U+9119,U+912F,U+9131,U+9163,U+9170,U+949B,U+94A8,U+94C0,U+94E2,U+94FF,U+9525,U+9535,U+9539,U+9550,U+955B,U+965B,U+96CC,U+96F9,U+9774,U+988D,U+9975,U+998D,U+998F,U+9A7F,U+9AB8,U+9C7F,U+9C9F,U+9CA4,U+9E35,U+9E8B,U+9E92,U+9EEF} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/32.woff2") format("woff2");unicode-range:U+4E10,U+4EA5,U+4EC3,U+4F36,U+4F84,U+4F88,U+4F8D,U+4F97,U+501A,U+504C,U+5201,U+52FA,U+5366,U+543C,U+545C,U+5471,U+5480,U+5495,U+54B3,U+54C7,U+54DF,U+557C,U+55DC,U+5632,U+566C,U+5676,U+56B7,U+56DA,U+56E4,U+5742,U+57A2-57A3,U+5815,U+58D1,U+5919,U+592D,U+5955,U+5983,U+59E5,U+5A13,U+5A34,U+5B70,U+5BD0,U+5BF0,U+5C41,U+5C94,U+5CE6,U+5D69,U+5DC5,U+5E16,U+5EB5,U+5F3C,U+5F95,U+604D,U+60E6,U+618B,U+61A7,U+61AC,U+61CB,U+62CE,U+62E7,U+62EE,U+631F,U+634E,U+6479,U+6482,U+655B,U+660A,U+664F,U+66E6,U+672D,U+675E,U+67C4,U+67DA,U+6809,U+6868,U+68F1,U+68FA,U+693F,U+695E,U+69AD,U+6A71,U+6A90,U+6B89,U+6BD9,U+6BE1,U+6C40,U+6CB1,U+6D85,U+6DA3,U+6DAA,U+6DC6-6DC7,U+6E0D,U+6E3A,U+6E43,U+6EBA,U+6F3E,U+6F88,U+6FEE,U+70D9,U+7109,U+71EE,U+722A,U+733F,U+73F2,U+7409,U+7459,U+747E,U+7487,U+748B,U+749E,U+759A,U+75A1,U+75CA,U+75D2,U+75EB,U+766B,U+7738,U+773A,U+777F,U+7785,U+77AA,U+77BF,U+78BE,U+795B,U+796F,U+79A7,U+7A20,U+7A96,U+7AA5,U+7B03,U+7B28,U+7B50,U+7B77,U+7BF1,U+7C38,U+7C95,U+7EAD,U+7EC5,U+7EF0,U+7EFD,U+7F0E,U+7F2E,U+7F9A,U+7FCC,U+8046,U+8098,U+80DA,U+80E7,U+80F0,U+8237-8238,U+8299,U+82CE,U+82D3,U+8301,U+8398,U+83E0,U+83E9,U+8426,U+8475,U+85E9,U+86DF,U+871A,U+8910,U+891A,U+8BB4,U+8BE3,U+8BEC,U+8BF2,U+8C06,U+8C0D,U+8C19,U+8DB4,U+8DE4,U+8E1E,U+8E66,U+8E6C,U+8F84,U+901E,U+9083,U+90E1,U+90F4,U+9165,U+91C9,U+91DC,U+94B3,U+94E8,U+9504,U+9530,U+9532,U+954C-954D,U+95FA,U+962A,U+9685,U+9704,U+9776,U+9985,U+9A6F,U+9AA5,U+9CB8,U+9E25,U+9E4A} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/31.woff2") format("woff2");unicode-range:U+4EA2,U+4FA3,U+4FAE,U+4FD8,U+5029,U+516E,U+5195,U+51A5,U+51C4,U+51DB,U+51F8,U+5228,U+527F,U+5288,U+5321,U+5352,U+53E9,U+5450,U+5484,U+5490,U+54C9,U+5501,U+56A3,U+56BC,U+5703,U+5729,U+575E,U+589F,U+5984,U+5A04-5A05,U+5A25,U+5A36,U+5A77,U+5A9B,U+5AB2,U+5B75,U+5BC7,U+5BDD,U+5C51,U+5C79,U+5C91,U+5CD9,U+5CED,U+5E87,U+5F13,U+5F27,U+5F64,U+5FFB,U+6020,U+6055,U+60EB,U+60F6,U+6123,U+6151,U+61A8,U+620D-620E,U+6241,U+6273,U+6289,U+62C7,U+62CC,U+62D9,U+634D,U+6363,U+6452,U+64D2,U+6726,U+6756,U+6789,U+6829,U+6853,U+6862,U+6866,U+6897,U+68E3,U+6942,U+6995,U+69A8,U+69B4,U+69CC,U+6A1F,U+6A44,U+6A59,U+6BCB,U+6BD3,U+6CBD,U+6CF8,U+6CFE,U+6DA1,U+6E0E,U+6E44,U+6EA5,U+6EE6,U+6F2F,U+6F8E,U+7078,U+7095,U+7099,U+709C,U+70AF,U+70BD,U+70DB,U+70E8,U+70FD,U+714E,U+715E,U+7184,U+71CE,U+7235,U+7239,U+72F8,U+749C,U+74A8,U+74E3,U+75AE,U+75F9,U+7620,U+7693,U+76CF,U+776B,U+778E,U+7852,U+7948,U+7960,U+797A,U+79BA,U+79BE,U+79C3,U+79C6,U+79F8,U+7AA6,U+7BC6,U+7C27,U+7CD9,U+7D0A,U+7D6E,U+7EE2,U+7EEE,U+7F24,U+7F2D,U+7F79,U+800D,U+80F3,U+80FA,U+8151,U+818A,U+81B3,U+81E7,U+82B8,U+8339,U+837B,U+8386,U+83BD,U+83CF,U+85C9,U+85D5,U+85DC,U+8747,U+8749,U+8774,U+888D,U+88F4,U+8912,U+8B6C,U+8C0E,U+8D31,U+8D48,U+8E4A,U+8F97,U+8F99,U+8FE9,U+914C,U+94A0,U+94B0,U+94F5,U+951A,U+952F,U+956D,U+95F5,U+9640,U+9668,U+968B,U+9698,U+96BD,U+96CD,U+96EF,U+9713,U+9773,U+97EC,U+9885,U+9890,U+996A,U+997A,U+9992,U+9A74,U+9A79,U+9A8B,U+9CBB,U+9CC4,U+9CCC} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/30.woff2") format("woff2");unicode-range:U+4E5E,U+4FEF,U+50A3,U+5189,U+51F3,U+51F9,U+52FF-5300,U+532E,U+533F,U+5351,U+53DB,U+53ED,U+543B,U+5455,U+54E9,U+5507,U+5543,U+5578,U+55D3,U+560E,U+5830,U+5962,U+59E8,U+59EC,U+5A07,U+5A23,U+5A3C,U+5A9A,U+5AC2,U+5AD6,U+5C2C,U+5C34,U+5CB1,U+5CE8,U+5D0E,U+5DEB,U+5E3C,U+5ED3,U+5F87,U+5FE1,U+5FF1,U+6064,U+606A,U+6096,U+60DF,U+60F9,U+6252,U+627C,U+62C2,U+62E3,U+62EF,U+634F,U+6361,U+63B7,U+63E3,U+6405,U+6495,U+6512,U+6518,U+65A9,U+65F7,U+6627,U+66AE,U+6795,U+6813,U+6893,U+68D5,U+690E,U+6977,U+6984,U+69BB,U+6A31,U+6B7C,U+6BA1,U+6C13,U+6C72,U+6C79,U+6C81,U+6C90,U+6C93,U+6CA6,U+6CBC,U+6CCC,U+6CD3,U+6CD7,U+6CFB,U+6D4A,U+6D5A,U+6D9F,U+6DA9,U+6DCC,U+6E2D,U+6EAF,U+6F13,U+6FA1,U+7011,U+701A,U+707C,U+70D8,U+71A0,U+7280-7281,U+72D0,U+7357,U+73AB,U+7410,U+745B-745C,U+7480,U+74A7,U+7578,U+7682,U+76B1,U+76CE,U+7736,U+77D7,U+77E2-77E3,U+77EB,U+780C,U+781A,U+789F,U+7941,U+7A8D,U+7A98,U+7A9C,U+7AFF,U+7B1B,U+7BF7,U+7C07,U+7CA5,U+7E82,U+7EAB,U+7EDA,U+7EDE,U+7FD8,U+7FDF,U+7FE9,U+803D,U+80AA,U+8116,U+8155,U+8205,U+821C,U+82C7,U+82DB,U+82EF,U+8304,U+832C,U+8335,U+8446,U+846B,U+8587,U+85AF,U+85FB,U+8611,U+8682,U+868A,U+868C,U+86D9,U+86EE,U+889C,U+88D8,U+88F8,U+8BBD,U+8BF5,U+8C41,U+8C79,U+8D3B-8D3C,U+8D50,U+8DEA,U+8E29,U+8E35,U+8E44,U+8EAC,U+8F8D,U+8FE2,U+9017,U+916F,U+9175-9176,U+918B,U+94CE,U+94F2,U+9541,U+9672,U+9699,U+9761,U+97A0,U+9A6D,U+9A85,U+9A8F,U+9CD7,U+9CDE,U+9E26,U+9E93,U+9F9F} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/29.woff2") format("woff2");unicode-range:U+4E4D,U+4EC7,U+4ED5,U+4F51,U+4F63,U+5018,U+5026,U+508D,U+50DA,U+50E7,U+515C,U+51A4,U+51FF,U+5203,U+5254,U+533E,U+5375,U+53EE,U+5435,U+548B,U+548F,U+54D7,U+54FA,U+5587,U+5598,U+55A7,U+575F-5760,U+576F,U+5777,U+57AE,U+58A9,U+5978,U+5A49,U+5B5A,U+5BB5,U+5BC5,U+5BDE,U+5BE1,U+5C7F,U+5CEA,U+5E18,U+5F57,U+5F5D,U+6016,U+601C,U+6021,U+606D,U+60E7,U+6177,U+61D2,U+625B,U+6296,U+62F1,U+631D,U+63A0,U+6401,U+6467,U+64BC,U+64C2,U+6577,U+6590,U+65A7,U+6643,U+6655-6656,U+6687,U+66DD,U+6714,U+67D1,U+67FF,U+6816,U+68CD,U+68D8,U+68E0,U+6930,U+6960,U+69D0,U+6BB4,U+6BD7,U+6C22,U+6C2E-6C2F,U+6C7E,U+6CA5,U+6CE3,U+6D3C,U+6DE4,U+6DF3,U+6EC7,U+6ED5,U+6F33,U+6F62,U+708A,U+70C1,U+70EB,U+70F9,U+711A,U+716E,U+7194,U+71AC,U+71E5,U+7316,U+7384,U+7405,U+7422,U+742A,U+745F,U+7574,U+75AF,U+75EA,U+763E,U+7741,U+7792,U+77A7,U+77EE,U+77F6,U+785D,U+78C5,U+7985,U+79E4,U+7A1A,U+7AD6,U+7B5D,U+7C3F,U+7C9F,U+7CDF,U+7ECE,U+7EE5,U+7F09,U+7F15,U+7F2A,U+7F38,U+7F9E,U+803B,U+804B,U+80B4,U+813E,U+817A-817B,U+819D,U+81FB,U+82AD,U+82B9,U+82DF,U+8354,U+835F,U+8367,U+83B9,U+841D,U+853D,U+8650,U+865E,U+8700,U+8862,U+88D4,U+895F,U+89C5,U+8A79,U+8BB3,U+8BB6,U+8BC0,U+8BEB,U+8C23,U+8D1E,U+8DCB,U+8E72,U+8E81,U+8EAF,U+9050,U+9091,U+90F8,U+914B,U+948A,U+9499,U+94BE,U+94EE,U+950C,U+9524,U+952D,U+9540,U+9576,U+9600,U+962E,U+9647,U+9661,U+96CF,U+9716,U+97E7,U+98A4,U+9A7C,U+9A86,U+9AE6,U+9CD6,U+9E3D} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/28.woff2") format("woff2");unicode-range:U+4E19,U+4E38,U+4EA8,U+4ED1,U+4F1E,U+4F7C,U+4F83,U+4FA0,U+4FD1,U+4FDE,U+4FFA,U+50BB,U+50F5,U+50FB,U+5162,U+5256,U+5319,U+5320,U+538C,U+5413,U+541F,U+5475,U+54BD,U+54D1,U+5589,U+561B,U+57E0,U+5937,U+5974,U+59AE,U+5A1F,U+5AB3,U+5B99,U+5B9B,U+5BA0,U+5BE5,U+5C09,U+5C27,U+5C38,U+5D16,U+5DCD,U+5DE2,U+5E90,U+5E9A,U+5F26,U+5F8A,U+5F98,U+607C,U+60BC,U+60D5,U+611A,U+614C,U+6254,U+626F,U+6292,U+62B9,U+62E2,U+62FE,U+631A,U+6320,U+6346,U+63BA,U+658B,U+663C,U+6652,U+6674,U+66D9,U+66F0,U+673D,U+67AB,U+6817,U+68A7,U+68AD,U+6986,U+6B47,U+6B49,U+6B6A,U+6C1F,U+6CBE,U+6E1D,U+6E23,U+6E9C,U+6ED4,U+6F4D,U+6F84,U+7130,U+718F,U+72FC,U+7334,U+73CA,U+7426,U+7470,U+7529,U+75D5,U+75F4,U+762B,U+7696,U+7737,U+7766,U+77BB,U+7845,U+788C,U+78CA,U+7901,U+796D,U+79B9,U+79FD,U+7A3C,U+7A57,U+7A74,U+7A9F,U+7B0B,U+7C7D,U+7CAA,U+7CB9,U+7CD5,U+7EAC,U+7EB6,U+7F20,U+7F69,U+7FA1,U+7FC5,U+8018,U+8038,U+803F,U+80A2,U+80BE,U+80D6,U+81ED,U+820C,U+829C,U+82AF,U+82BD,U+82D1,U+8327,U+836B,U+839E,U+8403,U+840C,U+840E,U+8471,U+849C,U+84B2,U+84C9,U+8517,U+851A,U+8549,U+8681,U+8721,U+8776,U+87F9,U+88D9,U+88F9,U+8C1C,U+8C34,U+8D2E,U+8D43,U+8D81,U+8D9F,U+8E0A,U+8E22,U+8EB2,U+8F69,U+901B,U+902E,U+9038,U+906E,U+90AA,U+90AF,U+90DD,U+915D,U+9171,U+946B,U+9489,U+94A7,U+9508,U+9523,U+9551,U+95F7,U+964C,U+96B6,U+96C0,U+9756,U+97F6,U+98A0,U+9965,U+997F,U+99A8,U+9A73,U+9A82,U+9AD3,U+9ED4} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/27.woff2") format("woff2");unicode-range:U+4E53,U+4E7E,U+4E9F,U+4EC6,U+4F50,U+502A,U+5154,U+517D,U+51D1,U+51F0,U+5239,U+52C9,U+52FE,U+5308,U+532A,U+535C,U+5384,U+53A2,U+53A8,U+5496,U+54AC,U+54C0,U+54C4,U+54E8,U+5561,U+5566,U+5582,U+5631,U+5764,U+576A,U+5792,U+57AB,U+584C,U+5885,U+58F6,U+5ACC,U+5BC2,U+5C39,U+5C60,U+5C6F,U+5C82,U+5C90,U+5E37,U+5EB8,U+5F6A,U+5FCC,U+6012,U+6068,U+6073,U+6094,U+6109,U+621A,U+626E,U+6284,U+62D0,U+6321,U+6328,U+632B,U+6349,U+6454,U+6591,U+65ED,U+660F,U+66A8,U+6749,U+674F,U+6760,U+67AF,U+67EF,U+6850,U+6854,U+6869,U+68A8,U+68D2,U+6912,U+6BEF,U+6C28,U+6C5D,U+6C82,U+6CAB,U+6CB8,U+6CF5,U+6D47,U+6D74,U+6D78,U+6D9D,U+6DA4,U+6DC4,U+6DCB,U+6DF9,U+6E0A,U+6E5B,U+6E89,U+6EB6,U+6F06,U+6F47,U+6F9C,U+6FD2,U+7076,U+70AC,U+70EF,U+7199,U+723D,U+72AC,U+72ED,U+7476,U+752B,U+754F,U+7554,U+7626,U+76EF,U+7720,U+7784,U+77AC,U+780D,U+7838,U+786B,U+78B3,U+7978,U+7984,U+79C9,U+79E7,U+7A3D,U+7A84,U+7B52,U+7F1A,U+7FF0,U+8086-8087,U+808C,U+809A,U+80BA,U+810A,U+8180,U+818F,U+81C2,U+8231,U+8292,U+82A6,U+8346,U+838E,U+83C7,U+83F1,U+8513,U+857E,U+859B,U+85E4,U+867E,U+871C,U+87BA,U+884D,U+8944,U+8A93,U+8C05,U+8C10,U+8D2C,U+8D42,U+8DFB,U+8EBA,U+8F9C,U+8FA3,U+8FA8,U+8FB1,U+9042,U+904F,U+90B5,U+9102,U+9187,U+94C5,U+95F8,U+95FD,U+960E,U+964B,U+96C1,U+9709,U+971C,U+97AD,U+9882,U+9976,U+9A9A,U+9B3C,U+9B41,U+9C8D,U+9E45,U+9E70,U+9E9F,U+9F3B,U+9F7F} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/26.woff2") format("woff2");unicode-range:U+4E11,U+4ED7,U+4FE9,U+4FED,U+50B2,U+5112,U+5180,U+5188,U+51F6,U+522E,U+5265,U+52CB,U+52DF,U+5349,U+5367,U+5378,U+5395,U+5398,U+53D4,U+543E,U+5446,U+54B8,U+5580,U+55BB,U+56CA,U+574E,U+5782,U+5784,U+58F3,U+5938,U+5948,U+594E,U+5A74,U+5AE9,U+5B55,U+5B5C,U+5BB0,U+5BD3,U+5D14,U+5D2D,U+5DF7,U+5DFE,U+5E05-5E06,U+5E1C,U+5E62,U+5E7B,U+5E7D,U+5ED6,U+5F2F,U+5F66,U+5F6C,U+5FA1,U+604B,U+60A6,U+60E8,U+6101,U+6124,U+6127,U+6148,U+61BE,U+6247,U+62D8,U+633D,U+635E,U+6367,U+6380,U+638F,U+63A9,U+64E6,U+655E,U+6691,U+6735,U+67A2-67A3,U+6905,U+6A0A,U+6A61,U+6B79,U+6BB7,U+6BBF,U+6C41,U+6C55,U+6C83,U+6C9B,U+6CA7,U+6CFC,U+6D46,U+6D51,U+6DAF,U+6DC0,U+6DEB,U+6E17,U+6E24,U+6EA2,U+6EF4,U+6F6D,U+70B3,U+70E4,U+710A,U+725F,U+7261,U+72EE,U+72F1,U+730E,U+732B,U+7433,U+7538,U+75BC,U+7624,U+7750,U+7779,U+7802,U+7898,U+78A7,U+78B1,U+78CB,U+78F7,U+7AED,U+7B3C,U+7B4B,U+7C98,U+7CA4,U+7EB9,U+7EE3,U+7EF3,U+7EF5,U+7F05,U+7F55,U+7FC1,U+7FD4,U+7FE0,U+8042,U+806A,U+80A0,U+80C3,U+8102,U+8106,U+814A,U+8154,U+8247,U+8258,U+8328,U+832B,U+8389,U+845B,U+846C,U+84B8,U+8574,U+8680,U+8695,U+86C7,U+8702,U+886C,U+8896,U+88B1,U+88E4,U+8BC8,U+8C26,U+8C2D,U+8D4C,U+8D63,U+8F67,U+8FC4,U+9006,U+9063,U+90A2,U+9177,U+9189,U+9493,U+949E,U+9610,U+961C,U+96A7,U+96FE,U+978D,U+97F5,U+997C,U+9A84,U+9B44-9B45,U+9B54,U+9E64,U+9F9A} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/25.woff2") format("woff2");unicode-range:U+4E18,U+4E1B,U+4E52,U+4EAD,U+4EF0,U+4FA6,U+4FCF,U+5076,U+50AC,U+5179,U+51EF,U+526A,U+529D,U+5306,U+537F,U+53D9,U+540A,U+5439,U+5440,U+54ED,U+558A,U+55B7,U+5634,U+572D,U+574A,U+57A6,U+57CB,U+5835,U+58E4,U+5951,U+5986,U+59DA,U+59FB,U+59FF,U+5A03,U+5A1C,U+5A46,U+5AC1,U+5B5D,U+5BF8,U+5C3F,U+5C48,U+5C61,U+5CB3,U+5E15,U+5E3D,U+5E99,U+5E9E,U+5F0A,U+5F17-5F18,U+5F25,U+5F7C,U+5FCD,U+6028,U+609F-60A0,U+60AC,U+614E,U+6155,U+6168,U+61C8,U+6212,U+6251,U+62AB-62AC,U+62DA,U+6323,U+63FD,U+6492,U+64B0,U+64C5,U+6572,U+6602,U+6614,U+662D,U+6664,U+6746,U+67D4,U+680B,U+682A,U+68DA,U+6B3A,U+6B67,U+6C5B,U+6C70,U+6CAA,U+6CE1,U+6D12,U+6DD1,U+6E34,U+6E7F,U+6F02,U+707F,U+7092,U+7115,U+7237,U+727A,U+72C2,U+739B,U+73B2,U+743C,U+751C,U+758F,U+75B2,U+7686,U+76C6,U+76D2,U+76FC,U+7709,U+77A9,U+788E,U+7897,U+79BD,U+7A83,U+7A91,U+7A9D,U+7AE3,U+7BAD,U+7D2B,U+7EB1,U+7F06,U+7F14,U+7F1D,U+7F50,U+7F62,U+7FFC,U+8036,U+80A4,U+80BF,U+80CE,U+80F8,U+810F,U+8179,U+819C,U+821F,U+829D,U+82CD,U+82F9,U+8305,U+83B2,U+83CA,U+8404,U+840D,U+8427,U+8679,U+8854,U+886B,U+8BBC,U+8C28,U+8C6B,U+8E48,U+8F90,U+8FB0,U+9022,U+903C,U+90B1,U+90C1,U+916C,U+917F,U+94A9,U+94C3,U+94ED,U+9510,U+953B,U+96C7,U+970D,U+9738,U+97E6,U+9877,U+987D,U+98D8,U+9A70,U+9A91,U+9B42,U+9B4F,U+9E2D,U+9E3F,U+9E7F,U+9F0E,U+9F20} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/24.woff2") format("woff2");unicode-range:U+4E08,U+4E27,U+4E59,U+4E73,U+4ED9,U+4F0F,U+4FAF,U+5085,U+5144,U+5146,U+5151,U+51AF,U+51BB,U+51C9,U+51CC,U+524A,U+5251,U+52AB,U+5353,U+5389,U+53F9,U+5415,U+541B,U+54F2,U+5524,U+5564,U+575D,U+57D4,U+5821,U+5824,U+582A,U+5858,U+5893,U+5915,U+5960,U+5999,U+59A5,U+59B9,U+59C6,U+59D1,U+59DC,U+5B64,U+5B87,U+5BB4,U+5BFA,U+5C16,U+5C18,U+5C9A,U+5CFB,U+5D1B,U+5DE1,U+5DE9,U+5ECA,U+5EF7,U+5F79,U+6052,U+6084,U+60B2,U+60DC,U+60E9,U+6208,U+6291,U+629A,U+62F3,U+632A,U+640F,U+642C,U+643A,U+6458,U+6478,U+649E,U+6500,U+654C,U+658C,U+659C,U+6676,U+6697,U+679D,U+67CF,U+67DC,U+683D,U+6843,U+684C,U+699C,U+6C0F,U+6C1B,U+6C27,U+6C64,U+6C6A,U+6CCA,U+6D3D,U+6D82,U+6DB5,U+6DEE,U+6E58,U+6ECB,U+6EE5,U+6F20,U+6F58,U+70C2,U+718A,U+7238,U+7262,U+7272,U+72B9,U+72D7,U+72E0,U+741B,U+7434,U+74F7,U+764C,U+775B,U+7761,U+7816,U+7855,U+7891,U+7A00,U+7ED2,U+7EF8,U+7FBD,U+8000,U+8096,U+809D,U+80A9,U+80C1,U+8109,U+8170,U+817F,U+81E3,U+8212,U+8230,U+8236,U+8273,U+82AC,U+8302,U+8361,U+8377,U+83CC,U+83F2,U+8461,U+8482,U+84EC,U+8521,U+8870,U+8877,U+8881,U+88AD,U+88C2,U+8BF1,U+8D24,U+8D2A,U+8D3E-8D3F,U+8D41,U+8D64,U+8DCC,U+8E0F,U+8F70,U+8FA9,U+9003,U+901D,U+903E,U+9065,U+90CE,U+94A6,U+94DD,U+94F8,U+95EA,U+95EF,U+9601,U+96D5,U+971E,U+9896-9897,U+9A7E,U+9E1F} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/23.woff2") format("woff2");unicode-range:U+4E43,U+4EA6,U+4EF2,U+4EFF,U+4F69,U+4FD7,U+4FF1,U+5021,U+5077,U+51B6,U+51DD,U+51E4,U+5200,U+523A,U+52D8,U+5401,U+54A8,U+559D,U+5609,U+5723,U+575B,U+57C3,U+5899,U+58C1,U+5976,U+5988,U+59D0,U+5A18,U+5A31,U+5B54,U+5B5F,U+5B85,U+5BD2,U+5BE8,U+5BFF,U+5C65,U+5CA9,U+5CAD,U+5D07,U+5E1D,U+5E7C,U+5F6D,U+5F70,U+5F92,U+6050,U+6108,U+6167,U+6170,U+6234,U+626D,U+62B1,U+62BC,U+62D2,U+62DC,U+6324,U+63ED,U+6447,U+64A4,U+6566,U+65C1,U+65E8,U+65EC,U+65F1,U+6606,U+6628,U+6668,U+6696,U+66F9,U+66FC,U+6717,U+6734,U+675C,U+676D,U+679A,U+67AA,U+67EC,U+67F3-67F4,U+6842,U+6851,U+6B32,U+6BC1,U+6BC5,U+6C57,U+6CEA,U+6CF3,U+6D53,U+6D66,U+6D69,U+6D9B,U+6DB2,U+6DE1,U+6EAA,U+6EDE,U+6EE8-6EE9,U+6F2B,U+704C,U+706D,U+7089,U+708E,U+70AE,U+70B8,U+71C3,U+71D5,U+7275,U+73BB,U+7483,U+74DC,U+7532,U+75AB,U+75C7,U+7687,U+76D7,U+788D,U+78E8,U+7A3B,U+7A46,U+7AF9,U+7BEE,U+7CD6,U+7EA4,U+7EB2,U+7EB5,U+7EBD,U+7F18,U+8010,U+8033,U+80C0,U+80F6,U+8138,U+81A8,U+8206,U+827E,U+82B3,U+83B1,U+848B,U+864E,U+888B,U+8BCA,U+8C0A,U+8C46,U+8D29,U+8D4F,U+8D5A,U+8D60,U+8D62,U+8D6B,U+8F7F,U+8F88,U+8F96,U+8F9E-8F9F,U+90B9,U+90CA,U+9178,U+94BB,U+94DC,U+94FA,U+9505,U+950B,U+9521,U+95F9,U+9634,U+966A,U+9675,U+9887,U+9971-9972,U+9A76,U+9A97,U+9E23} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/22.woff2") format("woff2");unicode-range:U+4E01,U+4E39,U+4E4C,U+4E54,U+4E95,U+4EA1,U+4EC1,U+4F10,U+4F26,U+4F2F,U+4F5B,U+4FA8,U+4FB5,U+4FCA,U+5192,U+51AC,U+51B0,U+51C0,U+51E1,U+51ED,U+5211,U+5242,U+52A3,U+52B2,U+52C3,U+52C7,U+52D2,U+5362,U+53A6,U+54E5,U+5510,U+5761,U+5851,U+5854,U+58A8,U+58EE,U+593A,U+5949,U+5954,U+59BB,U+59D3,U+5A5A,U+5B8B,U+5BAA-5BAB,U+5C0A,U+5C4B,U+5CE1,U+5E10,U+5E8A,U+5E9F,U+5EC9,U+5F1F,U+5FBD,U+5FE0,U+5FE7,U+6069,U+60A3,U+60A8,U+624E,U+6263,U+62D4,U+62E8,U+6350,U+63F4,U+6446,U+644A,U+6469,U+6562,U+656C,U+65D7,U+65FA,U+6653,U+676F-6770,U+67F1,U+6881,U+6885,U+68A6,U+68CB,U+68EE,U+6B20,U+6B23,U+6B96,U+6C88,U+6CC9,U+6D17,U+6D1B,U+6D59,U+6D8C,U+6E14,U+6E21,U+70AD,U+70E7,U+714C,U+7259,U+7267,U+731B,U+732A,U+73CD,U+7518,U+755C,U+756A,U+7586,U+76C8,U+76D0,U+76DF,U+76F2,U+77DB,U+795D,U+7965,U+79E6,U+79E9,U+7A3F,U+7A77,U+7B11,U+7C89,U+7EA0,U+7F34,U+7F8A,U+8015,U+8083,U+80C6,U+80DE,U+8150,U+820D,U+82D7,U+8352,U+83AB,U+8428,U+8463,U+84C4,U+852C,U+8584,U+86CB,U+88D5,U+8BDA,U+8BFA,U+8C0B,U+8C6A,U+8C8C,U+8D3A,U+8D54,U+8D74,U+8D76,U+8F9B,U+8FBD,U+8FC8,U+8FEA-8FEB,U+8FF9,U+906D,U+90A6,U+90ED,U+91CE,U+9526,U+9614,U+9655,U+9676,U+9686,U+96EA,U+9707,U+978B,U+9881,U+9E21,U+9EA6,U+9ECE,U+9F84} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/21.woff2") format("woff2");unicode-range:U+4E3D,U+4E8F,U+4EA9,U+4EEA,U+4F0A,U+4F0D,U+4F11,U+4F1F,U+4FC4,U+503A,U+507F,U+5170,U+51A0,U+520A,U+5238,U+5267,U+52E4,U+5348,U+5371,U+5385,U+539A,U+5409,U+5428,U+5434,U+547C,U+5531,U+56ED,U+5733,U+5766,U+5802,U+590F,U+591C,U+594B,U+5965,U+5987,U+5B59,U+5B63,U+5B81,U+5B97,U+5B9C,U+5BBE,U+5C1A,U+5C3C,U+5C81,U+5C9B,U+5CB8,U+5DDD,U+5E84,U+5E86,U+5EA7,U+5EAD,U+5F90,U+6000,U+60E0,U+626C,U+6276,U+6297,U+62B5,U+632F,U+6551,U+65A4,U+660C,U+671D,U+6728,U+6731,U+6768,U+6865,U+68B0,U+68C9,U+690D,U+697C,U+6B27,U+6B8B,U+6BD2,U+6BDB,U+6C49,U+6C61,U+6C7D,U+6C99,U+6CE5,U+6CF0,U+6CFD,U+6D0B,U+6D25,U+6D2A,U+6DA6,U+6DA8,U+6E29,U+6E56,U+6E7E,U+6F6E,U+6FB3,U+707E,U+70DF,U+7164,U+7389,U+73E0,U+745E,U+74E6,U+7530,U+7537,U+7597,U+75BE,U+76AE,U+76DB,U+76FE,U+7763,U+77FF,U+793C,U+7956,U+7981,U+798F,U+79CB,U+79D8,U+79DF,U+7AE5,U+7B51,U+7B79,U+7EB7-7EB8,U+7EBA,U+7F5A,U+7F6A,U+8089,U+80A5,U+80E1,U+821E,U+8239,U+8270,U+82CF,U+8336,U+8349,U+8499,U+8840,U+8857,U+8863,U+8A89,U+8D2B,U+8D2F,U+8D37,U+8D75,U+8F68,U+8F86,U+8F89,U+8FDD,U+9093,U+90D1,U+9152,U+94A2,U+9646,U+96C4,U+96E8,U+96F7,U+97E9,U+996D-996E,U+9986,U+9A7B,U+9AA8,U+9C7C,U+9C81,U+9E4F,U+9F99} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/20.woff2") format("woff2");unicode-range:U+4E03,U+4E1D,U+4E4F,U+4E5D,U+4E61,U+4E88,U+4EAC,U+4F19,U+4F24,U+4F2A,U+4FC3,U+515A,U+516B,U+5175,U+5197,U+519B-519C,U+51B7,U+5218,U+52B3,U+533B,U+5357,U+536B,U+53BF,U+53E0,U+53E4,U+53EC,U+5565,U+566A,U+5706,U+571F,U+592E,U+5939,U+594F,U+59A8,U+59D4,U+5A01,U+5B88,U+5BA3-5BA4,U+5BBF,U+5C3A,U+5C45,U+5C4A,U+5DDE,U+5E2D,U+5E78,U+5E9C,U+5EB7,U+5F04,U+5F7B,U+5FB7,U+5FD9,U+602A,U+6076,U+60CA,U+6233,U+6258,U+6270,U+62A2,U+62D6,U+62E6,U+633A,U+6355,U+63AA,U+653F,U+6597,U+65A5,U+65C5,U+65CB,U+65CF,U+6625,U+664B,U+665A,U+672B,U+6751,U+6797,U+68AF,U+68F5,U+69FD,U+6A2A,U+6B66,U+6C5F,U+6C89,U+6CB3,U+6CB9,U+6CBF,U+6CC4,U+6D01,U+6D32,U+6D45,U+6D4E,U+6D6E,U+6E20,U+6E2F,U+6E83,U+6ED1,U+706F-7070,U+70C8,U+7206,U+72AF,U+731C,U+7545,U+75C5,U+77F3,U+7A0E,U+7B5B,U+7C4D,U+7C73,U+7C97,U+7CAE,U+7CCA,U+7EAA,U+7EFF-7F00,U+7F57,U+80B2,U+80DC,U+8131,U+822A,U+8363,U+836F,U+83DC,U+85AA,U+88C1,U+8BDE,U+8BF8,U+8C31,U+8D21,U+8D35,U+8D38,U+8D5B,U+8D5E,U+8F74,U+8FC5,U+8FF7,U+900A,U+9080,U+90BB,U+94C1,U+9547,U+95FB,U+9608,U+9633,U+9644,U+9648,U+9662,U+9752,U+9769,U+98DF,U+9910,U+9970,U+9999,U+9C9C,U+9EC4,U+9F13,U+FF5E} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/19.woff2") format("woff2");unicode-range:U+251C,U+4E30,U+4E58,U+4E71,U+4E9A,U+4EAE,U+4EB2,U+4EBF,U+4ED3-4ED4,U+4F34,U+4F38,U+4F73,U+5012,U+503E,U+5065,U+516D,U+517B,U+5237,U+5269,U+5272,U+52AA,U+5317,U+534E,U+5377,U+53C9,U+53F6,U+5403,U+5410,U+541E,U+5438,U+5448,U+54B1,U+554A,U+5708,U+573E,U+5750-5751,U+575A,U+5783,U+57CE,U+57F9,U+586B,U+58EB,U+592B,U+5956,U+5973,U+5979,U+5A92,U+5B69,U+5B8F,U+5BB3,U+5C71,U+5C97,U+5CF0,U+5D29,U+5DF4,U+5E55,U+5E97,U+5F31,U+5F39,U+5F69,U+5FC6,U+5FD8,U+5FFD,U+6015,U+6025,U+6070,U+60D1,U+6298,U+629B,U+62D3,U+62F7,U+62FC,U+6316,U+6398,U+6491,U+65E7,U+661F,U+6682,U+66F2,U+6740,U+674E,U+6750,U+6876,U+6B4C,U+6BCD,U+6BEB,U+6C11,U+6C38,U+6CBB,U+6CE2,U+6D3E,U+6D6A,U+6DD8,U+6EDA,U+6F5C,U+70BC,U+70E6,U+725B,U+732E,U+738B,U+73ED,U+75DB,U+78B0,U+7A0D,U+7A7F,U+7B14,U+7BB1,U+7C92,U+7EB3,U+7ED1,U+7EEA,U+7EFC,U+8026,U+8058,U+80A1,U+817E,U+826F,U+827A,U+82E5-82E6,U+8457,U+84DD,U+85CF,U+866B,U+8B66,U+8BAF,U+8BD7,U+8C37,U+8D22,U+8D27,U+8D34,U+8D8B,U+8DC3,U+8F85,U+9014,U+9057,U+9075,U+90AE,U+9192,U+9274,U+94AE,U+94F6,U+95F2,U+96C5,U+987F,U+9888,U+989C,U+98DE,U+9EBB,U+9F50} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/18.woff2") format("woff2");unicode-range:U+4E22,U+4E94,U+4ECD,U+4F17,U+4FA7,U+500D,U+5141,U+514B,U+517C,U+526F,U+52B1,U+5343,U+5356,U+535A,U+5382,U+53F2,U+552E-552F,U+5584,U+56F0,U+56FA,U+574F,U+5947,U+5B9D,U+5BA1,U+5BBD,U+5BC4,U+5BCC,U+5BFB,U+5C14,U+5C24,U+5C3E,U+5C4F,U+5D4C,U+5DE7-5DE8,U+5E01,U+5E27,U+5E45,U+5F03,U+5F8B,U+6062,U+6089,U+60EF,U+613F,U+61C2,U+622A,U+626B,U+6279,U+6293,U+62C5,U+62C9,U+62DB,U+62E5,U+6302,U+6325,U+635F,U+6377,U+6388,U+641E,U+6444,U+653B,U+654F,U+6563,U+65AF,U+65E6,U+6670,U+66B4,U+66FE,U+677E,U+6821,U+68B3,U+6B3E,U+6B7B,U+6B8A,U+6C14,U+6C60,U+6D1E,U+6D77,U+6DF7,U+6E10,U+6EE4,U+6F0F,U+6FC0,U+706B,U+7126,U+722C,U+7231,U+7236,U+724C,U+73A9,U+7403,U+74F6,U+7533,U+767B,U+76D6,U+7701,U+773C,U+77E9,U+7834,U+78C1,U+7968,U+79C0-79C1,U+7A97,U+7ADE-7ADF,U+7D27,U+7D2F,U+7E41,U+7EA2,U+7EAF,U+7ED5,U+7ED8,U+7EE9,U+7FFB,U+82F1,U+878D,U+8986,U+8C01,U+8C13,U+8C22,U+8D1D,U+8D26,U+8D4B,U+8DD1,U+8DDD,U+8DE8,U+8E2A,U+8F6E,U+8FC1,U+8FDF,U+8FFD,U+9000,U+900F,U+94A5,U+955C,U+9632,U+9635,U+963B,U+963F,U+9677,U+9690,U+96F6,U+9732,U+9988} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/17.woff2") format("woff2");unicode-range:U+2500,U+2502,U+4E16,U+4E1C,U+4E25,U+4E34,U+4E45,U+4E4E,U+4E50,U+4E70,U+4E89,U+4F30,U+4F4F,U+4F59,U+501F,U+504F,U+505C,U+513F,U+5145,U+5149,U+5174,U+518C,U+51B2,U+5220,U+523B,U+5339,U+534A,U+5360-5361,U+5370,U+5374,U+53CC,U+53F3,U+5427,U+5473,U+54C8,U+559C,U+5806,U+585E,U+5B98,U+5BDF,U+5C01,U+5C04,U+5C1D,U+5DE6,U+5E72,U+5EF6,U+5F84,U+6162,U+620F,U+623F,U+627F,U+62BD,U+62C6,U+62CD,U+62FF,U+6311,U+6389,U+63A2,U+641C,U+642D,U+64AD,U+64CE,U+6559,U+6599,U+65BD,U+65E2,U+65E9,U+6620,U+666E,U+66FF,U+6708,U+67D3,U+6B62,U+6BD5,U+6C47,U+6C9F,U+6CDB,U+6D4F,U+6D89,U+6DFB,U+6E32,U+6F14,U+7075,U+70ED,U+7591,U+767D-767E,U+76CA,U+793E,U+795E,U+79D1-79D2,U+7A33,U+7A81,U+7B7E,U+7EC7,U+7EDD,U+7F29,U+7F8E,U+8017,U+805A,U+80AF,U+8111,U+811A,U+8425,U+843D,U+8861,U+8865,U+897F,U+89C8,U+89E6,U+8BA2,U+8BC9,U+8C08,U+8D25,U+8D2D,U+8D70,U+8DA3,U+8DF3,U+8F66,U+8F7B,U+8FED,U+9010,U+904D,U+949F,U+94B1,U+95ED,U+9669,U+9694,U+9759,U+9760,U+9876,U+987E,U+989D,U+9A6C,U+9A71,U+9AA4,U+9ED1,U+9ED8} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/16.woff2") format("woff2");unicode-range:U+4E07,U+4E32,U+4E3E,U+4E66,U+4E91,U+4ED8,U+4EFD,U+4F01,U+5047,U+514D,U+5178,U+51CF,U+51FB,U+5207,U+5212,U+521A,U+5224,U+529E,U+52BF,U+5341,U+5386,U+538B,U+53CB,U+53D7,U+53EB,U+5417,U+542C,U+5468,U+56DB,U+56F4,U+56FD,U+5740,U+5747,U+58F0,U+592A,U+5957,U+5BC6,U+5C3D,U+5C40,U+5E02,U+5F20,U+5F52,U+5F81,U+5F85,U+5FAA,U+5FD7,U+5FF5,U+6218,U+6269,U+6295,U+62A4-62A5,U+62DF,U+62EC,U+638C,U+63CF,U+63D2,U+63E1,U+6545,U+667A,U+670B,U+671B,U+672A,U+675F,U+677F,U+6781,U+6808,U+680F,U+6811,U+6863,U+695A,U+6C34,U+6E38,U+6EE1,U+719F,U+72EC,U+751A,U+7535,U+753B,U+76D1,U+76D8,U+77ED,U+7814,U+786C,U+79EF-79F0,U+79FB,U+7A76,U+7AD9,U+7CBE,U+7EA6,U+7EC3,U+7EE7,U+7F3A,U+7F72,U+7FA4,U+8001,U+804A,U+804C,U+80CC,U+822C,U+82B1,U+8303,U+8350,U+8651,U+865A,U+867D,U+8BA8,U+8BAD,U+8BB8,U+8BC4,U+8BE6,U+8D23,U+8D56,U+8D85,U+8DF5,U+8FB9,U+8FD1,U+8FDC,U+9012,U+9047,U+907F,U+91CA,U+91D1,U+9500-9501,U+9605,U+964D,U+969C,U+97F3,U+987A-987B,U+98CE} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/15.woff2") format("woff2");unicode-range:U+4E13,U+4E92,U+4EC5,U+4ECB,U+4EF7,U+4F3C,U+4F4E,U+4F9D,U+4FBF,U+4FEE,U+5171,U+521D,U+5229,U+52A9,U+5347,U+5373,U+53C8,U+53CD,U+53E5-53E6,U+53F7,U+542B,U+542F,U+544A,U+5546,U+5883,U+5931,U+5934,U+5B89,U+5C5E,U+5DEE,U+5E08,U+5E0C,U+5E2E,U+5E7F,U+5E95,U+5F02,U+5F3A,U+5F62,U+5F71,U+5F80,U+5FAE,U+5FC5,U+5FEB,U+62E9,U+6309,U+6392,U+65E5,U+6613,U+663E,U+6742-6743,U+67D0,U+6846,U+68C0,U+6982,U+6B22,U+6D3B,U+6DF1,U+6E05,U+7167,U+7248,U+7269,U+754C,U+7559,U+7565,U+771F,U+7840,U+79BB,U+7A7A,U+7ACB,U+7AE0,U+7B54,U+7B56,U+7BC7,U+7D20,U+7EC6,U+7EC8,U+7ECD,U+7F13,U+8054,U+81F3,U+8272,U+88C5,U+89C1-89C2,U+89C9,U+89D2,U+8BBF,U+8BCD,U+8BD1,U+8BE2,U+8BEF,U+8D1F,U+8D28,U+8D39,U+8D8A,U+8DB3,U+8DDF,U+8EAB,U+8F6F,U+8F7D,U+8FCE,U+8FD4,U+8FDE,U+8FF0,U+9001-9002,U+901F-9020,U+903B,U+91C7,U+9488,U+94FE,U+9519,U+95E8,U+9636,U+9650,U+968F,U+96BE,U+9875,U+9884,U+9886,U+9891,U+9996} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/14.woff2") format("woff2");unicode-range:U+4E09,U+4E14,U+4E8C,U+4EA4,U+4EAB,U+4ECA,U+4EE4,U+4EFB,U+4F20,U+4F4D,U+4F9B,U+4FDD,U+50A8,U+50CF,U+5143,U+51B5,U+51C6,U+51E0,U+5217,U+521B,U+534F,U+53E3,U+53F0,U+53F8,U+5404,U+540D,U+5426,U+5458,U+547D,U+54CD,U+54EA,U+56E2,U+573A,U+5757,U+57DF,U+589E,U+5907,U+591F,U+59CB,U+5BA2,U+5BB6,U+5BFC,U+5C11,U+5C55,U+5DF2,U+5E03,U+5E26,U+5E73-5E74,U+5F15,U+5F55,U+600E,U+603B,U+611F,U+624B,U+624D,U+6253,U+627E,U+6301,U+6362,U+63A7-63A8,U+652F,U+6536,U+6539,U+653E,U+65AD,U+65E0,U+660E,U+666F,U+671F,U+6761,U+6790,U+6838-6839,U+683C,U+6848,U+6D88,U+7247,U+72B6,U+7387,U+73AF,U+7531,U+7740,U+7B26,U+7B80,U+7D22,U+7EA7,U+7EDC,U+7EED,U+7EF4,U+81F4,U+83B7,U+89C4,U+89C6,U+8BA4,U+8BAE,U+8BB0,U+8BBA,U+8BC1,U+8BC6,U+8BDD,U+8BF7,U+8BFB,U+8D44,U+8DEF,U+8F6C,U+8F83,U+8F91,U+8F93,U+8FBE,U+952E,U+957F,U+9645,U+9664,U+9A8C} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/13.woff2") format("woff2");unicode-range:U+4E0E,U+4E3B,U+4E49,U+4E60,U+4EA7,U+4EC0,U+4ED6,U+4F18,U+4F55,U+4FE1,U+5019,U+5148,U+5168,U+516C,U+5177,U+518D,U+5199,U+51B3,U+51FD,U+5219,U+522B,U+5236,U+529B,U+529F,U+5305,U+533A,U+539F,U+53BB,U+53C2,U+53CA,U+53D6,U+5411,U+5462,U+54C1,U+56DE,U+57FA,U+5904,U+590D,U+5916,U+5929,U+5B50,U+5B57,U+5B8C,U+5C06,U+5C0F,U+5C42,U+5DF1,U+5E93,U+5FC3,U+6001,U+601D,U+606F,U+60C5,U+60F3,U+610F,U+6216,U+6267,U+6280,U+6307,U+64CD,U+6548,U+6574,U+672F,U+67B6,U+67E5,U+6B21,U+6B63-6B65,U+6BB5,U+6BCF,U+6CE8,U+6D41,U+6D4B,U+6E90,U+7279,U+76F4,U+77E5,U+786E,U+793A,U+7AEF,U+7BA1,U+7EBF,U+7EC4,U+7F16,U+7F51,U+7F6E,U+8003,U+88AB,U+8A00,U+8BA9,U+8BB2,U+8BE5,U+8BED,U+8BFE,U+8C61,U+8D77,U+8FD0,U+9009,U+9053,U+914D,U+961F,U+96C6,U+975E,U+9879,U+9AD8} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/12.woff2") format("woff2");unicode-range:U+4E1A,U+4E24,U+4E4B,U+4E8B,U+4EBA,U+4ECE,U+4F46,U+4F53,U+4F7F,U+4F8B,U+503C,U+505A,U+5165,U+5173,U+5176,U+5185,U+524D,U+52A0,U+52A8,U+5316,U+5355,U+53D8,U+53EA,U+5408,U+5668,U+56E0,U+56FE,U+5730,U+578B,U+597D,U+5B58,U+5B66,U+5B83,U+5BB9,U+5DE5,U+5E38,U+5E76,U+5E8F,U+5E94,U+5EA6,U+5EFA,U+5F00,U+5F53,U+5F88,U+5F97,U+6237,U+628A,U+63D0,U+6587,U+65B0,U+66F4,U+6700,U+670D,U+672C,U+673A,U+6784,U+6807,U+6837,U+6A21,U+6BD4,U+6C42,U+6CA1,U+7136,U+751F,U+76EE,U+76F8,U+79CD,U+7B2C,U+7B49,U+7B97,U+7C7B,U+7CFB,U+7ECF,U+7ED3,U+7ED9,U+7EDF,U+8005,U+81EA,U+8282,U+8868,U+8BA1,U+8BBE,U+8BD5,U+8BF4,U+8C03,U+8FD8,U+90A3,U+90E8,U+91CC-91CD,U+91CF,U+95F4} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/11.woff2") format("woff2");unicode-range:U+4E00,U+4E0A-4E0B,U+4E0D,U+4E2A,U+4E2D,U+4E3A,U+4E48,U+4E5F,U+4E86,U+4E8E,U+4E9B,U+4EE3,U+4EE5,U+4EEC,U+4EF6,U+4F1A,U+4F5C,U+4F60,U+51FA,U+5206,U+5230,U+52A1,U+53D1,U+53EF,U+540C,U+540E,U+548C,U+5728,U+591A,U+5927,U+5982,U+5B9A,U+5B9E,U+5BF9,U+5C31,U+5F0F,U+6027,U+6210-6211,U+6240,U+636E,U+63A5,U+6570,U+65B9,U+65F6,U+662F,U+6709,U+6765,U+679C,U+6CD5,U+70B9,U+73B0,U+7406,U+7528,U+7684,U+770B,U+7801,U+7A0B,U+800C,U+80FD,U+884C,U+8981,U+89E3,U+8FC7,U+8FD9,U+8FDB,U+901A,U+90FD,U+95EE,U+9700,U+9762,U+9898} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/10.woff2") format("woff2");unicode-range:U+2026,U+3001-3002,U+300A-300B,U+FF01,U+FF08-FF09,U+FF0D,U+FF1A-FF1B,U+FF1F,U+FF5C} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/9.woff2") format("woff2");unicode-range:U+409,U+40F,U+418,U+41F,U+427-428,U+42B} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/8.woff2") format("woff2");unicode-range:U+3CD,U+1F45} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/7.woff2") format("woff2");unicode-range:U+374-375,U+37E,U+384-385,U+387,U+389-38A,U+38F-390,U+393-394,U+39E,U+3A3,U+3A6,U+3A8-3A9,U+3AC,U+3B0-3B1,U+3B3,U+3B6,U+3B8,U+3BA,U+3BD-3BE,U+3C0,U+3C2-3C4,U+3C7,U+3CB,U+3D1,U+3D6,U+402-403,U+40A,U+40E,U+411,U+413-414,U+423-424,U+426,U+429,U+42C,U+431,U+433-434,U+436-437,U+43A,U+442,U+446,U+449-44A,U+44C,U+453,U+459-45A,U+45C,U+462-463,U+46A-46B,U+474-475,U+490-491,U+493,U+497,U+49B,U+49D,U+4A2-4A3,U+4B0-4B1,U+4B6-4B7,U+4BA,U+4CA,U+4EE} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/6.woff2") format("woff2");unicode-range:U+259,U+2B0,U+2B2-2B3,U+2B7-2B8,U+2BB-2BC,U+2C6-2C7,U+2C9-2CB,U+2D8-2DD,U+2E1-2E3} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/5.woff2") format("woff2");unicode-range:U+18A,U+192} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/4.woff2") format("woff2");unicode-range:U+135,U+140} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/3.woff2") format("woff2");unicode-range:U+F0,U+F7} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/2.woff2") format("woff2");unicode-range:U+A0-AA,U+AC-B1,U+B4-BF,U+C3,U+D7} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/1.woff2") format("woff2");unicode-range:U+7B-7F} +@font-face{font-family:"MiSans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/regular/0.woff2") format("woff2");unicode-range:U+0-1,U+A,U+D,U+20-7A,U+AB,U+B2-B3,U+C0-C2,U+C4-D6,U+D8-EF,U+F1-F6,U+F8-134,U+136-13F,U+141-17F,U+181,U+18F,U+198-199,U+1A0-1A1,U+1AF-1B0,U+1B3-1B4,U+1B7,U+1CD-1DC,U+1E4-1E9,U+1EE-1F0,U+1F9-1FF,U+218-21B,U+21E-21F,U+237,U+251,U+253,U+257,U+261,U+292,U+386,U+388,U+38C,U+38E,U+391-392,U+395-39D,U+39F-3A1,U+3A4-3A5,U+3A7,U+3AA-3AB,U+3AD-3AF,U+3B2,U+3B4-3B5,U+3B7,U+3B9,U+3BB-3BC,U+3BF,U+3C1,U+3C5-3C6,U+3C8-3CA,U+3CC,U+3CE,U+3D2-3D4,U+400-401,U+404-408,U+40B-40D,U+410,U+412,U+415-417,U+419-41E,U+420-422,U+425,U+42A,U+42D-430,U+432,U+435,U+438-439,U+43B-441,U+443-445,U+447-448,U+44B,U+44D-452,U+454-458,U+45B,U+45D-45F,U+472-473,U+492,U+49A,U+49C,U+4AE-4AF,U+4B2-4B3,U+4B8-4B9,U+4BB,U+4D8-4D9,U+4E2-4E3,U+4E8-4E9,U+4EF,U+1E00-1E01,U+1E3E-1E3F,U+1E80-1E85,U+1E9E,U+1EA0-1EF9,U+2013-2014,U+2018-2019,U+201C-201D,U+2044,U+2070,U+2074-2079,U+2080,U+2082-2089,U+20A8,U+FF0C} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/187.woff2") format("woff2");unicode-range:U+2C361,U+2C364,U+2C488,U+2C494,U+2C497,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+2C8D9,U+2C8DE,U+2C8E1,U+2C8F3,U+2C907,U+2C90A,U+2C91D,U+2CA02,U+2CA0E,U+2CA7D,U+2CAA9,U+2CB29,U+2CB2D-2CB2E,U+2CB31,U+2CB38-2CB39,U+2CB3B,U+2CB3F,U+2CB41,U+2CB4A,U+2CB4E,U+2CB5A-2CB5B,U+2CB64,U+2CB69,U+2CB6C,U+2CB6F,U+2CB73,U+2CB76,U+2CB78,U+2CB7C,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+2CE18,U+2CE1A,U+2CE23,U+2CE26,U+2CE2A,U+2CE7C,U+2CE88,U+2CE93} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/186.woff2") format("woff2");unicode-range:U+9FC6-9FEF,U+F92C,U+F979,U+F995,U+F9E7,U+F9F1,U+FA0C-FA0F,U+FA11,U+FA13-FA14,U+FA18,U+FA1F-FA21,U+FA23-FA24,U+FA27-FA29,U+FE10-FE19,U+FE30-FE31,U+FE33-FE44,U+FE49-FE52,U+FE54-FE57,U+FE59-FE66,U+FE68-FE6B,U+20087,U+20089,U+200CC,U+20164,U+20676,U+20CD0,U+2139A,U+21413,U+215D7,U+2298F,U+235CB,U+23C97-23C98,U+23E23,U+241FE,U+249DB,U+24A7D,U+24AC9,U+25532,U+25562,U+255A8,U+25ED7,U+26221,U+2648D,U+26676,U+2677C,U+26B5C,U+26C21,U+27FF9,U+28408,U+28678,U+28695,U+287E0,U+28B49,U+28C47,U+28C4F,U+28C51,U+28C54,U+28E99,U+29F7E,U+29F83,U+29F8C,U+2A7DD,U+2A8FB,U+2A917,U+2AA30,U+2AA36,U+2AA58,U+2AFA2,U+2B127-2B128,U+2B137-2B138,U+2B1ED,U+2B300,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+2B626-2B628,U+2B62A,U+2B62C,U+2B695-2B696,U+2B6AD,U+2B6ED,U+2B7A9,U+2B7C5,U+2B7E6,U+2B7F9,U+2B7FC,U+2B806,U+2B80A,U+2B81C,U+2B8B8,U+2BAC7,U+2BB5F,U+2BB62,U+2BB7C,U+2BB83,U+2BC1B,U+2BD77,U+2BD87,U+2BDF7,U+2BE29,U+2C029-2C02A,U+2C0A9,U+2C0CA,U+2C1D5,U+2C1D9,U+2C1F9,U+2C27C,U+2C288,U+2C2A4,U+2C317,U+2C35B} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/185.woff2") format("woff2");unicode-range: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-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+9ED2-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-9F16,U+9F18,U+9F1A-9F1F,U+9F21,U+9F23-9F2B,U+9F2D-9F2E,U+9F30-9F33,U+9F35-9F36,U+9F38,U+9F3A,U+9F3C,U+9F3F-9F43,U+9F45-9F49,U+9F4C-9F4D,U+9F53,U+9F55-9F58,U+9F5A-9F5B,U+9F5D-9F5E,U+9F62-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-9FC5} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/184.woff2") format("woff2");unicode-range:U+9CFA-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-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} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/183.woff2") format("woff2");unicode-range:U+9B7A-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-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-9CF9} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/182.woff2") format("woff2");unicode-range:U+99F5-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-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-9B07,U+9B09-9B0E,U+9B10-9B12,U+9B14-9B1E,U+9B20-9B21,U+9B24,U+9B26,U+9B28,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-9B79} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/181.woff2") format("woff2");unicode-range:U+983E-9845,U+9847-984B,U+9850-9852,U+9854-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-98B5,U+98B7-98BB,U+98BD-98C3,U+98C5,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-98F1,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-992D,U+992F-9930,U+9932,U+9934-9936,U+9938-993B,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-99F4} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/180.woff2") format("woff2");unicode-range:U+96C8,U+96CA,U+96D0-96D1,U+96D3-96D4,U+96D7-96D8,U+96DA,U+96DD,U+96DF,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-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-97AA,U+97AC,U+97AE,U+97B0-97B1,U+97B3,U+97B5-97BC,U+97BE-97C2,U+97C4-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-983D} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/179.woff2") format("woff2");unicode-range:U+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+95A4,U+95A6-95A7,U+95AA,U+95AE-95B0,U+95B2-95B5,U+95B7-95B8,U+95BA,U+95C0-95C2,U+95C4-95C7,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-96BB,U+96BF,U+96C2-96C3} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/178.woff2") format("woff2");unicode-range:U+92D8-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-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-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} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/177.woff2") format("woff2");unicode-range:U+913F-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-91E6,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-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-92D7} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/176.woff2") format("woff2");unicode-range: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+8FF4-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-902C,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+905D,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-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-913E} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/175.woff2") format("woff2");unicode-range: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+8E30,U+8E32-8E34,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+8F13,U+8F16-8F1A,U+8F20-8F24,U+8F27-8F28,U+8F2B-8F2E,U+8F30-8F32,U+8F34-8F37,U+8F39-8F3A} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/174.woff2") format("woff2");unicode-range:U+8B7A-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+8C8D-8C92,U+8C95-8C97,U+8C99-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,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} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/173.woff2") format("woff2");unicode-range: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+8A3B-8A3D,U+8A3F-8A40,U+8A42-8A45,U+8A47,U+8A49-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-8A8C,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-8B65,U+8B67-8B6A,U+8B6D-8B6E,U+8B71-8B73,U+8B75-8B76,U+8B78-8B79} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/172.woff2") format("woff2");unicode-range:U+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+88FD,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-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-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-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} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/171.woff2") format("woff2");unicode-range:U+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-883F,U+8841-8843,U+8847-884B,U+884E-8852,U+8855-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-889A} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/170.woff2") format("woff2");unicode-range: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-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+8716,U+8719,U+871B,U+871D,U+871F-8720,U+8724,U+8726-8728,U+872A-872C} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/169.woff2") format("woff2");unicode-range:U+844D-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,U+848D,U+848F-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-84C8,U+84CC,U+84CE-84CF,U+84D2,U+84D4-84D5,U+84D7-84DC,U+84DE,U+84E1-84E2,U+84E4,U+84E7-84EB,U+84ED,U+84F1-84F3,U+84F5-84FB,U+84FE,U+8500-850B,U+850D-8510,U+8512,U+8514-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+8553-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-8593,U+8595-859A,U+859D-859E,U+85A0-85A3,U+85A5,U+85A7,U+85AB-85AD,U+85B1-85B6,U+85B8} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/168.woff2") format("woff2");unicode-range: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-83D3,U+83D5,U+83D7,U+83D9-83DB,U+83DE,U+83E2-83E4,U+83E6-83E8,U+83EB-83EE,U+83F3-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-844C} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/167.woff2") format("woff2");unicode-range:U+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-812A,U+812D-812E,U+8130,U+8133-8135,U+8137,U+813A-813D,U+813F-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+8181,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-81B2,U+81B4-81B9,U+81BC,U+81C4-81C5,U+81C7-81C8,U+81CB,U+81CE,U+81D0-81D7,U+81D9,U+81DB-81DF,U+81E1-81E2,U+81E4,U+81E6,U+81E9,U+81EB,U+81EE-81F2,U+81F5-81F9,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-8278,U+827B-827C,U+8280-8281,U+8283,U+8285-8287} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/166.woff2") format("woff2");unicode-range: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-7FED,U+7FEF,U+7FF2,U+7FF4-7FF8,U+7FFA,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-80D4} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/165.woff2") format("woff2");unicode-range: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-7D92,U+7D94-7D9B,U+7D9D,U+7D9F,U+7DA1,U+7DA4-7DA5,U+7DA7-7DAB,U+7DAF,U+7DB3,U+7DB5-7DB7,U+7DBC,U+7DC0-7DC3,U+7DC5-7DC6,U+7DC8-7DC9,U+7DCC-7DD1,U+7DD3-7DD6,U+7DDB-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-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+7E48-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,U+7E8B,U+7E8D-7E8E,U+7E90-7E92,U+7E94-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} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/164.woff2") format("woff2");unicode-range: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-7C15,U+7C17-7C1D,U+7C20,U+7C22,U+7C24-7C25,U+7C28-7C29,U+7C2C-7C37,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-7C71,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,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-7D2E,U+7D34-7D38,U+7D3B,U+7D3D-7D3E,U+7D41,U+7D43,U+7D45,U+7D47-7D4D} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/163.woff2") format("woff2");unicode-range: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-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} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/162.woff2") format("woff2");unicode-range:U+7918,U+791A-791D,U+791F-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-79A6,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-7A1D,U+7A21-7A22,U+7A24-7A2D,U+7A2F-7A30,U+7A32,U+7A34-7A36,U+7A38,U+7A3A,U+7A3E,U+7A40-7A45,U+7A47-7A4B,U+7A4F-7A50,U+7A52-7A56,U+7A58-7A60,U+7A63-7A68,U+7A6A-7A6C} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/161.woff2") format("woff2");unicode-range:U+77B7-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-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,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-7917} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/160.woff2") format("woff2");unicode-range:U+7641,U+7644-7645,U+7648,U+764A-764B,U+764E-7653,U+7655,U+7657,U+7659-765B,U+765D,U+7660,U+7665-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+76C3-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-7700,U+7702-7703,U+7705-7706,U+770A,U+770C,U+770E-7718,U+771B-771E,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-774F,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-778B,U+778F-7790,U+7793-7797,U+7799-779D,U+77A1,U+77A3-77A4,U+77A6,U+77A8,U+77AB,U+77AD-77AF,U+77B1-77B2,U+77B4,U+77B6} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/159.woff2") format("woff2");unicode-range:U+74B1-74B9,U+74BB-74BC,U+74BE-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-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-75E1,U+75E5,U+75E9,U+75EC-75EF,U+75F2-75F3,U+75F5-75F8,U+75FA-75FB,U+75FD,U+7604,U+7606-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} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/158.woff2") format("woff2");unicode-range:U+7366-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-739E,U+73A0-73A1,U+73A3-73A8,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-744A,U+744C-7454,U+7456,U+7458,U+745D,U+7460-7462,U+7465-7468,U+746B-746C,U+746E-746F,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} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/157.woff2") format("woff2");unicode-range: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+7260,U+7263-7265,U+7268,U+726A-726D,U+7270-7271,U+7273-7274,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-7343,U+7346-734C,U+734F,U+7351,U+7353-7356,U+7358-735F,U+7361-7365} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/156.woff2") format("woff2");unicode-range: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+7146-7148,U+714B,U+714D,U+714F-7151,U+7153-7158,U+715A-715B,U+715D,U+715F-7161,U+7163,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} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/155.woff2") format("woff2");unicode-range: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-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} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/154.woff2") format("woff2");unicode-range:U+6E01-6E04,U+6E06-6E09,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-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-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-6F6C,U+6F71} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/153.woff2") format("woff2");unicode-range:U+6C51-6C53,U+6C56,U+6C58-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-6CDD,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-6D29,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+6DE9,U+6DED,U+6DEF-6DF0,U+6DF2,U+6DF4,U+6DF8,U+6DFD-6E00} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/152.woff2") format("woff2");unicode-range:U+6AE3-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-6BAD,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+6C39-6C3C,U+6C3E-6C3F,U+6C43-6C45,U+6C48,U+6C4B-6C4F} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/151.woff2") format("woff2");unicode-range:U+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-69EC,U+69EE-69F1,U+69F4-69FC,U+69FE,U+6A00,U+6A03-6A04,U+6A06-6A09,U+6A0B-6A12,U+6A14-6A16,U+6A1A-6A1D,U+6A20,U+6A22,U+6A24-6A27,U+6A29,U+6A2B-6A2E,U+6A30,U+6A32-6A34,U+6A36-6A37,U+6A3B-6A3C,U+6A3F-6A43,U+6A45-6A46,U+6A49-6A4A,U+6A4C-6A4F,U+6A51-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-6AC2,U+6AC4-6AD2,U+6AD4-6AD9,U+6ADC,U+6AE0-6AE2} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/150.woff2") format("woff2");unicode-range:U+687F-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+68CA,U+68CC,U+68CE-68D1,U+68D3-68D4,U+68D9,U+68DB-68DE,U+68E1-68E2,U+68E4-68E6,U+68E8-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-69B9} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/149.woff2") format("woff2");unicode-range: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+6736-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-67FC,U+67FE,U+6801-6804,U+6806,U+680D,U+6810,U+6812,U+6814-6815,U+6818-681C,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} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/148.woff2") format("woff2");unicode-range:U+65AE,U+65B1-65B6,U+65B8,U+65BA-65BB,U+65BE-65C0,U+65C2,U+65C7-65CA,U+65CD,U+65D0-65D1,U+65D3-65D5,U+65D8-65DF,U+65E1,U+65E3-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} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/147.woff2") format("woff2");unicode-range:U+6433,U+6435,U+6437-6439,U+643B-643C,U+643E,U+6440,U+6442-6443,U+6449,U+644B-6450,U+6453,U+6455-6457,U+6459-645B,U+645D,U+6460-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+649A-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+64E1,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,U+653A,U+653C-653D,U+6540-6544,U+6546-6547,U+654A-654B,U+654D-654E,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} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/146.woff2") format("woff2");unicode-range: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+62DD-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-6331,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+6368,U+636A,U+636C,U+636F-6370,U+6372-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+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+63F9-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-6425,U+6427-6429,U+642B,U+642E-6432} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/145.woff2") format("woff2");unicode-range:U+6130-6133,U+6135-6136,U+6138-613D,U+6140-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-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,U+6235,U+6238-623C,U+6242,U+6244-6246,U+624A,U+624F-6250,U+6255-6257,U+6259-625A,U+625C-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} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/144.woff2") format("woff2");unicode-range:U+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-612F} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/143.woff2") format("woff2");unicode-range:U+5E21-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+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+5F14,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+5F5E-5F60,U+5F63,U+5F67-5F68,U+5F6B,U+5F6E-5F6F,U+5F72,U+5F74-5F76,U+5F78,U+5F7A,U+5F7D-5F7F,U+5F83,U+5F86,U+5F8D-5F8F,U+5F93-5F94,U+5F96,U+5F9A-5F9B,U+5F9D,U+5F9F,U+5FA2-5FA6} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/142.woff2") format("woff2");unicode-range:U+5CF3,U+5CF5,U+5CF7-5CFA,U+5CFC,U+5CFE-5D01,U+5D04-5D05,U+5D08-5D0C,U+5D0F-5D13,U+5D15,U+5D18-5D1A,U+5D1C-5D1D,U+5D1F-5D21,U+5D23,U+5D25,U+5D28,U+5D2A-5D2B,U+5D2F-5D33,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-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+5DE3-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-5E20} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/141.woff2") format("woff2");unicode-range:U+5B61,U+5B67-5B68,U+5B6D-5B6F,U+5B72,U+5B74,U+5B76-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-5C5B,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-5CE0,U+5CE2-5CE3,U+5CE7,U+5CE9,U+5CEB-5CEC,U+5CEE,U+5CF1-5CF2} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/140.woff2") format("woff2");unicode-range:U+5A4D-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-5B4B,U+5B4D-5B4F,U+5B52,U+5B56,U+5B5E,U+5B60} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/139.woff2") format("woff2");unicode-range:U+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+59B3-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+59E6-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,U+5A4B-5A4C} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/138.woff2") format("woff2");unicode-range:U+57C6-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-5856,U+5859-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} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/137.woff2") format("woff2");unicode-range:U+5677,U+5679-567A,U+567D-567F,U+5681-5684,U+5688-568B,U+568D,U+5690-5692,U+5694,U+5696-56A2,U+56A4-56A5,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-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+5770-5772,U+5774-5775,U+5778-577A,U+577D-5781,U+5787-578A,U+578D-5791,U+5794-579A,U+579C-579F,U+57A5,U+57A8,U+57AA,U+57AC,U+57AF-57B1,U+57B3,U+57B5-57B7,U+57B9-57C1,U+57C4-57C5} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/136.woff2") format("woff2");unicode-range:U+54F9,U+54FB,U+54FE,U+5500,U+5502-5503,U+5505,U+5508,U+550A-550E,U+5512-5513,U+5515-551A,U+551C-551F,U+5521,U+5525-5526,U+5528-5529,U+552B,U+552D,U+5532,U+5534-5536,U+5538-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-5586,U+558C-558E,U+5590,U+5592-5593,U+5595-5597,U+559B,U+559E,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-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} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/135.woff2") format("woff2");unicode-range: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+5379,U+537C-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+5451,U+545A,U+545D-5461,U+5463,U+5465,U+5467,U+5469-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+54E2-54E4,U+54EB-54EC,U+54EF-54F1,U+54F4-54F8} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/134.woff2") format("woff2");unicode-range:U+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+524B,U+524F,U+5252-5253,U+5255,U+5257-525A,U+525F-5260,U+5262-5264,U+5266,U+5268,U+526B-526D,U+5270-5271,U+5273,U+5276-527C,U+527E,U+5280,U+5284-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} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/133.woff2") format("woff2");unicode-range:U+5063-5064,U+5066-506B,U+506D-5073,U+5078-507A,U+507C-507D,U+5081-5084,U+5086-5087,U+5089-508C,U+508E-5090,U+5092-5095,U+5097,U+509A-50A2,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-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+5101,U+5103,U+5105,U+510A,U+510C-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+516A,U+516F,U+5172,U+517A,U+517E-517F,U+5183-5184,U+5186-5187,U+518B,U+518E-5191,U+5193-5194,U+5198,U+519A,U+519D-519F,U+51A1,U+51A3,U+51A6-51A9,U+51AD} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/132.woff2") format("woff2");unicode-range:U+4EFE,U+4F00,U+4F02-4F08,U+4F0B-4F0C,U+4F12-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+4F48-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+4FB7-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-4FEC,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-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-5062} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/131.woff2") format("woff2");unicode-range:U+4D5C-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} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/130.woff2") format("woff2");unicode-range:U+4CA0-4D5B} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/129.woff2") format("woff2");unicode-range:U+4BE5-4C9F} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/128.woff2") format("woff2");unicode-range:U+4B2B-4BE4} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/127.woff2") format("woff2");unicode-range:U+4A72-4B2A} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/126.woff2") format("woff2");unicode-range:U+49BB-4A71} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/125.woff2") format("woff2");unicode-range:U+4905-49BA} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/124.woff2") format("woff2");unicode-range:U+4850-4904} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/123.woff2") format("woff2");unicode-range:U+479D-484F} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/122.woff2") format("woff2");unicode-range:U+46EB-479C} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/121.woff2") format("woff2");unicode-range:U+463B-46EA} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/120.woff2") format("woff2");unicode-range:U+458C-463A} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/119.woff2") format("woff2");unicode-range:U+44DF-458B} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/118.woff2") format("woff2");unicode-range:U+4433-44DE} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/117.woff2") format("woff2");unicode-range:U+4389-4432} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/116.woff2") format("woff2");unicode-range:U+42E0-4388} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/115.woff2") format("woff2");unicode-range:U+4239-42DF} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/114.woff2") format("woff2");unicode-range:U+4193-4238} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/113.woff2") format("woff2");unicode-range:U+40EF-4192} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/112.woff2") format("woff2");unicode-range:U+404D-40EE} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/111.woff2") format("woff2");unicode-range:U+3FAC-404C} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/110.woff2") format("woff2");unicode-range:U+3F0D-3FAB} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/109.woff2") format("woff2");unicode-range:U+3E70-3F0C} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/108.woff2") format("woff2");unicode-range:U+3DD5-3E6F} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/107.woff2") format("woff2");unicode-range:U+3D3C-3DD4} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/106.woff2") format("woff2");unicode-range:U+3CA4-3D3B} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/105.woff2") format("woff2");unicode-range:U+3C0E-3CA3} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/104.woff2") format("woff2");unicode-range:U+3B7A-3C0D} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/103.woff2") format("woff2");unicode-range:U+3AE8-3B79} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/102.woff2") format("woff2");unicode-range:U+3A59-3AE7} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/101.woff2") format("woff2");unicode-range:U+39CC-3A58} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/100.woff2") format("woff2");unicode-range:U+3941-39CB} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/99.woff2") format("woff2");unicode-range:U+38B8-3940} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/98.woff2") format("woff2");unicode-range:U+3832-38B7} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/97.woff2") format("woff2");unicode-range:U+37AE-3831} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/96.woff2") format("woff2");unicode-range:U+372D-37AD} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/95.woff2") format("woff2");unicode-range:U+36AF-372C} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/94.woff2") format("woff2");unicode-range:U+3634-36AE} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/93.woff2") format("woff2");unicode-range:U+35BC-3633} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/92.woff2") format("woff2");unicode-range:U+3547-35BB} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/91.woff2") format("woff2");unicode-range:U+34D5-3546} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/90.woff2") format("woff2");unicode-range:U+3467-34D4} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/89.woff2") format("woff2");unicode-range:U+33CE,U+33D1-33D2,U+33D5,U+3400-3466} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/88.woff2") format("woff2");unicode-range:U+2EB7,U+2EBB,U+2ECA,U+2FF0-2FFB,U+3000,U+3003,U+3005-3009,U+300C-3017,U+301D-301E,U+3021-3029,U+303E,U+3105-3129,U+3220-3229,U+3231,U+32A3,U+338E-338F,U+339C-339E,U+33A1,U+33C4} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/87.woff2") format("woff2");unicode-range:U+255E-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+2713,U+2715,U+275B-275E,U+2780-2793,U+2E81,U+2E84,U+2E88,U+2E8B-2E8C,U+2E97,U+2EA7,U+2EAA,U+2EAE,U+2EB3,U+2EB6} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/86.woff2") format("woff2");unicode-range:U+2496-249B,U+2501,U+2503-251B,U+251D-254B,U+2550-255D} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/85.woff2") format("woff2");unicode-range:U+2199,U+21C4,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+223C-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-2495} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/84.woff2") format("woff2");unicode-range:U+207A-207B,U+207F,U+2081,U+20A3-20A4,U+20A9,U+20AB-20AC,U+20AE,U+20B1,U+20B8-20BA,U+20BD,U+20BF,U+2103,U+2105,U+2109,U+2113-2114,U+2116-2117,U+211E,U+2120-2122,U+2126,U+212E,U+2153-2154,U+215B-215E,U+2160-2179,U+2180-2182,U+2190-2193,U+2196-2198} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/83.woff2") format("woff2");unicode-range:U+2004-2006,U+2009-200A,U+2010,U+2015-2016,U+201A,U+201E,U+2020-2022,U+2025,U+2028-2029,U+2030-2037,U+2039-203C,U+203E,U+2042-2043,U+2047-2049,U+204E,U+2051,U+2055,U+2060-2064,U+2066-2069,U+2071} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/82.woff2") format("woff2");unicode-range:U+300-304,U+306-30C,U+312,U+323,U+326-328,U+1D43,U+1D47-1D49,U+1D4D,U+1D4F-1D50,U+1D52,U+1D56-1D58,U+1D5B,U+1D9C,U+1DA0,U+1DBB} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/81.woff2") format("woff2");unicode-range:U+FF3C-FF5B,U+FF5D,U+FFE0-FFE5} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/80.woff2") format("woff2");unicode-range:U+FF02-FF07,U+FF0A-FF0B,U+FF0E-FF19,U+FF1C-FF1E,U+FF20-FF3B} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/79.woff2") format("woff2");unicode-range:U+E3F} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/78.woff2") format("woff2");unicode-range:U+30EC-30FA,U+30FC-30FE} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/77.woff2") format("woff2");unicode-range:U+308A-3094,U+3099-309E,U+30A1-30EB} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/76.woff2") format("woff2");unicode-range:U+3041-3089} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/75.woff2") format("woff2");unicode-range:U+5244,U+5666,U+568C,U+58DA,U+5C68,U+5E58,U+5F33,U+61CD,U+6214,U+6516,U+69E7,U+6BFF,U+7020,U+7044,U+7798,U+78FD,U+7E6F-7E70,U+802E,U+8079,U+8183,U+8464,U+861E,U+8811,U+8A92,U+8B56,U+8CB0,U+8CD5,U+8CE7,U+8E93,U+8EA6,U+8EE4,U+8F62,U+9233,U+92DD,U+9301,U+9307,U+9346,U+9370,U+93AA,U+93F7,U+940B,U+9412,U+9426,U+958C,U+95AB,U+95BD,U+95BF,U+986C,U+9A2D,U+9A38,U+9B9A,U+9BEB,U+9BF4,U+9C12,U+9C20,U+9C35,U+9C37,U+9C5D,U+9D1D,U+9DA5,U+9F60} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/74.woff2") format("woff2");unicode-range:U+50E8,U+532D,U+53B4,U+54BC,U+5672,U+56C5,U+5852,U+5B24,U+5B4C,U+5D2C,U+5DF9,U+5E6C,U+5F12,U+649F,U+6504,U+68D6,U+69BF,U+6AEB,U+6AF3,U+6E5E,U+736B,U+7380,U+7649,U+79B0,U+7A6D,U+7AB6,U+7C5C,U+7D06,U+7D32,U+7DE6,U+7DF6,U+7DF9,U+7E0B,U+7E36,U+7E52,U+7E62,U+7F8B,U+8156,U+8264,U+826B,U+8526,U+8546,U+8558,U+8577,U+85F6,U+87EF,U+8806,U+89AF,U+89BF,U+8A01,U+8A75,U+8A84,U+8AD7,U+8B7E,U+8B9E,U+8DB2,U+8E60,U+8EFA,U+8F05,U+8F07,U+91D9,U+923D,U+9255,U+929A,U+92B1,U+92DF,U+92E3,U+92E6,U+92F6,U+9306,U+9308,U+9364,U+9369,U+9384,U+93BF,U+93C7,U+9414,U+9428,U+9479,U+95E5,U+9689,U+97BD,U+97DD,U+9859,U+98EB,U+9937,U+993C,U+99D4,U+99D8,U+9A0D,U+9A36,U+9A42,U+9B9E,U+9BAA,U+9BDD,U+9C08,U+9C29,U+9C3E,U+9D1F,U+9D2F,U+9D42,U+9D6F,U+9D93,U+9DE5,U+9E0C,U+9E7A,U+9EF2,U+9F09} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/73.woff2") format("woff2");unicode-range:U+50B4,U+5110,U+51AA,U+528C,U+52F1,U+56B3,U+5969,U+5ABC,U+6192,U+61AE,U+61DF,U+6207,U+6451,U+69AA,U+6A48,U+6AE7,U+6F59,U+6F77,U+6FA9,U+6FFC,U+700B,U+7027,U+7197,U+736A,U+7602,U+761E,U+7667,U+76B8,U+7C69-7C6A,U+7CDD,U+7CF4,U+7CF6,U+7D07,U+7D86,U+7DC4,U+7DD9,U+7E11,U+7E2D,U+7E32,U+7E8A,U+7F86,U+8161,U+81DA,U+81E0,U+8452,U+851E,U+8552,U+8562,U+859F,U+86FA,U+87E3,U+8949,U+89A1,U+89F6,U+8AD1,U+8B6B,U+8B8E,U+8D10,U+8EF9,U+8F0A,U+91C3,U+9201,U+9225,U+92A9,U+92CF,U+9312,U+931F,U+9343,U+9387,U+93F9,U+9420,U+944A,U+9465,U+980A,U+980F,U+9862,U+9931,U+9933,U+994A,U+995C,U+9A4F,U+9B83,U+9B92,U+9BC1,U+9C39,U+9C5F,U+9C67,U+9C6D,U+9C7A,U+9D30,U+9D53,U+9D5C,U+9D87,U+9D98,U+9DBC,U+9DD9-9DDA,U+9DE6,U+9DEF,U+9ECC,U+9EF7,U+9F34,U+9F4E-9F4F,U+9F54,U+9F59} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/72.woff2") format("woff2");unicode-range:U+50C9,U+5274,U+5635,U+5638,U+56A6,U+5B00,U+5B19,U+5B2A,U+5D81,U+5DF0,U+5E43,U+5EE1,U+6128,U+616A,U+6227,U+651B,U+6898,U+6ADE,U+6AE8,U+6C0C,U+7026,U+733B,U+74A6,U+74D4,U+766D,U+7725,U+7868,U+78B8,U+7BCB,U+7CF2,U+7CF9,U+7D83,U+7D88,U+7DB0,U+7DC7,U+7DE1,U+7DF1,U+84EF,U+84F4,U+84FD,U+863A,U+8810,U+889E,U+8933,U+895D,U+8A4E,U+8A58,U+8A7F,U+8A86,U+8A91,U+8AC2,U+8ACF,U+8ADE,U+8AE1,U+8AFC,U+8B92,U+8B96,U+8CB2,U+8CFE,U+8E92,U+8EDB,U+8F1C,U+8F61,U+90DF,U+9227,U+9230,U+9248,U+927F,U+9283,U+92A0,U+92A3,U+92A6,U+92AA,U+92F1,U+9338,U+9340,U+93CD,U+93DD,U+9413,U+943F,U+944C,U+9454,U+946D,U+95BC,U+95C8,U+979D,U+97C9,U+9807,U+9826,U+98AE,U+98BC,U+98E0,U+9A05,U+9AD5,U+9B4E,U+9BC0,U+9BC7,U+9BD7,U+9BE1,U+9BEA,U+9C23,U+9C28,U+9C33,U+9D52,U+9DF3,U+9F5C} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/71.woff2") format("woff2");unicode-range:U+5000,U+5114-5115,U+526E,U+5399,U+53C4,U+55F6,U+5665,U+566F,U+581D,U+584F,U+58E2,U+6173,U+61E8,U+645C,U+6476,U+6894,U+6A89,U+6ADD,U+6AF8,U+6B0F,U+6B5F,U+6B7F,U+6FAE,U+7341,U+7658,U+7670,U+7906,U+7C00,U+7C1E,U+7D3C,U+7D4E,U+7D5D,U+7D9E,U+7DA3,U+7E35,U+7E7E,U+7E98,U+802C,U+8407,U+8494,U+8541,U+8555,U+87BB,U+8A6C,U+8A9A,U+8AE2,U+8B05,U+8B59,U+8B9C,U+8CBA,U+8CFB,U+8D04,U+8E55,U+8E91,U+8F1E,U+8F54,U+9134,U+9136,U+91D3-91D5,U+91E4,U+91F7,U+9204,U+9266,U+926D,U+92E8,U+92EE,U+931B,U+93A6,U+93A9,U+93C3,U+93CC,U+93E8,U+9403,U+9419,U+9582,U+95C3,U+95CB,U+95D0,U+984E,U+9853,U+9873,U+9908,U+99D1,U+99D9,U+9A43-9A44,U+9B29,U+9B2E,U+9B74,U+9B90,U+9BD6,U+9BFF,U+9C13,U+9C45,U+9D07,U+9DC0,U+9DC2,U+9DF8,U+9EFD,U+9EFF,U+9F76} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/70.woff2") format("woff2");unicode-range:U+4FC1,U+5096,U+5108,U+565D,U+56B6,U+56C0-56C1,U+56C8,U+5816,U+58D9,U+5B21,U+5D0D,U+5EE9,U+614D,U+64FC,U+6A05,U+6A81,U+6AA3,U+6AEC,U+6B1E,U+6BA4,U+6BAE,U+6DE5,U+6F35,U+6F7F,U+7032,U+7162,U+716C,U+743A,U+74DA,U+7646,U+7669,U+7864,U+78E7,U+7A61,U+7B67,U+7BF3,U+7D08,U+7D3A,U+7D3F,U+7D5B,U+7DB9,U+7E0A,U+7E1E-7E1F,U+7FB6,U+8435,U+85CE,U+85EA,U+861A,U+8784,U+87F6,U+8A0C,U+8A10,U+8AB6,U+8ADB,U+8AE4,U+8B04,U+8B16,U+8B95,U+8CB3,U+8E95,U+8ED4,U+8EEB,U+8F26,U+8F33,U+8F64,U+9015,U+9090,U+91C5,U+91E7,U+9200,U+9239,U+923F,U+927A,U+92AB,U+9319,U+9477,U+958E,U+95B6,U+95B9,U+9768,U+97D9,U+980E,U+9874,U+98ED,U+9949,U+994C,U+99E2,U+9ACF,U+9B58,U+9B8E,U+9BAB,U+9BE2,U+9C09,U+9C48,U+9CE7,U+9DBB,U+9E15,U+9E1D,U+9F66,U+9F6A,U+9F77} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/69.woff2") format("woff2");unicode-range:U+50AF,U+513B,U+528A,U+561C,U+5707,U+584B,U+58CE,U+5950,U+5AA7,U+5B0B,U+5CF4,U+5DA0,U+5EC4,U+613E,U+6229,U+64A3,U+64B3,U+64C4,U+66D6,U+690F,U+69E8,U+6A9C,U+6AD3,U+6AF1,U+6B9E,U+6C2C,U+6C33,U+7028,U+7043,U+7069,U+7296,U+7319,U+74A3,U+75FE,U+7664,U+76B0,U+77BC,U+78E3,U+7D02,U+7D13,U+7D15,U+7DBE,U+7E88,U+81CF,U+82BB,U+858C,U+8604,U+875F,U+87C8,U+8960,U+8998,U+89B2,U+89F4,U+8A15,U+8A41,U+8AA5,U+8B2B,U+8CDA,U+8E89,U+8E9A,U+8EA1,U+8EA5,U+8F42,U+9106,U+91F9,U+9210-9211,U+922E,U+9238,U+924D,U+925E,U+9278,U+92BC,U+92C3,U+9358,U+93A7,U+93B0,U+93B5,U+93D1,U+97C3,U+9865,U+993F,U+9948,U+9962,U+9A16,U+9A41,U+9BAD,U+9BDB,U+9BE4,U+9BE7,U+9C25,U+9C56,U+9C78,U+9CF6,U+9D06,U+9D9A,U+9DD3,U+9E1B,U+9F5F,U+9F67,U+9F6C} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/68.woff2") format("woff2");unicode-range:U+50C2,U+5137,U+57E1,U+58E0,U+5AD7,U+5B08,U+5DA7,U+5DCB,U+60F2,U+60FB,U+615F,U+616B,U+61CC,U+61E3,U+636B,U+64EF,U+689F,U+6ADF,U+6AEA,U+6F1A,U+71DC,U+71F4,U+7370,U+763B,U+7931,U+7A4C,U+7C23,U+7D31,U+7DCB,U+7DD7-7DD8,U+7DF2,U+7E39,U+7FA5,U+83A2,U+83A7,U+866F,U+8706,U+87C4,U+87CE,U+8814,U+8831,U+8938,U+893B,U+8964,U+89A6,U+89AC,U+8A03,U+8A16,U+8A46,U+8A52,U+8A5B,U+8A85,U+8ACD,U+8AED,U+8B14,U+8B3E,U+8B4E,U+8E4C,U+8EAA,U+8EF2,U+8EFB,U+9148,U+91F5,U+9245,U+9315,U+9347,U+93DC,U+9410,U+9427,U+9463,U+9481,U+95AC,U+95DE,U+9658,U+973D,U+9744,U+97DE,U+981C,U+9821,U+9837,U+9870,U+98B6,U+98F4,U+9909,U+9911,U+9A0F,U+9A2E,U+9A64,U+9BF0,U+9C0D,U+9C2D,U+9C31,U+9C52,U+9CE9,U+9D23,U+9D6A,U+9DF2,U+9EA9,U+9F95} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/67.woff2") format("woff2");unicode-range:U+513A,U+5504,U+55C7,U+560D,U+5660,U+5680,U+5695,U+56A8,U+580A,U+597C,U+5B7F,U+6134,U+61AB,U+61FA,U+64BB,U+6506,U+6523,U+66B1,U+66C7,U+6D79,U+6DF6,U+6FA0,U+7005,U+71FC,U+7258,U+72FD,U+75D9,U+765F,U+78AD,U+79C8,U+7A1F,U+7C2B,U+7C5F,U+7C6E,U+7CC9,U+7D40,U+7DAC,U+7E09,U+7E10,U+8075,U+8594,U+85BA,U+8768,U+87A2,U+8823,U+89B7,U+8A1B,U+8A36,U+8A54,U+8A61,U+8A70,U+8AEB,U+8AF6,U+8CC5,U+8CE1,U+8D17,U+8EFE,U+91D2,U+91E9,U+9249,U+9262,U+92BB,U+92C7,U+92E5,U+92EF-92F0,U+9341,U+9354,U+9376,U+937A,U+9398,U+93A3,U+93E4,U+93F5,U+943A,U+9460,U+947E,U+95A1,U+95AD,U+95D4,U+9742,U+975A,U+9766,U+98AF,U+98C6,U+98C8,U+98E9,U+991B,U+991E,U+9A4D,U+9AD6,U+9B81,U+9BCA,U+9BFD,U+9D61,U+9D89,U+9E1A,U+9F72} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/66.woff2") format("woff2");unicode-range:U+4F47,U+5102,U+5157,U+55E9,U+562E,U+5630,U+5879,U+5ACB,U+5AF5,U+5B38,U+5C5C,U+5D97,U+5DB8,U+5EDD,U+60B5,U+611C,U+619A,U+6384,U+64FB,U+66C4,U+6A62,U+6ADA,U+6B12,U+6BAB,U+6ECE,U+6F6F,U+705D,U+70F4,U+71C9,U+71FE,U+729B,U+72A2,U+7489,U+750C,U+7515,U+766C,U+769A,U+792B-792C,U+7B8B,U+7C0D,U+7D73,U+7E1D,U+7E6E,U+7F4C,U+81BE,U+8477,U+849E,U+84C0,U+854E,U+85FA,U+8622,U+865C,U+86FB,U+8778,U+8956,U+8A6D,U+8B01,U+8B10,U+8B28,U+8CC1,U+8D05,U+8E7A,U+8F45-8F46,U+8FAE,U+913A,U+91C1,U+91FA,U+9208,U+920D,U+9251,U+925A,U+926C,U+9291,U+92A5,U+92CC,U+942E,U+9432,U+9586,U+958F,U+95CC,U+95D5,U+9830,U+9839,U+9957,U+995E,U+99DF,U+99ED,U+9A2B,U+9A3E,U+9AAF,U+9C54,U+9D1B,U+9D26,U+9D60,U+9DA9} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/65.woff2") format("woff2");unicode-range:U+4E99,U+50E5,U+513C,U+55C6,U+5616,U+5653,U+56EA,U+59CD,U+5D22,U+5D87,U+6137,U+615A,U+645F,U+6473,U+64F7,U+6519,U+6595,U+67F5,U+6AB3,U+6AB8,U+6ABB,U+6EF7-6EF8,U+6F41,U+6F97,U+6FD8,U+6FFA,U+7377,U+737A,U+737C,U+744B,U+74BD,U+74CF,U+7627,U+792A,U+79B1,U+7D46,U+7E43,U+7E45,U+7E93,U+7F88,U+811B,U+81BF,U+81CD,U+8396,U+858A,U+880D,U+8A25,U+8A6B,U+8A6E,U+8A7C,U+8AB9,U+8AC9,U+8AFA,U+8B17,U+8B2C,U+8B4F,U+8CD2,U+8D16,U+8E63,U+8E8A,U+8EFC,U+8F25,U+9114,U+9116,U+9183,U+9226,U+9237,U+923E,U+927B,U+9296,U+92AC,U+9310,U+932E,U+93A2,U+93D7-93D8,U+93DE,U+93E2,U+990C,U+9912,U+993E,U+9943,U+99B1,U+9A40,U+9A5B,U+9A6A,U+9B22,U+9B77,U+9BC9,U+9C58,U+9D15,U+9D51,U+9DFA,U+9E1E} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/64.woff2") format("woff2");unicode-range:U+55B2,U+55DA,U+58AE,U+5A6D,U+5AFA,U+5BE2,U+5DD2,U+5DD4,U+5FA0,U+60BD,U+618A,U+633E,U+64F0,U+6582-6583,U+6727,U+69F3,U+6ADB,U+6AE5,U+6C08,U+6D87,U+6E19,U+6E26,U+6F2C,U+7006,U+7018,U+71BE,U+743F,U+760D,U+7647,U+7662,U+7672,U+76DE,U+798E,U+79BF,U+7ABA,U+7BE4,U+7C6C,U+7D1C,U+7D33,U+7D79,U+7DBB,U+7DBD,U+7DDE,U+7E08,U+7E55,U+82E7,U+85F9,U+87EC,U+8805,U+8A63,U+8AA3,U+8AA8,U+8AC4,U+8ADC,U+8AE6,U+8AF3,U+8B33,U+8CD1,U+8CE4,U+8D0D,U+8F12,U+8F3E,U+923A,U+9257,U+9293,U+92A8,U+92E4,U+9333,U+9365,U+936C,U+93AC,U+93B3,U+942B,U+9433,U+9594,U+95A8,U+9695,U+96CB,U+9824,U+98EA,U+9903,U+9921,U+99B4,U+9A01,U+9A62,U+9A65,U+9BE8,U+9C32,U+9C77,U+9D72,U+9DAF,U+9DD7} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/63.woff2") format("woff2");unicode-range:U+4FB6,U+51DC,U+52FB,U+5331,U+5436,U+5614,U+562F,U+56C2,U+5862,U+5A41,U+5B0C,U+5C37,U+5E57,U+61FE,U+63C0,U+6417,U+64BF,U+64F2,U+6522,U+652A,U+65AC,U+66E0,U+6968,U+6A3A,U+6AFB,U+6B16,U+6BAF,U+6BB2,U+6D36,U+6DEA,U+6F23,U+6F80,U+6FC1,U+7009,U+7064,U+7152,U+71C1,U+71ED,U+7463,U+7469,U+7621,U+7681,U+76BA,U+77EF,U+786F,U+7A08,U+7AC4-7AC5,U+7AC7,U+7D09,U+7D5E,U+7D62,U+7DBA,U+7E5A,U+7E7D,U+7FF9,U+8123,U+81A9,U+8466,U+879E,U+883B,U+896A,U+8A1D,U+8AA6,U+8AF7,U+8B0A,U+8CBD,U+8CCA,U+8CDC,U+8F1F,U+8F4D,U+9087,U+9209,U+92F8,U+9328,U+9382,U+93DF,U+9438,U+97DC,U+9871,U+991A,U+9945,U+99AD,U+99D2,U+99FF,U+9A4A,U+9BD4,U+9C3B,U+9C57,U+9D09,U+9F9C} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/62.woff2") format("woff2");unicode-range:U+4FE0,U+50AD,U+53AD,U+555E,U+589C,U+58B3,U+5DBC,U+6065,U+6190,U+61F6,U+61FC,U+6493,U+64BE,U+64F1,U+650F,U+6688-6689,U+66EC,U+68F2,U+6BC6,U+6C2B,U+6FF0,U+701D,U+7192,U+71D9,U+71FB,U+720D,U+7587,U+760B,U+7613,U+7661,U+766E,U+775C,U+779E,U+78EF,U+79AA,U+7A62,U+7AAA,U+7B8F,U+7C3E,U+7CDE,U+7D8F,U+7DDD,U+7DEF,U+7E37,U+7E46,U+7E6D,U+7E79,U+8073,U+807E,U+8588,U+863F,U+87FB,U+881F,U+8993,U+8A23,U+8A60,U+8AA1,U+8AF1,U+8B20,U+8B41,U+8B74,U+8C4E,U+8C9E,U+8EC0,U+9132,U+919E,U+91D7-91D8,U+9223,U+9240,U+92C5,U+9318,U+931A,U+9320,U+934D,U+93FD,U+9472,U+947F,U+95A5,U+96B4,U+96DB,U+97CC,U+985B,U+986B,U+99DD,U+99F1,U+9C49,U+9D3F} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/61.woff2") format("woff2");unicode-range:U+4F96,U+5098,U+50D5,U+524E,U+5687,U+56D1,U+582F,U+588A,U+58D8,U+58FA,U+5BF5,U+5C4D,U+5EC2,U+5EDA,U+5EEC,U+60B6,U+60F1,U+61C7,U+646F,U+647B,U+64CB,U+665D,U+6953,U+69D3,U+6A01,U+6DF5,U+6E4A,U+6ECC,U+6F86-6F87,U+7015,U+701F,U+703E,U+72F9,U+7378,U+7464,U+7671,U+797F,U+798D,U+7AC8,U+7B4D,U+7DB8,U+7E1B,U+7E8F,U+7F75,U+7FA8,U+814E,U+81D8,U+8259,U+834A,U+8525,U+852D,U+856A,U+8606,U+8766,U+8AD2,U+8AE7,U+8B0E,U+8C48,U+8CAF,U+8CB6,U+8CC2,U+8D13,U+8E8B,U+8ED2,U+919C,U+91AC,U+921E,U+925B,U+938A,U+947C,U+9598,U+95A9,U+95BB,U+96B8,U+980C,U+98E2,U+9913,U+9952,U+99C1,U+9A37,U+9B91,U+9D5D,U+9DF9,U+9EF4,U+9F4B,U+9F52} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/60.woff2") format("woff2");unicode-range:U+5006,U+5109,U+5147,U+525D,U+52F3,U+58DF,U+599D,U+5B30,U+5CA1,U+5D84,U+5E25,U+5E5F,U+5EC1,U+5F4E,U+5F65,U+6085,U+6158,U+61A4,U+6200,U+6488,U+652C,U+68D7,U+68DF,U+6A1E,U+6BBC,U+6E3E,U+6EC4,U+6EF2,U+6F3F,U+6F51,U+6FB1,U+71E6,U+7344-7345,U+7375,U+775E,U+7AAF,U+7ACA,U+7C60,U+7CB5,U+7D0B,U+7D17,U+7DBF,U+7DEC,U+7E61,U+7E69,U+7F77,U+8070,U+8076,U+8178,U+819A,U+81E5,U+8332,U+84BC,U+860A,U+8755,U+8836,U+8932,U+896F,U+8A50,U+8A87,U+8B19,U+8B39,U+8B5A,U+8C93,U+8CED,U+8D1B,U+8ECB,U+91D0,U+91E3,U+9214,U+9298,U+95E1,U+9727,U+97CB,U+97FB,U+9811,U+9905,U+9A0E,U+9A55,U+9B31,U+9DB4,U+9E79,U+9E7C,U+9F94} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/59.woff2") format("woff2");unicode-range:U+5075,U+50F1,U+51CD,U+51F1,U+528D,U+52F8,U+53E2,U+5442,U+559A,U+55AA,U+5606,U+5674,U+5857,U+5875,U+58BE,U+58E9,U+5C62,U+5D50,U+5DBD,U+5EDF,U+5F4C,U+6046,U+61F8,U+6399,U+64AB,U+64B2,U+651C,U+6558,U+6DBC,U+6E6F,U+6EBC,U+6EEC,U+6FEB,U+7051,U+7165,U+721B,U+723A,U+72A7,U+7336,U+746A,U+74CA,U+77DA,U+78A9,U+78DA,U+7AA9,U+7D68,U+7DA2,U+7DE0,U+7E2B,U+7E9C,U+8105,U+8108,U+816B,U+8266,U+84EE,U+856D,U+860B,U+8A1F,U+8A98,U+8C54,U+8CC3-8CC4,U+8F3B,U+8F5F,U+8FAF,U+9059,U+91C0,U+9234,U+9264,U+929C,U+92B3,U+92C1,U+935B,U+9444,U+9583,U+95A3,U+9803,U+98C4,U+99B3,U+9AD2,U+9D28,U+9D3B,U+9F90} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/58.woff2") format("woff2");unicode-range:U+514C,U+53B2,U+58C7,U+58FD,U+5A1B,U+5ABD,U+5DBA,U+5DD6,U+61F2,U+6416,U+64E0,U+6575,U+69CD,U+6A38,U+6AC3,U+6B3D,U+6BC0,U+6DDA,U+6EC5,U+6EEF,U+6FC3,U+6FE4,U+6FF1,U+7058,U+7210,U+7246,U+727D,U+76DC,U+7919,U+7A4E,U+7C43,U+7C72,U+7D10,U+7DB1,U+7DE3,U+7E31,U+7E96,U+8056,U+8139,U+81A0,U+81C9,U+840A,U+8523,U+8569,U+8972,U+8A3A,U+8ABC,U+8AEE,U+8CA9-8CAA,U+8CC8,U+8CDE,U+8CE2,U+8CFA,U+8D08,U+8D0F,U+8F29,U+8F3F,U+8F44,U+8F4E,U+8FAD,U+9112,U+9285,U+92D2,U+934B,U+947D,U+95D6,U+95E2,U+9670,U+978F,U+9817,U+9846,U+98FC-98FD,U+99AE,U+99D5,U+99DB,U+9A19,U+9B27,U+9CE5,U+9CF4} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/57.woff2") format("woff2");unicode-range:U+502B,U+5091,U+50D1,U+5100,U+511F,U+5291,U+52C1,U+55AC,U+58EF,U+5922,U+596A,U+5B6B,U+5BAE,U+5CFD,U+5E33,U+5EC8,U+5EE2,U+6182,U+6191,U+61B2,U+61F7,U+64A5,U+64FA,U+6524,U+66C9,U+6B98,U+6DE8,U+6E67,U+6F01,U+6F32,U+6F64,U+70CF,U+71D2,U+7240,U+76E7,U+7955,U+79AE,U+7AAE,U+7CFE,U+7D21,U+7E73,U+8085,U+81BD,U+838A,U+85A9,U+8AA0,U+8AFE,U+8B00,U+8C6C,U+8CC0,U+8CD3,U+8CE0,U+8D95,U+8D99,U+8DE1,U+8ECC,U+8F1B,U+8F1D,U+907C,U+9081,U+912D,U+92EA,U+9326,U+932B,U+95CA,U+965D,U+96DE,U+9812,U+98F2,U+99D0,U+9B5A,U+9B6F,U+9CF3,U+9D6C,U+9E7D,U+9E97,U+9EA5,U+9F61} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/56.woff2") format("woff2");unicode-range:U+5049,U+50B5,U+5287,U+5289,U+52DE,U+5433,U+5678,U+5712,U+5967,U+596E,U+5A66,U+5BE7,U+5C46,U+5CF6,U+5EF3,U+6176,U+63DA,U+6514,U+694A,U+69AE,U+6A13,U+6A4B,U+6B50,U+6B72,U+6DFA,U+6EAB,U+6F22,U+6FA4,U+7063,U+707D,U+7159,U+755D,U+7642,U+7926,U+7A05,U+7BC9,U+7BE9,U+7C4C,U+7CE7,U+7D00,U+7D19,U+7D1B,U+7D72,U+7DA0,U+7E23,U+7F70,U+8271,U+85E5,U+8607,U+862D,U+8667,U+885B,U+8B7D,U+8CA7,U+8CAB,U+8CB8,U+8CBF,U+8FB2,U+9055,U+9109,U+9127,U+91AB,U+92FC,U+93AE,U+9435,U+9673,U+9678,U+967D,U+97D3,U+98EF,U+9928,U+9A5A,U+9B25,U+9EE8,U+9F8D} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/55.woff2") format("woff2");unicode-range:U+4E9E,U+50B7,U+50BE,U+50DE,U+5104,U+52DD,U+5713,U+593E,U+5CEF,U+5D17,U+5FB9,U+60E1,U+62CB,U+6436,U+6490,U+64FE,U+6649,U+66A2,U+6A6B,U+6F54,U+6F5B,U+6F70,U+6FDF,U+7149,U+7169,U+71C8,U+734E,U+737B,U+758A,U+7D0D,U+7D9C,U+7DB4,U+7DD2,U+7F85,U+805E,U+812B,U+83EF,U+8449,U+85CD,U+85DD,U+89AA,U+8A0A,U+8A17,U+8A69,U+8A95,U+8AF8,U+8B5C,U+8CA1-8CA2,U+8CB4,U+8CBC,U+8CFD,U+8D0A,U+8E8D,U+8ECD,U+8EF8,U+8F14,U+905C,U+90F5,U+9130,U+9215,U+9280,U+9451,U+9592,U+95BE,U+9813,U+98DB,U+98FE,U+9A30,U+9BAE,U+9EC3,U+9F4A} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/54.woff2") format("woff2");unicode-range:U+4E1F,U+4E82,U+5009,U+5074,U+52F5,U+55AB,U+5805,U+58DE,U+5BE9,U+5BEC,U+5BF6,U+5C0B,U+5E40,U+5E63,U+5F48,U+611B,U+6163,U+61B6,U+6383,U+639B,U+63EE,U+640D,U+64C1,U+64D4,U+651D,U+66AB,U+68C4,U+6BBA,U+6C23,U+6EFE,U+6F38,U+6FFE,U+723E,U+7B46,U+7D05,U+7D14,U+7D81,U+7DCA,U+7E3E,U+7E5E,U+7E6A,U+820A,U+84CB,U+87F2,U+8846,U+8AB0,U+8B02,U+8B1D,U+8C50,U+8C9D,U+8CA8,U+8CE3,U+8CE6,U+8CEC,U+8DA8,U+8E64,U+8F2A,U+9072,U+9077,U+907A,U+93E1,U+9470,U+9663,U+96B1,U+9838,U+984F,U+9858,U+990A,U+994B} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/53.woff2") format("woff2");unicode-range:U+4F54,U+5152,U+518A,U+522A,U+532F,U+537B,U+5617,U+56B4,U+5E79,U+5EE0,U+5F91,U+6232,U+6557,U+6771,U+6975,U+6A02,U+6B04,U+6E9D,U+700F,U+71B1,U+71DF,U+722D,U+7562,U+7A4D,U+7A69,U+7AF6,U+7C64,U+7D04,U+7D55,U+7E2E,U+7E54,U+8166,U+8173,U+81E8,U+8208,U+842C,U+885D,U+88DC,U+89BD,U+89F8,U+8A02,U+8A34,U+8A73,U+8AC7,U+8CB7,U+8CFC,U+8ECA,U+8F15,U+904A,U+905E,U+92B7,U+9322,U+937E,U+9589,U+95B1,U+96AA,U+96D9,U+9748,U+975C,U+9802,U+984D,U+9867,U+9918,U+99AC,U+9A45,U+9A5F} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/52.woff2") format("woff2");unicode-range:U+50C5,U+525B,U+5283,U+52E2,U+55CE,U+570B,U+570D,U+5831,U+58D3,U+5EE3,U+5F35,U+616E,U+6230,U+64CA,U+64EC,U+64F4,U+66F8,U+68E7,U+6A39,U+6A94,U+6AA2,U+6B77-6B78,U+6E1B,U+6EFF,U+7368,U+756B,U+76E1,U+76E3-76E4,U+7A31,U+7BC4,U+7DF4,U+7E7C,U+7FA3,U+8072,U+8077,U+807D,U+8209,U+85A6,U+865B,U+8A0E,U+8A13,U+8A31,U+8A55,U+8AA4,U+8B77,U+8CAC,U+8CF4,U+8E10,U+8FA6,U+9060,U+908A,U+91CB,U+91DD,U+9396,U+96D6,U+96F2,U+96FB,U+9806,U+9808,U+98A8} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/51.woff2") format("woff2");unicode-range:U+50F9,U+5354,U+5553,U+5C08,U+5C6C,U+5E2B,U+5E6B,U+5F37,U+63A1,U+64C7,U+6B0A,U+6B61,U+72C0,U+7570,U+790E,U+7D30,U+7D39,U+7D42,U+7D61,U+7DE9,U+806F,U+865F,U+88DD,U+898B,U+89BA,U+89C0,U+8A2A,U+8A5E,U+8A62,U+8B6F-8B70,U+8CA0,U+8CBB,U+8CEA,U+8EDF,U+8F09,U+8F2F,U+9023,U+9054,U+9069,U+908F,U+932F,U+9375,U+93C8,U+9580,U+968E,U+969B,U+96A8,U+96DC,U+96E2-96E3,U+97FF,U+9801,U+9810,U+9818,U+982D,U+983B,U+986F} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/50.woff2") format("woff2");unicode-range:U+5099,U+50B3,U+512A,U+5132,U+5247,U+5275,U+5340,U+54E1,U+5718,U+5834,U+584A,U+5920,U+5C0E,U+5E36,U+5E7E,U+63DB,U+65B7,U+689D,U+6CC1,U+6E96,U+7121,U+7372,U+74B0,U+78BA,U+7C21,U+7D1A,U+7DAD,U+7E3D,U+7E8C,U+7FA9,U+81FA,U+898F,U+8996,U+8A18,U+8A71-8A72,U+8A8D,U+8AB2,U+8ACB,U+8AD6,U+8B49,U+8B58,U+8B80,U+8CC7,U+8F03,U+8F38,U+8F49,U+9078,U+9304,U+9577,U+968A,U+9A57} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/49.woff2") format("woff2");unicode-range:U+4E26,U+5169,U+5225,U+52D5,U+53C3,U+55AE,U+5716,U+57F7,U+5B78,U+5BEB,U+5C07,U+5C64,U+5EAB,U+5F9E,U+5FA9,U+614B,U+6236,U+69CB,U+6A19,U+6A5F,U+6C7A,U+6C92,U+6E2C,U+7522,U+7576,U+7BC0,U+7D44,U+7D66,U+7D93,U+7DB2,U+7DDA,U+7DE8,U+7FD2,U+8207,U+8655,U+8853,U+8A2D,U+8A66,U+8A9E,U+8ABF,U+8B1B,U+8B8A,U+8B93,U+904B,U+9805,U+9AD4} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/48.woff2") format("woff2");unicode-range:U+4F86,U+500B,U+5011,U+5167,U+52D9,U+554F,U+5BE6,U+5C0D,U+61C9,U+64DA,U+6578,U+6642,U+6703,U+696D,U+6A23,U+7232,U+73FE,U+767C,U+78BC,U+7A2E,U+7D50,U+7D71,U+88CF,U+8A08,U+8AAA,U+9019,U+9032,U+904E,U+9084,U+958B,U+9593,U+95DC,U+984C,U+985E,U+9EBC,U+9EDE} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/47.woff2") format("woff2");unicode-range:U+4E2C,U+4E3F,U+5107,U+5155,U+5216,U+5281,U+5293,U+53B6,U+54F3,U+5523,U+553C,U+570A,U+5776,U+5819,U+5ADC,U+5B32,U+5B65,U+5C88,U+5C8D,U+5E3B,U+5F61,U+5FEE,U+6067,U+614A,U+615D,U+62F6,U+63AD,U+63BE,U+6420,U+6434,U+6484,U+6499,U+652E,U+67D9,U+67DD,U+6920,U+6A8E,U+6AA0,U+6B43,U+6BF5,U+6C15,U+6D5E,U+6EE0,U+6F46,U+717A,U+71E0,U+72C1,U+72C3,U+72E8,U+730A,U+73E7,U+746D,U+752F,U+754E,U+75B0,U+7603,U+7722,U+778D,U+77E7,U+7809,U+7811,U+7857,U+78C9,U+78F2,U+7946,U+7967,U+799A,U+7B45,U+7B9D,U+7BA2,U+7CD7,U+7F32,U+8014,U+8028,U+804D,U+8080,U+80C2,U+80D9,U+80EC,U+810E,U+8159,U+817D,U+81A3,U+81AA,U+8201,U+833C,U+836E,U+83E5,U+8459,U+848E,U+84CD,U+84D0,U+84F0,U+85B7,U+871E,U+8723,U+8729,U+8753,U+877E,U+878B,U+8793,U+87D3,U+88D2,U+8966,U+89DC,U+89EB,U+8B26,U+8BF6,U+8C2E,U+8D33,U+8D47,U+8D55,U+8E2C,U+8E9C,U+8E9E,U+8ECE,U+8FEE,U+9139,U+914F,U+9174,U+9191,U+928E,U+94F4,U+952B,U+953C,U+9564,U+960C,U+9622,U+9674,U+98A5,U+9A98,U+9A9F,U+9B48,U+9CA6,U+9CB0,U+9CBA,U+9CCB,U+9E32,U+9E88,U+9EB4,U+9F3D,U+9F44,U+9F86} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/46.woff2") format("woff2");unicode-range:U+4E0C,U+4E28,U+4E36,U+4EA0,U+4EC2,U+4F91,U+4F94,U+4FC5,U+502E,U+50BA,U+5181,U+51F5,U+522D,U+5290,U+52F9,U+5369,U+53A3,U+5423,U+54D5,U+54DC,U+54FF,U+552A,U+5588,U+55B5,U+55C9,U+5686,U+5786,U+57A4,U+57F4,U+580B,U+5820,U+5865,U+58BC,U+5902,U+5AB5,U+5BE4,U+5C1C,U+5C22,U+5C66,U+5C6E,U+5D3E,U+5DDB,U+5E80,U+5EE8,U+5F2A,U+5F50,U+5FED,U+609B,U+619D,U+61D4,U+620B,U+6224-6225,U+6343,U+63CE,U+63F2,U+640B-640C,U+6445,U+6496,U+64D0,U+64D7,U+6509,U+6534,U+6549,U+6603,U+674C,U+67A8,U+6833,U+6844,U+684A,U+6864,U+6957,U+6971,U+6A65,U+6A91,U+6BEA,U+6C5C,U+6CD0,U+6D48,U+6E2B,U+6EB2,U+6F24,U+6F2D,U+706C,U+70B1,U+70C0,U+721D,U+728B,U+72F3,U+7303,U+7338-7339,U+734D,U+736C,U+736F,U+74A9,U+750D,U+7513,U+7592,U+75C4,U+762D,U+7707,U+770D,U+7735,U+77A2,U+7839,U+784E,U+786D,U+791E,U+7924,U+793B,U+7953,U+798A,U+7A78,U+7A80,U+7B58,U+7BA8,U+7BDA,U+7CC8,U+7EC1,U+7F0F,U+7F12,U+7F33,U+7F68,U+8022,U+8025,U+8029,U+8035,U+809C,U+80AD,U+80B7,U+80E9,U+80F2,U+8204,U+8221,U+8223,U+822D,U+824B,U+825A,U+8274,U+827F,U+82B0,U+82E0,U+8307,U+8347,U+837D,U+839B,U+83B0,U+83F9,U+8487-8488,U+8539,U+8579-857A,U+85A8,U+867C,U+86B0,U+86D8,U+86F2,U+8764,U+8788,U+8797,U+87B5,U+8803,U+881B,U+8844,U+88FC,U+8930,U+89CC,U+8BA0,U+8C02,U+8C85,U+8C8A,U+8C98,U+8DBC,U+8E23,U+8E40,U+8E94,U+8F77,U+8F79,U+8F81,U+8FD5,U+9058,U+90B6,U+914E,U+91A2,U+91B5,U+943E,U+9494,U+94B6-94B8,U+94DE,U+94EB,U+94F9,U+950A,U+950E,U+9516,U+9518,U+951E,U+9538,U+953E-953F,U+9544,U+954E,U+955F,U+9561,U+9569,U+95F6,U+95FC,U+9603,U+960D,U+963D,U+9667,U+9792,U+9794,U+97AB,U+98A1,U+9969,U+9A80,U+9A96,U+9AB1,U+9AD1,U+9B0F,U+9B2F,U+9C92,U+9C95,U+9CBC,U+9CC6,U+9CD0,U+9CD8,U+9E38,U+9E3A,U+9E5B,U+9E7E,U+9EE5,U+9EEA,U+9EF9,U+9EFB-9EFC,U+9FA0} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/45.woff2") format("woff2");unicode-range:U+4EF3,U+4F74,U+507E,U+50D6,U+50EC-50ED,U+5182,U+51B1,U+525E,U+5282,U+52D6,U+5326,U+537A,U+5459,U+54A9,U+54B4,U+54D9,U+55BE,U+55CD,U+564D,U+572F,U+574C,U+57D8,U+57FD,U+5844,U+5925,U+59D2,U+5A08,U+5AE0,U+5B16,U+5B37,U+5B80,U+5B84,U+5CB5,U+5CBD,U+5E31,U+5E5E,U+5F11,U+5F56,U+5F82,U+5F8C,U+5FC4,U+5FC9,U+5FE4,U+600A,U+605D,U+609D,U+60DD,U+6106,U+610D,U+6243,U+6322,U+63DE,U+63F8,U+641B,U+643F,U+64E4,U+656B,U+6634,U+676A,U+67B5,U+680A,U+681D,U+6883,U+69D4,U+6B37,U+6B9B,U+6C2A,U+6C46,U+6D0E,U+6D7C,U+6EB7,U+6F36,U+6F72,U+6F89,U+6F8C,U+7039,U+7113,U+7173,U+71D4,U+727F,U+728F,U+72EF,U+72F4,U+7321,U+7331,U+74DE,U+74E0,U+75C3,U+75E6,U+7605,U+760A,U+761B,U+7625,U+763C,U+7643,U+764D,U+7704,U+7743,U+7747,U+7780,U+77B5,U+7847,U+786A,U+78A5,U+78B9,U+7962,U+7A02-7A03,U+7A06,U+7AAC-7AAD,U+7AB3,U+7B0A,U+7B4C,U+7BE5,U+7C0F,U+7C40,U+7CA2,U+7CC7,U+7CF8,U+7D77,U+7E3B,U+7EA1,U+7EA9,U+7F02,U+7F07,U+7F0B-7F0C,U+7F23,U+7F2F,U+7F9D,U+8020,U+80E8,U+811E,U+812C,U+8136,U+8182,U+822F,U+8233,U+823B,U+8268,U+827D,U+8284,U+8288,U+828F,U+8291,U+82C8,U+8311,U+831B,U+835B,U+83A9,U+83DD,U+8406,U+845C,U+84B4,U+84B9,U+84E3,U+850C,U+863C,U+864D,U+867F,U+86BA,U+86D1,U+86DE,U+86F1,U+8770,U+87AC-87AD,U+87BD,U+87DB,U+880A,U+883C,U+887F,U+88BC,U+895E,U+89CF,U+89D6,U+8A48,U+8BD4,U+8BDC,U+8C2B,U+8C33,U+8C94,U+8DB1,U+8DCF,U+8DD6,U+8DE3,U+8E90,U+8F7A,U+8F82,U+902D,U+90DB,U+9164,U+91AD,U+91AF,U+936A,U+948B,U+94D8,U+950D,U+9513-9514,U+9529,U+956C,U+963C,U+966C,U+9688,U+96B3,U+97B2,U+996B,U+9987,U+9998,U+9A75,U+9A7A,U+9A92,U+9ABA,U+9ADF,U+9B08,U+9B43,U+9C8E,U+9C94,U+9C9A,U+9CB4,U+9CBD,U+9CD4,U+9E31,U+9E36,U+9E4B,U+9E4E,U+9E55,U+9E63,U+9E68-9E69,U+9E71,U+9EBD,U+9EC9,U+9F0D,U+9F37,U+9F51} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/44.woff2") format("woff2");unicode-range:U+4F1B,U+4F67,U+4F7E,U+4FDC,U+50E6,U+5196,U+51AB,U+5202,U+5233,U+523F,U+52A2,U+536E,U+5476,U+54CF,U+54DA,U+5537,U+561E,U+56DD,U+56DF,U+5709,U+572C,U+576B,U+57CF,U+57F8,U+580D,U+5881,U+589A,U+5941,U+59B2,U+5B5B,U+5C25,U+5D1E,U+5D24,U+5D6B,U+5E42,U+5E8B,U+5EB3,U+5ED2,U+5EFE,U+5F58,U+5F73,U+5FAD,U+6003,U+602B,U+603C,U+6083,U+6100,U+6126,U+6206,U+62CA,U+638E,U+63B4,U+6426,U+646D,U+6535,U+65C4,U+66DB,U+6715,U+6769,U+6798,U+67C3,U+6861,U+698D,U+69B1,U+69CA,U+69E0,U+69ED,U+69F2,U+69FF,U+6A18,U+6B39,U+6BB3,U+6C0D,U+6CB2,U+6CD6,U+6CF7,U+6CFA,U+6D33,U+6E16,U+6E53-6E54,U+6EBB,U+6ED7,U+6FB6,U+7035,U+709D,U+7178,U+7228,U+72AD,U+72B4,U+72F7,U+72FB,U+7313,U+739F,U+7481,U+74BA,U+7540,U+754B,U+755B,U+758B,U+75AC,U+75D6,U+7617,U+7635,U+7640,U+76A4,U+76B2,U+76CD,U+775A,U+77BD,U+781F,U+79B3,U+7B2B,U+7B31,U+7B3E,U+7B6E,U+7B7B,U+7B9C,U+7BFC,U+7C0B,U+7C16,U+7C9E,U+7CC1,U+7CE8,U+7EA5,U+7EF2,U+7F21,U+7F27,U+7F74,U+7FB0,U+7FBC,U+8016,U+8031,U+8071,U+80EA,U+8114,U+8160,U+81A6,U+81C1,U+825F,U+829F,U+82A4,U+82FB,U+8308,U+831A,U+8333,U+836C-836D,U+83B6,U+83F8,U+840F,U+8411,U+841C,U+8489,U+848C,U+855E,U+85A4,U+8627,U+8629,U+866E,U+86B5,U+86C4,U+872E,U+8731,U+873E,U+877B,U+877D,U+87EA,U+8813,U+8864,U+88CE,U+88E5,U+88F0,U+890A,U+892B,U+897B,U+89CB,U+89EF,U+89F3,U+8BFC,U+8C18,U+8C35,U+8D46,U+8DBA,U+8E3A,U+8E6F,U+8F75,U+8F7E,U+8FB6,U+8FD3,U+90BE,U+9161,U+9179,U+917E,U+91A3,U+94AC,U+94D7,U+94E5,U+952C,U+953A,U+9545,U+9565,U+9568,U+956A,U+961D,U+9654,U+96E0,U+972A,U+9730,U+97B4,U+989F,U+98E7,U+990D,U+9967,U+9993,U+9AA3,U+9AC0,U+9AE1,U+9AEB,U+9AED,U+9AF9,U+9C86,U+9C8B,U+9CA0-9CA1,U+9CA3,U+9CE2,U+9E2B,U+9E48,U+9E6A,U+9E87,U+9EE2,U+9EE9,U+9F17,U+9F19,U+9F2C,U+9F80} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/43.woff2") format("woff2");unicode-range:U+4E69,U+4F65,U+50A7,U+5240,U+5261,U+52AC,U+531A,U+5363,U+5416,U+5432,U+5452,U+5456,U+5472,U+5478,U+54AD,U+54BB,U+553F,U+5575,U+5581,U+55CC,U+55FE,U+5601,U+572E,U+57D2,U+57EF,U+581E,U+5924,U+5981,U+5997,U+59A3,U+5AAA,U+5AB8,U+5B34,U+5D5B,U+5D5D,U+5D74,U+5DEF,U+5E11,U+5E91,U+5ED1,U+5EF4,U+5F40,U+600D,U+6019,U+601B,U+605A,U+6092,U+60AB,U+6217,U+623D,U+624C,U+6369,U+65D2,U+6661,U+670A,U+6753,U+67A7,U+6855,U+686B,U+68F9,U+6939,U+696E,U+6980,U+6A7C,U+6AAB,U+6B82,U+6B84,U+6BF3,U+6BF9,U+6C05-6C06,U+6C19-6C1A,U+6CA9,U+6CF6,U+6D07,U+6D1A,U+6D27,U+6D2B,U+6DAB,U+6E8F,U+6F74,U+7085,U+7198,U+71B5,U+7256,U+725D,U+727E,U+72F2,U+72FA,U+7322,U+738E,U+73E5,U+7477,U+750F,U+755A,U+7594,U+75B3,U+760C,U+7615,U+7630,U+763F,U+7654,U+7726,U+77EC,U+7817,U+78A1,U+78D4,U+78D9,U+7905,U+7B2A,U+7B2E,U+7B62,U+7B85,U+7BA7,U+7BCC,U+7BEA,U+7C26,U+7C74,U+7C9C-7C9D,U+7E47,U+7E9B,U+7E9F,U+7EE0-7EE1,U+7EE8,U+7EF1,U+7F01,U+7F11,U+7F17,U+7F36,U+7F7E,U+7FEE,U+802A,U+809F,U+80AB,U+80CD,U+80D7,U+8112,U+8118,U+8169,U+8234,U+8279,U+8298,U+82CA,U+82D8,U+82E1,U+83C0,U+83D4,U+83DF,U+8401,U+8451,U+845A,U+8476,U+8478,U+84BA,U+84BD,U+84E0,U+851F,U+8548,U+8556,U+8585,U+868B,U+868D,U+86E9,U+86F4,U+86F8,U+8765,U+8785,U+87AB,U+87EE,U+8816,U+8832,U+8872,U+88B7,U+88E2-88E3,U+89DA,U+8BCE,U+8BD3,U+8BD6,U+8BF9,U+8C16,U+8C73,U+8D40,U+8D4D,U+8D5C,U+8DDE,U+8DEB,U+8F6D,U+8F94,U+8FE8,U+9011,U+915E,U+9185,U+918C,U+94AB,U+94D1,U+94F3,U+94FD,U+9515,U+951D,U+952A,U+9558,U+9567,U+96CE,U+96E9,U+9785,U+9878,U+987C,U+9883,U+98D1,U+9954,U+9963,U+9A93,U+9AC1,U+9ACC,U+9B1F,U+9B49,U+9B4D,U+9B51,U+9CA5,U+9CA7,U+9CAE,U+9CCE,U+9CD3,U+9E37,U+9E39,U+9E41,U+9E46,U+9F22,U+9F2F,U+9F39,U+9F85} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/42.woff2") format("woff2");unicode-range:U+4E47,U+4E5C,U+4E8D,U+4EDF,U+4F89,U+4FAA,U+50EE,U+520E,U+5412,U+5443,U+5454,U+54C2,U+54D3,U+54DE,U+5577,U+5591,U+55E5,U+560C,U+560F,U+562C,U+566B,U+56AF,U+5769,U+577C,U+578C,U+5793,U+57DA,U+57E4,U+5889,U+593C,U+59AB,U+5AD4,U+5AD8,U+5AF1,U+5B53,U+5BA5,U+5C59,U+5C63,U+5CA3,U+5E0F,U+5E14,U+5EDB,U+5F01,U+5FBC,U+6004,U+6029,U+6041,U+60AD,U+610E,U+61B7,U+634C,U+647A,U+64BA,U+65C3,U+65C6,U+65F0,U+6600,U+668C,U+66F7,U+67E2,U+67F0,U+680C,U+6874,U+691F,U+6989,U+69A7,U+6A17,U+6A3E,U+6B81,U+6C07,U+6C10,U+6C3D,U+6D91,U+6E6B,U+6FDE,U+70BB,U+71B3,U+723B,U+726E,U+72B0,U+72CE,U+7301,U+731E,U+7360,U+737E,U+73A2,U+748E,U+74FF,U+7633,U+771A,U+7765,U+7768,U+781C,U+7829,U+7913,U+7957,U+79D5,U+79EB,U+7A70,U+7A86,U+7AA8,U+7B04,U+7B25,U+7B38,U+7B47,U+7B72,U+7BA6,U+7CCD,U+7DAE,U+7ECB,U+7ED7,U+7EFE,U+7F26,U+7F31,U+7F35,U+7F5F,U+7FAF,U+801C,U+8043,U+80BC,U+8188,U+81CC,U+823E,U+8244,U+824F,U+82A1,U+82B4,U+82C1,U+82E4,U+82F4,U+8306,U+833A,U+835C,U+839C,U+83B3,U+83BC,U+83F0,U+846D,U+8497,U+85FF,U+867A,U+86A8-86A9,U+870A,U+8734,U+87CA,U+886E,U+887E,U+8888,U+88A2,U+88C9,U+8921,U+8BB5,U+8BF3,U+8C04,U+8C17,U+8C1D,U+8C25,U+8C2F,U+8C36,U+8C55,U+8C78,U+8D3D,U+8D59,U+8D67,U+8D91,U+8DBF,U+8DEC,U+8DFD,U+8E05,U+8E14,U+8E41,U+8F8E,U+900B,U+9044,U+9062,U+90CF,U+9123,U+9146,U+9162,U+9172,U+918D,U+9190,U+92C8,U+933E,U+93CA,U+9486,U+948C,U+94AA,U+94B2,U+94C8,U+94CA,U+94D5,U+94DF,U+94E9-94EA,U+94F7,U+94FC,U+951B,U+9546,U+954F,U+9554,U+9559,U+9566,U+9571-9572,U+95F1,U+960B,U+960F,U+97AF,U+988F,U+98D5,U+992E,U+9955,U+9A7D,U+9AA2,U+9AB0,U+9AC2,U+9B23,U+9B32,U+9C90,U+9C9E,U+9CA9,U+9CAD,U+9CB1,U+9CC3,U+9E47,U+9E6C,U+9EE7,U+9EFE,U+9F0B,U+9F87} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/41.woff2") format("woff2");unicode-range:U+4F25,U+4F32,U+4F5E,U+4F76,U+4F7B,U+4FE6,U+5028,U+5048,U+5250,U+535F,U+538D,U+53C1,U+54A6,U+54D4,U+54DD,U+5533,U+5541,U+5550,U+5556,U+55DD,U+55F3,U+55F5,U+5627,U+5657-5658,U+5664,U+567B,U+56D4,U+575C,U+57B2,U+5800,U+59A4,U+5A62,U+5AEB,U+5C3B,U+5CAC,U+5D26,U+5D9D,U+5DB7,U+5F2D,U+5FB5,U+5FDD,U+5FF8,U+6079,U+60B1,U+6222,U+623E,U+629F,U+6332,U+638A,U+63BC,U+63E0,U+6485,U+64B8,U+64E2,U+65AB,U+669D,U+66BE,U+66E9,U+67C1,U+67D2,U+67FD,U+6800,U+6877,U+68FC,U+690B,U+6924,U+6978,U+69C1,U+6A50,U+6A5B,U+6A97,U+6B24,U+6B8D,U+6BAA,U+6C18,U+6C54,U+6CEB,U+6CEE,U+6D04,U+6D4D,U+6DE0,U+6EB1,U+6EBD,U+7110,U+7168,U+71F9,U+7230,U+728D,U+7292,U+72B8,U+72D2,U+73B3,U+73D0,U+7511,U+75A0,U+75A5,U+75C8,U+75E7,U+7610,U+7619,U+772D,U+779F,U+7826,U+782C,U+7856,U+7877,U+7A39,U+7AE6,U+7B0F,U+7BAA,U+7BAC,U+7C1F,U+7ED0,U+7ED4,U+7EFB,U+7F0D,U+7F1F,U+7F45,U+7FBF,U+7FD5,U+7FE5,U+8027,U+8052,U+80DB,U+80DD,U+80FC,U+8132,U+815A,U+8167,U+816D,U+81CA,U+8228,U+8249,U+82A9,U+82AB,U+82CC,U+8315,U+8351,U+8368,U+8378,U+83AA,U+83B4,U+83B8,U+83D8,U+83EA,U+84C1,U+8538,U+858F,U+859C,U+85AE,U+867B,U+86B4,U+86CF,U+8722,U+876E,U+877C,U+87E5,U+88DF,U+8919,U+8BA7,U+8BCC,U+8BDF,U+8BE8,U+8BEE,U+8C20,U+8C30,U+8D36,U+8DB5,U+8DF8,U+8E1F,U+8E2F,U+8E70,U+8F78,U+8F8B,U+8F8F,U+8F9A,U+9021,U+908B,U+90B4,U+90BA,U+90C4,U+90D0,U+90EB,U+90FE,U+917D,U+91AA,U+9487,U+948D,U+9490,U+94AD,U+94BD,U+94CD,U+94D6,U+94D9,U+94EF,U+9507,U+954A,U+955E,U+956B,U+95E9,U+9604,U+9612,U+9615,U+9617,U+96B9,U+96D2,U+9708,U+989A-989B,U+989E,U+98A7,U+9A78,U+9A88,U+9AA0,U+9AB6,U+9B3B,U+9B47,U+9C82,U+9C9B,U+9CCA,U+9CD9,U+9E28,U+9E2C,U+9E5A,U+9E5E,U+9ECD,U+9ECF,U+9F8A,U+9F8C} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/40.woff2") format("woff2");unicode-range:U+4E93,U+4EBB,U+4EC4,U+4EF5,U+4F27,U+4FA9,U+4FE3,U+502C,U+5080,U+5121,U+51C7,U+51EB,U+5208,U+523D,U+52F0,U+530F,U+53F5,U+5453,U+5464,U+5466,U+549D,U+54A3,U+54BF,U+54D0,U+5527,U+5549,U+556D,U+558F,U+55B1,U+55EB,U+55F2,U+5624,U+5685,U+56E1,U+571C,U+5739,U+57B4,U+5807,U+58C5,U+59AF,U+59D8,U+5A09,U+5A0C,U+5A4A,U+5A67,U+5AD2,U+5B6C,U+5C50,U+5CA2,U+5D03,U+5D27,U+5D6C,U+5DF3,U+5E19,U+5EA0,U+5EBE,U+5EEA,U+5FEA,U+5FFE,U+6042,U+6120,U+6175,U+6215,U+6221,U+622C,U+6266,U+6308,U+6339,U+643D,U+659B,U+6677,U+679E,U+67B3,U+6832,U+6840,U+68C2,U+68F0,U+6934,U+6966,U+6987,U+6998,U+69CE,U+6A2F,U+6A3D,U+6A84,U+6AA9,U+6B87,U+6B93,U+6BD6,U+6BFD,U+6C16,U+6CD4,U+6D0C,U+6DA0,U+6E0C,U+6E86,U+6ECF,U+6F4B,U+7023,U+7080,U+70B7,U+70CA,U+72D9,U+7352,U+740A,U+7441,U+7457,U+74D2,U+7596,U+75A3,U+75D8,U+75E4,U+75E8,U+7622,U+765E,U+76B4,U+7812,U+7818,U+784C,U+7850,U+7887,U+789A-789B,U+78F4,U+7A51,U+7B15,U+7B1E,U+7B24,U+7B5A,U+7BB8,U+7BC1,U+7BD9,U+7CCC,U+7EA8,U+7EC0,U+7EE6,U+7EFA,U+7F1B,U+7F22,U+7F71,U+7FA7,U+7FF3,U+8084,U+8093,U+80B1,U+80F4,U+81BB,U+81EC,U+8202,U+8210,U+828A,U+828E,U+82A8,U+82E3,U+8366,U+8369,U+83A8,U+83E1,U+84D1,U+84FC,U+853B,U+857B,U+85D3,U+8605,U+8662,U+86B1,U+86C9,U+86D4,U+86F3,U+8725,U+8748,U+874C,U+8763,U+879F,U+87B3,U+887D,U+88FE,U+89DE,U+8A8A,U+8BA6,U+8C00,U+8C07,U+8C14,U+8C21,U+8C47,U+8C7A,U+8D30,U+8DD7,U+8E59,U+8E69,U+8E85,U+8F6B,U+8F73,U+8F87,U+8FF3,U+9026,U+902F,U+9099,U+90C7,U+9143,U+91AE,U+91BA,U+948F,U+94AF,U+94BF,U+94D2,U+9531,U+95F3,U+9609-960A,U+9765,U+97EA,U+9880,U+996C,U+9980,U+9991,U+9994,U+9AB7,U+9AC5,U+9AFB,U+9C87,U+9CB5,U+9CC7,U+9CD5,U+9E42,U+9E58,U+9F88} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/39.woff2") format("woff2");unicode-range:U+4EDD-4EDE,U+4EE1,U+4FFE,U+5025,U+506C,U+50A5,U+51FC,U+5241,U+525C,U+539D,U+53FC,U+54E7,U+5514,U+555C,U+556E,U+5576,U+557E,U+5599,U+55B9,U+55D4,U+5623,U+564C,U+5654,U+5671,U+568F,U+56D7,U+57A1,U+57A7,U+57D9,U+584D,U+58EC,U+5942,U+59DD,U+5A32,U+5A55,U+5A75,U+5B71,U+5B73,U+5C7A,U+5C98,U+5D06,U+5D6F,U+5DFD,U+5E5B,U+5EB9,U+5FD2,U+600F,U+606B,U+62BB,U+62BF,U+634B,U+636D,U+6387,U+63B3,U+63B8,U+63C4,U+63C6,U+63F6,U+6441,U+6555,U+661D,U+6657,U+66A7,U+6775,U+678B,U+6849,U+6860,U+6867,U+6901,U+6910,U+699B,U+6A28,U+6A79,U+6B46,U+6B92,U+6BC2,U+6C21,U+6C24,U+6C29,U+6C32,U+6C35,U+6C4A,U+6C86,U+6CA3,U+6CC5,U+6D39,U+6D60,U+6D63,U+6D6F,U+6DDD,U+6ED3,U+6EDF,U+6FB9,U+6FD1,U+6FEF,U+712F,U+7145,U+71E7,U+7284,U+72CD,U+72F0,U+7325,U+732C,U+7350,U+7391,U+73C8,U+73D9,U+741A,U+757F,U+7583,U+759D,U+75B4,U+75E3,U+75F1,U+75FF,U+7688,U+7699,U+76E5,U+7751,U+789C,U+78B2,U+7A17,U+7B08,U+7B33,U+7BE6,U+7C2A,U+7EA3,U+7EB0,U+7F2C,U+7FE6,U+8012,U+80E4,U+80EF,U+8171,U+821B,U+826E,U+8297,U+82BE,U+82EB,U+830C,U+8314,U+8331,U+83C5,U+83D6,U+83FD,U+843C,U+8473,U+84A1,U+84CA,U+8564,U+8572,U+85C1,U+86A3,U+86AA,U+86C6,U+86ED,U+8708-8709,U+870D,U+8869,U+89C7,U+89D0,U+89E5,U+8A3E,U+8B07,U+8BC2,U+8BF0,U+8C2A,U+8C49,U+8C62,U+8C89,U+8D49,U+8D6D,U+8D94,U+8DF9,U+8E2E,U+8E3D,U+8E47,U+8E51,U+8E7F,U+9005,U+9016,U+907D,U+9082,U+9088,U+909B,U+90B0,U+914A,U+9495,U+94A3-94A4,U+94B9-94BA,U+94CB-94CC,U+94E1,U+94F0,U+9509,U+9512,U+9552-9553,U+955D,U+9697,U+96B0,U+96BC,U+975B,U+977C,U+98A2,U+998A,U+9990,U+9995,U+9A9B,U+9C91,U+9CB3,U+9CB7,U+9CCF,U+9E29,U+9E57,U+9E82,U+9EDC,U+9EDF-9EE0,U+9F83,U+9F89} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/38.woff2") format("woff2");unicode-range:U+4EE8,U+4EEB,U+4F22,U+4F43,U+4F5D,U+4FDA,U+502D,U+50F3,U+5106,U+520D,U+52AD,U+530D,U+5310,U+53A9,U+53DF,U+5421,U+544B,U+5477,U+5482,U+551B,U+5530,U+5555,U+55C4,U+55D1,U+55DF,U+55E4,U+55EA,U+55EC,U+55EF,U+562D,U+56EB,U+56F5,U+56F9,U+5704,U+576D,U+57D5,U+57DD,U+5880,U+5958,U+599E,U+59BE,U+5ABE,U+5B51,U+5BEE,U+5CBF,U+5CC4,U+5CD2,U+5CE4,U+5E96,U+603F,U+607B,U+609A,U+61D1,U+61FF,U+622E,U+626A,U+6371,U+63AE,U+63CD,U+63D6,U+63FF,U+6410,U+6414,U+6421,U+6448,U+64C0,U+66DC,U+67A5,U+680E,U+6841,U+6954,U+6A47,U+6C94,U+6CA4,U+6D94,U+6E4E,U+705E,U+70E9,U+7116,U+726F,U+72DE,U+74E4,U+753E,U+7548,U+75B1,U+75B8,U+75BD,U+75C2,U+75CD,U+7618,U+766F,U+76F1,U+7719,U+777E,U+7791,U+7823,U+7827,U+7889,U+7893,U+795C,U+79E3,U+7A88,U+7A95,U+7AA0,U+7B90,U+7BD1,U+7BFE,U+7CB2,U+7DA6,U+7EBE,U+7EC2,U+7EEB,U+7EEF,U+7F03,U+7F08,U+7F25,U+7F58,U+7F61,U+7F9F,U+7FB8,U+8037,U+8153,U+8174,U+8191,U+8214,U+8222,U+82D5,U+82DC,U+8334,U+835A,U+8418,U+84FF,U+852B,U+8616,U+866C,U+8693,U+869D,U+86A7,U+86AC,U+86AF,U+86B6,U+86CA,U+86D0,U+8759,U+8760,U+87A8,U+87AF,U+87C6,U+87D1,U+8839,U+88C6,U+8934,U+89CA,U+89CE,U+8BA3,U+8BAA-8BAB,U+8BC5,U+8BDB,U+8BE4,U+8C15,U+8D84,U+8DB8,U+8DC4,U+8DC6,U+8DCE,U+8DDB,U+8E09,U+8E1D,U+8E39,U+8E42,U+8E49,U+8E4B,U+8E8F,U+8F71-8F72,U+9051,U+9097,U+90D3,U+90E2,U+90E6,U+90EF,U+9104,U+9150,U+919A,U+91B4,U+938F,U+9497,U+949C,U+94E0,U+951F,U+9534,U+9557,U+9562-9563,U+9606,U+965F,U+973E,U+9791,U+9889,U+9894,U+9974,U+9A90,U+9A9C-9A9D,U+9AA7,U+9AEF,U+9C88,U+9CA2,U+9CB2,U+9CB6,U+9CC5,U+9CCD,U+9CDC,U+9CDF,U+9E22,U+9E2A,U+9E67,U+9E6B,U+9E73,U+9EB8,U+9F9B} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/37.woff2") format("woff2");unicode-range:U+4EB5,U+4F09,U+4F57,U+4F6F,U+4FCE,U+4FEA,U+4FF3,U+4FF8,U+500C,U+500F,U+504E,U+507B,U+5088,U+5345,U+53FB,U+5457,U+549B,U+54A7,U+54CC,U+54D2,U+550F,U+5544,U+558B,U+5594,U+559F,U+55D6,U+55E8,U+564E,U+565C,U+5785,U+57A9,U+57AD,U+57ED,U+5914,U+59AA,U+5A06,U+5A40,U+5AE1,U+5AE6,U+5B40,U+5B6A,U+5B93,U+5C99,U+5C9C,U+5CC1,U+5CCB,U+5D2E,U+5D47,U+5D4B,U+5D99,U+5E54,U+5E61,U+6002,U+6014,U+6078,U+607D,U+607F,U+608C,U+60C6,U+60F4,U+60FA,U+621B,U+621F,U+62C8,U+631B,U+631E,U+64D8,U+6635,U+6641,U+6710,U+6748,U+6772,U+67AD,U+67B0,U+67B7,U+695D,U+69AB,U+6A58,U+6C68,U+6C8F,U+6D2E,U+6D35,U+6D9E,U+6DD9,U+6DEC,U+6E11,U+6E98,U+6EC2,U+6EE2,U+6F09,U+6F66,U+6F8D,U+6FC9,U+6FE0,U+7094,U+7119,U+723F,U+729F,U+73C9,U+73DE,U+7430,U+75C9,U+7634,U+76C5,U+7708,U+778C,U+77B3,U+77DC,U+7800,U+781D,U+782D,U+7830,U+783B-783C,U+78A3,U+78EC,U+7949,U+7980,U+7A14,U+7A23,U+7AFD,U+7B95,U+7C0C,U+7C41,U+7C91,U+7CC5,U+7ECC,U+7F04,U+7FCA,U+8006,U+8069,U+807F,U+80BD,U+814B,U+8198,U+8200,U+828D,U+82C4,U+82CB,U+82D2,U+82F7,U+832D,U+834F,U+840B,U+8438,U+847A,U+84D6,U+84E5,U+8537,U+85D0,U+8671,U+86A4,U+86F0,U+86F9,U+8703,U+8707,U+8737,U+873B,U+8782,U+87D2,U+87E0,U+8815,U+8913,U+891B,U+8941,U+8A07,U+8BC3,U+8BCB,U+8BCF,U+8BD2,U+8C0C,U+8C11,U+8C29,U+8C32,U+8C4C,U+8D45,U+8DFA,U+8E31,U+8E6D,U+8E76,U+9004,U+9036,U+90D7,U+90DC,U+9169,U+9485,U+94DB,U+9506,U+950F,U+9528,U+9542,U+9549,U+9573,U+95FE,U+961A,U+9649,U+9701,U+970E,U+972D,U+9798,U+97A3,U+97EB,U+988C,U+98A6,U+98D3,U+98D9,U+9968,U+996F,U+9977,U+997D,U+9984,U+9997,U+9ACB,U+9E20,U+9E49,U+9E4C,U+9E51,U+9E66,U+9F3E} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/36.woff2") format("woff2");unicode-range:U+4EC9,U+4F5A,U+4F8F,U+4FDF,U+501C,U+50A9,U+510B,U+51A2,U+527D,U+52BE,U+5420,U+542E,U+5486,U+5499,U+54C6,U+5506,U+5511,U+5522,U+552C,U+556C,U+55B3,U+55BD,U+55FD,U+567C,U+568E,U+5693,U+572A,U+5768,U+577B,U+5892,U+59F9,U+5A11,U+5A20,U+5B17,U+5BB8,U+5C15,U+5D3D,U+5D4A,U+5D82,U+5E1A-5E1B,U+5EA5,U+5FCF-5FD1,U+5FD6,U+5FFF,U+6006,U+6035,U+60AF,U+60B4,U+60BB,U+60DA,U+612B,U+6194,U+61CA,U+61E6,U+61F5,U+620A,U+62A1,U+62D7,U+637B,U+63E9,U+64B5,U+655D,U+65CC,U+6619,U+664C,U+665F,U+66B9,U+6773,U+6777,U+6787,U+67E9,U+6845,U+684E,U+688F,U+693D,U+696B,U+6994,U+69B7,U+6A35,U+6C26,U+6D43,U+6D5C,U+6D8E,U+6E6E,U+6E9F,U+6EF9,U+6F2A,U+6F78,U+6FC2,U+704F,U+70EC,U+7118,U+714A,U+724D,U+72C8,U+7337,U+733E,U+7428,U+74F4,U+7566,U+7572,U+7579,U+75E2,U+75FC,U+762A,U+7638,U+7678,U+76C2,U+76F9,U+7762,U+77CD,U+77FD,U+7837,U+78B4,U+795F,U+7A1E,U+7B20,U+7BA9,U+7BAB,U+7BDD,U+7C7C,U+7CB3,U+7CBD,U+7CDC,U+7EC9,U+7EF6,U+7F19,U+7F44,U+7F54,U+7F94,U+8004,U+800B,U+8019,U+809B,U+80C4,U+80ED,U+80F1,U+8146,U+816E,U+817C,U+81C0,U+81FC,U+81FE,U+830F,U+8360,U+8365,U+8385,U+8392,U+83A0,U+8424,U+84AF,U+869C,U+86CE,U+8713,U+8718,U+87C0,U+87CB,U+8821,U+8936,U+89D1,U+8BB9,U+8BD8,U+8BE9,U+8C0F,U+8D53,U+8D5D,U+8D73,U+8DF7,U+8F7C,U+8FE4,U+9041,U+9052,U+905B,U+9095,U+909D,U+90C5,U+911E,U+9122,U+916A,U+919B,U+92AE,U+9492,U+94A1,U+94B5,U+94BC,U+94C4,U+94C9,U+94E7,U+9502-9503,U+9511,U+9517,U+9536-9537,U+9602,U+9616,U+9621,U+9631,U+96C9,U+96F3,U+9739,U+9753,U+98D2,U+98DA,U+998B,U+9A81,U+9A9E,U+9B03,U+9C85,U+9CA8,U+9CAB,U+9EBE,U+9F10,U+9F8B} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/35.woff2") format("woff2");unicode-range:U+4E15,U+4E1E,U+4E2B,U+4EB3,U+4F0E,U+4F3D,U+4F64,U+5043,U+5140,U+51BC,U+51CB,U+52D0,U+531D,U+536F,U+53A5,U+53FD,U+541D,U+5429,U+5494,U+54A4,U+54AA,U+54AF,U+54CE,U+5520,U+553E,U+557B,U+55C5,U+55D2,U+55E1,U+55E6,U+55F7,U+5608,U+561F,U+5636,U+5639,U+563B,U+5659,U+5662,U+5773,U+5811,U+587E,U+58D5,U+598A,U+5992,U+59A9,U+5A6A,U+5A76,U+5A7A,U+5AC9,U+5AE3,U+5B7D,U+5B95,U+5C8C,U+5CAB,U+5CB7,U+5D02,U+5D34,U+5D58,U+5E7A,U+5EFF,U+5F0B,U+5F29,U+5F77,U+5F89,U+5F9C,U+5FA8,U+6005,U+6026,U+6063,U+60D8,U+60EC,U+60EE,U+6115,U+618E,U+6248,U+6376,U+637A,U+6390,U+63B0,U+64DE,U+652B,U+6538,U+65BC,U+65CE,U+65D6,U+6631,U+6636,U+6654,U+6666,U+6684,U+677C,U+67D8,U+67DE,U+692D,U+6979,U+6B59,U+6C69,U+6C74,U+6CAE,U+6CE0,U+6D54,U+6DD6,U+6DDE,U+6DE6,U+6EB4,U+6F15,U+6F29,U+70C3,U+715C,U+7172,U+71B9,U+7266,U+728A,U+7329,U+7396,U+73B7,U+73CF,U+73E9,U+7425,U+742C,U+742E,U+7455,U+74EE,U+7519,U+7525,U+753A,U+75B5,U+75B9,U+75D4,U+75DE,U+7656,U+765C,U+768E,U+7691,U+7729,U+777D,U+7825,U+783E,U+787C,U+795A,U+79ED,U+7A79,U+7B06,U+7B3A,U+7B8D,U+7BB4,U+7BD3,U+7BE1,U+7CBC,U+7F30,U+7F42,U+7F8C,U+7FCE,U+7FF1,U+80AE,U+810D,U+81C6,U+822B,U+82A5,U+82AA,U+82DE,U+832F,U+8340,U+8343,U+835E,U+8364,U+836A,U+8543,U+854A,U+8559,U+8568,U+85B0,U+86E4,U+8717,U+87FE,U+8845,U+88F1,U+8902,U+8C12,U+8C82,U+8D32,U+8D58,U+8DBE,U+8E7C,U+8F95,U+8FAB,U+9035,U+90A1,U+9149,U+9157,U+948E,U+949A,U+94C2,U+94C6,U+94F1,U+956F-9570,U+95EB,U+95F0,U+9611,U+9642,U+968D,U+9706,U+970F,U+97ED,U+988A,U+98E8,U+99A5,U+9A77,U+9CDD,U+9E2F,U+9E33,U+9E44,U+9EDD} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/34.woff2") format("woff2");unicode-range:U+4F2B,U+4F58,U+4FAC,U+4FD0,U+5055,U+5156,U+51BD,U+5243,U+5323,U+535E,U+53AE,U+53F1,U+540F,U+542D,U+5431,U+545B,U+547B,U+548E,U+5492,U+54AB,U+54FD,U+5509,U+5567,U+5600,U+5618,U+563F,U+56F1,U+56FF,U+573B,U+574D,U+579B,U+57A0,U+57B8,U+57C2,U+586C,U+58F9,U+5993,U+5996,U+59E3,U+5A29,U+5B62,U+5B7A,U+5BA6,U+5C49,U+5C4E,U+5C96,U+5CE5,U+5E44,U+5F99,U+6043,U+6059,U+606C,U+60B8,U+60ED,U+60F0,U+61A9,U+62A8,U+62FD,U+630E,U+6342,U+63A3,U+63AC,U+6402,U+642A,U+64A9,U+64AE,U+64B7,U+6525,U+659F,U+65EE-65EF,U+66F3,U+67B8,U+67E0,U+683E,U+6886,U+68B5,U+6963,U+6988,U+69DF,U+6AAC,U+6B86,U+6B9A,U+6C50,U+6C76,U+6C85,U+6C8C,U+6CEF,U+6D19,U+6D52,U+6D95,U+6DA7,U+6DB8,U+6DC5,U+6DFC,U+6E1A,U+6E25,U+6E4D,U+6EA7,U+6EC1,U+6F31,U+6F7A,U+6FA7,U+6FE1,U+7096,U+70F7,U+7131,U+7166,U+72E1,U+7317,U+731D,U+736D,U+73C2,U+740F,U+7490,U+74EF,U+7663,U+76BF,U+76D4,U+7728,U+776C,U+77A0,U+77F8,U+77FE,U+78D0,U+7A37,U+7A92,U+7ABF,U+7AFA,U+7B4F,U+7B60,U+7B75,U+7CEF,U+7EDB,U+7F1C,U+7FB2,U+7FB9,U+7FE1,U+808B,U+80E5,U+8113,U+81C3,U+82D4,U+8309,U+8317,U+8431,U+8469,U+84BF,U+84DF,U+8511,U+853A,U+85B9,U+8638,U+864F,U+86C0,U+8715,U+8822,U+8882,U+8884-8885,U+8892,U+88E8,U+892A,U+8983,U+8BA5,U+8BD9,U+8BE0-8BE1,U+8BE7,U+8C1A,U+8C1F,U+8C27,U+8C5A,U+8E0C,U+8E52,U+8E74,U+8E87,U+8F98,U+8FC2,U+8FE6,U+900D,U+9068,U+9074,U+90B3,U+90E7,U+915A,U+916E,U+93D6,U+949D,U+94B4,U+94D0,U+94E3-94E4,U+94EC,U+9522,U+9556,U+9619,U+9893,U+9981,U+9A6E,U+9A87,U+9A8A,U+9AA1,U+9ABC,U+9B13,U+9E3E,U+9E43,U+9E5C,U+9E6D,U+9E9D,U+9EDB} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/33.woff2") format("woff2");unicode-range:U+4E56,U+4E98,U+4F3A,U+4F5F,U+4F6C,U+4F70,U+4FA5,U+4FE8,U+5014,U+524C,U+5315,U+5364,U+53E8,U+5406,U+549A,U+54E6,U+54EE,U+54FC,U+556A,U+5583,U+55E3,U+5669,U+592F,U+5944,U+595A,U+598D,U+59CA,U+59D7,U+5A7F,U+5B09,U+5E4C,U+5EB6,U+5F08,U+5F1B,U+602F,U+607A,U+608D,U+60CB,U+620C,U+6249,U+62A0,U+62C4,U+62ED,U+62F4,U+6345,U+6382,U+6396,U+63C9,U+63EA,U+6400,U+6413,U+645E,U+6487,U+64AC,U+6556,U+6593,U+65A1,U+6615,U+667E,U+6805,U+68A2,U+69DB,U+6A80,U+6B83,U+6C30,U+6C5E,U+6CAD,U+6CDE,U+6CF1,U+6D31,U+6D93,U+6DAE,U+6DBF,U+6E5F,U+6E85,U+6F5E,U+6F7C,U+701B,U+70AB,U+717D,U+71A8,U+7252,U+72B7,U+72C4,U+72E9,U+7315,U+73AE,U+73BA,U+73C0,U+73D1,U+7435-7436,U+745A,U+74E2,U+7504,U+752C-752D,U+7599,U+759F,U+75A4,U+75F0,U+7600-7601,U+761F,U+7629,U+768B,U+772F,U+77A5,U+77B0,U+783A,U+78D5,U+78FA,U+7934,U+7940,U+7977,U+7B19,U+7B71,U+7B94,U+7CB1,U+7CE0,U+7ECA,U+7EF7,U+7F28,U+7F2B,U+7F81,U+80EB,U+8110,U+812F,U+8148,U+814C,U+8165,U+819B,U+81BA,U+8235,U+828B,U+82AE,U+82B7,U+830E,U+831C,U+8338,U+837C,U+8393,U+83BA,U+83C1,U+84D3,U+84E6,U+853C,U+8654,U+86DB,U+86FE,U+8712,U+873F,U+874E,U+8757,U+8783,U+8859,U+88A4,U+88F3,U+8925,U+8BB7,U+8BFD,U+8BFF,U+8C1B,U+8C24,U+8C2C,U+8D4A,U+8D4E,U+8D61,U+8D66,U+8DDA,U+8F76,U+8F8A,U+8FE5,U+8FF8,U+90AC,U+90B8,U+9119,U+912F,U+9131,U+9163,U+9170,U+949B,U+94A8,U+94C0,U+94E2,U+94FF,U+9525,U+9535,U+9539,U+9550,U+955B,U+965B,U+96CC,U+96F9,U+9774,U+988D,U+9975,U+998D,U+998F,U+9A7F,U+9AB8,U+9C7F,U+9C9F,U+9CA4,U+9E35,U+9E8B,U+9E92,U+9EEF} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/32.woff2") format("woff2");unicode-range:U+4E10,U+4EA5,U+4EC3,U+4F36,U+4F84,U+4F88,U+4F8D,U+4F97,U+501A,U+504C,U+5201,U+52FA,U+5366,U+543C,U+545C,U+5471,U+5480,U+5495,U+54B3,U+54C7,U+54DF,U+557C,U+55DC,U+5632,U+566C,U+5676,U+56B7,U+56DA,U+56E4,U+5742,U+57A2-57A3,U+5815,U+58D1,U+5919,U+592D,U+5955,U+5983,U+59E5,U+5A13,U+5A34,U+5B70,U+5BD0,U+5BF0,U+5C41,U+5C94,U+5CE6,U+5D69,U+5DC5,U+5E16,U+5EB5,U+5F3C,U+5F95,U+604D,U+60E6,U+618B,U+61A7,U+61AC,U+61CB,U+62CE,U+62E7,U+62EE,U+631F,U+634E,U+6479,U+6482,U+655B,U+660A,U+664F,U+66E6,U+672D,U+675E,U+67C4,U+67DA,U+6809,U+6868,U+68F1,U+68FA,U+693F,U+695E,U+69AD,U+6A71,U+6A90,U+6B89,U+6BD9,U+6BE1,U+6C40,U+6CB1,U+6D85,U+6DA3,U+6DAA,U+6DC6-6DC7,U+6E0D,U+6E3A,U+6E43,U+6EBA,U+6F3E,U+6F88,U+6FEE,U+70D9,U+7109,U+71EE,U+722A,U+733F,U+73F2,U+7409,U+7459,U+747E,U+7487,U+748B,U+749E,U+759A,U+75A1,U+75CA,U+75D2,U+75EB,U+766B,U+7738,U+773A,U+777F,U+7785,U+77AA,U+77BF,U+78BE,U+795B,U+796F,U+79A7,U+7A20,U+7A96,U+7AA5,U+7B03,U+7B28,U+7B50,U+7B77,U+7BF1,U+7C38,U+7C95,U+7EAD,U+7EC5,U+7EF0,U+7EFD,U+7F0E,U+7F2E,U+7F9A,U+7FCC,U+8046,U+8098,U+80DA,U+80E7,U+80F0,U+8237-8238,U+8299,U+82CE,U+82D3,U+8301,U+8398,U+83E0,U+83E9,U+8426,U+8475,U+85E9,U+86DF,U+871A,U+8910,U+891A,U+8BB4,U+8BE3,U+8BEC,U+8BF2,U+8C06,U+8C0D,U+8C19,U+8DB4,U+8DE4,U+8E1E,U+8E66,U+8E6C,U+8F84,U+901E,U+9083,U+90E1,U+90F4,U+9165,U+91C9,U+91DC,U+94B3,U+94E8,U+9504,U+9530,U+9532,U+954C-954D,U+95FA,U+962A,U+9685,U+9704,U+9776,U+9985,U+9A6F,U+9AA5,U+9CB8,U+9E25,U+9E4A} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/31.woff2") format("woff2");unicode-range:U+4EA2,U+4FA3,U+4FAE,U+4FD8,U+5029,U+516E,U+5195,U+51A5,U+51C4,U+51DB,U+51F8,U+5228,U+527F,U+5288,U+5321,U+5352,U+53E9,U+5450,U+5484,U+5490,U+54C9,U+5501,U+56A3,U+56BC,U+5703,U+5729,U+575E,U+589F,U+5984,U+5A04-5A05,U+5A25,U+5A36,U+5A77,U+5A9B,U+5AB2,U+5B75,U+5BC7,U+5BDD,U+5C51,U+5C79,U+5C91,U+5CD9,U+5CED,U+5E87,U+5F13,U+5F27,U+5F64,U+5FFB,U+6020,U+6055,U+60EB,U+60F6,U+6123,U+6151,U+61A8,U+620D-620E,U+6241,U+6273,U+6289,U+62C7,U+62CC,U+62D9,U+634D,U+6363,U+6452,U+64D2,U+6726,U+6756,U+6789,U+6829,U+6853,U+6862,U+6866,U+6897,U+68E3,U+6942,U+6995,U+69A8,U+69B4,U+69CC,U+6A1F,U+6A44,U+6A59,U+6BCB,U+6BD3,U+6CBD,U+6CF8,U+6CFE,U+6DA1,U+6E0E,U+6E44,U+6EA5,U+6EE6,U+6F2F,U+6F8E,U+7078,U+7095,U+7099,U+709C,U+70AF,U+70BD,U+70DB,U+70E8,U+70FD,U+714E,U+715E,U+7184,U+71CE,U+7235,U+7239,U+72F8,U+749C,U+74A8,U+74E3,U+75AE,U+75F9,U+7620,U+7693,U+76CF,U+776B,U+778E,U+7852,U+7948,U+7960,U+797A,U+79BA,U+79BE,U+79C3,U+79C6,U+79F8,U+7AA6,U+7BC6,U+7C27,U+7CD9,U+7D0A,U+7D6E,U+7EE2,U+7EEE,U+7F24,U+7F2D,U+7F79,U+800D,U+80F3,U+80FA,U+8151,U+818A,U+81B3,U+81E7,U+82B8,U+8339,U+837B,U+8386,U+83BD,U+83CF,U+85C9,U+85D5,U+85DC,U+8747,U+8749,U+8774,U+888D,U+88F4,U+8912,U+8B6C,U+8C0E,U+8D31,U+8D48,U+8E4A,U+8F97,U+8F99,U+8FE9,U+914C,U+94A0,U+94B0,U+94F5,U+951A,U+952F,U+956D,U+95F5,U+9640,U+9668,U+968B,U+9698,U+96BD,U+96CD,U+96EF,U+9713,U+9773,U+97EC,U+9885,U+9890,U+996A,U+997A,U+9992,U+9A74,U+9A79,U+9A8B,U+9CBB,U+9CC4,U+9CCC} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/30.woff2") format("woff2");unicode-range:U+4E5E,U+4FEF,U+50A3,U+5189,U+51F3,U+51F9,U+52FF-5300,U+532E,U+533F,U+5351,U+53DB,U+53ED,U+543B,U+5455,U+54E9,U+5507,U+5543,U+5578,U+55D3,U+560E,U+5830,U+5962,U+59E8,U+59EC,U+5A07,U+5A23,U+5A3C,U+5A9A,U+5AC2,U+5AD6,U+5C2C,U+5C34,U+5CB1,U+5CE8,U+5D0E,U+5DEB,U+5E3C,U+5ED3,U+5F87,U+5FE1,U+5FF1,U+6064,U+606A,U+6096,U+60DF,U+60F9,U+6252,U+627C,U+62C2,U+62E3,U+62EF,U+634F,U+6361,U+63B7,U+63E3,U+6405,U+6495,U+6512,U+6518,U+65A9,U+65F7,U+6627,U+66AE,U+6795,U+6813,U+6893,U+68D5,U+690E,U+6977,U+6984,U+69BB,U+6A31,U+6B7C,U+6BA1,U+6C13,U+6C72,U+6C79,U+6C81,U+6C90,U+6C93,U+6CA6,U+6CBC,U+6CCC,U+6CD3,U+6CD7,U+6CFB,U+6D4A,U+6D5A,U+6D9F,U+6DA9,U+6DCC,U+6E2D,U+6EAF,U+6F13,U+6FA1,U+7011,U+701A,U+707C,U+70D8,U+71A0,U+7280-7281,U+72D0,U+7357,U+73AB,U+7410,U+745B-745C,U+7480,U+74A7,U+7578,U+7682,U+76B1,U+76CE,U+7736,U+77D7,U+77E2-77E3,U+77EB,U+780C,U+781A,U+789F,U+7941,U+7A8D,U+7A98,U+7A9C,U+7AFF,U+7B1B,U+7BF7,U+7C07,U+7CA5,U+7E82,U+7EAB,U+7EDA,U+7EDE,U+7FD8,U+7FDF,U+7FE9,U+803D,U+80AA,U+8116,U+8155,U+8205,U+821C,U+82C7,U+82DB,U+82EF,U+8304,U+832C,U+8335,U+8446,U+846B,U+8587,U+85AF,U+85FB,U+8611,U+8682,U+868A,U+868C,U+86D9,U+86EE,U+889C,U+88D8,U+88F8,U+8BBD,U+8BF5,U+8C41,U+8C79,U+8D3B-8D3C,U+8D50,U+8DEA,U+8E29,U+8E35,U+8E44,U+8EAC,U+8F8D,U+8FE2,U+9017,U+916F,U+9175-9176,U+918B,U+94CE,U+94F2,U+9541,U+9672,U+9699,U+9761,U+97A0,U+9A6D,U+9A85,U+9A8F,U+9CD7,U+9CDE,U+9E26,U+9E93,U+9F9F} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/29.woff2") format("woff2");unicode-range:U+4E4D,U+4EC7,U+4ED5,U+4F51,U+4F63,U+5018,U+5026,U+508D,U+50DA,U+50E7,U+515C,U+51A4,U+51FF,U+5203,U+5254,U+533E,U+5375,U+53EE,U+5435,U+548B,U+548F,U+54D7,U+54FA,U+5587,U+5598,U+55A7,U+575F-5760,U+576F,U+5777,U+57AE,U+58A9,U+5978,U+5A49,U+5B5A,U+5BB5,U+5BC5,U+5BDE,U+5BE1,U+5C7F,U+5CEA,U+5E18,U+5F57,U+5F5D,U+6016,U+601C,U+6021,U+606D,U+60E7,U+6177,U+61D2,U+625B,U+6296,U+62F1,U+631D,U+63A0,U+6401,U+6467,U+64BC,U+64C2,U+6577,U+6590,U+65A7,U+6643,U+6655-6656,U+6687,U+66DD,U+6714,U+67D1,U+67FF,U+6816,U+68CD,U+68D8,U+68E0,U+6930,U+6960,U+69D0,U+6BB4,U+6BD7,U+6C22,U+6C2E-6C2F,U+6C7E,U+6CA5,U+6CE3,U+6D3C,U+6DE4,U+6DF3,U+6EC7,U+6ED5,U+6F33,U+6F62,U+708A,U+70C1,U+70EB,U+70F9,U+711A,U+716E,U+7194,U+71AC,U+71E5,U+7316,U+7384,U+7405,U+7422,U+742A,U+745F,U+7574,U+75AF,U+75EA,U+763E,U+7741,U+7792,U+77A7,U+77EE,U+77F6,U+785D,U+78C5,U+7985,U+79E4,U+7A1A,U+7AD6,U+7B5D,U+7C3F,U+7C9F,U+7CDF,U+7ECE,U+7EE5,U+7F09,U+7F15,U+7F2A,U+7F38,U+7F9E,U+803B,U+804B,U+80B4,U+813E,U+817A-817B,U+819D,U+81FB,U+82AD,U+82B9,U+82DF,U+8354,U+835F,U+8367,U+83B9,U+841D,U+853D,U+8650,U+865E,U+8700,U+8862,U+88D4,U+895F,U+89C5,U+8A79,U+8BB3,U+8BB6,U+8BC0,U+8BEB,U+8C23,U+8D1E,U+8DCB,U+8E72,U+8E81,U+8EAF,U+9050,U+9091,U+90F8,U+914B,U+948A,U+9499,U+94BE,U+94EE,U+950C,U+9524,U+952D,U+9540,U+9576,U+9600,U+962E,U+9647,U+9661,U+96CF,U+9716,U+97E7,U+98A4,U+9A7C,U+9A86,U+9AE6,U+9CD6,U+9E3D} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/28.woff2") format("woff2");unicode-range:U+4E19,U+4E38,U+4EA8,U+4ED1,U+4F1E,U+4F7C,U+4F83,U+4FA0,U+4FD1,U+4FDE,U+4FFA,U+50BB,U+50F5,U+50FB,U+5162,U+5256,U+5319,U+5320,U+538C,U+5413,U+541F,U+5475,U+54BD,U+54D1,U+5589,U+561B,U+57E0,U+5937,U+5974,U+59AE,U+5A1F,U+5AB3,U+5B99,U+5B9B,U+5BA0,U+5BE5,U+5C09,U+5C27,U+5C38,U+5D16,U+5DCD,U+5DE2,U+5E90,U+5E9A,U+5F26,U+5F8A,U+5F98,U+607C,U+60BC,U+60D5,U+611A,U+614C,U+6254,U+626F,U+6292,U+62B9,U+62E2,U+62FE,U+631A,U+6320,U+6346,U+63BA,U+658B,U+663C,U+6652,U+6674,U+66D9,U+66F0,U+673D,U+67AB,U+6817,U+68A7,U+68AD,U+6986,U+6B47,U+6B49,U+6B6A,U+6C1F,U+6CBE,U+6E1D,U+6E23,U+6E9C,U+6ED4,U+6F4D,U+6F84,U+7130,U+718F,U+72FC,U+7334,U+73CA,U+7426,U+7470,U+7529,U+75D5,U+75F4,U+762B,U+7696,U+7737,U+7766,U+77BB,U+7845,U+788C,U+78CA,U+7901,U+796D,U+79B9,U+79FD,U+7A3C,U+7A57,U+7A74,U+7A9F,U+7B0B,U+7C7D,U+7CAA,U+7CB9,U+7CD5,U+7EAC,U+7EB6,U+7F20,U+7F69,U+7FA1,U+7FC5,U+8018,U+8038,U+803F,U+80A2,U+80BE,U+80D6,U+81ED,U+820C,U+829C,U+82AF,U+82BD,U+82D1,U+8327,U+836B,U+839E,U+8403,U+840C,U+840E,U+8471,U+849C,U+84B2,U+84C9,U+8517,U+851A,U+8549,U+8681,U+8721,U+8776,U+87F9,U+88D9,U+88F9,U+8C1C,U+8C34,U+8D2E,U+8D43,U+8D81,U+8D9F,U+8E0A,U+8E22,U+8EB2,U+8F69,U+901B,U+902E,U+9038,U+906E,U+90AA,U+90AF,U+90DD,U+915D,U+9171,U+946B,U+9489,U+94A7,U+9508,U+9523,U+9551,U+95F7,U+964C,U+96B6,U+96C0,U+9756,U+97F6,U+98A0,U+9965,U+997F,U+99A8,U+9A73,U+9A82,U+9AD3,U+9ED4} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/27.woff2") format("woff2");unicode-range:U+4E53,U+4E7E,U+4E9F,U+4EC6,U+4F50,U+502A,U+5154,U+517D,U+51D1,U+51F0,U+5239,U+52C9,U+52FE,U+5308,U+532A,U+535C,U+5384,U+53A2,U+53A8,U+5496,U+54AC,U+54C0,U+54C4,U+54E8,U+5561,U+5566,U+5582,U+5631,U+5764,U+576A,U+5792,U+57AB,U+584C,U+5885,U+58F6,U+5ACC,U+5BC2,U+5C39,U+5C60,U+5C6F,U+5C82,U+5C90,U+5E37,U+5EB8,U+5F6A,U+5FCC,U+6012,U+6068,U+6073,U+6094,U+6109,U+621A,U+626E,U+6284,U+62D0,U+6321,U+6328,U+632B,U+6349,U+6454,U+6591,U+65ED,U+660F,U+66A8,U+6749,U+674F,U+6760,U+67AF,U+67EF,U+6850,U+6854,U+6869,U+68A8,U+68D2,U+6912,U+6BEF,U+6C28,U+6C5D,U+6C82,U+6CAB,U+6CB8,U+6CF5,U+6D47,U+6D74,U+6D78,U+6D9D,U+6DA4,U+6DC4,U+6DCB,U+6DF9,U+6E0A,U+6E5B,U+6E89,U+6EB6,U+6F06,U+6F47,U+6F9C,U+6FD2,U+7076,U+70AC,U+70EF,U+7199,U+723D,U+72AC,U+72ED,U+7476,U+752B,U+754F,U+7554,U+7626,U+76EF,U+7720,U+7784,U+77AC,U+780D,U+7838,U+786B,U+78B3,U+7978,U+7984,U+79C9,U+79E7,U+7A3D,U+7A84,U+7B52,U+7F1A,U+7FF0,U+8086-8087,U+808C,U+809A,U+80BA,U+810A,U+8180,U+818F,U+81C2,U+8231,U+8292,U+82A6,U+8346,U+838E,U+83C7,U+83F1,U+8513,U+857E,U+859B,U+85E4,U+867E,U+871C,U+87BA,U+884D,U+8944,U+8A93,U+8C05,U+8C10,U+8D2C,U+8D42,U+8DFB,U+8EBA,U+8F9C,U+8FA3,U+8FA8,U+8FB1,U+9042,U+904F,U+90B5,U+9102,U+9187,U+94C5,U+95F8,U+95FD,U+960E,U+964B,U+96C1,U+9709,U+971C,U+97AD,U+9882,U+9976,U+9A9A,U+9B3C,U+9B41,U+9C8D,U+9E45,U+9E70,U+9E9F,U+9F3B,U+9F7F} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/26.woff2") format("woff2");unicode-range:U+4E11,U+4ED7,U+4FE9,U+4FED,U+50B2,U+5112,U+5180,U+5188,U+51F6,U+522E,U+5265,U+52CB,U+52DF,U+5349,U+5367,U+5378,U+5395,U+5398,U+53D4,U+543E,U+5446,U+54B8,U+5580,U+55BB,U+56CA,U+574E,U+5782,U+5784,U+58F3,U+5938,U+5948,U+594E,U+5A74,U+5AE9,U+5B55,U+5B5C,U+5BB0,U+5BD3,U+5D14,U+5D2D,U+5DF7,U+5DFE,U+5E05-5E06,U+5E1C,U+5E62,U+5E7B,U+5E7D,U+5ED6,U+5F2F,U+5F66,U+5F6C,U+5FA1,U+604B,U+60A6,U+60E8,U+6101,U+6124,U+6127,U+6148,U+61BE,U+6247,U+62D8,U+633D,U+635E,U+6367,U+6380,U+638F,U+63A9,U+64E6,U+655E,U+6691,U+6735,U+67A2-67A3,U+6905,U+6A0A,U+6A61,U+6B79,U+6BB7,U+6BBF,U+6C41,U+6C55,U+6C83,U+6C9B,U+6CA7,U+6CFC,U+6D46,U+6D51,U+6DAF,U+6DC0,U+6DEB,U+6E17,U+6E24,U+6EA2,U+6EF4,U+6F6D,U+70B3,U+70E4,U+710A,U+725F,U+7261,U+72EE,U+72F1,U+730E,U+732B,U+7433,U+7538,U+75BC,U+7624,U+7750,U+7779,U+7802,U+7898,U+78A7,U+78B1,U+78CB,U+78F7,U+7AED,U+7B3C,U+7B4B,U+7C98,U+7CA4,U+7EB9,U+7EE3,U+7EF3,U+7EF5,U+7F05,U+7F55,U+7FC1,U+7FD4,U+7FE0,U+8042,U+806A,U+80A0,U+80C3,U+8102,U+8106,U+814A,U+8154,U+8247,U+8258,U+8328,U+832B,U+8389,U+845B,U+846C,U+84B8,U+8574,U+8680,U+8695,U+86C7,U+8702,U+886C,U+8896,U+88B1,U+88E4,U+8BC8,U+8C26,U+8C2D,U+8D4C,U+8D63,U+8F67,U+8FC4,U+9006,U+9063,U+90A2,U+9177,U+9189,U+9493,U+949E,U+9610,U+961C,U+96A7,U+96FE,U+978D,U+97F5,U+997C,U+9A84,U+9B44-9B45,U+9B54,U+9E64,U+9F9A} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/25.woff2") format("woff2");unicode-range:U+4E18,U+4E1B,U+4E52,U+4EAD,U+4EF0,U+4FA6,U+4FCF,U+5076,U+50AC,U+5179,U+51EF,U+526A,U+529D,U+5306,U+537F,U+53D9,U+540A,U+5439,U+5440,U+54ED,U+558A,U+55B7,U+5634,U+572D,U+574A,U+57A6,U+57CB,U+5835,U+58E4,U+5951,U+5986,U+59DA,U+59FB,U+59FF,U+5A03,U+5A1C,U+5A46,U+5AC1,U+5B5D,U+5BF8,U+5C3F,U+5C48,U+5C61,U+5CB3,U+5E15,U+5E3D,U+5E99,U+5E9E,U+5F0A,U+5F17-5F18,U+5F25,U+5F7C,U+5FCD,U+6028,U+609F-60A0,U+60AC,U+614E,U+6155,U+6168,U+61C8,U+6212,U+6251,U+62AB-62AC,U+62DA,U+6323,U+63FD,U+6492,U+64B0,U+64C5,U+6572,U+6602,U+6614,U+662D,U+6664,U+6746,U+67D4,U+680B,U+682A,U+68DA,U+6B3A,U+6B67,U+6C5B,U+6C70,U+6CAA,U+6CE1,U+6D12,U+6DD1,U+6E34,U+6E7F,U+6F02,U+707F,U+7092,U+7115,U+7237,U+727A,U+72C2,U+739B,U+73B2,U+743C,U+751C,U+758F,U+75B2,U+7686,U+76C6,U+76D2,U+76FC,U+7709,U+77A9,U+788E,U+7897,U+79BD,U+7A83,U+7A91,U+7A9D,U+7AE3,U+7BAD,U+7D2B,U+7EB1,U+7F06,U+7F14,U+7F1D,U+7F50,U+7F62,U+7FFC,U+8036,U+80A4,U+80BF,U+80CE,U+80F8,U+810F,U+8179,U+819C,U+821F,U+829D,U+82CD,U+82F9,U+8305,U+83B2,U+83CA,U+8404,U+840D,U+8427,U+8679,U+8854,U+886B,U+8BBC,U+8C28,U+8C6B,U+8E48,U+8F90,U+8FB0,U+9022,U+903C,U+90B1,U+90C1,U+916C,U+917F,U+94A9,U+94C3,U+94ED,U+9510,U+953B,U+96C7,U+970D,U+9738,U+97E6,U+9877,U+987D,U+98D8,U+9A70,U+9A91,U+9B42,U+9B4F,U+9E2D,U+9E3F,U+9E7F,U+9F0E,U+9F20} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/24.woff2") format("woff2");unicode-range:U+4E08,U+4E27,U+4E59,U+4E73,U+4ED9,U+4F0F,U+4FAF,U+5085,U+5144,U+5146,U+5151,U+51AF,U+51BB,U+51C9,U+51CC,U+524A,U+5251,U+52AB,U+5353,U+5389,U+53F9,U+5415,U+541B,U+54F2,U+5524,U+5564,U+575D,U+57D4,U+5821,U+5824,U+582A,U+5858,U+5893,U+5915,U+5960,U+5999,U+59A5,U+59B9,U+59C6,U+59D1,U+59DC,U+5B64,U+5B87,U+5BB4,U+5BFA,U+5C16,U+5C18,U+5C9A,U+5CFB,U+5D1B,U+5DE1,U+5DE9,U+5ECA,U+5EF7,U+5F79,U+6052,U+6084,U+60B2,U+60DC,U+60E9,U+6208,U+6291,U+629A,U+62F3,U+632A,U+640F,U+642C,U+643A,U+6458,U+6478,U+649E,U+6500,U+654C,U+658C,U+659C,U+6676,U+6697,U+679D,U+67CF,U+67DC,U+683D,U+6843,U+684C,U+699C,U+6C0F,U+6C1B,U+6C27,U+6C64,U+6C6A,U+6CCA,U+6D3D,U+6D82,U+6DB5,U+6DEE,U+6E58,U+6ECB,U+6EE5,U+6F20,U+6F58,U+70C2,U+718A,U+7238,U+7262,U+7272,U+72B9,U+72D7,U+72E0,U+741B,U+7434,U+74F7,U+764C,U+775B,U+7761,U+7816,U+7855,U+7891,U+7A00,U+7ED2,U+7EF8,U+7FBD,U+8000,U+8096,U+809D,U+80A9,U+80C1,U+8109,U+8170,U+817F,U+81E3,U+8212,U+8230,U+8236,U+8273,U+82AC,U+8302,U+8361,U+8377,U+83CC,U+83F2,U+8461,U+8482,U+84EC,U+8521,U+8870,U+8877,U+8881,U+88AD,U+88C2,U+8BF1,U+8D24,U+8D2A,U+8D3E-8D3F,U+8D41,U+8D64,U+8DCC,U+8E0F,U+8F70,U+8FA9,U+9003,U+901D,U+903E,U+9065,U+90CE,U+94A6,U+94DD,U+94F8,U+95EA,U+95EF,U+9601,U+96D5,U+971E,U+9896-9897,U+9A7E,U+9E1F} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/23.woff2") format("woff2");unicode-range:U+4E43,U+4EA6,U+4EF2,U+4EFF,U+4F69,U+4FD7,U+4FF1,U+5021,U+5077,U+51B6,U+51DD,U+51E4,U+5200,U+523A,U+52D8,U+5401,U+54A8,U+559D,U+5609,U+5723,U+575B,U+57C3,U+5899,U+58C1,U+5976,U+5988,U+59D0,U+5A18,U+5A31,U+5B54,U+5B5F,U+5B85,U+5BD2,U+5BE8,U+5BFF,U+5C65,U+5CA9,U+5CAD,U+5D07,U+5E1D,U+5E7C,U+5F6D,U+5F70,U+5F92,U+6050,U+6108,U+6167,U+6170,U+6234,U+626D,U+62B1,U+62BC,U+62D2,U+62DC,U+6324,U+63ED,U+6447,U+64A4,U+6566,U+65C1,U+65E8,U+65EC,U+65F1,U+6606,U+6628,U+6668,U+6696,U+66F9,U+66FC,U+6717,U+6734,U+675C,U+676D,U+679A,U+67AA,U+67EC,U+67F3-67F4,U+6842,U+6851,U+6B32,U+6BC1,U+6BC5,U+6C57,U+6CEA,U+6CF3,U+6D53,U+6D66,U+6D69,U+6D9B,U+6DB2,U+6DE1,U+6EAA,U+6EDE,U+6EE8-6EE9,U+6F2B,U+704C,U+706D,U+7089,U+708E,U+70AE,U+70B8,U+71C3,U+71D5,U+7275,U+73BB,U+7483,U+74DC,U+7532,U+75AB,U+75C7,U+7687,U+76D7,U+788D,U+78E8,U+7A3B,U+7A46,U+7AF9,U+7BEE,U+7CD6,U+7EA4,U+7EB2,U+7EB5,U+7EBD,U+7F18,U+8010,U+8033,U+80C0,U+80F6,U+8138,U+81A8,U+8206,U+827E,U+82B3,U+83B1,U+848B,U+864E,U+888B,U+8BCA,U+8C0A,U+8C46,U+8D29,U+8D4F,U+8D5A,U+8D60,U+8D62,U+8D6B,U+8F7F,U+8F88,U+8F96,U+8F9E-8F9F,U+90B9,U+90CA,U+9178,U+94BB,U+94DC,U+94FA,U+9505,U+950B,U+9521,U+95F9,U+9634,U+966A,U+9675,U+9887,U+9971-9972,U+9A76,U+9A97,U+9E23} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/22.woff2") format("woff2");unicode-range:U+4E01,U+4E39,U+4E4C,U+4E54,U+4E95,U+4EA1,U+4EC1,U+4F10,U+4F26,U+4F2F,U+4F5B,U+4FA8,U+4FB5,U+4FCA,U+5192,U+51AC,U+51B0,U+51C0,U+51E1,U+51ED,U+5211,U+5242,U+52A3,U+52B2,U+52C3,U+52C7,U+52D2,U+5362,U+53A6,U+54E5,U+5510,U+5761,U+5851,U+5854,U+58A8,U+58EE,U+593A,U+5949,U+5954,U+59BB,U+59D3,U+5A5A,U+5B8B,U+5BAA-5BAB,U+5C0A,U+5C4B,U+5CE1,U+5E10,U+5E8A,U+5E9F,U+5EC9,U+5F1F,U+5FBD,U+5FE0,U+5FE7,U+6069,U+60A3,U+60A8,U+624E,U+6263,U+62D4,U+62E8,U+6350,U+63F4,U+6446,U+644A,U+6469,U+6562,U+656C,U+65D7,U+65FA,U+6653,U+676F-6770,U+67F1,U+6881,U+6885,U+68A6,U+68CB,U+68EE,U+6B20,U+6B23,U+6B96,U+6C88,U+6CC9,U+6D17,U+6D1B,U+6D59,U+6D8C,U+6E14,U+6E21,U+70AD,U+70E7,U+714C,U+7259,U+7267,U+731B,U+732A,U+73CD,U+7518,U+755C,U+756A,U+7586,U+76C8,U+76D0,U+76DF,U+76F2,U+77DB,U+795D,U+7965,U+79E6,U+79E9,U+7A3F,U+7A77,U+7B11,U+7C89,U+7EA0,U+7F34,U+7F8A,U+8015,U+8083,U+80C6,U+80DE,U+8150,U+820D,U+82D7,U+8352,U+83AB,U+8428,U+8463,U+84C4,U+852C,U+8584,U+86CB,U+88D5,U+8BDA,U+8BFA,U+8C0B,U+8C6A,U+8C8C,U+8D3A,U+8D54,U+8D74,U+8D76,U+8F9B,U+8FBD,U+8FC8,U+8FEA-8FEB,U+8FF9,U+906D,U+90A6,U+90ED,U+91CE,U+9526,U+9614,U+9655,U+9676,U+9686,U+96EA,U+9707,U+978B,U+9881,U+9E21,U+9EA6,U+9ECE,U+9F84} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/21.woff2") format("woff2");unicode-range:U+4E3D,U+4E8F,U+4EA9,U+4EEA,U+4F0A,U+4F0D,U+4F11,U+4F1F,U+4FC4,U+503A,U+507F,U+5170,U+51A0,U+520A,U+5238,U+5267,U+52E4,U+5348,U+5371,U+5385,U+539A,U+5409,U+5428,U+5434,U+547C,U+5531,U+56ED,U+5733,U+5766,U+5802,U+590F,U+591C,U+594B,U+5965,U+5987,U+5B59,U+5B63,U+5B81,U+5B97,U+5B9C,U+5BBE,U+5C1A,U+5C3C,U+5C81,U+5C9B,U+5CB8,U+5DDD,U+5E84,U+5E86,U+5EA7,U+5EAD,U+5F90,U+6000,U+60E0,U+626C,U+6276,U+6297,U+62B5,U+632F,U+6551,U+65A4,U+660C,U+671D,U+6728,U+6731,U+6768,U+6865,U+68B0,U+68C9,U+690D,U+697C,U+6B27,U+6B8B,U+6BD2,U+6BDB,U+6C49,U+6C61,U+6C7D,U+6C99,U+6CE5,U+6CF0,U+6CFD,U+6D0B,U+6D25,U+6D2A,U+6DA6,U+6DA8,U+6E29,U+6E56,U+6E7E,U+6F6E,U+6FB3,U+707E,U+70DF,U+7164,U+7389,U+73E0,U+745E,U+74E6,U+7530,U+7537,U+7597,U+75BE,U+76AE,U+76DB,U+76FE,U+7763,U+77FF,U+793C,U+7956,U+7981,U+798F,U+79CB,U+79D8,U+79DF,U+7AE5,U+7B51,U+7B79,U+7EB7-7EB8,U+7EBA,U+7F5A,U+7F6A,U+8089,U+80A5,U+80E1,U+821E,U+8239,U+8270,U+82CF,U+8336,U+8349,U+8499,U+8840,U+8857,U+8863,U+8A89,U+8D2B,U+8D2F,U+8D37,U+8D75,U+8F68,U+8F86,U+8F89,U+8FDD,U+9093,U+90D1,U+9152,U+94A2,U+9646,U+96C4,U+96E8,U+96F7,U+97E9,U+996D-996E,U+9986,U+9A7B,U+9AA8,U+9C7C,U+9C81,U+9E4F,U+9F99} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/20.woff2") format("woff2");unicode-range:U+4E03,U+4E1D,U+4E4F,U+4E5D,U+4E61,U+4E88,U+4EAC,U+4F19,U+4F24,U+4F2A,U+4FC3,U+515A,U+516B,U+5175,U+5197,U+519B-519C,U+51B7,U+5218,U+52B3,U+533B,U+5357,U+536B,U+53BF,U+53E0,U+53E4,U+53EC,U+5565,U+566A,U+5706,U+571F,U+592E,U+5939,U+594F,U+59A8,U+59D4,U+5A01,U+5B88,U+5BA3-5BA4,U+5BBF,U+5C3A,U+5C45,U+5C4A,U+5DDE,U+5E2D,U+5E78,U+5E9C,U+5EB7,U+5F04,U+5F7B,U+5FB7,U+5FD9,U+602A,U+6076,U+60CA,U+6233,U+6258,U+6270,U+62A2,U+62D6,U+62E6,U+633A,U+6355,U+63AA,U+653F,U+6597,U+65A5,U+65C5,U+65CB,U+65CF,U+6625,U+664B,U+665A,U+672B,U+6751,U+6797,U+68AF,U+68F5,U+69FD,U+6A2A,U+6B66,U+6C5F,U+6C89,U+6CB3,U+6CB9,U+6CBF,U+6CC4,U+6D01,U+6D32,U+6D45,U+6D4E,U+6D6E,U+6E20,U+6E2F,U+6E83,U+6ED1,U+706F-7070,U+70C8,U+7206,U+72AF,U+731C,U+7545,U+75C5,U+77F3,U+7A0E,U+7B5B,U+7C4D,U+7C73,U+7C97,U+7CAE,U+7CCA,U+7EAA,U+7EFF-7F00,U+7F57,U+80B2,U+80DC,U+8131,U+822A,U+8363,U+836F,U+83DC,U+85AA,U+88C1,U+8BDE,U+8BF8,U+8C31,U+8D21,U+8D35,U+8D38,U+8D5B,U+8D5E,U+8F74,U+8FC5,U+8FF7,U+900A,U+9080,U+90BB,U+94C1,U+9547,U+95FB,U+9608,U+9633,U+9644,U+9648,U+9662,U+9752,U+9769,U+98DF,U+9910,U+9970,U+9999,U+9C9C,U+9EC4,U+9F13,U+FF5E} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/19.woff2") format("woff2");unicode-range:U+251C,U+4E30,U+4E58,U+4E71,U+4E9A,U+4EAE,U+4EB2,U+4EBF,U+4ED3-4ED4,U+4F34,U+4F38,U+4F73,U+5012,U+503E,U+5065,U+516D,U+517B,U+5237,U+5269,U+5272,U+52AA,U+5317,U+534E,U+5377,U+53C9,U+53F6,U+5403,U+5410,U+541E,U+5438,U+5448,U+54B1,U+554A,U+5708,U+573E,U+5750-5751,U+575A,U+5783,U+57CE,U+57F9,U+586B,U+58EB,U+592B,U+5956,U+5973,U+5979,U+5A92,U+5B69,U+5B8F,U+5BB3,U+5C71,U+5C97,U+5CF0,U+5D29,U+5DF4,U+5E55,U+5E97,U+5F31,U+5F39,U+5F69,U+5FC6,U+5FD8,U+5FFD,U+6015,U+6025,U+6070,U+60D1,U+6298,U+629B,U+62D3,U+62F7,U+62FC,U+6316,U+6398,U+6491,U+65E7,U+661F,U+6682,U+66F2,U+6740,U+674E,U+6750,U+6876,U+6B4C,U+6BCD,U+6BEB,U+6C11,U+6C38,U+6CBB,U+6CE2,U+6D3E,U+6D6A,U+6DD8,U+6EDA,U+6F5C,U+70BC,U+70E6,U+725B,U+732E,U+738B,U+73ED,U+75DB,U+78B0,U+7A0D,U+7A7F,U+7B14,U+7BB1,U+7C92,U+7EB3,U+7ED1,U+7EEA,U+7EFC,U+8026,U+8058,U+80A1,U+817E,U+826F,U+827A,U+82E5-82E6,U+8457,U+84DD,U+85CF,U+866B,U+8B66,U+8BAF,U+8BD7,U+8C37,U+8D22,U+8D27,U+8D34,U+8D8B,U+8DC3,U+8F85,U+9014,U+9057,U+9075,U+90AE,U+9192,U+9274,U+94AE,U+94F6,U+95F2,U+96C5,U+987F,U+9888,U+989C,U+98DE,U+9EBB,U+9F50} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/18.woff2") format("woff2");unicode-range:U+4E22,U+4E94,U+4ECD,U+4F17,U+4FA7,U+500D,U+5141,U+514B,U+517C,U+526F,U+52B1,U+5343,U+5356,U+535A,U+5382,U+53F2,U+552E-552F,U+5584,U+56F0,U+56FA,U+574F,U+5947,U+5B9D,U+5BA1,U+5BBD,U+5BC4,U+5BCC,U+5BFB,U+5C14,U+5C24,U+5C3E,U+5C4F,U+5D4C,U+5DE7-5DE8,U+5E01,U+5E27,U+5E45,U+5F03,U+5F8B,U+6062,U+6089,U+60EF,U+613F,U+61C2,U+622A,U+626B,U+6279,U+6293,U+62C5,U+62C9,U+62DB,U+62E5,U+6302,U+6325,U+635F,U+6377,U+6388,U+641E,U+6444,U+653B,U+654F,U+6563,U+65AF,U+65E6,U+6670,U+66B4,U+66FE,U+677E,U+6821,U+68B3,U+6B3E,U+6B7B,U+6B8A,U+6C14,U+6C60,U+6D1E,U+6D77,U+6DF7,U+6E10,U+6EE4,U+6F0F,U+6FC0,U+706B,U+7126,U+722C,U+7231,U+7236,U+724C,U+73A9,U+7403,U+74F6,U+7533,U+767B,U+76D6,U+7701,U+773C,U+77E9,U+7834,U+78C1,U+7968,U+79C0-79C1,U+7A97,U+7ADE-7ADF,U+7D27,U+7D2F,U+7E41,U+7EA2,U+7EAF,U+7ED5,U+7ED8,U+7EE9,U+7FFB,U+82F1,U+878D,U+8986,U+8C01,U+8C13,U+8C22,U+8D1D,U+8D26,U+8D4B,U+8DD1,U+8DDD,U+8DE8,U+8E2A,U+8F6E,U+8FC1,U+8FDF,U+8FFD,U+9000,U+900F,U+94A5,U+955C,U+9632,U+9635,U+963B,U+963F,U+9677,U+9690,U+96F6,U+9732,U+9988} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/17.woff2") format("woff2");unicode-range:U+2500,U+2502,U+4E16,U+4E1C,U+4E25,U+4E34,U+4E45,U+4E4E,U+4E50,U+4E70,U+4E89,U+4F30,U+4F4F,U+4F59,U+501F,U+504F,U+505C,U+513F,U+5145,U+5149,U+5174,U+518C,U+51B2,U+5220,U+523B,U+5339,U+534A,U+5360-5361,U+5370,U+5374,U+53CC,U+53F3,U+5427,U+5473,U+54C8,U+559C,U+5806,U+585E,U+5B98,U+5BDF,U+5C01,U+5C04,U+5C1D,U+5DE6,U+5E72,U+5EF6,U+5F84,U+6162,U+620F,U+623F,U+627F,U+62BD,U+62C6,U+62CD,U+62FF,U+6311,U+6389,U+63A2,U+641C,U+642D,U+64AD,U+64CE,U+6559,U+6599,U+65BD,U+65E2,U+65E9,U+6620,U+666E,U+66FF,U+6708,U+67D3,U+6B62,U+6BD5,U+6C47,U+6C9F,U+6CDB,U+6D4F,U+6D89,U+6DFB,U+6E32,U+6F14,U+7075,U+70ED,U+7591,U+767D-767E,U+76CA,U+793E,U+795E,U+79D1-79D2,U+7A33,U+7A81,U+7B7E,U+7EC7,U+7EDD,U+7F29,U+7F8E,U+8017,U+805A,U+80AF,U+8111,U+811A,U+8425,U+843D,U+8861,U+8865,U+897F,U+89C8,U+89E6,U+8BA2,U+8BC9,U+8C08,U+8D25,U+8D2D,U+8D70,U+8DA3,U+8DF3,U+8F66,U+8F7B,U+8FED,U+9010,U+904D,U+949F,U+94B1,U+95ED,U+9669,U+9694,U+9759,U+9760,U+9876,U+987E,U+989D,U+9A6C,U+9A71,U+9AA4,U+9ED1,U+9ED8} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/16.woff2") format("woff2");unicode-range:U+4E07,U+4E32,U+4E3E,U+4E66,U+4E91,U+4ED8,U+4EFD,U+4F01,U+5047,U+514D,U+5178,U+51CF,U+51FB,U+5207,U+5212,U+521A,U+5224,U+529E,U+52BF,U+5341,U+5386,U+538B,U+53CB,U+53D7,U+53EB,U+5417,U+542C,U+5468,U+56DB,U+56F4,U+56FD,U+5740,U+5747,U+58F0,U+592A,U+5957,U+5BC6,U+5C3D,U+5C40,U+5E02,U+5F20,U+5F52,U+5F81,U+5F85,U+5FAA,U+5FD7,U+5FF5,U+6218,U+6269,U+6295,U+62A4-62A5,U+62DF,U+62EC,U+638C,U+63CF,U+63D2,U+63E1,U+6545,U+667A,U+670B,U+671B,U+672A,U+675F,U+677F,U+6781,U+6808,U+680F,U+6811,U+6863,U+695A,U+6C34,U+6E38,U+6EE1,U+719F,U+72EC,U+751A,U+7535,U+753B,U+76D1,U+76D8,U+77ED,U+7814,U+786C,U+79EF-79F0,U+79FB,U+7A76,U+7AD9,U+7CBE,U+7EA6,U+7EC3,U+7EE7,U+7F3A,U+7F72,U+7FA4,U+8001,U+804A,U+804C,U+80CC,U+822C,U+82B1,U+8303,U+8350,U+8651,U+865A,U+867D,U+8BA8,U+8BAD,U+8BB8,U+8BC4,U+8BE6,U+8D23,U+8D56,U+8D85,U+8DF5,U+8FB9,U+8FD1,U+8FDC,U+9012,U+9047,U+907F,U+91CA,U+91D1,U+9500-9501,U+9605,U+964D,U+969C,U+97F3,U+987A-987B,U+98CE} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/15.woff2") format("woff2");unicode-range:U+4E13,U+4E92,U+4EC5,U+4ECB,U+4EF7,U+4F3C,U+4F4E,U+4F9D,U+4FBF,U+4FEE,U+5171,U+521D,U+5229,U+52A9,U+5347,U+5373,U+53C8,U+53CD,U+53E5-53E6,U+53F7,U+542B,U+542F,U+544A,U+5546,U+5883,U+5931,U+5934,U+5B89,U+5C5E,U+5DEE,U+5E08,U+5E0C,U+5E2E,U+5E7F,U+5E95,U+5F02,U+5F3A,U+5F62,U+5F71,U+5F80,U+5FAE,U+5FC5,U+5FEB,U+62E9,U+6309,U+6392,U+65E5,U+6613,U+663E,U+6742-6743,U+67D0,U+6846,U+68C0,U+6982,U+6B22,U+6D3B,U+6DF1,U+6E05,U+7167,U+7248,U+7269,U+754C,U+7559,U+7565,U+771F,U+7840,U+79BB,U+7A7A,U+7ACB,U+7AE0,U+7B54,U+7B56,U+7BC7,U+7D20,U+7EC6,U+7EC8,U+7ECD,U+7F13,U+8054,U+81F3,U+8272,U+88C5,U+89C1-89C2,U+89C9,U+89D2,U+8BBF,U+8BCD,U+8BD1,U+8BE2,U+8BEF,U+8D1F,U+8D28,U+8D39,U+8D8A,U+8DB3,U+8DDF,U+8EAB,U+8F6F,U+8F7D,U+8FCE,U+8FD4,U+8FDE,U+8FF0,U+9001-9002,U+901F-9020,U+903B,U+91C7,U+9488,U+94FE,U+9519,U+95E8,U+9636,U+9650,U+968F,U+96BE,U+9875,U+9884,U+9886,U+9891,U+9996} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/14.woff2") format("woff2");unicode-range:U+4E09,U+4E14,U+4E8C,U+4EA4,U+4EAB,U+4ECA,U+4EE4,U+4EFB,U+4F20,U+4F4D,U+4F9B,U+4FDD,U+50A8,U+50CF,U+5143,U+51B5,U+51C6,U+51E0,U+5217,U+521B,U+534F,U+53E3,U+53F0,U+53F8,U+5404,U+540D,U+5426,U+5458,U+547D,U+54CD,U+54EA,U+56E2,U+573A,U+5757,U+57DF,U+589E,U+5907,U+591F,U+59CB,U+5BA2,U+5BB6,U+5BFC,U+5C11,U+5C55,U+5DF2,U+5E03,U+5E26,U+5E73-5E74,U+5F15,U+5F55,U+600E,U+603B,U+611F,U+624B,U+624D,U+6253,U+627E,U+6301,U+6362,U+63A7-63A8,U+652F,U+6536,U+6539,U+653E,U+65AD,U+65E0,U+660E,U+666F,U+671F,U+6761,U+6790,U+6838-6839,U+683C,U+6848,U+6D88,U+7247,U+72B6,U+7387,U+73AF,U+7531,U+7740,U+7B26,U+7B80,U+7D22,U+7EA7,U+7EDC,U+7EED,U+7EF4,U+81F4,U+83B7,U+89C4,U+89C6,U+8BA4,U+8BAE,U+8BB0,U+8BBA,U+8BC1,U+8BC6,U+8BDD,U+8BF7,U+8BFB,U+8D44,U+8DEF,U+8F6C,U+8F83,U+8F91,U+8F93,U+8FBE,U+952E,U+957F,U+9645,U+9664,U+9A8C} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/13.woff2") format("woff2");unicode-range:U+4E0E,U+4E3B,U+4E49,U+4E60,U+4EA7,U+4EC0,U+4ED6,U+4F18,U+4F55,U+4FE1,U+5019,U+5148,U+5168,U+516C,U+5177,U+518D,U+5199,U+51B3,U+51FD,U+5219,U+522B,U+5236,U+529B,U+529F,U+5305,U+533A,U+539F,U+53BB,U+53C2,U+53CA,U+53D6,U+5411,U+5462,U+54C1,U+56DE,U+57FA,U+5904,U+590D,U+5916,U+5929,U+5B50,U+5B57,U+5B8C,U+5C06,U+5C0F,U+5C42,U+5DF1,U+5E93,U+5FC3,U+6001,U+601D,U+606F,U+60C5,U+60F3,U+610F,U+6216,U+6267,U+6280,U+6307,U+64CD,U+6548,U+6574,U+672F,U+67B6,U+67E5,U+6B21,U+6B63-6B65,U+6BB5,U+6BCF,U+6CE8,U+6D41,U+6D4B,U+6E90,U+7279,U+76F4,U+77E5,U+786E,U+793A,U+7AEF,U+7BA1,U+7EBF,U+7EC4,U+7F16,U+7F51,U+7F6E,U+8003,U+88AB,U+8A00,U+8BA9,U+8BB2,U+8BE5,U+8BED,U+8BFE,U+8C61,U+8D77,U+8FD0,U+9009,U+9053,U+914D,U+961F,U+96C6,U+975E,U+9879,U+9AD8} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/12.woff2") format("woff2");unicode-range:U+4E1A,U+4E24,U+4E4B,U+4E8B,U+4EBA,U+4ECE,U+4F46,U+4F53,U+4F7F,U+4F8B,U+503C,U+505A,U+5165,U+5173,U+5176,U+5185,U+524D,U+52A0,U+52A8,U+5316,U+5355,U+53D8,U+53EA,U+5408,U+5668,U+56E0,U+56FE,U+5730,U+578B,U+597D,U+5B58,U+5B66,U+5B83,U+5BB9,U+5DE5,U+5E38,U+5E76,U+5E8F,U+5E94,U+5EA6,U+5EFA,U+5F00,U+5F53,U+5F88,U+5F97,U+6237,U+628A,U+63D0,U+6587,U+65B0,U+66F4,U+6700,U+670D,U+672C,U+673A,U+6784,U+6807,U+6837,U+6A21,U+6BD4,U+6C42,U+6CA1,U+7136,U+751F,U+76EE,U+76F8,U+79CD,U+7B2C,U+7B49,U+7B97,U+7C7B,U+7CFB,U+7ECF,U+7ED3,U+7ED9,U+7EDF,U+8005,U+81EA,U+8282,U+8868,U+8BA1,U+8BBE,U+8BD5,U+8BF4,U+8C03,U+8FD8,U+90A3,U+90E8,U+91CC-91CD,U+91CF,U+95F4} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/11.woff2") format("woff2");unicode-range:U+4E00,U+4E0A-4E0B,U+4E0D,U+4E2A,U+4E2D,U+4E3A,U+4E48,U+4E5F,U+4E86,U+4E8E,U+4E9B,U+4EE3,U+4EE5,U+4EEC,U+4EF6,U+4F1A,U+4F5C,U+4F60,U+51FA,U+5206,U+5230,U+52A1,U+53D1,U+53EF,U+540C,U+540E,U+548C,U+5728,U+591A,U+5927,U+5982,U+5B9A,U+5B9E,U+5BF9,U+5C31,U+5F0F,U+6027,U+6210-6211,U+6240,U+636E,U+63A5,U+6570,U+65B9,U+65F6,U+662F,U+6709,U+6765,U+679C,U+6CD5,U+70B9,U+73B0,U+7406,U+7528,U+7684,U+770B,U+7801,U+7A0B,U+800C,U+80FD,U+884C,U+8981,U+89E3,U+8FC7,U+8FD9,U+8FDB,U+901A,U+90FD,U+95EE,U+9700,U+9762,U+9898} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/10.woff2") format("woff2");unicode-range:U+2026,U+3001-3002,U+300A-300B,U+FF01,U+FF08-FF09,U+FF0D,U+FF1A-FF1B,U+FF1F,U+FF5C} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/9.woff2") format("woff2");unicode-range:U+409,U+40F,U+418,U+41F,U+427-428,U+42B} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/8.woff2") format("woff2");unicode-range:U+3CD,U+1F45} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/7.woff2") format("woff2");unicode-range:U+374-375,U+37E,U+384-385,U+387,U+389-38A,U+38F-390,U+393-394,U+39E,U+3A3,U+3A6,U+3A8-3A9,U+3AC,U+3B0-3B1,U+3B3,U+3B6,U+3B8,U+3BA,U+3BD-3BE,U+3C0,U+3C2-3C4,U+3C7,U+3CB,U+3D1,U+3D6,U+402-403,U+40A,U+40E,U+411,U+413-414,U+423-424,U+426,U+429,U+42C,U+431,U+433-434,U+436-437,U+43A,U+442,U+446,U+449-44A,U+44C,U+453,U+459-45A,U+45C,U+462-463,U+46A-46B,U+474-475,U+490-491,U+493,U+497,U+49B,U+49D,U+4A2-4A3,U+4B0-4B1,U+4B6-4B7,U+4BA,U+4CA,U+4EE} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/6.woff2") format("woff2");unicode-range:U+259,U+2B0,U+2B2-2B3,U+2B7-2B8,U+2BB-2BC,U+2C6-2C7,U+2C9-2CB,U+2D8-2DD,U+2E1-2E3} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/5.woff2") format("woff2");unicode-range:U+18A,U+192} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/4.woff2") format("woff2");unicode-range:U+135,U+140} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/3.woff2") format("woff2");unicode-range:U+F0,U+F7} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/2.woff2") format("woff2");unicode-range:U+A0-AA,U+AC-B1,U+B4-BF,U+C3,U+D7} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/1.woff2") format("woff2");unicode-range:U+7B-7F} +@font-face{font-family:"MiSans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/demibold/0.woff2") format("woff2");unicode-range:U+0-1,U+A,U+D,U+20-7A,U+AB,U+B2-B3,U+C0-C2,U+C4-D6,U+D8-EF,U+F1-F6,U+F8-134,U+136-13F,U+141-17F,U+181,U+18F,U+198-199,U+1A0-1A1,U+1AF-1B0,U+1B3-1B4,U+1B7,U+1CD-1DC,U+1E4-1E9,U+1EE-1F0,U+1F9-1FF,U+218-21B,U+21E-21F,U+237,U+251,U+253,U+257,U+261,U+292,U+386,U+388,U+38C,U+38E,U+391-392,U+395-39D,U+39F-3A1,U+3A4-3A5,U+3A7,U+3AA-3AB,U+3AD-3AF,U+3B2,U+3B4-3B5,U+3B7,U+3B9,U+3BB-3BC,U+3BF,U+3C1,U+3C5-3C6,U+3C8-3CA,U+3CC,U+3CE,U+3D2-3D4,U+400-401,U+404-408,U+40B-40D,U+410,U+412,U+415-417,U+419-41E,U+420-422,U+425,U+42A,U+42D-430,U+432,U+435,U+438-439,U+43B-441,U+443-445,U+447-448,U+44B,U+44D-452,U+454-458,U+45B,U+45D-45F,U+472-473,U+492,U+49A,U+49C,U+4AE-4AF,U+4B2-4B3,U+4B8-4B9,U+4BB,U+4D8-4D9,U+4E2-4E3,U+4E8-4E9,U+4EF,U+1E00-1E01,U+1E3E-1E3F,U+1E80-1E85,U+1E9E,U+1EA0-1EF9,U+2013-2014,U+2018-2019,U+201C-201D,U+2044,U+2070,U+2074-2079,U+2080,U+2082-2089,U+20A8,U+FF0C} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/188.woff2") format("woff2");unicode-range:U+FE51-FE52,U+FE54-FE57,U+FE59-FE66,U+FE68-FE6B,U+20087,U+20089,U+200CC,U+20164,U+20676,U+20CD0,U+2139A,U+21413,U+215D7,U+2298F,U+235CB,U+23C97-23C98,U+23E23,U+241FE,U+249DB,U+24A7D,U+24AC9,U+25532,U+25562,U+255A8,U+25ED7,U+26221,U+2648D,U+26676,U+2677C,U+26B5C,U+26C21,U+27FF9,U+28408,U+28678,U+28695,U+287E0,U+28B49,U+28C47,U+28C4F,U+28C51,U+28C54,U+28E99,U+29F7E,U+29F83,U+29F8C,U+2A7DD,U+2A8FB,U+2A917,U+2AA30,U+2AA36,U+2AA58,U+2AFA2,U+2B127-2B128,U+2B137-2B138,U+2B1ED,U+2B300,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+2B626-2B628,U+2B62A,U+2B62C,U+2B695-2B696,U+2B6AD,U+2B6ED,U+2B7A9,U+2B7C5,U+2B7E6,U+2B7F9,U+2B7FC,U+2B806,U+2B80A,U+2B81C,U+2B8B8,U+2BAC7,U+2BB5F,U+2BB62,U+2BB7C,U+2BB83,U+2BC1B,U+2BD77,U+2BD87,U+2BDF7,U+2BE29,U+2C029-2C02A,U+2C0A9,U+2C0CA,U+2C1D5,U+2C1D9,U+2C1F9,U+2C27C,U+2C288,U+2C2A4,U+2C317,U+2C35B,U+2C361,U+2C364,U+2C488,U+2C494,U+2C497,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+2C8D9,U+2C8DE,U+2C8E1,U+2C8F3,U+2C907,U+2C90A,U+2C91D,U+2CA02,U+2CA0E,U+2CA7D,U+2CAA9,U+2CB29,U+2CB2D-2CB2E,U+2CB31,U+2CB38-2CB39,U+2CB3B,U+2CB3F,U+2CB41,U+2CB4A,U+2CB4E,U+2CB5A-2CB5B,U+2CB64,U+2CB69,U+2CB6C,U+2CB6F,U+2CB73,U+2CB76,U+2CB78,U+2CB7C,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+2CE18,U+2CE1A,U+2CE23,U+2CE26,U+2CE2A,U+2CE7C,U+2CE88,U+2CE93} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/187.woff2") format("woff2");unicode-range:U+9F00-9F08,U+9F0A,U+9F0C,U+9F0F,U+9F11-9F12,U+9F14-9F16,U+9F18,U+9F1A-9F1F,U+9F21,U+9F23-9F2B,U+9F2D-9F2E,U+9F30-9F33,U+9F35-9F36,U+9F38,U+9F3A,U+9F3C,U+9F3F-9F43,U+9F45-9F49,U+9F4C-9F4D,U+9F53,U+9F55-9F58,U+9F5A-9F5B,U+9F5D-9F5E,U+9F62-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-9FEF,U+F92C,U+F979,U+F995,U+F9E7,U+F9F1,U+FA0C-FA0F,U+FA11,U+FA13-FA14,U+FA18,U+FA1F-FA21,U+FA23-FA24,U+FA27-FA29,U+FE10-FE19,U+FE30-FE31,U+FE33-FE44,U+FE49-FE50} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/186.woff2") format("woff2");unicode-range:U+9D7F-9D86,U+9D88,U+9D8A-9D92,U+9D94-9D97,U+9D99,U+9D9B-9DA4,U+9DA6-9DA8,U+9DAA-9DAE,U+9DB0-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-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+9ED2-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} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/185.woff2") format("woff2");unicode-range:U+9C0C,U+9C0E-9C11,U+9C14-9C1F,U+9C21-9C22,U+9C24,U+9C26-9C27,U+9C2A-9C2C,U+9C2E-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-9D7E} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/184.woff2") format("woff2");unicode-range:U+9AC9-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-9B07,U+9B09-9B0E,U+9B10-9B12,U+9B14-9B1E,U+9B20-9B21,U+9B24,U+9B26,U+9B28,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-9C0B} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/183.woff2") format("woff2");unicode-range:U+9925-9927,U+9929-992D,U+992F-9930,U+9932,U+9934-9936,U+9938-993B,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-9AC8} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/182.woff2") format("woff2");unicode-range:U+9789-978A,U+978C,U+978E,U+9790,U+9793,U+9795-9797,U+9799-979C,U+979E-979F,U+97A1-97A2,U+97A4-97AA,U+97AC,U+97AE,U+97B0-97B1,U+97B3,U+97B5-97BC,U+97BE-97C2,U+97C4-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-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-98B5,U+98B7-98BB,U+98BD-98C3,U+98C5,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-98F1,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-9924} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/181.woff2") format("woff2");unicode-range:U+95C2,U+95C4-95C7,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-96BB,U+96BF,U+96C2-96C3,U+96C8,U+96CA,U+96D0-96D1,U+96D3-96D4,U+96D7-96D8,U+96DA,U+96DD,U+96DF,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-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-9788} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/180.woff2") format("woff2");unicode-range:U+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-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+95A4,U+95A6-95A7,U+95AA,U+95AE-95B0,U+95B2-95B5,U+95B7-95B8,U+95BA,U+95C0-95C1} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/179.woff2") format("woff2");unicode-range:U+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-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-9394} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/178.woff2") format("woff2");unicode-range: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-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-91E6,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-9213,U+9216-921D,U+921F-9222,U+9224,U+9228-922C} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/177.woff2") format("woff2");unicode-range: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+8F13,U+8F16-8F1A,U+8F20-8F24,U+8F27-8F28,U+8F2B-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+8FF4-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-902C,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+905D,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} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/176.woff2") format("woff2");unicode-range: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+8E30,U+8E32-8E34,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} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/175.woff2") format("woff2");unicode-range:U+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-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+8C8D-8C92,U+8C95-8C97,U+8C99-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,U+8CDF,U+8CE5,U+8CE8-8CE9} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/174.woff2") format("woff2");unicode-range:U+896E,U+8970-8971,U+8973-897A,U+897C-897E,U+8980,U+8982,U+8984-8985,U+8987-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-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+8A3B-8A3D,U+8A3F-8A40,U+8A42-8A45,U+8A47,U+8A49-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-8A8C,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} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/173.woff2") format("woff2");unicode-range:U+8818-881A,U+881C-881E,U+8820,U+8824-8830,U+8833-8835,U+8837-8838,U+883A,U+883D-883F,U+8841-8843,U+8847-884B,U+884E-8852,U+8855-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+88FD,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-8948,U+894A-8955,U+8957-895C,U+8961-8963,U+8965,U+8967-8969,U+896B-896D} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/172.woff2") format("woff2");unicode-range:U+8699-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+8716,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} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/171.woff2") format("woff2");unicode-range:U+8540,U+8542,U+8544-8545,U+8547,U+854B-854D,U+854F-8551,U+8553-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-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-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-8698} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/170.woff2") format("woff2");unicode-range:U+83DA-83DB,U+83DE,U+83E2-83E4,U+83E6-83E8,U+83EB-83EE,U+83F3-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,U+848D,U+848F-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-84C8,U+84CC,U+84CE-84CF,U+84D2,U+84D4-84D5,U+84D7-84DC,U+84DE,U+84E1-84E2,U+84E4,U+84E7-84EB,U+84ED,U+84F1-84F3,U+84F5-84FB,U+84FE,U+8500-850B,U+850D-8510,U+8512,U+8514-8516,U+8518-8519,U+851B-851D,U+8520,U+8522,U+8524,U+8527-852A,U+852E-8536,U+853E-853F} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/169.woff2") format("woff2");unicode-range: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-8278,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-83D3,U+83D5,U+83D7,U+83D9} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/168.woff2") format("woff2");unicode-range:U+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-812A,U+812D-812E,U+8130,U+8133-8135,U+8137,U+813A-813D,U+813F-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+8181,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-81B2,U+81B4-81B9,U+81BC,U+81C4-81C5,U+81C7-81C8,U+81CB,U+81CE,U+81D0-81D7,U+81D9,U+81DB-81DF,U+81E1-81E2,U+81E4,U+81E6,U+81E9,U+81EB,U+81EE-81F2,U+81F5-81F9,U+81FD,U+81FF,U+8203} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/167.woff2") format("woff2");unicode-range:U+7E4F-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,U+7E8B,U+7E8D-7E8E,U+7E90-7E92,U+7E94-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-7FED,U+7FEF,U+7FF2,U+7FF4-7FF8,U+7FFA,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-8050} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/166.woff2") format("woff2");unicode-range:U+7CE4-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-7D2E,U+7D34-7D38,U+7D3B,U+7D3D-7D3E,U+7D41,U+7D43,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-7D92,U+7D94-7D9B,U+7D9D,U+7D9F,U+7DA1,U+7DA4-7DA5,U+7DA7-7DAB,U+7DAF,U+7DB3,U+7DB5-7DB7,U+7DBC,U+7DC0-7DC3,U+7DC5-7DC6,U+7DC8-7DC9,U+7DCC-7DD1,U+7DD3-7DD6,U+7DDB-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-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+7E48-7E4E} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/165.woff2") format("woff2");unicode-range: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-7C15,U+7C17-7C1D,U+7C20,U+7C22,U+7C24-7C25,U+7C28-7C29,U+7C2C-7C37,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-7C71,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,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-7CE3} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/164.woff2") format("woff2");unicode-range:U+7A29-7A2D,U+7A2F-7A30,U+7A32,U+7A34-7A36,U+7A38,U+7A3A,U+7A3E,U+7A40-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-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} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/163.woff2") format("woff2");unicode-range:U+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+791F-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-79A6,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-7A1D,U+7A21-7A22,U+7A24-7A28} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/162.woff2") format("woff2");unicode-range:U+7772-7778,U+777A-777C,U+7781-7783,U+7786-778B,U+778F-7790,U+7793-7797,U+7799-779D,U+77A1,U+77A3-77A4,U+77A6,U+77A8,U+77AB,U+77AD-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-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,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-78D7} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/161.woff2") format("woff2");unicode-range:U+75E5,U+75E9,U+75EC-75EF,U+75F2-75F3,U+75F5-75F8,U+75FA-75FB,U+75FD,U+7604,U+7606-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-7653,U+7655,U+7657,U+7659-765B,U+765D,U+7660,U+7665-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+76C3-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-7700,U+7702-7703,U+7705-7706,U+770A,U+770C,U+770E-7718,U+771B-771E,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-774F,U+7752-7759,U+775D,U+775F-7760,U+7764,U+7767,U+7769-776A,U+776D-7771} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/160.woff2") format("woff2");unicode-range:U+747A-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-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-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-75E1} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/159.woff2") format("woff2");unicode-range:U+7332-7333,U+7335,U+733A,U+733C-733D,U+7340,U+7342-7343,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-739E,U+73A0-73A1,U+73A3-73A8,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-744A,U+744C-7454,U+7456,U+7458,U+745D,U+7460-7462,U+7465-7468,U+746B-746C,U+746E-746F,U+7471-7475,U+7478-7479} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/158.woff2") format("woff2");unicode-range:U+71CC-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+7260,U+7263-7265,U+7268,U+726A-726D,U+7270-7271,U+7273-7274,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} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/157.woff2") format("woff2");unicode-range:U+7090-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+7146-7148,U+714B,U+714D,U+714F-7151,U+7153-7158,U+715A-715B,U+715D,U+715F-7161,U+7163,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-71CB} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/156.woff2") format("woff2");unicode-range:U+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-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-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} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/155.woff2") format("woff2");unicode-range:U+6DD4-6DD5,U+6DD7,U+6DDB-6DDC,U+6DDF,U+6DE2-6DE3,U+6DE7,U+6DE9,U+6DED,U+6DEF-6DF0,U+6DF2,U+6DF4,U+6DF8,U+6DFD-6E04,U+6E06-6E09,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-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-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-6F49} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/154.woff2") format("woff2");unicode-range:U+6C37,U+6C39-6C3C,U+6C3E-6C3F,U+6C43-6C45,U+6C48,U+6C4B-6C4F,U+6C51-6C53,U+6C56,U+6C58-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-6CDD,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-6D29,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-6DD3} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/153.woff2") format("woff2");unicode-range:U+6AD1-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-6BAD,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} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/152.woff2") format("woff2");unicode-range: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-69EC,U+69EE-69F1,U+69F4-69FC,U+69FE,U+6A00,U+6A03-6A04,U+6A06-6A09,U+6A0B-6A12,U+6A14-6A16,U+6A1A-6A1D,U+6A20,U+6A22,U+6A24-6A27,U+6A29,U+6A2B-6A2E,U+6A30,U+6A32-6A34,U+6A36-6A37,U+6A3B-6A3C,U+6A3F-6A43,U+6A45-6A46,U+6A49-6A4A,U+6A4C-6A4F,U+6A51-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-6AC2,U+6AC4-6AD0} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/151.woff2") format("woff2");unicode-range:U+6879-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+68CA,U+68CC,U+68CE-68D1,U+68D3-68D4,U+68D9,U+68DB-68DE,U+68E1-68E2,U+68E4-68E6,U+68E8-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} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/150.woff2") format("woff2");unicode-range: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+6736-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-67FC,U+67FE,U+6801-6804,U+6806,U+680D,U+6810,U+6812,U+6814-6815,U+6818-681C,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} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/149.woff2") format("woff2");unicode-range:U+65AE,U+65B1-65B6,U+65B8,U+65BA-65BB,U+65BE-65C0,U+65C2,U+65C7-65CA,U+65CD,U+65D0-65D1,U+65D3-65D5,U+65D8-65DF,U+65E1,U+65E3-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} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/148.woff2") format("woff2");unicode-range:U+6438-6439,U+643B-643C,U+643E,U+6440,U+6442-6443,U+6449,U+644B-6450,U+6453,U+6455-6457,U+6459-645B,U+645D,U+6460-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+649A-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+64E1,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,U+653A,U+653C-653D,U+6540-6544,U+6546-6547,U+654A-654B,U+654D-654E,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} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/147.woff2") format("woff2");unicode-range: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+62DD-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-6331,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+6368,U+636A,U+636C,U+636F-6370,U+6372-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+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+63F9-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-6425,U+6427-6429,U+642B,U+642E-6433,U+6435,U+6437} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/146.woff2") format("woff2");unicode-range:U+613B-613D,U+6140-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-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,U+6235,U+6238-623C,U+6242,U+6244-6246,U+624A,U+624F-6250,U+6255-6257,U+6259-625A,U+625C-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} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/145.woff2") format("woff2");unicode-range: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-613A} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/144.woff2") format("woff2");unicode-range:U+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+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+5F14,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+5F5E-5F60,U+5F63,U+5F67-5F68,U+5F6B,U+5F6E-5F6F,U+5F72,U+5F74-5F76,U+5F78,U+5F7A,U+5F7D-5F7F,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} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/143.woff2") format("woff2");unicode-range:U+5D0C,U+5D0F-5D13,U+5D15,U+5D18-5D1A,U+5D1C-5D1D,U+5D1F-5D21,U+5D23,U+5D25,U+5D28,U+5D2A-5D2B,U+5D2F-5D33,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-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+5DE3-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} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/142.woff2") format("woff2");unicode-range:U+5B91-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-5C5B,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-5CE0,U+5CE2-5CE3,U+5CE7,U+5CE9,U+5CEB-5CEC,U+5CEE,U+5CF1-5CF3,U+5CF5,U+5CF7-5CFA,U+5CFC,U+5CFE-5D01,U+5D04-5D05,U+5D08-5D0B} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/141.woff2") format("woff2");unicode-range:U+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-5B4B,U+5B4D-5B4F,U+5B52,U+5B56,U+5B5E,U+5B60-5B61,U+5B67-5B68,U+5B6D-5B6F,U+5B72,U+5B74,U+5B76-5B77,U+5B79,U+5B7B-5B7C,U+5B7E,U+5B82,U+5B86,U+5B8A,U+5B8D-5B8E,U+5B90} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/140.woff2") format("woff2");unicode-range:U+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+59B3-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+59E6-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,U+5A4B-5A54,U+5A56-5A59,U+5A5B-5A61,U+5A63-5A65,U+5A68} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/139.woff2") format("woff2");unicode-range:U+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-5856,U+5859-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} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/138.woff2") format("woff2");unicode-range:U+56A4-56A5,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-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+5770-5772,U+5774-5775,U+5778-577A,U+577D-5781,U+5787-578A,U+578D-5791,U+5794-579A,U+579C-579F,U+57A5,U+57A8,U+57AA,U+57AC,U+57AF-57B1,U+57B3,U+57B5-57B7,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-57F2} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/137.woff2") format("woff2");unicode-range:U+5536,U+5538-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-5586,U+558C-558E,U+5590,U+5592-5593,U+5595-5597,U+559B,U+559E,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-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+5694,U+5696-56A2} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/136.woff2") format("woff2");unicode-range: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+5451,U+545A,U+545D-5461,U+5463,U+5465,U+5467,U+5469-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+54E2-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+5515-551A,U+551C-551F,U+5521,U+5525-5526,U+5528-5529,U+552B,U+552D,U+5532,U+5534-5535} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/135.woff2") format("woff2");unicode-range: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+524B,U+524F,U+5252-5253,U+5255,U+5257-525A,U+525F-5260,U+5262-5264,U+5266,U+5268,U+526B-526D,U+5270-5271,U+5273,U+5276-527C,U+527E,U+5280,U+5284-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+5379,U+537C-537E} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/134.woff2") format("woff2");unicode-range:U+509E-50A2,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-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+5101,U+5103,U+5105,U+510A,U+510C-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+516A,U+516F,U+5172,U+517A,U+517E-517F,U+5183-5184,U+5186-5187,U+518B,U+518E-5191,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} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/133.woff2") format("woff2");unicode-range: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+4FB7-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-4FEC,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-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+5078-507A,U+507C-507D,U+5081-5084,U+5086-5087,U+5089-508C,U+508E-5090,U+5092-5095,U+5097,U+509A-509D} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/132.woff2") format("woff2");unicode-range:U+4D8C-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-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+4F48-4F4C,U+4F52,U+4F56} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/131.woff2") format("woff2");unicode-range:U+4CD2-4D8B} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/130.woff2") format("woff2");unicode-range:U+4C19-4CD1} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/129.woff2") format("woff2");unicode-range:U+4B62-4C18} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/128.woff2") format("woff2");unicode-range:U+4AAC-4B61} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/127.woff2") format("woff2");unicode-range:U+49F7-4AAB} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/126.woff2") format("woff2");unicode-range:U+4943-49F6} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/125.woff2") format("woff2");unicode-range:U+4891-4942} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/124.woff2") format("woff2");unicode-range:U+47E0-4890} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/123.woff2") format("woff2");unicode-range:U+4730-47DF} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/122.woff2") format("woff2");unicode-range:U+4682-472F} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/121.woff2") format("woff2");unicode-range:U+45D5-4681} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/120.woff2") format("woff2");unicode-range:U+452A-45D4} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/119.woff2") format("woff2");unicode-range:U+4480-4529} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/118.woff2") format("woff2");unicode-range:U+43D7-447F} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/117.woff2") format("woff2");unicode-range:U+4330-43D6} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/116.woff2") format("woff2");unicode-range:U+428A-432F} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/115.woff2") format("woff2");unicode-range:U+41E6-4289} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/114.woff2") format("woff2");unicode-range:U+4144-41E5} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/113.woff2") format("woff2");unicode-range:U+40A3-4143} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/112.woff2") format("woff2");unicode-range:U+4004-40A2} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/111.woff2") format("woff2");unicode-range:U+3F66-4003} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/110.woff2") format("woff2");unicode-range:U+3ECA-3F65} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/109.woff2") format("woff2");unicode-range:U+3E30-3EC9} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/108.woff2") format("woff2");unicode-range:U+3D98-3E2F} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/107.woff2") format("woff2");unicode-range:U+3D02-3D97} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/106.woff2") format("woff2");unicode-range:U+3C6D-3D01} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/105.woff2") format("woff2");unicode-range:U+3BDA-3C6C} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/104.woff2") format("woff2");unicode-range:U+3B49-3BD9} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/103.woff2") format("woff2");unicode-range:U+3ABA-3B48} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/102.woff2") format("woff2");unicode-range:U+3A2D-3AB9} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/101.woff2") format("woff2");unicode-range:U+39A3-3A2C} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/100.woff2") format("woff2");unicode-range:U+391B-39A2} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/99.woff2") format("woff2");unicode-range:U+3895-391A} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/98.woff2") format("woff2");unicode-range:U+3811-3894} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/97.woff2") format("woff2");unicode-range:U+3790-3810} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/96.woff2") format("woff2");unicode-range:U+3712-378F} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/95.woff2") format("woff2");unicode-range:U+3696-3711} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/94.woff2") format("woff2");unicode-range:U+361D-3695} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/93.woff2") format("woff2");unicode-range:U+35A7-361C} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/92.woff2") format("woff2");unicode-range:U+3534-35A6} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/91.woff2") format("woff2");unicode-range:U+34C4-3533} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/90.woff2") format("woff2");unicode-range:U+3458-34C3} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/89.woff2") format("woff2");unicode-range:U+3226-3229,U+3231,U+32A3,U+338E-338F,U+339C-339E,U+33A1,U+33C4,U+33CE,U+33D1-33D2,U+33D5,U+3400-3457} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/88.woff2") format("woff2");unicode-range: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-3009,U+300C-3017,U+301D-301E,U+3021-3029,U+303E,U+3105-3129,U+3220-3225} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/87.woff2") format("woff2");unicode-range:U+2555-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+2713,U+2715,U+275B-275E,U+2780-2793} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/86.woff2") format("woff2");unicode-range:U+248F-249B,U+2501,U+2503-251B,U+251D-254B,U+2550-2554} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/85.woff2") format("woff2");unicode-range:U+2192-2193,U+2196-2199,U+21C4,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+223C-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-248E} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/84.woff2") format("woff2");unicode-range:U+207A-207B,U+207F,U+2081,U+20A3-20A4,U+20A9,U+20AB-20AC,U+20AE,U+20B1,U+20B8-20BA,U+20BD,U+20BF,U+2103,U+2105,U+2109,U+2113-2114,U+2116-2117,U+211E,U+2120-2122,U+2126,U+212E,U+2153-2154,U+215B-215E,U+2160-2179,U+2180-2182,U+2190-2191} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/83.woff2") format("woff2");unicode-range:U+2004-2006,U+2009-200A,U+2010,U+2015-2016,U+201A,U+201E,U+2020-2022,U+2025,U+2028-2029,U+2030-2037,U+2039-203C,U+203E,U+2042-2043,U+2047-2049,U+204E,U+2051,U+2055,U+2060-2064,U+2066-2069,U+2071} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/82.woff2") format("woff2");unicode-range:U+300-304,U+306-30C,U+312,U+323,U+326-328,U+1D43,U+1D47-1D49,U+1D4D,U+1D4F-1D50,U+1D52,U+1D56-1D58,U+1D5B,U+1D9C,U+1DA0,U+1DBB} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/81.woff2") format("woff2");unicode-range:U+FF3B-FF5B,U+FF5D,U+FFE0-FFE5} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/80.woff2") format("woff2");unicode-range:U+FF02-FF07,U+FF0A-FF0B,U+FF0E-FF19,U+FF1C-FF1E,U+FF20-FF3A} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/79.woff2") format("woff2");unicode-range:U+E3F} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/78.woff2") format("woff2");unicode-range:U+30EA-30FA,U+30FC-30FE} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/77.woff2") format("woff2");unicode-range:U+3089-3094,U+3099-309E,U+30A1-30E9} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/76.woff2") format("woff2");unicode-range:U+3041-3088} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/75.woff2") format("woff2");unicode-range:U+5244,U+5666,U+568C,U+58DA,U+5C68,U+5E58,U+5F33,U+61CD,U+6214,U+6516,U+69E7,U+6BFF,U+7020,U+7044,U+7798,U+78FD,U+7E6F-7E70,U+802E,U+8079,U+8183,U+8464,U+861E,U+8811,U+8A92,U+8B56,U+8CB0,U+8CD5,U+8CE7,U+8E93,U+8EA6,U+8EE4,U+8F62,U+9233,U+92DD,U+9301,U+9307,U+9346,U+9370,U+93AA,U+93F7,U+940B,U+9412,U+9426,U+958C,U+95AB,U+95BD,U+95BF,U+986C,U+9A2D,U+9A38,U+9B9A,U+9BEB,U+9BF4,U+9C12,U+9C20,U+9C35,U+9C37,U+9C5D,U+9D1D,U+9DA5,U+9F60} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/74.woff2") format("woff2");unicode-range:U+50E8,U+532D,U+53B4,U+54BC,U+5672,U+56C5,U+5852,U+5B24,U+5B4C,U+5D2C,U+5DF9,U+5E6C,U+5F12,U+649F,U+6504,U+68D6,U+69BF,U+6AEB,U+6AF3,U+6E5E,U+736B,U+7380,U+7649,U+79B0,U+7A6D,U+7AB6,U+7C5C,U+7D06,U+7D32,U+7DE6,U+7DF6,U+7DF9,U+7E0B,U+7E36,U+7E52,U+7E62,U+7F8B,U+8156,U+8264,U+826B,U+8526,U+8546,U+8558,U+8577,U+85F6,U+87EF,U+8806,U+89AF,U+89BF,U+8A01,U+8A75,U+8A84,U+8AD7,U+8B7E,U+8B9E,U+8DB2,U+8E60,U+8EFA,U+8F05,U+8F07,U+91D9,U+923D,U+9255,U+929A,U+92B1,U+92DF,U+92E3,U+92E6,U+92F6,U+9306,U+9308,U+9364,U+9369,U+9384,U+93BF,U+93C7,U+9414,U+9428,U+9479,U+95E5,U+9689,U+97BD,U+97DD,U+9859,U+98EB,U+9937,U+993C,U+99D4,U+99D8,U+9A0D,U+9A36,U+9A42,U+9B9E,U+9BAA,U+9BDD,U+9C08,U+9C29,U+9C3E,U+9D1F,U+9D2F,U+9D42,U+9D6F,U+9D93,U+9DE5,U+9E0C,U+9E7A,U+9EF2,U+9F09} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/73.woff2") format("woff2");unicode-range:U+50B4,U+5110,U+51AA,U+528C,U+52F1,U+56B3,U+5969,U+5ABC,U+6192,U+61AE,U+61DF,U+6207,U+6451,U+69AA,U+6A48,U+6AE7,U+6F59,U+6F77,U+6FA9,U+6FFC,U+700B,U+7027,U+7197,U+736A,U+7602,U+761E,U+7667,U+76B8,U+7C69-7C6A,U+7CDD,U+7CF4,U+7CF6,U+7D07,U+7D86,U+7DC4,U+7DD9,U+7E11,U+7E2D,U+7E32,U+7E8A,U+7F86,U+8161,U+81DA,U+81E0,U+8452,U+851E,U+8552,U+8562,U+859F,U+86FA,U+87E3,U+8949,U+89A1,U+89F6,U+8AD1,U+8B6B,U+8B8E,U+8D10,U+8EF9,U+8F0A,U+91C3,U+9201,U+9225,U+92A9,U+92CF,U+9312,U+931F,U+9343,U+9387,U+93F9,U+9420,U+944A,U+9465,U+980A,U+980F,U+9862,U+9931,U+9933,U+994A,U+995C,U+9A4F,U+9B83,U+9B92,U+9BC1,U+9C39,U+9C5F,U+9C67,U+9C6D,U+9C7A,U+9D30,U+9D53,U+9D5C,U+9D87,U+9D98,U+9DBC,U+9DD9-9DDA,U+9DE6,U+9DEF,U+9ECC,U+9EF7,U+9F34,U+9F4E-9F4F,U+9F54,U+9F59} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/72.woff2") format("woff2");unicode-range:U+50C9,U+5274,U+5635,U+5638,U+56A6,U+5B00,U+5B19,U+5B2A,U+5D81,U+5DF0,U+5E43,U+5EE1,U+6128,U+616A,U+6227,U+651B,U+6898,U+6ADE,U+6AE8,U+6C0C,U+7026,U+733B,U+74A6,U+74D4,U+766D,U+7725,U+7868,U+78B8,U+7BCB,U+7CF2,U+7CF9,U+7D83,U+7D88,U+7DB0,U+7DC7,U+7DE1,U+7DF1,U+84EF,U+84F4,U+84FD,U+863A,U+8810,U+889E,U+8933,U+895D,U+8A4E,U+8A58,U+8A7F,U+8A86,U+8A91,U+8AC2,U+8ACF,U+8ADE,U+8AE1,U+8AFC,U+8B92,U+8B96,U+8CB2,U+8CFE,U+8E92,U+8EDB,U+8F1C,U+8F61,U+90DF,U+9227,U+9230,U+9248,U+927F,U+9283,U+92A0,U+92A3,U+92A6,U+92AA,U+92F1,U+9338,U+9340,U+93CD,U+93DD,U+9413,U+943F,U+944C,U+9454,U+946D,U+95BC,U+95C8,U+979D,U+97C9,U+9807,U+9826,U+98AE,U+98BC,U+98E0,U+9A05,U+9AD5,U+9B4E,U+9BC0,U+9BC7,U+9BD7,U+9BE1,U+9BEA,U+9C23,U+9C28,U+9C33,U+9D52,U+9DF3,U+9F5C} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/71.woff2") format("woff2");unicode-range:U+5000,U+5114-5115,U+526E,U+5399,U+53C4,U+55F6,U+5665,U+566F,U+581D,U+584F,U+58E2,U+6173,U+61E8,U+645C,U+6476,U+6894,U+6A89,U+6ADD,U+6AF8,U+6B0F,U+6B5F,U+6B7F,U+6FAE,U+7341,U+7658,U+7670,U+7906,U+7C00,U+7C1E,U+7D3C,U+7D4E,U+7D5D,U+7D9E,U+7DA3,U+7E35,U+7E7E,U+7E98,U+802C,U+8407,U+8494,U+8541,U+8555,U+87BB,U+8A6C,U+8A9A,U+8AE2,U+8B05,U+8B59,U+8B9C,U+8CBA,U+8CFB,U+8D04,U+8E55,U+8E91,U+8F1E,U+8F54,U+9134,U+9136,U+91D3-91D5,U+91E4,U+91F7,U+9204,U+9266,U+926D,U+92E8,U+92EE,U+931B,U+93A6,U+93A9,U+93C3,U+93CC,U+93E8,U+9403,U+9419,U+9582,U+95C3,U+95CB,U+95D0,U+984E,U+9853,U+9873,U+9908,U+99D1,U+99D9,U+9A43-9A44,U+9B29,U+9B2E,U+9B74,U+9B90,U+9BD6,U+9BFF,U+9C13,U+9C45,U+9D07,U+9DC0,U+9DC2,U+9DF8,U+9EFD,U+9EFF,U+9F76} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/70.woff2") format("woff2");unicode-range:U+4FC1,U+5096,U+5108,U+565D,U+56B6,U+56C0-56C1,U+56C8,U+5816,U+58D9,U+5B21,U+5D0D,U+5EE9,U+614D,U+64FC,U+6A05,U+6A81,U+6AA3,U+6AEC,U+6B1E,U+6BA4,U+6BAE,U+6DE5,U+6F35,U+6F7F,U+7032,U+7162,U+716C,U+743A,U+74DA,U+7646,U+7669,U+7864,U+78E7,U+7A61,U+7B67,U+7BF3,U+7D08,U+7D3A,U+7D3F,U+7D5B,U+7DB9,U+7E0A,U+7E1E-7E1F,U+7FB6,U+8435,U+85CE,U+85EA,U+861A,U+8784,U+87F6,U+8A0C,U+8A10,U+8AB6,U+8ADB,U+8AE4,U+8B04,U+8B16,U+8B95,U+8CB3,U+8E95,U+8ED4,U+8EEB,U+8F26,U+8F33,U+8F64,U+9015,U+9090,U+91C5,U+91E7,U+9200,U+9239,U+923F,U+927A,U+92AB,U+9319,U+9477,U+958E,U+95B6,U+95B9,U+9768,U+97D9,U+980E,U+9874,U+98ED,U+9949,U+994C,U+99E2,U+9ACF,U+9B58,U+9B8E,U+9BAB,U+9BE2,U+9C09,U+9C48,U+9CE7,U+9DBB,U+9E15,U+9E1D,U+9F66,U+9F6A,U+9F77} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/69.woff2") format("woff2");unicode-range:U+50AF,U+513B,U+528A,U+561C,U+5707,U+584B,U+58CE,U+5950,U+5AA7,U+5B0B,U+5CF4,U+5DA0,U+5EC4,U+613E,U+6229,U+64A3,U+64B3,U+64C4,U+66D6,U+690F,U+69E8,U+6A9C,U+6AD3,U+6AF1,U+6B9E,U+6C2C,U+6C33,U+7028,U+7043,U+7069,U+7296,U+7319,U+74A3,U+75FE,U+7664,U+76B0,U+77BC,U+78E3,U+7D02,U+7D13,U+7D15,U+7DBE,U+7E88,U+81CF,U+82BB,U+858C,U+8604,U+875F,U+87C8,U+8960,U+8998,U+89B2,U+89F4,U+8A15,U+8A41,U+8AA5,U+8B2B,U+8CDA,U+8E89,U+8E9A,U+8EA1,U+8EA5,U+8F42,U+9106,U+91F9,U+9210-9211,U+922E,U+9238,U+924D,U+925E,U+9278,U+92BC,U+92C3,U+9358,U+93A7,U+93B0,U+93B5,U+93D1,U+97C3,U+9865,U+993F,U+9948,U+9962,U+9A16,U+9A41,U+9BAD,U+9BDB,U+9BE4,U+9BE7,U+9C25,U+9C56,U+9C78,U+9CF6,U+9D06,U+9D9A,U+9DD3,U+9E1B,U+9F5F,U+9F67,U+9F6C} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/68.woff2") format("woff2");unicode-range:U+50C2,U+5137,U+57E1,U+58E0,U+5AD7,U+5B08,U+5DA7,U+5DCB,U+60F2,U+60FB,U+615F,U+616B,U+61CC,U+61E3,U+636B,U+64EF,U+689F,U+6ADF,U+6AEA,U+6F1A,U+71DC,U+71F4,U+7370,U+763B,U+7931,U+7A4C,U+7C23,U+7D31,U+7DCB,U+7DD7-7DD8,U+7DF2,U+7E39,U+7FA5,U+83A2,U+83A7,U+866F,U+8706,U+87C4,U+87CE,U+8814,U+8831,U+8938,U+893B,U+8964,U+89A6,U+89AC,U+8A03,U+8A16,U+8A46,U+8A52,U+8A5B,U+8A85,U+8ACD,U+8AED,U+8B14,U+8B3E,U+8B4E,U+8E4C,U+8EAA,U+8EF2,U+8EFB,U+9148,U+91F5,U+9245,U+9315,U+9347,U+93DC,U+9410,U+9427,U+9463,U+9481,U+95AC,U+95DE,U+9658,U+973D,U+9744,U+97DE,U+981C,U+9821,U+9837,U+9870,U+98B6,U+98F4,U+9909,U+9911,U+9A0F,U+9A2E,U+9A64,U+9BF0,U+9C0D,U+9C2D,U+9C31,U+9C52,U+9CE9,U+9D23,U+9D6A,U+9DF2,U+9EA9,U+9F95} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/67.woff2") format("woff2");unicode-range:U+513A,U+5504,U+55C7,U+560D,U+5660,U+5680,U+5695,U+56A8,U+580A,U+597C,U+5B7F,U+6134,U+61AB,U+61FA,U+64BB,U+6506,U+6523,U+66B1,U+66C7,U+6D79,U+6DF6,U+6FA0,U+7005,U+71FC,U+7258,U+72FD,U+75D9,U+765F,U+78AD,U+79C8,U+7A1F,U+7C2B,U+7C5F,U+7C6E,U+7CC9,U+7D40,U+7DAC,U+7E09,U+7E10,U+8075,U+8594,U+85BA,U+8768,U+87A2,U+8823,U+89B7,U+8A1B,U+8A36,U+8A54,U+8A61,U+8A70,U+8AEB,U+8AF6,U+8CC5,U+8CE1,U+8D17,U+8EFE,U+91D2,U+91E9,U+9249,U+9262,U+92BB,U+92C7,U+92E5,U+92EF-92F0,U+9341,U+9354,U+9376,U+937A,U+9398,U+93A3,U+93E4,U+93F5,U+943A,U+9460,U+947E,U+95A1,U+95AD,U+95D4,U+9742,U+975A,U+9766,U+98AF,U+98C6,U+98C8,U+98E9,U+991B,U+991E,U+9A4D,U+9AD6,U+9B81,U+9BCA,U+9BFD,U+9D61,U+9D89,U+9E1A,U+9F72} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/66.woff2") format("woff2");unicode-range:U+4F47,U+5102,U+5157,U+55E9,U+562E,U+5630,U+5879,U+5ACB,U+5AF5,U+5B38,U+5C5C,U+5D97,U+5DB8,U+5EDD,U+60B5,U+611C,U+619A,U+6384,U+64FB,U+66C4,U+6A62,U+6ADA,U+6B12,U+6BAB,U+6ECE,U+6F6F,U+705D,U+70F4,U+71C9,U+71FE,U+729B,U+72A2,U+7489,U+750C,U+7515,U+766C,U+769A,U+792B-792C,U+7B8B,U+7C0D,U+7D73,U+7E1D,U+7E6E,U+7F4C,U+81BE,U+8477,U+849E,U+84C0,U+854E,U+85FA,U+8622,U+865C,U+86FB,U+8778,U+8956,U+8A6D,U+8B01,U+8B10,U+8B28,U+8CC1,U+8D05,U+8E7A,U+8F45-8F46,U+8FAE,U+913A,U+91C1,U+91FA,U+9208,U+920D,U+9251,U+925A,U+926C,U+9291,U+92A5,U+92CC,U+942E,U+9432,U+9586,U+958F,U+95CC,U+95D5,U+9830,U+9839,U+9957,U+995E,U+99DF,U+99ED,U+9A2B,U+9A3E,U+9AAF,U+9C54,U+9D1B,U+9D26,U+9D60,U+9DA9} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/65.woff2") format("woff2");unicode-range:U+4E99,U+50E5,U+513C,U+55C6,U+5616,U+5653,U+56EA,U+59CD,U+5D22,U+5D87,U+6137,U+615A,U+645F,U+6473,U+64F7,U+6519,U+6595,U+67F5,U+6AB3,U+6AB8,U+6ABB,U+6EF7-6EF8,U+6F41,U+6F97,U+6FD8,U+6FFA,U+7377,U+737A,U+737C,U+744B,U+74BD,U+74CF,U+7627,U+792A,U+79B1,U+7D46,U+7E43,U+7E45,U+7E93,U+7F88,U+811B,U+81BF,U+81CD,U+8396,U+858A,U+880D,U+8A25,U+8A6B,U+8A6E,U+8A7C,U+8AB9,U+8AC9,U+8AFA,U+8B17,U+8B2C,U+8B4F,U+8CD2,U+8D16,U+8E63,U+8E8A,U+8EFC,U+8F25,U+9114,U+9116,U+9183,U+9226,U+9237,U+923E,U+927B,U+9296,U+92AC,U+9310,U+932E,U+93A2,U+93D7-93D8,U+93DE,U+93E2,U+990C,U+9912,U+993E,U+9943,U+99B1,U+9A40,U+9A5B,U+9A6A,U+9B22,U+9B77,U+9BC9,U+9C58,U+9D15,U+9D51,U+9DFA,U+9E1E} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/64.woff2") format("woff2");unicode-range:U+55B2,U+55DA,U+58AE,U+5A6D,U+5AFA,U+5BE2,U+5DD2,U+5DD4,U+5FA0,U+60BD,U+618A,U+633E,U+64F0,U+6582-6583,U+6727,U+69F3,U+6ADB,U+6AE5,U+6C08,U+6D87,U+6E19,U+6E26,U+6F2C,U+7006,U+7018,U+71BE,U+743F,U+760D,U+7647,U+7662,U+7672,U+76DE,U+798E,U+79BF,U+7ABA,U+7BE4,U+7C6C,U+7D1C,U+7D33,U+7D79,U+7DBB,U+7DBD,U+7DDE,U+7E08,U+7E55,U+82E7,U+85F9,U+87EC,U+8805,U+8A63,U+8AA3,U+8AA8,U+8AC4,U+8ADC,U+8AE6,U+8AF3,U+8B33,U+8CD1,U+8CE4,U+8D0D,U+8F12,U+8F3E,U+923A,U+9257,U+9293,U+92A8,U+92E4,U+9333,U+9365,U+936C,U+93AC,U+93B3,U+942B,U+9433,U+9594,U+95A8,U+9695,U+96CB,U+9824,U+98EA,U+9903,U+9921,U+99B4,U+9A01,U+9A62,U+9A65,U+9BE8,U+9C32,U+9C77,U+9D72,U+9DAF,U+9DD7} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/63.woff2") format("woff2");unicode-range:U+4FB6,U+51DC,U+52FB,U+5331,U+5436,U+5614,U+562F,U+56C2,U+5862,U+5A41,U+5B0C,U+5C37,U+5E57,U+61FE,U+63C0,U+6417,U+64BF,U+64F2,U+6522,U+652A,U+65AC,U+66E0,U+6968,U+6A3A,U+6AFB,U+6B16,U+6BAF,U+6BB2,U+6D36,U+6DEA,U+6F23,U+6F80,U+6FC1,U+7009,U+7064,U+7152,U+71C1,U+71ED,U+7463,U+7469,U+7621,U+7681,U+76BA,U+77EF,U+786F,U+7A08,U+7AC4-7AC5,U+7AC7,U+7D09,U+7D5E,U+7D62,U+7DBA,U+7E5A,U+7E7D,U+7FF9,U+8123,U+81A9,U+8466,U+879E,U+883B,U+896A,U+8A1D,U+8AA6,U+8AF7,U+8B0A,U+8CBD,U+8CCA,U+8CDC,U+8F1F,U+8F4D,U+9087,U+9209,U+92F8,U+9328,U+9382,U+93DF,U+9438,U+97DC,U+9871,U+991A,U+9945,U+99AD,U+99D2,U+99FF,U+9A4A,U+9BD4,U+9C3B,U+9C57,U+9D09,U+9F9C} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/62.woff2") format("woff2");unicode-range:U+4FE0,U+50AD,U+53AD,U+555E,U+589C,U+58B3,U+5DBC,U+6065,U+6190,U+61F6,U+61FC,U+6493,U+64BE,U+64F1,U+650F,U+6688-6689,U+66EC,U+68F2,U+6BC6,U+6C2B,U+6FF0,U+701D,U+7192,U+71D9,U+71FB,U+720D,U+7587,U+760B,U+7613,U+7661,U+766E,U+775C,U+779E,U+78EF,U+79AA,U+7A62,U+7AAA,U+7B8F,U+7C3E,U+7CDE,U+7D8F,U+7DDD,U+7DEF,U+7E37,U+7E46,U+7E6D,U+7E79,U+8073,U+807E,U+8588,U+863F,U+87FB,U+881F,U+8993,U+8A23,U+8A60,U+8AA1,U+8AF1,U+8B20,U+8B41,U+8B74,U+8C4E,U+8C9E,U+8EC0,U+9132,U+919E,U+91D7-91D8,U+9223,U+9240,U+92C5,U+9318,U+931A,U+9320,U+934D,U+93FD,U+9472,U+947F,U+95A5,U+96B4,U+96DB,U+97CC,U+985B,U+986B,U+99DD,U+99F1,U+9C49,U+9D3F} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/61.woff2") format("woff2");unicode-range:U+4F96,U+5098,U+50D5,U+524E,U+5687,U+56D1,U+582F,U+588A,U+58D8,U+58FA,U+5BF5,U+5C4D,U+5EC2,U+5EDA,U+5EEC,U+60B6,U+60F1,U+61C7,U+646F,U+647B,U+64CB,U+665D,U+6953,U+69D3,U+6A01,U+6DF5,U+6E4A,U+6ECC,U+6F86-6F87,U+7015,U+701F,U+703E,U+72F9,U+7378,U+7464,U+7671,U+797F,U+798D,U+7AC8,U+7B4D,U+7DB8,U+7E1B,U+7E8F,U+7F75,U+7FA8,U+814E,U+81D8,U+8259,U+834A,U+8525,U+852D,U+856A,U+8606,U+8766,U+8AD2,U+8AE7,U+8B0E,U+8C48,U+8CAF,U+8CB6,U+8CC2,U+8D13,U+8E8B,U+8ED2,U+919C,U+91AC,U+921E,U+925B,U+938A,U+947C,U+9598,U+95A9,U+95BB,U+96B8,U+980C,U+98E2,U+9913,U+9952,U+99C1,U+9A37,U+9B91,U+9D5D,U+9DF9,U+9EF4,U+9F4B,U+9F52} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/60.woff2") format("woff2");unicode-range:U+5006,U+5109,U+5147,U+525D,U+52F3,U+58DF,U+599D,U+5B30,U+5CA1,U+5D84,U+5E25,U+5E5F,U+5EC1,U+5F4E,U+5F65,U+6085,U+6158,U+61A4,U+6200,U+6488,U+652C,U+68D7,U+68DF,U+6A1E,U+6BBC,U+6E3E,U+6EC4,U+6EF2,U+6F3F,U+6F51,U+6FB1,U+71E6,U+7344-7345,U+7375,U+775E,U+7AAF,U+7ACA,U+7C60,U+7CB5,U+7D0B,U+7D17,U+7DBF,U+7DEC,U+7E61,U+7E69,U+7F77,U+8070,U+8076,U+8178,U+819A,U+81E5,U+8332,U+84BC,U+860A,U+8755,U+8836,U+8932,U+896F,U+8A50,U+8A87,U+8B19,U+8B39,U+8B5A,U+8C93,U+8CED,U+8D1B,U+8ECB,U+91D0,U+91E3,U+9214,U+9298,U+95E1,U+9727,U+97CB,U+97FB,U+9811,U+9905,U+9A0E,U+9A55,U+9B31,U+9DB4,U+9E79,U+9E7C,U+9F94} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/59.woff2") format("woff2");unicode-range:U+5075,U+50F1,U+51CD,U+51F1,U+528D,U+52F8,U+53E2,U+5442,U+559A,U+55AA,U+5606,U+5674,U+5857,U+5875,U+58BE,U+58E9,U+5C62,U+5D50,U+5DBD,U+5EDF,U+5F4C,U+6046,U+61F8,U+6399,U+64AB,U+64B2,U+651C,U+6558,U+6DBC,U+6E6F,U+6EBC,U+6EEC,U+6FEB,U+7051,U+7165,U+721B,U+723A,U+72A7,U+7336,U+746A,U+74CA,U+77DA,U+78A9,U+78DA,U+7AA9,U+7D68,U+7DA2,U+7DE0,U+7E2B,U+7E9C,U+8105,U+8108,U+816B,U+8266,U+84EE,U+856D,U+860B,U+8A1F,U+8A98,U+8C54,U+8CC3-8CC4,U+8F3B,U+8F5F,U+8FAF,U+9059,U+91C0,U+9234,U+9264,U+929C,U+92B3,U+92C1,U+935B,U+9444,U+9583,U+95A3,U+9803,U+98C4,U+99B3,U+9AD2,U+9D28,U+9D3B,U+9F90} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/58.woff2") format("woff2");unicode-range:U+514C,U+53B2,U+58C7,U+58FD,U+5A1B,U+5ABD,U+5DBA,U+5DD6,U+61F2,U+6416,U+64E0,U+6575,U+69CD,U+6A38,U+6AC3,U+6B3D,U+6BC0,U+6DDA,U+6EC5,U+6EEF,U+6FC3,U+6FE4,U+6FF1,U+7058,U+7210,U+7246,U+727D,U+76DC,U+7919,U+7A4E,U+7C43,U+7C72,U+7D10,U+7DB1,U+7DE3,U+7E31,U+7E96,U+8056,U+8139,U+81A0,U+81C9,U+840A,U+8523,U+8569,U+8972,U+8A3A,U+8ABC,U+8AEE,U+8CA9-8CAA,U+8CC8,U+8CDE,U+8CE2,U+8CFA,U+8D08,U+8D0F,U+8F29,U+8F3F,U+8F44,U+8F4E,U+8FAD,U+9112,U+9285,U+92D2,U+934B,U+947D,U+95D6,U+95E2,U+9670,U+978F,U+9817,U+9846,U+98FC-98FD,U+99AE,U+99D5,U+99DB,U+9A19,U+9B27,U+9CE5,U+9CF4} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/57.woff2") format("woff2");unicode-range:U+502B,U+5091,U+50D1,U+5100,U+511F,U+5291,U+52C1,U+55AC,U+58EF,U+5922,U+596A,U+5B6B,U+5BAE,U+5CFD,U+5E33,U+5EC8,U+5EE2,U+6182,U+6191,U+61B2,U+61F7,U+64A5,U+64FA,U+6524,U+66C9,U+6B98,U+6DE8,U+6E67,U+6F01,U+6F32,U+6F64,U+70CF,U+71D2,U+7240,U+76E7,U+7955,U+79AE,U+7AAE,U+7CFE,U+7D21,U+7E73,U+8085,U+81BD,U+838A,U+85A9,U+8AA0,U+8AFE,U+8B00,U+8C6C,U+8CC0,U+8CD3,U+8CE0,U+8D95,U+8D99,U+8DE1,U+8ECC,U+8F1B,U+8F1D,U+907C,U+9081,U+912D,U+92EA,U+9326,U+932B,U+95CA,U+965D,U+96DE,U+9812,U+98F2,U+99D0,U+9B5A,U+9B6F,U+9CF3,U+9D6C,U+9E7D,U+9E97,U+9EA5,U+9F61} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/56.woff2") format("woff2");unicode-range:U+5049,U+50B5,U+5287,U+5289,U+52DE,U+5433,U+5678,U+5712,U+5967,U+596E,U+5A66,U+5BE7,U+5C46,U+5CF6,U+5EF3,U+6176,U+63DA,U+6514,U+694A,U+69AE,U+6A13,U+6A4B,U+6B50,U+6B72,U+6DFA,U+6EAB,U+6F22,U+6FA4,U+7063,U+707D,U+7159,U+755D,U+7642,U+7926,U+7A05,U+7BC9,U+7BE9,U+7C4C,U+7CE7,U+7D00,U+7D19,U+7D1B,U+7D72,U+7DA0,U+7E23,U+7F70,U+8271,U+85E5,U+8607,U+862D,U+8667,U+885B,U+8B7D,U+8CA7,U+8CAB,U+8CB8,U+8CBF,U+8FB2,U+9055,U+9109,U+9127,U+91AB,U+92FC,U+93AE,U+9435,U+9673,U+9678,U+967D,U+97D3,U+98EF,U+9928,U+9A5A,U+9B25,U+9EE8,U+9F8D} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/55.woff2") format("woff2");unicode-range:U+4E9E,U+50B7,U+50BE,U+50DE,U+5104,U+52DD,U+5713,U+593E,U+5CEF,U+5D17,U+5FB9,U+60E1,U+62CB,U+6436,U+6490,U+64FE,U+6649,U+66A2,U+6A6B,U+6F54,U+6F5B,U+6F70,U+6FDF,U+7149,U+7169,U+71C8,U+734E,U+737B,U+758A,U+7D0D,U+7D9C,U+7DB4,U+7DD2,U+7F85,U+805E,U+812B,U+83EF,U+8449,U+85CD,U+85DD,U+89AA,U+8A0A,U+8A17,U+8A69,U+8A95,U+8AF8,U+8B5C,U+8CA1-8CA2,U+8CB4,U+8CBC,U+8CFD,U+8D0A,U+8E8D,U+8ECD,U+8EF8,U+8F14,U+905C,U+90F5,U+9130,U+9215,U+9280,U+9451,U+9592,U+95BE,U+9813,U+98DB,U+98FE,U+9A30,U+9BAE,U+9EC3,U+9F4A} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/54.woff2") format("woff2");unicode-range:U+4E1F,U+4E82,U+5009,U+5074,U+52F5,U+55AB,U+5805,U+58DE,U+5BE9,U+5BEC,U+5BF6,U+5C0B,U+5E40,U+5E63,U+5F48,U+611B,U+6163,U+61B6,U+6383,U+639B,U+63EE,U+640D,U+64C1,U+64D4,U+651D,U+66AB,U+68C4,U+6BBA,U+6C23,U+6EFE,U+6F38,U+6FFE,U+723E,U+7B46,U+7D05,U+7D14,U+7D81,U+7DCA,U+7E3E,U+7E5E,U+7E6A,U+820A,U+84CB,U+87F2,U+8846,U+8AB0,U+8B02,U+8B1D,U+8C50,U+8C9D,U+8CA8,U+8CE3,U+8CE6,U+8CEC,U+8DA8,U+8E64,U+8F2A,U+9072,U+9077,U+907A,U+93E1,U+9470,U+9663,U+96B1,U+9838,U+984F,U+9858,U+990A,U+994B} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/53.woff2") format("woff2");unicode-range:U+4F54,U+5152,U+518A,U+522A,U+532F,U+537B,U+5617,U+56B4,U+5E79,U+5EE0,U+5F91,U+6232,U+6557,U+6771,U+6975,U+6A02,U+6B04,U+6E9D,U+700F,U+71B1,U+71DF,U+722D,U+7562,U+7A4D,U+7A69,U+7AF6,U+7C64,U+7D04,U+7D55,U+7E2E,U+7E54,U+8166,U+8173,U+81E8,U+8208,U+842C,U+885D,U+88DC,U+89BD,U+89F8,U+8A02,U+8A34,U+8A73,U+8AC7,U+8CB7,U+8CFC,U+8ECA,U+8F15,U+904A,U+905E,U+92B7,U+9322,U+937E,U+9589,U+95B1,U+96AA,U+96D9,U+9748,U+975C,U+9802,U+984D,U+9867,U+9918,U+99AC,U+9A45,U+9A5F} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/52.woff2") format("woff2");unicode-range:U+50C5,U+525B,U+5283,U+52E2,U+55CE,U+570B,U+570D,U+5831,U+58D3,U+5EE3,U+5F35,U+616E,U+6230,U+64CA,U+64EC,U+64F4,U+66F8,U+68E7,U+6A39,U+6A94,U+6AA2,U+6B77-6B78,U+6E1B,U+6EFF,U+7368,U+756B,U+76E1,U+76E3-76E4,U+7A31,U+7BC4,U+7DF4,U+7E7C,U+7FA3,U+8072,U+8077,U+807D,U+8209,U+85A6,U+865B,U+8A0E,U+8A13,U+8A31,U+8A55,U+8AA4,U+8B77,U+8CAC,U+8CF4,U+8E10,U+8FA6,U+9060,U+908A,U+91CB,U+91DD,U+9396,U+96D6,U+96F2,U+96FB,U+9806,U+9808,U+98A8} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/51.woff2") format("woff2");unicode-range:U+50F9,U+5354,U+5553,U+5C08,U+5C6C,U+5E2B,U+5E6B,U+5F37,U+63A1,U+64C7,U+6B0A,U+6B61,U+72C0,U+7570,U+790E,U+7D30,U+7D39,U+7D42,U+7D61,U+7DE9,U+806F,U+865F,U+88DD,U+898B,U+89BA,U+89C0,U+8A2A,U+8A5E,U+8A62,U+8B6F-8B70,U+8CA0,U+8CBB,U+8CEA,U+8EDF,U+8F09,U+8F2F,U+9023,U+9054,U+9069,U+908F,U+932F,U+9375,U+93C8,U+9580,U+968E,U+969B,U+96A8,U+96DC,U+96E2-96E3,U+97FF,U+9801,U+9810,U+9818,U+982D,U+983B,U+986F} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/50.woff2") format("woff2");unicode-range:U+5099,U+50B3,U+512A,U+5132,U+5247,U+5275,U+5340,U+54E1,U+5718,U+5834,U+584A,U+5920,U+5C0E,U+5E36,U+5E7E,U+63DB,U+65B7,U+689D,U+6CC1,U+6E96,U+7121,U+7372,U+74B0,U+78BA,U+7C21,U+7D1A,U+7DAD,U+7E3D,U+7E8C,U+7FA9,U+81FA,U+898F,U+8996,U+8A18,U+8A71-8A72,U+8A8D,U+8AB2,U+8ACB,U+8AD6,U+8B49,U+8B58,U+8B80,U+8CC7,U+8F03,U+8F38,U+8F49,U+9078,U+9304,U+9577,U+968A,U+9A57} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/49.woff2") format("woff2");unicode-range:U+4E26,U+5169,U+5225,U+52D5,U+53C3,U+55AE,U+5716,U+57F7,U+5B78,U+5BEB,U+5C07,U+5C64,U+5EAB,U+5F9E,U+5FA9,U+614B,U+6236,U+69CB,U+6A19,U+6A5F,U+6C7A,U+6C92,U+6E2C,U+7522,U+7576,U+7BC0,U+7D44,U+7D66,U+7D93,U+7DB2,U+7DDA,U+7DE8,U+7FD2,U+8207,U+8655,U+8853,U+8A2D,U+8A66,U+8A9E,U+8ABF,U+8B1B,U+8B8A,U+8B93,U+904B,U+9805,U+9AD4} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/48.woff2") format("woff2");unicode-range:U+4F86,U+500B,U+5011,U+5167,U+52D9,U+554F,U+5BE6,U+5C0D,U+61C9,U+64DA,U+6578,U+6642,U+6703,U+696D,U+6A23,U+7232,U+73FE,U+767C,U+78BC,U+7A2E,U+7D50,U+7D71,U+88CF,U+8A08,U+8AAA,U+9019,U+9032,U+904E,U+9084,U+958B,U+9593,U+95DC,U+984C,U+985E,U+9EBC,U+9EDE} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/47.woff2") format("woff2");unicode-range:U+4E0C,U+4E28,U+4E2C,U+4E3F,U+4EC2,U+502E,U+5107,U+5155,U+5216,U+522D,U+5281,U+5290,U+5293,U+5369,U+53B6,U+54D5,U+54DC,U+54F3,U+54FF,U+5523,U+553C,U+5588,U+55B5,U+5686,U+570A,U+5776,U+5786,U+5819,U+5820,U+5865,U+58BC,U+5ADC,U+5B32,U+5B65,U+5C1C,U+5C66,U+5C88,U+5C8D,U+5E3B,U+5F2A,U+5F50,U+5F61,U+5FEE,U+6067,U+614A,U+615D,U+61D4,U+620B,U+6224,U+62F6,U+6343,U+63AD,U+63BE,U+6420,U+6434,U+6484,U+6496,U+6499,U+64D0,U+6509,U+652E,U+67D9,U+67DD,U+6833,U+6844,U+684A,U+6920,U+6957,U+6A8E,U+6A91,U+6AA0,U+6B43,U+6BEA,U+6BF5,U+6C15,U+6CD0,U+6D5E,U+6EE0,U+6F24,U+6F2D,U+6F46,U+70C0,U+717A,U+71E0,U+721D,U+728B,U+72C1,U+72C3,U+72E8,U+730A,U+7338,U+734D,U+73E7,U+746D,U+752F,U+754E,U+75B0,U+7603,U+770D,U+7722,U+778D,U+77A2,U+77E7,U+7809,U+7811,U+7857,U+786D,U+78C9,U+78F2,U+791E,U+7946,U+7967,U+799A,U+7B45,U+7B58,U+7B9D,U+7BA2,U+7CD7,U+7F32-7F33,U+8014,U+8022,U+8028-8029,U+804D,U+8080,U+80C2,U+80D9,U+80E9,U+80EC,U+80F2,U+810E,U+8159,U+817D,U+81A3,U+81AA,U+8201,U+8274,U+82B0,U+82E0,U+833C,U+836E,U+83B0,U+83E5,U+8459,U+8488,U+848E,U+84CD,U+84D0,U+84F0,U+8539,U+857A,U+85A8,U+85B7,U+867C,U+871E,U+8723,U+8729,U+8753,U+877E,U+878B,U+8793,U+87D3,U+8803,U+88D2,U+8966,U+89DC,U+89EB,U+8B26,U+8BF6,U+8C2E,U+8C8A,U+8C98,U+8D33,U+8D47,U+8D55,U+8DBC,U+8E2C,U+8E9C,U+8E9E,U+8ECE,U+8F77,U+8F79,U+8FEE,U+9058,U+9139,U+914F,U+9174,U+9191,U+91A2,U+91B5,U+928E,U+9494,U+94B6,U+94F4,U+94F9,U+950A,U+950E,U+951E,U+952B,U+953C,U+953E,U+9564,U+95F6,U+9603,U+960C-960D,U+9622,U+9674,U+9794,U+97AB,U+98A5,U+9A98,U+9A9F,U+9AB1,U+9B0F,U+9B48,U+9C92,U+9CA6,U+9CB0,U+9CBA,U+9CC6,U+9CCB,U+9CD8,U+9E32,U+9E5B,U+9E88,U+9EB4,U+9EFB-9EFC,U+9F3D,U+9F44,U+9F86} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/46.woff2") format("woff2");unicode-range:U+4E36,U+4EA0,U+4F74,U+4F91,U+4F94,U+4FC5,U+507E,U+50BA,U+50ED,U+5181-5182,U+51F5,U+525E,U+5282,U+52F9,U+5326,U+537A,U+53A3,U+5423,U+5459,U+54B4,U+54D9,U+552A,U+55C9,U+57A4,U+57F4,U+580B,U+5902,U+5925,U+5A08,U+5AB5,U+5B84,U+5BE4,U+5C22,U+5C6E,U+5CB5,U+5CBD,U+5D3E,U+5DDB,U+5E31,U+5E5E,U+5E80,U+5EE8,U+5F82,U+5FC9,U+5FED,U+600A,U+605D,U+609B,U+609D,U+60DD,U+619D,U+6225,U+6243,U+6322,U+63CE,U+63F2,U+640B-640C,U+643F,U+6445,U+64D7,U+6534,U+6549,U+656B,U+6603,U+674C,U+67A8,U+680A,U+6864,U+6971,U+69D4,U+6A65,U+6C2A,U+6C46,U+6C5C,U+6D0E,U+6D48,U+6E2B,U+6EB2,U+6EB7,U+6F89,U+706C,U+70B1,U+7113,U+71D4,U+727F,U+72F3,U+7303,U+7321,U+7339,U+736C,U+736F,U+74A9,U+74DE,U+750D,U+7513,U+7592,U+75C4,U+7605,U+760A,U+761B,U+7625,U+762D,U+7643,U+7707,U+7735,U+7747,U+77B5,U+7839,U+784E,U+7924,U+793B,U+7953,U+798A,U+7A03,U+7A06,U+7A78,U+7A80,U+7AAD,U+7BA8,U+7BDA,U+7BE5,U+7CC8,U+7EC1,U+7F0B,U+7F0F,U+7F12,U+7F68,U+7F9D,U+8025,U+8035,U+809C,U+80AD,U+80B7,U+80E8,U+811E,U+8204,U+8221,U+8223,U+822D,U+823B,U+824B,U+825A,U+827D,U+827F,U+828F,U+82C8,U+8307,U+831B,U+8347,U+837D,U+839B,U+83A9,U+83F9,U+8487,U+84B9,U+8579,U+864D,U+867F,U+86B0,U+86D1,U+86D8,U+86F2,U+8764,U+8770,U+8788,U+8797,U+87AD,U+87B5,U+881B,U+8844,U+88BC,U+88FC,U+8930,U+89CC,U+89CF,U+89D6,U+8BA0,U+8BD4,U+8C02,U+8C2B,U+8C85,U+8E23,U+8E40,U+8E94,U+8F81-8F82,U+8FD5,U+90B6,U+90DB,U+914E,U+9164,U+943E,U+94B7-94B8,U+94DE,U+94EB,U+950D,U+9514,U+9516,U+9518,U+9529,U+9538,U+953F,U+9544,U+954E,U+955F,U+9561,U+9569,U+95FC,U+963D,U+9667,U+96B3,U+9792,U+97B2,U+98A1,U+9969,U+9998,U+9A80,U+9A92,U+9A96,U+9AD1,U+9B2F,U+9C95,U+9CB4,U+9CBC-9CBD,U+9CD0,U+9E31,U+9E38,U+9E3A,U+9E71,U+9E7E,U+9EE5,U+9EEA,U+9EF9,U+9FA0} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/45.woff2") format("woff2");unicode-range:U+4EF3,U+4F1B,U+4F7E,U+50D6,U+50EC,U+51AB,U+51B1,U+52D6,U+536E,U+54A9,U+54DA,U+55BE,U+55CD,U+564D,U+572C,U+572F,U+574C,U+576B,U+57CF,U+57D8,U+57F8,U+57FD,U+5844,U+59D2,U+5AE0,U+5B16,U+5B37,U+5B5B,U+5B80,U+5D1E,U+5D6B,U+5EFE,U+5F11,U+5F56,U+5F58,U+5F73,U+5F8C,U+5FC4,U+5FE4,U+602B,U+6106,U+610D,U+63DE,U+63F8,U+641B,U+64E4,U+6634,U+66DB,U+676A,U+6798,U+67B5,U+681D,U+6883,U+698D,U+69B1,U+69E0,U+6A18,U+6B37,U+6B39,U+6B9B,U+6BB3,U+6D33,U+6D7C,U+6E16,U+6EBB,U+6ED7,U+6F36,U+6F72,U+6F8C,U+7035,U+7039,U+7173,U+7178,U+7228,U+728F,U+72B4,U+72EF,U+72F4,U+7331,U+7481,U+74E0,U+7540,U+755B,U+758B,U+75C3,U+75E6,U+7635,U+763C,U+764D,U+76CD,U+7704,U+7743,U+7780,U+7847,U+786A,U+78A5,U+78B9,U+7962,U+7A02,U+7AAC,U+7AB3,U+7B0A,U+7B4C,U+7B6E,U+7B7B,U+7BFC,U+7C0F,U+7C16,U+7C40,U+7C9E,U+7CA2,U+7CC7,U+7CF8,U+7D77,U+7E3B,U+7EA1,U+7EA9,U+7EF2,U+7F02,U+7F07,U+7F0C,U+7F23,U+7F2F,U+7FBC,U+8016,U+8020,U+812C,U+8136,U+8182,U+822F,U+8233,U+825F,U+8268,U+8284,U+8288,U+8291,U+829F,U+8308,U+8311,U+831A,U+835B,U+836D,U+83B6,U+83DD,U+8406,U+840F,U+845C,U+84B4,U+84E3,U+850C,U+855E,U+85A4,U+863C,U+86BA,U+86C4,U+86DE,U+86F1,U+873E,U+87AC,U+87BD,U+87DB,U+880A,U+883C,U+8864,U+887F,U+88F0,U+890A,U+892B,U+895E,U+89CB,U+89EF,U+8A48,U+8BDC,U+8C18,U+8C33,U+8C94,U+8DB1,U+8DCF,U+8DD6,U+8DE3,U+8E6F,U+8E90,U+8F7A,U+8F7E,U+8FB6,U+8FD3,U+902D,U+90BE,U+9161,U+91A3,U+91AD,U+91AF,U+936A,U+948B,U+94D8,U+9513,U+953A,U+956C,U+963C,U+9654,U+966C,U+9688,U+972A,U+9730,U+97B4,U+98E7,U+9967,U+996B,U+9987,U+9A75,U+9A7A,U+9AA3,U+9ABA,U+9ADF,U+9AED,U+9AF9,U+9B08,U+9B43,U+9C8E,U+9C94,U+9C9A,U+9CD4,U+9E2B,U+9E36,U+9E4B,U+9E4E,U+9E55,U+9E63,U+9E68-9E69,U+9EBD,U+9EC9,U+9F0D,U+9F2C,U+9F37,U+9F51} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/44.woff2") format("woff2");unicode-range:U+4E69,U+4F67,U+4FDC,U+50A7,U+50E6,U+5196,U+5202,U+5233,U+523F,U+52A2,U+5363,U+5432,U+5476,U+54AD,U+54CF,U+5537,U+5575,U+5581,U+55CC,U+55FE,U+5601,U+561E,U+56DD,U+56DF,U+5709,U+580D,U+5881,U+589A,U+5941,U+5981,U+59B2,U+5AAA,U+5C25,U+5D24,U+5D74,U+5DEF,U+5E42,U+5E8B,U+5EB3,U+5ED2,U+5EF4,U+5FAD,U+6003,U+601B,U+603C,U+6083,U+6092,U+60AB,U+6100,U+6126,U+6206,U+6217,U+62CA,U+638E,U+63B4,U+6426,U+646D,U+6535,U+65C4,U+6661,U+6715,U+6769,U+67C3,U+6861,U+68F9,U+69CA,U+69ED,U+69F2,U+69FF,U+6BF9,U+6C05,U+6C0D,U+6CA9,U+6CB2,U+6CD6,U+6CF6-6CF7,U+6CFA,U+6DAB,U+6E53-6E54,U+6FB6,U+709D,U+71B5,U+7256,U+725D,U+727E,U+72AD,U+72F7,U+72FB,U+7313,U+739F,U+73E5,U+74BA,U+750F,U+754B,U+75AC,U+75D6,U+7617,U+7630,U+7640,U+76A4,U+76B2,U+775A,U+77BD,U+781F,U+78A1,U+7905,U+79B3,U+7B2B,U+7B2E,U+7B31,U+7B3E,U+7B62,U+7B85,U+7B9C,U+7BCC,U+7BEA,U+7C0B,U+7C74,U+7C9C,U+7CC1,U+7CE8,U+7E47,U+7E9F,U+7EA5,U+7EE0,U+7EE8,U+7EF1,U+7F21,U+7F27,U+7F74,U+7F7E,U+7FB0,U+802A,U+8031,U+8071,U+80EA,U+8112,U+8114,U+8160,U+8169,U+81A6,U+81C1,U+8234,U+82A4,U+82E1,U+82FB,U+8333,U+836C,U+83C0,U+83F8,U+8411,U+841C,U+845A,U+8489,U+848C,U+84BD,U+8548,U+8627,U+8629,U+866E,U+86B5,U+86E9,U+86F4,U+86F8,U+872E,U+8731,U+877B,U+877D,U+8785,U+87AB,U+87EA,U+8813,U+8816,U+88CE,U+88E5,U+897B,U+89DA,U+89F3,U+8BFC,U+8C35,U+8D46,U+8D4D,U+8DBA,U+8E3A,U+8F75,U+8F94,U+9179,U+917E,U+94AB-94AC,U+94D7,U+94E5,U+9515,U+952A,U+952C,U+9545,U+9558,U+9565,U+9568,U+956A,U+961D,U+96E0,U+987C,U+9883,U+989F,U+990D,U+9993,U+9AC0,U+9AE1,U+9AEB,U+9B51,U+9C86,U+9C8B,U+9CA0-9CA1,U+9CA3,U+9CCE,U+9CE2,U+9E37,U+9E39,U+9E41,U+9E48,U+9E6A,U+9E87,U+9EE2,U+9EE9,U+9F17,U+9F19,U+9F2F,U+9F39,U+9F80,U+9F85} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/43.woff2") format("woff2");unicode-range:U+4F65,U+5240,U+5261,U+52AC,U+531A,U+5416,U+5452,U+5454,U+5456,U+5472,U+5478,U+54BB,U+54C2,U+54D3,U+54DE,U+553F,U+560C,U+566B,U+572E,U+57D2,U+57EF,U+581E,U+5924,U+5997,U+59A3,U+5AB8,U+5AD4,U+5AF1,U+5B34,U+5BA5,U+5C59,U+5C63,U+5D5B,U+5D5D,U+5E0F,U+5E11,U+5E14,U+5E91,U+5ED1,U+5F40,U+6004,U+600D,U+6019,U+605A,U+610E,U+623D,U+624C,U+634C,U+6369,U+65D2,U+670A,U+6753,U+67A7,U+67F0,U+680C,U+6855,U+686B,U+6939,U+696E,U+6980,U+6A7C,U+6AAB,U+6B82,U+6B84,U+6BF3,U+6C06-6C07,U+6C19-6C1A,U+6D07,U+6D1A,U+6D27,U+6D2B,U+6D91,U+6E8F,U+6F74,U+7085,U+70BB,U+7198,U+726E,U+72B0,U+72CE,U+72F2,U+72FA,U+7322,U+737E,U+738E,U+7477,U+748E,U+755A,U+7594,U+75B3,U+760C,U+7615,U+763F,U+7654,U+7726,U+7765,U+7768,U+77EC,U+7817,U+78D4,U+78D9,U+7913,U+79D5,U+79EB,U+7A70,U+7A86,U+7B25,U+7B2A,U+7B38,U+7B47,U+7B72,U+7BA7,U+7C26,U+7C9D,U+7DAE,U+7E9B,U+7EE1,U+7F01,U+7F11,U+7F17,U+7F36,U+7FEE,U+8043,U+809F,U+80AB,U+80CD,U+80D7,U+8118,U+8188,U+823E,U+8279,U+8298,U+82C1,U+82CA,U+82D8,U+82E4,U+82F4,U+8306,U+835C,U+83D4,U+83DF,U+8401,U+8451,U+846D,U+8476,U+8478,U+84BA,U+84E0,U+851F,U+8556,U+8585,U+867A,U+868B,U+868D,U+8765,U+87EE,U+8832,U+8872,U+88A2,U+88B7,U+88C9,U+88E2-88E3,U+8BB5,U+8BCE,U+8BD3,U+8BD6,U+8BF3,U+8BF9,U+8C16-8C17,U+8C1D,U+8C55,U+8C73,U+8C78,U+8D40,U+8D5C,U+8D91,U+8DDE,U+8DEB,U+8E14,U+8E41,U+8F6D,U+8F8E,U+8FE8,U+9011,U+915E,U+9162,U+9185,U+918C,U+9190,U+92C8,U+94CA,U+94D1,U+94D5,U+94EA,U+94F3,U+94F7,U+94FD,U+951D,U+9559,U+9566-9567,U+9572,U+95F1,U+96CE,U+96E9,U+9785,U+9878,U+988F,U+98D1,U+9954,U+9963,U+9A93,U+9AC1,U+9ACC,U+9B1F,U+9B32,U+9B49,U+9B4D,U+9CA5,U+9CA7,U+9CA9,U+9CAE,U+9CB1,U+9CD3,U+9E46,U+9F22,U+9F87} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/42.woff2") format("woff2");unicode-range:U+4E47,U+4E5C,U+4E8D,U+4EDF,U+4F5E,U+4F76,U+4F89,U+4FAA,U+4FE6,U+5028,U+5048,U+50EE,U+520E,U+53C1,U+5412,U+5443,U+54DD,U+5577,U+5591,U+55E5,U+55F3,U+560F,U+562C,U+56AF,U+575C,U+5769,U+577C,U+578C,U+5793,U+57DA,U+57E4,U+5800,U+5889,U+593C,U+59AB,U+5AD8,U+5B53,U+5CA3,U+5D26,U+5EDB,U+5F01,U+5FBC,U+5FDD,U+6029,U+6041,U+60AD,U+60B1,U+61B7,U+629F,U+6332,U+63BC,U+647A,U+64BA,U+65AB,U+65C3,U+65C6,U+65F0,U+6600,U+668C,U+669D,U+66BE,U+66F7,U+67E2,U+6800,U+6874,U+68FC,U+690B,U+691F,U+6978,U+6989,U+69A7,U+6A17,U+6A3E,U+6A50,U+6A5B,U+6B24,U+6B81,U+6C10,U+6C3D,U+6C54,U+6E6B,U+6EBD,U+6FDE,U+71B3,U+723B,U+7292,U+72B8,U+72D2,U+7301,U+731E,U+7360,U+73A2,U+74FF,U+75A0,U+7633,U+771A,U+779F,U+781C,U+7829,U+7957,U+7AA8,U+7B04,U+7BA6,U+7C1F,U+7CCD,U+7ECB,U+7ED4,U+7ED7,U+7EFB,U+7EFE,U+7F26,U+7F31,U+7F35,U+7F5F,U+7FAF,U+7FD5,U+801C,U+8027,U+80BC,U+815A,U+8167,U+81CA,U+81CC,U+8244,U+824F,U+82A1,U+82A9,U+82AB,U+82B4,U+833A,U+8351,U+8368,U+839C,U+83B3,U+83B8,U+83BC,U+83D8,U+83F0,U+8497,U+858F,U+85FF,U+867B,U+86A8-86A9,U+870A,U+8734,U+877C,U+87CA,U+886E,U+887E,U+8888,U+88DF,U+8921,U+8BEE,U+8C04,U+8C20,U+8C25,U+8C2F,U+8C36,U+8D3D,U+8D59,U+8D67,U+8DBF,U+8DEC,U+8DF8,U+8DFD,U+8E05,U+900B,U+9021,U+9044,U+9062,U+908B,U+90B4,U+90CF,U+90EB,U+9123,U+9146,U+9172,U+918D,U+91AA,U+933E,U+93CA,U+9486,U+948C,U+9490,U+94AA,U+94AD,U+94B2,U+94C8,U+94D6,U+94D9,U+94DF,U+94E9,U+94FC,U+951B,U+9546,U+954F,U+9554,U+955E,U+956B,U+9571,U+95E9,U+960B,U+960F,U+9612,U+9617,U+97AF,U+989A,U+98D5,U+992E,U+9955,U+9A78,U+9A7D,U+9AA0,U+9AA2,U+9AB0,U+9AC2,U+9B23,U+9C82,U+9C90,U+9C9E,U+9CAD,U+9CC3,U+9E28,U+9E47,U+9E6C,U+9EE7,U+9EFE,U+9F0B} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/41.woff2") format("woff2");unicode-range:U+4E93,U+4EC4,U+4EF5,U+4F25,U+4F27,U+4F32,U+4F7B,U+4FE3,U+5080,U+5121,U+5208,U+5250,U+52F0,U+535F,U+538D,U+5453,U+54A6,U+54D0,U+54D4,U+5533,U+5541,U+5549,U+5550,U+5556,U+55DD,U+55F5,U+5627,U+5657-5658,U+5664,U+567B,U+56D4,U+571C,U+57B2,U+57B4,U+5807,U+58C5,U+59A4,U+59AF,U+5A09,U+5A62,U+5AEB,U+5C3B,U+5CAC,U+5D03,U+5D9D,U+5DB7,U+5EBE,U+5F2D,U+5FB5,U+5FEA,U+5FF8,U+6079,U+6120,U+6221-6222,U+623E,U+638A,U+63E0,U+6485,U+64B8,U+64E2,U+66E9,U+67C1,U+67D2,U+67FD,U+6877,U+68F0,U+6924,U+6934,U+6998,U+69C1,U+6A84,U+6A97,U+6AA9,U+6B87,U+6B8D,U+6BAA,U+6BD6,U+6C18,U+6CD4,U+6CEB,U+6CEE,U+6D04,U+6D4D,U+6DE0,U+6EB1,U+6ECF,U+70B7,U+7110,U+7168,U+71F9,U+7230,U+728D,U+72D9,U+7352,U+73B3,U+73D0,U+7511,U+75A5,U+75C8,U+75E7-75E8,U+7610,U+7619,U+772D,U+7826,U+782C,U+784C,U+7850,U+7856,U+7877,U+789B,U+78F4,U+7A39,U+7AE6,U+7B0F,U+7B15,U+7B1E,U+7B24,U+7B5A,U+7BAA,U+7BAC,U+7BD9,U+7ED0,U+7F0D,U+7F1F,U+7F45,U+7F71,U+7FBF,U+7FE5,U+8052,U+80B1,U+80DB,U+80DD,U+80FC,U+8132,U+816D,U+81EC,U+8210,U+8228,U+8249,U+828A,U+828E,U+82CC,U+8315,U+8369,U+8378,U+83A8,U+83AA,U+83B4,U+83EA,U+84C1,U+8538,U+859C,U+85AE,U+86B4,U+86CF,U+8722,U+876E,U+87E5,U+8919,U+8BA7,U+8BCC,U+8BDF,U+8BE8,U+8C07,U+8C30,U+8C47,U+8D36,U+8DB5,U+8E1F,U+8E2F,U+8E70,U+8E85,U+8F78,U+8F87,U+8F8B,U+8F8F,U+8F9A,U+90BA,U+90C4,U+90D0,U+90FE,U+9143,U+917D,U+9487,U+948D,U+948F,U+94BD,U+94CD,U+94EF,U+9507,U+954A,U+9604,U+9609-960A,U+9615,U+96B9,U+96D2,U+9708,U+97EA,U+989B,U+989E,U+98A7,U+996C,U+9980,U+9991,U+9A88,U+9AB6,U+9B3B,U+9B47,U+9C87,U+9C9B,U+9CB5,U+9CC7,U+9CCA,U+9CD9,U+9E2C,U+9E42,U+9E58,U+9E5A,U+9E5E,U+9ECD,U+9ECF,U+9F8A,U+9F8C} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/40.woff2") format("woff2");unicode-range:U+4EBB,U+4FA9,U+502C,U+51C7,U+51EB,U+523D,U+530F,U+53F5,U+5464,U+5466,U+549D,U+54A3,U+54BF,U+5527,U+555C,U+556D,U+5576,U+558F,U+55B1,U+55EB,U+55F2,U+5624,U+564C,U+5685,U+568F,U+56E1,U+5739,U+57A1,U+5942,U+59D8,U+5A0C,U+5A4A,U+5A67,U+5AD2,U+5B6C,U+5C50,U+5C7A,U+5C98,U+5CA2,U+5D27,U+5D6C,U+5DF3,U+5E19,U+5EA0,U+5EB9,U+5EEA,U+5FFE,U+6042,U+6175,U+6215,U+622C,U+6266,U+62BB,U+6308,U+6339,U+63B8,U+643D,U+6441,U+6555,U+659B,U+6677,U+6775,U+678B,U+679E,U+67B3,U+6832,U+6840,U+6860,U+68C2,U+6910,U+6966,U+6987,U+69CE,U+6A2F,U+6A3D,U+6B92-6B93,U+6BC2,U+6BFD,U+6C16,U+6C29,U+6CC5,U+6D0C,U+6D60,U+6DA0,U+6DDD,U+6E0C,U+6E86,U+6ED3,U+6EDF,U+6F4B,U+6FB9,U+6FD1,U+7023,U+7080,U+70CA,U+712F,U+7145,U+7284,U+732C,U+73C8,U+740A,U+7441,U+7457,U+74D2,U+7596,U+759D,U+75A3,U+75D8,U+75E3-75E4,U+75FF,U+7622,U+765E,U+7688,U+76B4,U+76E5,U+7812,U+7818,U+7887,U+789A,U+78B2,U+7A51,U+7BB8,U+7BC1,U+7CCC,U+7EA8,U+7EC0,U+7EE6,U+7EFA,U+7F1B,U+7F22,U+7FA7,U+7FE6,U+7FF3,U+8012,U+8084,U+8093,U+80E4,U+80EF,U+80F4,U+81BB,U+8202,U+82A8,U+82E3,U+8331,U+8366,U+83E1,U+8473,U+84A1,U+84CA,U+84D1,U+84FC,U+853B,U+857B,U+85D3,U+8605,U+8662,U+86B1,U+86C9,U+86D4,U+86ED,U+86F3,U+8725,U+8748,U+874C,U+8763,U+879F,U+87B3,U+887D,U+88FE,U+89C7,U+89DE,U+89E5,U+8A8A,U+8BA6,U+8C00,U+8C14,U+8C21,U+8C49,U+8C7A,U+8D30,U+8DD7,U+8E59,U+8E69,U+8F6B,U+8F73,U+8FF3,U+9026,U+902F,U+9099,U+909B,U+90C7,U+914A,U+91AE,U+91BA,U+94AF,U+94BA,U+94BF,U+94CC,U+94D2,U+94F0,U+9531,U+95F3,U+9697,U+975B,U+9765,U+977C,U+9880,U+98A2,U+998A,U+9994-9995,U+9A9B,U+9AB7,U+9AC5,U+9AFB,U+9C91,U+9CCF,U+9CD5,U+9E29,U+9EDC,U+9F88-9F89} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/39.woff2") format("woff2");unicode-range:U+4EDD-4EDE,U+4EE1,U+4EEB,U+4FFE,U+5025,U+506C,U+50A5,U+50F3,U+51FC,U+520D,U+5241,U+525C,U+52AD,U+530D,U+5310,U+539D,U+53A9,U+53FC,U+5421,U+5477,U+54E7,U+5514,U+551B,U+5530,U+556E,U+557E,U+5599,U+55B9,U+55D4,U+5623,U+562D,U+5654,U+5671,U+56D7,U+56EB,U+56F5,U+57A7,U+57D5,U+57D9,U+57DD,U+584D,U+5880,U+58EC,U+59DD,U+5A32,U+5A55,U+5A75,U+5B51,U+5B71,U+5B73,U+5CD2,U+5CE4,U+5D06,U+5D6F,U+5DFD,U+5E5B,U+5E96,U+5FD2,U+600F,U+606B,U+62BF,U+634B,U+636D,U+6387,U+63B3,U+63C4,U+63C6,U+63F6,U+63FF,U+661D,U+6657,U+66A7,U+6841,U+6849,U+6867,U+6901,U+699B,U+6A28,U+6A79,U+6B46,U+6C21,U+6C24,U+6C32,U+6C35,U+6C4A,U+6C86,U+6C94,U+6CA3,U+6D39,U+6D63,U+6D6F,U+6FEF,U+71E7,U+72CD,U+72F0,U+7325,U+7350,U+7391,U+73D9,U+741A,U+757F,U+7583,U+75B1,U+75B4,U+75B8,U+75F1,U+766F,U+7699,U+7751,U+789C,U+7A17,U+7B08,U+7B33,U+7BE6,U+7C2A,U+7CB2,U+7EA3,U+7EB0,U+7EBE,U+7EEB,U+7F2C,U+7FB8,U+8037,U+8153,U+8171,U+8191,U+8214,U+821B,U+8222,U+826E,U+8297,U+82BE,U+82EB,U+830C,U+8314,U+8334,U+83C5,U+83D6,U+83FD,U+843C,U+84FF,U+8564,U+8572,U+85C1,U+8616,U+8693,U+86A3,U+86AA,U+86AF,U+86B6,U+86C6,U+86CA,U+8708-8709,U+870D,U+8759,U+8760,U+87AF,U+87C6,U+8869,U+88C6,U+89D0,U+8A3E,U+8B07,U+8BAA,U+8BC2,U+8BE4,U+8BF0,U+8C2A,U+8C62,U+8C89,U+8D49,U+8D6D,U+8D84,U+8D94,U+8DB8,U+8DF9,U+8E2E,U+8E3D,U+8E47,U+8E51,U+8E7F,U+9005,U+9016,U+9051,U+907D,U+9082,U+9088,U+90B0,U+90D3,U+9495,U+94A3-94A4,U+94B9,U+94CB,U+94E0-94E1,U+9509,U+9512,U+9552-9553,U+955D,U+96B0,U+96BC,U+9791,U+9990,U+9A9C,U+9C88,U+9CB2-9CB3,U+9CB7,U+9CDC,U+9E22,U+9E57,U+9E67,U+9E73,U+9E82,U+9EB8,U+9EDF-9EE0,U+9F83} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/38.woff2") format("woff2");unicode-range:U+4EE8,U+4F22,U+4F43,U+4F57,U+4F5D,U+4FDA,U+502D,U+507B,U+5106,U+53DF,U+544B,U+5482,U+54A7,U+54CC,U+5544,U+5555,U+5594,U+55C4,U+55D1,U+55DF,U+55E4,U+55E8,U+55EA,U+55EC,U+55EF,U+564E,U+56F9,U+5704,U+576D,U+57AD,U+5914,U+5958,U+599E,U+59AA,U+59BE,U+5A06,U+5ABE,U+5AE1,U+5B40,U+5BEE,U+5CBF,U+5CC4,U+5CCB,U+603F,U+6078,U+607B,U+607D,U+608C,U+609A,U+60FA,U+61D1,U+61FF,U+621B,U+622E,U+626A,U+6371,U+63AE,U+63CD,U+63D6,U+6410,U+6414,U+6421,U+6448,U+64C0,U+64D8,U+66DC,U+6748,U+6772,U+67A5,U+680E,U+6954,U+69AB,U+6A47,U+6CA4,U+6D2E,U+6D94,U+6E4E,U+6E98,U+705E,U+7094,U+70E9,U+7116,U+723F,U+726F,U+72DE,U+73C9,U+74E4,U+753E,U+7548,U+75BD,U+75C2,U+75CD,U+7618,U+76C5,U+76F1,U+7708,U+7719,U+777E,U+7791,U+77B3,U+7823,U+7827,U+7889,U+7893,U+795C,U+79E3,U+7A88,U+7A95,U+7AA0,U+7B90,U+7BD1,U+7BFE,U+7DA6,U+7EC2,U+7EEF,U+7F03-7F04,U+7F08,U+7F25,U+7F58,U+7F61,U+7F9F,U+8174,U+828D,U+82C4,U+82D5,U+82DC,U+82F7,U+832D,U+835A,U+840B,U+8418,U+8438,U+852B,U+866C,U+869D,U+86A7,U+86AC,U+86D0,U+86F0,U+87A8,U+87D1,U+87E0,U+8839,U+8913,U+891B,U+8934,U+8941,U+89CA,U+89CE,U+8A07,U+8BA3,U+8BAB,U+8BC5,U+8BDB,U+8C11,U+8C15,U+8C32,U+8DC4,U+8DC6,U+8DCE,U+8DDB,U+8DFA,U+8E09,U+8E1D,U+8E39,U+8E42,U+8E49,U+8E4B,U+8E8F,U+8F71-8F72,U+9004,U+9036,U+9097,U+90DC,U+90E2,U+90E6,U+90EF,U+9104,U+9150,U+919A,U+91B4,U+938F,U+9497,U+949C,U+950F,U+951F,U+9534,U+9557,U+9562-9563,U+9573,U+9606,U+965F,U+972D,U+973E,U+97EB,U+9889,U+988C,U+9894,U+98A6,U+9974,U+9977,U+997D,U+9A90,U+9A9D,U+9AA7,U+9AEF,U+9CA2,U+9CB6,U+9CC5,U+9CCD,U+9CDF,U+9E20,U+9E2A,U+9E6B,U+9F3E,U+9F9B} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/37.woff2") format("woff2");unicode-range:U+4EB5,U+4F09,U+4F5A,U+4F6F,U+4FCE,U+4FEA,U+4FF3,U+4FF8,U+500C,U+500F,U+504E,U+5088,U+52BE,U+5345,U+53FB,U+5420,U+5457,U+5499,U+549B,U+54C6,U+54D2,U+550F,U+558B,U+559F,U+55BD,U+55D6,U+565C,U+568E,U+5785,U+57A9,U+57ED,U+59F9,U+5A11,U+5A40,U+5AE6,U+5B6A,U+5B93,U+5BB8,U+5C15,U+5C99,U+5C9C,U+5CC1,U+5D2E,U+5D47,U+5D4B,U+5D99,U+5E54,U+5E61,U+5FCF,U+6002,U+6006,U+6014,U+607F,U+60C6,U+60DA,U+60F4,U+621F,U+62C8,U+631B,U+631E,U+63E9,U+6619,U+6635,U+6641,U+6710,U+67AD,U+67B0,U+67B7,U+67E9,U+684E,U+688F,U+695D,U+696B,U+69B7,U+6A58,U+6C26,U+6C68,U+6C8F,U+6D35,U+6D43,U+6D9E,U+6DD9,U+6DEC,U+6E11,U+6EC2,U+6EE2,U+6F09,U+6F66,U+6F8D,U+6FC2,U+6FC9,U+6FE0,U+7119,U+729F,U+72C8,U+73DE,U+7430,U+7566,U+7579,U+75C9,U+75E2,U+75FC,U+762A,U+7634,U+7638,U+7678,U+76F9,U+778C,U+77DC,U+7800,U+781D,U+782D,U+7830,U+783B-783C,U+78A3,U+78EC,U+7949,U+7980,U+7A14,U+7A23,U+7AFD,U+7B95,U+7BDD,U+7C0C,U+7C41,U+7C91,U+7CC5,U+7ECC,U+7F19,U+7FCA,U+8006,U+8069,U+807F,U+80BD,U+80ED,U+814B,U+8198,U+8200,U+82CB,U+82D2,U+834F,U+8360,U+847A,U+84D6,U+84E5,U+8537,U+85D0,U+8671,U+86A4,U+86CE,U+86F9,U+8703,U+8707,U+8737,U+873B,U+8782,U+87D2,U+8815,U+8936,U+8BC3,U+8BCB,U+8BCF,U+8BD2,U+8BD8,U+8BE9,U+8C0C,U+8C0F,U+8C29,U+8C4C,U+8D45,U+8D73,U+8E31,U+8E6D,U+8E76,U+8FE4,U+9041,U+90D7,U+9169,U+9485,U+94A1,U+94DB,U+94E7,U+9503,U+9506,U+9528,U+9537,U+9542,U+9549,U+95FE,U+961A,U+9649,U+96C9,U+96F3,U+9701,U+970E,U+9739,U+9753,U+9798,U+97A3,U+98D2-98D3,U+98D9-98DA,U+9968,U+996F,U+9984,U+9997,U+9ACB,U+9C85,U+9CA8,U+9CAB,U+9E49,U+9E4C,U+9E51,U+9E66} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/36.woff2") format("woff2");unicode-range:U+4E2B,U+4EB3,U+4EC9,U+4F0E,U+4F64,U+4F8F,U+4FDF,U+501C,U+50A9,U+510B,U+51A2,U+51BC,U+527D,U+5429,U+542E,U+5486,U+54AF,U+5506,U+5511,U+5522,U+552C,U+556C,U+55B3,U+55D2,U+55E6,U+55FD,U+5639,U+5659,U+5662,U+567C,U+5693,U+572A,U+5768,U+577B,U+5892,U+598A,U+5992,U+59A9,U+5A20,U+5AE3,U+5B17,U+5B7D,U+5D34,U+5D3D,U+5D4A,U+5D82,U+5E1A-5E1B,U+5EA5,U+5F77,U+5FD0-5FD1,U+5FD6,U+5FFF,U+6035,U+6063,U+60AF,U+60B4,U+60BB,U+612B,U+6194,U+61CA,U+61E6,U+61F5,U+620A,U+6248,U+62A1,U+62D7,U+6376,U+637B,U+64B5,U+655D,U+65BC,U+65CC,U+65CE,U+65D6,U+664C,U+665F,U+66B9,U+6773,U+6777,U+6787,U+67DE,U+6845,U+693D,U+6994,U+6A35,U+6D54,U+6D5C,U+6D8E,U+6DD6,U+6E6E,U+6E9F,U+6EF9,U+6F2A,U+6F78,U+704F,U+70EC,U+7118,U+714A,U+71B9,U+724D,U+7337,U+733E,U+73B7,U+73CF,U+7428,U+742C,U+74F4,U+7525,U+753A,U+7572,U+768E,U+76C2,U+7762,U+77CD,U+77FD,U+7825,U+7837,U+78B4,U+795F,U+7A1E,U+7B06,U+7B20,U+7BA9,U+7BAB,U+7C7C,U+7CB3,U+7CBD,U+7CDC,U+7EC9,U+7EF6,U+7F44,U+7F54,U+7F94,U+8004,U+800B,U+8019,U+809B,U+80AE,U+80C4,U+80F1,U+8146,U+816E,U+817C,U+81C0,U+81FC,U+81FE,U+822B,U+830F,U+832F,U+8340,U+8365,U+8385,U+8392,U+83A0,U+8424,U+84AF,U+869C,U+8713,U+8717-8718,U+87C0,U+87CB,U+87FE,U+8821,U+8902,U+89D1,U+8BB9,U+8C12,U+8D32,U+8D53,U+8D5D,U+8DF7,U+8E7C,U+8F7C,U+8F95,U+8FAB,U+9052,U+905B,U+9095,U+909D,U+90C5,U+911E,U+9122,U+916A,U+919B,U+92AE,U+948E,U+9492,U+949A,U+94B5,U+94BC,U+94C4,U+94C6,U+94C9,U+94F1,U+9502,U+9511,U+9517,U+9536,U+956F,U+9602,U+9616,U+9621,U+9631,U+998B,U+99A5,U+9A81,U+9A9E,U+9B03,U+9EBE,U+9F10,U+9F8B} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/35.woff2") format("woff2");unicode-range:U+4E15,U+4E1E,U+4F2B,U+4F3D,U+4FAC,U+5043,U+5055,U+5140,U+51CB,U+5243,U+52D0,U+531D,U+536F,U+53A5,U+53AE,U+53FD,U+541D,U+5492,U+5494,U+54A4,U+54AA,U+54CE,U+54FD,U+5520,U+553E,U+557B,U+55C5,U+55E1,U+55F7,U+5608,U+561F,U+5636,U+563B,U+5773,U+57A0,U+5811,U+587E,U+58D5,U+5A29,U+5A6A,U+5A76,U+5A7A,U+5AC9,U+5B95,U+5C49,U+5C8C,U+5CAB,U+5CB7,U+5D02,U+5D58,U+5E44,U+5E7A,U+5EFF,U+5F0B,U+5F29,U+5F89,U+5F9C,U+5FA8,U+6005,U+6026,U+6043,U+60D8,U+60EC,U+60EE,U+6115,U+618E,U+630E,U+637A,U+6390,U+63AC,U+63B0,U+64DE,U+6525,U+652B,U+6538,U+65EE-65EF,U+6631,U+6636,U+6654,U+6666,U+6684,U+677C,U+67D8,U+683E,U+68B5,U+692D,U+6979,U+6988,U+6B59,U+6B9A,U+6C69,U+6C74,U+6CAE,U+6CE0,U+6D95,U+6DDE,U+6DE6,U+6DFC,U+6EB4,U+6F15,U+6F29,U+7096,U+70C3,U+7131,U+715C,U+7172,U+7266,U+728A,U+7317,U+731D,U+7329,U+7396,U+73E9,U+7425,U+742E,U+7455,U+7490,U+74EE-74EF,U+7519,U+75B5,U+75B9,U+75D4,U+75DE,U+7656,U+765C,U+7663,U+7691,U+7729,U+777D,U+783E,U+787C,U+795A,U+79ED,U+7A79,U+7ABF,U+7B3A,U+7B4F,U+7B60,U+7B75,U+7B8D,U+7BB4,U+7BD3,U+7BE1,U+7CBC,U+7EDB,U+7F1C,U+7F30,U+7F42,U+7F8C,U+7FCE,U+7FF1,U+810D,U+81C6,U+82A5,U+82AA,U+82DE,U+8317,U+8343,U+835E,U+8364,U+836A,U+853A,U+8543,U+854A,U+8559,U+8568,U+85B0,U+85B9,U+864F,U+86E4,U+8715,U+8845,U+8884,U+88E8,U+88F1,U+8C1F,U+8C82,U+8D58,U+8DBE,U+8F98,U+9035,U+9074,U+90A1,U+9149,U+9157,U+93D6,U+94C2,U+94E4,U+9570,U+95EB,U+95F0,U+9611,U+9619,U+9642,U+968D,U+9706,U+970F,U+97ED,U+988A,U+98E8,U+9A77,U+9AA1,U+9ABC,U+9CDD,U+9E2F,U+9E33,U+9E44,U+9E5C,U+9EDD} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/34.woff2") format("woff2");unicode-range:U+4F58,U+4F6C,U+4FD0,U+5156,U+51BD,U+5315,U+5323,U+535E,U+53F1,U+540F,U+542D,U+5431,U+545B,U+547B,U+548E,U+549A,U+54AB,U+54FC,U+5509,U+5567,U+5600,U+5618,U+563F,U+5669,U+56F1,U+56FF,U+573B,U+574D,U+579B,U+57B8,U+57C2,U+586C,U+58F9,U+595A,U+5993,U+5996,U+59D7,U+59E3,U+5B62,U+5B7A,U+5BA6,U+5C4E,U+5C96,U+5CE5,U+5F99,U+602F,U+6059,U+606C,U+60B8,U+60ED,U+60F0,U+61A9,U+620C,U+6249,U+62A8,U+62ED,U+62FD,U+6342,U+63A3,U+6402,U+6413,U+642A,U+6487,U+64A9,U+64AC,U+64AE,U+64B7,U+659F,U+667E,U+66F3,U+67B8,U+67E0,U+6886,U+6963,U+69DF,U+6AAC,U+6B86,U+6C50,U+6C5E,U+6C76,U+6C85,U+6C8C,U+6CDE,U+6CEF,U+6D19,U+6D52,U+6DA7,U+6DB8,U+6DC5,U+6E1A,U+6E25,U+6E4D,U+6EA7,U+6EC1,U+6F31,U+6F7A,U+6FA7,U+6FE1,U+701B,U+70AB,U+70F7,U+7166,U+72E1,U+7315,U+736D,U+73C0,U+73C2,U+740F,U+7600-7601,U+768B,U+76BF,U+76D4,U+7728,U+776C,U+77A0,U+77B0,U+77F8,U+77FE,U+783A,U+78D0,U+78FA,U+7977,U+7A37,U+7A92,U+7AFA,U+7B94,U+7CEF,U+7F28,U+7FB2,U+7FB9,U+7FE1,U+808B,U+80E5,U+80EB,U+8113,U+812F,U+81C3,U+8235,U+82D4,U+8309,U+83C1,U+8431,U+8469,U+84BF,U+84D3,U+84DF,U+8511,U+8638,U+86C0,U+8757,U+8822,U+8882,U+8885,U+8892,U+88F3,U+892A,U+8983,U+8BA5,U+8BD9,U+8BE0-8BE1,U+8BE7,U+8C1A,U+8C27,U+8C5A,U+8D4A,U+8DDA,U+8E0C,U+8E52,U+8E74,U+8E87,U+8FC2,U+8FE6,U+900D,U+9068,U+90AC,U+90B3,U+90E7,U+9119,U+915A,U+916E,U+949D,U+94B4,U+94D0,U+94E2-94E3,U+94EC,U+9522,U+9556,U+965B,U+9774,U+9893,U+9981,U+998D,U+998F,U+9A6E,U+9A7F,U+9A87,U+9A8A,U+9B13,U+9C9F,U+9E3E,U+9E43,U+9E6D,U+9E92,U+9E9D,U+9EDB} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/33.woff2") format("woff2");unicode-range:U+4E10,U+4E56,U+4E98,U+4F3A,U+4F5F,U+4F70,U+4F88,U+4F97,U+4FA5,U+4FE8,U+5014,U+504C,U+524C,U+52FA,U+5364,U+53E8,U+5406,U+5471,U+54DF,U+54E6,U+54EE,U+556A,U+557C,U+5583,U+55DC,U+55E3,U+566C,U+592F,U+5944,U+5983,U+598D,U+59CA,U+5A13,U+5A7F,U+5B09,U+5E4C,U+5EB6,U+5F08,U+5F1B,U+607A,U+608D,U+60CB,U+61A7,U+61AC,U+62A0,U+62C4,U+62EE,U+62F4,U+6345,U+6382,U+6396,U+63C9,U+63EA,U+6400,U+645E,U+6556,U+6593,U+65A1,U+6615,U+66E6,U+67DA,U+6805,U+68A2,U+69AD,U+69DB,U+6A80,U+6A90,U+6B83,U+6BE1,U+6C30,U+6CAD,U+6CB1,U+6CF1,U+6D31,U+6D93,U+6DAE,U+6DBF,U+6DC6,U+6E0D,U+6E3A,U+6E5F,U+6E85,U+6EBA,U+6F5E,U+6F7C,U+717D,U+71A8,U+71EE,U+7252,U+72B7,U+72C4,U+72E9,U+73AE,U+73BA,U+73D1,U+7435-7436,U+7459-745A,U+747E,U+7487,U+74E2,U+7504,U+752C-752D,U+7599,U+759F,U+75A4,U+75CA,U+75F0,U+761F,U+7629,U+772F,U+777F,U+7785,U+77A5,U+77BF,U+78D5,U+7934,U+7940,U+79A7,U+7B19,U+7B71,U+7C95,U+7CB1,U+7CE0,U+7ECA,U+7EF7,U+7F2B,U+7F81,U+8046,U+8110,U+8148,U+814C,U+8165,U+819B,U+81BA,U+828B,U+82AE,U+82B7,U+82D3,U+8301,U+830E,U+831C,U+8338,U+837C,U+8393,U+83BA,U+84E6,U+853C,U+8654,U+86DB,U+86FE,U+8712,U+873F,U+874E,U+8783,U+8859,U+88A4,U+8925,U+8BB7,U+8BFD,U+8BFF,U+8C1B,U+8C24,U+8C2C,U+8D4E,U+8D61,U+8D66,U+8F76,U+8F8A,U+8FE5,U+8FF8,U+90B8,U+912F,U+9131,U+9163,U+9170,U+91DC,U+949B,U+94A8,U+94C0,U+94FF,U+9525,U+9535,U+9539,U+954C,U+9550,U+955B,U+962A,U+9685,U+96CC,U+96F9,U+988D,U+9975,U+9985,U+9AB8,U+9C7F,U+9CA4,U+9CB8,U+9E25,U+9E35,U+9E4A,U+9E8B,U+9EEF} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/32.woff2") format("woff2");unicode-range:U+4EA2,U+4EA5,U+4EC3,U+4F36,U+4F84,U+4F8D,U+501A,U+5029,U+51A5,U+51C4,U+51F8,U+5201,U+5321,U+5352,U+5366,U+53E9,U+543C,U+545C,U+5480,U+5495,U+54B3,U+54C7,U+5632,U+5676,U+56B7,U+56DA,U+56E4,U+5703,U+5742,U+57A2-57A3,U+5815,U+58D1,U+5919,U+592D,U+5955,U+59E5,U+5A34,U+5B70,U+5B75,U+5BD0,U+5BF0,U+5C41,U+5C91,U+5C94,U+5CE6,U+5CED,U+5D69,U+5DC5,U+5E16,U+5EB5,U+5F3C,U+5F95,U+6020,U+604D,U+6055,U+60E6,U+6123,U+618B,U+61CB,U+62CE,U+62D9,U+62E7,U+631F,U+634E,U+6452,U+6479,U+6482,U+64D2,U+655B,U+660A,U+664F,U+672D,U+675E,U+67C4,U+6809,U+6853,U+6868,U+68F1,U+68FA,U+693F,U+695E,U+69B4,U+6A71,U+6B89,U+6BCB,U+6BD9,U+6C40,U+6CF8,U+6D85,U+6DA3,U+6DAA,U+6DC7,U+6E0E,U+6E43,U+6F3E,U+6F88,U+6FEE,U+70D9,U+7109,U+7184,U+722A,U+733F,U+73F2,U+7409,U+748B,U+749C,U+749E,U+759A,U+75A1,U+75D2,U+75EB,U+7620,U+766B,U+7738,U+773A,U+77AA,U+78BE,U+795B,U+796F,U+7A20,U+7A96,U+7AA5,U+7B03,U+7B28,U+7B50,U+7B77,U+7BF1,U+7C27,U+7C38,U+7EAD,U+7EC5,U+7EF0,U+7EFD,U+7F0E,U+7F2E,U+7F79,U+7F9A,U+7FCC,U+8098,U+80DA,U+80E7,U+80F0,U+80F3,U+8237-8238,U+8299,U+82CE,U+8398,U+83BD,U+83E0,U+83E9,U+8426,U+8475,U+85D5,U+85E9,U+86DF,U+871A,U+888D,U+8910,U+891A,U+8BB4,U+8BE3,U+8BEC,U+8BF2,U+8C06,U+8C0D,U+8C19,U+8DB4,U+8DE4,U+8E1E,U+8E66,U+8E6C,U+8F84,U+8F97,U+901E,U+9083,U+90E1,U+90F4,U+9165,U+91C9,U+94B0,U+94B3,U+94E8,U+94F5,U+9504,U+9530,U+9532,U+954D,U+956D,U+95FA,U+9668,U+96BD,U+9704,U+9773,U+9776,U+9890,U+997A,U+9A6F,U+9A8B,U+9AA5,U+9CC4} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/31.woff2") format("woff2");unicode-range:U+4FA3,U+4FAE,U+4FD8,U+516E,U+5189,U+5195,U+51DB,U+5228,U+527F,U+5288,U+5450,U+5484,U+5490,U+54C9,U+54E9,U+5501,U+56A3,U+56BC,U+5729,U+575E,U+589F,U+5984,U+5A04-5A05,U+5A25,U+5A36,U+5A77,U+5A9B,U+5AB2,U+5BC7,U+5BDD,U+5C51,U+5C79,U+5CD9,U+5D0E,U+5DEB,U+5E3C,U+5E87,U+5ED3,U+5F13,U+5F27,U+5F64,U+5FFB,U+6096,U+60EB,U+60F6,U+6151,U+61A8,U+620D-620E,U+6241,U+6273,U+627C,U+6289,U+62C7,U+62CC,U+634D,U+6363,U+63B7,U+6518,U+66AE,U+6726,U+6756,U+6789,U+6829,U+6862,U+6866,U+6893,U+6897,U+68E3,U+6942,U+6995,U+69A8,U+69CC,U+6A1F,U+6A44,U+6A59,U+6BD3,U+6C13,U+6CBD,U+6CD3,U+6CD7,U+6CFE,U+6D9F,U+6DA1,U+6E44,U+6EA5,U+6EE6,U+6F2F,U+6F8E,U+701A,U+7078,U+7095,U+7099,U+709C,U+70AF,U+70BD,U+70DB,U+70E8,U+70FD,U+714E,U+715E,U+71CE,U+7235,U+7239,U+72D0,U+72F8,U+745C,U+7480,U+74A7-74A8,U+74E3,U+75AE,U+75F9,U+7693,U+76CF,U+776B,U+778E,U+77E3,U+7852,U+7948,U+7960,U+797A,U+79BA,U+79BE,U+79C3,U+79C6,U+79F8,U+7A8D,U+7AA6,U+7AFF,U+7BC6,U+7CD9,U+7D0A,U+7D6E,U+7EE2,U+7EEE,U+7F24,U+7F2D,U+7FD8,U+800D,U+80FA,U+8151,U+818A,U+81B3,U+81E7,U+82B8,U+82C7,U+82DB,U+8339,U+837B,U+8386,U+83CF,U+85C9,U+85DC,U+8611,U+868C,U+8747,U+8749,U+8774,U+88F4,U+8912,U+8B6C,U+8BBD,U+8C0E,U+8D31,U+8D3B,U+8D48,U+8E35,U+8E4A,U+8F99,U+8FE9,U+9017,U+914C,U+918B,U+94A0,U+94CE,U+951A,U+952F,U+9541,U+95F5,U+9640,U+9672,U+968B,U+9698,U+96CD,U+96EF,U+9713,U+97EC,U+9885,U+996A,U+9992,U+9A74,U+9A79,U+9CBB,U+9CCC,U+9CD7,U+9CDE,U+9E93} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/30.woff2") format("woff2");unicode-range:U+4E4D,U+4E5E,U+4FEF,U+50A3,U+51F3,U+51F9,U+5203,U+52FF-5300,U+532E,U+533E-533F,U+5351,U+53DB,U+53ED,U+543B,U+5455,U+548B,U+54D7,U+5507,U+5543,U+5578,U+55D3,U+560E,U+5830,U+58A9,U+5962,U+59E8,U+59EC,U+5A07,U+5A23,U+5A3C,U+5A9A,U+5AC2,U+5AD6,U+5B5A,U+5C2C,U+5C34,U+5CB1,U+5CE8,U+5CEA,U+5F87,U+5FE1,U+5FF1,U+601C,U+6064,U+606A,U+60DF,U+60F9,U+6177,U+6252,U+62C2,U+62E3,U+62EF,U+62F1,U+634F,U+6361,U+63E3,U+6405,U+6495,U+6512,U+6577,U+65A9,U+65F7,U+6627,U+6655,U+6714,U+6795,U+67FF,U+6813,U+68D5,U+690E,U+6977,U+6984,U+69BB,U+69D0,U+6A31,U+6B7C,U+6BA1,U+6C72,U+6C79,U+6C81,U+6C90,U+6C93,U+6CA6,U+6CBC,U+6CCC,U+6CFB,U+6D4A,U+6D5A,U+6DA9,U+6DCC,U+6E2D,U+6EAF,U+6EC7,U+6F13,U+6F62,U+6FA1,U+7011,U+707C,U+70D8,U+71A0,U+7280-7281,U+7357,U+7384,U+73AB,U+7410,U+745B,U+7578,U+75EA,U+7682,U+76B1,U+76CE,U+7736,U+77D7,U+77E2,U+77EB,U+780C,U+781A,U+789F,U+7941,U+7A98,U+7A9C,U+7B1B,U+7BF7,U+7C07,U+7CA5,U+7E82,U+7EAB,U+7EDA,U+7EDE,U+7F9E,U+7FDF,U+7FE9,U+803D,U+80AA,U+80B4,U+8116,U+813E,U+8155,U+817B,U+819D,U+8205,U+821C,U+82B9,U+82DF,U+82EF,U+8304,U+832C,U+8335,U+83B9,U+8446,U+846B,U+8587,U+85AF,U+85FB,U+8682,U+868A,U+86D9,U+86EE,U+8862,U+889C,U+88D4,U+88D8,U+88F8,U+895F,U+8A79,U+8BB6,U+8BF5,U+8C41,U+8C79,U+8D3C,U+8D50,U+8DCB,U+8DEA,U+8E29,U+8E44,U+8EAC,U+8F8D,U+8FE2,U+90F8,U+916F,U+9175-9176,U+948A,U+94F2,U+9661,U+9699,U+9761,U+97A0,U+9A6D,U+9A85,U+9A8F,U+9E26,U+9F9F} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/29.woff2") format("woff2");unicode-range:U+4EA8,U+4EC7,U+4ED5,U+4F51,U+4F63,U+4FA0,U+5018,U+5026,U+508D,U+50DA,U+50E7,U+515C,U+51A4,U+51FF,U+5254,U+5375,U+538C,U+53EE,U+541F,U+5435,U+5475,U+548F,U+54FA,U+5587,U+5589,U+5598,U+55A7,U+575F-5760,U+576F,U+5777,U+57AE,U+5937,U+5978,U+5A1F,U+5A49,U+5AB3,U+5BB5,U+5BC5,U+5BDE,U+5BE1,U+5BE5,U+5C09,U+5C7F,U+5E18,U+5F57,U+5F5D,U+6016,U+6021,U+606D,U+60D5,U+60E7,U+614C,U+61D2,U+625B,U+6296,U+631D,U+63A0,U+6401,U+6467,U+64BC,U+64C2,U+6590,U+65A7,U+6643,U+6656,U+6687,U+66DD,U+67D1,U+6816,U+68AD,U+68CD,U+68D8,U+68E0,U+6930,U+6960,U+6BB4,U+6BD7,U+6C22,U+6C2E-6C2F,U+6C7E,U+6CA5,U+6CE3,U+6D3C,U+6DE4,U+6DF3,U+6E1D,U+6ED5,U+6F33,U+708A,U+70C1,U+70EB,U+70F9,U+711A,U+7130,U+716E,U+718F,U+7194,U+71AC,U+71E5,U+7316,U+73CA,U+7405,U+7422,U+7426,U+742A,U+745F,U+7574,U+75AF,U+763E,U+7741,U+7792,U+77A7,U+77BB,U+77EE,U+77F6,U+785D,U+78C5,U+7985,U+79E4,U+79FD,U+7A1A,U+7AD6,U+7B5D,U+7C3F,U+7C9F,U+7CB9,U+7CDF,U+7ECE,U+7EE5,U+7F09,U+7F15,U+7F2A,U+7F38,U+8038,U+803B,U+804B,U+80D6,U+817A,U+81FB,U+82AD,U+8354,U+835F,U+8367,U+841D,U+84B2,U+853D,U+8549,U+8650,U+865E,U+8681,U+8700,U+8721,U+88F9,U+89C5,U+8BB3,U+8BC0,U+8BEB,U+8C23,U+8C34,U+8D1E,U+8E72,U+8E81,U+8EAF,U+901B,U+9050,U+906E,U+9091,U+90AF,U+914B,U+9499,U+94BE,U+94EE,U+950C,U+9524,U+952D,U+9540,U+9576,U+9600,U+962E,U+9647,U+96CF,U+9716,U+97E7,U+97F6,U+98A0,U+98A4,U+9A7C,U+9A86,U+9AE6,U+9CD6,U+9E3D} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/28.woff2") format("woff2");unicode-range:U+4E19,U+4E38,U+4EC6,U+4ED1,U+4F1E,U+4F7C,U+4F83,U+4FD1,U+4FDE,U+4FFA,U+50BB,U+50F5,U+50FB,U+5162,U+51D1,U+5256,U+5319,U+5320,U+5384,U+5413,U+54BD,U+54D1,U+54E8,U+5582,U+561B,U+5631,U+57E0,U+58F6,U+5974,U+59AE,U+5B99,U+5B9B,U+5BA0,U+5C27,U+5C38,U+5C90,U+5D16,U+5DCD,U+5DE2,U+5E90,U+5E9A,U+5F26,U+5F6A,U+5F8A,U+5F98,U+5FCC,U+607C,U+6094,U+60BC,U+611A,U+6254,U+626F,U+6292,U+62B9,U+62E2,U+62FE,U+631A,U+6320,U+6346,U+63BA,U+6454,U+658B,U+663C,U+6652,U+6674,U+66D9,U+66F0,U+673D,U+6749,U+67AB,U+67AF,U+6817,U+6854,U+68A7,U+6986,U+6B47,U+6B49,U+6B6A,U+6C1F,U+6CAB,U+6CBE,U+6E0A,U+6E23,U+6E9C,U+6ED4,U+6F4D,U+6F84,U+72FC,U+7334,U+7470,U+7529,U+752B,U+75D5,U+75F4,U+762B,U+7696,U+7737,U+7766,U+7838,U+7845,U+788C,U+78CA,U+7901,U+796D,U+79B9,U+79E7,U+7A3C-7A3D,U+7A57,U+7A74,U+7A9F,U+7B0B,U+7B52,U+7C7D,U+7CAA,U+7CD5,U+7EAC,U+7EB6,U+7F20,U+7F69,U+7FA1,U+7FC5,U+8018,U+803F,U+8086,U+808C,U+80A2,U+80BE,U+8180,U+81ED,U+820C,U+829C,U+82AF,U+82BD,U+82D1,U+8327,U+836B,U+839E,U+83F1,U+8403,U+840C,U+840E,U+8471,U+849C,U+84C9,U+8517,U+851A,U+85E4,U+8776,U+87F9,U+88D9,U+8C05,U+8C1C,U+8D2C,U+8D2E,U+8D43,U+8D81,U+8D9F,U+8E0A,U+8E22,U+8EB2,U+8EBA,U+8F69,U+902E,U+9038,U+90AA,U+90DD,U+915D,U+9171,U+9187,U+946B,U+9489,U+94A7,U+9508,U+9523,U+9551,U+95F7,U+964C,U+96B6,U+96C0,U+971C,U+9756,U+9965,U+997F,U+99A8,U+9A73,U+9A82,U+9AD3,U+9ED4} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/27.woff2") format("woff2");unicode-range:U+4E11,U+4E53,U+4E7E,U+4E9F,U+4F50,U+502A,U+5154,U+517D,U+51F0,U+5239,U+52C9,U+52DF,U+52FE,U+5308,U+532A,U+535C,U+53A2,U+53A8,U+5496,U+54AC,U+54C0,U+54C4,U+5561,U+5566,U+5764,U+576A,U+5792,U+57AB,U+584C,U+5885,U+594E,U+5ACC,U+5B5C,U+5BC2,U+5C39,U+5C60,U+5C6F,U+5C82,U+5E37,U+5EB8,U+5F2F,U+6012,U+6068,U+6073,U+6109,U+6127,U+621A,U+626E,U+6284,U+62D0,U+6321,U+6328,U+632B,U+6349,U+6367,U+638F,U+63A9,U+655E,U+6591,U+65ED,U+660F,U+66A8,U+6735,U+674F,U+6760,U+67EF,U+6850,U+6869,U+68A8,U+68D2,U+6912,U+6BEF,U+6C28,U+6C5D,U+6C82,U+6C9B,U+6CB8,U+6CF5,U+6CFC,U+6D47,U+6D51,U+6D74,U+6D78,U+6D9D,U+6DA4,U+6DAF,U+6DC4,U+6DCB,U+6DF9,U+6E5B,U+6E89,U+6EB6,U+6F06,U+6F47,U+6F9C,U+6FD2,U+7076,U+70AC,U+70B3,U+70EF,U+7199,U+723D,U+72AC,U+72ED-72EE,U+7433,U+7476,U+754F,U+7554,U+75BC,U+7626,U+76EF,U+7720,U+7784,U+77AC,U+780D,U+786B,U+78B3,U+78F7,U+7978,U+7984,U+79C9,U+7A84,U+7C98,U+7EE3,U+7F1A,U+7FD4,U+7FF0,U+8042,U+8087,U+809A,U+80BA,U+80C3,U+810A,U+814A,U+818F,U+81C2,U+8231,U+8292,U+82A6,U+8346,U+838E,U+83C7,U+8513,U+857E,U+859B,U+867E,U+8695,U+871C,U+87BA,U+884D,U+8944,U+8A93,U+8C10,U+8D42,U+8DFB,U+8F9C,U+8FA3,U+8FA8,U+8FB1,U+9042,U+904F,U+90B5,U+9102,U+94C5,U+95F8,U+95FD,U+960E,U+964B,U+96A7,U+96C1,U+9709,U+97AD,U+97F5,U+9882,U+9976,U+9A9A,U+9B3C,U+9B41,U+9B44,U+9C8D,U+9E45,U+9E70,U+9E9F,U+9F3B,U+9F7F} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/26.woff2") format("woff2");unicode-range:U+4ED7,U+4FCF,U+4FE9,U+4FED,U+50AC,U+50B2,U+5112,U+5180,U+5188,U+51F6,U+522E,U+5265,U+52CB,U+5349,U+5367,U+5378,U+537F,U+5395,U+5398,U+53D4,U+543E,U+5440,U+5446,U+54B8,U+5580,U+55BB,U+56CA,U+572D,U+574E,U+5782,U+5784,U+58F3,U+5938,U+5948,U+59FB,U+5A1C,U+5A74,U+5AE9,U+5B55,U+5BB0,U+5BD3,U+5BF8,U+5C3F,U+5D14,U+5D2D,U+5DF7,U+5DFE,U+5E05-5E06,U+5E1C,U+5E62,U+5E7B,U+5E7D,U+5ED6,U+5F66,U+5F6C,U+5FA1,U+604B,U+609F,U+60A6,U+60E8,U+6101,U+6124,U+6148,U+61BE,U+6247,U+62D8,U+62DA,U+633D,U+635E,U+6380,U+63FD,U+64E6,U+6572,U+6691,U+67A2-67A3,U+67D4,U+680B,U+6905,U+6A0A,U+6A61,U+6B79,U+6BB7,U+6BBF,U+6C41,U+6C55,U+6C83,U+6CA7,U+6D46,U+6DC0,U+6DEB,U+6E17,U+6E24,U+6EA2,U+6EF4,U+6F6D,U+707F,U+70E4,U+710A,U+725F,U+7261,U+72F1,U+730E,U+732B,U+7538,U+7624,U+7709,U+7750,U+7779,U+7802,U+7898,U+78A7,U+78B1,U+78CB,U+7A83,U+7A91,U+7AED,U+7B3C,U+7B4B,U+7CA4,U+7EB9,U+7EF3,U+7EF5,U+7F05,U+7F55,U+7F62,U+7FC1,U+7FE0,U+806A,U+80A0,U+80A4,U+8102,U+8106,U+8154,U+8247,U+8258,U+82CD,U+8328,U+832B,U+8389,U+83CA,U+845B,U+846C,U+84B8,U+8574,U+8680,U+86C7,U+8702,U+886C,U+8896,U+88B1,U+88E4,U+8BC8,U+8C26,U+8C28,U+8C2D,U+8D4C,U+8D63,U+8F67,U+8FC4,U+9006,U+9063,U+90A2,U+90B1,U+90C1,U+9177,U+9189,U+9493,U+949E,U+9610,U+961C,U+96FE,U+978D,U+97E6,U+997C,U+9A84,U+9B45,U+9B54,U+9E64,U+9F0E,U+9F9A} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/25.woff2") format("woff2");unicode-range:U+4E08,U+4E18,U+4E1B,U+4E52,U+4E73,U+4EAD,U+4ED9,U+4EF0,U+4FA6,U+5076,U+5179,U+51BB,U+51EF,U+524A,U+526A,U+529D,U+52AB,U+5306,U+53D9,U+540A,U+5439,U+54ED,U+558A,U+55B7,U+5634,U+574A,U+57A6,U+57CB,U+57D4,U+582A,U+5835,U+5858,U+58E4,U+5951,U+5986,U+59DA,U+59FF,U+5A03,U+5A46,U+5AC1,U+5B5D,U+5BFA,U+5C18,U+5C48,U+5C61,U+5CB3,U+5E15,U+5E3D,U+5E99,U+5E9E,U+5ECA,U+5F0A,U+5F17-5F18,U+5F25,U+5F7C,U+5FCD,U+6028,U+60A0,U+60AC,U+614E,U+6155,U+6168,U+61C8,U+6212,U+6251,U+62AB-62AC,U+6323,U+632A,U+643A,U+6492,U+64B0,U+64C5,U+6602,U+6614,U+662D,U+6664,U+6676,U+6746,U+682A,U+68DA,U+6B3A,U+6B67,U+6C27,U+6C5B,U+6C64,U+6C70,U+6CAA,U+6CCA,U+6CE1,U+6D12,U+6DD1,U+6E34,U+6E7F,U+6F02,U+7092,U+7115,U+7237,U+727A,U+72C2,U+739B,U+73B2,U+743C,U+751C,U+758F,U+75B2,U+7686,U+76C6,U+76D2,U+76FC,U+775B,U+77A9,U+788E,U+7897,U+79BD,U+7A9D,U+7AE3,U+7BAD,U+7D2B,U+7EB1,U+7F06,U+7F14,U+7F1D,U+7F50,U+7FFC,U+8036,U+80BF,U+80CE,U+80F8,U+810F,U+8170,U+8179,U+819C,U+821F,U+8236,U+8273,U+829D,U+82F9,U+8305,U+83B2,U+83CC,U+8404,U+840D,U+8427,U+8482,U+8679,U+8854,U+886B,U+8BBC,U+8C6B,U+8E48,U+8F90,U+8FB0,U+9022,U+903C,U+9065,U+916C,U+917F,U+94A9,U+94C3,U+94ED,U+9510,U+953B,U+96C7,U+970D,U+9738,U+9877,U+987D,U+98D8,U+9A70,U+9A91,U+9B42,U+9B4F,U+9E2D,U+9E3F,U+9E7F,U+9F20} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/24.woff2") format("woff2");unicode-range:U+4E27,U+4E59,U+4F0F,U+4FAF,U+4FF1,U+5077,U+5085,U+5144,U+5146,U+5151,U+51AF,U+51C9,U+51CC,U+523A,U+5251,U+52D8,U+5353,U+5389,U+53F9,U+5415,U+541B,U+54F2,U+5524,U+5564,U+559D,U+575D,U+5821,U+5824,U+5893,U+5915,U+5960,U+5999,U+59A5,U+59B9,U+59C6,U+59D1,U+59DC,U+5B64,U+5B87,U+5BB4,U+5C16,U+5C9A,U+5CFB,U+5D1B,U+5DE1,U+5DE9,U+5EF7,U+5F70,U+5F79,U+6052,U+6084,U+60B2,U+60DC,U+60E9,U+6167,U+6170,U+6208,U+6291,U+629A,U+62F3,U+640F,U+642C,U+6458,U+6478,U+649E,U+6500,U+654C,U+658C,U+659C,U+65EC,U+6697,U+6734,U+679D,U+67CF,U+67DC,U+67F4,U+683D,U+6843,U+684C,U+699C,U+6C0F,U+6C1B,U+6C6A,U+6D3D,U+6D82,U+6DB5,U+6DEE,U+6E58,U+6ECB,U+6EDE,U+6EE5,U+6EE9,U+6F20,U+6F58,U+704C,U+70C2,U+718A,U+7238,U+7262,U+7272,U+7275,U+72B9,U+72D7,U+72E0,U+741B,U+7434,U+74F7,U+764C,U+7761,U+7816,U+7855,U+7891,U+7A00,U+7A3B,U+7ED2,U+7EF8,U+7F18,U+7FBD,U+8000,U+8096,U+809D,U+80A9,U+80C1,U+8109,U+817F,U+81E3,U+8206,U+8212,U+8230,U+82AC,U+8302,U+8361,U+8377,U+83F2,U+8461,U+848B,U+84EC,U+8521,U+8870,U+8877,U+8881,U+88AD,U+88C2,U+8BF1,U+8D24,U+8D2A,U+8D3E-8D3F,U+8D41,U+8D64,U+8D6B,U+8DCC,U+8E0F,U+8F70,U+8FA9,U+9003,U+901D,U+903E,U+90B9,U+90CE,U+94A6,U+94DD,U+94F8,U+95EA,U+95EF,U+95F9,U+9601,U+96D5,U+971E,U+9896-9897,U+9972,U+9A7E,U+9E1F} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/23.woff2") format("woff2");unicode-range:U+4E43,U+4EA6,U+4EF2,U+4EFF,U+4F2F,U+4F69,U+4FCA,U+4FD7,U+5021,U+51B6,U+51C0,U+51DD,U+51E4,U+5200,U+5362,U+5401,U+54A8,U+5609,U+5723,U+575B,U+57C3,U+5899,U+58A8,U+58C1,U+5976,U+5988,U+59BB,U+59D0,U+5A18,U+5A31,U+5B54,U+5B5F,U+5B85,U+5BAA,U+5BD2,U+5BE8,U+5BFF,U+5C65,U+5CA9,U+5CAD,U+5D07,U+5E1D,U+5E7C,U+5F6D,U+5F92,U+5FBD,U+6050,U+6108,U+6234,U+626D,U+62B1,U+62BC,U+62D2,U+62DC,U+6324,U+63ED,U+6447,U+64A4,U+6566,U+65C1,U+65E8,U+65F1,U+6606,U+6628,U+6668,U+6696,U+66F9,U+66FC,U+6717,U+675C,U+676D,U+679A,U+67AA,U+67EC,U+67F3,U+6842,U+6851,U+6B20,U+6B23,U+6B32,U+6BC1,U+6BC5,U+6C57,U+6CEA,U+6CF3,U+6D53,U+6D66,U+6D69,U+6D9B,U+6DB2,U+6DE1,U+6EAA,U+6EE8,U+6F2B,U+706D,U+7089,U+708E,U+70AD-70AE,U+70B8,U+70E7,U+71C3,U+71D5,U+731B,U+73BB,U+7483,U+74DC,U+7532,U+75AB,U+75C7,U+7687,U+76D7,U+76F2,U+788D,U+78E8,U+79E6,U+7A46,U+7AF9,U+7BEE,U+7C89,U+7CD6,U+7EA4,U+7EB2,U+7EB5,U+7EBD,U+8010,U+8033,U+80C0,U+80F6,U+8138,U+81A8,U+827E,U+82B3,U+82D7,U+83B1,U+864E,U+86CB,U+888B,U+8BCA,U+8C0A,U+8C46,U+8C8C,U+8D29,U+8D4F,U+8D5A,U+8D60,U+8D62,U+8F7F,U+8F88,U+8F96,U+8F9E-8F9F,U+8FEA,U+90CA,U+9178,U+94BB,U+94DC,U+94FA,U+9505,U+950B,U+9521,U+9634,U+9655,U+966A,U+9675,U+9887,U+9971,U+9A76,U+9A97,U+9E23} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/22.woff2") format("woff2");unicode-range:U+4E01,U+4E39,U+4E3D,U+4E4C,U+4E54,U+4E95,U+4EA1,U+4EC1,U+4F10,U+4F26,U+4F5B,U+4FA8,U+4FB5,U+5192,U+51A0,U+51AC,U+51B0,U+51E1,U+51ED,U+5211,U+5242,U+52A3,U+52B2,U+52C3,U+52C7,U+52D2,U+52E4,U+539A,U+53A6,U+54E5,U+5510,U+5531,U+5761,U+5851,U+5854,U+58EE,U+593A,U+5949,U+5954,U+59D3,U+5A5A,U+5B8B,U+5B9C,U+5BAB,U+5C0A,U+5C4B,U+5CE1,U+5E10,U+5E8A,U+5E9F,U+5EC9,U+5F1F,U+5FE0,U+5FE7,U+6069,U+60A3,U+60A8,U+624E,U+6263,U+62B5,U+62D4,U+62E8,U+6350,U+63F4,U+6446,U+644A,U+6469,U+6562,U+656C,U+65D7,U+65FA,U+660C,U+6653,U+676F-6770,U+67F1,U+6881,U+6885,U+68A6,U+68CB,U+68EE,U+6B96,U+6C88,U+6CC9,U+6CE5,U+6D17,U+6D1B,U+6D59,U+6D8C,U+6DA8,U+6E14,U+6E21,U+714C,U+7259,U+7267,U+732A,U+73CD,U+7518,U+755C,U+756A,U+7586,U+76C8,U+76D0,U+76DF,U+76FE,U+77DB,U+795D,U+7965,U+79CB,U+79E9,U+7A3F,U+7A77,U+7B11,U+7EA0,U+7F34,U+7F8A,U+8015,U+8083,U+8089,U+80C6,U+80DE,U+8150,U+820D,U+8336,U+8352,U+83AB,U+8428,U+8463,U+84C4,U+852C,U+8584,U+88D5,U+8BDA,U+8BFA,U+8C0B,U+8C6A,U+8D3A,U+8D54,U+8D74,U+8D76,U+8F68,U+8F9B,U+8FBD,U+8FC8,U+8FEB,U+8FF9,U+906D,U+90A6,U+90D1,U+90ED,U+91CE,U+9526,U+9614,U+9676,U+9686,U+96EA,U+9707,U+978B,U+9881,U+996E,U+9C81,U+9E21,U+9EA6,U+9ECE,U+9F84} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/21.woff2") format("woff2");unicode-range:U+4E5D,U+4E8F,U+4EA9,U+4EEA,U+4F0A,U+4F0D,U+4F11,U+4F1F,U+4FC4,U+503A,U+507F,U+5170,U+520A,U+5218,U+5238,U+5267,U+5348,U+536B,U+5371,U+5385,U+53E4,U+5409,U+5428,U+5434,U+547C,U+56ED,U+5733,U+5766,U+5802,U+590F,U+591C,U+594B,U+5965,U+5987,U+5B59,U+5B63,U+5B81,U+5B97,U+5BBE,U+5C1A,U+5C3C,U+5C4A,U+5C81,U+5C9B,U+5CB8,U+5DDD,U+5E84,U+5E86,U+5EA7,U+5EAD,U+5F90,U+6000,U+60E0,U+626C,U+6276,U+6297,U+632F,U+6551,U+65A4,U+6625,U+671D,U+6728,U+6731,U+6768,U+6865,U+68B0,U+68C9,U+690D,U+697C,U+6B27,U+6B66,U+6B8B,U+6BD2,U+6BDB,U+6C49,U+6C61,U+6C7D,U+6C99,U+6CB9,U+6CF0,U+6CFD,U+6D0B,U+6D25,U+6D2A,U+6D32,U+6DA6,U+6E29,U+6E56,U+6E7E,U+6F6E,U+6FB3,U+707E,U+70DF,U+7164,U+7389,U+73E0,U+745E,U+74E6,U+7530,U+7537,U+7597,U+75BE,U+76AE,U+76DB,U+7763,U+77FF,U+793C,U+7956,U+7981,U+798F,U+79D8,U+79DF,U+7AE5,U+7B51,U+7B79,U+7CAE,U+7EB7-7EB8,U+7EBA,U+7F5A,U+7F6A,U+80A5,U+80E1,U+821E,U+8239,U+8270,U+82CF,U+8349,U+8363,U+836F,U+8499,U+8840,U+8857,U+8863,U+8A89,U+8D2B,U+8D2F,U+8D37,U+8D75,U+8F86,U+8F89,U+8FDD,U+9093,U+9152,U+94A2,U+94C1,U+9633,U+9646,U+9648,U+96C4,U+96E8,U+96F7,U+97E9,U+996D,U+9986,U+9999,U+9A7B,U+9AA8,U+9C7C,U+9E4F,U+9F99} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/20.woff2") format("woff2");unicode-range:U+4E03,U+4E1D,U+4E4F,U+4E61,U+4E88,U+4EAC,U+4F19,U+4F24,U+4F2A,U+4F38,U+4FC3,U+503E,U+515A,U+516B,U+5175,U+5197,U+519B-519C,U+51B7,U+52B3,U+5317,U+533B,U+534E,U+5357,U+53BF,U+53E0,U+53EC,U+541E,U+5565,U+566A,U+5706,U+571F,U+592B,U+592E,U+5939,U+594F,U+59A8,U+59D4,U+5A01,U+5B69,U+5B88,U+5BA3-5BA4,U+5BBF,U+5C3A,U+5C45,U+5DDE,U+5E2D,U+5E78,U+5E9C,U+5EB7,U+5F04,U+5F7B,U+5FB7,U+5FD9,U+602A,U+6076,U+60CA,U+6233,U+6258,U+6270,U+62A2,U+62D6,U+62E6,U+633A,U+6355,U+6398,U+63AA,U+653F,U+6597,U+65A5,U+65C5,U+65CB,U+65CF,U+664B,U+665A,U+672B,U+674E,U+6751,U+6797,U+6876,U+68AF,U+68F5,U+69FD,U+6A2A,U+6C11,U+6C5F,U+6C89,U+6CB3,U+6CBF,U+6CC4,U+6D01,U+6D45,U+6D4E,U+6D6E,U+6E20,U+6E2F,U+6E83,U+6ED1,U+706F-7070,U+70C8,U+7206,U+72AF,U+731C,U+7545,U+75C5,U+77F3,U+7A0E,U+7B5B,U+7C4D,U+7C73,U+7C97,U+7CCA,U+7EAA,U+7EFF-7F00,U+7F57,U+80B2,U+80DC,U+8131,U+817E,U+822A,U+83DC,U+84DD,U+85AA,U+88C1,U+8BDE,U+8BF8,U+8C31,U+8D21,U+8D35,U+8D38,U+8D5B,U+8D5E,U+8F74,U+8FC5,U+8FF7,U+900A,U+9080,U+90BB,U+9547,U+95FB,U+9608,U+9644,U+9662,U+9752,U+9769,U+987F,U+98DF,U+9910,U+9970,U+9C9C,U+9EC4,U+9F13,U+FF5E} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/19.woff2") format("woff2");unicode-range:U+251C,U+4E30,U+4E58,U+4E71,U+4E9A,U+4EAE,U+4EB2,U+4EBF,U+4ED3-4ED4,U+4F34,U+4F73,U+5012,U+5065,U+516D,U+517B,U+5237,U+5269,U+5272,U+52AA,U+52B1,U+5377,U+53C9,U+53F6,U+5403,U+5410,U+5438,U+5448,U+54B1,U+554A,U+5708,U+573E,U+5750-5751,U+575A,U+5783,U+57CE,U+57F9,U+586B,U+58EB,U+5956,U+5973,U+5979,U+5A92,U+5B8F,U+5BB3,U+5C71,U+5C97,U+5CF0,U+5D29,U+5DE8,U+5DF4,U+5E55,U+5E97,U+5F31,U+5F39,U+5F69,U+5FC6,U+5FD8,U+5FFD,U+6015,U+6025,U+6070,U+60D1,U+622A,U+626B,U+6298,U+629B,U+62D3,U+62F7,U+62FC,U+6316,U+6325,U+6444,U+6491,U+65E7,U+661F,U+6682,U+66F2,U+6740,U+6750,U+68B3,U+6B4C,U+6BCD,U+6BEB,U+6C38,U+6CBB,U+6CE2,U+6D3E,U+6D6A,U+6DD8,U+6EDA,U+6F5C,U+70BC,U+70E6,U+725B,U+732E,U+738B,U+73ED,U+74F6,U+75DB,U+78B0,U+7A0D,U+7A7F,U+7B14,U+7BB1,U+7C92,U+7EB3,U+7ED1,U+7EEA,U+7EFC,U+8026,U+8058,U+80A1,U+826F,U+827A,U+82E5-82E6,U+8457,U+85CF,U+866B,U+8B66,U+8BAF,U+8BD7,U+8C37,U+8D22,U+8D27,U+8D34,U+8D8B,U+8DC3,U+8F85,U+8FC1,U+9014,U+9057,U+9075,U+90AE,U+9192,U+9274,U+94AE,U+94F6,U+95F2,U+9677,U+96C5,U+96F6,U+9732,U+9888,U+989C,U+98DE,U+9EBB,U+9F50} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/18.woff2") format("woff2");unicode-range:U+4E22,U+4E25,U+4E50,U+4E94,U+4ECD,U+4F17,U+4F59,U+4FA7,U+500D,U+5141,U+514B,U+517C,U+526F,U+5343,U+5356,U+535A,U+5382,U+53F2,U+552E-552F,U+5584,U+56F0,U+56FA,U+574F,U+585E,U+5947,U+5B9D,U+5BA1,U+5BBD,U+5BC4,U+5BCC,U+5BFB,U+5C14,U+5C24,U+5C3E,U+5C4F,U+5D4C,U+5DE7,U+5E01,U+5E27,U+5E45,U+5F03,U+5F8B,U+6062,U+6089,U+60EF,U+613F,U+61C2,U+6279,U+6293,U+62C5,U+62C9,U+62DB,U+62E5,U+6302,U+635F,U+6377,U+6388,U+641E,U+653B,U+654F,U+6563,U+65AF,U+65BD,U+65E6,U+6670,U+66B4,U+66FE,U+677E,U+6821,U+6B3E,U+6B7B,U+6B8A,U+6C14,U+6C60,U+6D1E,U+6D77,U+6DF7,U+6E10,U+6EE4,U+6F0F,U+6FC0,U+706B,U+7075,U+7126,U+722C,U+7231,U+7236,U+724C,U+73A9,U+7403,U+7533,U+767B,U+76D6,U+7701,U+773C,U+77E9,U+7834,U+78C1,U+7968,U+79C0-79C1,U+7A97,U+7ADE-7ADF,U+7D27,U+7D2F,U+7E41,U+7EA2,U+7EAF,U+7ED5,U+7ED8,U+7EDD,U+7EE9,U+7FFB,U+80AF,U+8111,U+82F1,U+878D,U+8986,U+8C01,U+8C13,U+8C22,U+8D1D,U+8D26,U+8D4B,U+8DD1,U+8DDD,U+8DE8,U+8E2A,U+8F6E,U+8FDF,U+8FFD,U+9000,U+900F,U+94A5,U+94B1,U+955C,U+9632,U+9635,U+963B,U+963F,U+9690,U+9988} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/17.woff2") format("woff2");unicode-range:U+2500,U+2502,U+4E16,U+4E1C,U+4E34,U+4E45,U+4E4E,U+4E70,U+4E89,U+4EFD,U+4F30,U+4F4F,U+501F,U+504F,U+505C,U+513F,U+5145,U+5149,U+5174,U+518C,U+51B2,U+5220,U+523B,U+5339,U+534A,U+5360-5361,U+5370,U+5374,U+53CC,U+53F3,U+5427,U+5473,U+54C8,U+559C,U+5747,U+5806,U+5B98,U+5BDF,U+5C01,U+5C04,U+5C1D,U+5DE6,U+5E72,U+5EF6,U+5F84,U+6162,U+620F,U+623F,U+627F,U+62BD,U+62C6,U+62CD,U+62FF,U+6311,U+6389,U+63A2,U+641C,U+642D,U+64AD,U+64CE,U+6559,U+6599,U+65E2,U+65E9,U+6620,U+666E,U+66FF,U+6708,U+67D3,U+695A,U+6B62,U+6BD5,U+6C47,U+6C9F,U+6CDB,U+6D4F,U+6D89,U+6DFB,U+6E32,U+6F14,U+70ED,U+7591,U+767D-767E,U+76CA,U+793E,U+795E,U+79D1-79D2,U+7A33,U+7A81,U+7AD9,U+7B7E,U+7EC7,U+7F29,U+7F8E,U+8017,U+805A,U+80CC,U+811A,U+82B1,U+8425,U+843D,U+8861,U+8865,U+897F,U+89C8,U+89E6,U+8BA2,U+8BC9,U+8C08,U+8D25,U+8D2D,U+8D70,U+8DA3,U+8DF3,U+8F66,U+8F7B,U+8FED,U+9010,U+9012,U+904D,U+949F,U+9500,U+95ED,U+9669,U+9694,U+9759,U+9760,U+9876,U+987E,U+989D,U+9A6C,U+9A71,U+9AA4,U+9ED1,U+9ED8} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/16.woff2") format("woff2");unicode-range:U+4E07,U+4E32,U+4E3E,U+4E66,U+4E91,U+4EC5,U+4ED8,U+4F01,U+5047,U+514D,U+5178,U+51CF,U+51FB,U+5207,U+5212,U+521A,U+5224,U+529E,U+52BF,U+5341,U+5386,U+538B,U+53CB,U+53D7,U+53EB,U+5417,U+542B-542C,U+5468,U+56DB,U+56F4,U+56FD,U+5740,U+58F0,U+592A,U+5957,U+5BC6,U+5C3D,U+5C40,U+5E02,U+5E7F,U+5F20,U+5F52,U+5F81,U+5F85,U+5FAA,U+5FD7,U+5FF5,U+6218,U+6269,U+6295,U+62A4-62A5,U+62DF,U+62EC,U+638C,U+63CF,U+63D2,U+63E1,U+6545,U+667A,U+670B,U+671B,U+672A,U+675F,U+677F,U+6781,U+6808,U+680F,U+6811,U+6863,U+6C34,U+6E38,U+6EE1,U+719F,U+72EC,U+751A,U+7535,U+753B,U+76D1,U+76D8,U+77ED,U+7814,U+786C,U+79EF-79F0,U+79FB,U+7A76,U+7CBE,U+7EA6,U+7EC3,U+7EE7,U+7F3A,U+7F72,U+7FA4,U+8001,U+804A,U+804C,U+822C,U+8303,U+8350,U+8651,U+865A,U+867D,U+8BA8,U+8BAD,U+8BB8,U+8BC4,U+8BE6,U+8BEF,U+8D23,U+8D56,U+8D85,U+8DF5,U+8FB9,U+8FD1,U+8FDC,U+9001,U+9047,U+907F,U+91CA,U+91D1,U+9488,U+9501,U+9605,U+964D,U+969C,U+97F3,U+987A-987B,U+98CE} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/15.woff2") format("woff2");unicode-range:U+4E13,U+4E92,U+4ECB,U+4EF7,U+4F3C,U+4F4E,U+4F9D,U+4FBF,U+4FEE,U+5171,U+521D,U+5229,U+52A9,U+5347,U+534F,U+5373,U+53C8,U+53CD,U+53E5-53E6,U+53F7,U+542F,U+544A,U+5546,U+5883,U+5931,U+5934,U+5B89,U+5C5E,U+5DEE,U+5E08,U+5E0C,U+5E2E,U+5E95,U+5F02,U+5F3A,U+5F62,U+5F71,U+5F80,U+5FAE,U+5FC5,U+5FEB,U+6253,U+62E9,U+6309,U+6392,U+65E5,U+6613,U+663E,U+6742-6743,U+67D0,U+683C,U+6846,U+68C0,U+6982,U+6B22,U+6D3B,U+6DF1,U+6E05,U+7167,U+7248,U+7269,U+754C,U+7559,U+7565,U+771F,U+7840,U+79BB,U+7A7A,U+7ACB,U+7AE0,U+7B54,U+7B56,U+7BC7,U+7D20,U+7EC6,U+7EC8,U+7ECD,U+7F13,U+8054,U+81F3,U+8272,U+88C5,U+89C1-89C2,U+89C9,U+89D2,U+8BBF,U+8BCD,U+8BD1,U+8BE2,U+8D1F,U+8D28,U+8D39,U+8D8A,U+8DB3,U+8DDF,U+8EAB,U+8F6F,U+8F7D,U+8FBE,U+8FCE,U+8FD4,U+8FDE,U+8FF0,U+9002,U+901F-9020,U+903B,U+91C7,U+94FE,U+9519,U+952E,U+95E8,U+9636,U+9650,U+968F,U+96BE,U+9875,U+9884,U+9886,U+9891,U+9996} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/14.woff2") format("woff2");unicode-range:U+4E09,U+4E14,U+4E49,U+4E8C,U+4EA4,U+4EAB,U+4ECA,U+4EE4,U+4EFB,U+4F20,U+4F4D,U+4F9B,U+4FDD,U+50A8,U+50CF,U+5143,U+516C,U+51B5,U+51C6,U+51E0,U+5217,U+521B,U+533A,U+53E3,U+53F0,U+53F8,U+5404,U+540D,U+5426,U+5458,U+547D,U+54CD,U+54EA,U+56E2,U+573A,U+5757,U+57DF,U+589E,U+5907,U+591F,U+59CB,U+5BA2,U+5BB6,U+5BFC,U+5C11,U+5C55,U+5DF2,U+5E03,U+5E26,U+5E73-5E74,U+5F15,U+5F55,U+600E,U+603B,U+611F,U+624B,U+624D,U+627E,U+6301,U+6362,U+63A7-63A8,U+652F,U+6536,U+6539,U+653E,U+65AD,U+65E0,U+660E,U+666F,U+671F,U+6761,U+6790,U+6838-6839,U+6848,U+6D88,U+7247,U+72B6,U+7387,U+73AF,U+7531,U+7740,U+7B26,U+7B80,U+7D22,U+7EA7,U+7EDC,U+7EED,U+7EF4,U+81F4,U+83B7,U+89C4,U+89C6,U+8BA4,U+8BAE,U+8BB0,U+8BBA,U+8BC1,U+8BC6,U+8BDD,U+8BF7,U+8BFB,U+8D44,U+8DEF,U+8F6C,U+8F83,U+8F91,U+8F93,U+957F,U+9645,U+9664,U+96C6,U+9A8C} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/13.woff2") format("woff2");unicode-range:U+4E0E,U+4E3B,U+4E60,U+4EA7,U+4EC0,U+4ED6,U+4F18,U+4F55,U+4FE1,U+5019,U+5148,U+5168,U+5177,U+518D,U+5199,U+51B3,U+51FD,U+5219,U+522B,U+5236,U+529B,U+529F,U+5305,U+539F,U+53BB,U+53C2,U+53CA,U+53D6,U+5411,U+5462,U+54C1,U+56DE,U+57FA,U+5904,U+590D,U+5916,U+5929,U+5B50,U+5B57,U+5B8C,U+5C06,U+5C0F,U+5C42,U+5DF1,U+5E93,U+5FC3,U+6001,U+601D,U+606F,U+60C5,U+60F3,U+610F,U+6216,U+6267,U+6280,U+6307,U+64CD,U+6548,U+6574,U+672F,U+67B6,U+67E5,U+6807,U+6B21,U+6B63-6B65,U+6BB5,U+6BCF,U+6CE8,U+6D41,U+6D4B,U+6E90,U+7279,U+76F4,U+77E5,U+786E,U+793A,U+7AEF,U+7BA1,U+7EBF,U+7EC4,U+7ED9,U+7F16,U+7F51,U+7F6E,U+8003,U+88AB,U+8A00,U+8BA9,U+8BB2,U+8BE5,U+8BED,U+8BFE,U+8C61,U+8D77,U+8FD0,U+9009,U+9053,U+914D,U+961F,U+975E,U+9879,U+9AD8} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/12.woff2") format("woff2");unicode-range:U+4E1A,U+4E24,U+4E4B,U+4E8B,U+4EBA,U+4ECE,U+4F46,U+4F53,U+4F7F,U+4F8B,U+503C,U+505A,U+5165,U+5173,U+5176,U+5185,U+524D,U+52A0,U+52A8,U+5316,U+5355,U+53D8,U+53EA,U+5408,U+5668,U+56E0,U+56FE,U+5730,U+578B,U+597D,U+5B58,U+5B66,U+5B83,U+5BB9,U+5DE5,U+5E38,U+5E76,U+5E8F,U+5E94,U+5EA6,U+5EFA,U+5F00,U+5F53,U+5F88,U+5F97,U+6237,U+628A,U+63A5,U+63D0,U+6587,U+65B0,U+66F4,U+6700,U+670D,U+672C,U+673A,U+6784,U+6837,U+6A21,U+6BD4,U+6C42,U+6CA1,U+7136,U+751F,U+76EE,U+76F8,U+79CD,U+7B2C,U+7B49,U+7B97,U+7C7B,U+7CFB,U+7ECF,U+7ED3,U+7EDF,U+8005,U+81EA,U+8282,U+8868,U+8BA1,U+8BBE,U+8BD5,U+8BF4,U+8C03,U+8FD8,U+90A3,U+90E8,U+91CC-91CD,U+91CF,U+95F4} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/11.woff2") format("woff2");unicode-range:U+4E00,U+4E0A-4E0B,U+4E0D,U+4E2A,U+4E2D,U+4E3A,U+4E48,U+4E5F,U+4E86,U+4E8E,U+4E9B,U+4EE3,U+4EE5,U+4EEC,U+4EF6,U+4F1A,U+4F5C,U+4F60,U+51FA,U+5206,U+5230,U+52A1,U+53D1,U+53EF,U+540C,U+540E,U+548C,U+5728,U+591A,U+5927,U+5982,U+5B9A,U+5B9E,U+5BF9,U+5C31,U+5F0F,U+6027,U+6210-6211,U+6240,U+636E,U+6570,U+65B9,U+65F6,U+662F,U+6709,U+6765,U+679C,U+6CD5,U+70B9,U+73B0,U+7406,U+7528,U+7684,U+770B,U+7801,U+7A0B,U+800C,U+80FD,U+884C,U+8981,U+89E3,U+8FC7,U+8FD9,U+8FDB,U+901A,U+90FD,U+95EE,U+9700,U+9762,U+9898} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/10.woff2") format("woff2");unicode-range:U+2026,U+3001-3002,U+300A-300B,U+FF01,U+FF08-FF09,U+FF0D,U+FF1A-FF1B,U+FF1F,U+FF5C} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/9.woff2") format("woff2");unicode-range:U+409,U+40F,U+418,U+41F,U+427-428,U+42B} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/8.woff2") format("woff2");unicode-range:U+3CD,U+1F45} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/7.woff2") format("woff2");unicode-range:U+374-375,U+37E,U+384-385,U+387,U+389-38A,U+38F-390,U+393-394,U+39E,U+3A3,U+3A6,U+3A8-3A9,U+3AC,U+3B0-3B1,U+3B3,U+3B6,U+3B8,U+3BA,U+3BD-3BE,U+3C0,U+3C2-3C4,U+3C7,U+3CB,U+3D1,U+3D6,U+402-403,U+40A,U+40E,U+411,U+413-414,U+423-424,U+426,U+429,U+42C,U+431,U+433-434,U+436-437,U+43A,U+442,U+446,U+449-44A,U+44C,U+453,U+459-45A,U+45C,U+462-463,U+46A-46B,U+474-475,U+490-491,U+493,U+497,U+49B,U+49D,U+4A2-4A3,U+4B0-4B1,U+4B6-4B7,U+4BA,U+4CA,U+4EE} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/6.woff2") format("woff2");unicode-range:U+259,U+2B0,U+2B2-2B3,U+2B7-2B8,U+2BB-2BC,U+2C6-2C7,U+2C9-2CB,U+2D8-2DD,U+2E1-2E3} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/5.woff2") format("woff2");unicode-range:U+18A,U+192} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/4.woff2") format("woff2");unicode-range:U+135,U+140} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/3.woff2") format("woff2");unicode-range:U+F0,U+F7} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/2.woff2") format("woff2");unicode-range:U+A0-AA,U+AC-B1,U+B4-BF,U+C3,U+D7} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/1.woff2") format("woff2");unicode-range:U+7B-7F} +@font-face{font-family:"MiSans";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/misans-webfont-4.3.1/bold/0.woff2") format("woff2");unicode-range:U+0-1,U+A,U+D,U+20-7A,U+AB,U+B2-B3,U+C0-C2,U+C4-D6,U+D8-EF,U+F1-F6,U+F8-134,U+136-13F,U+141-17F,U+181,U+18F,U+198-199,U+1A0-1A1,U+1AF-1B0,U+1B3-1B4,U+1B7,U+1CD-1DC,U+1E4-1E9,U+1EE-1F0,U+1F9-1FF,U+218-21B,U+21E-21F,U+237,U+251,U+253,U+257,U+261,U+292,U+386,U+388,U+38C,U+38E,U+391-392,U+395-39D,U+39F-3A1,U+3A4-3A5,U+3A7,U+3AA-3AB,U+3AD-3AF,U+3B2,U+3B4-3B5,U+3B7,U+3B9,U+3BB-3BC,U+3BF,U+3C1,U+3C5-3C6,U+3C8-3CA,U+3CC,U+3CE,U+3D2-3D4,U+400-401,U+404-408,U+40B-40D,U+410,U+412,U+415-417,U+419-41E,U+420-422,U+425,U+42A,U+42D-430,U+432,U+435,U+438-439,U+43B-441,U+443-445,U+447-448,U+44B,U+44D-452,U+454-458,U+45B,U+45D-45F,U+472-473,U+492,U+49A,U+49C,U+4AE-4AF,U+4B2-4B3,U+4B8-4B9,U+4BB,U+4D8-4D9,U+4E2-4E3,U+4E8-4E9,U+4EF,U+1E00-1E01,U+1E3E-1E3F,U+1E80-1E85,U+1E9E,U+1EA0-1EF9,U+2013-2014,U+2018-2019,U+201C-201D,U+2044,U+2070,U+2074-2079,U+2080,U+2082-2089,U+20A8,U+FF0C} diff --git a/src/glass-reveal.ts b/src/glass-reveal.ts new file mode 100644 index 0000000..042acef --- /dev/null +++ b/src/glass-reveal.ts @@ -0,0 +1,45 @@ +// A feathered clearing front travels from the top to the bottom of the cover. +// Progress endpoints are wholly frosted / wholly clear, including the rim. +const FEATHER = 0.12; +export const FROSTED_ROUGHNESS = 0.42; +export const CLEAR_ROUGHNESS = 0.025; +const FROST_SPAN = 0.016; + +// Three's default mip rule grows blur relative to an object as the viewport +// shrinks. Bound the frosted footprint to 1.6% of the projected cover height. +export function frostedTransmissionLod( + panelPixels: number, + textureWidth: number, + roughness: number, + quality = 1, + ior = 1.46, +) { + const clamp = (v: number) => Math.max(0, Math.min(1, v)); + const strength = clamp((roughness - CLEAR_ROUGHNESS) / (FROSTED_ROUGHNESS - CLEAR_ROUGHNESS)); + const native = Math.log2(textureWidth) * roughness * clamp(ior * 2 - 2); + const clearLod = Math.log2(textureWidth) * CLEAR_ROUGHNESS * clamp(ior * 2 - 2); + const bounded = Math.log2(Math.max(2 ** clearLod, panelPixels * FROST_SPAN * Math.pow(strength, 1.15))); + return native + (Math.min(native, bounded) - native) * quality; +} +export const frostedTransmissionGLSL = ` +float archiveTransmissionLod(float roughness, float ior, vec2 samplerSize) { + float nativeLod = log2(samplerSize.x) * roughness * clamp(ior * 2.0 - 2.0, 0.0, 1.0); + float strength = clamp((roughness - ${CLEAR_ROUGHNESS}) / ${FROSTED_ROUGHNESS - CLEAR_ROUGHNESS}, 0.0, 1.0); + float panelPixels = length(vArchiveProjectedAxis * samplerSize); + float clearLod = log2(samplerSize.x) * ${CLEAR_ROUGHNESS} * clamp(ior * 2.0 - 2.0, 0.0, 1.0); + float boundedLod = log2(max(exp2(clearLod), panelPixels * ${FROST_SPAN} * pow(strength, 1.15))); + return mix(nativeLod, min(nativeLod, boundedLod), archiveQuality); +}`; +export function glassRevealAtHeight(progress: number, height: number) { + const edge = 1 - (1 + 2 * FEATHER) * Math.max(0, Math.min(1, progress)); + const x = Math.max( + 0, + Math.min(1, (Math.max(0, Math.min(1, height)) - edge) / (2 * FEATHER)), + ); + return x * x * (3 - 2 * x); +} +export const glassRevealGLSL = ` +float glassRevealAtHeight(float progress, float height) { + float edge = 1.0 - ${1 + 2 * FEATHER} * clamp(progress, 0.0, 1.0); + return smoothstep(edge, edge + ${2 * FEATHER}, clamp(height, 0.0, 1.0)); +}`; diff --git a/src/html.ts b/src/html.ts new file mode 100644 index 0000000..8b4d0f1 --- /dev/null +++ b/src/html.ts @@ -0,0 +1,17 @@ +/** Archive fields are plain text, including inside HTML attributes. */ +export function escapeHtml(value: string) { + return value.replace(/[&<>"']/g, (character) => { + switch (character) { + case "&": + return "&"; + case "<": + return "<"; + case ">": + return ">"; + case '"': + return """; + default: + return "'"; + } + }); +} diff --git a/src/hud-projection.ts b/src/hud-projection.ts new file mode 100644 index 0000000..36278f2 --- /dev/null +++ b/src/hud-projection.ts @@ -0,0 +1,107 @@ +export type HudPoint = { x: number; y: number }; + +/** Radial HUD surface with a bounded frame; tracking varies tangential terms. */ +export function projectHudPoint(point: HudPoint, width: number, height: number, depth: number, pointer: HudPoint): HudPoint { + const aspect = width / height; + const x = (point.x / width * 2 - 1) * aspect, y = point.y / height * 2 - 1; + const radius = x * x + y * y, curvature = depth * .08; + const fit = 1 / (1 + curvature * (aspect * aspect + 1)); + const cx = pointer.x * .0075 * depth, cy = pointer.y * .0075 * depth; + const tx = 2 * cy * x * y + cx * (radius + 2 * x * x); + const ty = cy * (radius + 2 * y * y) + 2 * cx * x * y; + return { x: width / 2 + (x * (1 + curvature * radius) - tx) * fit * height / 2, + y: height / 2 + (y * (1 + curvature * radius) - ty) * fit * height / 2 }; +} + +/** Project a DOM box to four corners; browser hit testing follows the trapezoid. */ +export function hudQuadMatrix(width: number, height: number, quad: HudPoint[]): number[] { + const [a, b, c, d] = quad; + const dx1 = b.x - c.x, dx2 = d.x - c.x, dx3 = a.x - b.x + c.x - d.x; + const dy1 = b.y - c.y, dy2 = d.y - c.y, dy3 = a.y - b.y + c.y - d.y; + const determinant = dx1 * dy2 - dx2 * dy1; + const g = Math.abs(determinant) > 1e-9 ? (dx3 * dy2 - dx2 * dy3) / determinant : 0; + const h = Math.abs(determinant) > 1e-9 ? (dx1 * dy3 - dx3 * dy1) / determinant : 0; + return [(b.x - a.x + g * b.x) / width, (b.y - a.y + g * b.y) / width, 0, g / width, + (d.x - a.x + h * d.x) / height, (d.y - a.y + h * d.y) / height, 0, h / height, + 0, 0, 1, 0, a.x, a.y, 0, 1]; +} + +type HudPanel = { node: HTMLElement; width: number; height: number; origin: HudPoint; corners: HudPoint[] }; +const bootPanels = ".access-text, .boot-logo, .auth-status, .scan, .welcome"; +const panels = ".brand, .system-nav, .system-footer > span, .system-footer > button, .powered, .wb-overview, .wb-module, .wb-nav > button, .archive-callout, .archive-counter, .archive-navigation, .column-navigation, .archive-hint, .detail-content, .back-button, .object-caption, .relay-entry, .relay-heading, .relay-actions"; + +export class HudProjection { + private nodes: HTMLElement[]; + private measured: HudPanel[] = []; + private dirty = true; + private width = 1920; + private height = 1080; + private layout = ""; + private observer: ResizeObserver; + constructor(private stage: HTMLElement) { + this.nodes = [...stage.querySelectorAll(`${panels}, ${bootPanels}`)]; + this.nodes.forEach(node => node.classList.add("hud-surface")); + this.observer = new ResizeObserver(() => this.invalidate()); + this.observer.observe(stage); + this.nodes.forEach(node => this.observer.observe(node)); + document.fonts.ready.then(() => this.invalidate()); + } + invalidate() { this.dirty = true; } + private measure() { + // Read rest poses without the HUD transform, retaining native responsive transforms. + const enabled = this.stage.dataset.hudDepth; + this.stage.dataset.hudDepth = "false"; + const stageRect = this.stage.getBoundingClientRect(); + this.width = this.stage.offsetWidth; this.height = this.stage.offsetHeight; + const scale = stageRect.width / this.width; + this.measured = []; + const styles = new Map(); + const readStyle = (node: HTMLElement) => { + if (!styles.has(node)) styles.set(node, getComputedStyle(node)); + return styles.get(node)!; + }; + const boot = this.stage.dataset.mode === "boot"; + for (const node of this.nodes) { + if (boot && !node.matches(`${bootPanels}, .brand, .powered`)) continue; + if (!boot && node.matches(bootPanels)) continue; + if (!node.offsetWidth || !node.offsetHeight || !node.getClientRects().length) continue; + const style = readStyle(node), rect = node.getBoundingClientRect(); + const matrix = new DOMMatrixReadOnly(style.transform === "none" ? undefined : style.transform); + const [ox, oy] = style.transformOrigin.split(" ").map(parseFloat); + const width = node.offsetWidth, height = node.offsetHeight; + const corners = [{ x: 0, y: 0 }, { x: width, y: 0 }, { x: width, y: height }, { x: 0, y: height }].map(p => { + const q = matrix.transformPoint({ x: p.x - ox, y: p.y - oy }); + return { x: q.x / q.w + ox, y: q.y / q.w + oy }; + }); + // The workbench's separate entrance translation continues around this + // static projection; do not bake an in-flight 9px offset into the rest pose. + const entrance = { x: 0, y: 0 }; + for (let ancestor: HTMLElement | null = node; ancestor && ancestor !== this.stage; ancestor = ancestor.parentElement) { + const translate = readStyle(ancestor).translate.split(" "); + entrance.x += parseFloat(translate[0]) || 0; + entrance.y += parseFloat(translate[1]) || 0; + } + const origin = { x: (rect.left - stageRect.left) / scale - entrance.x - Math.min(...corners.map(p => p.x)), y: (rect.top - stageRect.top) / scale - entrance.y - Math.min(...corners.map(p => p.y)) }; + this.measured.push({ node, width, height, origin, corners: corners.map(p => ({ x: p.x + origin.x, y: p.y + origin.y })) }); + } + this.stage.dataset.hudDepth = enabled ?? "false"; + this.dirty = false; + } + update(depth: number, pointer: HudPoint) { + const layout = `${this.stage.dataset.layout}/${this.stage.dataset.mode}/${this.stage.dataset.workbench}`; + if (layout !== this.layout) { this.layout = layout; this.invalidate(); } + if (depth < .00001) { this.stage.dataset.hudDepth = "false"; return; } + // Boot transforms and text dimensions follow the authored timeline each frame. + // Measure that current pose before composing the HUD, never cache an in-flight scale. + if (this.dirty || this.stage.dataset.mode === "boot") this.measure(); + for (const panel of this.measured) { + const corners = panel.corners.map(point => { + const q = projectHudPoint(point, this.width, this.height, depth, pointer); + return { x: q.x - panel.origin.x, y: q.y - panel.origin.y }; + }); + const matrix = hudQuadMatrix(panel.width, panel.height, corners); + panel.node.style.setProperty("--hud-projection", `matrix3d(${matrix.map(n => Number(n.toFixed(9))).join(",")})`); + } + this.stage.dataset.hudDepth = "true"; + } +} diff --git a/src/inspection-overlay.ts b/src/inspection-overlay.ts new file mode 100644 index 0000000..06aff86 --- /dev/null +++ b/src/inspection-overlay.ts @@ -0,0 +1,65 @@ +import { + SCAN_FROM, + SCAN_TO, + SCAN_CORNERS, + type DecryptionFrame, +} from "./decryption"; + +export function inspectionSegments(frame: DecryptionFrame) { + const point = (t: number): [number, number] => [ + SCAN_FROM[0] + (SCAN_TO[0] - SCAN_FROM[0]) * t, + SCAN_FROM[1] + (SCAN_TO[1] - SCAN_FROM[1]) * t, + ]; + return frame.intervals.map(([a, b]) => [point(a), point(b)] as const); +} + +export class InspectionOverlay { + private root = document.querySelector("#inspection-marks")!; + private line = this.root.querySelector("#inspection-lines")!; + private corners = this.root.querySelector( + "#inspection-corners", + )!; + private point = + this.root.querySelector("#inspection-point")!; + private label = document.querySelector("#inspection-text")!; + + render( + frame: DecryptionFrame, + project: (x: number, y: number) => number[], + showLabel: boolean, + ) { + const host = document.querySelector("#three-scene")!; + this.root.setAttribute("viewBox", `0 0 ${host.clientWidth} ${host.clientHeight}`); + this.root.style.opacity = + frame.intervals.length || frame.markers > 0 || frame.point > 0 + ? "1" + : "0"; + this.root.dataset.phase = frame.phase; + this.root.dataset.referenceTime = frame.time.toFixed(3); + this.line.setAttribute( + "d", + inspectionSegments(frame) + .map(([a, b]) => `M${project(...a)}L${project(...b)}`) + .join(""), + ); + this.corners.style.opacity = String(frame.markers); + this.corners.innerHTML = + frame.markers > 0 + ? SCAN_CORNERS.map(([x, y]) => { + const [px, py] = project(x, y); + return ``; + }).join("") + : ""; + const [cx, cy] = project( + (SCAN_FROM[0] + SCAN_TO[0]) / 2, + (SCAN_FROM[1] + SCAN_TO[1]) / 2, + ); + this.point.setAttribute("cx", String(cx)); + this.point.setAttribute("cy", String(cy)); + this.point.style.opacity = String(frame.point); + this.label.style.opacity = String(showLabel ? frame.label : 0); + this.label.querySelector("strong")!.style.opacity = String( + frame.labelValue, + ); + } +} diff --git a/src/internal-optics.ts b/src/internal-optics.ts new file mode 100644 index 0000000..cde9470 --- /dev/null +++ b/src/internal-optics.ts @@ -0,0 +1,70 @@ +import * as THREE from "three"; + +const surfaces: Record< + string, + { color: string; roughness: number; opacity: number; order: number } +> = { + Optical_Glass_Body: { + color: "#929894", + roughness: 0.38, + opacity: 0.27, + order: 20, + }, + Optical_Glass_Roof: { + color: "#929b94", + roughness: 0.34, + opacity: 0.42, + order: 24, + }, + Optical_Glass_Edge: { + color: "#edf0e7", + roughness: 0.19, + opacity: 0.9, + order: 28, + }, + Optical_Bridge_Glass: { + color: "#a0aca2", + roughness: 0.32, + opacity: 0.36, + order: 26, + }, +}; + +export function configureInternalOptics( + name: string, + mat: THREE.MeshPhysicalMaterial, +) { + const surface = surfaces[name]; + if (!surface) return; + mat.color.set(surface.color); + mat.roughness = surface.roughness; + mat.metalness = 0.015; + mat.clearcoat = 0.42; + mat.clearcoatRoughness = 0.24; + mat.opacity = surface.opacity; + // Three's outer transmission pass samples its opaque capture and omits other + // transmissive meshes. Composite these thin frosted layers into that capture + // with explicit alpha blending, after the opaque ribs/substrate. The exterior + // cover can then refract the complete interior in both scenes. + mat.transmission = 0; + mat.transparent = false; + mat.blending = THREE.CustomBlending; + mat.blendEquation = THREE.AddEquation; + mat.blendSrc = THREE.SrcAlphaFactor; + mat.blendDst = THREE.OneMinusSrcAlphaFactor; + mat.blendSrcAlpha = THREE.OneFactor; + mat.blendDstAlpha = THREE.OneMinusSrcAlphaFactor; + mat.depthWrite = false; + // These are closed volumes; rendering both shell faces doubles their density. + mat.side = THREE.FrontSide; + mat.userData.opticalOrder = surface.order; +} + +export function internalOpticsFragment(source: string) { + // Grazing edges accumulate density while the face stays lightly frosted. + return source.replace( + "#include ", + `diffuseColor.a = min(0.86, opacity + 0.42 * pow(1.0 - abs(dot(normal, normalize(vViewPosition))), 3.0)); + #include `, + ); +} diff --git a/src/main.ts b/src/main.ts new file mode 100644 index 0000000..5e16c7f --- /dev/null +++ b/src/main.ts @@ -0,0 +1,1524 @@ +import { createRollingClock } from "./rolling-clock"; +import { InspectionOverlay } from "./inspection-overlay"; +import { DocumentDecryption } from "./document-decryption"; +import "./document-decryption.css"; +import "./decryption.css"; +import { escapeHtml } from "./html"; +import { normalizeQuality, qualityPresets, type QualityPreset, type RenderQuality } from "./render-quality"; +import { qualityMarkup, syncQualityUI } from "./quality-settings"; +import { superPerformanceQuality, wallpaperQuality } from "./wallpaper-quality"; +import "@kitlangton/rolling-number/styles.css"; +import "./style.css"; +import "./quality-settings.css"; +import "./responsive.css"; +import { viewportLayout, openingLayout } from "./viewport-layout"; +import { assetUrl } from "./asset-url"; +import { initPwa, pwaSettingsMarkup } from "./pwa"; +import { createRollingNumber, createRollingText } from "@kitlangton/rolling-number"; +import { ArchiveScene } from "./scene"; +import { ModelViewer } from "./model-viewer"; +import { ContentTransition, SurfaceTransition } from "./ui-transitions"; +import { BootSequence } from "./boot"; +import { loadBootWebfonts } from "./boot-lettering"; +import { wrap, type ArchiveNavigation } from "./archive-loop"; +import { + records, + categories, + archiveColumns, + columnFiles, + fileLocation, +} from "./data"; +import { TerminalAudio } from "./audio"; +import { audioSettingsMarkup } from "./audio-settings"; +import { isWallpaper, wallpaperHost, wallpaperFrame, type WallpaperProperties } from "./wallpaper"; +import "./startup.css"; +import "./wallpaper.css"; +import { Workbench } from "./workbench"; +let workbench: Workbench | undefined; +import { ArchivePlayground } from "./archive-playground"; +import { ARRAY_OPENING_END, openingShowsDetail } from "./wallpaper-opening"; +import { paintTheme, themeSettingsMarkup } from "./theme-ui"; +let playground: ArchivePlayground | undefined; +import { WallpaperEffects } from "./wallpaper-effects"; +import { WallpaperBackground } from "./wallpaper-background"; +let wallpaperEffects: WallpaperEffects | undefined; +import { LocalArchivePanel } from "./archive-panel"; +import { + fetchStatus, + formatBytes, + loadArchiveIndex, + openEntry, + revealEntry, + searchArchive, + type ArchiveEntry, + type ArchiveIndex, +} from "./archive-service"; +let localArchive: LocalArchivePanel; + +const $ = (selector: string) => + document.querySelector(selector)!; +import { logo, brandHeading } from "./brand"; + +$("#stage").innerHTML = ` +
+
+
+
${brandHeading}
+ + + + +
+
ACCESS
+ +
▪
+
PERMISSION AUTHORIZED
+
WELCOME TO
RHINE LAB.LLC.
INTERNAL DATABASE
+
+ + +
+
INTERNAL DATABASE / 机构档案
莱茵生命BUSINESS AREA
+ +
ARCHIVE / SELECT
01/12
+
+
COLUMN 03 / 05机构档案
+
← → 切换列 / ↑ ↓ 前后档案 / ENTER 读取
+
+ +
POWERED BY RHINE LAB
+
SESSION AUTHORIZED${isWallpaper ? '' : ''}JOYCE MOORE / 00:00:00
+ +
+
${logo}
CONNECTING TO INTERNAL DATABASE
+`; + +$("#boot-background").insertAdjacentHTML( + "beforeend", + '
', +); +const bootSequence = new BootSequence($("#stage")); +$("#viewport").insertAdjacentHTML("beforeend", ''); + +type Mode = "boot" | "archive" | "detail"; +let mode: Mode = "boot", + selected = 0, + bootStart = 0, + lastStep = "", + ready = false; +let modal: "search" | "saved" | "settings" | null = null, + searchQuery = "", + filter = "全部档案"; +let activeTab = "overview"; +const reviewParams = new URLSearchParams(location.search); +let frozenTime = + reviewParams.get("freeze") === "1" + ? Number(reviewParams.get("time") ?? 0) + : null; +if (reviewParams.get("review") === "1") { + $("#stage").dataset.review = "true"; + window.addEventListener("message", (event) => { + if ( + event.origin !== location.origin || + event.source !== window.parent || + event.data?.type !== "rhine-review-frame" + ) + return; + const t = Number(event.data.time); + if (!Number.isFinite(t) || t < 0 || t >= 35) return; + frozenTime = t; + if (ready && mode !== "boot") setMode("boot"); + }); +} +let toastTimer: ReturnType; +let previousFocus: HTMLElement | null = null; +const detailTransition = new SurfaceTransition($("#detail-ui"), undefined, 180, 180); +const tabTransition = new ContentTransition(); +let modalTransition: SurfaceTransition | undefined; +let modalClosing = false; +let modalSiblings: { node: HTMLElement; inert: boolean }[] = []; +let pendingDetailFocus = false; +let bookmarkFeedback: Animation | undefined; +function readLocal(key: string, fallback: T): T { + try { + return JSON.parse(localStorage.getItem(key) ?? "null") ?? fallback; + } catch { + return fallback; + } +} +const saved = new Set(readLocal("rhine-saved", [])); +const storedPrefs = readLocal>("rhine-settings", {}); +const prefs = { + sound: true, + music: storedPrefs.sound ?? true, + soundVolume: .55, + musicVolume: .5, + reduced: matchMedia("(prefers-reduced-motion: reduce)").matches, + quality: true, + superPerformance: false, + ...storedPrefs, + rendering: normalizeQuality(storedPrefs.rendering, storedPrefs.quality !== false), + colorTheme: storedPrefs.colorTheme === "dark" ? "dark" : "light", +}; +paintTheme(prefs.colorTheme === "dark" ? 1 : 0); +const rollingMotion = { + duration: 460, + motionBlur: true, + animated: !prefs.reduced, +}; +const updateFooterClock = createRollingClock($("#clock")); +const numberOptions = { + ...rollingMotion, + locales: "en-US", + format: { minimumIntegerDigits: 2, useGrouping: false }, +}; +const fileCounter = createRollingNumber($("#selected-number"), { + ...numberOptions, + value: 1, +}); +const columnCounter = createRollingNumber($("#column-index"), { + ...numberOptions, + value: 3, +}); +const codeOptions = { + ...numberOptions, + format: { minimumIntegerDigits: 3, useGrouping: false }, + value: 1, +}; +const textOptions = { + ...rollingMotion, + transition: "direct" as const, + stagger: "none" as const, +}; +const selectionTitle = createRollingText($("#selected-title"), { + ...textOptions, + text: $("#selected-title").textContent ?? "", +}); +const columnTitle = createRollingText($("#column-name"), { + ...textOptions, + text: $("#column-name").textContent ?? "", +}); +const hoverTitle = createRollingText($("#hover-title"), { ...textOptions, text: "" }); +const categoryTitle = createRollingText($("#archive-category"), { + ...textOptions, + text: $("#archive-category").textContent ?? "", +}); +const clearanceTitle = createRollingText($("#selected-clearance"), { + ...textOptions, + text: $("#selected-clearance").textContent ?? "", +}); +const rollingTitles = [selectionTitle, columnTitle, hoverTitle, categoryTitle, clearanceTitle]; +const selectedCode = createRollingNumber($("#selected-code"), codeOptions); +const hoverCode = createRollingNumber($("#hover-code"), codeOptions); +const audio = new TerminalAudio(); +let musicSuppressed = false; +function configureAudio() { audio.configure({ ...prefs, music: prefs.music && !musicSuppressed }); } +configureAudio(); +const reviewEntry = reviewParams.has("scene") || reviewParams.has("time") || reviewParams.get("review") === "1"; +let started = false; +const loading = $("#loading"); +// The entry screen uses the actual viewport, including portrait phones; the +// reference animation still uses its calibrated 1920 x 1080 stage. +$("#viewport").append(loading); +$("#stage").inert = true; +$(".mobile-entry").inert = true; +// 开场不再有"点击进入"门槛:应用启动后自动播放(见 beginOpening)。 +// 浏览器要求用户手势才允许启动音频设备,所以加载时先尝试解锁; +// 解锁失败则本次开场静音,并在首次真实交互时补上(armAudioUnlock), +// **不清空**用户的音效偏好。 +const ENTRY_LOGO_HOLD = 1000; +const ENTRY_UNLOCK_TIMEOUT = 1500; +let audioUnlockArmed = false; +let audioPreview = false, audioPreviewRequest = 0; +let scene: ArchiveScene | undefined; +let threeState: "on" | "closing" | "off" | "loading" = "on"; +let resumeCell: { lane: number; row: number } | undefined; +let resumeSelection = -1; +let viewer: ModelViewer | undefined; +const accessLog: { id: string; time: string }[] = []; +let columnMemory: number[] = archiveColumns.map((_, lane) => columnFiles(lane)[0] ?? 0); +function recordAccess() { + accessLog.unshift({ + id: records[selected].id, + time: new Date().toLocaleTimeString("en-GB"), + }); +} +function saveAudioPrefs() { + try { + localStorage.setItem("rhine-settings", JSON.stringify(prefs)); + } catch {} + configureAudio(); +} +function superPerformanceEnabled() { return isWallpaper ? wallpaperHost()?.properties.superperformance?.value === true : prefs.superPerformance; } +function effectiveRenderQuality() { return superPerformanceEnabled() ? superPerformanceQuality : prefs.rendering; } +function savePrefs() { + saveAudioPrefs(); + if (prefs.reduced) { + rollingTitles.forEach(title => title.finish()); + detailTransition.finish(); + modalTransition?.finish(); + tabTransition.cancel(); + bookmarkFeedback?.cancel(); + } + scene?.setReduced(prefs.reduced); + scene?.setTheme(prefs.colorTheme === "dark", prefs.reduced || !started); + document.querySelectorAll("[data-color-theme]").forEach(button => button.setAttribute("aria-pressed", String(button.dataset.colorTheme === prefs.colorTheme))); + scene?.setSuperPerformance(superPerformanceEnabled()); + viewer?.setSuperPerformance(superPerformanceEnabled()); + scene?.setQuality(effectiveRenderQuality()); + viewer?.setQuality(effectiveRenderQuality()); + syncQualityUI(prefs.rendering); + updateQualitySummary(); + fileCounter.update({ animated: !prefs.reduced && mode === "archive" }); + rollingTitles.forEach(title => title.update({ animated: !prefs.reduced && mode === "archive" })); + columnCounter.update({ animated: !prefs.reduced && mode === "archive" }); + selectedCode.update({ animated: !prefs.reduced && mode === "archive" }); + hoverCode.update({ animated: !prefs.reduced && mode === "archive" }); + $("#stage").classList.toggle("reduce-motion", prefs.reduced); + syncWallpaperBackground(); +} +let previousLayout = ""; +function fit() { + const stage = $("#stage"); + const viewport = $("#viewport"); + const coarse = matchMedia("(pointer: coarse)").matches; + const reference = reviewParams.has("time") || reviewParams.get("review") === "1"; + const { width, height, scale, kind } = mode === "boot" && !reference + ? openingLayout(viewport.clientWidth, viewport.clientHeight) + : viewportLayout(viewport.clientWidth, viewport.clientHeight, coarse, mode === "boot"); + stage.style.width = `${width}px`; + stage.style.height = `${height}px`; + stage.style.transform = `translate(-50%, -50%) scale(${scale})`; + stage.dataset.layout = kind; + stage.dataset.touch = String(coarse); + viewport.dataset.mobileBoot = String(mode === "boot" && (coarse || viewport.clientWidth < 1100)); + stage.style.setProperty("--stage-scale", String(scale)); + stage.style.setProperty("--opening-width", `${width}px`); + stage.style.setProperty("--opening-height", `${height}px`); + stage.style.setProperty("--opening-scan-scale", String(Math.min(1, width / 1920))); + stage.dataset.openingPortrait = String(width < height); + // The software keyboard resizes dialogs without recomposing the 3D scene. + const visible = window.visualViewport; + const stageTop = (viewport.clientHeight - height * scale) / 2; + stage.style.setProperty("--modal-top", `${Math.max(0, (visible?.offsetTop ?? 0) - stageTop) / scale}px`); + stage.style.setProperty("--modal-height", `${Math.min(height, (visible?.height ?? viewport.clientHeight) / scale)}px`); + $("#viewport").style.setProperty("--scale", String(scale)); + const marks = document.querySelector("#inspection-marks"); + marks?.setAttribute("viewBox", `0 0 ${width} ${height}`); + const layoutKey = JSON.stringify([width, height, scale, kind, devicePixelRatio]); + if (layoutKey !== previousLayout) { + previousLayout = layoutKey; + scene?.resize(); + viewer?.resize(); + } + updateQualitySummary(); + // Re-measure line covers and tab underline after wrapping changes. + requestAnimationFrame(() => { + documentDecryption.refresh(); + const tab = document.querySelector(".detail-tabs button.active"); + const indicator = document.querySelector(".tab-indicator"); + if (tab && indicator) indicator.style.transform = `translateX(${tab.offsetLeft}px) scaleX(${tab.offsetWidth})`; + }); +} +window.addEventListener("resize", fit); +window.visualViewport?.addEventListener("resize", fit); +window.visualViewport?.addEventListener("scroll", fit); +matchMedia("(pointer: coarse)").addEventListener("change", fit); +fit(); +// 刻度条固定窗口:一列没有条数上限,一条一个按钮会渲染几百上千个节点, +// 也会撑破竖条布局。窗口随选中位置滑动,语义与阵列的循环一致。 +const TICK_WINDOW = 8; +let fileTicks: HTMLButtonElement[] = []; +let tickLane = -1; +function tickWindow(lane: number, position: number) { + const members = columnFiles(lane); + const len = members.length; + const count = Math.min(TICK_WINDOW, len); + if (!count) return [] as { index: number; position: number }[]; + // 列不超窗口时按自然顺序铺满(1,2,3…),与改动前完全一致—— + // 否则「以选中项居中」会把短列的顺序整体转位:高亮停在正中,标签在旁边轮转。 + const start = len <= TICK_WINDOW ? 0 : position - Math.floor(count / 2); + const out: { index: number; position: number }[] = []; + for (let k = 0; k < count; k += 1) { + const pos = (((start + k) % len) + len) % len; + out.push({ index: members[pos], position: pos }); + } + return out; +} +function renderTicks(lane: number) { + const count = Math.min(TICK_WINDOW, columnFiles(lane).length); + $("#file-ticks").innerHTML = Array.from({ length: count }, () => "").join(""); + fileTicks = [...$("#file-ticks").querySelectorAll("button")]; + tickLane = lane; +} +renderTicks(fileLocation(selected).lane); + +function setMode(next: Mode) { + if (workbench?.enabled && next === "detail") next = "archive"; + const previousMode = mode; + rollingTitles.forEach(title => title.update({ animated: !prefs.reduced && next === "archive" })); + if (next !== "archive") { + rollingTitles.forEach(title => title.finish()); + hoverCode.finish(); + $("#hover-label").hidden = true; + } + if (next === "detail" && mode !== "detail") recordAccess(); + mode = next; + syncWallpaperBackground(); + audio.setScene(next); + if (next !== "boot" && audioPreview) { + audioPreview = false; + audioPreviewRequest++; + configureAudio(); + } + $("#stage").dataset.mode = next; + workbench?.syncVisibility(); + if (previousMode !== next) fit(); + $("#boot").inert = next !== "boot"; + $("#boot").setAttribute("aria-hidden", String(next !== "boot")); + $("#archive-ui").inert = next !== "archive" || Boolean(modal) || Boolean(workbench?.enabled); + $("#archive-ui").setAttribute("aria-hidden", String(next !== "archive" || Boolean(workbench?.enabled))); + $(".system-nav").inert = next === "boot" || Boolean(modal); + $(".system-footer").inert = next === "boot" || Boolean(modal); + if (next === "detail") { + if (previousMode !== "detail") detailTransition.show(prefs.reduced); + } else if (previousMode === "detail" || (next === "boot" && !$("#detail-ui").hidden)) { + pendingDetailFocus = false; + tabTransition.cancel(); + detailTransition.hide(prefs.reduced || next === "boot"); + if (!modal && next === "archive") $(".read-file").focus({ preventScroll: true }); + } + $("#detail-ui").inert = next !== "detail" || Boolean(modal); + scene?.setMode(next === "boot" ? "hidden" : next); + if (next !== "boot") { + bootSequence.reset(); + $(".file-title").firstChild!.textContent = "FILE NUMBER: "; + $("#stage").dataset.boot = "done"; + } + if (next === "detail" && previousMode !== "detail") { + renderDetail(); + pendingDetailFocus = true; + if (!scene) { + $("#detail-content").style.opacity = "1"; + $("#detail-content").style.translate = "0 0"; + $("#detail-content").inert = false; + } + } +} +function select(index: number, navigation?: ArchiveNavigation) { + selected = (index + records.length) % records.length; + columnMemory[fileLocation(selected).lane] = selected; + if (mode === "detail") setMode("archive"); + activeTab = "overview"; + scene?.select(selected, navigation); + updateSelection(navigation); + const columnMove = navigation && "axis" in navigation && navigation.axis === "lane"; + audio.play(columnMove ? "column" : "tick", columnMove ? navigation.direction * .45 : 0); +} +function stepFile(direction: number) { + const files = columnFiles(fileLocation(selected).lane); + if (files.length < 2) return; + select( + files[(files.indexOf(selected) + direction + files.length) % files.length], + { axis: "row", direction }, + ); +} +function stepColumn(direction: number) { + const lane = fileLocation(selected).lane; + const next = wrap(lane + direction, archiveColumns.length); + select(columnMemory[next], { axis: "lane", direction }); +} +function updateSelection(navigation?: ArchiveNavigation) { + const r = records[selected]; + const { lane } = fileLocation(selected); + const files = columnFiles(lane); + selectionTitle.update({ text: r.title, animated: !prefs.reduced && mode === "archive" }); + clearanceTitle.update({ text: r.clearance, animated: !prefs.reduced && mode === "archive" }); + categoryTitle.update({ text: r.category, animated: !prefs.reduced && mode === "archive" }); + const direction = + navigation && "axis" in navigation + ? navigation.direction > 0 + ? "up" + : "down" + : "auto"; + selectedCode.update({ + value: Number(r.id.slice(2)), + animated: !prefs.reduced && mode === "archive", + direction, + }); + fileCounter.update({ + value: files.indexOf(selected) + 1, + animated: !prefs.reduced && mode === "archive", + direction: + navigation && "axis" in navigation && navigation.axis === "row" + ? direction + : "auto", + }); + $(".count-total").textContent = String(files.length).padStart(2, "0"); + columnCounter.update({ + value: lane + 1, + animated: !prefs.reduced && mode === "archive", + direction: + navigation && "axis" in navigation && navigation.axis === "lane" + ? direction + : "auto", + }); + columnTitle.update({ text: archiveColumns[lane], animated: !prefs.reduced && mode === "archive" }); + $('[data-action="column-prev"]').disabled = false; + $('[data-action="column-next"]').disabled = false; + if (tickLane !== lane) renderTicks(lane); + const position = fileLocation(selected).row - 12; + tickWindow(lane, position).forEach((tick, slot) => { + const button = fileTicks[slot]; + if (!button) return; + const record = records[tick.index]; + button.dataset.select = String(tick.index); + button.setAttribute("aria-label", `选择档案 ${record.id} ${record.title}`); + button.title = `${record.id} · ${record.title}`; + button.classList.toggle("selected", tick.position === position); + button.setAttribute("aria-pressed", String(tick.position === position)); + }); + $("#saved-count").textContent = String(saved.size).padStart(2, "0"); +} +function replayBoot(forcePreview = false) { + if (!ready) return; + closeModal(() => replayBootAfterModal(forcePreview)); +} +function replayBootAfterModal(forcePreview: boolean) { + bootStart = performance.now() / 1000 - 1.76; + frozenTime = null; + lastStep = ""; + setMode(prefs.reduced && !forcePreview ? "archive" : "boot"); + audio.restartBoot(); + scene?.select(0); + selected = 0; + updateSelection(); + if (!forcePreview) audio.play("ui-tick"); +} +function openFile() { + if (!ready) return; + closeModal(() => { + setMode("detail"); + audio.play("open"); + }); +} +function toggleSaved() { + const id = records[selected].id; + if (saved.has(id)) saved.delete(id); + else saved.add(id); + try { + localStorage.setItem("rhine-saved", JSON.stringify([...saved])); + } catch {} + $("#saved-count").textContent = String(saved.size).padStart(2, "0"); + const button = $('[data-action="bookmark"]'); + const added = saved.has(id); + button.firstChild!.textContent = added ? "− REMOVE FROM SAVED" : "+ SAVE ARCHIVE"; + button.querySelector("span")!.textContent = added ? "已收藏" : "收藏档案"; + button.setAttribute("aria-pressed", String(added)); + bookmarkFeedback?.cancel(); + if (!prefs.reduced) bookmarkFeedback = button.animate( + [{ backgroundColor: "#67634c" }, { backgroundColor: "#252820" }], + { duration: 220, easing: "ease-out" }, + ); + audio.play("confirm"); + notify(saved.has(id) ? "档案已加入收藏" : "已取消收藏"); +} +function renderDetail() { + tabTransition.cancel(); + const r = records[detailIndex()]; + $("#object-id").textContent = "NO." + String(detailIndex() + 1).padStart(3, "0"); + $("#detail-content").innerHTML = ` +
FILE ${r.id}${escapeHtml(r.clearance)}
+

${escapeHtml(r.en)}

${escapeHtml(r.title)}${escapeHtml(r.category)}
+
+ +
+
${overview()}
+
+
${escapeHtml(r.path ?? r.source)}${String(detailIndex() + 1).padStart(3, "0")} / ${String(records.length).padStart(3, "0")}
`; + $("#detail-content").setAttribute("tabindex", "-1"); + $('[data-action="bookmark"]').setAttribute("aria-pressed", String(saved.has(r.id))); + documentDecryption.reset($("#detail-content"), prefs.reduced || !scene || scene.decryptionFrame.phase === "clear"); + setTab(activeTab, false); +} +function overview() { + return `
ABSTRACT / 摘要

${escapeHtml(records[detailIndex()].abstract)}

`; +} + +// ---------- 详情页 ---------- +// 原版行为:索引就是那四十条档案,没有"列外条目", +// 详情页始终跟随阵列选中项。 +function detailIndex() { + return selected; +} + +function setTab(tab: string, sound = true) { + if (sound && tab === activeTab) return; + activeTab = tab; + document.querySelectorAll("[data-tab]").forEach((b) => { + const active = (b as HTMLElement).dataset.tab === tab; + b.classList.toggle("active", active); + b.setAttribute("aria-selected", String(active)); + b.setAttribute("tabindex", active ? "0" : "-1"); + }); + const r = records[detailIndex()]; + const tabButton = $(`[data-tab="${tab}"]`); + const indicator = $(".tab-indicator"); + indicator.style.transition = sound ? "" : "none"; + indicator.style.transform = `translateX(${tabButton.offsetLeft}px) scaleX(${tabButton.offsetWidth})`; + $("#tab-panel").setAttribute("aria-labelledby", tabButton.id); + $("#tab-panel").innerHTML = + tab === "overview" + ? overview() + : tab === "notes" + ? `
RESEARCH NOTES / 研究记录
    ${r.findings.map((f, i) => `
  1. ${String(i + 1).padStart(2, "0")}${escapeHtml(f)}
  2. `).join("")}
` + : `
ACCESS LOG / 本次访问
${accessLog + .filter((entry) => entry.id === r.id) + .slice(0, 4) + .map( + (entry) => + `
${entry.time}JOYCE MOOREREAD AUTHORIZED
`, + ) + .join( + "", + )}

本次会话已通过身份验证。档案内容以当前终端可访问范围展示。

`; + $("#tab-panel").scrollTop = 0; + documentDecryption.refresh(); + if (sound) { + tabTransition.reveal($("#tab-panel"), prefs.reduced); + audio.play("ui-tick"); + } +} +function notify(message: string) { + clearTimeout(toastTimer); + $("#toast").textContent = message; + $("#toast").classList.add("visible"); + toastTimer = setTimeout(() => $("#toast").classList.remove("visible"), 2600); +} + +// 在资源管理器中定位当前档案。路径来自 content/archives.json 的真实归档路径, +// 由本地归档服务的 /reveal 处理;服务只监听回环,因此需要它处于运行状态。 +async function revealSelectedArchive() { + const record = records[detailIndex()]; + if (!record.path) { + notify("这条档案没有对应的本地路径"); + return; + } + const status = await fetchStatus(); + if (!status) { + notify("需要本地归档服务:请运行 npm run dev:archive"); + return; + } + try { + await revealEntry(record.path); + notify(`已在资源管理器中定位:${record.title}`); + } catch (error) { + notify(`无法打开文件位置:${(error as Error).message}`); + } +} + +function openModal(kind: NonNullable) { + if (!ready) return; + if (!modal) { + previousFocus = document.activeElement as HTMLElement; + modalSiblings = [...$("#stage").children] + .filter((node): node is HTMLElement => node instanceof HTMLElement && node.id !== "modal-root") + .map((node) => ({ node, inert: node.inert })); + modalSiblings.forEach(({ node }) => (node.inert = true)); + } + modalClosing = false; + modal = kind; + searchQuery = ""; + filter = "全部档案"; + audio.play("page-open"); + renderModal(); +} +function closeModal(afterClose?: () => void) { + if (!modal) { + afterClose?.(); + return; + } + if (modalClosing) return; + modalClosing = true; + audio.play("page-close"); + modalTransition!.hide(prefs.reduced, () => { + modal = null; + modalClosing = false; + $("#modal-root").replaceChildren(); + modalTransition = undefined; + modalSiblings.forEach(({ node, inert }) => (node.inert = inert)); + modalSiblings = []; + $("#archive-ui").inert = mode !== "archive" || Boolean(workbench?.enabled); + $("#detail-ui").inert = mode !== "detail"; + previousFocus?.focus({ preventScroll: true }); + afterClose?.(); + }); +} +function renderModal() { + if (!modal) return; + modalTransition?.dispose(); + $("#modal-root").innerHTML = + ``; + const backdrop = $(".modal-backdrop"); + backdrop.hidden = true; + modalTransition = new SurfaceTransition(backdrop, $(".terminal-modal")); + modalTransition.show(prefs.reduced); + if (modal === "settings") updateQualitySummary(); + if (modal !== "settings") { + renderResults(); + requestAnimationFrame(() => { + if (backdrop.isConnected && !modalClosing) $("#archive-search").focus(); + }); + } else + requestAnimationFrame(() => { + if (backdrop.isConnected && !modalClosing) $('[data-action="close-modal"]').focus(); + }); + $("#modal-root") + .querySelector(".modal-backdrop") + ?.addEventListener("click", (e) => { + if (e.target === e.currentTarget) closeModal(); + }); +} +// 单次渲染上限。全量归档下单列可上万条,列表不能无界增长。 +const RESULT_LIMIT = 60; + +function renderResults() { + const results = records + .map((r, i) => ({ r, i })) + .filter( + ({ r }) => + (modal !== "saved" || saved.has(r.id)) && + (filter === "全部档案" || r.category === filter) && + `${r.id} ${r.title} ${r.en} ${r.department} ${r.lead}` + .toLowerCase() + .includes(searchQuery.toLowerCase()), + ); + + $("#search-results").innerHTML = results.length + ? results + .map( + ({ r, i }) => + ``, + ) + .join("") + : `
∅${modal === "saved" && !searchQuery ? "尚无收藏档案" : "没有匹配的档案"}

${modal === "saved" && !searchQuery ? "读取档案时,选择 SAVE ARCHIVE 将其保存在此处。" : "尝试其他名称、档案编号,或切换科室分类。"}

`; + $("#result-count").textContent = + `${String(results.length).padStart(2, "0")} RECORDS FOUND`; + scheduleLocalHits(); +} + +// ---------- 主检索里的本地归档命中 ---------- +// 那 40 条精选档案是固定集合,归档里新增的文件永远不会出现在上面的列表里。 +// 这里补一段来自全量索引的命中,让"检索档案"覆盖整个 E:\归档。 +const LOCAL_HIT_LIMIT = 5; +let localIndex: ArchiveIndex | null = null; +let localHitsTimer: ReturnType | undefined; +let localHitsToken = 0; + +function renderLocalHits() { + const host = document.querySelector("#local-hits"); + if (!host) return; + const query = searchQuery.trim(); + + if (modal !== "search" || !query) { + host.hidden = true; + host.innerHTML = ""; + host.closest(".terminal-modal")?.classList.remove("has-local-hits"); + return; + } + + host.hidden = false; + host.closest(".terminal-modal")?.classList.add("has-local-hits"); + + if (!localIndex) { + host.innerHTML = `
LOCAL ARCHIVE / 本地归档索引不可用 · 运行 npm run dev:archive 后重试
`; + return; + } + + const { hits, total } = searchArchive(localIndex, query, LOCAL_HIT_LIMIT); + if (!total) { + host.innerHTML = `
LOCAL ARCHIVE / 本地归档无命中
`; + return; + } + + host.innerHTML = + `
LOCAL ARCHIVE / 本地归档命中 ${total} 项${ + total > hits.length ? ` · 显示前 ${hits.length} 项` : "" + }
` + + hits.map((entry) => localHitMarkup(entry)).join(""); +} + +function localHitMarkup(entry: ArchiveEntry) { + const cut = entry.path.lastIndexOf("/"); + const dir = cut < 0 ? "" : entry.path.slice(0, cut); + const name = cut < 0 ? entry.path : entry.path.slice(cut + 1); + return `
${ + entry.kind === "d" ? "DIR" : "FILE" + }${escapeHtml(name)}${escapeHtml(dir)}${ + entry.kind === "d" ? "—" : formatBytes(entry.size) + }
`; +} + +function scheduleLocalHits() { + clearTimeout(localHitsTimer); + const query = searchQuery.trim(); + if (modal !== "search" || !query) { + renderLocalHits(); + return; + } + localHitsTimer = setTimeout(() => void refreshLocalHits(), 140); +} + +async function refreshLocalHits() { + const token = ++localHitsToken; + const query = searchQuery.trim(); + if (modal !== "search" || !query) return; + renderLocalHits(); // 先按现有索引出一版,避免输入时空白 + try { + const { index } = await loadArchiveIndex({ maxAgeMs: 15_000 }); + if (token !== localHitsToken) return; // 已有更新的输入,丢弃这次结果 + localIndex = index; + } catch { + localIndex = null; + } + renderLocalHits(); +} + +async function actOnLocalPath(kind: "open" | "reveal", path: string) { + const status = await fetchStatus(); + if (!status) { + notify("需要本地归档服务:请运行 npm run dev:archive"); + return; + } + try { + if (kind === "open") await openEntry(path); + else await revealEntry(path); + notify(`${kind === "open" ? "已打开" : "已在资源管理器中定位"}:${path}`); + } catch (error) { + notify(`操作失败:${(error as Error).message}`); + } +} + +function updateQualitySummary() { + const summary = document.querySelector("#quality-summary"); + if (!summary) return; + if (!scene) { summary.textContent = "3D 已关闭 · 三维模型与渲染资源已释放"; return; } + const canvas = scene.renderer.domElement; + const metrics = JSON.parse(canvas.parentElement?.dataset.renderQuality ?? "{}"); + summary.textContent = `${superPerformanceEnabled() ? "超级性能模式已启用 · 画质设置暂被覆盖,关闭后恢复 · " : ""}实际渲染 ${canvas.width} × ${canvas.height} · ${effectiveRenderQuality().antialias === "smaa" ? "SMAA" : "原始抗锯齿"} · 纹理 ${metrics.anisotropy ?? 1}×${metrics.limited ? " · 已达到缓冲上限" : ""}`; +} +function motionSettingsMarkup() { + return `

${prefs.reduced + ? `当前已减少动态效果。${matchMedia("(prefers-reduced-motion: reduce)").matches ? "系统也请求减少动画,可仅为本站启用完整动效。" : "关闭上方开关可恢复完整动效。"}` + : "当前使用完整动效。"}

${prefs.reduced ? '' : ""}
`; +} +function settingsMarkup() { + return `

SYSTEM SETTINGS终端偏好设置

JOYCE MOORE · SESSION AUTHORIZED

${isWallpaper ? '

每次启动都会读取 Wallpaper Engine 中的设置。在此修改仅对当前运行生效,无法持久保存;如需保留,请在 Wallpaper Engine 的壁纸属性中调整。

' : ""}
${themeSettingsMarkup(prefs.colorTheme === "dark")}${!isWallpaper ? `` : ""}${workbench?.settingsMarkup() ?? ""}${audioSettingsMarkup(prefs)}
${motionSettingsMarkup()}${qualityMarkup(prefs.rendering)}${pwaSettingsMarkup()}
${isWallpaper ? 'DESKTOP CONTROLS

拖动阵列或点击界面按钮浏览档案。桌面模式下,方向键与滚轮可能无法传入壁纸。

' : 'KEYBOARD CONTROLS

←→ 切列 ↑↓ 选档 ENTER 读取 / 检索 ESC 返回

'}
${!isWallpaper && document.fullscreenEnabled ? '' : ''}
`; +} + +document.addEventListener("input", (e) => { + const slider = e.target as HTMLInputElement; + if (slider.dataset.quality) { + const output = document.querySelector(`[data-quality-output="${slider.dataset.quality}"]`); + if (output) output.value = `${slider.value}%`; + } + const volume = e.target as HTMLInputElement; + if (volume.dataset.volume === "musicVolume" || volume.dataset.volume === "soundVolume") { + prefs[volume.dataset.volume] = Number(volume.value) / 100; + volume.closest("label")?.querySelector("output")?.replaceChildren(`${volume.value}%`); + saveAudioPrefs(); + } + if ((e.target as HTMLElement).id === "archive-search") { + searchQuery = (e.target as HTMLInputElement).value; + renderResults(); + } +}); +document.addEventListener("change", (e) => { + const el = e.target as HTMLInputElement; + if (el.id === "quality-preset" && Object.hasOwn(qualityPresets, el.value)) { + prefs.rendering = { ...qualityPresets[el.value as QualityPreset] }; + savePrefs(); + } else if (el.dataset.quality) { + const key = el.dataset.quality as keyof RenderQuality; + prefs.rendering = normalizeQuality({ ...prefs.rendering, [key]: key === "antialias" ? el.value : Number(el.value) }); + savePrefs(); + } + if (el.dataset.pref) { + const key = el.dataset.pref; + if (key === "sound" || key === "music" || key === "reduced" || key === "quality" || key === "superPerformance") prefs[key] = el.checked; + if (key === "sound" || key === "music") saveAudioPrefs(); else savePrefs(); + if (key === "reduced") $("#motion-preference-note").outerHTML = motionSettingsMarkup(); + audio.play("confirm"); + } +}); +document.addEventListener("click", (e) => { + const themeButton = (e.target as Element).closest("[data-color-theme]"); + if (themeButton) { prefs.colorTheme = themeButton.dataset.colorTheme === "dark" ? "dark" : "light"; savePrefs(); return; } + if (!started) return; + if (modalClosing) return; + const el = (e.target as Element).closest("button"); + if (!el) return; + if (el.dataset.select) { + select(Number(el.dataset.select)); + return; + } + if (el.dataset.result) { + const index = Number(el.dataset.result); + closeModal(() => { + select(index); + openFile(); + }); + return; + } + if (el.dataset.filter) { + filter = el.dataset.filter; + document + .querySelectorAll("[data-filter]") + .forEach((b) => + b.classList.toggle( + "active", + (b as HTMLElement).dataset.filter === filter, + ), + ); + renderResults(); + return; + } + if (el.dataset.tab) { + setTab(el.dataset.tab); + return; + } + const action = el.dataset.action; + if (action === "toggle-three") { void toggleThree(); return; } + if (action === "sound-preview") audio.play("confirm"); + if (action === "skip") { + setMode("archive"); + audio.play("confirm"); + } + if (action === "prev") stepFile(-1); + if (action === "next") stepFile(1); + if (action === "column-prev") stepColumn(-1); + if (action === "column-next") stepColumn(1); + if (action === "open") openFile(); + if (action === "model-viewer" && mode === "detail" && scene) { + const activeScene = scene; + // Safari does not always focus a button when it is tapped. Capture the + // actual opener so closing the modal reliably restores the right control. + el.focus({ preventScroll: true }); + viewer ??= new ModelViewer($("#stage"), () => { audio.setScene(mode); audio.play("page-close"); }, (sound) => audio.play(sound === "tick" ? "ui-tick" : sound)); + audio.setScene("viewer"); + viewer.setSuperPerformance(superPerformanceEnabled()); + viewer.setQuality(effectiveRenderQuality()); + scene.finishDecryption(); + viewer.open( + records[selected].id, + records[selected].title, + () => activeScene.createAssemblyModel(), + prefs.reduced, + ); + audio.play("page-open"); + } + if (action === "back") { + setMode("archive"); + audio.play("back"); + } + if (action === "search" || action === "saved" || action === "settings") { + el.focus({ preventScroll: true }); + openModal(action); + } + if (action === "local-archive") { + el.focus({ preventScroll: true }); + if (modal) closeModal(() => localArchive.toggle()); + else localArchive.toggle(); + } + if (action === "close-modal") closeModal(); + if (action === "bookmark") toggleSaved(); + if (action === "reveal-archive") void revealSelectedArchive(); + if (action === "exit") { + exitSystem(); + return; + } + if (action === "local-reveal") { + void actOnLocalPath("reveal", el.dataset.path!); + return; + } + if (action === "local-open") { + void actOnLocalPath("open", el.dataset.path!); + return; + } + if (action === "local-more") { + const query = el.dataset.query ?? searchQuery; + closeModal(() => localArchive.openWithQuery(query)); + return; + } + if (action === "reset-search") { + modal = "search"; + searchQuery = ""; + filter = "全部档案"; + renderModal(); + } + if (action === "replay" || action === "restart") { + replayBoot(); + } + if (action === "enable-motion") { + prefs.reduced = false; + savePrefs(); + replayBoot(); + } + if (action === "fullscreen" && document.fullscreenEnabled) { + if (document.fullscreenElement) void document.exitFullscreen(); + else + void document.documentElement + .requestFullscreen() + .catch(() => notify("请使用浏览器的全屏快捷键 F11")); + } +}); +document.addEventListener("keydown", (e) => { + if (!started) return; + if (viewer?.isOpen) return; + if (playground?.active && !modal) { + if (e.key === "Escape") { e.preventDefault(); playground.stop(); } + else if (["ArrowLeft", "ArrowRight", "ArrowUp", "ArrowDown", "Enter", "/"].includes(e.key) && !(e.target instanceof HTMLButtonElement)) e.preventDefault(); + return; + } + if (modalClosing) { + e.preventDefault(); + return; + } + const typing = e.target instanceof HTMLInputElement; + if (e.key === "Escape") { + if (modal) closeModal(); + else if (mode === "detail" || (mode === "boot" && ready)) { const sound = mode === "detail" ? "back" : "ui-tick"; setMode("archive"); audio.play(sound); } + return; + } + if (modal && e.key === "Tab") { + const focusables = [ + ...$("#modal-root").querySelectorAll( + 'button,input:not(:disabled),select:not(:disabled),summary,[tabindex="0"]', + ), + ]; + const visible = focusables.filter(el => el.getClientRects().length > 0); + const first = visible[0], + last = visible.at(-1); + if (e.shiftKey && document.activeElement === first) { + e.preventDefault(); + last?.focus(); + } else if (!e.shiftKey && document.activeElement === last) { + e.preventDefault(); + first?.focus(); + } + return; + } + if (typing || modal || !ready) return; + if ( + (e.target as HTMLElement).dataset.tab && + ["ArrowLeft", "ArrowRight"].includes(e.key) + ) { + e.preventDefault(); + const tabs = ["overview", "notes", "history"]; + setTab( + tabs[(tabs.indexOf(activeTab) + (e.key === "ArrowRight" ? 1 : 2)) % 3], + ); + $(`[data-tab="${activeTab}"]`).focus(); + return; + } + if (e.key === "/") { + e.preventDefault(); + if (mode === "boot") setMode("archive"); + openModal("search"); + } + if (e.key === "ArrowLeft" && mode !== "boot") { + e.preventDefault(); + stepColumn(-1); + } + if (e.key === "ArrowRight" && mode !== "boot") { + e.preventDefault(); + stepColumn(1); + } + if (["ArrowUp", "ArrowDown"].includes(e.key) && mode !== "boot") { + e.preventDefault(); + stepFile(e.key === "ArrowUp" ? -1 : 1); + } + if ( + e.key === "Enter" && + (document.activeElement === document.body || + document.activeElement?.id === "detail-content" || + ["prev", "next", "column-prev", "column-next"].includes( + (document.activeElement as HTMLElement)?.dataset.action ?? "", + ) || + (document.activeElement as HTMLElement)?.dataset.select) + ) { + e.preventDefault(); + if (mode === "boot") setMode("archive"); + else if (mode === "archive") openFile(); + } +}); + +const ease = (t: number) => { + t = Math.max(0, Math.min(1, t)); + return t * t * (3 - 2 * t); +}; +function bootFrame(t: number) { + if (isWallpaper && !scene && frozenTime === null && t >= 21.9) { + setMode("archive"); + return undefined; + } + if (isWallpaper && frozenTime === null && t >= ARRAY_OPENING_END && + !openingShowsDetail(wallpaperHost()?.properties.openingdetail?.value, !!workbench?.enabled)) { + setMode("archive"); + return undefined; + } + audio.updateBoot(t, frozenTime !== null); + const motion = bootSequence.update(t); + if (workbench?.enabled && frozenTime === null) { + const end = openingShowsDetail(wallpaperHost()?.properties.openingdetail?.value, true) ? 35 : ARRAY_OPENING_END; + if (t > end - .35) $(".powered").style.opacity = String(1 - ease((t - end + .35) / .35)); + } + let step: string = motion.step; + if (t >= 22) { + step = "array"; + } + if (t >= 25.68) { + step = "select"; + } + if (t >= 28.3) { + step = "inspect"; + } + if (step !== lastStep) { + $("#stage").dataset.boot = step; + lastStep = step; + } + $(".file-title").firstChild!.textContent = + step === "array" + ? "SELECTING FILES...".slice(0, Math.max(0, Math.floor((t - 21.94) * 18))) + : "FILE NUMBER: "; + $("#stage").style.setProperty( + "--entry-opacity", + String(ease((t - 21.9) / 0.13)), + ); + $(".callout-rule").style.transform = `scaleX(${ease((t - 22.08) / 0.9)})`; + const reveal = ease((t - 22) / 0.4), + lift = ease((t - 26) / 1.8), + zoom = 0.55 * ease((t - 27.3) / 1.65) + 0.45 * ease((t - 29.0) / 5.0); + if (t >= 35) { + // 原片时间轴结束。网页版直接进入浏览(阵列),由用户自己选取档案—— + // 自动展开头一张盘会让人以为"档案是替你选好的"。 + // 壁纸版仍按「入场详情」属性决定是否自动展开选中的档案。 + const showDetail = + isWallpaper && + frozenTime === null && + openingShowsDetail( + wallpaperHost()?.properties.openingdetail?.value, + !!workbench?.enabled, + ); + setMode(showDetail ? "detail" : "archive"); + return undefined; + } + return { reveal, lift, zoom, time: t }; +} + +const inspectionOverlay = new InspectionOverlay(); +const documentDecryption = new DocumentDecryption(); +// A newly opened archive can introduce another font shard. Re-measure its +// redaction lines after font swap while retaining the current reveal progress. +document.fonts.addEventListener("loadingdone", () => documentDecryption.refresh()); + +let lastTime = 0, + frameCount = 0, + frameStart = performance.now(), + fps = 0; +function frame(ms: number) { + if (!wallpaperFrame(ms)) { requestAnimationFrame(frame); return; } + if (document.hidden) { requestAnimationFrame(frame); return; } + workbench?.tick(); + const time = ms / 1000; + const theme = scene?.themeAmount ?? (prefs.colorTheme === "dark" ? 1 : 0); + paintTheme(theme); + viewer?.setTheme(theme); + playground?.tick(time); + const cinema = + mode === "boot" && ready + ? bootFrame(frozenTime ?? time - bootStart) + : undefined; + wallpaperEffects?.update(time, prefs.reduced); + // The calibrated 2D opening fully covers the scene until array entry. + if (!viewer?.isOpen && (!cinema || cinema.time >= 21.9)) scene?.update(time, cinema); + viewer?.update(time); + if (threeState === "closing" && scene?.presentationHidden) releaseThree(); + playground?.position(); + if (scene && mode === "detail") { + documentDecryption.update(time, scene.decryptionFrame, prefs.reduced); + $("#detail-content").style.opacity = String(scene.detailVisibility); + $("#detail-content").style.translate = + `0 ${(1 - scene.detailVisibility) * 18}px`; + $("#detail-content").inert = scene.detailVisibility < 0.1; + if (pendingDetailFocus && scene.detailVisibility >= 0.1 && !modal && !viewer?.isOpen) { + $("#detail-content").focus({ preventScroll: true }); + pendingDetailFocus = false; + } + } + $("#stage").style.setProperty("--detail-shade", String(mode === "boot" ? 0 : scene?.detailVisibility ?? 0)); + const currentScene = scene; + if (currentScene) inspectionOverlay.render(currentScene.decryptionFrame, + (x, y) => currentScene.projectCard(x, y), Boolean(cinema)); + if (Math.floor(time) !== lastTime) { + lastTime = Math.floor(time); + updateFooterClock(new Date(), !prefs.reduced); + } + frameCount++; + if (ms - frameStart > 1000) { + fps = (frameCount * 1000) / (ms - frameStart); + frameStart = ms; + frameCount = 0; + $("#three-scene").dataset.fps = String(Math.round(fps)); + $("#three-scene").dataset.renderStats = JSON.stringify(scene?.getStats() ?? { loaded: false, drawCalls: 0, triangles: 0 }); + } + requestAnimationFrame(frame); +} +function bindScene(scene: ArchiveScene, cell?: { lane: number; row: number }) { + scene.select(selected, cell ? { cell } : undefined); + scene.onSelect = (i, cell) => { + if (mode !== "archive" || modal || viewer?.isOpen) return; + select(i, cell ? { cell } : undefined); + }; + scene.onNavigate = (axis, direction) => { + if (mode !== "archive" || modal || viewer?.isOpen) return; + if (axis === "lane") stepColumn(direction); + else stepFile(direction); + }; + scene.onHover = (i) => { + const label = $("#hover-label"); + if (i === null) { + label.hidden = true; + hoverCode.finish(); + hoverTitle.finish(); + return; + } + const animated = !prefs.reduced && mode === "archive"; + hoverCode.update({ + value: Number(records[i].id.slice(2)), + animated: !label.hidden && animated, + }); + hoverTitle.update({ text: records[i].title, animated: !label.hidden && animated }); + label.hidden = false; + // Prepare the first visible value so the next hover can animate immediately. + hoverCode.update({ animated }); + hoverTitle.update({ animated }); + }; +} +function syncThreeButton() { + $("#stage").dataset.threeState = threeState; + syncWallpaperBackground(); + const button = document.querySelector('[data-action="toggle-three"]'); + if (!button) return; + button.textContent = threeState === "loading" ? "3D 载入中…" : threeState === "closing" ? "3D 关闭中…" : threeState === "off" ? "3D 关闭" : "3D 开启"; + button.disabled = threeState === "loading"; + button.setAttribute("aria-pressed", String(threeState === "on")); + button.title = threeState === "off" ? "重新载入三维模型" : threeState === "closing" ? "取消关闭,恢复三维画面" : "卸载三维模型,保留 2D 界面"; +} +function releaseThree() { + if (!scene) return; + resumeCell = { ...scene.getStats().selectedCell }; resumeSelection = selected; + viewer?.dispose(); viewer = undefined; + scene.dispose(); scene = undefined; + if (mode === "detail") { + $("#detail-content").style.opacity = "1"; + $("#detail-content").style.translate = "0 0"; + $("#detail-content").inert = false; + documentDecryption.reset($("#detail-content"), true); + } + threeState = "off"; syncThreeButton(); + $("#hover-label").hidden = true; + delete $("#three-scene").dataset.renderQuality; + updateQualitySummary(); +} +async function toggleThree() { + if (!isWallpaper || !ready || threeState === "loading") return; + if (threeState === "closing") { + scene?.setPresentationVisible(true, prefs.reduced); + threeState = "on"; syncThreeButton(); return; + } + if (scene) { + playground?.stop(); + threeState = "closing"; syncThreeButton(); + scene.setPresentationVisible(false, prefs.reduced); + if (prefs.reduced) releaseThree(); + return; + } + threeState = "loading"; syncThreeButton(); + let next: ArchiveScene | undefined; + try { + next = new ArchiveScene($("#three-scene")); + next.renderer.domElement.style.opacity = "0"; + next.setPresentationVisible(false, true); + await next.load(); + next.setMode(mode === "detail" ? "detail" : "archive"); + bindScene(next, resumeSelection === selected ? resumeCell : undefined); + next.revealImmediately(); + scene = next; + scene.setTheme(prefs.colorTheme === "dark", true); + scene.setArchiveCoverage(wallpaperHost()?.properties.archivecoverage?.value === "extra"); + savePrefs(); + scene.setPresentationVisible(true, prefs.reduced); + threeState = "on"; syncThreeButton(); + } catch (error) { + next?.dispose(); scene = undefined; + threeState = "off"; syncThreeButton(); + notify("三维模型载入失败,请点击 3D 关闭重试。"); + console.error(error); + } +} + +async function start() { + try { + if (isWallpaper) await window.rhineWallpaperPropertiesReady; + if (!isWallpaper || wallpaperHost()?.properties.load3donstartup?.value !== false) { + scene = new ArchiveScene($("#three-scene")); + scene.setTheme(prefs.colorTheme === "dark", true); + scene.setArchiveCoverage(wallpaperHost()?.properties.archivecoverage?.value === "extra"); + } else { + threeState = "off"; + syncThreeButton(); + } + await Promise.all([ + scene?.load(), + loadBootWebfonts(), + // With unicode-range faces, preload the opening's actual characters, + // not every font shard. Other archive text loads on demand. + document.fonts.load("300 20px MiSans", "ACCESS WELCOME TO INTERNAL DATABASE"), + document.fonts.load("400 20px MiSans", "身份信息确认请求已接收开始处理权限验证通过欢迎访问莱茵生命内部资料档案编号保密级别商业区选择档案:0123456789 JOYCE MOORE"), + document.fonts.load("600 20px MiSans", "SYNTHESIZE INFORMATION ANALYSIS OS"), + document.fonts.load("700 20px MiSans", "RHINE LAB WELCOME TO INTERNAL DATABASE"), + ]); + if (scene) bindScene(scene); + savePrefs(); + ready = true; + select(0); + // 自动开场:logo 先显示 1 秒,再渐出,然后直接播放,不等点击。 + await beginOpening(); + } catch (error) { + console.error(error); + $("#loading").innerHTML = + '
CONNECTION INTERRUPTED

三维档案资源未能载入。请确认浏览器已启用硬件加速,然后重新连接。

'; + } +} +/** + * 开场入口序列:先尝试解锁音频,再让 logo 停留 1 秒,然后渐出并开始播放。 + * 无手势时浏览器通常不允许启动音频设备(unlock 会失败),此时本次开场静音, + * 但用户的音效偏好保持不变,首次真实交互会自动把声音接上。 + */ +async function beginOpening() { + const wantsSound = !isWallpaper && !reviewEntry && (prefs.sound || prefs.music); + let audible = true; + if (isWallpaper) { + // CEF allows automatic audio; never block the visual on audio policy or decoding. + await Promise.race([audio.unlock().catch(() => false), new Promise(resolve => setTimeout(resolve, 3000))]); + } else if (wantsSound) { + audio.holdForEntry(); + if (prefs.music) void audio.prepareMusic().catch(() => { /* 首次交互时会补上 */ }); + audible = await Promise.race([ + audio.unlock().catch(() => false), + new Promise(resolve => { setTimeout(() => resolve(false), ENTRY_UNLOCK_TIMEOUT); }), + ]); + } + // 逐帧对照(?time= / ?review=1)不加这段停留,避免影响对照工具。 + await new Promise(resolve => setTimeout(resolve, reviewEntry ? 0 : ENTRY_LOGO_HOLD)); + if (!audible) armAudioUnlock(); + completeStartup(); +} + +// ---------- 退出系统 ---------- +// 白屏 → 标志快闪两下 → 下方出现 SHUTDOWN → 关窗。 +// 时序由 CSS 动画的 delay 驱动(见 style.css 的 .shutdown), +// JS 只负责显示、并在最后一步关窗。 +const SHUTDOWN_CLOSE = 880; +let exiting = false; + +function exitSystem() { + if (exiting || !started || !ready) return; + exiting = true; + $("#stage").dataset.shutdown = "true"; + audio.play("page-close"); + const overlay = $("#shutdown"); + overlay.hidden = false; + requestAnimationFrame(() => overlay.classList.add("visible")); + window.setTimeout(closeWindow, SHUTDOWN_CLOSE); +} + +/** 关窗。浏览器只允许脚本自己打开的窗口关闭,关不掉时退化为空白页, + * 免得停在一个"看起来已退出、其实还开着"的中间状态。 */ +function closeWindow() { + window.close(); + window.setTimeout(() => { + if (window.closed) return; + document.documentElement.replaceChildren(); + document.documentElement.style.background = "#fff"; + }, 150); +} + +/** 首次真实交互时补解锁音频,让音乐与后续交互音效接上。一次性。 */function armAudioUnlock() { + if (audioUnlockArmed) return; + audioUnlockArmed = true; + const attempt = () => { + void audio.unlock() + .then(unlocked => { + if (!unlocked) return; + configureAudio(); + window.removeEventListener("pointerdown", attempt); + window.removeEventListener("keydown", attempt); + }) + .catch(() => { /* 保持静音,等下一次交互 */ }); + }; + window.addEventListener("pointerdown", attempt, { passive: true }); + window.addEventListener("keydown", attempt); +} + +function completeStartup() { + if (started || !ready) return; + started = true; + audio.releaseEntry(); + audio.restartBoot(); + const fade = prefs.reduced ? 0 : 600; + bootStart = performance.now() / 1000 - (reviewParams.has("time") ? Number(reviewParams.get("time")) : 1.76); + if (!reviewParams.has("time")) bootStart += fade / 1000; + setMode("boot"); + if (reviewParams.get("scene") === "archive" || (prefs.reduced && !reviewParams.has("time"))) setMode("archive"); + if (reviewParams.get("scene") === "detail") setMode("detail"); + if (isWallpaper && wallpaperHost()?.properties.boot?.value === false) setMode("archive"); + $("#stage").inert = false; + $(".mobile-entry").inert = false; + loading.classList.add("loaded"); + loading.inert = true; + setTimeout(() => { + // 自动开场没有用户手势,不再抢焦点——右上角的圆形跳过按钮保持"不起眼"。 + loading.remove(); + }, fade); + requestAnimationFrame(frame); + // Do not compete with entry audio/font downloads. Full offline installation + // begins after startup is complete and remains atomic. + setTimeout(() => void initPwa(notify), 1500); +} +updateSelection(); +const customBackground = isWallpaper ? new WallpaperBackground($("#stage"), notify) : undefined; +function syncWallpaperBackground(retry = false) { + customBackground?.update(wallpaperHost()?.properties ?? {}, mode !== "boot" && (threeState === "off" || threeState === "loading"), prefs.reduced, retry); +} +if (isWallpaper) { + const apply = (properties: WallpaperProperties) => { + const theme = properties.colortheme?.value; + if (theme === "light" || theme === "dark") prefs.colorTheme = theme; + scene?.setArchiveCoverage(properties.archivecoverage?.value === "extra" || wallpaperHost()?.properties.archivecoverage?.value === "extra"); + for (const key of ["sound", "music", "reduced"] as const) + if (typeof properties[key]?.value === "boolean") prefs[key] = properties[key].value as boolean; + for (const key of ["soundVolume", "musicVolume"] as const) { + const value = properties[key.toLowerCase()]?.value; + if (typeof value === "number" && Number.isFinite(value)) prefs[key] = Math.max(0, Math.min(1, value / 100)); + } + const qualityProperties = { ...wallpaperHost()?.properties, ...properties }; + if (Object.keys(properties).some(key => key === "renderquality" || key.startsWith("quality"))) + prefs.rendering = wallpaperQuality(qualityProperties, prefs.rendering); + savePrefs(); + if (properties.customwallpaperfile || properties.customwallpaper?.value === true) syncWallpaperBackground(true); + if (properties.boot?.value === false && started && mode === "boot") setMode("archive"); + // Keep an already-open settings surface in sync without replacing focused controls. + document.querySelectorAll("[data-pref]").forEach(input => { + const key = input.dataset.pref as "sound" | "music" | "reduced"; + if (key in prefs) input.checked = prefs[key]; + }); + for (const key of ["soundVolume", "musicVolume"] as const) { + const input = document.querySelector(`[data-volume="${key}"]`); + if (input) { input.value = String(Math.round(prefs[key] * 100)); input.closest("label")?.querySelector("output")?.replaceChildren(`${input.value}%`); } + } + }; + window.addEventListener("rhine-wallpaper-properties", event => apply((event as CustomEvent).detail)); + let pausedAt: number | undefined; + const pause = () => { + const paused = wallpaperHost()?.paused ?? false; + if (paused && pausedAt === undefined) pausedAt = performance.now(); + if (!paused && pausedAt !== undefined) { + if (started && mode === "boot") bootStart += (performance.now() - pausedAt) / 1000; + pausedAt = undefined; + } + audio.setHostPaused(paused); + }; + window.addEventListener("rhine-wallpaper-pause", pause); + apply(wallpaperHost()?.properties ?? {}); + pause(); +} +if (isWallpaper) { + workbench = new Workbench($("#stage"), () => { + if (ready && mode !== "boot") setMode("archive"); + }, lane => { + if (ready && !modal) select(columnMemory[lane]); + }); + playground = new ArchivePlayground($("#stage"), () => scene, + () => ({ enabled: !!workbench?.enabled && mode === "archive" && ready, paused: Boolean(modal) || modalClosing || Boolean(wallpaperHost()?.paused) || document.hidden, reduced: prefs.reduced }), + value => { musicSuppressed = value; configureAudio(); }, () => audio.play("tick")); + wallpaperEffects = new WallpaperEffects($("#stage"), () => scene); + document.addEventListener("click", event => { + const button = (event.target as Element).closest("[data-workbench-mode]"); + if (button) closeModal(() => { workbench!.setEnabled(button.dataset.workbenchMode === "workbench"); }); + }); +} +// 本地归档面板:检索 E:\归档 全量索引,并在本地服务在线时执行归档写操作。 +localArchive = new LocalArchivePanel($("#stage"), notify); +void start();// Deterministic review controls: the running application, never a video surrogate. +Object.assign(window, { + rhine: { + // The review button supplies a real user activation. Preferences stay local to this preview. + playBootPreview: async (music = false) => { + if (!ready || !navigator.userActivation.isActive) return false; + const request = ++audioPreviewRequest; + audioPreview = true; + audio.configure({ ...prefs, sound: true, music }); + const unlocked = await audio.unlock(); + if (request !== audioPreviewRequest) return false; + if (!unlocked) { + audioPreview = false; + configureAudio(); + return false; + } + replayBoot(true); + return true; + }, + seek: (t: number) => { + setMode("boot"); + bootStart = performance.now() / 1000 - t; + lastStep = ""; + }, + archive: () => setMode("archive"), + detail: () => openFile(), + select: (i: number) => select(i), + stats: () => ({ + ...scene?.getStats(), + threeState, + fps: Math.round(fps), + mode, + ready, + startup: started ? "started" : "loading", + motion: { reduced: prefs.reduced, systemReduced: matchMedia("(prefers-reduced-motion: reduce)").matches }, + bootTime: mode === "boot" ? started ? (frozenTime ?? performance.now() / 1000 - bootStart) + 5 : 6.76 : null, + selected: records[selected].id, + saved: [...saved], + audio: audio.stats(), + wallpaper: isWallpaper ? wallpaperHost() : null, + }), + }, +}); +if (import.meta.hot) import.meta.hot.dispose(() => audio.dispose()); + diff --git a/src/model-viewer.ts b/src/model-viewer.ts new file mode 100644 index 0000000..40bb29e --- /dev/null +++ b/src/model-viewer.ts @@ -0,0 +1,622 @@ +import * as THREE from "three"; +import { disposeThreeTree } from "./three-resources"; +import { themeEnvironment } from "./theme-material"; +import { OrbitControls } from "three/addons/controls/OrbitControls.js"; +import { createArchiveLighting } from "./archive-lighting"; +import { damp } from "./motion"; +import { ViewerCameraMotion } from "./viewer-camera"; +import { normalizeQuality, type RenderQuality } from "./render-quality"; +import { + applyTextureQuality, + createViewerPipeline, + resizeQuality, +} from "./quality-renderer"; + +const PARTS = [ + { id: "fasteners", label: "紧固件", en: "FASTENERS", depth: 2.75 }, + { id: "cover", label: "透明盖板", en: "OPTICAL COVER", depth: 1.85 }, + { + id: "optical-lenses", + label: "折射环组", + en: "REFRACTIVE RINGS", + depth: 0.75, + }, + { id: "optical-core", label: "光学核心", en: "OPTICAL CORE", depth: -0.15 }, + { id: "substrate", label: "信息基板", en: "SUBSTRATE", depth: -1.1 }, + { id: "carrier", label: "背板与框架", en: "CARRIER", depth: -2.05 }, +] as const; + +type ModelSource = { model: THREE.Group; dispose: () => void; setClarity?: (value: number) => void }; +export class ModelViewer { + private themeAmount = 0; + setTheme(value: number) { this.themeAmount = value; } + readonly root: HTMLElement; + private canvasHost: HTMLElement; + private renderer: THREE.WebGLRenderer; + private pipeline: ReturnType; + private quality = normalizeQuality(undefined); + private superPerformance = false; + dispose() { + this.request++; + if (this.isOpen) this.finishClose(); + this.controls.dispose(); + disposeThreeTree(this.scene); + for (const pass of this.pipeline.composer.passes) pass.dispose(); + this.pipeline.composer.dispose(); + this.renderer.dispose(); + this.renderer.forceContextLoss(); + this.root.remove(); + } + setSuperPerformance(enabled: boolean) { + if (this.superPerformance === enabled) return; + this.superPerformance = enabled; + this.resize(); + } + private appliedQuality = ""; + private scene = new THREE.Scene(); + private camera = new THREE.PerspectiveCamera(34, 16 / 9, 0.3, 120); + private controlCamera = this.camera.clone(); + private cameraMotion = new ViewerCameraMotion(this.camera); + private controls: OrbitControls; + private source?: ModelSource; + private groups = new Map(); + private spread = { value: 0, velocity: 0 }; + private targetSpread = 0; + private clarity = { value: 1, velocity: 0 }; + private targetClarity = 1; + private lastTime = 0; + private request = 0; + private reduced = false; + private loading = false; + private closing = false; + private transitions: Animation[] = []; + private transitionId = 0; + private status = ""; + private opener: HTMLElement | null = null; + private siblings: { node: HTMLElement; inert: boolean }[] = []; + private initialCamera = new THREE.Vector3(7.2, 3.8, 12); + private onClose: () => void; + private provider?: () => Promise; + isOpen = false; + + constructor( + parent: HTMLElement, + onClose: () => void, + private onSound: ( + sound: "explode" | "assemble" | "tick", + ) => void = () => {}, + ) { + this.onClose = onClose; + this.root = document.createElement("section"); + this.root.className = "model-viewer"; + this.root.hidden = true; + this.root.setAttribute("role", "dialog"); + this.root.setAttribute("aria-modal", "true"); + this.root.setAttribute("aria-labelledby", "viewer-title"); + this.root.innerHTML = ` +
+ +
+ +
RHINE LAB / OBJECT STUDY

档案模型

+ 360° +
+
+ +
正在载入模型…
+
+
拖动旋转↑ ↓ ← → 平移滚轮缩放
+
+ +
+
已组装
`; + parent.appendChild(this.root); + this.canvasHost = this.root.querySelector(".viewer-canvas")!; + this.renderer = new THREE.WebGLRenderer({ + antialias: true, + powerPreference: "high-performance", + }); + this.renderer.toneMapping = THREE.ACESFilmicToneMapping; + this.renderer.toneMappingExposure = 1.05; + this.renderer.domElement.tabIndex = 0; + this.renderer.domElement.setAttribute( + "aria-label", + "档案三维模型:拖动旋转,方向键平移,滚轮或加减键缩放,Home 复位", + ); + this.canvasHost.appendChild(this.renderer.domElement); + this.scene.background = new THREE.Color("#eae5e1"); + this.scene.fog = new THREE.Fog("#eae5e1", 13.5, 26.5); + // Render-target textures belong to their WebGL context. Recreate the main + // scene's light room here so this renderer receives its actual illumination. + createArchiveLighting(this.renderer, this.scene); + this.camera.position.copy(this.initialCamera); + this.controlCamera.copy(this.camera); + this.controls = new OrbitControls( + this.controlCamera, + this.renderer.domElement, + ); + this.controls.enableDamping = false; + this.pipeline = createViewerPipeline( + this.renderer, + this.scene, + this.camera, + ); + this.pipeline.smaa.enabled = false; + this.controls.rotateSpeed = 0.65; + this.controls.zoomSpeed = 0.7; + this.controls.panSpeed = 0.7; + this.controls.minDistance = 5; + this.controls.maxDistance = 28; + this.controls.maxTargetRadius = 5; + this.controls.screenSpacePanning = true; + this.controls.touches.ONE = THREE.TOUCH.ROTATE; + this.controls.touches.TWO = THREE.TOUCH.DOLLY_PAN; + this.controls.enabled = false; + this.controls.update(); + this.cameraMotion.snap(this.controlCamera, this.controls.target); + this.controls.addEventListener("start", () => this.interruptReset()); + this.root.addEventListener("click", (event) => { + if (this.closing) return; + const action = (event.target as HTMLElement).closest( + "[data-viewer]", + )?.dataset.viewer; + if (action === "close") this.close(); + if (action === "retry") void this.load(); + if (this.loading || !this.source) return; + if (action === "clear" || action === "frosted") { + this.setSurface(action === "clear"); + this.onSound("tick"); + } + if (action === "explode" && this.targetSpread !== 1) { + this.setExploded(true); + this.onSound("explode"); + } + if (action === "assemble" && this.targetSpread !== 0) { + this.setExploded(false); + this.onSound("assemble"); + } + if (action === "reset") { + this.resetView(); + this.onSound("tick"); + } + }); + this.root.addEventListener("keydown", (event) => this.keydown(event)); + } + + open( + id: string, + title: string, + provider: () => Promise, + reduced: boolean, + ) { + if (this.isOpen) return; + this.isOpen = true; + this.closing = false; + this.reduced = reduced; + this.provider = provider; + this.opener = document.activeElement as HTMLElement | null; + this.siblings = [...this.root.parentElement!.children] + .filter( + (node): node is HTMLElement => + node instanceof HTMLElement && node !== this.root, + ) + .map((node) => ({ node, inert: node.inert })); + this.siblings.forEach(({ node }) => (node.inert = true)); + this.root.hidden = false; + this.root.dataset.transition = "opening"; + this.root.querySelector("#viewer-title")!.textContent = title; + this.root.querySelector("#viewer-file")!.textContent = + "FILE " + id + " / INTERNAL DATABASE"; + this.spread = { value: 0, velocity: 0 }; + this.targetSpread = 0; + this.clarity = { value: 1, velocity: 0 }; + this.setSurface(true); + this.lastTime = 0; + this.root.dataset.exploded = "false"; + this.resetView(false); + this.resize(); + this.renderer.domElement.focus({ preventScroll: true }); + this.enter(); + void this.load(); + } + + private async load() { + if (!this.provider || this.loading) return; + const ticket = ++this.request; + this.loading = true; + this.controls.enabled = false; + const loading = this.root.querySelector(".viewer-loading")!; + loading.hidden = false; + loading.querySelector("span")!.textContent = "正在载入模型…"; + loading.querySelector("button")!.hidden = true; + this.setButtonsDisabled(true); + try { + const source = await this.provider(); + if (!this.isOpen || this.closing || ticket !== this.request) { + source.dispose(); + return; + } + this.source = source; + for (const part of PARTS) { + const group = new THREE.Group(); + group.name = part.id; + this.groups.set(part.id, group); + } + for (const child of [...source.model.children]) { + const group = this.groups.get(child.userData.assemblyPart ?? "cover"); + group?.add(child); + } + for (const group of this.groups.values()) source.model.add(group); + source.model.position.set(0, -1.85, 0); + this.scene.add(source.model); + applyTextureQuality(source.model, this.renderer, this.quality); + this.loading = false; + loading.hidden = true; + this.controls.enabled = true; + this.setButtonsDisabled(false); + this.setExploded(false); + this.setStatus("已组装"); + // Render before revealing the canvas so a new model never flashes in. + this.update(this.lastTime); + if (!this.reduced) + this.transitions.push( + this.canvasHost.animate( + [ + { opacity: 0, transform: "scale(0.97)" }, + { opacity: 1, transform: "scale(1)" }, + ], + { duration: 380, easing: "cubic-bezier(0.22, 1, 0.36, 1)" }, + ), + ); + } catch (error) { + if (!this.isOpen || this.closing || ticket !== this.request) return; + this.loading = false; + loading.querySelector("span")!.textContent = "模型载入失败,请重试"; + loading.querySelector("button")!.hidden = false; + console.error("Model viewer failed to load", error); + } + } + + private enter() { + const ticket = ++this.transitionId; + this.transitions.forEach((animation) => animation.cancel()); + this.transitions = []; + if (this.reduced) { + this.root.dataset.transition = "open"; + return; + } + const fade = this.root.animate([{ opacity: 0 }, { opacity: 1 }], { + duration: 320, + easing: "cubic-bezier(0.22, 1, 0.36, 1)", + }); + this.transitions.push(fade); + for (const selector of [ + ".viewer-header", + ".viewer-footer", + ".viewer-state", + ]) { + const element = this.root.querySelector(selector)!; + this.transitions.push( + element.animate( + [ + { opacity: 0, translate: "0 10px" }, + { opacity: 1, translate: "0 0" }, + ], + { + duration: 300, + delay: 60, + fill: "backwards", + easing: "cubic-bezier(0.22, 1, 0.36, 1)", + }, + ), + ); + } + void fade.finished + .then(() => { + if (ticket === this.transitionId) this.root.dataset.transition = "open"; + }) + .catch(() => {}); + } + + close() { + if (!this.isOpen || this.closing) return; + this.closing = true; + this.request++; + this.loading = false; + this.controls.enabled = false; + this.setButtonsDisabled(true); + const ticket = ++this.transitionId; + // Capture the current fade when Escape interrupts opening. + const opacity = getComputedStyle(this.root).opacity; + const canvasStyle = getComputedStyle(this.canvasHost); + const canvasOpacity = canvasStyle.opacity; + const transform = canvasStyle.transform; + this.transitions.forEach((animation) => animation.cancel()); + this.transitions = []; + this.root.dataset.transition = "closing"; + if (this.reduced) { + this.finishClose(); + return; + } + const fade = this.root.animate([{ opacity }, { opacity: 0 }], { + duration: 220, + easing: "cubic-bezier(0.4, 0, 1, 1)", + fill: "forwards", + }); + this.transitions.push( + fade, + this.canvasHost.animate( + [ + { transform, opacity: canvasOpacity }, + { transform: "scale(0.97)", opacity: 0 }, + ], + { + duration: 220, + easing: "cubic-bezier(0.4, 0, 1, 1)", + fill: "forwards", + }, + ), + ); + void fade.finished + .then(() => { + if (ticket === this.transitionId) this.finishClose(); + }) + .catch(() => {}); + } + + private finishClose() { + // Keep rendering and retain modal focus until the visible exit completes. + this.isOpen = false; + this.closing = false; + this.root.hidden = true; + this.transitions.forEach((animation) => animation.cancel()); + this.transitions = []; + if (this.source) { + this.scene.remove(this.source.model); + this.source.dispose(); + this.source = undefined; + } + this.groups.clear(); + this.siblings.forEach(({ node, inert }) => (node.inert = inert)); + this.siblings = []; + this.opener?.focus({ preventScroll: true }); + this.onClose(); + } + + private setButtonsDisabled(disabled: boolean) { + for (const action of ["explode", "assemble", "reset", "clear", "frosted"]) { + this.root.querySelector( + `[data-viewer="${action}"]`, + )!.disabled = disabled; + } + } + private setSurface(clear: boolean) { + this.targetClarity = clear ? 1 : 0; + this.root.dataset.surface = clear ? "clear" : "frosted"; + this.root.querySelector('[data-viewer="clear"]')!.setAttribute("aria-pressed", String(clear)); + this.root.querySelector('[data-viewer="frosted"]')!.setAttribute("aria-pressed", String(!clear)); + if (this.reduced) this.clarity = { value: this.targetClarity, velocity: 0 }; + } + private setExploded(value: boolean) { + this.targetSpread = value ? 1 : 0; + this.root.dataset.exploded = String(value); + this.root + .querySelector('[data-viewer="explode"]')! + .setAttribute("aria-pressed", String(value)); + this.root + .querySelector('[data-viewer="assemble"]')! + .setAttribute("aria-pressed", String(!value)); + this.setStatus( + value ? "正在拆解" : this.spread.value > 0.001 ? "正在重组" : "已组装", + ); + if (this.reduced) this.spread = { value: this.targetSpread, velocity: 0 }; + } + private setStatus(value: string) { + if (value !== this.status) { + this.status = value; + this.root.querySelector(".viewer-state")!.textContent = value; + } + } + private resetView(animated = true) { + this.controls.enabled = false; + this.controls.enableDamping = false; + this.controls.update(); + this.controls.target.set(0, 0, 0); + this.controlCamera.position.copy(this.initialCamera); + this.controls.enableDamping = false; + this.controls.update(); + if (animated && !this.reduced) this.cameraMotion.reset(); + else this.cameraMotion.snap(this.controlCamera, this.controls.target); + this.controls.enabled = + this.isOpen && !this.loading && Boolean(this.source); + } + private interruptReset() { + if (!this.cameraMotion.resetting) return; + this.cameraMotion.interruptReset(this.controlCamera, this.controls.target); + this.controls.update(); + } + private keydown(event: KeyboardEvent) { + event.stopPropagation(); + if (event.key === "Escape") { + event.preventDefault(); + this.close(); + return; + } + if (this.closing) { + event.preventDefault(); + return; + } + if (event.key === "Tab") { + const elements = [ + ...this.root.querySelectorAll( + 'button:not([disabled]):not([hidden]),canvas[tabindex="0"]', + ), + ]; + const first = elements[0], + last = elements.at(-1); + if (event.shiftKey && document.activeElement === first) { + event.preventDefault(); + last?.focus(); + } + if (!event.shiftKey && document.activeElement === last) { + event.preventDefault(); + first?.focus(); + } + return; + } + if (!this.source || this.loading) return; + if (event.key === "Home") { + event.preventDefault(); + this.resetView(); + this.onSound("tick"); + return; + } + if (["+", "=", "-"].includes(event.key)) { + event.preventDefault(); + this.interruptReset(); + const distance = this.controlCamera.position.distanceTo( + this.controls.target, + ); + const next = THREE.MathUtils.clamp( + distance * (event.key === "-" ? 1.12 : 1 / 1.12), + 5, + 28, + ); + this.controlCamera.position + .sub(this.controls.target) + .multiplyScalar(next / distance) + .add(this.controls.target); + this.controls.update(); + return; + } + if ( + ["ArrowLeft", "ArrowRight", "ArrowUp", "ArrowDown"].includes(event.key) + ) { + event.preventDefault(); + this.interruptReset(); + const right = new THREE.Vector3().setFromMatrixColumn( + this.camera.matrix, + 0, + ); + const up = new THREE.Vector3().setFromMatrixColumn(this.camera.matrix, 1); + const delta = new THREE.Vector3(); + const step = + this.controlCamera.position.distanceTo(this.controls.target) * 0.025; + if (event.key === "ArrowLeft") delta.addScaledVector(right, -step); + if (event.key === "ArrowRight") delta.addScaledVector(right, step); + if (event.key === "ArrowUp") delta.addScaledVector(up, step); + if (event.key === "ArrowDown") delta.addScaledVector(up, -step); + // Clamp the requested focus before moving the camera by the same amount. + // This preserves orbit radius at the panning limit. + const previous = this.controls.target.clone(); + this.controls.target.add(delta); + this.controls.target.clampLength(0, this.controls.maxTargetRadius); + this.controlCamera.position.add( + this.controls.target.clone().sub(previous), + ); + this.controls.update(); + } + } + + setQuality(quality: RenderQuality) { + const key = JSON.stringify(quality); + if (this.appliedQuality === key) return; + this.appliedQuality = key; + this.quality = normalizeQuality(quality); + this.pipeline.smaa.enabled = this.quality.antialias === "smaa"; + applyTextureQuality(this.scene, this.renderer, this.quality); + this.resize(); + } + + resize() { + if (!this.isOpen) return; + const width = this.canvasHost.clientWidth, + height = this.canvasHost.clientHeight; + resizeQuality( + this.renderer, + this.pipeline.composer, + this.canvasHost, + this.quality, + this.superPerformance, + ); + this.camera.aspect = width / height; + this.camera.updateProjectionMatrix(); + this.controlCamera.aspect = this.camera.aspect; + this.controlCamera.updateProjectionMatrix(); + const touch = matchMedia("(pointer: coarse)").matches; + const help = this.root.querySelector(".viewer-help")!; + help.innerHTML = touch + ? "单指旋转双指缩放 / 平移" + : "拖动旋转↑ ↓ ← → 平移滚轮缩放"; + } + + update(time: number) { + if (!this.isOpen) return; + themeEnvironment(this.scene, this.renderer, this.themeAmount); + this.source?.model.traverse(child => { if (child.userData.themeAmount) child.userData.themeAmount.value = this.themeAmount; }); + const dt = Math.min(this.lastTime ? time - this.lastTime : 1 / 60, 0.05); + this.lastTime = time; + if (this.source) { + damp(this.clarity, this.targetClarity, 8, dt); + if (Math.abs(this.clarity.value - this.targetClarity) < .0001 && Math.abs(this.clarity.velocity) < .001) + this.clarity = { value: this.targetClarity, velocity: 0 }; + this.source.setClarity?.(this.clarity.value); + damp(this.spread, this.targetSpread, this.reduced ? 45 : 5.5, dt); + if ( + Math.abs(this.spread.value - this.targetSpread) < 0.0001 && + Math.abs(this.spread.velocity) < 0.001 + ) { + this.spread = { value: this.targetSpread, velocity: 0 }; + this.setStatus(this.targetSpread ? "已拆解" : "已组装"); + } + for (const part of PARTS) { + this.groups.get(part.id)!.position.z = part.depth * this.spread.value; + } + } + this.controls.update(); + this.cameraMotion.update( + this.controlCamera, + this.controls.target, + dt, + this.reduced, + ); + const portrait = this.root.closest("[data-layout]")?.dataset.layout === "portrait"; + const zoom = portrait ? Math.min(1.15, this.camera.aspect / 0.85) / (1 + 0.08 * this.spread.value) : 1; + if (this.camera.zoom !== zoom) { + this.camera.zoom = zoom; + this.controlCamera.zoom = zoom; + this.camera.updateProjectionMatrix(); + this.controlCamera.updateProjectionMatrix(); + } + // Match the detail scene's gentle haze without washing out the object as + // the user zooms. The assembled model is centered on the world origin. + const fog = this.scene.fog as THREE.Fog; + const objectDistance = this.camera.position.length(); + fog.near = Math.max(0, objectDistance - 1); + fog.far = objectDistance + 12; + if (this.quality.antialias === "smaa") this.pipeline.composer.render(); + else this.renderer.render(this.scene, this.camera); + this.root.dataset.stats = JSON.stringify({ + ready: Boolean(this.source), + clarity: this.clarity.value, + targetClarity: this.targetClarity, + spread: this.spread.value, + target: this.targetSpread, + distance: this.camera.position.distanceTo(this.cameraMotion.focus), + targetPosition: this.cameraMotion.focus.toArray(), + requestedTarget: this.controls.target.toArray(), + requestedDistance: this.controlCamera.position.distanceTo( + this.controls.target, + ), + cameraPosition: this.camera.position.toArray(), + resetting: this.cameraMotion.resetting, + azimuth: this.controls.getAzimuthalAngle(), + polar: this.controls.getPolarAngle(), + parts: [...this.groups].map(([id, group]) => ({ + id, + z: group.position.z, + meshes: group.children.length, + })), + }); + } +} diff --git a/src/motion.ts b/src/motion.ts new file mode 100644 index 0000000..6f26b0b --- /dev/null +++ b/src/motion.ts @@ -0,0 +1,117 @@ +// Times are seconds from the reference video's 05:00 frame (25 fps). +export const smooth = (t: number) => { + t = Math.max(0, Math.min(1, t)); + return t * t * t * (10 + t * (-15 + 6 * t)); +}; +const bell = (x: number, width: number) => Math.exp(-0.5 * (x / width) ** 2); +export function archiveWave(row: number, lane: number, time: number, laneOrigin = 2) { + const t = time - 22; + const phase = row + (lane - laneOrigin) * 0.65; + const enter = smooth(t / 0.32); + const first = 3 + t * 19; + const returning = 32 - (t - 2.3) * 24; + // A crest has shoulders and a trailing trough: neighboring files describe + // one continuous surface, rather than independent staggered tweens. + const packet = (distance: number) => + 2.5 * bell(distance, 3.8) - 0.58 * bell(distance - 6, 3.5); + return ( + enter * + (packet(phase - first) * (1 - smooth((t - 2.15) / 0.65)) + + packet(phase - returning) * + smooth((t - 2.17) / 0.32) * + (1 - smooth((t - 3.5) / 0.85))) + ); +} +export function extraction(time: number) { + return ( + 0.4 * smooth((time - 25.58) / 0.82) + 2.95 * smooth((time - 27.55) / 1.3) + ); +} +// The returning scan leaves two moving shoulders around the selected file. +// Their delay grows with distance, so the neighboring files keep moving during +// the first extraction and settle before the second extraction. +export function settlingWave(distance: number, time: number) { + const age = time - 25.05 - Math.abs(distance) * 0.065; + const envelope = Math.max( + -0.42, + 2.15 - 0.17 * (Math.sqrt(distance * distance + 1) - 1), + ); + const rise = smooth(age / 0.62); + const ring = age > 0 ? Math.sin(age * 5.1) * Math.exp(-age * 1.3) : 0; + return envelope * (rise + 0.18 * ring * smooth(age / 0.16)); +} +export function baselineSelectionWave(distance: number, age: number) { + if (age < 0 || age > 3.2) return 0; + return ( + 0.8 * + smooth(age / 0.2) * + Math.exp(-age * 1.15) * + Math.cos((distance - age * 8) * 0.58) * + bell(distance - age * 8, 3.4) + ); +} + +// Retained for the comparison experiments; the user chose the signed baseline. +export function selectionWave(distance: number, age: number) { + return Math.max(0, baselineSelectionWave(distance, age)); +} + +// The source stays still while the crest expands around it. Squaring the +// positive cosine gives the ripple zero velocity at its leading/trailing edge. +export function rippleEnvelope(distance: number, age: number) { + return ( + smooth(distance / 2.5) * Math.max(0, Math.cos((distance - age * 8) * 0.58)) + ); +} + +export function columnStrength(lane: number, focus: number, progress = 1) { + const selected = 0.25 + 0.75 * bell(lane - focus, 0.55); + return 1 + (selected - 1) * smooth(progress); +} + +// A quiet idle drift, with neighboring cards slightly out of phase. +// Maximum displacement is 0.102, under 3% of a card's height. +export function idleWave(row: number, lane: number, time: number) { + return ( + 0.075 * Math.sin((time * Math.PI * 2) / 8 + row * 0.3 - lane * 0.45) + + 0.027 * Math.sin((time * Math.PI * 2) / 13 - row * 0.17 + lane * 0.3) + ); +} + +export function cinematicField( + row: number, + lane: number, + time: number, + center = 12, + focus = 2, + laneOrigin = 2, +) { + const handoff = smooth((time - 24.95) / 0.45); + const selection = smooth((time - 25.4) / 0.95); + const shoulderTime = time + 0.3 * handoff * (1 - selection); + return ( + archiveWave(row, lane, time, laneOrigin) * (1 - handoff) + + settlingWave(row - center, shoulderTime) * + columnStrength(lane, focus, (time - 25.4) / 0.95) + ); +} + +export const INSPECTION_LIFT = 4.05; +export const ALIGNMENT_EPSILON = 0.001; +// Hold altitude while facing back into the slot. Descent begins only once +// alignment is complete; this also applies to independently returning copies. +export function returnStep(angle: number, dt: number, reduced = false) { + const next = angle * Math.exp(-dt * (reduced ? 35 : 7)); + return Math.abs(next) <= ALIGNMENT_EPSILON ? 0 : next; +} +export interface Spring { + value: number; + velocity: number; +} +export function damp(s: Spring, target: number, rate: number, dt: number) { + const delta = s.value - target; + const impulse = s.velocity + rate * delta; + const decay = Math.exp(-rate * dt); + s.value = target + (delta + impulse * dt) * decay; + s.velocity = (s.velocity - rate * impulse * dt) * decay; +} diff --git a/src/pwa.ts b/src/pwa.ts new file mode 100644 index 0000000..bf86bb1 --- /dev/null +++ b/src/pwa.ts @@ -0,0 +1,108 @@ +import { assetUrl } from "./asset-url"; +import { isWallpaper } from "./wallpaper"; + +interface InstallPrompt extends Event { + prompt(): Promise; + userChoice: Promise<{ outcome: "accepted" | "dismissed" }>; +} +let installPrompt: InstallPrompt | undefined; +let registration: ServiceWorkerRegistration | undefined; +let ready = false, failed = false, reloading = false, started = false; +let tell: (message: string) => void = () => {}; +const installed = () => matchMedia("(display-mode: standalone)").matches || + Boolean((navigator as Navigator & { standalone?: boolean }).standalone); +const ios = () => /iPad|iPhone|iPod/.test(navigator.userAgent) || (navigator.platform === "MacIntel" && navigator.maxTouchPoints > 1); + +window.addEventListener("beforeinstallprompt", event => { + event.preventDefault(); + installPrompt = event as InstallPrompt; + refresh(); +}); +window.addEventListener("appinstalled", () => { installPrompt = undefined; refresh(); }); +matchMedia("(display-mode: standalone)").addEventListener("change", refresh); + +export function pwaSettingsMarkup() { + if (isWallpaper) return ""; + const status = !import.meta.env.PROD ? "开发预览不保存离线副本。" + : !window.isSecureContext ? "使用 HTTPS 地址后可保存离线副本。" + : !("serviceWorker" in navigator) ? "当前浏览器支持在线使用。" + : failed ? "离线副本未能保存,可联网后重试。" + : ready ? "离线资源已就绪,可离线浏览档案与模型。" + : "正在准备离线资源,首次需要保持联网。"; + const guidance = installed() ? "已从主屏幕打开。" + : ios() ? "在 Safari 中轻点“分享”→“添加到主屏幕”,然后从主屏幕图标打开。" + : installPrompt ? "安装后可在独立窗口中打开档案。" + : "可通过浏览器菜单安装或添加到主屏幕。"; + return `

APP / 主屏幕与离线

${guidance}

${status}

${installPrompt && !installed() ? '' : ""}${registration?.waiting ? '新版本已准备好' : ""}${failed ? '' : ""}
`; +} +function refresh() { + const current = document.querySelector("#pwa-settings"); + if (current) current.outerHTML = pwaSettingsMarkup(); + document.documentElement.dataset.offlineReady = String(ready); + const notice = document.querySelector("#pwa-update-notice"); + const waiting = Boolean(registration?.waiting); + if (notice) notice.hidden = !waiting; + const stage = document.querySelector("#stage"); + if (stage) stage.dataset.pwaUpdate = String(waiting); +} + +export async function initPwa(notify: (message: string) => void) { + if (isWallpaper) return; + tell = notify; + if (started || !import.meta.env.PROD || !window.isSecureContext || !("serviceWorker" in navigator)) return; + started = true; + try { + registration = await navigator.serviceWorker.register(assetUrl("sw.js"), { + scope: import.meta.env.BASE_URL, updateViaCache: "none", + }); + const watch = () => { + const worker = registration?.installing; + if (!worker) return; + worker.addEventListener("statechange", () => { + if (worker.state === "installed") { + failed = false; + refresh(); + } else if (worker.state === "redundant" && !registration?.active) { + failed = true; refresh(); + } + }); + }; + registration.addEventListener("updatefound", watch); + watch(); + refresh(); + void navigator.serviceWorker.ready.then(() => { ready = true; failed = false; refresh(); }); + let lastCheck = Date.now(); + document.addEventListener("visibilitychange", () => { + if (!document.hidden && Date.now() - lastCheck > 3_600_000) { + lastCheck = Date.now(); void registration?.update().catch(() => {}); + } + }); + } catch { failed = true; } + refresh(); +} + +if ("serviceWorker" in navigator) navigator.serviceWorker.addEventListener("controllerchange", () => { + if (reloading) location.reload(); + else refresh(); +}); +document.addEventListener("click", async event => { + const button = (event.target as Element).closest("[data-pwa-action]"); + if (!button) return; + if (button.dataset.pwaAction === "install" && installPrompt) { + const prompt = installPrompt; installPrompt = undefined; + try { await prompt.prompt(); await prompt.userChoice; } catch { tell("请通过浏览器菜单添加到主屏幕"); } + refresh(); + } + if (button.dataset.pwaAction === "update" && registration?.waiting) { + reloading = true; + button.disabled = true; + registration.waiting.postMessage({ type: "RHINE_APPLY_UPDATE" }); + } + if (button.dataset.pwaAction === "retry") { + failed = false; + refresh(); + if (registration) { + try { await registration.update(); } catch { failed = true; refresh(); } + } else { started = false; void initPwa(tell); } + } +}); diff --git a/src/quality-renderer.ts b/src/quality-renderer.ts new file mode 100644 index 0000000..be5d367 --- /dev/null +++ b/src/quality-renderer.ts @@ -0,0 +1,83 @@ +import * as THREE from "three"; +import { EffectComposer } from "three/addons/postprocessing/EffectComposer.js"; +import { RenderPass } from "three/addons/postprocessing/RenderPass.js"; +import { SMAAPass } from "three/addons/postprocessing/SMAAPass.js"; +import { OutputPass } from "three/addons/postprocessing/OutputPass.js"; +import { renderDimensions, type RenderQuality } from "./render-quality"; + +export function applyTextureQuality( + root: THREE.Object3D, + renderer: THREE.WebGLRenderer, + quality: RenderQuality, +) { + const maximum = Math.min( + quality.anisotropy, + renderer.capabilities.getMaxAnisotropy(), + ); + const textures = new Set(); + root.traverse((object) => { + if (!(object instanceof THREE.Mesh)) return; + for (const material of Array.isArray(object.material) + ? object.material + : [object.material]) { + for (const value of Object.values(material)) { + if (value instanceof THREE.Texture && !value.isRenderTargetTexture) + textures.add(value); + } + } + }); + for (const texture of textures) { + if (texture.anisotropy === maximum) continue; + texture.anisotropy = maximum; + texture.needsUpdate = true; + } +} + +export function resizeQuality( + renderer: THREE.WebGLRenderer, + composer: EffectComposer, + host: HTMLElement, + quality: RenderQuality, + superPerformance = false, +) { + const width = Math.max(1, host.clientWidth), + height = Math.max(1, host.clientHeight); + const dimensions = renderDimensions( + quality, + width, + height, + host.getBoundingClientRect().width / width, + devicePixelRatio, + renderer.capabilities.maxTextureSize, + superPerformance ? 921_600 : 8_294_400, + ); + renderer.setPixelRatio(dimensions.ratio); + renderer.setSize(width, height); + composer.setPixelRatio(dimensions.ratio); + composer.setSize(width, height); + renderer.transmissionResolutionScale = quality.transmission; + host.dataset.renderQuality = JSON.stringify({ + ...dimensions, + antialias: quality.antialias, + transmission: quality.transmission, + anisotropy: Math.min( + quality.anisotropy, + renderer.capabilities.getMaxAnisotropy(), + ), + superPerformance, + }); + return dimensions; +} + +export function createViewerPipeline( + renderer: THREE.WebGLRenderer, + scene: THREE.Scene, + camera: THREE.Camera, +) { + const composer = new EffectComposer(renderer); + const smaa = new SMAAPass(); + composer.addPass(new RenderPass(scene, camera)); + composer.addPass(smaa); + composer.addPass(new OutputPass()); + return { composer, smaa }; +} diff --git a/src/quality-settings.css b/src/quality-settings.css new file mode 100644 index 0000000..21489fc --- /dev/null +++ b/src/quality-settings.css @@ -0,0 +1,123 @@ +.settings-modal { + width: 1080px; + height: auto; + max-height: 950px; + overflow-y: auto; + scrollbar-gutter: stable; + scrollbar-color: #aca18d transparent; + scrollbar-width: thin; +} +.settings-modal .modal-bottom { + position: static; + margin-top: 28px; +} +.settings-modal .settings-intro { + margin-bottom: 20px; +} +.settings-modal .settings-list label { + padding: 17px 0; +} +.quality-settings { + margin-top: 24px; + border-bottom: 1px solid #c1b8a7; +} +.quality-heading { + display: flex; + justify-content: space-between; + align-items: center; + gap: 30px; +} +.quality-heading h3 { + font-size: 12px; + font-weight: 400; + letter-spacing: 0.8px; +} +.quality-heading h3 span { + margin-left: 18px; + color: #776f60; + letter-spacing: 0; +} +.quality-settings select { + font: inherit; + font-size: 12px; + color: #393d32; + background: #e7e3d9; + border: 1px solid #bdb4a1; + border-radius: 0; + padding: 9px 28px 9px 12px; + min-width: 125px; +} +.quality-summary { + font-size: 12px; + color: #6c6859; + margin: 12px 0 20px; + font-variant-numeric: tabular-nums; +} +.quality-advanced summary { + cursor: pointer; + font-size: 13px; + padding: 15px 0; + border-top: 1px solid #c1b8a7; +} +.quality-advanced summary span { + margin-left: 16px; + font-size: 11px; + color: #807866; +} +.quality-grid { + display: grid; + grid-template-columns: 1fr 1fr; + column-gap: 32px; +} +.quality-control { + display: flex; + align-items: center; + justify-content: space-between; + gap: 18px; + padding: 15px 0; + border-top: 1px solid #d0c6b5; + font-size: 13px; +} +.quality-control small { + display: block; + font-size: 10px; + color: #807866; + margin-top: 7px; + line-height: 1.5; +} +.quality-control select { + min-width: 115px; + max-width: 135px; +} +.quality-control:has(:disabled) { + opacity: 0.5; +} +.quality-range { + grid-column: 1 / -1; +} +.quality-range > div { + display: flex; + align-items: center; + gap: 18px; +} +.quality-range input { + width: 250px; + accent-color: #565c46; + cursor: pointer; +} +.quality-range output { + min-width: 44px; + text-align: right; + font-size: 12px; + font-variant-numeric: tabular-nums; +} +.quality-note { + font-size: 11px; + line-height: 1.8; + color: #807866; + margin: 12px 0 18px; +} +.quality-settings :is(select, input, summary):focus-visible { + outline: 2px solid #a67d48; + outline-offset: 4px; +} diff --git a/src/quality-settings.ts b/src/quality-settings.ts new file mode 100644 index 0000000..6fd860c --- /dev/null +++ b/src/quality-settings.ts @@ -0,0 +1,136 @@ +import { + matchingPreset, + presetLabels, + type QualityPreset, + type RenderQuality, +} from "./render-quality"; +import { isWallpaper } from "./wallpaper"; +import { escapeHtml } from "./html"; + +function choiceControl(attributes: string, label: string, value: string | number, choices: (readonly [string | number, string])[]) { + if (isWallpaper) { + const text = choices.find(([key]) => key === value)?.[1] ?? "自定义"; + return ``; + } + return ``; +} + +if (isWallpaper) document.addEventListener("click", event => { + const button = (event.target as Element).closest("[data-quality-choices]"); + if (!button || button.disabled) return; + const choices = JSON.parse(button.dataset.qualityChoices!) as [string | number, string][]; + const index = choices.findIndex(([value]) => String(value) === button.value); + const [value, label] = choices[(index + 1) % choices.length]; + button.value = String(value); + button.querySelector("[data-quality-label]")!.textContent = label; + button.dispatchEvent(new Event("change", { bubbles: true })); +}); + +function select( + quality: RenderQuality, + key: keyof RenderQuality, + label: string, + hint: string, + choices: (readonly [string | number, string])[], +) { + return ``; +} +function range( + quality: RenderQuality, + key: "scale" | "depthOfField", + label: string, + hint: string, + min: number, + max: number, +) { + return ``; +} +export function qualityMarkup(quality: RenderQuality) { + const preset = matchingPreset(quality); + return `
+

RENDER QUALITY 渲染画质

${choiceControl('id="quality-preset"', "画质预设", preset, (Object.keys(presetLabels) as QualityPreset[]).map(key => [key, presetLabels[key]]))}
+

+
精细设置 清晰度 / 材质 / 阴影
+ ${range(quality, "scale", "渲染比例", "相对屏幕像素,受密度上限限制;高比例改善细线", 50, 200)} + ${select( + quality, + "pixelRatio", + "像素密度上限", + "控制高密度屏幕的原生像素倍率", + [1, 1.5, 2, 3].map((v) => [v, `${v}×`]), + )} + ${select(quality, "antialias", "抗锯齿", "SMAA 平滑模型边缘与后处理结果", [ + ["off", "原始"], + ["smaa", "SMAA"], + ])} + ${select( + quality, + "anisotropy", + "纹理过滤", + "改善倾斜视角下的标签细节", + [1, 2, 4, 8, 16].map((v) => [v, `${v}×`]), + )} + ${select( + quality, + "transmission", + "透明材质分辨率", + "控制盖板折射画面的清晰度", + [0.25, 0.5, 0.75, 1].map((v) => [v, `${v * 100}%`]), + )} + ${select( + quality, + "shadows", + "阴影分辨率 · 阵列", + "更高分辨率保留更细的投影边缘", + [ + [0, "关闭"], + [1024, "1024"], + [2048, "2048"], + [4096, "4096"], + ], + )} + ${select( + quality, + "aoSamples", + "环境遮蔽 · 阵列", + "采样越多,接缝暗部越细腻", + [ + [0, "关闭"], + [16, "16 采样"], + [32, "32 采样"], + [64, "64 采样"], + ], + )} + ${select( + quality, + "aoResolution", + "遮蔽分辨率 · 阵列", + "降低可减轻环境遮蔽的渲染负担", + [0.5, 0.75, 1].map((v) => [v, `${v * 100}%`]), + )} + ${range(quality, "depthOfField", "景深强度 · 阵列", "0% 关闭;100% 保留原始镜头虚化", 0, 150)} +

${isWallpaper ? "即时生效,仅限当前运行;长期设置请在 Wallpaper Engine 中调整。" : "即时生效并自动保存。"}清晰度与材质设置同步至 360° 查看器。高渲染比例更适合静态观察;缓冲上限为 829 万像素,硬件限制时自动收敛。

+
`; +} + +export function syncQualityUI(quality: RenderQuality) { + const preset = document.querySelector("#quality-preset"); + if (!preset) return; + preset.value = matchingPreset(quality); + document + .querySelectorAll("[data-quality]") + .forEach((control) => { + const key = control.dataset.quality as keyof RenderQuality; + control.value = String(quality[key]); + control.disabled = key === "aoResolution" && quality.aoSamples === 0; + }); + document.querySelectorAll("[data-quality-choices]").forEach(button => { + const choices = JSON.parse(button.dataset.qualityChoices!) as [string | number, string][]; + button.querySelector("[data-quality-label]")!.textContent = choices.find(([value]) => String(value) === button.value)?.[1] ?? "自定义"; + }); + document + .querySelectorAll("[data-quality-output]") + .forEach((output) => { + output.value = `${quality[output.dataset.qualityOutput as keyof RenderQuality]}%`; + }); +} diff --git a/src/render-quality.ts b/src/render-quality.ts new file mode 100644 index 0000000..5ae6219 --- /dev/null +++ b/src/render-quality.ts @@ -0,0 +1,151 @@ +/** Rendering controls are independent of lighting, materials and animation. */ +export type RenderQuality = { + scale: number; + pixelRatio: number; + antialias: "off" | "smaa"; + shadows: number; + aoSamples: number; + aoResolution: number; + depthOfField: number; + transmission: number; + anisotropy: number; +}; + +export const qualityPresets = { + performance: { + scale: 80, + pixelRatio: 1, + antialias: "off", + shadows: 1024, + aoSamples: 0, + aoResolution: 0.5, + depthOfField: 0, + transmission: 0.5, + anisotropy: 4, + }, + original: { + scale: 100, + pixelRatio: 1.5, + antialias: "off", + shadows: 2048, + aoSamples: 32, + aoResolution: 1, + depthOfField: 100, + transmission: 1, + anisotropy: 16, + }, + high: { + scale: 125, + pixelRatio: 2, + antialias: "smaa", + shadows: 4096, + aoSamples: 32, + aoResolution: 1, + depthOfField: 100, + transmission: 1, + anisotropy: 16, + }, + ultra: { + scale: 150, + pixelRatio: 2, + antialias: "smaa", + shadows: 4096, + aoSamples: 64, + aoResolution: 1, + depthOfField: 100, + transmission: 1, + anisotropy: 16, + }, +} as const satisfies Record; +export type QualityPreset = keyof typeof qualityPresets; +export const presetLabels: Record = { + performance: "性能", + original: "原始", + high: "高", + ultra: "极高", +}; + +const member = (value: unknown, choices: readonly T[], fallback: T): T => + choices.includes(value as T) ? (value as T) : fallback; +const range = ( + value: unknown, + min: number, + max: number, + step: number, + fallback: number, +) => + typeof value === "number" && Number.isFinite(value) + ? Math.min(max, Math.max(min, Math.round(value / step) * step)) + : fallback; + +export function normalizeQuality( + value: unknown, + legacyHigh = true, +): RenderQuality { + const base = qualityPresets.original; + const v = + value && typeof value === "object" + ? (value as Record) + : {}; + // Older low quality retained full resolution, shadows and transmission. + const fallback = legacyHigh + ? base + : { ...base, pixelRatio: 1, aoSamples: 0, depthOfField: 0 }; + return { + scale: range(v.scale, 50, 200, 5, fallback.scale), + pixelRatio: member(v.pixelRatio, [1, 1.5, 2, 3], fallback.pixelRatio), + antialias: member( + v.antialias, + ["off", "smaa"] as const, + fallback.antialias, + ), + shadows: member(v.shadows, [0, 1024, 2048, 4096], fallback.shadows), + aoSamples: member(v.aoSamples, [0, 16, 32, 64], fallback.aoSamples), + aoResolution: member(v.aoResolution, [0.5, 0.75, 1], fallback.aoResolution), + depthOfField: range(v.depthOfField, 0, 150, 5, fallback.depthOfField), + transmission: member( + v.transmission, + [0.25, 0.5, 0.75, 1], + fallback.transmission, + ), + anisotropy: member(v.anisotropy, [1, 2, 4, 8, 16], fallback.anisotropy), + }; +} + +export function matchingPreset( + quality: RenderQuality, +): QualityPreset | "custom" { + return ( + (Object.keys(qualityPresets) as QualityPreset[]).find((key) => + Object.entries(qualityPresets[key]).every( + ([field, value]) => quality[field as keyof RenderQuality] === value, + ), + ) ?? "custom" + ); +} + +export function renderDimensions( + quality: RenderQuality, + width: number, + height: number, + stageScale: number, + deviceRatio: number, + maxTextureSize: number, + pixelBudget = 8_294_400, +) { + const requested = + (Math.min(deviceRatio, quality.pixelRatio) * stageScale * quality.scale) / + 100; + // Bound all full-resolution postprocessing targets to 8.3 MP and device limits. + const ratio = Math.min( + requested, + Math.sqrt(pixelBudget / Math.max(1, width * height)), + maxTextureSize / Math.max(1, width, height), + ); + return { + ratio, + width: Math.max(1, Math.floor(width * ratio)), + height: Math.max(1, Math.floor(height * ratio)), + limited: ratio < requested - 0.0001, + }; +} diff --git a/src/responsive.css b/src/responsive.css new file mode 100644 index 0000000..4bfc30d --- /dev/null +++ b/src/responsive.css @@ -0,0 +1,214 @@ +/* The film keeps its reference stage. Interactive layouts share the same DOM. */ +#stage { --edge: 59px; } +/* Opening artwork keeps undistorted local coordinates while its backdrop and + 3D canvas fill the actual display. Corner identity remains edge anchored. */ +[data-layout="opening"] .boot { inset: auto; left: calc(50% - 960px); top: calc(50% - 540px); width: 1920px; height: 1080px; } +[data-layout="opening"] .boot-white { left: calc((1920px - var(--opening-width)) / 2); top: calc((1080px - var(--opening-height)) / 2); width: var(--opening-width); height: var(--opening-height); right: auto; bottom: auto; } +[data-layout="opening"] .scan { transform: scale(var(--opening-scan-scale)); transform-origin: center; } +[data-layout="opening"][data-opening-portrait="true"] :is(.access-text, .auth-status) { font-size: 36px; } +[data-layout="opening"][data-opening-portrait="true"] .scan > span { font-size: 52px !important; letter-spacing: 1px !important; } +[data-layout="opening"] .archive-callout { left: 50.520833%; width: 49.479167%; } +[data-layout="opening"] .file-title { font-size: clamp(22px, 2vw, 32px); } +[data-mobile-boot="true"] [data-layout="opening"] .powered { left: var(--edge); right: auto; bottom: 50px; } +.mobile-entry { display: none; } +.system-nav .settings-button { position: relative; } +.settings-label { position: absolute; left: 50%; top: calc(100% + 3px); transform: translateX(-50%); font-size: 10px; white-space: nowrap; letter-spacing: 1px; } +.pwa-update-notice { position: absolute; z-index: 12; right: var(--edge); bottom: var(--bottom-edge, 32px); max-width: calc(100% - 2 * var(--edge)); min-height: 44px; display: flex; align-items: center; gap: 20px; padding: 0 16px; background: #30362a; color: #f1efdf; font-size: 12px; } +.pwa-update-notice button { min-height: 44px; color: inherit; white-space: nowrap; border-left: 1px solid #737767; padding-left: 16px; font-size: inherit; } +.pwa-update-notice[hidden], .pwa-update-notice[inert], [data-mode="boot"] .pwa-update-notice { display: none; } +#stage[data-pwa-update="true"]:not([data-mode="boot"]) .system-footer { visibility: hidden; } +.motion-preference-note { margin-top: 12px; color: #776f60; font-size: 12px; line-height: 1.7; } +.motion-preference-note p { margin: 0; } +.motion-preference-note button { min-height: 44px; margin-top: 4px; border-bottom: 1px solid #8e897b; font-size: 12px; } +.pwa-settings { margin-top: 24px; padding: 20px 0; border-block: 1px solid #c1b8a7; } +.pwa-settings h3 { font-size: 12px; font-weight: 400; letter-spacing: .8px; margin: 0 0 12px; } +.pwa-settings p { font-size: 12px; line-height: 1.8; margin: 6px 0; color: #776f60; } +.pwa-actions { display: flex; align-items: center; flex-wrap: wrap; gap: 12px; font-size: 12px; } +.pwa-actions:empty { display: none; } +.pwa-actions button { min-height: 44px; border-bottom: 1px solid #8e897b; font-size: 12px; } +@media (display-mode: standalone) { body { overscroll-behavior: none; } } +[data-mobile-boot="true"] > .mobile-entry { display: flex; position: absolute; bottom: max(28px, env(safe-area-inset-bottom)); right: max(24px, env(safe-area-inset-right)); align-items: center; gap: 32px; min-height: 48px; padding: 0 16px; font-size: 14px; border-bottom: 1px solid #8e897b; } +[data-mobile-boot="true"] .skip { display: none; } +.three-scene canvas { touch-action: none; } +[data-layout="desktop"] .archive-callout { left: 50.520833%; width: 49.479167%; } +[data-layout="desktop"] .file-summary { width: min(652px, calc(100% - 117px)); gap: 20px; } +[data-layout="desktop"] .archive-navigation { left: 26.25%; } +[data-layout="desktop"] .column-navigation { left: 52.34375%; } +[data-layout="desktop"] .archive-hint { left: 26.302083%; } +[data-layout="desktop"] .detail-content { left: 60.104167%; width: min(636px, 33.125%); max-height: calc(100% - 369px); overflow-y: auto; scrollbar-width: thin; } +[data-layout="desktop"] .terminal-modal { max-width: calc(100% - 100px); } +[data-layout="desktop"] .viewer-help, [data-layout="desktop"] .viewer-reset { width: 25%; } + +/* Compact controls use actual CSS pixels, independent of render resolution. */ +#stage:is([data-layout="compact"], [data-layout="portrait"]) { + --edge: max(20px, env(safe-area-inset-left), env(safe-area-inset-right)); + --top-edge: max(16px, env(safe-area-inset-top)); + --bottom-edge: max(12px, env(safe-area-inset-bottom)); +} +:is([data-layout="compact"], [data-layout="portrait"]) .brand { left: var(--edge); top: var(--top-edge); transform: scale(.48) !important; transform-origin: top left; } +:is([data-layout="compact"], [data-layout="portrait"]) .system-nav { top: var(--top-edge); right: var(--edge); gap: 18px; } +:is([data-layout="compact"], [data-layout="portrait"]) .system-nav button { min-height: 44px; min-width: 44px; font-size: 11px; justify-content: center; gap: 7px; } +:is([data-layout="compact"], [data-layout="portrait"]) :is(.key, .powered, .archive-hint, .hover-label) { display: none; } +:is([data-layout="compact"], [data-layout="portrait"]) .system-footer { left: var(--edge); right: var(--edge); bottom: var(--bottom-edge); font-size: 8px; gap: 12px; } +:is([data-layout="compact"], [data-layout="portrait"]) .system-footer > span:nth-child(2) { display: none; } +:is([data-layout="compact"], [data-layout="portrait"]) .system-footer button { margin-left: auto; font-size: 9px; min-height: 28px; } +:is([data-layout="compact"], [data-layout="portrait"]) .archive-callout { left: 53%; top: 35%; width: calc(47% - var(--edge)); } +:is([data-layout="compact"], [data-layout="portrait"]) .eyebrow { font-size: 9px; letter-spacing: .4px; margin-bottom: 12px; } +:is([data-layout="compact"], [data-layout="portrait"]) .eyebrow > span { margin-left: 5px; } +:is([data-layout="compact"], [data-layout="portrait"]) .file-title { font-size: 20px; gap: 6px; min-height: 44px; white-space: nowrap; } +:is([data-layout="compact"], [data-layout="portrait"]) .file-open { font-size: 20px; margin-left: auto; opacity: 1; } +:is([data-layout="compact"], [data-layout="portrait"]) .callout-rule { margin: 8px 0 0 20px; } +:is([data-layout="compact"], [data-layout="portrait"]) .callout-rule i { left: -20px; } +:is([data-layout="compact"], [data-layout="portrait"]) .file-summary { margin: 12px 0 0; width: 100%; gap: 8px; font-size: 15px; flex-wrap: wrap; } +:is([data-layout="compact"], [data-layout="portrait"]) .file-summary > span:last-child { font-size: 8px; } +:is([data-layout="compact"], [data-layout="portrait"]) .read-file { margin: 8px 0 0; min-height: 44px; font-size: 11px; gap: 36px; } +:is([data-layout="compact"], [data-layout="portrait"]) .archive-counter { left: var(--edge); bottom: 61px; } +:is([data-layout="compact"], [data-layout="portrait"]) .archive-counter .tiny-label { font-size: 8px; } +:is([data-layout="compact"], [data-layout="portrait"]) .archive-counter > div { font-size: 34px; margin-top: 4px; gap: 10px; } +:is([data-layout="compact"], [data-layout="portrait"]) .archive-counter i { font-size: 20px; } +:is([data-layout="compact"], [data-layout="portrait"]) .count-total { font-size: 15px; } +:is([data-layout="compact"], [data-layout="portrait"]) .archive-navigation { left: 20%; bottom: 62px; gap: 4px; } +:is([data-layout="compact"], [data-layout="portrait"]) .file-ticks { gap: 0; } +:is([data-layout="compact"], [data-layout="portrait"]) .file-ticks button { width: 22px; height: 44px; } +:is([data-layout="compact"], [data-layout="portrait"]) .file-ticks button:before { left: 10px; } +:is([data-layout="compact"], [data-layout="portrait"]) .column-navigation { left: auto; right: var(--edge); bottom: 62px; gap: 8px; } +:is([data-layout="compact"], [data-layout="portrait"]) .column-navigation > div { min-width: 100px; gap: 4px; } +:is([data-layout="compact"], [data-layout="portrait"]) .column-navigation strong { font-size: 13px; } +:is([data-layout="compact"], [data-layout="portrait"]) .column-navigation > div > span { font-size: 8px; } +:is([data-layout="compact"], [data-layout="portrait"]) .back-button { left: var(--edge); top: 84px; min-height: 44px; gap: 8px; } +:is([data-layout="compact"], [data-layout="portrait"]) .back-button span { font-size: 10px; } +:is([data-layout="compact"], [data-layout="portrait"]) .back-button small { display: none; } +:is([data-layout="compact"], [data-layout="portrait"]) .object-caption { left: var(--edge); bottom: 55px; } +:is([data-layout="compact"], [data-layout="portrait"]) .object-caption > span { font-size: 20px; } +:is([data-layout="compact"], [data-layout="portrait"]) .object-caption > div { font-size: 8px; } +:is([data-layout="compact"], [data-layout="portrait"]) .object-caption > small { display: none; } +:is([data-layout="compact"], [data-layout="portrait"]) .viewer-open { margin-top: 8px; min-height: 44px; padding: 5px 0; font-size: 12px; } +:is([data-layout="compact"], [data-layout="portrait"]) .detail-content { left: 53%; top: 84px; width: calc(47% - var(--edge)); bottom: 53px; overflow-y: auto; overscroll-behavior: contain; scrollbar-width: thin; padding: 0 3px 12px 0; } +:is([data-layout="compact"], [data-layout="portrait"]) .detail-kicker { font-size: 9px; gap: 8px; flex-wrap: wrap; } +:is([data-layout="compact"], [data-layout="portrait"]) .detail-content h2 { font-size: 27px; margin: 14px 0 8px; letter-spacing: -.6px; overflow-wrap: anywhere; } +:is([data-layout="compact"], [data-layout="portrait"]) .detail-title-cn { font-size: 18px; gap: 10px; flex-wrap: wrap; } +:is([data-layout="compact"], [data-layout="portrait"]) .detail-rule { margin-top: 18px; } +:is([data-layout="compact"], [data-layout="portrait"]) .metadata { margin: 18px 0; gap: 14px; } +:is([data-layout="compact"], [data-layout="portrait"]) .metadata dd { font-size: 13px; margin-top: 5px; } +:is([data-layout="compact"], [data-layout="portrait"]) .detail-tabs { gap: 16px; } +:is([data-layout="compact"], [data-layout="portrait"]) .detail-tabs button { min-height: 44px; padding: 8px 0; font-size: 9px; } +:is([data-layout="compact"], [data-layout="portrait"]) .detail-tabs button span { font-size: 12px; margin-left: 4px; } +:is([data-layout="compact"], [data-layout="portrait"]) .tab-panel { height: auto; overflow: visible; padding-top: 12px; } +:is([data-layout="compact"], [data-layout="portrait"]) .tab-panel p { font-size: 15px; text-align: left; } +:is([data-layout="compact"], [data-layout="portrait"]) .detail-actions { gap: 12px; } +:is([data-layout="compact"], [data-layout="portrait"]) .solid-button { font-size: 9px; padding: 0 12px; gap: 10px; } +:is([data-layout="compact"], [data-layout="portrait"]) .export-button { width: 132px; font-size: 9px; gap: 8px; } +:is([data-layout="compact"], [data-layout="portrait"]) .export-button span { font-size: 9px; } +:is([data-layout="compact"], [data-layout="portrait"]) .detail-footnote { gap: 12px; line-height: 1.8; flex-wrap: wrap; } + +/* Portrait keeps the specimen above an independently scrollable document. */ +[data-layout="portrait"] .brand { transform: scale(.42) !important; } +[data-layout="portrait"] .system-nav { gap: 8px; } +[data-layout="portrait"] .system-nav button[data-action="search"] { font-size: 0; gap: 0; } +[data-layout="portrait"] .system-nav button[data-action="saved"] { font-size: 10px; } +[data-layout="portrait"] .archive-callout { left: var(--edge); top: auto; bottom: 205px; width: calc(100% - 2 * var(--edge)); } +[data-layout="portrait"] .archive-counter { bottom: 58px; } +[data-layout="portrait"] .column-navigation { bottom: 58px; } +[data-layout="portrait"] .archive-navigation { bottom: 124px; left: 50%; transform: translateX(-50%); gap: 6px; } +[data-layout="portrait"] .file-ticks button { width: 27px; } +[data-layout="portrait"] .file-ticks button:before { left: 12px; } +[data-layout="portrait"] .back-button { top: 77px; } +[data-layout="portrait"] .detail-content { left: var(--edge); right: var(--edge); width: auto; top: 54%; bottom: 40px; padding-top: 12px; border-top: 1px solid #aaa496; } +[data-layout="portrait"] .object-caption { left: var(--edge); right: var(--edge); bottom: 46%; display: flex; align-items: center; justify-content: space-between; gap: 12px; } +[data-layout="portrait"] .object-caption > div { display: none; } +[data-layout="portrait"] .object-caption > span { font-size: 15px; } +[data-layout="portrait"] .viewer-open { margin: 0; border: 0; } +[data-layout="portrait"] .archive-atmosphere::after { background: linear-gradient(180deg, transparent 28%, rgba(232,229,225,.2) 39%, rgba(232,229,225,.96) 51%, #e8e5e1 60%); } +[data-layout="portrait"] .archive-atmosphere::before { background: linear-gradient(180deg, rgba(234,231,226,.35), transparent 25%, transparent 46%, rgba(232,229,225,.8) 65%, #e8e5e1 82%); } + +/* Dialogs remain inside the visual viewport when a software keyboard opens. */ +:is([data-layout="compact"], [data-layout="portrait"]) .modal-backdrop { top: var(--modal-top, 0px); height: var(--modal-height, 100%); bottom: auto; padding: var(--top-edge) var(--edge) var(--bottom-edge); } +:is([data-layout="compact"], [data-layout="portrait"]) .terminal-modal { width: min(760px, 100%); height: 100%; max-height: 850px; padding: 16px 20px; overflow: auto; overscroll-behavior: contain; scrollbar-width: thin; } +:is([data-layout="compact"], [data-layout="portrait"]) .modal-top { padding-bottom: 8px; font-size: 9px; gap: 12px; } +:is([data-layout="compact"], [data-layout="portrait"]) .modal-top button { min-height: 44px; min-width: 44px; gap: 8px; } +:is([data-layout="compact"], [data-layout="portrait"]) .terminal-modal h2 { font-size: 25px; margin: 18px 0; flex-wrap: wrap; gap: 8px; } +:is([data-layout="compact"], [data-layout="portrait"]) .terminal-modal h2 small { font-size: 12px; } +:is([data-layout="compact"], [data-layout="portrait"]) .search-field { height: 48px; gap: 12px; } +:is([data-layout="compact"], [data-layout="portrait"]) .search-field input, :is([data-layout="compact"], [data-layout="portrait"]) .search-field input::placeholder { font-size: 16px; } +:is([data-layout="compact"], [data-layout="portrait"]) .category-filters { gap: 4px 18px; padding: 12px 0; flex-wrap: wrap; } +:is([data-layout="compact"], [data-layout="portrait"]) .category-filters button { min-height: 44px; font-size: 12px; } +:is([data-layout="compact"], [data-layout="portrait"]) :is(.result-header, .result-row) { grid-template-columns: minmax(0, 1fr) 100px; } +:is([data-layout="compact"], [data-layout="portrait"]) :is(.result-header, .result-row) > span:last-child { display: none; } +:is([data-layout="compact"], [data-layout="portrait"]) .result-name { gap: 10px; padding-right: 12px; font-size: 14px; } +:is([data-layout="compact"], [data-layout="portrait"]) .result-name > b { font-size: 12px; flex-basis: 48px; } +:is([data-layout="compact"], [data-layout="portrait"]) .search-results { height: auto; overflow: visible; } +:is([data-layout="compact"], [data-layout="portrait"]) .modal-bottom { position: static; margin-top: 22px; gap: 12px; font-size: 8px; flex-wrap: wrap; } +:is([data-layout="compact"], [data-layout="portrait"]) .audio-settings { grid-template-columns: 1fr; gap: 0; } +:is([data-layout="compact"], [data-layout="portrait"]) .settings-list label { gap: 16px; } +:is([data-layout="compact"], [data-layout="portrait"]) .toggle { flex-shrink: 0; } +:is([data-layout="compact"], [data-layout="portrait"]) .quality-grid { grid-template-columns: 1fr; gap: 0; } +:is([data-layout="compact"], [data-layout="portrait"]) .quality-heading { gap: 12px; flex-wrap: wrap; } +:is([data-layout="compact"], [data-layout="portrait"]) .quality-heading h3 span { margin-left: 8px; } +:is([data-layout="compact"], [data-layout="portrait"]) .quality-range { display: block; } +:is([data-layout="compact"], [data-layout="portrait"]) .quality-range > div { margin-top: 12px; } +:is([data-layout="compact"], [data-layout="portrait"]) .quality-range input { width: 100%; min-width: 0; } +:is([data-layout="compact"], [data-layout="portrait"]) .settings-shortcuts { display: none; } +:is([data-layout="compact"], [data-layout="portrait"]) .toast { max-width: calc(100% - 40px); width: max-content; font-size: 13px; bottom: 52px; } + +/* Object study: keep controls outside the model's usable canvas. */ +:is([data-layout="compact"], [data-layout="portrait"]) .viewer-header { top: var(--top-edge); left: var(--edge); right: var(--edge); } +:is([data-layout="compact"], [data-layout="portrait"]) .viewer-back { padding: 0; min-height: 44px; gap: 8px; } +:is([data-layout="compact"], [data-layout="portrait"]) .viewer-back span { font-size: 13px; } +:is([data-layout="compact"], [data-layout="portrait"]) .viewer-back kbd { display: none; } +:is([data-layout="compact"], [data-layout="portrait"]) .viewer-heading { margin-left: 24px; padding-top: 4px; } +:is([data-layout="compact"], [data-layout="portrait"]) .viewer-heading > span { font-size: 8px; } +:is([data-layout="compact"], [data-layout="portrait"]) .viewer-heading h2 { font-size: 20px; margin: 5px 0; } +:is([data-layout="compact"], [data-layout="portrait"]) .viewer-heading p { font-size: 9px; } +:is([data-layout="compact"], [data-layout="portrait"]) .viewer-index { font-size: 30px; } +:is([data-layout="compact"], [data-layout="portrait"]) .viewer-index > span { font-size: 18px; } +:is([data-layout="compact"], [data-layout="portrait"]) .viewer-surface { top: 68px; right: var(--edge); } +:is([data-layout="compact"], [data-layout="portrait"]) .viewer-surface button { min-height: 44px; padding: 8px 16px; font-size: 12px; } +:is([data-layout="compact"], [data-layout="portrait"]) .viewer-footer { bottom: calc(var(--bottom-edge) + 18px); left: var(--edge); right: var(--edge); gap: 12px; } +:is([data-layout="compact"], [data-layout="portrait"]) .viewer-help { width: auto; max-width: 160px; font-size: 10px; gap: 5px; } +:is([data-layout="compact"], [data-layout="portrait"]) .viewer-reset { width: auto; min-height: 44px; font-size: 12px; } +:is([data-layout="compact"], [data-layout="portrait"]) .viewer-reset span { margin-left: 8px; } +:is([data-layout="compact"], [data-layout="portrait"]) .viewer-actions button { min-width: 100px; min-height: 44px; padding: 10px 12px; font-size: 12px; } +:is([data-layout="compact"], [data-layout="portrait"]) .viewer-actions button span { margin-right: 4px; } +:is([data-layout="compact"], [data-layout="portrait"]) .viewer-state { bottom: var(--bottom-edge); font-size: 9px; } +:is([data-layout="compact"], [data-layout="portrait"]) .viewer-parts { top: 130px; right: var(--edge); width: 110px; } +:is([data-layout="compact"], [data-layout="portrait"]) .viewer-parts > div { font-size: 8px; margin-bottom: 10px; } +:is([data-layout="compact"], [data-layout="portrait"]) .viewer-parts p { margin: 0 0 6px; padding: 0 0 6px 20px; } +:is([data-layout="compact"], [data-layout="portrait"]) .viewer-parts strong { font-size: 11px; } +:is([data-layout="compact"], [data-layout="portrait"]) .viewer-parts small { display: none; } +[data-layout="portrait"] .viewer-heading { position: absolute; top: 48px; left: 0; margin: 0; } +[data-layout="portrait"] .viewer-surface { top: var(--top-edge); right: var(--edge); } +[data-layout="portrait"] .viewer-index { display: none; } +[data-layout="portrait"] .viewer-canvas { top: 125px; bottom: 190px; } +[data-layout="portrait"] .viewer-parts { top: auto; bottom: 102px; left: var(--edge); width: calc(100% - 2 * var(--edge)); display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; } +[data-layout="portrait"] .viewer-parts > div { display: none; } +[data-layout="portrait"] .viewer-parts p { margin: 0; } +[data-layout="portrait"] .viewer-footer { bottom: calc(var(--bottom-edge) + 24px); flex-wrap: wrap; } +[data-layout="portrait"] .viewer-help { order: 3; max-width: none; width: 100%; justify-content: center; } + +@media (max-width: 370px) { + #stage[data-layout="portrait"] { --edge: max(14px, env(safe-area-inset-left), env(safe-area-inset-right)); } + [data-layout="portrait"] .system-nav { gap: 0; } + [data-layout="portrait"] .column-navigation { gap: 2px; } + [data-layout="portrait"] .file-ticks button { width: 24px; } + [data-layout="portrait"] .terminal-modal { padding: 12px; } + [data-layout="portrait"] :is(.result-header, .result-row) { grid-template-columns: minmax(0, 1fr) 68px; } + [data-layout="portrait"] .viewer-actions button { min-width: 90px; padding-inline: 8px; } +} +@media (max-height: 440px) and (orientation: landscape) { + [data-layout="compact"] .brand { transform: scale(.36) !important; } + [data-layout="compact"] .archive-callout { top: 25%; } + [data-layout="compact"] .eyebrow { display: none; } + [data-layout="compact"] .back-button { top: 60px; } + [data-layout="compact"] .detail-content { top: 70px; bottom: 40px; } + [data-layout="compact"] .archive-navigation { left: 17%; bottom: 44px; } + [data-layout="compact"] .column-navigation, [data-layout="compact"] .archive-counter { bottom: 44px; } + [data-layout="compact"] .viewer-heading > span, [data-layout="compact"] .viewer-parts { display: none; } +} +@media (max-width: 740px) and (max-height: 440px) and (orientation: landscape) { + [data-layout="compact"] .archive-navigation { left: var(--edge); bottom: 112px; } + [data-layout="compact"] .file-title { font-size: 18px; } + [data-layout="compact"] .file-open { margin-left: 0; } + [data-layout="compact"] .system-nav { gap: 8px; } + [data-layout="compact"] .system-nav [data-action="search"] { font-size: 0; gap: 0; } + [data-layout="compact"] .viewer-help { max-width: 110px; } +} diff --git a/src/rolling-clock.ts b/src/rolling-clock.ts new file mode 100644 index 0000000..dc91d60 --- /dev/null +++ b/src/rolling-clock.ts @@ -0,0 +1,24 @@ +import { createRollingNumber } from "@kitlangton/rolling-number"; + +export function createRollingClock(element: HTMLElement) { + element.replaceChildren(); + element.setAttribute("role", "timer"); + const numbers = [0, 1, 2].map((index) => { + if (index) element.append(":" ); + const part = document.createElement("span"); + part.setAttribute("aria-hidden", "true"); + element.append(part); + return createRollingNumber(part, { value: 0, duration: 460, motionBlur: true, + locales: "en-GB", format: { minimumIntegerDigits: 2, useGrouping: false }, animated: false }); + }); + let initialized = false; + return (date: Date, animated: boolean) => { + const values = [date.getHours(), date.getMinutes(), date.getSeconds()]; + element.setAttribute("aria-label", values.map(n => String(n).padStart(2, "0")).join(":")); + numbers.forEach((controller, index) => { + controller.update({ value: values[index], animated: animated && initialized, direction: "up" }); + if (!animated) controller.finish(); + }); + initialized = true; + }; +} diff --git a/src/scene.ts b/src/scene.ts new file mode 100644 index 0000000..d9e278d --- /dev/null +++ b/src/scene.ts @@ -0,0 +1,1778 @@ +import * as THREE from "three"; +import { ArchiveVisibility } from "./archive-visibility"; +import { disposeThreeTree } from "./three-resources"; +import { ThemeWave } from "./theme-motion"; +import { themeMaterial, themeEnvironment } from "./theme-material"; +import { RhythmMotion, rhythmDisplacement, quietBands, type MusicBands, type RhythmStyle } from "./archive-play-motion"; +import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js"; +import { createArchiveLighting, type LightingLook } from "./archive-lighting"; +import { EffectComposer } from "three/addons/postprocessing/EffectComposer.js"; +import { RenderPass } from "three/addons/postprocessing/RenderPass.js"; +import { SSAOPass } from "three/addons/postprocessing/SSAOPass.js"; +import { OutputPass } from "three/addons/postprocessing/OutputPass.js"; +import { BokehPass } from "three/addons/postprocessing/BokehPass.js"; +import { SMAAPass } from "three/addons/postprocessing/SMAAPass.js"; +import { normalizeQuality, type RenderQuality } from "./render-quality"; +import { applyTextureQuality, resizeQuality } from "./quality-renderer"; +import { CardAppearance } from "./appearance"; +import { configureInternalOptics } from "./internal-optics"; +import { DecryptionController } from "./decryption"; +import { fileAtSlot, fileLocation } from "./data"; +import { + cellKey, + sameCell, + selectionCell, + fileAtCell, + poolCell, + LOOP_COLUMNS, + LOOP_ROWS, + COLUMN_SPACING, + ROW_SPACING, + type ArchiveCell, + type ArchiveNavigation, +} from "./archive-loop"; +import { labelMarkSvg } from "./brand"; +import { archiveFraming } from "./viewport-layout"; +import { ArchiveDrag, ArchivePlaneMomentum, type DragAxis, type DragProjection, type DragPosition } from "./archive-drag"; +import { assetUrl as publicAsset } from "./asset-url"; +import { + archiveWave, + extraction, + baselineSelectionWave, + rippleEnvelope, + settlingWave, + damp, + columnStrength, + idleWave, + cinematicField, + INSPECTION_LIFT, + returnStep, +} from "./motion"; + +const ease = (t: number) => { + t = THREE.MathUtils.clamp(t, 0, 1); + return t * t * t * (t * (t * 6 - 15) + 10); +}; +export class ArchiveScene { + private inputEvents = new AbortController(); + private presence = 1; + private presenceTarget = 1; + setPresentationVisible(visible: boolean, immediate = false) { + this.presenceTarget = Number(visible); + if (immediate) this.presence = this.presenceTarget; + if (!visible) this.cancelPointer(); + } + get presentationHidden() { return this.presenceTarget === 0 && this.presence === 0; } + private presentationDrop(cell: ArchiveCell) { + const delay = .15 * (1 + Math.tanh((cell.row - this.selectedCell.row) * .1 + (cell.lane - this.selectedCell.lane) * .25)); + return 35 * Math.pow(THREE.MathUtils.clamp((1 - this.presence - delay) / .7, 0, 1), 2); + } + revealImmediately() { this.reveal = this.targetReveal; } + dispose() { + this.inputEvents.abort(); + this.cancelPointer(); + disposeThreeTree(this.scene); + this.appearance.disposeSources(); + this.model.clear(); + this.outgoing = []; + this.instances = []; + this.assemblyTemplate?.then(disposeThreeTree).catch(() => {}); + this.assemblyTemplate = undefined; + this.light.shadow.map?.dispose(); + for (const pass of this.composer.passes) pass.dispose(); + this.composer.dispose(); + this.renderer.dispose(); + this.renderer.forceContextLoss(); + this.renderer.domElement.remove(); + this.loaded = false; + } + uiOnlyParallax = false; + private theme = new ThemeWave(); + private subduedIndex = { value: 0 }; + private selectedIndexOnly = false; + private superPerformance = false; + setSuperPerformance(enabled: boolean) { + if (this.superPerformance === enabled) return; + this.superPerformance = enabled; + for (const inst of this.instances) { + const original = (inst.userData.fullMaterial ??= inst.material) as THREE.MeshPhysicalMaterial; + if (enabled && !inst.userData.fastMaterial) { + const fast = original.clone(); + fast.onBeforeCompile = original.onBeforeCompile; + fast.customProgramCacheKey = original.customProgramCacheKey.bind(original); + fast.transmission = 0; + fast.clearcoat = 0; + fast.roughness = Math.max(.45, original.roughness); + inst.userData.fastMaterial = fast; + } + inst.material = enabled ? inst.userData.fastMaterial : original; + inst.visible = !enabled || original.name.replace(/\.\d+$/, "") !== "Titanium_Fasteners"; + } + this.resize(); + } + setSelectedIndexAccent(onlySelected: boolean) { this.selectedIndexOnly = onlySelected; } + private themeAttribute?: THREE.InstancedBufferAttribute; + get themeAmount() { return this.theme.background(performance.now() / 1000); } + setTheme(dark: boolean, immediate = false) { this.theme.set(dark, performance.now() / 1000, this.selectedCell, immediate); } + private playfield = { enabled: false, bands: quietBands(), strength: 1, flatten: 0, target: null as string | null, breathing: true }; + private flatMix = 0; + private rhythm = new RhythmMotion(); + private rhythmStyle: RhythmStyle = "legacy"; + setRhythmStyle(style: RhythmStyle) { this.rhythmStyle = style; } + private relayLifts = new Map(); + private relayPoints = new Map(); + private relayActive = false; + onRelayPick?: (key: string | null) => void; + setPlayfield(enabled: boolean, bands: MusicBands, strength: number, flatten: number, target: string | null, breathing = true) { + this.playfield = { enabled, bands, strength, flatten, target, breathing }; + } + setRelayActive(active: boolean) { + if (active === this.relayActive) return; + this.cancelPointer(); this.setHover(null); this.relayActive = active; + this.pointer.set(0, 0); + } + relayPulse(key: string) { + const cell = this.relayPoints.get(key)?.cell; + if (cell && !this.reduced) this.emitPulse(cell); + } + projectRelay(key: string) { + const item = this.relayPoints.get(key); + if (!item) return null; + const point = item.point.clone().project(this.camera); + const rect = this.renderer.domElement.getBoundingClientRect(); + return { x: rect.left + (point.x + 1) * rect.width / 2, y: rect.top + (1 - point.y) * rect.height / 2 }; + } + relayCandidates() { + const rect = this.renderer.domElement.getBoundingClientRect(); + this.scene.updateMatrixWorld(true); + return [...this.relayPoints.keys()].filter(key => { + const p = this.projectRelay(key)!; + const x = (p.x - rect.left) / rect.width, y = (p.y - rect.top) / rect.height; + if (x < .18 || x > .82 || y < .32 || y > .76) return false; + const hit = this.pickCell(p.x, p.y); + return hit && cellKey(hit) === key; + }); + } + readonly renderer: THREE.WebGLRenderer; + readonly scene = new THREE.Scene(); + // The reference uses a long lens 72–140 units from the cassette. A 0.1 near + // plane quantizes adjacent optical layers to the same depth (visible shimmer). + // All visible foreground geometry is beyond 5; retain the framing and lens. + readonly camera = new THREE.PerspectiveCamera(34, 16 / 9, 5, 300); + private composer: EffectComposer; + private ao: SSAOPass; + private bokeh: BokehPass; + private instances: THREE.InstancedMesh[] = []; + private visibility = new ArchiveVisibility(); + private instanceCapacity = LOOP_COLUMNS * LOOP_ROWS; + private drawnCells: ArchiveCell[] = []; + private extraCoverage = false; + setArchiveCoverage(extra: boolean) { this.extraCoverage = extra; } + private model = new THREE.Group(); + private appearance = new CardAppearance(); + private decryption = new DecryptionController(); + private cursor = new THREE.Vector2(); + private raycaster = new THREE.Raycaster(); + private dummy = new THREE.Object3D(); + private cells: ArchiveCell[] = []; + private selectedCell: ArchiveCell = { lane: 2, row: 12 }; + private looping = false; + private coordinateOrigin: ArchiveCell = { lane: 0, row: 0 }; + private lift = { value: 0, velocity: 0 }; + private rail = { value: 0, velocity: 0 }; + private shoulder = { value: 12, velocity: 0 }; + private laneFocus = { value: 2, velocity: 0 }; + private columnCamera = { value: 0, velocity: 0 }; + private returnY: number | null = null; + private canInspect = false; + private clearance = 0; + private pulseGain = 1; + private idleGain = 0; + private lastInteraction = 0; + private scanTime = 29.1; + private scanBlend = 0; + private cameraAim = new THREE.Vector3(); + private outgoing: { + group: THREE.Group; + slot: number; + cell: ArchiveCell; + lift: { value: number; velocity: number }; + returnY: number | null; + clarity: number; + }[] = []; + private pulses: { row: number; lane: number; time: number }[] = []; + private pendingPulse: ArchiveCell | null = null; + private selectedSlot = 76; + private detail = 0; + private targetDetail = 0; + private reveal = 0; + private targetReveal = 0; + private last = 0; + private pointer = new THREE.Vector2(); + private dragging = false; + private hoverCell: ArchiveCell | null = null; + private hoverLifts = new Map(); + private archiveDrag = new ArchiveDrag(); + private dragTrack: DragPosition | null = null; + private navigatingDrag = false; + private archiveMomentum: { motion: ArchivePlaneMomentum; time: number } | null = null; + private holdingArchive = false; + private cancelPointer = () => {}; + private rotation = 0; + private targetRotation = 0; + private light: THREE.DirectionalLight; + private clock = 0; + private loaded = false; + private labelCanvas = document.createElement("canvas"); + private labelTexture?: THREE.CanvasTexture; + private labelMark = new Image(); + private reduced = false; + private quality = normalizeQuality(undefined); + private appliedQuality = ""; + private smaa = new SMAAPass(); + private aoKernelSize = 32; + private displayHeight = 0; + private layoutKind = ""; + onSelect?: (index: number, cell?: ArchiveCell) => void; + onHover?: (index: number | null) => void; + onNavigate?: (axis: "row" | "lane", direction: number) => void; + constructor( + private container: HTMLElement, + private readonly selectionPulse = baselineSelectionWave, + private readonly deferSelectionPulse = false, + private readonly lightingLook: LightingLook = "baseline", + ) { + this.renderer = new THREE.WebGLRenderer({ + antialias: true, + alpha: false, + powerPreference: "high-performance", + }); + this.renderer.setPixelRatio( + Math.min(devicePixelRatio, 1.5) * + Math.min(innerWidth / 1920, innerHeight / 1080), + ); + this.renderer.setSize(container.clientWidth, container.clientHeight); + this.renderer.info.autoReset = false; + this.renderer.shadowMap.enabled = true; + this.renderer.shadowMap.type = THREE.PCFSoftShadowMap; + this.renderer.toneMapping = THREE.ACESFilmicToneMapping; + this.renderer.toneMappingExposure = 1.05; + this.renderer.domElement.setAttribute( + "aria-label", + "三维研究档案阵列,点击选择,左右拖动切列,上下拖动或滚轮切换列内档案", + ); + container.appendChild(this.renderer.domElement); + this.scene.background = new THREE.Color("#eae5e1"); + this.scene.fog = new THREE.Fog("#eae5e1", 22, 47); + this.light = createArchiveLighting(this.renderer, this.scene, lightingLook); + this.light.castShadow = true; + Object.assign(this.light.shadow.camera, { + left: -16, + right: 16, + top: 15, + bottom: -15, + near: 0.1, + far: 45, + }); + this.light.shadow.mapSize.set(2048, 2048); + this.light.shadow.normalBias = lightingLook === "refined" ? 0.018 : 0.035; + this.light.shadow.bias = lightingLook === "refined" ? -0.00012 : -0.0003; + this.light.shadow.radius = 4; + const floor = new THREE.Mesh( + new THREE.PlaneGeometry(200, 200), + new THREE.MeshStandardMaterial({ color: "#d8c9b9", roughness: 0.95 }), + ); + floor.rotation.x = -Math.PI / 2; + floor.name = "archive-floor"; + floor.position.y = -4.63; + floor.receiveShadow = true; + this.scene.add(floor); + this.camera.position.set(-62.26, 35.98, 43.28); + this.cameraAim.set(-0.5, 1.1, 0.4); + this.camera.fov = 6.15; + this.camera.lookAt(this.cameraAim); + this.composer = new EffectComposer(this.renderer); + this.composer.addPass(new RenderPass(this.scene, this.camera)); + this.ao = new SSAOPass( + this.scene, + this.camera, + container.clientWidth, + container.clientHeight, + ); + this.ao.kernelRadius = lightingLook === "refined" ? 0.44 : 0.38; + this.ao.minDistance = 0.001; + this.ao.maxDistance = 0.09; + this.composer.addPass(this.ao); + this.bokeh = new BokehPass(this.scene, this.camera, { + focus: 25, + aperture: 0.0018, + maxblur: 0.011, + }); + this.composer.addPass(this.bokeh); + this.smaa.enabled = false; + this.composer.addPass(this.smaa); + this.composer.addPass(new OutputPass()); + this.bindPointer(); + } + async load(assetUrl = publicAsset("assets/archive-cassette.glb")) { + this.labelMark.src = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(labelMarkSvg)}`; + await this.labelMark.decode(); + const gltf = await new GLTFLoader().loadAsync( + assetUrl, + ); + gltf.scene.updateMatrixWorld(true); + const meshes: THREE.Mesh[] = []; + gltf.scene.traverse((o) => { + if (o instanceof THREE.Mesh) meshes.push(o); + }); + const count = LOOP_COLUMNS * LOOP_ROWS; + for (let index = 0; index < count; index++) { + const cell = poolCell(index); + this.cells.push(cell); + } + for (const mesh of meshes) { + const geom = mesh.geometry + .clone() + .applyMatrix4(mesh.matrixWorld) + .scale(1, 1, 1); + const source = mesh.material as THREE.MeshStandardMaterial; + const name = source.name.replace(/\.\d+$/, ""); + const mat = source.clone() as THREE.MeshPhysicalMaterial; + mat.envMapIntensity = 0.6; + if (name === "Frosted_Polymer") { + mat.color.set("#fffdfa"); + mat.transmission = 0.9; + mat.thickness = 0.12; + mat.roughness = 0.21; + mat.ior = 1.46; + mat.attenuationColor = new THREE.Color("#eee6df"); + mat.attenuationDistance = 2; + } + if (name === "Internal_Ceramic") { + mat.color.set(this.lightingLook === "refined" ? "#c4baae" : "#c7beb6"); + mat.roughness = 0.6; + } + if (name === "Printed_Label") mat.color.set("#eae5dc"); + if (name === "Ivory_Edges") { + mat.color.set("#f0e7df"); + mat.roughness = 0.31; + mat.transmission = 0.65; + mat.thickness = 0.04; + } + if (name === "Optical_Diffuser") { + mat.color.set("#e2dad4"); + mat.transmission = 0; + mat.roughness = 0.7; + } + if (name === "Subsurface_Optics") { + mat.color.set(this.lightingLook === "refined" ? "#b9a796" : "#b9aba1"); + mat.roughness = 0.48; + mat.metalness = 0.05; + } + if (name === "Optical_Edges") { + // Internal refractive shoulders must be in the opaque capture: WebGL's + // screen-space transmission cannot recursively sample another glass mesh. + mat.transmission = 0; + mat.color.set(this.lightingLook === "refined" ? "#d8c7b5" : "#d4c7be"); + mat.roughness = 0.26; + mat.metalness = 0.08; + } + configureInternalOptics(name, mat); + if (name === "Carbon_Ink") continue; + const selectedMesh = new THREE.Mesh(geom, mat); + selectedMesh.userData.surface = name; + selectedMesh.castShadow = name === "Optical_Diffuser"; + selectedMesh.receiveShadow = true; + this.model.add(selectedMesh); + // Only the shell, edge and fasteners remain visible within tightly packed rows. + // Keep sub-millimetre optical/typographic geometry on the extracted cassette. + if ( + ![ + "Frosted_Polymer", + "Ivory_Edges", + "Titanium_Fasteners", + "Index_Inlay", + "Optical_Diffuser", + ].includes(name) + ) { + this.appearance.register(name, mat); + continue; + } + const arrayMat = mat.clone(); + if (name === "Frosted_Polymer") { + arrayMat.transmission = 0.78; + if (this.lightingLook === "refined") { + // Longer oblique paths pick up the warm body tint, while the thin + // edges and the extracted clear cover retain a brighter response. + arrayMat.thickness = 0.28; + arrayMat.attenuationColor.set("#d4c7b4"); + arrayMat.attenuationDistance = 1.2; + } + arrayMat.transparent = false; + arrayMat.color.set("#fff7ed"); + arrayMat.onBeforeCompile = (shader) => { + shader.vertexShader = + "varying float vPanelHeight;\n" + shader.vertexShader; + shader.vertexShader = shader.vertexShader.replace( + "#include ", + "#include \nvPanelHeight = position.y / 3.7;", + ); + shader.fragmentShader = + "varying float vPanelHeight;\n" + shader.fragmentShader; + shader.fragmentShader = shader.fragmentShader.replace( + "#include ", + "#include \ndiffuseColor.rgb *= mix(vec3(0.40, 0.30, 0.20), vec3(1.0, 0.98, 0.94), smoothstep(0.1, 1.0, vPanelHeight));", + ); + }; + arrayMat.roughness = 0.28; + arrayMat.clearcoat = 0.3; + arrayMat.clearcoatRoughness = 0.25; + } + if (name === "Optical_Diffuser") arrayMat.color.set("#806447"); + if (name === "Ivory_Edges") { + arrayMat.transmission = 0; + arrayMat.color.set( + this.lightingLook === "refined" ? "#dcc9b0" : "#fff5e9", + ); + arrayMat.roughness = 0.38; + } + if (name === "Index_Inlay") { + arrayMat.color.set("#e4d6c5"); + arrayMat.metalness = 0.05; + } + this.appearance.register(name, mat, arrayMat); + this.themeAttribute ??= new THREE.InstancedBufferAttribute(new Float32Array(count), 1).setUsage(THREE.DynamicDrawUsage); + geom.setAttribute("archiveTheme", this.themeAttribute); + themeMaterial(arrayMat, name, true, this.subduedIndex); + const inst = new THREE.InstancedMesh(geom, arrayMat, count); + inst.instanceMatrix.setUsage(THREE.DynamicDrawUsage); + inst.castShadow = name === "Optical_Diffuser"; + inst.receiveShadow = true; + inst.frustumCulled = false; + this.instances.push(inst); + this.scene.add(inst); + } + this.labelCanvas.width = 1024; + this.labelCanvas.height = 440; + this.labelTexture = new THREE.CanvasTexture(this.labelCanvas); + this.labelTexture.colorSpace = THREE.SRGBColorSpace; + this.labelTexture.anisotropy = + this.renderer.capabilities.getMaxAnisotropy(); + const label = new THREE.Mesh( + new THREE.PlaneGeometry(0.99, 0.46), + new THREE.MeshBasicMaterial({ + map: this.labelTexture, + toneMapped: false, + transparent: true, + depthWrite: false, + }), + ); + label.position.set(-1.36, 3.04, 0.255); + this.model.add(label); + this.appearance.prepare(this.model); + this.appearance.apply(this.model, 0); + this.drawLabel(0); + this.scene.add(this.model); + this.model.position.copy(this.cellPosition(poolCell(this.selectedSlot))); + this.loaded = true; + } + + private assemblyTemplate?: Promise; + async createAssemblyModel() { + this.assemblyTemplate ??= new GLTFLoader() + .loadAsync(publicAsset("assets/archive-assembly.glb")) + .then((gltf) => { + gltf.scene.updateMatrixWorld(true); + return gltf.scene; + }) + .catch((error) => { + this.assemblyTemplate = undefined; + throw error; + }); + const template = await this.assemblyTemplate; + const model = new THREE.Group(); + const meshes: THREE.Mesh[] = []; + template.traverse((object) => { + if (!(object instanceof THREE.Mesh)) return; + const name = (object.material as THREE.Material).name.replace( + /\.\d+$/, + "", + ); + const mesh = new THREE.Mesh( + object.geometry.clone().applyMatrix4(object.matrixWorld), + object.material, + ); + mesh.userData.surface = name; + mesh.userData.assemblyPart = object.userData.assemblyPart; + model.add(mesh); + meshes.push(mesh); + }); + this.appearance.prepare(model); + this.appearance.apply(model, 1); + this.appearance.setClarity(model, this.decryption.clarity); + this.appearance.setTheme(model, this.themeAmount); + const canvas = document.createElement("canvas"); + canvas.width = this.labelCanvas.width; + canvas.height = this.labelCanvas.height; + canvas.getContext("2d")!.drawImage(this.labelCanvas, 0, 0); + const texture = new THREE.CanvasTexture(canvas); + texture.colorSpace = THREE.SRGBColorSpace; + texture.anisotropy = this.renderer.capabilities.getMaxAnisotropy(); + const label = new THREE.Mesh( + new THREE.PlaneGeometry(0.99, 0.46), + new THREE.MeshBasicMaterial({ + map: texture, + toneMapped: false, + transparent: true, + depthWrite: false, + }), + ); + label.position.set(-1.36, 3.04, 0.255); + label.userData.assemblyPart = "cover"; + label.userData.themeAmount = themeMaterial(label.material, "Printed_Canvas"); + label.userData.themeAmount.value = this.themeAmount; + model.add(label); + meshes.push(label); + return { + model, + setClarity: (value: number) => this.appearance.setClarity(model, value), + dispose: () => { + for (const mesh of meshes) { + mesh.geometry.dispose(); + (mesh.material as THREE.Material).dispose(); + } + texture.dispose(); + }, + }; + } + setMode(mode: "hidden" | "archive" | "detail") { + this.cancelPointer(); + this.setHover(null); + if (mode === "detail") this.decryption.enter(this.scanBlend > .9 && this.decryption.clarity > .999); + else this.decryption.leave(); + if (mode === "hidden") this.decryption.select(); + if (mode !== "archive") this.pendingPulse = null; + this.looping = mode !== "hidden"; + if (!this.looping) { + const canonical = fileLocation(fileAtSlot(this.selectedSlot)); + this.selectedCell = { lane: canonical.lane, row: canonical.row }; + this.coordinateOrigin = { lane: 0, row: 0 }; + for (const old of this.outgoing) { + this.scene.remove(old.group); + this.appearance.dispose(old.group); + } + this.outgoing = []; + } + this.lastInteraction = this.clock; + this.targetReveal = mode === "hidden" ? 0 : 1; + this.targetDetail = mode === "detail" ? 1 : 0; + this.dragging = false; + if (mode !== "detail") { + this.targetRotation = 0; + if (this.rotation !== 0) this.returnY = this.model.position.y; + } else this.returnY = null; + } + setReduced(value: boolean) { + if (value && !this.reduced) this.cancelPointer(); + this.reduced = value; + } + setQuality(value: RenderQuality | boolean) { + const quality = + typeof value === "boolean" + ? normalizeQuality(undefined, value) + : normalizeQuality(value); + const key = JSON.stringify(quality); + if (this.appliedQuality === key) return; + this.appliedQuality = key; + this.quality = quality; + if (quality.aoSamples && quality.aoSamples !== this.aoKernelSize) { + const old = this.ao; + this.ao = new SSAOPass(this.scene, this.camera, 1, 1, quality.aoSamples); + this.ao.kernelRadius = old.kernelRadius; + this.ao.minDistance = old.minDistance; + this.ao.maxDistance = old.maxDistance; + const index = this.composer.passes.indexOf(old); + this.composer.removePass(old); + this.composer.insertPass(this.ao, index); + old.dispose(); + this.aoKernelSize = quality.aoSamples; + } + this.ao.enabled = quality.aoSamples > 0; + this.bokeh.enabled = quality.depthOfField > 0; + this.smaa.enabled = quality.antialias === "smaa"; + this.renderer.shadowMap.enabled = quality.shadows > 0; + const size = Math.min( + quality.shadows || 1024, + this.renderer.capabilities.maxTextureSize, + ); + if (this.light.shadow.mapSize.x !== size) { + this.light.shadow.map?.dispose(); + this.light.shadow.map = null; + this.light.shadow.mapSize.set(size, size); + } + this.light.shadow.needsUpdate = true; + applyTextureQuality(this.scene, this.renderer, quality); + this.resize(); + } + private cellPosition(cell: ArchiveCell) { + return new THREE.Vector3( + (cell.lane - 2) * COLUMN_SPACING, + -4.6, + (cell.row - 15.5) * ROW_SPACING, + ); + } + private rebaseCoordinates() { + // Periodically reduce the logical coordinates while preserving every + // relative position, spring velocity, ripple and idle phase. + const shift = { + lane: + Math.abs(this.selectedCell.lane) > 2048 + ? Math.round((this.selectedCell.lane - 2) / 5) * 5 + : 0, + row: + Math.abs(this.selectedCell.row) > 2048 + ? Math.floor((this.selectedCell.row - 12) / 8) * 8 + : 0, + }; + if (!shift.lane && !shift.row) return; + this.setHover(null); + this.hoverLifts.clear(); + this.selectedCell.lane -= shift.lane; + this.selectedCell.row -= shift.row; + this.coordinateOrigin.lane += shift.lane; + this.coordinateOrigin.row += shift.row; + this.laneFocus.value -= shift.lane; + this.shoulder.value -= shift.row; + this.columnCamera.value -= shift.lane * COLUMN_SPACING; + this.rail.value += shift.row * ROW_SPACING; + for (const old of this.outgoing) { + old.cell.lane -= shift.lane; + old.cell.row -= shift.row; + } + for (const pulse of this.pulses) { + pulse.lane -= shift.lane; + pulse.row -= shift.row; + } + if (this.pendingPulse) { + this.pendingPulse.lane -= shift.lane; + this.pendingPulse.row -= shift.row; + } + } + select(index: number, navigation?: ArchiveNavigation) { + if (!this.navigatingDrag) this.cancelPointer(); + this.setHover(null); + this.lastInteraction = this.clock; + const next = fileLocation(index).slot; + const canonical = fileLocation(index); + const cell = this.looping + ? selectionCell(index, this.selectedCell, navigation) + : { lane: canonical.lane, row: canonical.row }; + const changed = !sameCell(cell, this.selectedCell); + if (this.looping && changed && this.loaded && this.lift.value > 0.0001) { + const group = this.model.clone(true); + const label = group.children[group.children.length - 1] as THREE.Mesh; + const canvas = document.createElement("canvas"); + canvas.width = 1024; + canvas.height = 440; + canvas.getContext("2d")!.drawImage(this.labelCanvas, 0, 0); + const map = new THREE.CanvasTexture(canvas); + map.colorSpace = THREE.SRGBColorSpace; + label.material = new THREE.MeshBasicMaterial({ + map, + toneMapped: false, + transparent: true, + depthWrite: false, + }); + // Clone carries the selected label material by reference. Replace it + // before installing appearance shaders, so theme hooks are not appended + // to the original label a second time on every selection. + this.appearance.prepare(group); + this.appearance.apply(group, ease(this.lift.value / 0.4)); + this.appearance.setClarity(group, this.decryption.clarity); + this.scene.add(group); + this.outgoing.push({ + group, + slot: this.selectedSlot, + cell: { ...this.selectedCell }, + lift: { ...this.lift }, + returnY: group.rotation.y !== 0 ? group.position.y : null, + clarity: this.decryption.clarity, + }); + this.lift.value = 0; + this.lift.velocity = 0; + } + this.selectedSlot = next; + this.selectedCell = cell; + if (changed) { + this.decryption.select(); + this.rotation = 0; + this.returnY = null; + } + const returning = this.outgoing.findIndex((o) => sameCell(o.cell, cell)); + if (returning >= 0) { + const o = this.outgoing[returning]; + this.lift = { ...o.lift }; + this.rotation = o.group.rotation.y; + this.returnY = o.returnY; + this.decryption.select(o.clarity); + this.scene.remove(o.group); + this.appearance.dispose(o.group); + this.outgoing.splice(returning, 1); + } + if (this.deferSelectionPulse) { + this.pendingPulse = this.looping ? { ...cell } : null; + } else this.emitPulse(cell); + this.targetRotation = 0; + this.drawLabel(index); + } + private emitPulse(cell: ArchiveCell) { + this.pulses.push({ ...cell, time: this.clock }); + this.pulses = this.pulses.slice(-6); + } + private drawLabel(index: number) { + if (!this.labelTexture) return; + const c = this.labelCanvas.getContext("2d")!; + c.fillStyle = "#e6e2d9"; + c.fillRect(0, 0, 1024, 440); + c.fillStyle = "#171713"; + c.fillRect(12, 12, 1000, 6); + c.fillRect(12, 419, 1000, 3); + c.font = "bold 81px MiSans"; + c.fillText("RHINE LAB, LLC.", 22, 116); + c.font = "32px MiSans"; + c.fillStyle = "#878476"; + c.fillText("INTERNAL DATABASE", 25, 174); + c.fillStyle = "#171713"; + c.font = "bold 130px MiSans"; + c.fillText("NO." + String(index + 1).padStart(3, "0"), 22, 360); + c.fillRect(782, 32, 221, 39); + c.fillStyle = "#eee9de"; + c.font = "24px MiSans"; + c.fillText("R L / I S", 809, 61); + c.fillStyle = "#171713"; + c.font = "bold 64px MiSans"; + c.fillText("INFO", 830, 143); + c.drawImage(this.labelMark, 790, 242, 210, 98); + this.labelTexture.needsUpdate = true; + } + private ensureInstanceCapacity(required: number) { + if (required <= this.instanceCapacity) return; + const capacity = Math.max(required, this.instanceCapacity * 2); + for (const inst of this.instances) { + inst.dispose(); + const matrix = new THREE.InstancedBufferAttribute(new Float32Array(capacity * 16), 16).setUsage(THREE.DynamicDrawUsage); + matrix.array.set(inst.instanceMatrix.array); + inst.instanceMatrix = matrix; + } + const previousTheme = this.themeAttribute; + this.themeAttribute = new THREE.InstancedBufferAttribute(new Float32Array(capacity), 1).setUsage(THREE.DynamicDrawUsage); + if (previousTheme) this.themeAttribute.array.set(previousTheme.array); + for (const inst of this.instances) inst.geometry.setAttribute("archiveTheme", this.themeAttribute); + this.instanceCapacity = capacity; + } + resize() { + const w = this.container.clientWidth, + h = this.container.clientHeight; + const kind = this.container.closest("[data-layout]")?.dataset.layout ?? ""; + const displayHeight = this.container.getBoundingClientRect().height; + if (this.layoutKind === "cinematic" && kind !== "cinematic" && this.displayHeight > 0) { + // Removing letterboxing starts from the same apparent model size. The + // existing camera interpolation then carries it to the responsive anchor. + this.camera.fov = THREE.MathUtils.radToDeg(2 * Math.atan( + Math.tan(THREE.MathUtils.degToRad(this.camera.fov / 2)) * displayHeight / this.displayHeight, + )); + } + this.displayHeight = displayHeight; + this.layoutKind = kind; + const dimensions = resizeQuality( + this.renderer, + this.composer, + this.container, + this.quality, + this.superPerformance, + ); + this.ao.setSize( + Math.max(1, Math.floor(dimensions.width * this.quality.aoResolution)), + Math.max(1, Math.floor(dimensions.height * this.quality.aoResolution)), + ); + this.container.dataset.renderQuality = JSON.stringify({ + ...JSON.parse(this.container.dataset.renderQuality!), + aoSamples: this.ao.enabled ? this.aoKernelSize : 0, + aoWidth: this.ao.width, + aoHeight: this.ao.height, + shadows: this.renderer.shadowMap.enabled + ? this.light.shadow.mapSize.x + : 0, + depthOfField: this.bokeh.enabled ? this.quality.depthOfField : 0, + }); + this.camera.aspect = w / h; + this.camera.updateProjectionMatrix(); + } + private canBrowse() { + return ( + this.presenceTarget === 1 && + this.looping && + !this.targetDetail && + this.detail < 0.2 && + this.reveal >= 0.8 && + this.loaded && + !this.container.closest("[inert]") + ); + } + private setHover(cell: ArchiveCell | null) { + if ( + (!cell && !this.hoverCell) || + (cell && this.hoverCell && sameCell(cell, this.hoverCell)) + ) + return; + this.hoverCell = cell ? { ...cell } : null; + this.onHover?.(cell ? fileAtCell(cell) : null); + } + private pickCell(x: number, y: number) { + const r = this.renderer.domElement.getBoundingClientRect(); + this.cursor.set( + ((x - r.left) / r.width) * 2 - 1, + (-(y - r.top) / r.height) * 2 + 1, + ); + this.raycaster.setFromCamera(this.cursor, this.camera); + const hit = this.raycaster.intersectObjects( + [this.instances[0], this.model, ...this.outgoing.map((o) => o.group)], + true, + )[0]; + if (!hit) return null; + if (hit.instanceId !== undefined) return { ...this.drawnCells[hit.instanceId] }; + let object: THREE.Object3D | null = hit.object; + while (object) { + const copy = this.outgoing.find((o) => o.group === object); + if (copy) return { ...copy.cell }; + object = object.parent; + } + return { ...this.selectedCell }; + } + private trackCoordinate(axis: DragAxis, value: number) { + return axis === "lane" + ? value / COLUMN_SPACING + 2 + : (-value - 2.17) / ROW_SPACING + 15.5; + } + private dragProjection(): DragProjection { + this.model.updateMatrixWorld(true); + this.camera.updateMatrixWorld(true); + const center = this.model.localToWorld(new THREE.Vector3(0, 1.85, 0)); + const rect = this.renderer.domElement.getBoundingClientRect(); + const project = (motion: THREE.Vector3) => { + const from = center + .clone() + .addScaledVector(motion, -0.5) + .project(this.camera); + const to = center + .clone() + .addScaledVector(motion, 0.5) + .project(this.camera); + return { + x: ((to.x - from.x) * rect.width) / 2, + y: (-(to.y - from.y) * rect.height) / 2, + }; + }; + // Positive navigation moves the array along -X for columns and -Z for rows. + return { + lane: project(new THREE.Vector3(-COLUMN_SPACING, 0, 0)), + row: project(new THREE.Vector3(0, 0, -ROW_SPACING)), + }; + } + private trackPosition(axis: DragAxis, coordinate: number) { + return axis === "lane" + ? (coordinate - 2) * COLUMN_SPACING + : -2.17 - (coordinate - 15.5) * ROW_SPACING; + } + private navigatePlane(coordinate: DragPosition) { + const goal = { lane: Math.round(coordinate.lane), row: Math.round(coordinate.row) }; + if (sameCell(goal, this.selectedCell)) return; + const from = { ...this.selectedCell }; + const steps = Math.min(64, Math.max(Math.abs(goal.lane - from.lane), Math.abs(goal.row - from.row))); + this.navigatingDrag = true; + try { + // Select physical cells along the travelled segment in one update per cell. + // Column memory is updated by onSelect, but never pulls a held plane away. + for (let i = 1; i <= steps; i++) { + const cell = { + lane: Math.round(from.lane + (goal.lane - from.lane) * i / steps), + row: Math.round(from.row + (goal.row - from.row) * i / steps), + }; + if (!sameCell(cell, this.selectedCell)) this.onSelect?.(fileAtCell(cell), cell); + } + } finally { + this.navigatingDrag = false; + } + } + private stopMomentum() { + if (this.archiveMomentum) { + this.columnCamera.velocity = 0; + this.rail.velocity = 0; + } + this.archiveMomentum = null; + } + private bindPointer() { + const canvas = this.renderer.domElement; + let activePointer: number | null = null; + let previousX = 0, + startX = 0, + startY = 0, + moved = false; + let browse = false, + cancelled = false; + let startTrack = { lane: 0, row: 0 }; + let wheelTotal = 0, + wheelTime = 0; + const pointers = new Set(); + const hover = (e: PointerEvent) => { + if ( + e.pointerType !== "mouse" || + !this.canBrowse() || + this.archiveMomentum + ) + return; + const r = canvas.getBoundingClientRect(); + this.pointer.set( + (e.clientX - r.left) / r.width - 0.5, + (e.clientY - r.top) / r.height - 0.5, + ); + const cell = this.pickCell(e.clientX, e.clientY); + this.setHover(cell); + canvas.style.cursor = cell ? "pointer" : "grab"; + }; + const reset = () => { + const id = activePointer; + activePointer = null; + this.dragging = false; + this.dragTrack = null; + this.holdingArchive = false; + browse = false; + this.setHover(null); + canvas.style.cursor = this.canBrowse() ? "grab" : "default"; + if (id !== null && canvas.hasPointerCapture(id)) + canvas.releasePointerCapture(id); + }; + this.cancelPointer = () => { + cancelled = true; + this.stopMomentum(); + pointers.clear(); + wheelTotal = 0; + reset(); + }; + const moveArchive = (e: PointerEvent) => { + const pending = !this.archiveDrag.active; + for (const sample of e.getCoalescedEvents?.() ?? []) { + this.archiveDrag.move(sample.clientX, sample.clientY, sample.timeStamp); + } + this.archiveDrag.move(e.clientX, e.clientY, e.timeStamp); + moved ||= this.archiveDrag.moved; + if (!this.archiveDrag.active) return; + if (pending) { + startTrack = { lane: this.columnCamera.value, row: this.rail.value }; + } + this.setHover(null); + this.lastInteraction = this.clock; + canvas.style.cursor = "grabbing"; + this.dragTrack = { + lane: startTrack.lane + this.archiveDrag.value.lane * COLUMN_SPACING, + row: startTrack.row - this.archiveDrag.value.row * ROW_SPACING, + }; + this.columnCamera.value = this.dragTrack.lane; + this.rail.value = this.dragTrack.row; + this.columnCamera.velocity = this.rail.velocity = 0; + this.navigatePlane({ + lane: this.trackCoordinate("lane", this.columnCamera.value), + row: this.trackCoordinate("row", this.rail.value), + }); + }; + canvas.addEventListener("pointerdown", (e) => { + if (e.pointerType === "mouse" && e.button !== 0) return; + if (!this.canBrowse() && !this.canInspect) return; + pointers.add(e.pointerId); + if (pointers.size > 1) { + cancelled = true; + reset(); + return; + } + activePointer = e.pointerId; + cancelled = false; + moved = this.archiveMomentum !== null; + startX = previousX = e.clientX; + startY = e.clientY; + browse = this.canBrowse(); + this.stopMomentum(); + if (browse) { + this.columnCamera.velocity = 0; + this.rail.velocity = 0; + } + this.holdingArchive = browse; + this.dragging = !browse && this.canInspect; + startTrack = { lane: this.columnCamera.value, row: this.rail.value }; + this.archiveDrag.start(e.clientX, e.clientY, this.dragProjection(), e.timeStamp); + this.setHover(null); + canvas.setPointerCapture(e.pointerId); + if (this.relayActive) { this.holdingArchive = false; this.dragging = false; } + }, { signal: this.inputEvents.signal }); + canvas.addEventListener("pointermove", (e) => { + if (this.relayActive) { + if (e.pointerId === activePointer) moved ||= Math.hypot(e.clientX - startX, e.clientY - startY) > 7; + return; + } + if (activePointer !== null && e.pointerId !== activePointer) return; + if (activePointer === null) { + hover(e); + return; + } + if (cancelled) return; + moved ||= Math.hypot(e.clientX - startX, e.clientY - startY) > 7; + if (browse) { + if (!this.canBrowse()) { + this.cancelPointer(); + return; + } + moveArchive(e); + return; + } + if (this.dragging && this.canInspect) { + this.targetRotation = THREE.MathUtils.clamp( + this.targetRotation + (e.clientX - previousX) * 0.004, + -0.8, + 0.8, + ); + previousX = e.clientX; + } + }, { signal: this.inputEvents.signal }); + canvas.addEventListener("pointerup", (e) => { + pointers.delete(e.pointerId); + if (e.pointerId !== activePointer) return; + if (this.relayActive) { + if (!cancelled && !moved) { const cell = this.pickCell(e.clientX, e.clientY); this.onRelayPick?.(cell ? cellKey(cell) : null); } + reset(); return; + } + if (!cancelled && browse && this.canBrowse()) { + moveArchive(e); + if (this.archiveDrag.active) { + if (!this.reduced) { + this.archiveMomentum = { + time: performance.now() / 1000, + motion: new ArchivePlaneMomentum( + { lane: this.trackCoordinate("lane", this.columnCamera.value), row: this.trackCoordinate("row", this.rail.value) }, + this.archiveDrag.releaseVelocity(e.timeStamp, false), + ), + }; + } + } else if (!moved) { + const cell = this.pickCell(e.clientX, e.clientY); + if (cell) this.onSelect?.(fileAtCell(cell), cell); + } + } + reset(); + }, { signal: this.inputEvents.signal }); + canvas.addEventListener("pointercancel", (e) => { + pointers.delete(e.pointerId); + if (e.pointerId === activePointer) { + cancelled = true; + reset(); + } + }, { signal: this.inputEvents.signal }); + canvas.addEventListener("lostpointercapture", (e) => { + pointers.delete(e.pointerId); + if (e.pointerId === activePointer) { + cancelled = true; + reset(); + } + }, { signal: this.inputEvents.signal }); + canvas.addEventListener("pointerleave", () => { + this.pointer.set(0, 0); + this.setHover(null); + }, { signal: this.inputEvents.signal }); + canvas.addEventListener( + "wheel", + (e) => { + if (this.relayActive) { e.preventDefault(); return; } + if ( + !this.canBrowse() || + activePointer !== null || + e.ctrlKey || + Math.abs(e.deltaX) > Math.abs(e.deltaY) + ) + return; + e.preventDefault(); + if (this.archiveMomentum) this.stopMomentum(); + const now = performance.now(); + const delta = THREE.MathUtils.clamp( + e.deltaY * + (e.deltaMode === 1 + ? 40 + : e.deltaMode === 2 + ? canvas.clientHeight + : 1), + -300, + 300, + ); + if (now - wheelTime > 180 || Math.sign(delta) !== Math.sign(wheelTotal)) + wheelTotal = 0; + wheelTime = now; + wheelTotal += delta; + const steps = Math.min(3, Math.floor(Math.abs(wheelTotal) / 100)); + if (!steps) return; + const direction = Math.sign(wheelTotal); + wheelTotal -= direction * steps * 100; + this.navigatingDrag = true; + try { + for (let i = 0; i < steps; i++) this.onNavigate?.("row", direction); + } finally { + this.navigatingDrag = false; + } + }, + { ...{ passive: false }, signal: this.inputEvents.signal }, + ); + window.addEventListener("blur", () => this.cancelPointer(), { signal: this.inputEvents.signal }); + document.addEventListener("visibilitychange", () => { + if (document.hidden) this.cancelPointer(); + }, { signal: this.inputEvents.signal }); + window.addEventListener("resize", () => this.cancelPointer(), { signal: this.inputEvents.signal }); + // After multi-touch cancels capture, a finger can finish outside the canvas. + window.addEventListener("pointerup", (e) => pointers.delete(e.pointerId), { signal: this.inputEvents.signal }); + window.addEventListener("pointercancel", (e) => + pointers.delete(e.pointerId), { signal: this.inputEvents.signal } + ); + } + update( + time: number, + cinematic?: { reveal: number; lift: number; zoom: number; time: number }, + ) { + const elapsed = Math.max(0, time - this.last || 0.016); + const dt = Math.min(elapsed, 0.05); + this.last = time; + this.clock = time; + if (!this.loaded) return; + const step = this.reduced ? 1 : Math.min(elapsed, .25) / 1.1; + this.presence += Math.sign(this.presenceTarget - this.presence) * Math.min(step, Math.abs(this.presenceTarget - this.presence)); + this.renderer.domElement.style.opacity = String(THREE.MathUtils.clamp(this.presence / .16, 0, 1)); + this.theme.beginFrame(); + themeEnvironment(this.scene, this.renderer, this.themeAmount); + const blend = 1 - Math.exp(-dt * (this.reduced ? 35 : 2.8)); + this.reveal = cinematic + ? cinematic.reveal + : THREE.MathUtils.lerp(this.reveal, this.targetReveal, blend); + this.rotation = this.targetDetail + ? THREE.MathUtils.lerp(this.rotation, this.targetRotation, blend) + : returnStep(this.rotation, dt, this.reduced); + const shot = cinematic?.time ?? 29.1; + if (cinematic) { + this.scanTime = shot; + this.scanBlend = 1; + } else { + this.scanTime += dt; + this.scanBlend *= Math.exp(-dt * 3); + } + if (!this.canBrowse()) { + this.setHover(null); + if (this.holdingArchive || this.archiveMomentum) this.cancelPointer(); + } + if (this.looping && !cinematic && !this.holdingArchive && !this.archiveMomentum) this.rebaseCoordinates(); + const momentum = !cinematic ? this.archiveMomentum : null; + if (momentum) { + momentum.motion.step(Math.min(Math.max(time - momentum.time, 0), 0.25)); + momentum.time = time; + this.navigatePlane(momentum.motion.value); + this.lastInteraction = time; + } + const hoverKey = !cinematic && this.hoverCell ? cellKey(this.hoverCell) : null; + if (hoverKey && !this.hoverLifts.has(hoverKey)) this.hoverLifts.set(hoverKey, 0); + for (const [key, value] of this.hoverLifts) { + const target = key === hoverKey ? 0.28 : 0; + const next = cinematic ? 0 : this.reduced ? target : THREE.MathUtils.lerp(value, target, 1 - Math.exp(-dt * 14)); + if (target === 0 && next < 0.0001) this.hoverLifts.delete(key); + else this.hoverLifts.set(key, next); + } + const hoverLift = (cell: ArchiveCell) => this.hoverLifts.get(cellKey(cell)) ?? 0; + const chosen = this.cellPosition(this.selectedCell); + const selectedRow = this.selectedCell.row; + const selectedLane = this.selectedCell.lane; + damp(this.shoulder, selectedRow, this.reduced ? 35 : 5, dt); + damp(this.laneFocus, selectedLane, this.reduced ? 35 : 4, dt); + // A held or freely coasting plane owns both tracks; selection cannot pull it. + if (!this.holdingArchive && !momentum) { + damp(this.columnCamera, chosen.x, this.reduced ? 35 : 3.7, dt); + damp(this.rail, cinematic ? 0 : -2.17 - chosen.z, this.reduced ? 35 : 3.7, dt); + } + if (momentum) { + this.columnCamera.value = this.trackPosition("lane", momentum.motion.lane.value); + this.rail.value = this.trackPosition("row", momentum.motion.row.value); + this.columnCamera.velocity = momentum.motion.lane.velocity * COLUMN_SPACING; + this.rail.velocity = -momentum.motion.row.velocity * ROW_SPACING; + if (momentum.motion.phase === "idle") this.archiveMomentum = null; + } + if (this.dragTrack && !cinematic) { + this.columnCamera.value = this.dragTrack.lane; + this.rail.value = this.dragTrack.row; + this.columnCamera.velocity = this.rail.velocity = 0; + } + if (cinematic) { + this.rail.value = 0; + this.rail.velocity = 0; + this.lift.value = extraction(shot); + this.lift.velocity = 0; + this.shoulder.value = selectedRow; + this.laneFocus.value = selectedLane; + this.laneFocus.velocity = 0; + this.columnCamera.value = chosen.x; + this.columnCamera.velocity = 0; + } + // Keep the illuminated set near the origin. Lateral navigation is a track + // movement of the whole array, just like the existing front/back rail. + const trackX = cinematic ? 0 : this.columnCamera.value; + this.pulses = this.pulses.filter((p) => time - p.time < 3.2); + const aligningCopy = this.outgoing.some((o) => o.returnY !== null); + const idle = + !cinematic && + !this.reduced && + this.targetReveal > 0 && + !this.targetDetail && + this.detail < 0.01 && + this.returnY === null && + !aligningCopy && + time - this.lastInteraction > 2.5; + this.idleGain = cinematic + ? 0 + : THREE.MathUtils.lerp( + this.idleGain, + idle ? (this.playfield.enabled ? (this.playfield.breathing && !this.relayActive ? 1 - this.playfield.bands.activity : 0) : 1) : 0, + 1 - Math.exp(-dt * (idle ? 0.8 : 4)), + ); + this.pulseGain = THREE.MathUtils.lerp( + this.pulseGain, + this.targetDetail || this.returnY !== null || aligningCopy ? 0 : 1, + 1 - Math.exp(-dt * 8), + ); + const play = this.playfield; + const activePlay = !cinematic && !this.targetDetail && play.enabled; + const rhythm = this.rhythm.update(activePlay && !this.reduced ? play.bands : quietBands(), time, dt, this.rhythmStyle); + this.flatMix += ((activePlay ? play.flatten : 0) - this.flatMix) * (this.reduced ? 1 : 1 - Math.exp(-dt * 4)); + this.subduedIndex.value = Math.max(Number(this.selectedIndexOnly), this.flatMix); + const indexDim = (lift: number) => this.selectedIndexOnly + ? 1 - ease(lift / .4) * (1 - this.flatMix) + : this.flatMix; + const gameTarget = activePlay ? play.target : null; + if (gameTarget && !this.relayLifts.has(gameTarget)) this.relayLifts.set(gameTarget, 0); + for (const [key, height] of this.relayLifts) { + const next = height + ((key === gameTarget ? .95 : 0) - height) * (this.reduced ? 1 : 1 - Math.exp(-dt * 8)); + if (next < .001 && key !== gameTarget) this.relayLifts.delete(key); else this.relayLifts.set(key, next); + } + const spectrumPoint = new THREE.Vector3(); + const screenX = (row: number, lane: number) => { + spectrumPoint.set((lane - 2) * COLUMN_SPACING - trackX, -4.6, (row - 15.5) * ROW_SPACING + this.rail.value).project(this.camera); + return (spectrumPoint.x + 1) / 2; + }; + const field = (row: number, lane: number) => { + if (cinematic) + return cinematicField( + row, + lane, + shot, + this.shoulder.value, + this.laneFocus.value, + ); + const height = + archiveWave( + row + this.coordinateOrigin.row, + lane + this.coordinateOrigin.lane, + this.scanTime, + ) * + this.scanBlend; + const breathing = idleWave( + row + this.coordinateOrigin.row, + lane + this.coordinateOrigin.lane, + time, + ) * + this.idleGain; + let pulseHeight = 0; + if (!cinematic && !this.reduced) { + let ripple = 0; + for (const p of this.pulses) { + const distance = Math.hypot(row - p.row, (lane - p.lane) * 2.2); + const age = time - p.time; + ripple += + this.selectionPulse(distance, age) * + (this.deferSelectionPulse ? rippleEnvelope(distance, age) : 1); + } + pulseHeight = THREE.MathUtils.clamp(ripple, -0.6, 0.6) * this.pulseGain; + } + const distance = row - this.shoulder.value; + return ( + (height + + settlingWave(distance, 26.56) * + columnStrength(lane, this.laneFocus.value)) * (1 - this.flatMix) + breathing + pulseHeight + + (activePlay && !this.reduced ? rhythmDisplacement(row, lane, time, play.bands, play.strength, rhythm, screenX(row, lane)) : 0) + + (this.relayLifts.get(cellKey({ row, lane })) ?? 0) + ); + }; + const selectedBase = chosen.y + field(selectedRow, selectedLane); + if (!cinematic) { + if (this.returnY !== null && this.rotation !== 0) { + this.lift.value = this.returnY - selectedBase; + this.lift.velocity = 0; + } else { + this.returnY = null; + damp( + this.lift, + this.targetDetail + ? INSPECTION_LIFT + : this.outgoing.some( + (o) => + o.returnY !== null && + o.cell.lane === selectedLane && + Math.abs(o.cell.row - selectedRow) < 5, + ) + ? 0 + : 0.4 * this.targetReveal * (1 - this.flatMix), + this.reduced + ? 35 + : this.deferSelectionPulse && + !this.targetDetail && + this.lift.value < 0.4 + ? 7.6 + : 4.2, + dt, + ); + } + } + const cameraTarget = this.targetDetail + ? ease((this.lift.value - 0.8) / 2.4) + : this.returnY !== null + ? this.detail + : ease((this.lift.value - 0.4) / (INSPECTION_LIFT - 0.4)); + this.detail = cinematic + ? cinematic.zoom + : THREE.MathUtils.lerp(this.detail, cameraTarget, blend); + const detail = this.detail; + this.decryption.update(dt, detail > .78 && this.lift.value > 3.3, this.reduced, + cinematic ? shot + 5 : undefined); + this.appearance.apply(this.model, ease(this.lift.value / 0.4)); + this.appearance.setClarity(this.model, this.decryption.clarity); + // Reference 26.92–27.76: the array travels horizontally into a white field. + const entry = cinematic ? ease((shot - 21.9) / 0.86) : this.reveal; + const entranceTime = THREE.MathUtils.clamp((shot - 21.92) / 0.75, 0, 1); + const entryZ = cinematic + ? -23 * (1 - entranceTime) ** 2 + : -28 * (1 - entry); + for (let i = this.outgoing.length - 1; i >= 0; i--) { + const o = this.outgoing[i]; + const p = this.cellPosition(o.cell); + const baseY = p.y + field(o.cell.row, o.cell.lane); + o.group.rotation.y = returnStep(o.group.rotation.y, dt, this.reduced); + if (o.returnY !== null) { + o.lift.value = o.returnY - baseY; + o.lift.velocity = 0; + if (o.group.rotation.y === 0) o.returnY = null; + } else damp(o.lift, 0, this.reduced ? 35 : 4.5, dt); + o.group.position.set( + p.x - trackX, + baseY + o.lift.value + hoverLift(o.cell) - this.presentationDrop(o.cell), + p.z + entryZ + this.rail.value, + ); + const quality = ease(o.lift.value / 0.4); + this.appearance.apply(o.group, quality); + this.appearance.setTheme(o.group, this.theme.sample(o.cell, time), indexDim(o.lift.value)); + o.clarity = this.reduced ? 0 : o.clarity * Math.exp(-dt * 9); + this.appearance.setClarity(o.group, o.clarity); + const { row, lane } = o.cell; + o.group.rotation.x = + (field(row + 0.5, lane) - field(row - 0.5, lane)) * + 0.024 * + (1 - detail) * + (1 - quality); + if (o.lift.value < 0.0001 && Math.abs(o.group.rotation.y) < 0.0001) { + this.scene.remove(o.group); + this.appearance.dispose(o.group); + this.outgoing.splice(i, 1); + } + } + if ( + this.pendingPulse && + !cinematic && + !this.targetDetail && + this.targetReveal + ) { + const selectedY = selectedBase + this.lift.value; + const oldCardsLower = this.outgoing.every( + (old) => + old.cell.lane !== selectedLane || + Math.abs(old.cell.row - selectedRow) > 4 || + old.group.position.y + 0.015 < selectedY, + ); + // The new file causes the wave: finish most of its rise and let nearby + // outgoing files get below it before starting the outward pulse. + if (this.lift.value >= 0.35 && this.returnY === null && oldCardsLower) { + if (!this.reduced) this.emitPulse(this.pendingPulse); + this.pendingPulse = null; + } + } + this.appearance.setTheme(this.model, this.theme.sample(this.selectedCell, time), indexDim(this.lift.value)); + this.model.position.set( + chosen.x - trackX, + chosen.y + field(selectedRow, selectedLane) + this.lift.value + hoverLift(this.selectedCell) - this.presentationDrop(this.selectedCell), + chosen.z + entryZ + this.rail.value, + ); + // Extraction only changes elevation. Reframing belongs to the camera. + this.model.rotation.set( + (field(selectedRow + 0.5, selectedLane) - + field(selectedRow - 0.5, selectedLane)) * + 0.024 * + (1 - detail) * + (1 - ease(this.lift.value / 0.4)), + cinematic ? 0 : this.rotation, + 0, + ); + // Measured from frame 787: X edge (382,-204), adjacent row (78,38). + // The label vertical edge constrains height; the file base is occluded. + // Do not calibrate field of view from the visible fragment of a file. + const orbit = ease((shot - 22.6) / 1.6); + const settle = ease((shot - 24.25) / 2.25); + const yaw = THREE.MathUtils.degToRad(89 - 22 * orbit - 8 * settle); + const elevation = THREE.MathUtils.degToRad( + 3 + 40 * ease((shot - 21.96) / 0.22) - 8 * orbit - 16 * settle, + ); + const span = THREE.MathUtils.lerp( + THREE.MathUtils.lerp(10.8, 10.3, orbit), + 7.33, + settle, + ); + const responsiveOpening = Boolean(cinematic) && this.container.closest("[data-layout]")?.dataset.layout === "opening"; + const openingAspect = responsiveOpening ? this.container.clientWidth / this.container.clientHeight / (16 / 9) : 1; + const openingSpan = (value: number) => value / Math.min(1, openingAspect); + const distance = THREE.MathUtils.lerp( + THREE.MathUtils.lerp(28 + 7 * orbit, 140, settle), + 72, + detail, + ); + const arrayAim = new THREE.Vector3( + -1.091, + THREE.MathUtils.lerp(-2.55 + 0.4 * orbit, -0.045, settle), + THREE.MathUtils.lerp(2.48, 0.481, settle), + ); + const cameraAim = arrayAim.clone(); + const viewDirection = new THREE.Vector3( + -Math.sin(yaw) * Math.cos(elevation), + Math.sin(elevation), + Math.cos(yaw) * Math.cos(elevation), + ); + if (cinematic) { + const earlyTurn = ease((shot - 27.3) / 1.3); + const finalTurn = ease((shot - 28.6) / 5.4); + const shotYaw = + yaw - THREE.MathUtils.degToRad(9 * earlyTurn + 32 * finalTurn); + const shotElevation = + elevation - THREE.MathUtils.degToRad(1.5 * earlyTurn + 3.7 * finalTurn); + viewDirection.set( + -Math.sin(shotYaw) * Math.cos(shotElevation), + Math.sin(shotElevation), + Math.cos(shotYaw) * Math.cos(shotElevation), + ); + } else { + viewDirection + .lerp(new THREE.Vector3(-0.277, 0.238, 0.931), detail) + .normalize(); + } + if (cinematic) { + const pan = ease((shot - 25.4) / 0.95); + const right = new THREE.Vector3() + .crossVectors(new THREE.Vector3(0, 1, 0), viewDirection) + .normalize(); + cameraAim.addScaledVector( + right, + -2.05 * (1 - pan) * ease((shot - 24.2) / 0.8), + ); + } + if (cinematic && shot >= 25.05 && shot <= 27.3) { + // Frames 760–785: the camera carries the same physical column from the + // right into the selected position while the neighboring crests subside. + const pan = ease((shot - 25.4) / 1.05); + const right = new THREE.Vector3() + .crossVectors(new THREE.Vector3(0, 1, 0), viewDirection) + .normalize(); + const up = new THREE.Vector3() + .crossVectors(viewDirection, right) + .normalize(); + const pixelScale = 1080 / openingSpan(span); + const anchorAim = this.model.position + .clone() + .add(new THREE.Vector3(-2.5, 3.7, 0)); + anchorAim.addScaledVector( + right, + -(THREE.MathUtils.lerp(840, 518, pan) - 960) * openingAspect / pixelScale, + ); + anchorAim.addScaledVector( + up, + -(540 - THREE.MathUtils.lerp(340, 288, pan)) / pixelScale, + ); + cameraAim.lerp(anchorAim, ease((shot - 25.05) / 0.35)); + } + if (cinematic && shot > 27.3) { + const close = ease((shot - 27.3) / 6.7); + const extractionCamera = ease((shot - 27.3) / 1.25); + const screenX = THREE.MathUtils.lerp( + 518 - 98 * extractionCamera, + 618, + close, + ); + const screenY = THREE.MathUtils.lerp( + 296 + 34 * extractionCamera, + 287, + close, + ); + const pixelScale = 1080 / openingSpan(THREE.MathUtils.lerp(span, 5.9, detail)); + const right = new THREE.Vector3() + .crossVectors(new THREE.Vector3(0, 1, 0), viewDirection) + .normalize(); + const up = new THREE.Vector3() + .crossVectors(viewDirection, right) + .normalize(); + const anchorAim = this.model.position + .clone() + .add(new THREE.Vector3(-2.5, 3.7, 0)); + anchorAim.addScaledVector(right, -(screenX - 960) * openingAspect / pixelScale); + anchorAim.addScaledVector(up, -(540 - screenY) / pixelScale); + cameraAim.lerp(anchorAim, ease((shot - 27.3) / 0.5)); + } + const framing = archiveFraming(this.container.clientWidth, this.container.clientHeight, span, detail, + this.container.closest("[data-layout]")?.dataset.layout === "compact"); + if (!cinematic) { + const right = new THREE.Vector3() + .crossVectors(new THREE.Vector3(0, 1, 0), viewDirection) + .normalize(); + const up = new THREE.Vector3() + .crossVectors(viewDirection, right) + .normalize(); + const width = this.container.clientWidth, height = this.container.clientHeight; + const pixelScale = height / framing.span; + if (framing.portrait) { + // Keep the preview camera independent of the live lift, wave and rail. + // Following model.position here would visually cancel those motions. + const previewAim = new THREE.Vector3(0, -4.6 + settlingWave(0, 26.56) + 0.4 + 1.85, -2.17); + previewAim.addScaledVector(up, (framing.previewY - 0.5) * height / pixelScale); + cameraAim.copy(previewAim); + } + const detailAim = this.model.position + .clone() + .add(new THREE.Vector3(0, 1.85, 0)); + detailAim.addScaledVector(right, (0.5 - framing.detailX) * width / pixelScale); + detailAim.addScaledVector(up, (framing.detailY - 0.5) * height / pixelScale); + cameraAim.lerp(detailAim, detail); + } + const cameraPosition = cameraAim + .clone() + .addScaledVector(viewDirection, distance); + if (!cinematic && !this.reduced && !this.uiOnlyParallax) { + cameraPosition.x += this.pointer.x * 0.12; + cameraPosition.y -= this.pointer.y * 0.12; + } + const cameraBlend = cinematic ? 1 : 1 - Math.exp(-dt * 5); + this.camera.position.lerp(cameraPosition, cameraBlend); + this.cameraAim.lerp(cameraAim, cameraBlend); + this.camera.lookAt(this.cameraAim); + this.camera.fov = THREE.MathUtils.lerp( + this.camera.fov, + THREE.MathUtils.radToDeg( + 2 * Math.atan((cinematic ? openingSpan(THREE.MathUtils.lerp(span, 5.9, detail)) : framing.span) / (2 * distance)), + ), + cameraBlend, + ); + const fog = this.scene.fog as THREE.Fog; + // The camera position is damped after its target distance changes. Anchor + // fog to the rendered camera, or entry puts the array behind the far plane + // until the camera catches up (a brief white wash that exit never showed). + const renderedDistance = this.camera.position.distanceTo(this.cameraAim); + const fogTheme = this.themeAmount; + fog.near = renderedDistance + THREE.MathUtils.lerp(5 - 4 * fogTheme, -1, detail); + fog.far = renderedDistance + THREE.MathUtils.lerp(25 - 9 * fogTheme, 12, detail); + + this.camera.updateProjectionMatrix(); + this.camera.updateMatrixWorld(); + // Build and compact the instance set only after the actual damped camera + // is final for this frame. Picking uses the same packed index-to-cell map. + const fixed = (Boolean(cinematic) || !this.looping) && !responsiveOpening; + this.cells = fixed ? Array.from({ length: 160 }, (_, i) => poolCell(i)) + : this.visibility.update(this.camera, fog.far, trackX, entryZ + this.rail.value, this.extraCoverage); + const hidden = new Set(this.outgoing.map(o => cellKey(o.cell))); + hidden.add(cellKey(this.selectedCell)); + this.drawnCells = []; + this.relayPoints.clear(); + for (const cell of this.cells) { + const { row, lane } = cell; + if (hidden.has(cellKey(cell))) continue; + const x = (lane - 2) * COLUMN_SPACING - trackX; + const y = -4.6 + field(row, lane) + hoverLift(cell) - this.presentationDrop(cell); + const z = (row - 15.5) * ROW_SPACING + entryZ + this.rail.value; + if (!fixed && !this.visibility.intersects(x, y, z)) continue; + const i = this.drawnCells.length; + this.ensureInstanceCapacity(i + 1); + this.drawnCells.push(cell); + this.themeAttribute?.setX(i, this.theme.sample(cell, time)); + const slope = field(row + .5, lane) - field(row - .5, lane); + this.dummy.position.set(x, y, z); + this.dummy.rotation.set(slope * .024 * (1 - detail), 0, 0); + this.dummy.scale.setScalar(1); + this.dummy.updateMatrix(); + if (play.enabled) this.relayPoints.set(cellKey(cell), { cell: { ...cell }, point: new THREE.Vector3(0, 3.5, 0).applyMatrix4(this.dummy.matrix) }); + for (const inst of this.instances) inst.setMatrixAt(i, this.dummy.matrix); + } + for (const inst of this.instances) { + inst.count = this.drawnCells.length; + inst.instanceMatrix.needsUpdate = true; + } + // Picking uses only the first instanced surface. The other batches disable + // renderer culling and do not need an O(n) bound recomputation each frame. + this.instances[0]?.computeBoundingSphere(); + if (this.themeAttribute) this.themeAttribute.needsUpdate = true; + let neighborTop = -Infinity; + const lane = selectedLane, + row = selectedRow; + for (let r = row - 5; r <= row + 5; r++) { + if (r !== row) + neighborTop = Math.max(neighborTop, -4.6 + field(r, lane) + 3.76); + } + for (const o of this.outgoing) { + if (o.cell.lane === lane && Math.abs(o.cell.row - row) <= 5) { + neighborTop = Math.max(neighborTop, o.group.position.y + 3.76); + } + } + this.clearance = this.model.position.y - neighborTop; + this.canInspect = + !cinematic && + Boolean(this.targetDetail) && + detail > 0.9 && + this.pulseGain < 0.01 && + this.clearance > 0.3; + this.container.dataset.inspection = + this.returnY !== null + ? "aligning" + : this.canInspect + ? "ready" + : this.targetDetail + ? "lifting" + : "preview"; + const focalPoint = this.model.position + .clone() + .add(new THREE.Vector3(0, 2, 0)) + .applyMatrix4(this.camera.matrixWorldInverse); + const bokehUniforms = this.bokeh.uniforms as Record< + string, + { value: number } + >; + bokehUniforms.focus.value = -focalPoint.z; + bokehUniforms.aperture.value = + (THREE.MathUtils.lerp(0.0003, 0.0008, detail) * + this.quality.depthOfField) / + 100; + this.renderer.info.reset(); + if (this.superPerformance) this.renderer.render(this.scene, this.camera); + else this.composer.render(); + } + projectCard(x: number, y: number) { + this.model.updateMatrixWorld(true); + const p = this.model + .localToWorld(new THREE.Vector3(x, y, 0.255)) + .project(this.camera); + return [(p.x + 1) * this.container.clientWidth / 2, (1 - p.y) * this.container.clientHeight / 2]; + } + get decryptionFrame() { return this.decryption.frame; } + finishDecryption() { this.decryption.finish(); } + get detailVisibility() { + return ease((this.detail - 0.25) / 0.55); + } + getStats() { + this.model.updateMatrixWorld(true); + const project = (x: number, y: number, z: number) => { + const p = this.model + .localToWorld(new THREE.Vector3(x, y, z)) + .project(this.camera); + return [Math.round((p.x + 1) * this.container.clientWidth / 2), Math.round((1 - p.y) * this.container.clientHeight / 2)]; + }; + return { + decryption: { ...this.decryption.frame, clarity: this.decryption.clarity }, + topLeft: project(-2.5, 3.7, 0), + topRight: project(2.5, 3.7, 0), + labelTopLeft: project(-1.855, 3.27, 0.255), + labelBottomLeft: project(-1.855, 2.81, 0.255), + modelPosition: this.model.position + .toArray() + .map((v) => Math.round(v * 10000) / 10000), + cameraPosition: this.camera.position + .toArray() + .map((v) => Math.round(v * 10000) / 10000), + fieldOfView: this.camera.fov, + loaded: this.loaded, + drawCalls: this.renderer.info.render.calls, + superPerformance: this.superPerformance, + presentation: this.presence, + triangles: this.renderer.info.render.triangles, + archiveCount: this.drawnCells.length, + archiveCandidates: this.cells.length, + archiveCulled: this.cells.length - this.drawnCells.length, + archiveCapacity: this.instanceCapacity, + archiveCoverage: this.extraCoverage ? "extra" : "standard", + returningFiles: this.outgoing.length, + selectionPhase: this.pendingPulse + ? "lifting" + : this.pulses.length + ? "wave" + : "settled", + pendingPulse: this.pendingPulse ? { ...this.pendingPulse } : null, + pulses: this.pulses.map((pulse) => ({ ...pulse })), + referenceTime: Math.round((this.scanTime + 5) * 100) / 100, + selectedSlot: this.selectedSlot, + selectedLane: Math.floor(this.selectedSlot / 32), + selectedCell: { ...this.selectedCell }, + hoverCell: this.hoverCell ? { ...this.hoverCell } : null, + hoverLifts: Object.fromEntries(this.hoverLifts), + dragTrack: this.dragTrack ? { ...this.dragTrack } : null, + archiveMomentum: this.archiveMomentum ? { + phase: this.archiveMomentum.motion.phase, + value: this.archiveMomentum.motion.value, + velocity: this.archiveMomentum.motion.velocity, + } : null, + holdingArchive: this.holdingArchive, + dragProjection: this.dragProjection(), + dragMapping: this.archiveDrag.active ? "free" : null, + coordinateOrigin: { ...this.coordinateOrigin }, + poolBounds: { + minLane: Math.min(...this.cells.map((c) => c.lane)), + maxLane: Math.max(...this.cells.map((c) => c.lane)), + minRow: Math.min(...this.cells.map((c) => c.row)), + maxRow: Math.max(...this.cells.map((c) => c.row)), + }, + laneFocus: this.laneFocus.value, + columnCamera: this.columnCamera.value, + rotation: this.rotation, + clearance: this.clearance, + canInspect: this.canInspect, + returnPhase: this.returnY !== null ? "aligning" : "lowering", + extraction: Math.round(this.lift.value * 1000) / 1000, + appearance: Math.round(ease(this.lift.value / 0.4) * 1000) / 1000, + cameraDetail: Math.round(this.detail * 1000) / 1000, + idleGain: this.idleGain, + flatten: this.flatMix, + spectrumActivity: this.playfield.bands.activity, + selectedIndexDim: this.model.children.find(child => child.userData.surface === "Index_Inlay")?.userData.subduedIndex?.value, + returningIndexDims: this.outgoing.map(o => ({ cell: o.cell, dim: o.group.children.find(child => child.userData.surface === "Index_Inlay")?.userData.subduedIndex?.value })), + cameraDistance: this.camera.position.distanceTo(this.cameraAim), + cameraNear: this.camera.near, + cameraFar: this.camera.far, + fogNear: (this.scene.fog as THREE.Fog).near, + fogFar: (this.scene.fog as THREE.Fog).far, + returningAppearance: this.outgoing.map((o) => ({ + slot: o.slot, + cell: { ...o.cell }, + lift: o.lift.value, + quality: ease(o.lift.value / 0.4), + rotation: o.group.rotation.y, + worldY: o.group.position.y, + phase: o.returnY !== null ? "aligning" : "lowering", + })), + rail: Math.round(this.rail.value * 1000) / 1000, + }; + } +} diff --git a/src/screen-finish.ts b/src/screen-finish.ts new file mode 100644 index 0000000..c075d5b --- /dev/null +++ b/src/screen-finish.ts @@ -0,0 +1,88 @@ +/** SourceGraphic includes WebGL, DOM controls, frosted surfaces and the viewer. */ +export class ScreenFinish { + private svg: SVGSVGElement; + private filter: SVGFilterElement; + private width = 0; + private height = 0; + private signature = ""; + private seed = -1; + private mapCache?: { displacement: string; vignette: string }; + constructor(private stage: HTMLElement) { + this.svg = document.createElementNS("http://www.w3.org/2000/svg", "svg"); + this.svg.classList.add("screen-finish-defs"); + this.svg.setAttribute("aria-hidden", "true"); + this.svg.innerHTML = ``; + document.body.append(this.svg); + this.filter = this.svg.querySelector("filter")!; + } + private maps(width: number, height: number) { + const canvas = document.createElement("canvas"); + canvas.width = 384; + canvas.height = Math.max(64, Math.round(384 * height / width)); + const ctx = canvas.getContext("2d")!; + const map = ctx.createImageData(canvas.width, canvas.height); + const vignette = ctx.createImageData(canvas.width, canvas.height); + for (let y = 0; y < canvas.height; y++) for (let x = 0; x < canvas.width; x++) { + const u = x / (canvas.width - 1), v = y / (canvas.height - 1), i = (y * canvas.width + x) * 4; + const edge = Math.min(1, Math.min(u, v, 1 - u, 1 - v) / .02); + const fade = edge * edge * (3 - 2 * edge); + map.data[i] = 128 + (u - .5) * 254 * fade; + map.data[i + 1] = 128 + (v - .5) * 254 * height / width * fade; + map.data[i + 2] = 128; map.data[i + 3] = 255; + const radius = Math.hypot((u - .5) * 2, (v - .5) * 2) / Math.SQRT2; + const shade = Math.round(255 * (1 - .85 * Math.pow(radius, 1.6))); + vignette.data[i] = vignette.data[i + 1] = vignette.data[i + 2] = shade; + vignette.data[i + 3] = 255; + } + ctx.putImageData(map, 0, 0); const displacement = canvas.toDataURL(); + ctx.putImageData(vignette, 0, 0); return { displacement, vignette: canvas.toDataURL() }; + } + update(enabled: boolean, grain: number, fringe: number, vignette: number, size: number, time: number, reduced: boolean) { + const active = enabled && (grain > 0 || fringe > 0 || vignette > 0); + this.stage.dataset.screenFinish = String(active); + if (!active) { this.stage.style.removeProperty("filter"); return; } + const width = this.stage.offsetWidth, height = this.stage.offsetHeight; + const signature = `${grain}/${fringe}/${vignette}/${size}/${width}/${height}`; + if (signature !== this.signature) { + this.signature = signature; + const resized = width !== this.width || height !== this.height; + this.width = width; this.height = height; + this.filter.setAttribute("x", "0"); this.filter.setAttribute("y", "0"); + this.filter.setAttribute("width", String(width)); this.filter.setAttribute("height", String(height)); + const maps = (resized || !this.mapCache) ? this.maps(width, height) : this.mapCache; + this.mapCache = maps; + let input = "SourceGraphic", markup = ""; + const extent = `x="0" y="0" width="${width}" height="${height}" preserveAspectRatio="none"`; + if (fringe > 0) { + const offset = fringe * 20 * width / 1920; + markup += ` + + + + + + + + `; + input = "chroma"; + } + if (grain > 0) { + markup += ` + + + `; + input = "textured"; + } + if (vignette > 0) markup += ` + `; + this.filter.innerHTML = markup; + this.seed = -1; + } + this.stage.style.filter = "url(#rhine-screen-finish)"; + const seed = reduced ? 0 : Math.floor(time * 12) % 97; + if (grain > 0 && seed !== this.seed) { + this.seed = seed; + this.filter.querySelector("feTurbulence")?.setAttribute("seed", String(seed)); + } + } +} diff --git a/src/startup.css b/src/startup.css new file mode 100644 index 0000000..3b38dfe --- /dev/null +++ b/src/startup.css @@ -0,0 +1,12 @@ +#viewport > .loading { position: fixed; } +.entry-controls { display: flex; align-items: center; flex-direction: column; margin-top: 32px; max-width: calc(100vw - 40px); text-align: center; } +.entry-start { min-height: 48px; padding: 8px 24px; border-block: 1px solid #969183; font-size: 16px; letter-spacing: 2px; } +.entry-start:disabled, .entry-start[aria-disabled="true"] { color: #888277; cursor: wait; } +.entry-start:focus-visible, .entry-silent:focus-visible { outline: 2px solid #555c45; outline-offset: 5px; } +.entry-silent { min-height: 44px; margin-top: 12px; font-size: 13px; color: #666454; text-decoration: underline; text-underline-offset: 4px; } +.entry-status { font-size: 12px; color: #777468; line-height: 1.8; margin: 14px 0 0; } +.loading[data-entry="waiting"] > i:after, .loading[data-entry="error"] > i:after { animation: none; width: 100%; } +@media (max-width: 600px) { + .loading-mark { width: 125px; margin-bottom: 32px; } + .loading > span { font-size: 9px; letter-spacing: 1.3px; } +} diff --git a/src/startup.ts b/src/startup.ts new file mode 100644 index 0000000..cc87ffb --- /dev/null +++ b/src/startup.ts @@ -0,0 +1,95 @@ +type EntryOptions = { + root: HTMLElement; + unlock: () => Promise; + cancel: () => void; + start: (silent: boolean) => void; +}; + +/** Owns the entry gesture, including keyboard focus and failed audio startup. */ +export class StartupGate { + private state: "loading" | "waiting" | "starting" | "error" | "started" = "loading"; + private request = 0; + private button: HTMLButtonElement; + private silent: HTMLButtonElement; + private status: HTMLElement; + constructor(private options: EntryOptions) { + const { root } = options; + root.setAttribute("role", "dialog"); + root.setAttribute("aria-modal", "true"); + root.setAttribute("aria-label", "进入莱茵生命档案终端"); + root.insertAdjacentHTML("beforeend", '

资源就绪后即可进入

'); + this.button = root.querySelector(".entry-start")!; + this.silent = root.querySelector(".entry-silent")!; + this.status = root.querySelector(".entry-status")!; + root.addEventListener("click", event => { + event.stopPropagation(); + if ((event.target as Element).closest(".entry-silent")) { + this.request++; + options.cancel(); + this.finish(true); + } else if (this.state === "waiting" || this.state === "error") void this.enter(); + }); + root.addEventListener("keydown", event => { + event.stopPropagation(); + if (event.key === "Tab") { + const buttons = [this.button, this.silent].filter(button => !button.disabled && !button.hidden); + if (!buttons.length) { event.preventDefault(); return; } + const index = buttons.indexOf(document.activeElement as HTMLButtonElement); + event.preventDefault(); + buttons[(index + (event.shiftKey ? buttons.length - 1 : 1)) % buttons.length].focus(); + } + // Let native buttons activate on Enter/Space, and never leak this event + // to the terminal's Enter-to-skip handler. + }); + } + get phase() { return this.state; } + ready() { + this.state = "waiting"; + this.options.root.dataset.entry = "waiting"; + this.button.disabled = false; + this.button.textContent = "点击进入 →"; + this.options.root.querySelector(":scope > span")!.textContent = "INTERNAL DATABASE / READY"; + this.status.textContent = "轻触屏幕或按 Enter 开始"; + this.button.focus({ preventScroll: true }); + } + private async enter() { + const request = ++this.request; + this.state = "starting"; + this.options.root.dataset.entry = "starting"; + // aria-disabled preserves keyboard focus while repeated input is ignored. + this.button.setAttribute("aria-disabled", "true"); + this.button.textContent = "正在准备声音…"; + this.status.textContent = "准备完成后开始播放"; + this.silent.hidden = false; + let timer: ReturnType | undefined; + try { + const unlocked = await Promise.race([ + this.options.unlock(), + new Promise(resolve => { timer = setTimeout(() => resolve(false), 20000); }), + ]); + if (request !== this.request) return; + if (unlocked && !document.hidden) this.finish(false); + else { + this.options.cancel(); + this.state = "error"; + this.options.root.dataset.entry = "error"; + this.button.removeAttribute("aria-disabled"); + this.button.textContent = "重试声音并进入 →"; + this.status.textContent = "声音暂未就绪,请重试或无声进入"; + } + } catch { + if (request !== this.request) return; + this.options.cancel(); + this.state = "error"; + this.options.root.dataset.entry = "error"; + this.button.removeAttribute("aria-disabled"); + this.button.textContent = "重试声音并进入 →"; + this.status.textContent = "声音暂未就绪,请重试或无声进入"; + } finally { clearTimeout(timer); } + } + private finish(silent: boolean) { + if (this.state === "started") return; + this.state = "started"; + this.options.start(silent); + } +} diff --git a/src/style.css b/src/style.css new file mode 100644 index 0000000..b8c381c --- /dev/null +++ b/src/style.css @@ -0,0 +1,2494 @@ +@import "./fonts.css"; +* { + box-sizing: border-box; +} +:root { + font-family: + "MiSans", "Mi Sans", "PingFang SC", "Microsoft YaHei", system-ui, sans-serif; + color: #080a08; + background: #e8e5e1; + font-synthesis: none; +} +body { + margin: 0; + overflow: hidden; +} +button, +input { + font: inherit; +} +button { + border: 0; + color: inherit; + background: none; + cursor: pointer; + padding: 0; +} +button:focus-visible, +input:focus-visible { + outline: 2px solid #a67d48; + outline-offset: 7px; +} +button:disabled { + cursor: default; +} +button, +input { + -webkit-tap-highlight-color: transparent; +} +button { + touch-action: manipulation; +} +::selection { + background: #cbb397; + color: #151713; +} +#viewport { + width: 100vw; + height: 100dvh; + position: relative; + overflow: hidden; + background: #e8e5e1; +} +#stage { + position: absolute; + left: 50%; + top: 50%; + width: 1920px; + height: 1080px; + overflow: hidden; + transform-origin: center; + isolation: isolate; + background: #e8e5e1; +} +.three-scene { + position: absolute; + inset: 0; + opacity: 0; + transition: opacity 0.45s; +} +.three-scene canvas { + display: block; + width: 100%; + height: 100%; +} +[data-review="true"] * { + transition: none !important; +} +[data-review="true"] .skip { + display: none; +} +.scene-atmosphere { + position: absolute; + inset: 0; + pointer-events: none; + background: + linear-gradient(180deg, rgba(234, 231, 226, 0.35), transparent 38%), + radial-gradient( + ellipse at 42% 47%, + transparent 32%, + rgba(232, 229, 225, 0.19) 85% + ); + opacity: 0; + transition: opacity 1.5s; +} +.brand { + position: absolute; + left: 59px; + top: 114px; + z-index: 5; + line-height: 1; + user-select: none; + width: 270px; + white-space: nowrap; +} +.brand h1 { + font-size: 44px; + letter-spacing: 2.324px; + line-height: 48px; + margin: 0; + font-weight: 750; +} +.brand > div { + font-size: 19px; + line-height: 23px; + letter-spacing: 0.587px; + font-weight: 600; +} +.brand p { + margin: 0; + font-size: 35px; + line-height: 40px; + letter-spacing: 0; + word-spacing: 0; + display: flex; + justify-content: space-between; +} +.brand p b { + font-weight: 750; + letter-spacing: 3px; +} +.brand-analysis { + position: relative; + display: block; + width: 189px; + height: 40px; +} +.brand-analysis > span { + position: absolute; + top: 0; + letter-spacing: 0; + font-kerning: none; +} +.powered { + position: absolute; + right: 59px; + bottom: 108px; + font-size: 18px; + z-index: 5; + display: flex; + align-items: center; + gap: 5px; + line-height: 24px; +} +.powered b { + font-weight: 750; +} +.powered i { + height: 6px; + width: 25px; + background: #090a08; + display: inline-block; + margin-left: 11px; +} +.boot-background { + position: absolute; + inset: 0; + background: radial-gradient( + ellipse at 51% 48%, + #e7e7e1 0%, + #e3e1dc 76%, + #e4dfdb 100% + ); + transition: none; + pointer-events: none; +} +.boot-background svg { + width: 100%; + height: 100%; + opacity: 0.15; + filter: blur(2px); +} +.boot { + position: absolute; + inset: 0; + pointer-events: none; + z-index: 3; +} +.boot-logo { + position: absolute; + width: 296px; + height: 177px; + left: 518px; + top: 462px; + transition: none; + opacity: 0; +} +.boot-logo svg { + width: 100%; + height: 100%; +} +.auth-status { + position: absolute; + left: 931px; + top: 529px; + font-size: 22px; + display: flex; + align-items: center; + gap: 12px; + white-space: nowrap; + opacity: 0; +} +.auth-status > span:first-child { + font-size: 14px; +} +.auth-status i { + width: 3px; + height: 22px; + background: #282b26; + animation: blink 0.8s steps(1) infinite; +} +.access-text { + position: absolute; + top: 532px; + left: 800px; + width: 420px; + text-align: left; + font-size: 18px; + opacity: 0; +} +.scan { + position: absolute; + left: 410px; + top: -10px; + width: 1100px; + height: 1100px; + opacity: 0; + transform: scale(0.82); + transition: + transform 1.6s cubic-bezier(0.16, 1, 0.3, 1), + opacity 0.6s; +} +.scan svg { + width: 100%; + height: 100%; +} +.scan > span { + position: absolute; + inset: 0; + display: flex; + align-items: center; + justify-content: center; + font-size: 25px; +} +.scan-track { + stroke: #f7f5f0; + stroke-width: 8; +} +.scan-ring { + stroke: #aaa9a0; + stroke-width: 8; + transform-origin: center; + stroke-dasharray: 2400 900; +} +.ring-one { + animation: orbit 8s linear infinite; +} +.ring-two { + stroke-width: 9; + stroke-dasharray: 590 500; + animation: orbit 4s linear infinite reverse; +} +.ring-three { + stroke: #f4f3eb; + stroke-width: 3; + stroke-dasharray: 600 200; + animation: orbit 16s linear infinite; +} +.scan-nodes { + fill: #e7c69b; + filter: drop-shadow(0 0 15px #ddb788); + transform-origin: center; + animation: orbit 8s linear infinite; +} +.welcome { + position: absolute; + top: 403px; + left: 0; + width: 100%; + text-align: center; + font-size: 55px; + font-weight: 700; + line-height: 1.05; + opacity: 0; + transform: translateY(14px); + transition: + opacity 0.7s, + transform 1s; +} +.welcome strong { + display: inline-block; + background: #080a08; + color: #ece9e4; + padding: 4px 10px 6px; + font-size: 51px; + line-height: 1; +} +.welcome-logo { + width: 203px; + height: 121px; + margin: 58px auto 0; +} +.welcome-logo svg { + width: 100%; + height: 100%; +} +/* 跳过开场:右上角一个不起眼的圆形按钮,不再是文字按钮 */ +.skip { + position: absolute; + right: 38px; + top: 30px; + z-index: 10; + width: 46px; + height: 46px; + border: 1px solid currentColor; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + opacity: 0.3; + transition: + opacity 0.25s, + transform 0.25s; +} +.skip-glyph { + font-size: 18px; + line-height: 1; + transform: translateY(-1px); +} +.skip:hover, +.skip:focus-visible { + opacity: 1; + transform: scale(1.06); +} + +/* 退出系统:与跳过按钮共用右上角那一个圆形位置,开场结束后取而代之。 + 开场中隐藏、可交互时出现,两者不会同时存在。 */ +.exit { + position: absolute; + right: 38px; + top: 30px; + z-index: 10; + width: 46px; + height: 46px; + border: 1px solid currentColor; + border-radius: 50%; + display: none; + align-items: center; + justify-content: center; + opacity: 0.3; + transition: + opacity 0.25s, + transform 0.25s; +} +[data-mode="archive"] .exit, +[data-mode="detail"] .exit { + display: flex; +} +.exit-glyph { + font-size: 15px; + line-height: 1; +} +.exit:hover, +.exit:focus-visible { + opacity: 1; + transform: scale(1.06); + color: #947444; +} +/* 正在退出时不接受再次点击,视觉交给系统屏。 */ +[data-mode="boot"] .exit, +[data-shutdown="true"] .exit { + display: none; +} + +/* 退出序列:白屏 + 中央标志,标志快闪两下后下方出现 SHUTDOWN,然后关窗。 + 时序全部交给动画 delay,JS 只负责显示与关窗(见 main.ts 的 exitSystem)。 */ +.shutdown { + position: absolute; + inset: 0; + z-index: 60; + background: #fff; + color: #080a08; + display: flex; + align-items: center; + justify-content: center; + flex-direction: column; + opacity: 0; + pointer-events: none; +} +.shutdown.visible { + opacity: 1; + pointer-events: auto; + animation: shutdownScreen 120ms ease-out both; +} +.shutdown[hidden] { + display: none; +} +@keyframes shutdownScreen { + from { + opacity: 0; + } + to { + opacity: 1; + } +} +.shutdown-mark { + width: 150px; + height: 90px; +} +.shutdown-mark svg { + width: 100%; + height: 100%; +} +/* 两下:两个凹陷,分别在 15% 与 45%,其余保持不透明。 */ +/* SHUTDOWN:完全照搬开场那行 RHINE LAB.LLC. 的排版与闪动做法—— + 两层叠放,底层深色字,上层深底浅字,靠上层的裁切从左扫到右。 + 字号 48 / 行高 50 / 字距 -0.5 / 内边距 5px 8px,与 .welcome-company strong 一致。 */ +.shutdown-word { + position: relative; + display: inline-block; + margin-top: 40px; + font-size: 48px; + line-height: 50px; + letter-spacing: -0.5px; + white-space: nowrap; + opacity: 0; +} +.shutdown.visible .shutdown-word { + animation: shutdownWord 120ms linear 120ms 1 forwards; +} +@keyframes shutdownWord { + to { + opacity: 1; + } +} +.shutdown-word > strong { + display: block; + padding: 5px 8px; + background: transparent; + color: #080a08; +} +/* 只有高亮层绝对定位、盖在底层之上。底层留在文档流里,容器才有宽度, + 才能在居中的 flex 列里真正水平居中——两层都 absolute 会让容器宽度塌成 0。 */ +.shutdown-word > .shutdown-highlight { + position: absolute; + inset: 0; + background: #080a08; + color: #eeede8; + clip-path: inset(0 100% 0 0); +} +/* 字符本体闪动:逐字明灭数次,白底透出来。只作用在底层文字上, + 高亮层不跟着淡,否则黑条会一起消失。 */ +.shutdown.visible .shutdown-word > strong:not(.shutdown-highlight) { + animation: shutdownFlicker 620ms linear 140ms 1 both; +} +@keyframes shutdownFlicker { + 0% { opacity: 1; } + 9% { opacity: 0.12; } + 18% { opacity: 1; } + 33% { opacity: 0.2; } + 42% { opacity: 1; } + 58% { opacity: 0.08; } + 67% { opacity: 1; } + 82% { opacity: 0.22; } + 91%, 100% { opacity: 1; } +} +/* 黑色滑条只扫一次,并放慢到能看清扫过的过程。 */ +.shutdown.visible .shutdown-highlight { + animation: shutdownSweep 640ms cubic-bezier(0.25, 0.6, 0.3, 1) 180ms 1 both; +} +@keyframes shutdownSweep { + 0% { + clip-path: inset(0 100% 0 0); + } + 100% { + clip-path: inset(0 0 0 0); + } +} +.brand, +.powered { + transition: opacity 0.6s; +} +.cinema-caption { + position: absolute; + bottom: 30px; + left: 0; + width: 100%; + font-size: 22px; + text-align: center; + color: #66645f; + opacity: 0.75; + pointer-events: none; +} +.system-nav { + position: absolute; + right: 59px; + top: 124px; + z-index: 6; + display: flex; + align-items: center; + gap: 43px; + opacity: 0; + pointer-events: none; + transition: opacity 0.8s; +} +.system-nav button { + font-size: 14px; + line-height: 1; + min-height: 28px; + letter-spacing: 0.7px; + display: flex; + align-items: center; + gap: 12px; +} +.system-nav button:hover { + color: #9b7247; +} +.nav-glyph { + width: 23px; + height: 23px; +} +.key { + font-size: 11px; + border: 1px solid #a6a49c; + display: inline-flex; + justify-content: center; + align-items: center; + min-width: 19px; + height: 21px; + flex-shrink: 0; + line-height: 1; + opacity: 0.6; + margin-left: 6px; +} +.settings-glyph { + font-size: 27px; +} +.archive-ui { + opacity: 0; + pointer-events: none; + transition: opacity 0.8s 0.3s; + position: absolute; + inset: 0; +} +.archive-callout { + position: absolute; + left: 970px; + top: 487px; + width: 950px; +} +.eyebrow { + font-size: 12px; + letter-spacing: 1.3px; + margin-bottom: 22px; + color: #77756d; + opacity: 0; + transition: opacity 0.7s; +} +.eyebrow > span { + margin-left: 16px; +} +.file-title { + font-size: 28px; + font-weight: 750; + display: flex; + gap: 10px; + align-items: center; + position: relative; + letter-spacing: 0.2px; +} +.file-open { + font-size: 25px; + margin-left: 48px; + opacity: 0; + transition: + opacity 0.3s, + transform 0.3s; +} +.file-title:hover .file-open { + opacity: 1; + transform: translate(3px, -3px); +} +.callout-rule { + height: 1px; + background: #504b41; + margin-left: 57px; + margin-top: 55px; + position: relative; + transform-origin: right; + transform: scaleX(0); + transition: transform 1.3s cubic-bezier(0.22, 1, 0.36, 1); +} +.callout-rule i { + position: absolute; + left: -61px; + top: -3px; + width: 6px; + height: 6px; + background: #282a24; + opacity: 0; +} +.file-summary { + margin-left: 58px; + margin-top: 22px; + width: 652px; + display: flex; + align-items: center; + justify-content: space-between; + font-size: 16px; + opacity: 0; + transform: translateY(8px); + transition: + opacity 0.7s 0.4s, + transform 0.7s 0.4s; +} +.file-summary > span:last-child { + font-size: 11px; + letter-spacing: 1px; + color: #666257; +} +.read-file { + display: flex; + align-items: center; + gap: 70px; + margin-left: 58px; + margin-top: 58px; + font-size: 14px; + letter-spacing: 1px; + opacity: 0; + transition: opacity 0.7s 0.4s; +} +.read-file > span { + font-size: 24px; + transition: transform 0.3s; +} +.read-file:hover > span { + transform: translateX(9px); +} +.archive-counter { + position: absolute; + bottom: 115px; + left: 60px; +} +.tiny-label { + font-size: 11px; + letter-spacing: 1.7px; +} +.archive-counter > div { + font-size: 58px; + line-height: 1.1; + margin-top: 16px; + display: flex; + gap: 21px; + align-items: baseline; +} +.archive-counter i { + font-style: normal; + font-weight: 300; + font-size: 32px; + color: #8f8a80; +} +.count-total { + font-size: 22px; + color: #726f67; +} +.archive-navigation { + position: absolute; + left: 504px; + bottom: 131px; + display: flex; + align-items: center; + gap: 33px; +} +.archive-navigation > button { + width: 46px; + height: 46px; + font-size: 28px; + transition: background 0.3s; +} +.archive-navigation > button:hover { + background: #e4d5c1; +} +.column-navigation { + position: absolute; + left: 1005px; + bottom: 131px; + display: none; + align-items: center; + gap: 24px; +} +[data-mode="archive"] .column-navigation { + display: flex; +} +.column-navigation button { + width: 46px; + height: 46px; + font-size: 24px; +} +.column-navigation button:hover { + background: #e4d5c1; +} +.column-navigation button:disabled { + opacity: 0.25; + cursor: default; +} +.column-navigation > div { + display: grid; + gap: 7px; + min-width: 140px; +} +.column-navigation > div > span { + font-size: 10px; + letter-spacing: 1.2px; + color: #726f67; +} +.column-navigation strong { + font-size: 15px; + font-weight: 400; +} +#selected-number, +#column-index { + min-width: 2ch; + font-variant-numeric: tabular-nums; + --rn-blur: 0.35; + --rn-edge-fade: 0.08em; +} +.file-ticks { + display: flex; + align-items: center; + gap: 12px; +} +.file-ticks button { + width: 14px; + height: 40px; + position: relative; +} +.file-ticks button:before { + content: ""; + position: absolute; + left: 6px; + top: 14px; + width: 2px; + height: 12px; + background: #aaa59a; + transition: + height 0.4s, + background 0.4s, + top 0.4s; +} +.file-ticks button.selected:before { + height: 33px; + top: 3px; + background: #24261f; +} +.file-ticks button:hover:before { + height: 24px; + top: 8px; + background: #a27849; +} +.archive-hint { + position: absolute; + bottom: 60px; + left: 505px; + color: #858077; + font-size: 10px; + letter-spacing: 0.9px; +} +.archive-hint > span { + margin: 0 12px; +} +.archive-hint kbd { + font: inherit; +} +.hover-label { + position: absolute; + left: 60px; + bottom: 264px; + font-size: 13px; + color: #6f675b; + letter-spacing: 1px; +} +.system-footer { + position: absolute; + bottom: 35px; + left: 59px; + right: 59px; + font-size: 10px; + letter-spacing: 0.9px; + display: flex; + gap: 44px; + color: #78776e; + z-index: 5; + opacity: 0; + pointer-events: none; + transition: opacity 0.8s; +} +.system-footer > span { + display: flex; + align-items: center; + gap: 10px; +} +.status-light { + width: 5px; + height: 5px; + background: #777b60; + display: inline-block; +} +.system-footer > span:nth-child(2) { + margin-left: auto; +} +.system-footer i { + font-style: normal; + margin: 0 5px; +} +.system-footer button { + font-size: 10px; + letter-spacing: 1px; +} +.system-footer button:hover { + color: #111; +} +.detail-ui { + position: absolute; + inset: 0; + pointer-events: none; + z-index: 4; +} +.detail-ui[hidden] { + display: none; +} +.back-button { + position: absolute; + left: 59px; + top: 289px; + pointer-events: auto; + display: flex; + align-items: center; + gap: 20px; + font-size: 24px; +} +.back-button span { + font-size: 12px; + letter-spacing: 1px; +} +.back-button small { + font-size: 10px; + margin-left: 18px; + color: #88877f; + border: 1px solid #b8b3a9; + padding: 4px; +} +.back-button:hover { + color: #946b3c; +} +.object-caption { + position: absolute; + left: 60px; + bottom: 235px; +} +.object-caption > span { + font-size: 37px; + letter-spacing: -1px; +} +.object-caption > div { + font-size: 10px; + letter-spacing: 1.8px; + margin-top: 7px; + color: #76736a; +} +.object-caption > small { + font-size: 10px; + letter-spacing: 1px; + display: block; + margin-top: 34px; + color: #817b70; +} +.object-caption > small span { + font-size: 18px; + margin-left: 18px; +} +.detail-content { + position: absolute; + left: 1154px; + top: 289px; + width: 636px; + pointer-events: auto; + opacity: 0; + outline: none !important; +} +.detail-kicker { + display: flex; + justify-content: space-between; + align-items: center; + font-size: 11px; + letter-spacing: 1.4px; +} +.detail-kicker span:last-child { + font-size: 9px; + color: #77756b; +} +.detail-content h2 { + font-size: 40px; + line-height: 1.12; + letter-spacing: -1.3px; + margin: 30px 0 13px; + max-width: 620px; +} +.detail-title-cn { + font-size: 23px; + display: flex; + align-items: center; + gap: 23px; +} +.detail-title-cn > span { + font-size: 11px; + color: #77756a; + letter-spacing: 1px; +} +.detail-rule { + height: 2px; + background: #20221d; + margin-top: 28px; +} +.metadata { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 25px 42px; + margin: 28px 0 34px; +} +.metadata dt { + font-size: 9px; + letter-spacing: 0.7px; + color: #807b70; +} +.metadata dd { + margin: 8px 0 0; + font-size: 14px; +} +.metadata dd i { + display: inline-block; + width: 5px; + height: 5px; + background: #8b8f75; + margin-right: 8px; + vertical-align: middle; +} +.detail-tabs { + position: relative; + display: flex; + gap: 34px; + border-bottom: 1px solid #bdb8ad; +} +.detail-tabs button { + font-size: 10px; + color: #989184; + position: relative; + padding: 0 0 14px; +} +.detail-tabs button span { + font-size: 13px; + margin-left: 8px; +} +.detail-tabs button.active { + color: #191c16; +} +.tab-indicator { + position: absolute; + bottom: -1px; + left: 0; + width: 1px; + height: 2px; + background: #25281e; + transform-origin: left center; + pointer-events: none; + transition: transform 180ms cubic-bezier(0.22, 1, 0.36, 1); +} +.tab-panel { + height: 189px; + padding-top: 22px; + overflow-y: auto; + overflow-x: hidden; + scrollbar-width: thin; + scrollbar-color: #bbb4a6 transparent; +} +.panel-label { + font-size: 9px; + letter-spacing: 1px; + color: #888074; +} +.tab-panel p { + font-size: 15px; + line-height: 1.95; + margin: 14px 0 0; + color: #5a584e; + text-align: justify; +} +.research-notes { + margin: 12px 0 0; + padding: 0; + list-style: none; +} +.research-notes li { + display: flex; + gap: 14px; + font-size: 13px; + line-height: 1.7; + margin: 8px 0; + color: #5a584e; +} +.research-notes li span { + font-size: 10px; + color: #a38e72; + line-height: 22px; +} +.log-row { + display: flex; + justify-content: space-between; + margin-top: 21px; + font-size: 10px; + gap: 12px; +} +.log-row b { + font-size: 9px; + font-weight: 400; + color: #7a8163; +} +.tab-panel .log-note { + font-size: 13px; +} +.detail-actions { + display: flex; + justify-content: space-between; + margin-top: 19px; + align-items: center; + gap: 30px; +} +.solid-button { + background: #252820; + color: #f0eee5; + height: 46px; + flex: 1; + font-size: 11px; + letter-spacing: 1px; + text-align: left; + padding: 0 18px; + display: flex; + align-items: center; + justify-content: space-between; + transition: background 0.2s; +} +.solid-button > span { + font-size: 11px; + color: #bcbfb1; + letter-spacing: 0; +} +.solid-button:hover { + background: #4b4a3b; +} +.export-button { + font-size: 11px; + letter-spacing: 1px; + width: 190px; + text-align: left; + display: flex; + align-items: center; + justify-content: space-between; + gap: 10px; + min-height: 46px; +} +.export-button span { + font-size: 11px; + line-height: 1; + letter-spacing: 0; + color: var(--theme-muted, #9b907e); + white-space: nowrap; +} +.detail-footnote { + display: flex; + justify-content: space-between; + font-size: 8px; + letter-spacing: 0.6px; + color: #9a9487; + margin-top: 22px; +} +.detail-footnote a { + color: inherit; + text-decoration: none; +} +.detail-footnote a:hover { + text-decoration: underline; +} +/* 归档内的相对路径:过长时省略,完整值放在 title 上。 */ +.detail-path { + min-width: 0; + max-width: 76%; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.modal-backdrop { + position: absolute; + inset: 0; + z-index: 20; + background: rgba(227, 224, 215, 0.45); + backdrop-filter: blur(18px); + display: flex; + align-items: center; + justify-content: center; +} +.modal-backdrop[hidden] { + display: none; +} +.modal-backdrop[data-transition="closing"] .terminal-modal { + pointer-events: none; +} +.terminal-modal { + width: 1260px; + height: 836px; + background: rgba(237, 235, 228, 0.97); + box-shadow: 0 26px 95px #63513a20; + border: 1px solid #f7f5ee; + position: relative; + padding: 35px 53px; +} +.modal-top { + display: flex; + align-items: center; + justify-content: space-between; + font-size: 11px; + letter-spacing: 1px; + border-bottom: 1px solid #c2bdb1; + padding-bottom: 20px; +} +.modal-top button { + font-size: 10px; + display: flex; + gap: 24px; + align-items: center; + line-height: 1; +} +.modal-top button > span { + position: relative; + flex: 0 0 16px; + height: 34px; + font-size: 0; +} +.modal-top button > span::before, +.modal-top button > span::after { + content: ""; + position: absolute; + left: 50%; + top: 50%; + width: 14px; + height: 2px; + background: currentColor; + transform: translate(-50%, -50%) rotate(45deg); +} +.modal-top button > span::after { + transform: translate(-50%, -50%) rotate(-45deg); +} +.terminal-modal h2 { + font-size: 38px; + letter-spacing: -0.6px; + margin: 32px 0 29px; + display: flex; + align-items: baseline; + gap: 23px; +} +.terminal-modal h2 small { + font-size: 15px; + color: #817a6c; + font-weight: 400; + letter-spacing: 2px; +} +.search-field { + height: 61px; + border-bottom: 2px solid #3b3c30; + display: flex; + align-items: center; + gap: 22px; +} +.search-field > span:first-child { + width: 26px; + height: 26px; +} +/* Fixed geometry keeps the search symbol independent of font fallback metrics. */ +.nav-glyph, +.search-field > span:first-child { + position: relative; + flex-shrink: 0; + display: block; + font-size: 0; +} +.nav-glyph::before, +.search-field > span:first-child::before { + content: ""; + position: absolute; + width: 60%; + height: 60%; + left: 30%; + top: 8%; + border: 2px solid currentColor; + border-radius: 50%; + box-sizing: border-box; +} +.nav-glyph::after, +.search-field > span:first-child::after { + content: ""; + position: absolute; + width: 42%; + height: 2px; + left: 9%; + top: 82%; + background: currentColor; + transform: rotate(-45deg); + transform-origin: left center; +} +.search-field input { + border: 0; + background: none; + width: 100%; + min-width: 0; + padding: 0; + height: 100%; + outline: none; + font-size: 19px; + color: #292b21; +} +.search-field input::placeholder { + color: #9f988a; + font-size: 17px; + letter-spacing: 0.5px; +} +.search-field .key { + width: 37px; + height: 24px; + margin-right: 4px; +} +.category-filters { + display: flex; + gap: 27px; + padding: 25px 0 24px; +} +.category-filters button { + font-size: 12px; + color: #8c8373; + transition: color 0.2s; + display: inline-flex; + align-items: center; + gap: 7px; +} +.category-filters button.active { + color: #1e2418; +} +.category-filters button:before { + content: ""; + width: 4px; + height: 4px; + background: currentColor; + visibility: hidden; +} +.category-filters button.active:before { + visibility: visible; +} +.result-header, +.result-row { + display: grid; + grid-template-columns: minmax(0, 1fr) 230px 175px; + padding-right: 7px; +} +.result-header { + padding-block: 14px; + /* Match the list's classic scrollbar gutter, including after filtering. */ + overflow-y: hidden; + scrollbar-gutter: stable; + scrollbar-width: thin; + border-top: 1px solid #bfb7a7; + border-bottom: 1px solid #bfb7a7; + font-size: 9px; + color: #82796a; + letter-spacing: 1px; +} +.search-results { + height: 387px; + overflow-y: auto; + overflow-x: hidden; + scrollbar-gutter: stable; + scrollbar-width: thin; + scrollbar-color: #bbb4a6 transparent; +} +.result-row { + width: 100%; + align-items: center; + min-height: 76px; + text-align: left; + border-bottom: 1px solid #d6d0c4; + transition: background 0.25s; +} +.result-row:hover, +.result-row:focus-visible { + background: #e2dccf; +} +.result-name { + display: flex; + align-items: center; + gap: 25px; + font-size: 16px; + min-width: 0; + padding-right: 24px; +} +.result-name > b { + font-size: 14px; + flex: 0 0 58px; + font-weight: 400; +} +.result-name > span { + display: block; + min-width: 0; + overflow-wrap: anywhere; +} +.result-name small { + display: block; + font-size: 9px; + letter-spacing: 0.7px; + color: #9b907e; + margin-top: 7px; +} +.result-name > i { + color: #ad9170; + font-size: 13px; + font-style: normal; +} +.result-row > span:nth-child(2) { + font-size: 12px; + color: #7d7565; +} +.result-row > span:last-child { + font-size: 9px; + letter-spacing: 0.5px; + color: #6b6d54; + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + padding-right: 30px; + line-height: 1; +} +.result-row > span:last-child > i { + font-size: 24px; + flex: 0 0 24px; + line-height: 1; + font-style: normal; + color: #343a29; +} +.modal-bottom { + position: absolute; + bottom: 30px; + left: 53px; + right: 53px; + display: flex; + justify-content: space-between; + font-size: 9px; + letter-spacing: 1px; + color: #8d8575; +} +.modal-bottom i { + font-style: normal; + color: #858a6b; + font-size: 8px; + margin: 0 8px; +} +.empty-results { + height: 100%; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; +} +.empty-results > span { + font-size: 44px; + font-weight: 300; + color: #a39b8a; +} +.empty-results > strong { + font-size: 18px; + font-weight: 400; + margin-top: 20px; +} +.empty-results > p { + font-size: 13px; + color: #8b8271; + margin: 15px 0 25px; +} +.empty-results > button { + font-size: 12px; + border-bottom: 1px solid #8d826c; + padding-bottom: 8px; +} + +/* 主检索里的本地归档命中:四十条精选档案之外的全量结果。 + 两段列表同时出现时把上面的结果列表让出高度,保持弹窗总高不变。 */ +.terminal-modal.has-local-hits .search-results { + height: 240px; +} +.local-hits { + height: 140px; + overflow-y: auto; + overflow-x: hidden; + scrollbar-gutter: stable; + scrollbar-width: thin; + scrollbar-color: #bbb4a6 transparent; + border-bottom: 1px solid #bfb7a7; +} +.local-hits[hidden] { + display: none; +} +.local-hits-head { + display: flex; + align-items: center; + justify-content: space-between; + gap: 16px; + padding: 9px 7px 9px 0; + font-size: 9px; + letter-spacing: 1px; + color: #82796a; + border-bottom: 1px solid #d6d0c4; + position: sticky; + top: 0; + background: rgba(237, 235, 228, 0.97); +} +.local-hits-head button { + font-size: 9px; + letter-spacing: 0.5px; + border-bottom: 1px solid #8d826c; + padding-bottom: 2px; + margin-left: 12px; +} +.local-hit { + display: grid; + grid-template-columns: 44px minmax(0, 1fr) 78px 116px; + align-items: center; + gap: 12px; + padding: 8px 7px 8px 0; + border-bottom: 1px solid #ded8cc; + font-size: 12px; +} +.local-hit:hover { + background: #e2dccf; +} +.local-hit-kind { + font-size: 8px; + letter-spacing: 0.8px; + text-align: center; + color: #8a8271; + border: 1px solid #c2bdb1; + padding: 2px 0; +} +.local-hit-name { + min-width: 0; + overflow-wrap: anywhere; +} +.local-hit-name small { + display: block; + margin-top: 3px; + font-size: 9px; + color: #9b907e; + overflow-wrap: anywhere; +} +.local-hit-size { + font-size: 10px; + color: #7d7565; + text-align: right; + white-space: nowrap; +} +.local-hit-actions { + display: flex; + gap: 7px; + justify-content: flex-end; +} +.local-hit-actions button { + font-size: 9px; + letter-spacing: 0.5px; + padding: 5px 9px; + border: 1px solid #b6b0a2; +} +.local-hit-actions button:hover { + border-color: #3b3c30; + background: #e7e2d6; +} + +/* 盘内浏览(详情页的「盘内文件」页签):一个项目一张盘,盘内逐层查看该项目 */ +.disc-crumbs { + display: flex; + align-items: center; + flex-wrap: wrap; + gap: 6px; + padding-bottom: 12px; + border-bottom: 1px solid #bfb7a7; + font-size: 11px; + letter-spacing: 0.4px; +} +.disc-crumb { + font-size: 11px; + letter-spacing: 0.4px; + color: #6b6d54; + border-bottom: 1px solid #8d826c; + padding-bottom: 1px; +} +.disc-crumb.current { + color: #3b3c30; + border-bottom: 0; +} +.disc-crumb-sep { + color: #a39b8a; +} +.disc-count { + margin-left: auto; + font-size: 9px; + letter-spacing: 0.6px; + color: #8d8575; +} +.disc-list { + max-height: 322px; + overflow-y: auto; + scrollbar-gutter: stable; + scrollbar-width: thin; + scrollbar-color: #bbb4a6 transparent; +} +.disc-row { + display: grid; + grid-template-columns: 46px minmax(0, 1fr) auto; + align-items: center; + gap: 14px; + padding: 11px 7px 11px 0; + border-bottom: 1px solid #ded8cc; +} +.disc-row:hover { + background: #e2dccf; +} +.disc-kind { + font-size: 8px; + letter-spacing: 0.8px; + text-align: center; + color: #8a8271; + border: 1px solid #c2bdb1; + padding: 3px 0; +} +.disc-name { + min-width: 0; + font-size: 13px; + overflow-wrap: anywhere; +} +.disc-name small { + display: block; + margin-top: 4px; + font-size: 9px; + letter-spacing: 0.4px; + color: #9b907e; +} +.disc-actions { + display: flex; + gap: 7px; +} +.disc-actions button { + font-size: 9px; + letter-spacing: 0.5px; + padding: 5px 10px; + border: 1px solid #b6b0a2; +} +.disc-actions button:hover { + border-color: #3b3c30; + background: #e7e2d6; +} +.disc-actions button[hidden] { + display: none; +} +.disc-empty { + font-size: 12px; + color: #8b8271; + margin: 18px 0; +} +[data-dark-surface="true"] :is(.disc-crumbs, .disc-row) { + border-color: var(--theme-line); +} +[data-dark-surface="true"] .disc-row:hover { + background: var(--theme-field); +} +[data-dark-surface="true"] :is(.disc-kind, .disc-name small, .disc-count, .disc-empty, .disc-crumb) { + color: var(--theme-muted); +} +[data-dark-surface="true"] :is(.disc-kind, .disc-actions button, .disc-crumb) { + border-color: var(--theme-line); +} +[data-dark-surface="true"] .disc-crumb.current { + color: var(--theme-ink); +} +[data-dark-surface="true"] .disc-actions button:hover { + background: var(--theme-field); + border-color: var(--theme-ink); +} +[data-dark-surface="true"] .local-hits-head { + background: var(--theme-panel); + color: var(--theme-muted); + border-color: var(--theme-line); +} +[data-dark-surface="true"] :is(.local-hits, .local-hit) { + border-color: var(--theme-line); + color: var(--theme-ink); +} +[data-dark-surface="true"] .local-hit:hover { + background: var(--theme-field); +} +[data-dark-surface="true"] :is(.local-hit-kind, .local-hit-name small, .local-hit-size, .local-hits-head button) { + color: var(--theme-muted); +} +[data-dark-surface="true"] :is(.local-hit-kind, .local-hit-actions button, .local-hits-head button) { + border-color: var(--theme-line); +} +[data-dark-surface="true"] .local-hit-actions button:hover { + background: var(--theme-field); + border-color: var(--theme-ink); +} +.settings-modal { + width: 930px; + height: 786px; +} +.settings-modal h2 { + font-size: 33px; +} +.settings-intro { + font-size: 10px; + letter-spacing: 1px; + color: #87806f; + margin: 0 0 34px; +} +.settings-intro > span { + margin: 0 18px; +} +.settings-list { + border-top: 1px solid #c1b8a7; +} +.audio-settings { display: grid; grid-template-columns: 1fr 1fr; gap: 32px; } +.settings-list .audio-toggle { padding: 20px 0 12px; border-bottom: 0; } +.settings-list .audio-toggle span { margin-top: 7px; } +.settings-list .audio-volume { justify-content: flex-start; gap: 12px; padding: 8px 0 18px; } +.settings-list .audio-volume span { margin: 0; font-size: 10px; white-space: nowrap; } +.settings-list .audio-volume input { position: static; width: 100%; min-width: 60px; height: 22px; opacity: 1; accent-color: #565c46; } +.audio-volume output { min-width: 36px; text-align: right; font-size: 10px; font-variant-numeric: tabular-nums; } +.audio-volume input:focus-visible { outline: 2px solid #a67d48; outline-offset: 4px; } +.settings-list label { + display: flex; + align-items: center; + justify-content: space-between; + padding: 27px 0; + border-bottom: 1px solid #d0c6b5; + cursor: pointer; + position: relative; +} +.settings-list strong { + font-size: 12px; + letter-spacing: 0.8px; + font-weight: 400; + display: block; +} +.settings-list label span { + display: block; + font-size: 12px; + color: #938977; + margin-top: 9px; +} +.settings-list input { + position: absolute; + right: 0; + opacity: 0; + width: 60px; + height: 40px; + cursor: pointer; +} +.toggle { + width: 50px; + height: 24px; + background: #d3ccbf; + position: relative; + pointer-events: none; + transition: background 0.3s; +} +.toggle:after { + content: ""; + position: absolute; + width: 16px; + height: 16px; + background: #f5f2e9; + left: 4px; + top: 4px; + transition: transform 0.3s; +} +.settings-list input:checked + .toggle { + background: #565c46; +} +.settings-list input:checked + .toggle:after { + transform: translateX(26px); +} +.settings-list input:focus-visible + .toggle { + outline: 2px solid #a67d48; + outline-offset: 5px; +} +.settings-shortcuts { + margin-top: 26px; +} +.settings-shortcuts > span { + font-size: 9px; + letter-spacing: 1px; + color: #8e826f; +} +.settings-shortcuts p { + font-size: 11px; + color: #776f60; + margin-top: 19px; +} +.settings-shortcuts kbd { + font-family: inherit; + padding: 5px; + border: 1px solid #c9c1b2; + font-size: 9px; + margin: 0 9px 0 18px; +} +.settings-shortcuts kbd:first-child { + margin-left: 0; +} +.settings-bottom { + margin-top: 30px; + display: flex; + justify-content: space-between; +} +.settings-bottom button { + font-size: 11px; + letter-spacing: 0.8px; + display: flex; + align-items: center; + gap: 20px; +} +.settings-bottom button span { + font-size: 20px; + line-height: 1; +} +.toast { + position: absolute; + z-index: 30; + left: 50%; + bottom: 85px; + transform: translate(-50%, 15px); + padding: 15px 27px; + background: #30362a; + color: #f1efdf; + font-size: 14px; + opacity: 0; + pointer-events: none; + transition: + opacity 0.3s, + transform 0.3s; +} +.toast.visible { + opacity: 1; + transform: translate(-50%, 0); +} +.loading { + position: absolute; + inset: 0; + background: #e8e5e1; + display: flex; + align-items: center; + justify-content: center; + flex-direction: column; + z-index: 50; + transition: opacity 0.6s; +} +.loading.loaded { + opacity: 0; + pointer-events: none; +} +.loading-mark { + width: 150px; + height: 90px; + margin-bottom: 49px; +} +.loading-mark svg { + width: 100%; + height: 100%; +} +.loading > span { + font-size: 11px; + letter-spacing: 2px; + color: #888277; +} +.loading > i { + width: 180px; + height: 1px; + background: #bab3a5; + position: relative; + margin-top: 20px; + overflow: hidden; +} +.loading > i:after { + content: ""; + position: absolute; + inset: 0; + width: 60px; + background: #414639; + animation: loading 1.3s ease-in-out infinite; +} +.error-state { + width: 700px; + text-align: center; +} +.error-state strong { + font-size: 29px; +} +.error-state p { + font-size: 18px; + line-height: 2; + color: #7c7568; +} +.error-state button { + margin-top: 30px; + font-size: 16px; + border-bottom: 1px solid; + padding-bottom: 8px; +} + +/* Reference sequence: each stage uses the same physical scene and DOM. */ +[data-mode="boot"] .system-nav, +[data-mode="boot"] .system-footer { + opacity: 0; + pointer-events: none; +} +[data-mode="boot"] .brand, +[data-mode="boot"] .powered { + opacity: 1; +} +[data-boot="access"] .brand, +[data-boot="access"] .powered, +[data-boot="logo"] .brand, +[data-boot="logo"] .powered { + opacity: 0; +} +[data-boot="access"] .access-text { + opacity: 1; +} +[data-boot="logo"] .boot-logo { + opacity: 1; + transform: translateX(294px); +} +[data-boot="auth"] .boot-logo { + opacity: 1; +} +[data-boot="auth"] .auth-status { + opacity: 1; + animation: fadeIn 0.7s; +} +[data-boot="scan"] .scan { + opacity: 1; + transform: scale(1); +} +[data-boot="welcome"] .welcome { + opacity: 1; + transform: translateY(0); +} +[data-boot="array"] .boot-background, +[data-boot="select"] .boot-background, +[data-boot="inspect"] .boot-background, +[data-boot="done"] .boot-background { + opacity: 0; +} +[data-boot="array"] .three-scene, +[data-boot="select"] .three-scene, +[data-boot="inspect"] .three-scene, +[data-boot="done"] .three-scene { + opacity: 1; +} +[data-boot="array"] .scene-atmosphere, +[data-boot="select"] .scene-atmosphere, +[data-boot="inspect"] .scene-atmosphere, +[data-boot="done"] .scene-atmosphere { + opacity: 1; +} +[data-boot="array"] .archive-ui, +[data-boot="select"] .archive-ui, +[data-boot="inspect"] .archive-ui { + opacity: 1; +} +[data-mode="boot"] .archive-counter, +[data-mode="boot"] .archive-navigation, +[data-mode="boot"] .archive-hint { + opacity: 0; +} +[data-boot="array"] .file-title { + font-size: 24px; +} +[data-boot="array"][data-mode="boot"] .file-title span { + display: none; +} +[data-boot="array"] .callout-rule { + transform: scaleX(1); +} +[data-boot="select"] .callout-rule { + transform: scaleX(1); +} +[data-boot="inspect"] .archive-ui { + opacity: 0; +} +[data-mode="archive"] .system-nav, +[data-mode="detail"] .system-nav, +[data-mode="archive"] .system-footer, +[data-mode="detail"] .system-footer { + opacity: 1; + pointer-events: auto; +} +[data-mode="archive"] .boot, +[data-mode="detail"] .boot { + opacity: 0; +} +[data-mode="archive"] .skip, +[data-mode="detail"] .skip { + display: none; +} +[data-mode="archive"] .archive-ui { + opacity: 1; + pointer-events: none; +} +[data-mode="archive"] .archive-ui button { + pointer-events: auto; +} +[data-mode="archive"] .eyebrow, +[data-mode="archive"] .read-file, +[data-mode="archive"] .file-summary { + opacity: 1; + transform: translateY(0); +} +[data-mode="archive"] .callout-rule { + transform: scaleX(1); +} +[data-mode="archive"] .callout-rule i { + opacity: 1; +} +[data-mode="detail"] .powered { + bottom: 108px; +} +[data-mode="detail"] .archive-ui { + opacity: 0; + pointer-events: none; +} +.archive-atmosphere { + background: none; +} +.archive-atmosphere::before, +.archive-atmosphere::after { + content: ""; + position: absolute; + inset: 0; +} +.archive-atmosphere::before { + opacity: calc(1 - var(--detail-shade, 0)); + background: + linear-gradient(180deg, rgba(234, 231, 226, 0.35), transparent 38%), + radial-gradient(ellipse at 42% 47%, transparent 32%, rgba(232, 229, 225, 0.19) 85%); +} +.archive-atmosphere::after { + opacity: var(--detail-shade, 0); + background: linear-gradient( + 90deg, + transparent 40%, + rgba(232, 229, 225, 0.2) 59%, + rgba(232, 229, 225, 0.92) 68%, + #e8e5e1 100% + ); +} +@keyframes orbit { + to { + transform: rotate(360deg); + } +} +@keyframes blink { + 50% { + opacity: 0; + } +} +@keyframes fadeIn { + from { + opacity: 0; + } + to { + opacity: 1; + } +} +@keyframes detailEnter { + from { + opacity: 0; + transform: translateY(23px); + } + to { + opacity: 1; + transform: translateY(0); + } +} +@keyframes modalEnter { + from { + opacity: 0; + transform: translateY(25px) scale(0.99); + } + to { + opacity: 1; + transform: translateY(0) scale(1); + } +} +@keyframes loading { + from { + transform: translateX(-60px); + } + to { + transform: translateX(180px); + } +} +.reduce-motion * { + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.01ms !important; + transition-delay: 0s !important; +} +@media (prefers-reduced-motion: reduce) { + .loading > i:after { + animation: none; + width: 100%; + } +} +/* Reading sizes for the extended research workspace. */ +.metadata dt { + font-size: 11px; +} +.metadata dd { + font-size: 17px; +} +.tab-panel p { + font-size: 18px; + line-height: 1.8; +} +.research-notes li { + font-size: 16px; +} +.panel-label { + font-size: 11px; +} +.detail-tabs button span { + font-size: 15px; +} +.tab-panel .log-note { + font-size: 16px; +} +.cinema-caption { + font-size: 24px; + color: #fff; + opacity: 0.88; + text-shadow: 0 1px 5px #4a4740; +} +.powered { + font-size: 19px; +} +.boot-background svg { + opacity: 0.09; + filter: blur(3px); +} +.archive-callout { + top: 471px; +} +.export-button { + color: inherit; + text-decoration: none; + cursor: pointer; +} +.export-button:hover { + color: #947444; +} +.export-button:focus-visible { + outline: 2px solid #a67d48; + outline-offset: 7px; +} + +[data-mode="boot"][data-boot="array"] .three-scene { + opacity: var(--entry-opacity, 1); + transition: none; +} +[data-mode="boot"] .callout-rule { + transform-origin: right center; +} +#inspection-marks { + position: absolute; + inset: 0; + width: 100%; + height: 100%; + pointer-events: none; + opacity: 0; + fill: #24221f; +} +#inspection-lines { + fill: none; + stroke: #24221f; + stroke-width: 2; +} +#inspection-text { + position: absolute; + left: 1202px; + top: 499px; + font-size: 23px; + font-weight: 600; + opacity: 0; + pointer-events: none; +} +#inspection-text strong { + display: block; + font-size: 30px; + margin-top: 16px; +} +.scene-atmosphere { + background: + linear-gradient( + 180deg, + #eae5e1 0%, + rgba(234, 229, 225, 0.82) 11%, + transparent 40% + ), + linear-gradient(0deg, rgba(234, 229, 225, 0.94) 0%, transparent 19%), + radial-gradient( + ellipse at 48% 48%, + transparent 35%, + rgba(234, 229, 225, 0.38) 85% + ); +} + +/* Independent object study, using the archive's typography and warm field. */ +.viewer-open { + pointer-events: auto; + display: flex; + align-items: center; + gap: 16px; + margin-top: 25px; + padding: 12px 0 8px; + border-bottom: 1px solid #8e897b; + font-size: 14px; + letter-spacing: 0.3px; +} +.viewer-open span { + font-size: 20px; +} +.viewer-open:hover { + color: #946b3c; + border-color: currentColor; +} +.model-viewer { + position: absolute; + inset: 0; + z-index: 30; + overflow: hidden; + background: #e8e5e1; + color: #20221c; +} +.model-viewer[hidden] { + display: none; +} +.model-viewer .viewer-atmosphere { + opacity: 1; + transition: none; +} +.viewer-canvas { + position: absolute; + inset: 0; +} +.viewer-canvas canvas { + display: block; + width: 100%; + height: 100%; + touch-action: none; + outline-offset: -8px; +} +.viewer-header { + position: absolute; + top: 58px; + left: 60px; + right: 60px; + display: flex; + align-items: flex-start; + pointer-events: none; +} +.viewer-back { + display: flex; + align-items: center; + gap: 18px; + font-size: 22px; + pointer-events: auto; + padding: 12px 0; +} +.viewer-back span { + font-size: 15px; +} +.viewer-back kbd { + font-family: inherit; + font-size: 10px; + border: 1px solid #b8b3a9; + padding: 4px; + color: #817b70; + margin-left: 8px; +} +.viewer-heading { + margin-left: 74px; + padding-top: 12px; +} +.viewer-heading > span { + font-size: 11px; + letter-spacing: 1.8px; + color: #79766a; +} +.viewer-heading h2 { + margin: 10px 0 8px; + font-size: 31px; + font-weight: 600; +} +.viewer-heading p { + margin: 0; + font-size: 11px; + letter-spacing: 1px; + color: #858073; +} +.viewer-index { + margin-left: auto; + font-size: 58px; + line-height: 1; + font-weight: 300; + color: #6a6b5d; +} +.viewer-index > span { + font-size: 32px; + vertical-align: top; +} +.viewer-parts { + position: absolute; + top: 300px; + right: 70px; + width: 230px; + pointer-events: none; + opacity: 0; + transform: translateX(12px); + transition: + opacity 0.4s, + transform 0.4s; +} +.model-viewer[data-exploded="true"] .viewer-parts { + opacity: 1; + transform: translateX(0); +} +.viewer-parts > div { + font-size: 11px; + letter-spacing: 1px; + margin-bottom: 24px; + color: #7e7a6d; +} +.viewer-parts p { + position: relative; + padding: 0 0 18px 38px; + margin: 0 0 20px; + border-bottom: 1px solid #cec9bc; +} +.viewer-parts p > span { + position: absolute; + left: 0; + top: 2px; + font-size: 11px; + color: #9b947f; +} +.viewer-parts strong { + font-size: 16px; + font-weight: 400; +} +.viewer-parts small { + display: block; + font-size: 9px; + letter-spacing: 1px; + margin-top: 6px; + color: #827b6c; +} +.viewer-footer { + position: absolute; + bottom: 68px; + left: 60px; + right: 60px; + display: flex; + align-items: center; + justify-content: space-between; + pointer-events: none; +} +.viewer-help { + display: flex; + flex-wrap: wrap; + gap: 18px; + width: 400px; + font-size: 12px; + color: #787568; +} +.viewer-actions { + display: flex; + pointer-events: auto; + border: 1px solid #a4a08f; + background: #e8e5e1; +} +.viewer-actions button { + min-width: 166px; + padding: 19px 24px; + font-size: 15px; + transition: + background 0.2s, + color 0.2s; +} +.viewer-actions button span { + margin-right: 10px; +} +.viewer-actions button[aria-pressed="true"] { + background: #292d24; + color: #eeeade; +} +.viewer-actions button:hover { + background: #c9c8b6; + color: #20221c; +} +.viewer-reset { + width: 400px; + text-align: right; + pointer-events: auto; + font-size: 14px; +} +.viewer-reset span { + margin-left: 18px; +} +.viewer-state { + position: absolute; + left: 50%; + bottom: 37px; + transform: translateX(-50%); + color: #85806f; + font-size: 11px; + letter-spacing: 1px; + pointer-events: none; +} +.viewer-loading { + position: absolute; + left: 50%; + top: 50%; + transform: translate(-50%, -50%); + text-align: center; + font-size: 15px; + color: #7c7565; +} +.viewer-loading[hidden] { + display: none; +} +.viewer-loading button { + display: block; + margin: 20px auto 0; + border-bottom: 1px solid; + padding: 8px; +} +.viewer-loading button[hidden] { + display: none; +} +.model-viewer button:disabled { + opacity: 0.35; + cursor: wait; +} +.reduce-motion .viewer-parts, +.reduce-motion .viewer-actions button { + transition: none; +} + +/* Use the same rolling engine as archive numbers, with quieter text blur. */ +#selected-title, +#column-name, +#hover-title, +#archive-category, +#selected-clearance { + flex: none; + --rn-blur: 0.2; + --rn-edge-fade: 0.08em; +} +/* Frame-driven opening: no elapsed CSS animation state when seeking/replaying. */ +[data-mode="boot"] .brand, +[data-mode="boot"] .powered, +[data-mode="boot"] .boot * { + transition: none; + animation: none; +} +[data-mode="boot"] .auth-status { + animation: none; +} +.auth-status i { + display: none; +} +.boot-white { + position: absolute; + inset: 0; + background: #fff; + opacity: 0; +} +.scan { + inset: 0; + width: 1920px; + height: 1080px; + transform: none; + transition: none; +} +.scan > span { + font-size: 22px; + white-space: nowrap; + padding-left: 0; +} +.welcome { + left: 720px; + top: 403px; + width: 480px; + height: 340px; + transform-origin: 50% 44%; + transition: none; +} +.welcome-heading { + position: relative; + z-index: 1; + line-height: 65px; + font-size: 55px; + white-space: nowrap; +} +.welcome-panel { + position: absolute; + top: -59px; + left: 5px; + width: 470px; + height: 392px; + background: #080a08; +} +.welcome-company { + position: absolute; + top: 67px; + left: 35px; + width: 410px; + height: 60px; +} +.welcome-company strong { + display: block; + position: absolute; + inset: 0; + padding: 5px 8px; + background: transparent; + color: #080a08; + font-size: 48px; + line-height: 50px; + white-space: nowrap; + letter-spacing: -0.5px; +} +.welcome-company .welcome-highlight { + background: #080a08; + color: #eeede8; +} +.welcome-database { + position: absolute; + top: 134px; + left: 0; + width: 100%; + font-size: 36px; + line-height: 43px; + white-space: nowrap; +} +.welcome-logo { + position: absolute; + top: 187px; + left: 138.5px; + margin: 0; +} +[data-mode="archive"] .boot, +[data-mode="detail"] .boot { + visibility: hidden; +} diff --git a/src/theme-material.ts b/src/theme-material.ts new file mode 100644 index 0000000..8095553 --- /dev/null +++ b/src/theme-material.ts @@ -0,0 +1,60 @@ +import * as THREE from "three"; + +const surfaces: Record = { + Frosted_Polymer: "#626b70", Ivory_Edges: "#687277", Optical_Diffuser: "#192226", + Titanium_Fasteners: "#b1b9bb", Index_Inlay: "#c6a36b", Printed_Label: "#303a3e", + Subsurface_Optics: "#939e9f", Optical_Edges: "#bbc3bc", Carbon_Ink: "#b6bdb8", +}; +/** Extend existing optical shaders; one float per instance avoids new meshes or passes. */ +export function themeMaterial(material: THREE.Material, name: string, instanced = false, subduedIndex = { value: 0 }) { + const amount = { value: 0 }; + const before = material.onBeforeCompile; + const cache = material.customProgramCacheKey.bind(material)(); + const color = new THREE.Color(surfaces[name] ?? (name.includes("Orange") ? "#bb8850" : "#969f9f")); + material.onBeforeCompile = (shader, renderer) => { + before.call(material, shader, renderer); + shader.uniforms.rhineTheme = amount; + shader.uniforms.rhineDarkSurface = { value: color }; + shader.uniforms.rhineSubduedIndex = subduedIndex; + if (instanced) { + shader.vertexShader = "attribute float archiveTheme; varying float vRhineTheme;\n" + shader.vertexShader; + shader.vertexShader = shader.vertexShader.replace("#include ", "#include \nvRhineTheme = archiveTheme;"); + shader.fragmentShader = "varying float vRhineTheme;\n" + shader.fragmentShader; + } + shader.fragmentShader = "uniform float rhineTheme; uniform vec3 rhineDarkSurface; uniform float rhineSubduedIndex;\n" + shader.fragmentShader; + const mix = instanced ? "vRhineTheme" : "rhineTheme"; + const printed = name === "Printed_Canvas"; + const anchor = printed ? "#include " : "#include "; + const dark = printed + ? "mix(vec3(0.023, 0.032, 0.037), vec3(0.78, 0.78, 0.71), 1.0 - smoothstep(0.12, 0.65, dot(diffuseColor.rgb, vec3(.2126,.7152,.0722))))" + : name === "Frosted_Polymer" && !instanced + ? "mix(rhineDarkSurface, vec3(0.92, 0.96, 0.97), glassRevealAtHeight(archiveClarity, vArchiveHeight))" + : name === "Index_Inlay" ? "mix(rhineDarkSurface, vec3(0.030, 0.042, 0.048), rhineSubduedIndex)" : "rhineDarkSurface"; + const output = printed ? "outgoingLight" : "diffuseColor.rgb"; + shader.fragmentShader = shader.fragmentShader.replace(anchor, `${output} = mix(${output}, ${dark}, ${mix});\n${anchor}`); + }; + material.customProgramCacheKey = () => `${cache}-rhine-theme-${name}-${instanced}`; + return amount; +} + +type Baseline = { background: THREE.Color; fog?: THREE.Color; intensity: number; exposure: number; lights: { light: THREE.Light; intensity: number }[]; floor?: { material: THREE.MeshStandardMaterial; color: THREE.Color } }; +const scenes = new WeakMap(); +const background = new THREE.Color("#11181b"), floorColor = new THREE.Color("#192125"), mistColor = new THREE.Color("#263136"); +export function themeEnvironment(scene: THREE.Scene, renderer: THREE.WebGLRenderer, amount: number) { + let baseline = scenes.get(scene); + if (!baseline) { + const lights: Baseline["lights"] = []; + scene.traverse(object => { if (object instanceof THREE.Light) lights.push({ light: object, intensity: object.intensity }); }); + const floor = scene.getObjectByName("archive-floor") as THREE.Mesh | undefined; + const material = floor?.material as THREE.MeshStandardMaterial | undefined; + baseline = { background: (scene.background as THREE.Color).clone(), fog: scene.fog?.color.clone(), intensity: scene.environmentIntensity, + exposure: renderer.toneMappingExposure, lights, floor: material ? { material, color: material.color.clone() } : undefined }; + scenes.set(scene, baseline); + } + (scene.background as THREE.Color).copy(baseline.background).lerp(background, amount); + if (scene.fog && baseline.fog) scene.fog.color.copy(baseline.fog).lerp(mistColor, amount); + if (baseline.floor) baseline.floor.material.color.copy(baseline.floor.color).lerp(floorColor, amount); + scene.environmentIntensity = THREE.MathUtils.lerp(baseline.intensity, .32, amount); + renderer.toneMappingExposure = THREE.MathUtils.lerp(baseline.exposure, .98, amount); + for (const { light, intensity } of baseline.lights) light.intensity = intensity * (1 - .35 * amount); +} diff --git a/src/theme-motion.ts b/src/theme-motion.ts new file mode 100644 index 0000000..3f60926 --- /dev/null +++ b/src/theme-motion.ts @@ -0,0 +1,30 @@ +export type ThemeCell = { row: number; lane: number }; +const key = (cell: ThemeCell) => `${cell.lane}:${cell.row}`; +const ease = (t: number) => { t = Math.max(0, Math.min(1, t)); return t * t * (3 - 2 * t); }; + +/** A material wave with a frozen origin and an interruptible per-card starting colour. */ +export class ThemeWave { + target = 0; + private start = -10; + private origin: ThemeCell = { row: 12, lane: 2 }; + private from = new Map(); + private latest = new Map(); + private backgroundFrom = 0; + set(dark: boolean, time: number, origin: ThemeCell, immediate = false) { + const target = dark ? 1 : 0; + if (target === this.target && !immediate) return; + this.backgroundFrom = immediate ? target : this.background(time); + this.from = immediate ? new Map() : new Map(this.latest); + this.target = target; this.start = immediate ? time - 10 : time; + this.origin = { ...origin }; + } + background(time: number) { return this.backgroundFrom + (this.target - this.backgroundFrom) * ease((time - this.start) / .85); } + beginFrame() { this.latest.clear(); } + sample(cell: ThemeCell, time: number) { + const delay = Math.min(.6, Math.abs(cell.row - this.origin.row) * .034 + Math.abs(cell.lane - this.origin.lane) * .11); + const from = this.from.get(key(cell)) ?? this.backgroundFrom; + const value = from + (this.target - from) * ease((time - this.start - delay) / .58); + this.latest.set(key(cell), value); + return value; + } +} diff --git a/src/theme-ui.ts b/src/theme-ui.ts new file mode 100644 index 0000000..aebcaed --- /dev/null +++ b/src/theme-ui.ts @@ -0,0 +1,24 @@ +import "./theme.css"; +const palette = { + ink: ["#080a08", "#e0e3dc"], muted: ["#77756d", "#a6b0b1"], line: ["#aaa59a", "#536166"], + paper: ["#eae5e1", "#11181b"], panel: ["#edebe4", "#202a2f"], field: ["#e7e3d9", "#2a363b"], + accent: ["#9b7247", "#c5a16b"], +} as const; +let previous = -1; +export let themeAmount = 0; +function rgb(hex: string) { return [1, 3, 5].map(i => parseInt(hex.slice(i, i + 2), 16)); } +export function paintTheme(amount: number) { + if (Math.abs(amount - previous) < .0001) return; + previous = themeAmount = amount; + const root = document.documentElement; + root.dataset.darkSurface = String(amount > .0001); + for (const [name, values] of Object.entries(palette)) { + const from = rgb(values[0]), to = rgb(values[1]); + const value = from.map((v, i) => Math.round(v + (to[i] - v) * amount)).join(", "); + root.style.setProperty(`--theme-${name}`, `rgb(${value})`); + root.style.setProperty(`--theme-${name}-rgb`, value); + } +} +export function themeSettingsMarkup(dark: boolean) { + return `
界面配色玻璃阵列随配色逐张过渡
`; +} diff --git a/src/theme.css b/src/theme.css new file mode 100644 index 0000000..c1fbfb9 --- /dev/null +++ b/src/theme.css @@ -0,0 +1,63 @@ +.theme-settings { padding: 16px 0; border-bottom: 1px solid #bdb8ad; display: flex; justify-content: space-between; align-items: center; gap: 20px; } +.theme-settings strong { display: block; font-size: 14px; } +.theme-settings span { display: block; margin-top: 6px; font-size: 12px; color: #77756d; } +.theme-choices { display: flex; gap: 6px; } +.theme-choices button { min-width: 76px; min-height: 42px; border: 1px solid #a6a49c; font-size: 13px; } +.theme-choices button[aria-pressed="true"] { background: #171713; color: #ece9e4; } + +[data-dark-surface="true"] { color-scheme: dark; color: var(--theme-ink); background: var(--theme-paper); } +[data-dark-surface="true"] :is(#viewport, #stage, .model-viewer, .boot-white) { background: var(--theme-paper); color: var(--theme-ink); } +[data-dark-surface="true"] :is(.brand, .powered, .workbench, .system-nav, .system-footer, .archive-ui, .detail-ui, .terminal-modal, .relay-entry, .relay-hud, .relay-target) { color: var(--theme-ink); } +[data-dark-surface="true"] .scene-atmosphere { background: linear-gradient(180deg, var(--theme-paper), rgba(var(--theme-paper-rgb), .82) 11%, transparent 40%), linear-gradient(0deg, rgba(var(--theme-paper-rgb), .94), transparent 19%), radial-gradient(ellipse at 48% 48%, transparent 35%, rgba(var(--theme-paper-rgb), .38) 85%); } +[data-dark-surface="true"] .boot-background { background: radial-gradient(ellipse at 51% 48%, var(--theme-panel), var(--theme-paper) 76%); } +[data-dark-surface="true"] :is(.terminal-modal, .viewer-loading, .toast) { background: var(--theme-panel); color: var(--theme-ink); box-shadow: 0 26px 95px #0003; border-color: var(--theme-line); } +[data-dark-surface="true"] .modal-backdrop { background: rgba(var(--theme-paper-rgb), .6); } +[data-dark-surface="true"] :is(.wallpaper-settings-note, .quality-cycle, .wb-cover, .relay-actions button, .relay-target) { background: var(--theme-field); color: var(--theme-ink); border-color: var(--theme-line); } +[data-dark-surface="true"] :is(.wb-muted, .wb-media small, .wb-timer-label, .wb-nav button, .wb-task[aria-pressed="true"], .theme-settings span, .settings-intro, .settings-list label span, .audio-setting span, .settings-shortcuts, .modal-bottom, .detail-kicker, .detail-content p, .detail-content li, .detail-tabs button, .file-summary, .archive-hint, .viewer-help, .viewer-heading p, .viewer-state, .object-caption small, .result-row small) { color: var(--theme-muted); } +[data-dark-surface="true"] :is(.wb-nav button[aria-pressed="true"], .detail-tabs button.active, .settings-list strong, .detail-content h2, .detail-content h3, .detail-content strong, .result-row, .quality-heading h3, .quality-label) { color: var(--theme-ink); } +[data-dark-surface="true"] :is(.wb-storage, a, .workbench button:hover, .relay-entry:hover) { color: var(--theme-accent); } +[data-dark-surface="true"] :is(.wb-kicker, .wb-today, .wb-title, .wb-task, .wb-check, .wb-timer-buttons button, .wb-settings, .wb-settings button, .theme-settings, .theme-choices button, .modal-top, .settings-list label, .settings-bottom, .settings-shortcuts, .detail-tabs, .result-header, .result-row, .quality-grid, .quality-heading, .quality-range, .viewer-parts p, .viewer-footer, .viewer-surface button, .relay-entry, .relay-actions button, .category-filters button) { border-color: var(--theme-line); } +[data-dark-surface="true"] :is(.wb-rule, .callout-rule, .file-ticks button::before, .wb-nav button::before, .relay-time) { background: var(--theme-line); } +[data-dark-surface="true"] :is(.wb-rule i, .powered i, .tab-indicator, .status-light, .relay-time i) { background: var(--theme-ink); } +[data-dark-surface="true"] .wb-nav button[aria-pressed="true"]::before { background: var(--theme-ink); } +[data-dark-surface="true"] .wb-nav button:hover::before { background: var(--theme-accent); } +[data-dark-surface="true"] .system-footer button:hover { color: var(--theme-accent); } +[data-dark-surface="true"] :is(.theme-choices button[aria-pressed="true"], .wb-settings button[aria-pressed="true"], .wb-task[aria-pressed="true"] .wb-check, .wb-timer-buttons button:first-child, .category-filters button.active, .viewer-surface button[aria-pressed="true"], .viewer-actions button[aria-pressed="true"]) { background: var(--theme-ink); color: var(--theme-paper); border-color: var(--theme-ink); } +[data-dark-surface="true"] :is(input, select) { color: var(--theme-ink); accent-color: var(--theme-accent); } +[data-dark-surface="true"] input::placeholder { color: var(--theme-muted); } +[data-dark-surface="true"] :is(.search-field, .toggle, .settings-shortcuts kbd) { background: var(--theme-field); border-color: var(--theme-line); } +[data-dark-surface="true"] .settings-list input:checked + .toggle { background: var(--theme-accent); } +[data-dark-surface="true"] .relay-target:hover { background: var(--theme-ink); color: var(--theme-paper); } +[data-dark-surface="true"] .relay-target { box-shadow: 0 0 0 5px rgba(var(--theme-paper-rgb), .35); } +[data-dark-surface="true"] :is(.boot-logo, .welcome-logo) svg text { fill: currentColor; } +[data-dark-surface="true"] .scan [stroke="#080a08"] { stroke: var(--theme-ink); } +[data-dark-surface="true"] .scan [stroke="#fff"] { stroke: var(--theme-line); } +[data-dark-surface="true"] .scan .scan-core { fill: var(--theme-ink); } +[data-dark-surface="true"] .welcome-panel { background: var(--theme-panel); } +[data-dark-surface="true"] .welcome-company strong { background: var(--theme-ink); color: var(--theme-paper); } +[data-dark-surface="true"] :is(#inspection-marks, .inspection-overlay) { color: var(--theme-ink); } +[data-dark-surface="true"] :is(.back-button, .viewer-back, .viewer-reset, .viewer-index, .viewer-parts, .file-title, .file-open, .read-file, .column-navigation, .archive-counter) { color: var(--theme-ink); } +@media(max-width: 700px) { .theme-settings { align-items: flex-start; }.theme-choices button { min-width: 58px; } } +[data-dark-surface="true"] .archive-atmosphere { background: none; } +[data-dark-surface="true"] .archive-atmosphere::before { background: linear-gradient(180deg, rgba(var(--theme-paper-rgb), .35), transparent 38%), radial-gradient(ellipse at 42% 47%, transparent 32%, rgba(var(--theme-paper-rgb), .19) 85%); } +[data-dark-surface="true"] .archive-atmosphere::after { background: linear-gradient(90deg, transparent 40%, rgba(var(--theme-paper-rgb), .2) 59%, rgba(var(--theme-paper-rgb), .92) 68%, var(--theme-paper) 100%); } +[data-dark-surface="true"] [data-layout="portrait"] .archive-atmosphere::after { background: linear-gradient(180deg, transparent 28%, rgba(var(--theme-paper-rgb), .2) 39%, rgba(var(--theme-paper-rgb), .96) 51%, var(--theme-paper) 60%); } +[data-dark-surface="true"] [data-layout="portrait"] .archive-atmosphere::before { background: linear-gradient(180deg, rgba(var(--theme-paper-rgb), .35), transparent 25%, transparent 46%, rgba(var(--theme-paper-rgb), .8) 65%, var(--theme-paper) 82%); } +[data-dark-surface="true"] :is(.wb-settings p, .terminal-modal h2 small, .motion-preference-note, .quality-summary, .quality-note, .quality-heading h3 span, .quality-advanced summary span, .quality-control small, .detail-kicker span, .detail-title-cn > span, .metadata dt, .panel-label, .detail-footnote, .viewer-heading > span, .viewer-parts > div, .viewer-parts small, .viewer-back kbd, .empty-results > p) { color: var(--theme-muted); } +[data-dark-surface="true"] :is(.viewer-actions, .quality-settings select) { background: var(--theme-panel); color: var(--theme-ink); border-color: var(--theme-line); } +[data-dark-surface="true"] .viewer-actions button:hover { background: var(--theme-field); color: var(--theme-ink); } +[data-dark-surface="true"] :is(.detail-rule, .solid-button) { background: var(--theme-ink); color: var(--theme-paper); } +[data-dark-surface="true"] .solid-button > span { color: inherit; } +[data-dark-surface="true"] :is(.audio-volume input, .quality-range input) { accent-color: var(--theme-accent); } +[data-dark-surface="true"] :is(.boot, .access-text, .boot-logo, .auth-status, .scan, .welcome-logo, .welcome-database) { color: var(--theme-ink); } +[data-dark-surface="true"] .scan [fill="#080a08"] { fill: var(--theme-ink); } +[data-dark-surface="true"] .scan [fill="#fff"] { fill: var(--theme-line); } +[data-dark-surface="true"] .boot-background [stroke="#fff"] { stroke: var(--theme-line); } +[data-dark-surface="true"] .welcome-company strong { background: transparent; color: var(--theme-ink); } +[data-dark-surface="true"] .welcome-company .welcome-highlight { background: var(--theme-ink); color: var(--theme-paper); } +[data-dark-surface="true"] .viewer-surface button { color: var(--theme-muted); } +[data-dark-surface="true"] .viewer-surface button[aria-pressed="true"] { color: var(--theme-paper); } +/* Dark edges need a visible slate haze, not only black attenuation. */ +[data-dark-surface="true"] .archive-atmosphere::before { + background: linear-gradient(180deg, rgba(var(--theme-paper-rgb), .82), transparent 36%), linear-gradient(0deg, rgba(var(--theme-paper-rgb), .84), transparent 25%), radial-gradient(ellipse at 48% 48%, transparent 42%, rgba(var(--theme-paper-rgb), .48) 90%); +} diff --git a/src/three-resources.ts b/src/three-resources.ts new file mode 100644 index 0000000..f556e11 --- /dev/null +++ b/src/three-resources.ts @@ -0,0 +1,26 @@ +import * as THREE from "three"; + +/** Dispose shared resources once before dropping the scene's JS references. */ +export function disposeThreeTree(root: THREE.Object3D) { + const geometries = new Set(); + const materials = new Set(); + const textures = new Set(); + root.traverse(object => { + if (!(object instanceof THREE.Mesh)) return; + if (object instanceof THREE.InstancedMesh) object.dispose(); + geometries.add(object.geometry); + for (const material of [object.material, object.userData.fullMaterial, object.userData.fastMaterial].flat()) + if (material instanceof THREE.Material) materials.add(material); + }); + for (const material of materials) { + for (const value of Object.values(material)) if (value instanceof THREE.Texture) textures.add(value); + material.dispose(); + } + if (root instanceof THREE.Scene) { + if (root.environment) textures.add(root.environment); + if (root.background instanceof THREE.Texture) textures.add(root.background); + } + geometries.forEach(geometry => geometry.dispose()); + textures.forEach(texture => texture.dispose()); + root.clear(); +} diff --git a/src/typing-rhythm.ts b/src/typing-rhythm.ts new file mode 100644 index 0000000..76b3483 --- /dev/null +++ b/src/typing-rhythm.ts @@ -0,0 +1,30 @@ +import { bootMotion } from "./boot-motion"; + +// Use the actual 25 fps text reveal, including the first character of each field. +// Glitch restoration at frames 479/485/486 is not new typing. +export const TYPING_FRAMES: readonly number[] = [ + [170, 187], + [282, 295], + [320, 339], + [367, 389], + [423, 440], + [449, 457], +].flatMap(([start, end]) => { + const frames: number[] = []; + let previous = 0; + for (let frame = start; frame <= end; frame++) { + const motion = bootMotion(frame / 25 - 5); + const text = frame < 200 ? motion.access : motion.auth; + const count = text.replace(/\s/g, "").length; + if (count > previous) frames.push(frame); + previous = count; + } + return frames; +}); + +export function hasTypingBetween(previousVideoTime: number, videoTime: number) { + return TYPING_FRAMES.some( + (frame) => + frame / 25 > previousVideoTime + 1e-6 && frame / 25 <= videoTime + 1e-6, + ); +} diff --git a/src/typing-samples.ts b/src/typing-samples.ts new file mode 100644 index 0000000..e580e47 --- /dev/null +++ b/src/typing-samples.ts @@ -0,0 +1,7 @@ +// Generated by scripts/extract-typing-audio.mjs. Source provenance: public/audio/typing-source.json. +export const TYPING_SAMPLE_RATE = 48000; +export const TYPING_PCM = [ + "AAD7////DQABAOv/8v/2/+v/9f8KAA0ACwAUABkA+f/P/+X/EAD9/+X/8P///xkAIwAAAPv/AQDG/7H/5//V/7T/BAA4AAcAAQAoAC0AKAAKAN//9P8UAAUAFwA4ABgA+/8HAPT/yf+6/8j/5P/w//n/KQBLAEIAYwCJAE0ABAAMABwAEwAZACAALgBEAC8ACwAQABMA9f/j/+f/5P/b//T/MQA+AAAA3P/j/8v/uP/M/8f/uP/Y//n/AAAWACcADADd/9H/AgA1ACcA/v/k/8n/xf/k//H/6//4/wYAAQDz/+3/CAAqAA8A0//H/+D/7//5/wUA///f/8n/5f8MAAkAAQAYACcAHQAdACQAEADs/+b/BAAEANL/wv/0/yEAGgD4//b/KwBYAD0AFwAmADAAFAAEAAwAFQAmADoAIwDt/9D/zv/j/w0ACADG/7X/5P/l/8T/3/8VABgA8v/e//7/HgAKAAQAGgDz/8b/6/8FAOn/9/8oAEUAQAD0/63/9/9hACcArf+x/xgAZABMAPX/6f9MAH0AHACn/5j/x//k/+3/8P/g/9j/CABJAEQABADb/9b/yv/J/+r/+//q/+//8//V/+P/GQACAMr/7f8eAPj/5/89AIQAXAAeADYAWQAiAN3/7v8gADoARwApAOf/9P9eAIcAHgCo/6z/+/8pAB0A7f/R//j/DwC+/4j/0/8ZABAAGQAhAOf/xf8MAFQALwDS/7///v8cAPb/1f/q/xQADgDe/9v/BgAPAPj/0/+f/7z/EAD6/87/GAA0AOL/yP/I/6b/3f8hANP/kv/o/z4AGwC1/5b/+/8/APb/3/80AFIATABgADMA9f8UAEYAVQBWADUAJABKADoA+v8MAEoAUwAvAPT/0//5/wQAvv/U/2QAbwDJ/57/LwBcAPT/6/9MAFQA+f++/9z/EADz/73/+P9LACoA9v8MACwAQQBCAAgA6v8gADUA8//K//L/JwAcAOX/2/8GABQA8/++/4n/f/+q/8z/0v/g/+r/2f/G/83/6v///+r/s/+g/8b/2P/H/+f/EADU/5j/2/8hAAgA+v8SAPf/zv/l/xQAJwArAD0AUAAnAN//AABoAFcA/v8AAA4A3v/z/z4ALAALACUADgDb/+r/+f8DADAADwC2/9r/KQASAA0AQAAkAOD/8v8mABMAy/+l/77/8f8dADIANQAjAC0AKgD+/xIAWABSAP7/qf+r//T/8f/P/yYAWADl/63/AQAbAOD/vf/O/+j/2v+3/7n/xv/f/wAA2P+e/7v/z/+9/9f/3P/R/wkAKwAbACsAKQAlAFYARQAQAEIAYwAxACUAJwAkAEEADwC7//n/WABHABYA7v/T/+H/8P///w4A6P/S/+v/5P/7/z8AIQDn//7/AwD9/xAA5/+4/8H/tv+5/7//XP8j/4r/0/+0/5v/1P9RAEEASv+k/kT/sgB5AUIAjv4K/5gAXAElAp8BWP42/tQFJAwHBrf52vZx/mIEngWEBqoCc/cw8gP9GQzKDND+nvUq/NIC1Pua9Hj7+geXCzYCOfWW9UMDiQgm/0L74QXzDSgFDfWY8dn9AAioA5r5VPdT/dsC4AI0AbYCOwQ5AJv69vwmBhgJmQEB/Cn/rQHi/dr7agGqB6gEZftk+iMDFwQ/+BzyN/vWBcwFWgCw/4ADiwO9/n79lALzBf8BJ/wv/AMA4v/m/M79AAGmAXUB/gGsAbEAO/66+Xf4/vwBAaYAQf9///QAAAIhAcT/TgCFAWoB4AALAYYBlgGZALL+I/3t/CL+JQDeAT8C7QAv/2D/VwHZARcA2/4l/7v/TAAFAFb+5/2c//3/m/8TAvYDOwEF//wAZgJKAZAAHQA9/3v/CAA//7b+iv+KAKwB4wL5ArgBFQD7/iH/kwCnASoAgP2l/dn/IwBa/7f/pf/3/nb/KgClAPYBJAIpAC//DwBlANH/If9w/mD++v5T/93/UAH8ATIAIP70/mwBNwJWAWUAuf+f/8P/K//7/i8AlwBT/6b+B/8h/37/1wCiAbQAXv/o/pr+Lv6a/mn/l/+g/3X/r/4K/zcBtgJWAnkBowBX/+H9jP1J/5IBwwEvADr/h/9GAPEALAHNACYAov9Z/0v/BwBkAXMBvf/e/uT/pQAbAHT/kv8oAE0AuP99/x4AkgAvAHn/9/61/p/+Bv8nABsBzQCv/xT/k/9yAJYALQAvAKAA9ADAAMX/7/54/04A0f/6/in/v/8IABwA/P/T/+H/2f+I/2H/1P+TAM8AiwCDAH8A9f+f/+r/JAA4AGQANADa/+v/9//B/+n/YgCrAM0AqQA5AC0AoACZANL/Nv9t/zQA9gAiAZMABgAcADYAvP9c/6z/RgCoAGwAtP+L/+//mv/5/lX/yP9j/2f/PgCcAF0AVABsAGwAmADVAMoAbgDr/4v/d/9v/2H/nP/h/4n/Jf/N/70AcACG/3b/6v8fAEgAYgBCAEgAVgD+/8P/5P+U/9v+wP5F/47/mf/g/xkA2/+r/wQAXABNADQAQQBOAE4AIQDJ/5z/n/+t//X/XQCIAJIAkQBKAOf/x//O/7z/lP+t/zcAngBAALP/yf8aAPj/vP/z/0EADgCk/6z/GABqAHkATAAQABYAKADh/5n/vP/9/xEAEgATACMAUwBrAFgASQAzAPn/0v/z/ycAMQAiAB8AFQDp/6j/c/9+/9z/LwAdANL/mP+G/6f/3/8DADAAdwCNAFEAFwAoAFsAWgAWANb/2f8UACwA4/+B/13/Wf9Z/5r/DwBiAG4AOgALAEEAnwCLADAAGwAtAP//uP/C/xkAUgAuAOf/y//U/93//P9bAKoAWACb/1b/uf8gACkA/v/k//f/AgDW/7r/AAB3AKQAWQDx/9n//v8EANj/t//L//H/8P/V/8f/xf/T//X/CwAQAA8A/v/z/wMA/P/b/97//f8QABoAEwAHABkAEADJ/7H/7/8EAML/of/x/24AmwBcAAUA7P8uAI8AgADW/zr/Qv+w//T/zP96/5H/IQB6ADcA1f/n/0AAWAAWAOb/9/8DAOb/2P/r//f/9P/1//L/4f/P/+X/MgCAAHkAJADm//b/CQDc/7r//v9VAD4A3P+3/wUAawBFAIb/Dv9z/xsASgAHAMT/0P8UADYALAA8AFgAMgDg/9T/KgBoACkAz//W/wYAEAATACQAJQABALn/kf/S/zUARgAXAPP/5f/h/9r/3f/9/wsA6v/j/wUAAwDc/8v/zf/R/9z/3//n//3/AADw/wIAKQAgAOP/uv/V/wsAKwAwAB0ADAAkACYA1f+w/xMAZgA2AO3/8v8pAEkAMgAPAAQA6v+5/7T/8/9NAHoARwD9/wAAIwAXAAYAFgALAMf/lf+x//H/FgAdABoAIABDAFUAJQDt/+//BwAKAPz/6v/y/wUA7v/N/9v/8//8/w4AFgARAB8AIgD//+L/4P/n//P/8f/g/+X/+v/+//z/BwAEAOj/2v/y//r/3//f/wIAAwDl/9z/5/8GACQAGgAOACIAGwD9/wgAFwAEAAIAFgAcACIAHwD//+//AwAJAP3/AwAVAAwA8//x//v/8//8/xwAFADx//D/+f/8/xYAGwDx/+7/GAAYAP//CwAWAAcACAAOAAIAAQACAOv/6P/+//X/8f8qAEkAEgDp//7/EAAMAAsA///x//7/DwD+/+f/7v/+//3/BwAkACcADAD+//3/9P/t/+X/4f/4/wwA/f/0//r/7//6/x4ADgDk//f/GAAAAOb/+f8MAAYAAQANAA8A5//M//3/LAD9/8//AwAyAAYA3P/y/wUA+/8AAAIA5//e/wQAHAAHAP7/EQAHAOT/6v8RAAsA2f/L//D/+P/Y/+b/GQAJANH/3f8LAA4ADQAfABgA/f/7/xQAJQAXAPv/8f/r/+D/+f8aAP3/3f8IADAAFgD9/wIAAgAKACAAJQATAPr/5//u//3/AQAHAAwA+v/t//f///8AAAcAEwAeABwAEAAWACkAIQAPABcAIAAWAA8ADAAJAAEA7v/g/+n/8P/y////+//m/+n/9f/9/wsA9v/N/+H/CAD1/+n//P/x/+n/AAD//wEAIAASAOX/6v8AAAEABgD5/93/5/8EABIAFAAIAAgAIgAfAAcAGwAxABsADwARAPn/7v8CAAsABwAEAP3/8f/f/9X/5v/f/7r/y//9//7/AwAlAA0A7/8jAEQAKgA8AE0AKQA4AGAAMgASADcAHADd//7/OwA2AA8A6//f/9X/s//I/wwA9v/I//T/+f+8/9L/5v+c/6D/AwAbAAkAHQAaAAIAAwALAB8ANgAuAB4AEQD5/+//6P/J/83/7//o/+r/FwASAPL/DwAeAPL/6f8FAP3/8P/3//L/9P8MAA0A7//m//n////o/+b/GwA/ACAAGQBKAFIAOgBEADYAAAD1//f/1v/P//L/GgBGAE8AKQAsAE0ANAAGAPH/5P/q//H/3f/t/wUA3P/Q//X/2P/B//7/EADv//X/5P/B/+X/BQD8/xQAGwD4/wYALwA6AEAAJQDu//r/HQASABkAJgABAO7/8v/S/9D/BAASAP3/7//X/9P/8P8GABIAEADv/+v/FAAdAAsABADo/87/7/8UABYAGgAOAO7/+f8aABYADwAaABUAAgD//wUACAABAPb/9f/z//D/AwAbABEA/P/5//3/CQAYAA4A9//z//v/+//7//3/+v/7/wAAAQADAAcACAABAPb/8/8AABEAGgAbABMABQAGABMAFQALAAQA/v/4//r///8BAAEAAgAAAP/////+//z/+f/3//n//P/+/wEAAQD9//v///8AAAEAAwACAAAAAQABAAAAAAAAAAAA", + "AAACAAcACwANAAAA7v/2/wkAFwAfAAMA3v/3/xsADQAJACMAKAAhAAoA6P/o/wIAEAAkAD8AQAAwABwACgAGAPz/6f/1/wwA7f+z/7D/0f/J/6r/uf/V/7v/jf+S/9L/KQBSADIAGQAtACgA/f/s//3/IQA9AC4ACQD2/+r///8wABwA2v/c//b/8P8FAB4ABAD6/xEAHQA9AFYAMgAgAEgAUAA5ADcAOQBBADkA9//J/+L/7//z/xoADgDW/93/4v+j/4j/r//S/+f/zP+J/4v/vP+8/77/6f8UADoAKADc/9r/GgAMANv/7P8ZABcA3//O/xsALADS/+j/XQBOAP7/EABOAHMAcABVAHQAkQAoAMn/EQA9AND/o/8iAHkAFgCQ/6X/PQBrAIv/yP6g/58Awf/f/sX/TwCI/4D/OgAzAMv/mP+b/yYAkQAbALz/DgBTAC8Az/+8/0wAeADU/8H/KgDQ/3D/4/87ACwAOgAiANb/4v9GAFkA4/+r/xwARADg//7/cQA0ANH/FQBoACwA2f8oAKYAQgCE/6X/AQDJ/63/3v/1/xwADgCx/+b/YwAKAIn/yP8UAOT/uf/W/9z/lP+D//b/GwCn/5L/FgBWAP3/o//z/2kACwCW/+j/8P+E/+//cgD0/7b/QgBiABgAJwBNAD4ABQCv/7D/NgBuABAA8v8tAPz/qP/l/zQAIQALAOX/xP81AJYAKQDc/yUANQAWAD0AWgA/ABYABQBBAHcAJwDQ/xoAnQCVAPv/p//4/0YAUwBeACIA3P8HAA8A3P8DAOv/Yf97/w0AFwC9/3T/hv/o/9//rP8NABIAYv9b/9r/y/+S/5H/j/+0/9//w/+c/6P/1P+7/yf/S/8nANb/Dv/V/5oA5/+K/xsAZwBoADEArv+7/0gAJACX/8f/UwA/AB4AoQC9AD0AjAD8AHsAIgDQAKgAqP/y/8sAqwB/ANQA3wB6ACIANABPAA4ALACZADgA1v9HACoAe//L/4wAWQCZ/2P/0v/l/2r/f//2/+P/3P/k/4L/sv8YAHT/Tv8qAO3/Kf+G//b/0f9p/8/+M/9CANH/WP+8ABkBcf9A/54AuAA0AJ4AagAb/1b/wgDH/979Av+qAPP/xf83APL+E/94AfgAef4q/+wAVgDq/38A3P+y/6ABrgHa/q3+QwH/AD//t/+u/0r/QAHKAGX9Lv+rAgn/gfsj/7EB6P6r/AX+MwKeBKAAQP+9CHkM9Pwb7333OwheDv8MVgn7/TfvuvATB8EVAQWn7dD2TxA3CRfm+Nzr/U8dRBb5+A/vWAHNDSkBhPanAzAS1Aho8/3s4/rkCI0Fwvmk97/97v+T/fj9zQPDCIgBHfFC72kESxQYCVT3svkOB/MGMvoA+DoIyxLAA7fw5ffpCeUE4PEH8goEBAsE/zT12fweCAcDkfbh+aQIowusASj9pAKOBaH/R/lS+2ECTgUHA7sB8ALtATn9pPk8+6//ugB9/I35kf2SA0AEbQFBAD8B7AJ/A68BiQB7AhMDhP9v/LX8Fv4e/8P/aADIAZIBxP3A++j/eQNKAHf8Rf7dAAP/qvxU/nwBVgIrAS8AgAD5AOz/wv6x//UAdgDp/54AUgEwAdz/Z/0e/fn/WQI9AhwBUgAEAG//K/4Z/mUAJgL5ACz/L//G/3n/Iv91/7v/wv8AAF4AMgGPAg0Cnf5g/En+4gDPAML//P+dALj/5v1//g8C3AONAYX/+gD7AloCBQCB/p7+Gf8H/3H/xQAhAdf/BP+B/6r/Xv8/ALwB1wHzAFsArP8P/5b/PgCs/wf/Cf+i/lL+Rv9mAHQAGwAxANIAEwGw/wv+qf7t/0b/0P6NAB8C5AHgAHT/X/4o/40AQwA5/0D/4/8MAKr/Q/9a/5f/V/8Q/0D/VP9M/+3/1gASAaEA7v+I/+j/CQDj/mX+QAC8AUkAa/5C/3QB6AFHAOH+Q/9IAHEAEADD/53/5v9IACsAFAA4ABIALwDKALgASgC0AAkBgQBQAMQAAAH4AKMA2/9f/2v/aP+U/0IAswB/ACwALQCGAL4AWgDo/yIAeQAjAIr/aP+t/+D/yP+A/2n/zv9KAEoAHgBMAHsAUwAdANn/ef97/8f/fv/j/hz/6f8eAOf/LgB9AO7/JP83/9v/KQD9/9f/FwBLAOr/lv/Z/9j/cv/J/4oAfQAMAP7/1P9c/z7/of/v/9z/6/9/AN0AhgBHAH4AbAD3/8D/5/8YAO7/Xv86/+b/YgAoAB8AkACBALn/Vf/U/1UAOADn/+j/RQCIABkAYf9z/w0AKwAOAE8AmgCMAEIA/v8lAIYAYQDd/7v/7v/9/7n/Zf+E/9X/ov9h/9n/cQBnAAAAuv/p/2QAaQDo/7b/4//b/7v/3v8iAFsAawASAIH/fv8sAJcAOwC9/6D/tv/o/yEAKQA9AHwAcQApADgAZwA8APb/3f/i/wUALAAcAPf/BAAmABcA2P+6/+f/DQDo/9L/EABLAD0ABQDe/+P/6P+s/3//yP8cAPf/uf/q/1YAbwAJAJ3/xf9hALQAWgDP/8f/GwAcAN3/6/8jAC4AJwAkACQAOgA2AA0AFAAoAPX/x//U/+T/2v+q/3H/pv8eABUAlf9W/4r/2//0/9D/uP++/8r/+/8uABAA6f8cAEsAJwACAB8AUABQAB0ACAApABkAsP9x/8b/UABvACEA7P8GADAAKgDw/7b/vP/j/+L/zf++/7v/7f8yABIAw//T/xoAVgCaAKAAKgDC/+L/NgBNABIAyv/g/0wAhgBtAE0ANQANAPP/+/8gAE4ASwAIAOz/MQB0AFUAAQDM/9P/+/8cACYAJwAoAA8A1v+0/8r/1/+o/4X/p//d/+//3v/N/+T/DgD0/6v/r//4/wkA5v///0kAWAAeAPX/DgA9AEgAHQDV/6//3v80AE8AMQAmADAAFQD5/xIAIADh/6T/0P8wAEIAAgDn/w8ADADC/6r/7f8ZAAMAAAAcAP//rv+d/9r/AQDo/8T/vP/W/wgAJAAVABgASQBcACgA/v8gAEYAIwDt/wEANgAcAM7/x/8UAEkALwD7//j/IgAzABAA7f/q/+j/4P/x/xcAKwAfAAgAAAAJAA0ABAD9//3/9v/f/8v/1v8GAC4AIgAAAAYAKAAiAOv/z//9/zUAKwD3/+T/AAAdABwACQD+/wYAFQAOAOz/1//o//r/8f/t////CQDz/9P/zf/q////6f/U/+f//f/7//n/+f/2/wAADQADAP7/DQAUAAwABgD///3//v/1/wIAKwAwAA4ADQAYAPT/4P8MACYACwAAAB0AMAAoAB0AGgAVAAcA///+//j/8v/z/+//4P/d/+j/6//f/9b/6f8FAPj/2P/w/yUAIADz/+P/+P8BAOn/2////x8ABwDz/wUADwAHAAcAAAD//xMAGAAOABoAIwAUAAsACAAAABAAGQD5/+//HQAuAAQA8f8KAA8A8v/g/+X/9P8RAC0ALQAkACEAFgAKAAEA8P/8/zgAQgDx/8X/8/8ZABUAEAAIAPH/4P/s/xYAMAAaAP//AwAIAAEAAQAHABEAFQABAPD//P8FAPj/6v/d/97/9/8FAPb/4//i//X/CAAAAPP/+v/4/+r/5v/n/+r/9P/y//X/FAAoAB8AHgAfABMAEgASAAUADgA0ADgACADn//z/CwDw/+z/EwAdAAYADwAeAPn/0v/Z/+z/4//S/+f/IAAuAAQA9P8BAPj/8v8DAAcADAAdABYAAgAHAPz/1v/X//X/9v/3/xUAGQAEAAkADAD6/wcAGgD6/+j/DQAjAAgA5f/d//D/6//g/x8AUAD+/8H/AwAzACoALwAjACEARwA0AAIA/f/R/6n/9/8dAOL//f8+ACYAHQAxAB4AKwA+AB0AKwA3AO7/8/8yAOf/nP/U/+j/3/8VAAcAw//c/wEA9//6/+P/2P8UABIA4v8UABsAtP+0//7/BAAbADIAEAA0AFcA+f/d/xcA6f/n/1EALwDP//H/7/+s/7H/vf/a/y4AJQACAFoAaAABAAMADQDH//j/QQDd/6D/6//7/+z/EQD5/7//4P82AHgAbAAJANL/6f/h/9//DQAEAOL/+f8EAAYAIQD1/7D/0P/h/7b/8/9MABEAwf/k/yIAJgD9/+j/BQABANf/BQA6APv/8/9eAGUAJwBLAF0AFQD3/+b/nf+D/6H/vP8HAD0A9P/I/xgASABDAFEAKwDp/wAAMwAuAP//s/+c/9n/2v/Q/0UAbwD1/9v/FgDv/9z/+v/C/5n/0v8SAEwANAC0/8D/MQD8/9L/RABHAPT/FgAiAP//FgD2/9D/GQARAM7/AwDs/4j/3/8UAI3/nv8yACsADwAxABcABAD///v/bACIAMT/pf89AA8Ax/8cAAoAy/8HABcAFgBWAB0Azf/5/8v/if8EAC8Ay/8GAFwAEgD6/yMAFAAqADcA+f8EAB4A1//g/x4A0f+q/xIALwAAABkANAAfAAQA7P/3/wEA0//m/zsAGwDZ/wgAFwDb/9v/9/8TAEwAOQD1/x0APQD4//X/FgDU/7b//v8ZAPz/7//g/9//6f/m/w0ANwADAOT/IgAlAOj/7//+/97/5P/0/9v/5f8OAAoA/f8CAPn/BQAmABsABAARAAcA4f/j//j/7f/j/+7/8//w//j/BgAFAOz/7f8UAB0A+f/z/wYA/v/0/wEABwAGAAcABwAHAAcAAgAFAAMA8f/0/xEAFQABAAEABAD7/wIACQD8//z/CwAGAAAADAARAAQA/P/+/wAAAAABAAQAAwD///7//v/5//r/AwABAPv//f8AAAIABQACAP3//v8BAAEAAQACAAEAAAAAAAAAAAAAAAAA", + "AAADAAUACgASABMAEgAMAAIACAAJAOj/xP+2/7n/2v/4/+b/1v/g/93/4//j/6z/l//e/wQA3//c//b/+/8PACEA/P/b////MwA0AAsA5v/Q/8f/3f/6//P/8P8WACMABgD///z/4P/f//j/BwAfADMAEwDp/+H/2//M/8z/3f8GADsAOADz/8z/7f8CAOP/4f8bADwAFgDZ/8n/9/8pAC8AKgAtABoAFAA3AD4AGwAdADkALAAPABMAJgAiAAUA7f/r/wgARABLAPb/3P8VAPP/yP87AHwAGAADACgA5P/o/1EAOAAGACMAw/9T/8v/QAD+/83/5f8VAE0AAwCG/8//GQCl/6D/RgBIAKj/Xf/D/34AYwBR/0j/fgCLAGn/cP+PALAAjv8F/9v/bQD+/9n/zv9E/63/hQDE/2z/2QCVAKn+N/8iAf8AQgCiAKwACwAhAOoACgEnAJj/CgB7AJsAngDk/zj/CgDsAFkArv/T/wsAFgDs/6r/+f98AEIAuf+k/+f/DADG/23/kf/f//n/FwAeAOD/qv+f/9D/BwCg/xb/dP/4/73/lP/C/67/6//AANgA8f+s/2kAhwCe/13/HgB1AGsAzAC1ALf/NP9+/5D/Y/9Y/03/Xv+S/6n/6v8/APn/tf9OAHgAgf9s/4gApAAUAHYAmgDm/yoA6QBFAIL/DwBrALX/Lf+O/0YAgwBfAJwAywA5AMT/EQDHAHQB9wBq/zD/QQBcABwAcwApAND/OACg/4f+bP/1AKMAnf+2/20AUQBC//v+KgC9ALT/A/8OAFIBkACl/rj+ZwBnACb/Lf/r/x8A2/8L/6P+iv/q/9/+Zv4T/+H/kQDPAHoAMwDb/2j/if/a/+b/DwD2/8L/RwBdAJj/DwBKARsByQCZAboBuABBAL4AIAF5AJn/8/9XAIH/Y/9jAEUAnv/l//b/p//L/8n/4v98AFIA0f9ZAMsAnADZANoAHAC9/6P/V/9p/5n/Nv+f/p7+RP9e/6j+1f5f/23+2/0T/ywA4ADeAFD/RAAMBNwCJP40/v//MADWA0MIrQd7BfwBt/sK/K4G6g3NBg/3+u0g9kEItxB2BRT0tvVbBzYDWObi5mwRZiYzB1PkO+hxAvcLj/1z+WQQeRsiACrlf++eB8UJDvyW96v8X/si90z96AavBwACLfn88kL8Hg04DQ3/OvrGA9gKnwHy9C7/PxSyDK3yofJABpwGHfWG78X9mAtaBaT2i/oiC3gLCPxj9loBPwqFAyb6hv/yCHoEjfuZ/TIE4wM6/sn7qf80AqH9YPjg9yb6Wv76ASUBev9FAEkAzP8KAX0B+wBCAt8DbgPDAakA+f+I/Sf7hv0kAWIAc/+sADT/rPxL/YX+QP/u/xz+D/1kAZYESQFp/jMAQQGt/8P/yAHuAT4ADf9M/oL+9P8+//j8xv6uAkQCWP82/v/+cAEWA4sBMAG4A/UCKv/G/k8AaP+P/g8A2gAO/4P92P73AK8AMv92/zkBVwI+AhUCpgFy/wb+FgEKBGMB0f5fAUsC7/3s+8P/4AIyAWX+T/5bAPsBswEwAJT/ZQCUAHP/4P5Z/8b/SwDeAE0AX/94AKwCIALU/nH9CP9S/279E/2A/uj+iP65/qj+I/6A/un/RwGuAcwAWP+c/v/+2f8sAGz/jP4X/4MAGQH7AB8BWgF1AWABwQA5AHgAfwDd/4H/UP/k/sP+9v57/5EAGAEDAC7/jQANAukAqf50/iIATgG1AA3/fv7s//UA6//Y/kb/EQB6AEIACP9O/qD/bwHlAYkBQgFUAWcBYwCR/gr+Mv9IAI8AkgCbAKAAcwDy/6n/LwD1APUARAC4/yr/5f3j/Mj9o/8JAP3+zf42AGwBSAH+AHsBrQHXALv//P7i/n//5/+c/9f/3gAYAVsAPADgAPAAPQDu/4kAMAH1ACsAxf8AAFYAMwB+/9T+/v7o/3kAAgBY/3D/qf85/wj/3v+tAIoAKQA4AEIA2v9l/2b/2f9RAHkAWgBFAF4ARgCp/xT/gP/CAFABggC6/w8AfwAVAKv/DwCRAFMAiv/v/vb+eP/V/8L/oP+u/5L/N/9F/wgAvQCuADIADgBKACMAX/8W/wkADwG+ALL/Zf/y/10ATgBQAJ0AtgBGALn/tv8/AIwABQBl/67/SQAcAKD/yf84ACcAtP9+/+v/tgD4AFoAtv+7/xEAQwBdAGsARADd/13/JP+J/0sAqQBPALH/af+o/x4AQwAVAAUAFgD///b/QAB6AEQA2P+T/5P/wP+x/z//Fv+9/2sARQDk/yUAngCiAGcASgBGAC0A1P90/53/IQAkALL/lv/o/ycAHwD6/wMAQQA7ALr/a//T/2oAhwBOAEQAcgBoAA4A8v9NAGQAz/9t/97/VQAeAMH/4P89ADcAo/8i/17/8P8DALD/hf+W/9L/FgATAOz/HACGAKMAaAAdAPn/FABUAGUAFwC5/8//RgB0ACcA1/+4/6r/yv8qAGsAPgDQ/5L/vv8UADkAOwBJACwAxv91/4b/xf/u/+7/wv+L/6L/HACBAHgARgBEAE8ANAAjAD0AOgD0/6r/kv+3/wIAFQDO/7z/KwCGAHUAVwBMABYA2//w/0MAeQBQANX/aP9v/8r/5P+g/6f/JwBZAP3/1v8lAFQALQAZAEEATQDl/1b/Sf/G/yUADwDe/+3/EQD3/9T/DgBtAHkASAAkABYADwD1/7//p//M//P/6v/H/8H/6//4/8j/z/9DAIoAPgDg//n/RgAyAMH/jf/e/z0AFACb/6D/LQA7AJr/dP8WAHgATQA2AEcAKADp/8b/2f8OABUA1/+3/+j/DADv/+//UQCkAGcAz/+F/+j/hAB/APr/4f8XANv/hv+r/+b/8f/x/7j/gf/X/1IAYABPAF8AWgBNAFIARAAjAP7/5P/v/+n/nv91/7j////2/9n/7f8fACoA+//S/+D/CAAUAPv/8v8OAA8A8f8AABwAAADv/w8AEQDr/97/7/8DAAUA+v8OACcA+f+2/8P/BgAuAB0A7f/5/1IAYgABAOT/PQBWAPr/0v8UADoACADQ/8H/xv/S/87/t/+9/+P/7v/d/+D/BAAyAD4AEADv/w8AHQD3/+3/BQADAPj/AgAOAB0AIgAKAP//FQAYAAkAFAAdABIAFQAeABMADwAQAPr/9f8bAB4A5v/K/+7/FQATAOz/yf/Y//3/AQD5/wsAEADp/83/5v8NAAwA3/+0/7f/4f8EAAoAFQAtACsAEgANABcAFgAUABIAAwDy/+b/7v8GAAIA3//f//T/9f8PADoANAAgACMACQDy/xQAIwD8/+r/6P/P/8f/3f/k/93/2v/h//L/AgAOAB4AFwD4/+3//P/8//D/8P8AABUAHAASAAQA/P8DABcAHAASABcALAAzABAA2//V/wYAIQD7/8b/wf/f/+n/3//+/y0AFwDc/9T/7P/6/wIA/v/z/wEAGAAdABAA9//s/wEADQD4//D/CgAfACEAHQAfACgAKAAUAP3//P8OABUAAwDz/+7/6//s//D/7v/q/+T/2f/d/+n/4v/o/xYAKwD//9b/3f/u/+X/1P/R/9j/5//9/wMA/P8CABYAIwAfABIAFAAdAAsA+f8MACAAGwARAAQAAAASABYADQAhACgABgD7/wYAAwASACcADgDr/+j/9P8CAP//8P8CABUA9f/l/wIAAgD1/wYAEAAZADgANAAKAP3/AQAAAAkA9v/O/+H/BgDv/9r/9P8IABMAJgAcAAYAAgD5/+P/yf+q/7r/8v/o/7X/xP/k/+L/8v/+/+X/3//r/+n/8f/+/wYAHQAYAPz/DAAXAO7/6v8KAA8AGgATAOj/AAAuAP3/6f8jABcA9f8dACEACgAzADsA/P/h//D/EgA9ACcABgA7AFcAIAARABUA5v/e/wkACQD3//z//f8FABYAEQACAPj/AwBBAGAAHgDs//z/8v/o/xgAIwD1/+3/9v/p//P/9f/O/9L/7//P/87/FAAMAL//w//s/9v/y//i/+7/z/+l/73/DAARAN3/9f8aAPT/+f8/AD4ACgDy/9f/xv/b/+z/AwAlAP7/xv/9/1wAegBwAEIA/f8CAFkAmwCCAB8A0P/W//D/BwBOAGAA+v/X/yoANwAKAAgA2f+R/7v/DAAlADkAJADZ/8P/3//x/w8AFAD4/wkACgDV/+X/EwDy/9n/2v/E/+7/IwD0//D/EACX/z//zv9LADYAHAD2/8P/2P8GACsAOADh/53/5f8CANL/AQAdAMT/vP8QADoAXwBQAMv/jP/S/woAMABPACcADQAlABoAJwBhAEIA+//9/wkAJQBgADAAxf/E/9L/s//o/zYALwA9AGgAOADz/wIAMQA3AP//y//4/y8ADQD0/wMA6v/t/ysAKADv/97/3//3/ycAEgDX/+P/+P/S/8H/6P8QABYA6/+3/7r/2//5/xAA9//I/+L/EAAAAPr/BADK/6L/0f/c/7j/6f82ACkADwAjACkANABWAEMAAQDj/+T/6//9/wQA//8JAA4ACwAWABgAFgAnAB4ABAAXACkAFAAlAEcAKwAMABMABwDn/+H/9/8cACoAEQACAP//8v8CABwABAD3/xEACwD5/w0ACgDt//X/BgAHABQADwDz/+3/7v/n//f/BADw/+j/9//7//T/6//m//D/8P/l//3/HwAXAAYABgD///T/9//8/wQADQAFAPf/9v/5//z/AgABAAIACQADAP//DAAIAPL/8/8BAAAABQAPAAcA//8GAAcAAAADAAsACAD///7/AwAFAAEAAAABAAAAAQACAAEAAQACAAAA/v8AAAIAAQABAAEAAAD//wAAAQAAAAAA" +] as const; diff --git a/src/ui-transitions.ts b/src/ui-transitions.ts new file mode 100644 index 0000000..5b239ae --- /dev/null +++ b/src/ui-transitions.ts @@ -0,0 +1,104 @@ +const enterEase = "cubic-bezier(0.22, 1, 0.36, 1)"; +const exitEase = "cubic-bezier(0.4, 0, 1, 1)"; + +/** Owns the visible lifetime, including a close that interrupts an opening. */ +export class SurfaceTransition { + private animations: Animation[] = []; + private revision = 0; + + constructor( + private root: HTMLElement, + private panel?: HTMLElement, + private enterDuration = 300, + private exitDuration = 200, + ) {} + + show(reduced: boolean) { + this.run(true, reduced); + } + + hide(reduced: boolean, finished: () => void = () => {}) { + this.run(false, reduced, finished); + } + + finish() { + this.animations.forEach((animation) => animation.finish()); + } + + dispose() { + this.revision++; + this.animations.forEach((animation) => animation.cancel()); + this.animations = []; + } + + private run(show: boolean, reduced: boolean, finished?: () => void) { + const revision = ++this.revision; + const hidden = this.root.hidden; + const opacity = hidden ? "0" : getComputedStyle(this.root).opacity; + const transform = this.panel + ? hidden + ? "translateY(12px)" + : getComputedStyle(this.panel).transform + : undefined; + this.animations.forEach((animation) => animation.cancel()); + this.animations = []; + this.root.hidden = false; + this.root.dataset.transition = show ? "opening" : "closing"; + const complete = () => { + if (revision !== this.revision) return; + this.root.hidden = !show; + this.root.dataset.transition = show ? "open" : "closed"; + this.animations.forEach((animation) => animation.cancel()); + this.animations = []; + finished?.(); + }; + if (reduced || (!show && hidden)) { + complete(); + return; + } + const options: KeyframeAnimationOptions = { + duration: show ? this.enterDuration : this.exitDuration, + easing: show ? enterEase : exitEase, + fill: "both", + }; + const fade = this.root.animate( + [{ opacity }, { opacity: show ? 1 : 0 }], + options, + ); + this.animations.push(fade); + if (this.panel) { + this.animations.push( + this.panel.animate( + [ + { transform }, + { transform: show ? "translateY(0)" : "translateY(8px)" }, + ], + options, + ), + ); + } + void fade.finished.then(complete).catch(() => {}); + } +} + +export class ContentTransition { + private animation?: Animation; + + reveal(element: HTMLElement, reduced: boolean) { + const opacity = + this.animation?.playState === "running" + ? getComputedStyle(element).opacity + : "0.35"; + this.cancel(); + if (!reduced) + this.animation = element.animate([{ opacity }, { opacity: 1 }], { + duration: 150, + easing: enterEase, + }); + } + + cancel() { + this.animation?.cancel(); + this.animation = undefined; + } +} diff --git a/src/viewer-camera.ts b/src/viewer-camera.ts new file mode 100644 index 0000000..667e88e --- /dev/null +++ b/src/viewer-camera.ts @@ -0,0 +1,92 @@ +import * as THREE from "three"; + +const shortestAngle = (angle: number) => + Math.atan2(Math.sin(angle), Math.cos(angle)); + +// OrbitControls records the requested pose; this camera is what is rendered. +// Spherical interpolation avoids cutting through the model on a large reset. +export class ViewerCameraMotion { + readonly focus = new THREE.Vector3(); + private pose = new THREE.Spherical(); + private desired = new THREE.Spherical(); + private offset = new THREE.Vector3(); + private resetFrom = new THREE.Spherical(); + private resetFocus = new THREE.Vector3(); + private elapsed = 0; + resetting = false; + + constructor(private camera: THREE.PerspectiveCamera) {} + + snap(goal: THREE.PerspectiveCamera, target: THREE.Vector3) { + this.resetting = false; + this.focus.copy(target); + this.pose.setFromVector3(this.offset.copy(goal.position).sub(target)); + this.apply(); + } + + reset() { + this.resetFrom.copy(this.pose); + this.resetFocus.copy(this.focus); + this.elapsed = 0; + this.resetting = true; + } + + interruptReset(goal: THREE.PerspectiveCamera, target: THREE.Vector3) { + if (!this.resetting) return; + this.resetting = false; + goal.position.copy(this.camera.position); + target.copy(this.focus); + goal.lookAt(target); + } + + update( + goal: THREE.PerspectiveCamera, + target: THREE.Vector3, + dt: number, + reduced = false, + ) { + if (reduced) { + this.snap(goal, target); + return; + } + this.desired.setFromVector3(this.offset.copy(goal.position).sub(target)); + if (this.resetting) { + this.elapsed += Math.max(0, dt); + const t = Math.min(1, this.elapsed / 0.56); + const p = 1 - (1 - t) ** 3; + this.focus.lerpVectors(this.resetFocus, target, p); + this.interpolate(this.resetFrom, this.desired, p, p); + if (t === 1) this.resetting = false; + } else { + const pan = 1 - Math.exp(-13 * Math.max(0, dt)); + const orbit = 1 - Math.exp(-9 * Math.max(0, dt)); + const zoom = 1 - Math.exp(-15 * Math.max(0, dt)); + this.focus.lerp(target, pan); + this.interpolate(this.pose, this.desired, orbit, zoom); + if (this.focus.distanceToSquared(target) < 1e-10) this.focus.copy(target); + } + this.apply(); + } + + private interpolate( + from: THREE.Spherical, + to: THREE.Spherical, + angle: number, + radius: number, + ) { + // Interpolate logarithmic distance so zoom in and zoom out feel symmetric. + this.pose.radius = Math.exp( + THREE.MathUtils.lerp(Math.log(from.radius), Math.log(to.radius), radius), + ); + this.pose.phi = THREE.MathUtils.lerp(from.phi, to.phi, angle); + this.pose.theta = from.theta + shortestAngle(to.theta - from.theta) * angle; + this.pose.makeSafe(); + } + + private apply() { + this.camera.position + .copy(this.offset.setFromSpherical(this.pose)) + .add(this.focus); + this.camera.lookAt(this.focus); + } +} diff --git a/src/viewport-layout.ts b/src/viewport-layout.ts new file mode 100644 index 0000000..51fcf38 --- /dev/null +++ b/src/viewport-layout.ts @@ -0,0 +1,38 @@ +/** Reference coordinates remain exact during the film and at 1920 × 1080. */ +export function openingLayout(width: number, height: number) { + width = Math.max(1, width); height = Math.max(1, height); + const scale = Math.min(height / 1080, width / 1280); + return { width: width / scale, height: height / scale, scale, kind: "opening" as const }; +} +export function viewportLayout(width: number, height: number, coarse: boolean, cinematic = false) { + width = Math.max(1, width); + height = Math.max(1, height); + if (cinematic) return { + width: 1920, height: 1080, scale: Math.min(width / 1920, height / 1080), + kind: "cinematic" as const, + }; + const portrait = width / height < 1.05; + const compact = portrait || width < 1100 || (coarse && height < 600); + const scale = compact ? 1 : height / 1080; + return { width: width / scale, height: height / scale, scale, + kind: portrait ? "portrait" as const : compact ? "compact" as const : "desktop" as const }; +} + +/** Preserve the long-lens perspective. Reframe only the camera, never the card. */ +export function archiveFraming(width: number, height: number, span: number, detail: number, compact: boolean) { + const aspect = width / height; + const portrait = aspect < 1.05; + const baseSpan = span + (5.9 - span) * detail; + const portraitDetailSpan = Math.max(6.3 / aspect, 3.7 * height / Math.max(100, 0.54 * height - 156)); + const viewSpan = portrait + ? Math.max(baseSpan, 8.4 / aspect + (portraitDetailSpan - 8.4 / aspect) * detail) + : Math.max(baseSpan, baseSpan * (16 / 9) / aspect); + return { + span: viewSpan, + portrait, + // Portrait selection is deliberately above its title and navigation. + previewY: portrait ? 0.36 : 0.5, + detailX: portrait ? 0.5 : compact ? 0.27 : 550 / 1920, + detailY: portrait ? 0.27 + 34 / height : compact ? 0.49 : 560 / 1080, + }; +} diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts new file mode 100644 index 0000000..11f02fe --- /dev/null +++ b/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/src/wallpaper-background.ts b/src/wallpaper-background.ts new file mode 100644 index 0000000..975d67c --- /dev/null +++ b/src/wallpaper-background.ts @@ -0,0 +1,72 @@ +import type { WallpaperProperties } from "./wallpaper"; +import { wallpaperImageUrl } from "./wallpaper-image-url"; + +/** A DOM image remains available after the WebGL contexts have been released. */ +export class WallpaperBackground { + private path = ""; + private ticket = 0; + private enabled = false; + private released = false; + private reduced = false; + private image?: HTMLImageElement; + private failed = false; + private root = document.createElement("div"); + constructor(private parent: HTMLElement, private notify: (message: string) => void) { + this.root.className = "wallpaper-background"; + this.root.setAttribute("aria-hidden", "true"); + // Above the opaque WebGL canvas so the image can fade while it restarts, + // beneath the atmosphere and all normal interface elements. + parent.querySelector("#three-scene")!.after(this.root); + } + update(properties: WallpaperProperties, released: boolean, reduced: boolean, retry = false) { + this.enabled = properties.customwallpaper?.value === true; + this.released = released; + this.reduced = reduced; + const amount = Number(properties.customwallpapermask?.value ?? 100); + const range = Number.isFinite(amount) ? Math.max(0, Math.min(100, amount)) : 100; + this.parent.style.setProperty("--wallpaper-mask-range", `${range}%`); + this.parent.style.setProperty("--wallpaper-mask-visible", range === 0 ? "0" : "1"); + this.root.style.transitionDuration = reduced ? "0s" : "650ms"; + const path = String(properties.customwallpaperfile?.value || ""); + if (path !== this.path || (retry && this.failed && path)) { + this.path = path; + this.failed = false; + const ticket = ++this.ticket; + if (!path) { this.image?.remove(); this.image = undefined; } + else { + const next = new Image(); + next.alt = ""; + next.onload = () => { + if (ticket !== this.ticket) return; + const old = this.image; + this.image = next; + next.style.opacity = "0"; + this.root.append(next); + requestAnimationFrame(() => requestAnimationFrame(() => { + if (ticket !== this.ticket) { next.remove(); return; } + next.style.opacity = "1"; + if (old) { + old.style.opacity = "0"; + setTimeout(() => old.remove(), this.reduced ? 0 : 700); + } + })); + this.paint(); + }; + next.onerror = () => { + if (ticket !== this.ticket) return; + this.failed = true; + this.image?.remove(); this.image = undefined; this.paint(); + this.notify("自定义壁纸无法读取,请在 Wallpaper Engine 属性中重新选择图片。"); + }; + next.src = wallpaperImageUrl(path); + } + } + this.paint(); + } + private paint() { + this.parent.dataset.customWallpaperVisible = String(this.enabled && this.released && Boolean(this.image)); + this.root.style.opacity = this.enabled && this.released && this.image ? "1" : "0"; + this.root.dataset.ready = String(Boolean(this.image)); + this.root.dataset.reduced = String(this.reduced); + } +} diff --git a/src/wallpaper-effects.css b/src/wallpaper-effects.css new file mode 100644 index 0000000..2e53168 --- /dev/null +++ b/src/wallpaper-effects.css @@ -0,0 +1,80 @@ +/* Every panel occupies its own tangent on the same radial HUD surface. */ +#stage .hud-surface { + /* Motion is driven by the projection controller. In particular, the global + reduced-motion duration must not create an implicit `all` transition that + retains the previous matrix while measuring the unprojected layout. */ + transition-property: opacity, color, background-color, border-color !important; +} +#stage[data-hud-depth="true"] .hud-surface { + transform-origin: 0 0 !important; + transform: var(--hud-projection, none) !important; +} +.screen-finish-defs { position: absolute; width: 0; height: 0; overflow: hidden; pointer-events: none; } +.system-footer .three-toggle { margin-left: 12px; padding: 2px 0 2px 14px; border-left: 1px solid currentColor; pointer-events: auto; white-space: nowrap; } +.system-footer .three-toggle[aria-pressed="false"] { opacity: .65; } +.wallpaper-background { position: absolute; inset: 0; opacity: 0; pointer-events: none; overflow: hidden; transition: opacity 650ms ease; } +.wallpaper-background img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; transition: opacity 650ms ease; } +.wallpaper-background[data-reduced="true"] img { transition: none; } +#stage:is([data-three-state="closing"], [data-three-state="off"], [data-three-state="loading"]) :is(.relay-entry, [data-action="model-viewer"], .inspection-overlay, .object-caption, #inspection-marks, #inspection-text, #hover-label) { visibility: hidden; pointer-events: none; } +#stage:is([data-three-state="closing"], [data-three-state="loading"]) #three-scene { pointer-events: none; } +[data-ui-frost="true"] .frost-surface { isolation: isolate; } +.wb-time, .wb-today, .relay-heading, .relay-actions { position: relative; } +.system-footer > .frost-surface { position: relative; } +.frost-surface::after { + content: ""; + position: absolute; + inset: -12px -16px; + z-index: -1; + pointer-events: none; + opacity: 0; + border-radius: 3px; + transition: opacity 220ms ease; +} +[data-ui-frost="true"] .frost-surface::after { + opacity: 1; + background: rgba(var(--theme-paper-rgb, 234, 229, 225), var(--frost-tint, .35)); + -webkit-backdrop-filter: blur(var(--frost-blur, 13px)) saturate(.8); + backdrop-filter: blur(var(--frost-blur, 13px)) saturate(.8); + box-shadow: inset 0 1px 0 rgba(255,255,255,.12), 0 1px 0 rgba(0,0,0,.08); +} +.wb-nav .frost-surface::after { inset: 0 -6px; } +#stage[data-super-performance="true"] .frost-surface::after { + -webkit-backdrop-filter: none; + backdrop-filter: none; +} +.system-footer .frost-surface::after { inset: -8px -12px; } +.relay-actions.frost-surface::after { left: auto; width: 240px; } +.reduce-motion .frost-surface::after { transition: none; } +@media(max-width:700px) { [data-ui-frost="true"] .wb-module { position: relative; } } + +/* Scale only the image's atmosphere; 100 preserves the original gradients. */ +#stage[data-custom-wallpaper-visible="true"] .scene-atmosphere { + background-size: 100% var(--wallpaper-mask-range), 100% var(--wallpaper-mask-range), 100% 100%; + background-position: top, bottom, center; + background-repeat: no-repeat; + opacity: var(--wallpaper-mask-visible) !important; + transition: none; +} +#stage[data-custom-wallpaper-visible="true"] .archive-atmosphere::before, +#stage[data-custom-wallpaper-visible="true"] .archive-atmosphere::after { + background-size: 100% var(--wallpaper-mask-range), 100% var(--wallpaper-mask-range), 100% 100%; + background-position: top, bottom, center; + background-repeat: no-repeat; +} + +#stage[data-custom-wallpaper-visible="true"] .archive-atmosphere::before { background-size: 100% var(--wallpaper-mask-range), 100% 100%; background-position: top, center; } +[data-dark-surface="true"] #stage[data-custom-wallpaper-visible="true"] .archive-atmosphere::before { background-size: 100% var(--wallpaper-mask-range), 100% var(--wallpaper-mask-range), 100% 100%; background-position: top, bottom, center; } + + +/* Text contrast over photography; keep the existing glass treatment. */ +#stage[data-custom-wallpaper-visible="true"] .system-footer { color: var(--theme-ink, #242622); } +#stage[data-custom-wallpaper-visible="true"] .system-footer .three-toggle[aria-pressed="false"] { opacity: 1; } + +#stage[data-custom-wallpaper-visible="true"] :is(.wb-nav button, .wb-task, .wb-muted, .wb-task-count, .wb-storage, .wb-media small, .wb-timer-label, .system-nav button, .relay-entry) { + color: var(--theme-ink, #242622); +} +#stage[data-custom-wallpaper-visible="true"] :is(.wb-nav button, .wb-task, .system-nav button, .system-footer button, .relay-entry):hover { + color: var(--theme-ink, #242622); +} +#clock { display: inline-flex; align-items: baseline; font-variant-numeric: tabular-nums; white-space: nowrap; } +#clock > span { flex: none; } diff --git a/src/wallpaper-effects.ts b/src/wallpaper-effects.ts new file mode 100644 index 0000000..0b95dfd --- /dev/null +++ b/src/wallpaper-effects.ts @@ -0,0 +1,93 @@ +import { wallpaperHost, type WallpaperProperties } from "./wallpaper"; +import type { ArchiveScene } from "./scene"; +import { HudProjection } from "./hud-projection"; +import { ScreenFinish } from "./screen-finish"; +import "./wallpaper-effects.css"; +import "./wallpaper-insets.css"; + +export function wallpaperInsets(props: WallpaperProperties) { + const pixels = (side: string) => { + const value = props[`uimargin${side}`]?.value; + return typeof value === "number" && Number.isFinite(value) ? Math.max(-300, Math.min(300, value)) : 0; + }; + return { top: pixels("top"), right: pixels("right"), bottom: pixels("bottom"), left: pixels("left") }; +} + +export function effectOptions(props: WallpaperProperties) { + const flag = (key: string) => props[key]?.value === true; + const amount = (key: string, fallback: number) => { + const value = props[key]?.value; + return typeof value === "number" && Number.isFinite(value) ? Math.max(0, Math.min(100, value)) / 100 : fallback; + }; + return { parallax: flag("hudparallax"), tracking: props.hudtracking?.value !== false, depth: amount("huddepth", .2), frost: flag("uifrost"), frostStrength: amount("uifroststrength", .55), screen: flag("screenfinish"), grain: amount("screengrain", .2), grainSize: amount("screengrainsize", .2), fringe: amount("screenfringe", .2), vignette: amount("screenvignette", .2) }; +} + +/** DOM-only pointer depth. The renderer and its camera never receive this input. */ +export class WallpaperEffects { + private props: WallpaperProperties = {}; + private pointer = { x: 0, y: 0 }; + private current = { x: 0, y: 0 }; + private depth = 0; + private last = 0; + private insetSignature = ""; + private projection: HudProjection; + private finish: ScreenFinish; + constructor(private stage: HTMLElement, private scene: () => ArchiveScene | undefined) { + this.projection = new HudProjection(stage); + this.finish = new ScreenFinish(stage); + Object.assign(this.props, wallpaperHost()?.properties ?? {}); + window.addEventListener("rhine-wallpaper-properties", event => { + Object.assign(this.props, (event as CustomEvent).detail); + this.projection.invalidate(); + }); + stage.addEventListener("pointermove", event => { + if (event.pointerType === "touch") return; + const rect = stage.getBoundingClientRect(); + this.pointer.x = Math.max(-1, Math.min(1, (event.clientX - rect.left) / rect.width * 2 - 1)); + this.pointer.y = Math.max(-1, Math.min(1, (event.clientY - rect.top) / rect.height * 2 - 1)); + }, { passive: true }); + const reset = () => { this.pointer.x = this.pointer.y = 0; }; + stage.addEventListener("pointerleave", reset); + window.addEventListener("blur", reset); + document.addEventListener("visibilitychange", reset); + stage.querySelectorAll(".brand, .system-nav, .system-footer > span, .system-footer > button, .wb-time, .wb-today, .wb-module, .wb-nav button, .archive-callout, .archive-counter, .column-navigation, .archive-hint, .detail-content, .back-button, .object-caption, .relay-heading, .relay-actions, .relay-entry").forEach(node => node.classList.add("frost-surface")); + } + update(time: number, reduced: boolean) { + const options = effectOptions(this.props); + const superPerformance = this.props.superperformance?.value === true; + this.stage.dataset.superPerformance = String(superPerformance); + const insets = wallpaperInsets(this.props), insetSignature = JSON.stringify(insets); + if (insetSignature !== this.insetSignature) { + this.insetSignature = insetSignature; + this.stage.dataset.uiInsets = String(Object.values(insets).some(value => value !== 0)); + for (const [side, value] of Object.entries(insets)) { + // Compensate for the reference-stage scale: a 60px slider remains 60px + // in the displayed wallpaper, rather than changing with monitor height. + this.stage.style.setProperty(`--ui-${side}`, `calc(${value}px / var(--stage-scale, 1))`); + } + this.projection.invalidate(); + } + const active = this.stage.dataset.mode !== "boot"; + const moving = options.parallax && options.tracking && !reduced && !this.stage.querySelector("#modal-root")?.childElementCount; + const dt = this.last ? Math.min(.1, Math.max(0, time - this.last)) : 0; + this.last = time; + const blend = reduced ? 1 : 1 - Math.exp(-dt * 7); + const tx = moving ? this.pointer.x : 0, ty = moving ? this.pointer.y : 0; + this.current.x += (tx - this.current.x) * blend; + this.current.y += (ty - this.current.y) * blend; + this.depth += ((options.parallax ? options.depth : 0) - this.depth) * blend; + this.projection.update(this.depth, this.current); + this.stage.dataset.hudTracking = String(moving); + this.stage.dataset.uiFrost = String(active && options.frost && options.frostStrength > 0); + this.stage.style.setProperty("--frost-blur", `${options.frostStrength * 24}px`); + this.stage.style.setProperty("--frost-tint", String(options.frostStrength * .65)); + const scene = this.scene(); + if (scene) { + // Wallpaper pointer depth belongs exclusively to the HUD, including when + // its switch is off. Explicit card dragging still uses the scene inputs. + scene.uiOnlyParallax = true; + scene.setSelectedIndexAccent(this.props.selectedindexaccent?.value === true); + } + this.finish.update(options.screen && !superPerformance, options.grain, options.fringe, options.vignette, options.grainSize, time, reduced); + } +} diff --git a/src/wallpaper-image-url.ts b/src/wallpaper-image-url.ts new file mode 100644 index 0000000..8442fa2 --- /dev/null +++ b/src/wallpaper-image-url.ts @@ -0,0 +1,23 @@ +/** Host file properties may contain a raw Windows path or an escaped path. */ +export function wallpaperImageUrl(value: string) { + let path = value.replaceAll("\\", "/"); + if (/^(https?:|data:|blob:)/i.test(path)) return path; + if (/^file:/i.test(path)) { + // This is already a URL: preserve escaped filename characters, but repair + // an escaped drive separator without escaping the percent sign again. + return path.replace(/^(file:\/{2,3})([a-z])%3a(?=\/)/i, "$1$2:"); + } + if (/^\/?[a-z]%3a(?:\/|%2f)/i.test(path)) { + try { path = decodeURIComponent(path); } + catch { path = path.replace(/^\/?([a-z])%3a/i, "$1:"); } + } + const encodePath = (text: string) => text.split("/").map(part => encodeURIComponent(part)).join("/"); + const drive = path.match(/^\/?([a-z]):\//i); + // Keep the drive outside the encoding operation entirely. + if (drive) return `file:///${drive[1]}:/${encodePath(path.slice(drive[0].length))}`; + if (path.startsWith("//")) { + const [server, ...parts] = path.slice(2).split("/"); + return `file://${server}/${encodePath(parts.join("/"))}`; + } + return path.startsWith("/") ? `file://${encodePath(path)}` : encodePath(path); +} diff --git a/src/wallpaper-insets.css b/src/wallpaper-insets.css new file mode 100644 index 0000000..ce877bf --- /dev/null +++ b/src/wallpaper-insets.css @@ -0,0 +1,61 @@ +/* Additional UI margins. Negative values intentionally extend the HUD frame. + The renderer, game targets and model-attached inspection marks stay full size. */ +#stage { + --ui-top: 0px; --ui-right: 0px; --ui-bottom: 0px; --ui-left: 0px; + --ui-brand-top: 114px; --ui-nav-top: 124px; --ui-footer-bottom: 35px; + --ui-viewer-top: 58px; --ui-viewer-bottom: 68px; --ui-viewer-side: 60px; --ui-viewer-state: 37px; + --ui-modal-clearance: 100px; +} +#stage[data-ui-insets="true"]:not([data-mode="boot"]) > :is(#archive-ui, #detail-ui) { + inset: var(--ui-top) var(--ui-right) var(--ui-bottom) var(--ui-left); +} +#stage[data-ui-insets="true"]:not([data-mode="boot"]) > .workbench { inset: var(--ui-top) var(--ui-right) 0 var(--ui-left); } +/* Translate the lower rows AFTER their HUD projection. Changing their layout + anchors would resample the curved surface and change their relative gaps. */ +#stage[data-ui-insets="true"]:not([data-mode="boot"]) :is(.wb-nav, .system-footer, .relay-entry) { + translate: 0 calc(-1 * var(--ui-bottom)); +} +#stage[data-ui-insets="true"]:not([data-mode="boot"]) > .brand { + left: calc(var(--edge) + var(--ui-left)); top: calc(var(--ui-brand-top) + var(--ui-top)); +} +#stage[data-ui-insets="true"]:not([data-mode="boot"]) > .system-nav { + right: calc(var(--edge) + var(--ui-right)); top: calc(var(--ui-nav-top) + var(--ui-top)); +} +#stage[data-ui-insets="true"]:not([data-mode="boot"]) > .system-footer { + left: calc(var(--edge) + var(--ui-left)); right: calc(var(--edge) + var(--ui-right)); bottom: var(--ui-footer-bottom); +} +#stage[data-ui-insets="true"]:not([data-mode="boot"]) > .powered { + right: calc(var(--edge) + var(--ui-right)); bottom: calc(108px + var(--ui-bottom)); +} +#stage[data-ui-insets="true"]:not([data-mode="boot"]) > .relay-entry { + left: calc(var(--edge) + var(--ui-left)); +} +#stage[data-ui-insets="true"]:not([data-mode="boot"]) > .relay-hud { + top: calc(15% + var(--ui-top)); left: calc(var(--edge) + var(--ui-left)); right: calc(var(--edge) + var(--ui-right)); +} +#stage[data-ui-insets="true"]:not([data-mode="boot"]) > .toast { + left: calc(50% + (var(--ui-left) - var(--ui-right)) / 2); bottom: calc(85px + var(--ui-bottom)); +} +#stage[data-ui-insets="true"]:not([data-mode="boot"]) .terminal-modal { + max-width: max(180px, calc(100% - var(--ui-left) - var(--ui-right) - var(--ui-modal-clearance))); + max-height: max(180px, calc(100% - var(--ui-top) - var(--ui-bottom) - var(--ui-modal-clearance))); + translate: calc((var(--ui-left) - var(--ui-right)) / 2) calc((var(--ui-top) - var(--ui-bottom)) / 2); + overflow: auto; +} +#stage[data-ui-insets="true"]:not([data-mode="boot"]) .viewer-header { + top: calc(var(--ui-viewer-top) + var(--ui-top)); left: calc(var(--ui-viewer-side) + var(--ui-left)); right: calc(var(--ui-viewer-side) + var(--ui-right)); +} +#stage[data-ui-insets="true"]:not([data-mode="boot"]) .viewer-footer { + bottom: calc(var(--ui-viewer-bottom) + var(--ui-bottom)); left: calc(var(--ui-viewer-side) + var(--ui-left)); right: calc(var(--ui-viewer-side) + var(--ui-right)); +} +#stage[data-ui-insets="true"]:not([data-mode="boot"]) .viewer-state { bottom: calc(var(--ui-viewer-state) + var(--ui-bottom)); } +#stage:is([data-layout="compact"], [data-layout="portrait"]) { + --ui-brand-top: var(--top-edge); --ui-nav-top: var(--top-edge); --ui-footer-bottom: var(--bottom-edge); + --ui-viewer-top: var(--top-edge); --ui-viewer-bottom: calc(var(--bottom-edge) + 18px); --ui-viewer-side: var(--edge); --ui-viewer-state: var(--bottom-edge); + --ui-modal-clearance: 0px; +} +#stage[data-layout="portrait"] { --ui-viewer-bottom: calc(var(--bottom-edge) + 24px); } +@media (max-width: 700px) { + #stage[data-ui-insets="true"]:not([data-mode="boot"]) > .workbench { top: calc(100px + var(--ui-top)); bottom: 50px; } + #stage[data-ui-insets="true"]:not([data-mode="boot"]) > .relay-hud { top: calc(max(120px, 18%) + var(--ui-top)); } +} diff --git a/src/wallpaper-opening.ts b/src/wallpaper-opening.ts new file mode 100644 index 0000000..b0e9840 --- /dev/null +++ b/src/wallpaper-opening.ts @@ -0,0 +1,5 @@ +// Original-video seconds minus five, immediately before the standalone extraction. +export const ARRAY_OPENING_END = 25.9; +export function openingShowsDetail(value: unknown, workbench: boolean) { + return value === "show" || (value !== "skip" && !workbench); +} diff --git a/src/wallpaper-quality.ts b/src/wallpaper-quality.ts new file mode 100644 index 0000000..b537ebf --- /dev/null +++ b/src/wallpaper-quality.ts @@ -0,0 +1,21 @@ +import { normalizeQuality, qualityPresets, type RenderQuality } from "./render-quality"; +import type { WallpaperProperties } from "./wallpaper"; + +export const superPerformanceQuality: RenderQuality = { + scale: 60, pixelRatio: 1, antialias: "off", shadows: 0, aoSamples: 0, + aoResolution: .5, depthOfField: 0, transmission: .25, anisotropy: 2, +}; + +export function wallpaperQuality(props: WallpaperProperties, fallback: RenderQuality): RenderQuality { + const preset = props.renderquality?.value; + if (typeof preset === "string" && Object.hasOwn(qualityPresets, preset)) + return { ...qualityPresets[preset as keyof typeof qualityPresets] }; + if (preset !== "custom") return fallback; + const custom: Record = {}; + for (const key of Object.keys(qualityPresets.original)) { + const value = props[`quality${key.toLowerCase()}`]?.value; + if (value === undefined) continue; + custom[key] = key === "antialias" ? value : typeof value === "string" ? Number(value) : value; + } + return normalizeQuality(custom); +} diff --git a/src/wallpaper.css b/src/wallpaper.css new file mode 100644 index 0000000..08ed6d8 --- /dev/null +++ b/src/wallpaper.css @@ -0,0 +1,17 @@ +/* Avoid native select rendering in Wallpaper Engine's off-screen CEF. */ +.quality-cycle { + display: inline-flex; + align-items: center; + justify-content: space-between; + gap: 20px; + min-width: 125px; + padding: 9px 12px; + font: inherit; + font-size: 12px; + color: #393d32; + background: #e7e3d9; + border: 1px solid #bdb4a1; +} +.quality-cycle:disabled { opacity: .4; cursor: default; } + +.wallpaper-settings-note { margin: 14px 0 20px; padding: 12px 16px; border-left: 2px solid #9b8b54; background: #e7e3d9; color: #555849; font-size: 13px; line-height: 1.7; } diff --git a/src/wallpaper.ts b/src/wallpaper.ts new file mode 100644 index 0000000..89306fd --- /dev/null +++ b/src/wallpaper.ts @@ -0,0 +1,22 @@ +export const isWallpaper = import.meta.env.MODE === "wallpaper"; +if (isWallpaper) document.documentElement.dataset.wallpaper = "true"; +export type WallpaperProperties = Record; +declare global { + interface Window { + rhineWallpaperPropertiesReady?: Promise; + rhineWallpaperHost?: { properties: WallpaperProperties; fps: number; paused: boolean }; + } +} +export const wallpaperHost = () => window.rhineWallpaperHost; +let lastFrame: number | undefined; +export function wallpaperFrame(ms: number) { + if (!isWallpaper) return true; + const host = wallpaperHost(); + if (host?.paused) { lastFrame = undefined; return false; } + const interval = 1000 / (host?.fps ?? 30); + if (lastFrame !== undefined && ms - lastFrame < interval - 0.5) return false; + // Quantize elapsed intervals with the same tolerance as the admission check; + // floating-point values just below one interval must not retain a full interval. + lastFrame = lastFrame === undefined ? ms : lastFrame + Math.max(1, Math.floor((ms - lastFrame + 0.5) / interval)) * interval; + return true; +} diff --git a/src/workbench-rolling.ts b/src/workbench-rolling.ts new file mode 100644 index 0000000..5404691 --- /dev/null +++ b/src/workbench-rolling.ts @@ -0,0 +1,69 @@ +import { createRollingText } from "@kitlangton/rolling-number"; + +const controllers = new WeakMap>(); +const textMeasure = document.createElement("canvas").getContext("2d")!; +function fitMediaText(element: HTMLElement, text: string) { + if (!element.matches(".wb-media h3, .wb-media p")) return text; + element.title = text; + const style = getComputedStyle(element); + textMeasure.font = `${style.fontWeight} ${style.fontSize} ${style.fontFamily}`; + const width = element.parentElement!.clientWidth; + if (!width) return text; + const spacing = parseFloat(style.letterSpacing) || 0; + const measure = (value: string) => textMeasure.measureText(value).width + [...value].length * spacing; + if (measure(text) <= width) return text; + const chars = [...text]; + let low = 0, high = chars.length; + while (low < high) { + const mid = Math.ceil((low + high) / 2); + if (measure(chars.slice(0, mid).join("") + "…") <= width) low = mid; + else high = mid - 1; + } + return chars.slice(0, low).join("") + "…"; +} +export function rollText(element: HTMLElement, text: string, animated: boolean) { + const fullText = text; + text = fitMediaText(element, text); + let controller = controllers.get(element); + if (!controller) { + controller = createRollingText(element, { text, duration: 460, motionBlur: true, transition: "direct", stagger: "none", direction: "up", animated }); + controllers.set(element, controller); + } else controller.update({ text, animated }); + if (!animated) controller.finish(); + if (element.matches(".wb-media h3, .wb-media p")) element.setAttribute("aria-label", fullText); +} + +/** Patch in place so media callbacks retain active reels, artwork and focus. */ +export function patchRollingPanel(parent: HTMLElement, html: string, animated: boolean) { + const template = document.createElement("template"); + template.innerHTML = html; + const patch = (target: Node, source: Node) => { + if (target instanceof HTMLElement && source instanceof HTMLElement) { + if (source.hasAttribute("data-wb-roll")) { + if (!controllers.has(target)) for (const attr of [...source.attributes]) target.setAttribute(attr.name, attr.value); + rollText(target, source.textContent ?? "", animated); return; + } + if (controllers.has(target)) { controllers.get(target)!.destroy(); controllers.delete(target); } + for (const attr of [...target.attributes]) if (!source.hasAttribute(attr.name)) target.removeAttribute(attr.name); + for (const attr of [...source.attributes]) if (target.getAttribute(attr.name) !== attr.value) target.setAttribute(attr.name, attr.value); + } + [...source.childNodes].forEach((next, index) => { + let current = target.childNodes[index]; + if (!current || current.nodeName !== next.nodeName) { + const fresh = next.cloneNode(false) as ChildNode; + if (current) { dispose(current); target.replaceChild(fresh, current); } else target.appendChild(fresh); + current = fresh; + } + if (next.nodeType === Node.TEXT_NODE) { if (current.textContent !== next.textContent) current.textContent = next.textContent; } + else patch(current, next); + }); + while (target.childNodes.length > source.childNodes.length) { dispose(target.lastChild!); target.lastChild!.remove(); } + }; + const dispose = (node: Node) => { + if (node instanceof HTMLElement) { + controllers.get(node)?.destroy(); controllers.delete(node); + node.querySelectorAll("[data-wb-roll]").forEach(el => { controllers.get(el)?.destroy(); controllers.delete(el); }); + } + }; + patch(parent, template.content); +} diff --git a/src/workbench-state.ts b/src/workbench-state.ts new file mode 100644 index 0000000..c77fdbe --- /dev/null +++ b/src/workbench-state.ts @@ -0,0 +1,22 @@ +export type TimerState = { phase: "focus" | "break"; status: "idle" | "running" | "paused" | "done"; remaining: number; deadline: number }; +export const idleTimer = (): TimerState => ({ phase: "focus", status: "idle", remaining: 0, deadline: 0 }); +export const dayKey = (now: Date) => `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}-${String(now.getDate()).padStart(2, "0")}`; +export function parseTarget(text: string): number | null { + const m = /^(\d{4})-(\d{2})-(\d{2})(?:[ T](\d{2}):(\d{2}))?$/.exec(text.trim()); + if (!m) return null; + const [, y, mo, d, h = "00", min = "00"] = m; + const date = new Date(+y, +mo - 1, +d, +h, +min); + return date.getFullYear() === +y && date.getMonth() === +mo - 1 && date.getDate() === +d && date.getHours() === +h && date.getMinutes() === +min ? date.getTime() : null; +} +export function timerLeft(timer: TimerState, now: number): number { + return timer.status === "running" ? Math.max(0, timer.deadline - now) : timer.remaining; +} +export function restoreTimer(value: unknown): TimerState { + const t = value as Partial | null; + if (!t || !["focus", "break"].includes(t.phase ?? "") || !["idle", "running", "paused", "done"].includes(t.status ?? "") || typeof t.remaining !== "number" || !Number.isFinite(t.remaining) || t.remaining < 0 || t.remaining > 7200000 || typeof t.deadline !== "number" || !Number.isFinite(t.deadline) || t.deadline < 0) return idleTimer(); + return { phase: t.phase!, status: t.status!, remaining: t.remaining, deadline: t.deadline }; +} +export function durationText(ms: number) { + const seconds = Math.ceil(Math.max(0, ms) / 1000); + return `${String(Math.floor(seconds / 60)).padStart(2, "0")}:${String(seconds % 60).padStart(2, "0")}`; +} diff --git a/src/workbench-visibility.ts b/src/workbench-visibility.ts new file mode 100644 index 0000000..3b4743b --- /dev/null +++ b/src/workbench-visibility.ts @@ -0,0 +1,16 @@ +export const workbenchElements = [ + ["clock", "时钟与日期"], ["tasks", "今日事项"], ["module", "右侧功能内容"], + ["navigation", "底部功能导航"], ["brand", "品牌文字"], ["footer", "页脚信息"], + ["settings", "设置入口"], +] as const; +export type WorkbenchElement = typeof workbenchElements[number][0]; +export type WorkbenchVisibility = Record; +export const defaultWorkbenchVisibility = (): WorkbenchVisibility => ({ clock: true, tasks: true, module: true, navigation: true, brand: true, footer: true, settings: true }); +export function applyVisibilityProperties(current: WorkbenchVisibility, properties: Record): WorkbenchVisibility { + const result = { ...current }; + for (const [key] of workbenchElements) { + const value = properties[`show${key}`]?.value; + if (typeof value === "boolean") result[key] = value; + } + return result; +} diff --git a/src/workbench.css b/src/workbench.css new file mode 100644 index 0000000..68c1551 --- /dev/null +++ b/src/workbench.css @@ -0,0 +1,81 @@ +.workbench { position: absolute; inset: 0; z-index: 5; pointer-events: none; color: #080a08; } +.workbench[hidden] { display: none; } +.workbench [hidden] { display: none !important; } +[data-workbench="true"][data-workbench-brand="false"]:not([data-mode="boot"]) > .brand, +[data-workbench="true"][data-workbench-footer="false"]:not([data-mode="boot"]) > .system-footer, +[data-workbench="true"][data-workbench-settings="false"]:not([data-mode="boot"]) > .system-nav { display: none; } +.workbench[data-clock-visible="false"] .wb-today { margin-top: 0; } +[data-workbench] #archive-ui { transition: opacity 260ms ease, visibility 0s; } +[data-workbench]:not([data-mode="boot"]) > .powered { transition: opacity 260ms ease, visibility 0s; } +[data-workbench="true"]:not([data-mode="boot"]) :is(#archive-ui, .powered, .inspection-overlay) { opacity: 0; visibility: hidden; pointer-events: none; transition: opacity 220ms ease-out, visibility 0s 220ms; } +[data-workbench="true"][data-mode="boot"] #archive-ui { opacity: 0; visibility: hidden; } +[data-workbench="true"] #archive-ui button { pointer-events: none; } +.reduce-motion[data-workbench] :is(#archive-ui, .powered) { transition: none; } +[data-workbench="true"] .system-nav > button:not(.settings-button) { display: none; } +.wb-overview { position: absolute; top: 24%; left: var(--edge); width: min(29%, 400px); } +.wb-kicker { font-size: 11px; letter-spacing: 1.8px; border-top: 1px solid #aaa59a; padding-top: 12px; display: flex; justify-content: space-between; gap: 12px; } +.wb-clock { font-size: clamp(52px, 6vw, 112px); line-height: 1.18; font-weight: 300; letter-spacing: -5px; font-variant-numeric: tabular-nums; } +.wb-date { font-size: 14px; margin-top: 8px; } +.wb-today { margin-top: 36px; padding: 18px 0; background: none; border-top: 1px solid #aaa59a; pointer-events: auto; } +.wb-heading { display: flex; align-items: center; justify-content: space-between; margin-bottom: 12px; } +.wb-heading h2 { margin: 0; font-size: 16px; font-weight: 500; } +.wb-task-count { font-size: 12px; } +.wb-task { display: flex; align-items: flex-start; gap: 12px; width: 100%; text-align: left; padding: 12px 0; font-size: 14px; line-height: 1.6; border-top: 1px solid #bdb8ad; overflow-wrap: anywhere; } +.wb-check { flex: 0 0 18px; height: 18px; line-height: 17px; text-align: center; border: 1px solid #a6a49c; margin-top: 3px; } +.wb-task[aria-pressed="true"] { color: #858077; } +.wb-task[aria-pressed="true"] > span:last-child { text-decoration: line-through; } +.wb-task[aria-pressed="true"] .wb-check { background: #080a08; color: #fff; } +.wb-module { position: absolute; top: 30%; right: var(--edge); width: min(34%, 500px); padding: 0; background: none; pointer-events: auto; } +.wb-title { font-size: 32px; font-weight: 400; margin: 22px 0; padding-bottom: 18px; border-bottom: 1px solid #aaa59a; } +.wb-large { font-size: clamp(45px, 5vw, 82px); font-weight: 300; line-height: 1.2; font-variant-numeric: tabular-nums; letter-spacing: -2px; } +.wb-large small { display: inline-block; font-size: 12px; margin-left: 14px; letter-spacing: 1px; } +.wb-rule { height: 2px; background: #bdb8ad; margin-top: 22px; } +.wb-rule i { display: block; height: 100%; background: #080a08; } +.wb-muted { font-size: 12px; line-height: 1.9; color: #77756d; margin: 14px 0 0; } +.wb-empty, .wb-event { font-size: 19px; line-height: 1.6; overflow-wrap: anywhere; } +.wb-storage { font-size: 12px; color: #9b7247; line-height: 1.7; } +.wb-storage:empty { display: none; } +.wb-media { display: flex; align-items: center; gap: 18px; } +.wb-media img, .wb-cover { width: 96px; height: 96px; object-fit: cover; flex-shrink: 0; } +.wb-cover { display: grid; place-items: center; font-size: 35px; background: #e8e5e1; } +.wb-media h3 { font-size: 21px; font-weight: 500; margin: 8px 0; overflow-wrap: anywhere; } +.wb-media p { font-size: 13px; margin: 0; overflow-wrap: anywhere; } +.wb-media small, .wb-timer-label { font-size: 11px; color: #77756d; } +.wb-timer-buttons { display: flex; gap: 10px; flex-wrap: wrap; margin-top: 24px; } +.wb-timer-buttons button { min-height: 42px; padding: 0 14px; border: 1px solid #a6a49c; font-size: 13px; } +.wb-timer-buttons button:first-child { color: #ece9e4; background: #080a08; border-color: #080a08; } +.wb-nav { position: absolute; right: var(--edge); left: var(--edge); bottom: 9%; display: flex; justify-content: flex-end; gap: 22px; pointer-events: none; } +.wb-nav button { position: relative; pointer-events: auto; width: 120px; min-height: 56px; background: none; text-align: left; padding: 10px 0 10px 15px; font-size: 12px; color: #77756d; } +.wb-nav button small { display: block; font-size: 9px; margin-bottom: 4px; } +.wb-nav button span { float: right; } +.wb-nav button::before { content: ""; position: absolute; left: 0; top: 20px; width: 2px; height: 12px; background: #aaa59a; } +.wb-nav button[aria-pressed="true"] { color: #080a08; } +.wb-nav button[aria-pressed="true"]::before { top: 10px; height: 33px; background: #080a08; } +.workbench button:hover { color: #9b7247; } +.wb-nav button:hover::before { background: #a27849; } +.wb-settings { padding: 16px 0; border-bottom: 1px solid #bdb8ad; } +.wb-settings > div { display: flex; gap: 10px; margin-top: 12px; } +.wb-settings button { padding: 12px; border: 1px solid #a6a49c; font-size: 13px; } +.wb-settings button[aria-pressed="true"] { background: #080a08; color: #ece9e4; } +.wb-settings p { font-size: 12px; line-height: 1.8; color: #77756d; } +@media (max-width: 1000px) { + .wb-overview { top: 20%; width: 35%; } + .wb-module { top: 22%; width: 45%; padding: 0; } + .wb-clock { letter-spacing: -2px; } + .wb-today { margin-top: 15px; padding: 12px 0; } + .wb-nav { bottom: 10%; } + .wb-nav button { width: 110px; } +} +@media (max-height: 650px) and (min-width: 701px) { + .wb-overview { top: 22%; }.wb-module { top: 20%; }.wb-clock { font-size: 48px; }.wb-today { margin-top: 12px; }.wb-task { padding: 7px 0; }.wb-title { margin: 12px 0; font-size: 22px; }.wb-large { font-size: 48px; }.wb-nav { bottom: 8%; }.wb-muted { margin-top: 8px; } +} +@media (max-width: 700px) { + .workbench { top: 100px; bottom: 50px; overflow-y: auto; pointer-events: auto; padding: 0 var(--edge); } + .wb-overview, .wb-module { position: static; width: auto; }.wb-module { margin-top: 20px; }.wb-overview { padding-top: 8px; } + .wb-nav { position: static; flex-wrap: wrap; margin-top: 20px; padding-bottom: 16px; justify-content: flex-start; }.wb-nav button { flex: 1 0 27%; }.wb-clock { font-size: 64px; } +} +.wb-media > div:last-child { min-width: 0; flex: 1; } +.wb-media :is(h3, p) { max-width: 100%; overflow: hidden; } +.wb-media > div:last-child { display: flex; flex-direction: column; align-items: flex-start; } +#stage[data-custom-wallpaper-visible="true"] .wb-media small, +#stage[data-custom-wallpaper-visible="true"] .wb-content > .wb-muted { color: var(--theme-ink, #242622); } diff --git a/src/workbench.ts b/src/workbench.ts new file mode 100644 index 0000000..8b614ff --- /dev/null +++ b/src/workbench.ts @@ -0,0 +1,211 @@ +import { rollText, patchRollingPanel } from "./workbench-rolling"; +import { escapeHtml } from "./html"; +import { wallpaperHost, type WallpaperProperties } from "./wallpaper"; +import { dayKey, durationText, idleTimer, parseTarget, restoreTimer, timerLeft } from "./workbench-state"; +import "./workbench.css"; +import { defaultWorkbenchVisibility, applyVisibilityProperties, type WorkbenchVisibility, type WorkbenchElement } from "./workbench-visibility"; + +type Media = { status?: { enabled?: boolean }; properties?: { title?: string; artist?: string; albumTitle?: string }; thumbnail?: { thumbnail?: string }; timeline?: { position?: number; duration?: number }; playing?: boolean }; +declare global { interface Window { rhineWallpaperMedia?: Media; } } +const names = ["时间日期", "今日事项", "重要日程", "正在播放", "专注计时"]; +const capabilityKeys = ["enabletime", "enabletasks", "enableevent", "enablemedia", "enablefocus"]; +const key = "rhine-workbench-v1"; +export class Workbench { + enabled = false; + private root: HTMLElement; + private props: WallpaperProperties = {}; + private lane = 0; + private timer = idleTimer(); + private done: string[] = []; + private date = dayKey(new Date()); + private storageOK = true; + private lastSecond = -1; + private exitAnimation?: Animation; + private visibility: WorkbenchVisibility = defaultWorkbenchVisibility(); + constructor(private stage: HTMLElement, private onMode: () => void, private onLane: (lane: number) => void) { + try { + const saved = JSON.parse(localStorage.getItem(key) ?? "null"); + this.timer = restoreTimer(saved?.timer); + if (saved?.date === this.date && Array.isArray(saved.done)) this.done = saved.done.filter((s: unknown) => typeof s === "string").slice(0, 3); + } catch { this.storageOK = false; } + stage.insertAdjacentHTML("beforeend", ``); + this.root = stage.querySelector(".workbench")!; + this.root.addEventListener("click", event => { + const button = (event.target as Element).closest("button"); + if (!button) return; + if (button.dataset.wbLane !== undefined) { this.select(+button.dataset.wbLane); onLane(this.lane); } + if (button.dataset.wbTask !== undefined) { + this.rollDay(); + const id = this.taskId(+button.dataset.wbTask); + this.done = this.done.includes(id) ? this.done.filter(d => d !== id) : [...this.done, id]; + this.save(); this.renderTasks(); + this.root.querySelector(`[data-wb-task="${button.dataset.wbTask}"]`)?.focus({ preventScroll: true }); + } + if (button.dataset.wbTimer) this.actTimer(button.dataset.wbTimer); + }); + window.addEventListener("rhine-wallpaper-properties", event => this.apply((event as CustomEvent).detail)); + window.addEventListener("rhine-wallpaper-media", () => { if (this.lane === 3) this.renderPanel(); }); + window.addEventListener("resize", () => { if (this.lane === 3) this.renderPanel(); }); + this.apply(wallpaperHost()?.properties ?? {}); + } + private text(key: string) { const v = this.props[key]?.value; return typeof v === "string" ? v.trim().slice(0, 240) : ""; } + private minutes(phase = this.timer.phase) { const n = this.props[phase === "focus" ? "focusminutes" : "breakminutes"]?.value; return typeof n === "number" && Number.isFinite(n) ? Math.max(1, Math.min(phase === "focus" ? 120 : 60, n)) : phase === "focus" ? 25 : 5; } + private taskId(i: number) { return `${i}:${this.text(`task${i + 1}`)}`; } + private apply(props: WallpaperProperties) { + Object.assign(this.props, props); + this.visibility = applyVisibilityProperties(this.visibility, props); + if (props.desktopmode) this.setEnabled(props.desktopmode.value === "workbench"); + // Early/partial host updates must not clear saved tasks before their text arrives. + const previous = this.done.length; + this.done = this.done.filter(id => [0, 1, 2].every(i => !props[`task${i + 1}`] || !id.startsWith(`${i}:`) || id === this.taskId(i))); + if (previous !== this.done.length) this.save(); + if (!this.laneEnabled(this.lane)) { + this.lane = capabilityKeys.findIndex((_, i) => this.laneEnabled(i)); + if (this.lane >= 0) this.onLane(this.lane); + } + this.renderTasks(); this.renderPanel(); + this.syncElements(); + } + setEnabled(value: boolean) { + this.enabled = value; + this.stage.dataset.workbench = String(value); + document.querySelectorAll("[data-workbench-mode]").forEach(button => button.setAttribute("aria-pressed", String((button.dataset.workbenchMode === "workbench") === value))); + this.onMode(); + this.syncVisibility(); + this.syncElements(); + } + syncVisibility() { + const hidden = !this.enabled || this.stage.dataset.mode === "boot"; + const entering = this.root.hidden && !hidden; + const reduced = this.stage.classList.contains("reduce-motion"); + this.root.inert = hidden; + this.root.setAttribute("aria-hidden", String(hidden)); + if (hidden && !this.root.hidden && !reduced) { + if (!this.exitAnimation) { + const animation = this.root.animate([{ opacity: getComputedStyle(this.root).opacity }, { opacity: 0 }], { duration: 220, fill: "forwards", easing: "ease-out" }); + this.exitAnimation = animation; + animation.onfinish = () => { this.root.hidden = true; animation.cancel(); this.exitAnimation = undefined; }; + } + return; + } + const interrupted = Boolean(this.exitAnimation); + const opacity = getComputedStyle(this.root).opacity; + this.exitAnimation?.cancel(); + this.exitAnimation = undefined; + this.root.hidden = hidden; + if (interrupted && !hidden && !reduced) this.root.animate([{ opacity }, { opacity: 1 }], { duration: 220, easing: "ease-out" }); + if (entering) { + [".wb-time", ".wb-today", ".wb-module", ".wb-nav"].forEach((selector, i) => { + const element = this.root.querySelector(selector)!; + element.getAnimations().forEach(a => a.cancel()); + if (!reduced) element.animate([{ opacity: 0, translate: "0 9px" }, { opacity: 1, translate: "0 0" }], + { duration: 460, delay: 60 + i * 65, easing: "cubic-bezier(.22,.7,.2,1)", fill: "backwards" }); + }); + } + } + private laneEnabled(lane: number) { return lane >= 0 && lane < names.length && this.props[capabilityKeys[lane]]?.value !== false; } + select(lane: number) { + if (!this.laneEnabled(lane)) return; + this.lane = lane; + this.renderPanel(); + } + settingsMarkup() { + return `
工作模式

事项、日程和计时时长请在 Wallpaper Engine 属性中填写。事项完成状态按天保存,计时进度单独保留。

工作台元素与设置入口的显示开关位于 Wallpaper Engine 的壁纸属性中。全部关闭后只显示档案阵列;需要恢复时从那里重新打开。隐藏专注内容不会停止计时。

`; + } + private syncElements() { + const selectors = { clock: ".wb-time", tasks: ".wb-today", module: ".wb-module", navigation: ".wb-nav" } as const; + for (const [key, selector] of Object.entries(selectors)) this.root.querySelector(selector)!.hidden = !this.visibility[key as WorkbenchElement]; + const available = capabilityKeys.some((_, i) => this.laneEnabled(i)); + this.root.querySelector(".wb-module")!.hidden = !this.visibility.module || !available; + this.root.querySelector(".wb-nav")!.hidden = !this.visibility.navigation || !available; + this.root.querySelectorAll("[data-wb-lane]").forEach(button => { button.hidden = !this.laneEnabled(+button.dataset.wbLane!); }); + this.root.querySelector(".wb-overview")!.hidden = !this.visibility.clock && !this.visibility.tasks; + this.root.dataset.clockVisible = String(this.visibility.clock); + this.stage.dataset.workbenchBrand = String(!this.enabled || this.visibility.brand); + this.stage.dataset.workbenchFooter = String(!this.enabled || this.visibility.footer); + this.stage.dataset.workbenchSettings = String(!this.enabled || this.visibility.settings); + } + private save() { + try { localStorage.setItem(key, JSON.stringify({ date: this.date, done: this.done, timer: this.timer })); this.storageOK = true; } + catch { this.storageOK = false; } + this.root.querySelector(".wb-storage")!.textContent = this.storageOK ? "" : "当前无法保存进度,重新加载后可能丢失。"; + } + private rollDay() { + const today = dayKey(new Date()); + if (this.date !== today) { this.date = today; this.done = []; this.save(); this.renderTasks(); } + } + private renderTasks() { + const entries = [0, 1, 2].filter(i => this.text(`task${i + 1}`)); + this.root.querySelector(".wb-task-count")!.textContent = entries.length ? `${entries.filter(i => this.done.includes(this.taskId(i))).length} / ${entries.length}` : ""; + this.root.querySelector(".wb-tasks")!.innerHTML = entries.length ? entries.map(i => ``).join("") : '

今天想完成什么?
在 Wallpaper Engine 属性中填写最多三件事。

'; + } + private actTimer(action: string) { + const now = Date.now(); + this.settle(now); + if (action === "reset") this.timer = { ...idleTimer(), phase: this.timer.phase }; + if (action === "phase") this.timer = { ...idleTimer(), phase: this.timer.phase === "focus" ? "break" : "focus" }; + if (action === "toggle") { + if (this.timer.status === "running") this.timer = { ...this.timer, status: "paused", remaining: timerLeft(this.timer, now), deadline: 0 }; + else { + const remaining = this.timer.status === "paused" ? this.timer.remaining : this.minutes() * 60000; + this.timer = { ...this.timer, remaining, deadline: now + remaining, status: "running" }; + } + } + this.save(); this.renderPanel(); + this.root.querySelector(`[data-wb-timer="${action}"]`)?.focus({ preventScroll: true }); + } + private settle(now: number) { + if (this.timer.status === "running" && timerLeft(this.timer, now) === 0) { + this.timer = { ...this.timer, status: "done", remaining: 0, deadline: 0 }; this.save(); this.renderPanel(); + } + } + tick(now = Date.now()) { + if (Math.floor(now / 1000) === this.lastSecond) return; + this.lastSecond = Math.floor(now / 1000); + this.rollDay(); this.settle(now); + const date = new Date(now); + rollText(this.root.querySelector(".wb-clock")!, date.toLocaleTimeString("en-GB", { hour: "2-digit", minute: "2-digit" }), !this.stage.classList.contains("reduce-motion")); + this.root.querySelector(".wb-date")!.textContent = date.toLocaleDateString("zh-CN", { year: "numeric", month: "long", day: "numeric", weekday: "long" }); + if (this.lane === 0 || this.lane === 2) this.renderPanel(); + const timer = this.root.querySelector(".wb-timer-digits"); + if (timer) rollText(timer as HTMLElement, durationText(this.timer.status === "idle" ? this.minutes() * 60000 : timerLeft(this.timer, now)), !this.stage.classList.contains("reduce-motion")); + } + private renderPanel() { + if (this.lane < 0) { + this.root.querySelector(".wb-title")!.textContent = ""; + this.root.querySelector(".wb-content")!.replaceChildren(); + return; + } + this.root.querySelector(".wb-title")!.textContent = names[this.lane]; + const available = capabilityKeys.map((_, i) => i).filter(i => this.laneEnabled(i)); + this.root.querySelector(".wb-index")!.textContent = `${String(available.indexOf(this.lane) + 1).padStart(2, "0")} / ${String(available.length).padStart(2, "0")}`; + this.root.querySelectorAll("[data-wb-lane]").forEach(b => b.setAttribute("aria-pressed", String(+b.dataset.wbLane! === this.lane))); + let html = ""; + if (this.lane === 0) { + const now = new Date(), end = new Date(now.getFullYear() + 1, 0, 1).getTime(), start = new Date(now.getFullYear(), 0, 1).getTime(); + const percent = (now.getTime() - start) / (end - start) * 100; + html = `
${now.getFullYear()}YEAR

今年已走过 ${percent.toFixed(1)}%

`; + } + if (this.lane === 1) html = '
03PRIORITIES

把今天留给最重要的三件事。
点击左侧事项标记完成,再点一次撤销。完成状态每天重置。

'; + if (this.lane === 2) { + const text = this.text("eventdate"), target = parseTarget(text), title = this.text("eventname"); + const delta = target === null ? 0 : target - Date.now(); + html = !text ? '

留一个值得期待的日子。

在 Wallpaper Engine 中填写日程名称与目标日期。

' : target === null ? '

目标日期格式不正确

请填写 YYYY-MM-DD,或 YYYY-MM-DD HH:mm。

' : `

${escapeHtml(title || "重要日程")}

${Math.ceil(Math.abs(delta) / 86400000)}${delta > 0 ? "天后" : "天前"}

${delta > 0 ? "距离目标" : "已到达目标"} · ${escapeHtml(text)}
${Math.floor(Math.abs(delta) / 3600000)} 小时 ${Math.floor(Math.abs(delta) / 60000) % 60} 分钟${delta > 0 ? "后" : "前"}

`; + } + if (this.lane === 3) { + const media = window.rhineWallpaperMedia ?? {}, p = media.properties, t = media.timeline; + const cover = media.thumbnail?.thumbnail; + const safeCover = typeof cover === "string" && /^data:image\/(png|jpeg|webp);base64,[a-z0-9+/=\s]+$/i.test(cover); + html = media.status?.enabled === false ? '

媒体信息未启用

请在 Wallpaper Engine 中启用媒体信息集成。

' : !p?.title ? '

此刻,留一点安静。

在支持系统媒体信息的播放器中播放音乐,歌曲与封面会显示在这里。

' : `
${safeCover ? `专辑封面` : ''}
${media.playing ? "正在播放" : "媒体已暂停或停止"}

${escapeHtml(p.title)}

${escapeHtml(p.artist || "")}

${t && typeof t.duration === "number" && t.duration > 0 && Number.isFinite(t.duration) && typeof t.position === "number" && Number.isFinite(t.position) ? `

${durationText(t.position * 1000)} / ${durationText(t.duration * 1000)}

` : ''}`; + } + if (this.lane === 4) html = `
${this.timer.phase === "focus" ? "专注" : "休息"} · ${this.timer.status === "done" ? "已结束" : this.timer.status === "running" ? "进行中" : this.timer.status === "paused" ? "已暂停" : "准备开始"}
${durationText(this.timer.status === "idle" ? this.minutes() * 60000 : timerLeft(this.timer, Date.now()))}

${this.timer.status === "done" ? "这一段时间已完成。准备好后再开始下一段。" : "暂停壁纸或重新加载后按实际时间校正。"}
时长在 Wallpaper Engine 中设置。

`; + patchRollingPanel(this.root.querySelector(".wb-content")!, html, !this.stage.classList.contains("reduce-motion")); + this.root.querySelector(".wb-storage")!.textContent = this.storageOK ? "" : "当前无法保存进度,重新加载后可能丢失。"; + } +} + diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..9bf764b --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "Bundler", + "resolveJsonModule": true, + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "strict": true, + "skipLibCheck": true, + "noEmit": true, + "allowImportingTsExtensions": true, + "useDefineForClassFields": true + }, + "include": ["src"] +} diff --git a/vercel.json b/vercel.json new file mode 100644 index 0000000..193b703 --- /dev/null +++ b/vercel.json @@ -0,0 +1,31 @@ +{ + "$schema": "https://openapi.vercel.sh/vercel.json", + "buildCommand": "npm run build", + "outputDirectory": "dist", + "headers": [ + { + "source": "/fonts/misans-webfont-4.3.1/(.*)", + "headers": [{ "key": "Cache-Control", "value": "public, max-age=31536000, immutable" }] + }, + { + "source": "/assets/:file(archive-(?:cassette|assembly)\\.[a-f0-9]{16}\\.glb)", + "headers": [{ "key": "Cache-Control", "value": "public, max-age=31536000, immutable" }] + }, + { + "source": "/update(.*)", + "headers": [{ "key": "Cache-Control", "value": "no-cache, no-store, must-revalidate" }] + }, + { + "source": "/sw.js", + "headers": [{ "key": "Cache-Control", "value": "no-cache, no-store, must-revalidate" }] + }, + { + "source": "/manifest.webmanifest", + "headers": [{ "key": "Cache-Control", "value": "no-cache, must-revalidate" }] + }, + { + "source": "/pwa-build.json", + "headers": [{ "key": "Cache-Control", "value": "no-cache, must-revalidate" }] + } + ] +} diff --git a/verification/ANGLE-FIX.md b/verification/ANGLE-FIX.md new file mode 100644 index 0000000..b5f81e8 --- /dev/null +++ b/verification/ANGLE-FIX.md @@ -0,0 +1,21 @@ +# 侧边角度条纹修复 + +基线:`ca578332ebe35107740e94d2406f208ed4624c03`,标签 `baseline-2026-09-08`。本地完整快照保存在 `backups/render-baseline-20260908-021308`。 + +标注位置对应 Amber_Lightguide。导光条前表面仅比透明盖板前表面靠前 0.002 世界单位,原有远距离透视镜头在部分角度下无法稳定区分深度,形成斜向条纹。以 -0.8 弧度稳定复现。 + +仅给 Amber_Lightguide 材质添加 polygonOffset,factor=-1、units=-2。模型 GLB 与基线 SHA256 相同;相机、裁剪面、灯光、AO、颜色、透射与动效均未改变。 + +验证: + +- 实际场景在 -0.8、-0.45、0、+0.8 弧度对照;标注侧条的斜纹消失,螺钉和标签的遮挡正常。 +- 关闭 AO 后基线问题仍存在;临时提高近裁剪面能消除问题但改变 AO 层次,因此最终只修正局部材质。 +- 切换档案并归位时,当前和旧卡片都保留同一深度偏移,旧卡片仍先转正再下降。 +- `node scripts/check-appearance.mjs`、`node scripts/check-motion.mjs`、`npm run build` 通过。 + +本地复核页面(开发服务): + +- 修复前:`/reference/detail-transition.html?t=5&angle=-0.8&sharp=1&close=1&offset=0` +- 修复后:`/reference/detail-transition.html?t=5&angle=-0.8&sharp=1&close=1` + +`sharp` 仅在验证页关闭景深以方便观察,产品仍使用原有景深。 diff --git a/verification/ARCHIVE-ARRAY.md b/verification/ARCHIVE-ARRAY.md new file mode 100644 index 0000000..7643f95 --- /dev/null +++ b/verification/ARCHIVE-ARRAY.md @@ -0,0 +1,159 @@ +# 全量归档阵列(2026-09-12)⚠️ 已回退,仅存档 + +> **本文档描述的方案已于 2026-09-12 当天整体回退**,用户判定"索引完全改坏"。文中提到的 +> `src/archive-array.ts` / `scripts/check-array.mjs` / `npm run check:array` / `loadArchiveDataset` / +> `searchDataset` / `laneCenter` / `rowPeriod` / `ROW_SNAP_DISTANCE` **都已不存在**,不要再按本文实现。 +> 当前机制见 [`../AGENTS.md`](../AGENTS.md) 顶部「索引与阵列:当前机制(权威)」。保留本文只为记录当时的 +> 设计与失败原因(列外条目错位、检索列表被 `00_索引` 元数据文件淹没)。 + +把三维档案阵列从**写死的 5 列 × 8 条(40 条精选档案)**改造为**全量归档驱动**:列 = 归档顶层内容分类,列内是该分类下的真实归档条目。条目简介由索引自动派生。 + +使用与安全边界见 [`../docs/LOCAL-ARCHIVE.md`](../docs/LOCAL-ARCHIVE.md)。 + +## 架构:两个数据集,一套寻址接口 + +| 数据集 | 来源 | 规模 | 作用 | +| --- | --- | ---: | --- | +| `curated` | `content/archives.json`(生成物,手写内容在 `content/curated.mjs`) | 5 列 / 40 条 | ① 模块加载时即刻可用,保证 `main.ts` 顶层的同步访问不炸 ② 归档索引不可用时降级 | +| `archive` | `public/archive-index.json`(生成物) | 7 列 / **32 张盘**(盘内 49,965 条) | `start()` 里由 `loadArchiveDataset()` 换上,阵列与检索的正式数据源 | + +`data.ts` 的 `columnFiles` / `fileLocation` / `fileAtSlot` / `indexAtCell` 对两个数据集是同一套接口,因此阵列、循环、拖拽、收藏都不需要知道当前用的是哪一份。 + +## 为什么必须做性能改造 + +优化前后的实测(真实 49,997 条索引): + +| 指标 | 旧实现 | 新实现 | +| --- | ---: | ---: | +| 2000 次寻址 | **38,365 ms** | **0.33 ms** | +| 单次寻址 | ~19 ms | ~0.0002 ms | +| 一百万个格子反查 | — | 23 ms | +| 建表(一次性) | — | 72 ms | +| 全量数据集载入 | — | 181–231 ms | + +旧实现每次调用都 `.map().filter()` 扫全量再用 `.indexOf()` 定位。真实后果不是"每帧卡",而是**输入路径**卡:hover 每次进新格一次全扫(约 19 ms)、滚轮每步两次、**一次拖动最多 64 次(约 1.2 秒)**。 + +> 记录一条判断失误:我曾据基准推算出"旧写法每帧 19 秒",这是错的。渲染循环 `scene.update` 里根本不调用 `columnFiles` / `fileAtCell`(每帧 0 次),开销全在输入路径。 + +## 关键设计决策 + +### 1. 全局行周期(这是全量化最容易错的一点) + +行坐标靠"减去周期整数倍"做归一化(rebase),而位移必须保持内容不变 —— 也就是必须是该列**取模周期的整数倍**。各列长度不同(2340 / 14797 / 20675 …)时**不存在公共周期**。 + +解法:引入**全局行周期 = 最长一列的条数**(`data.ts` 的 `rowPeriod()`)。`indexAtCell` 先用全局周期定位槽位、再落到该列自身长度上: + +``` +slot = wrap(row - ROW_BASE, rowPeriod) +item = members[slot % members.length] +``` + +于是任何全局周期整数倍的平移都不改变结果,rebase 成立。`curated` 下各列都是 8,与原行为逐位一致。 + +`scripts/check-array.mjs` 专门断言了这条不变性(±1 个周期与 +3 个周期)。 + +### 2. `fileAtSlot` 与 `indexAtCell` 语义不同,不能合并 + +- `fileAtSlot` —— **截断**(越界夹到首尾)。只服务 boot 与 `?time=`/`review=1` 逐帧对照的固定 160 布局,必须与原片一致。 +- `indexAtCell` —— **循环取模**。服务交互阵列的无限循环。 + +中途我把 `fileAtSlot` 误写成取模,等价性检查立刻报出 110 处差异(每列 12 行 × 9 列边距)。已改回截断。 + +### 3. 展示字段惰性物化 + 有界缓存 + +五万条记录若都生成摘要文本,仅数据就约 35 MB。`data.ts` 用 `Proxy` 惰性物化:只给真正被读到的条目算文本(首读一条含派生摘要约 **0.008 ms**)。缓存上限 1024,防止日后有人全量遍历时几十 MB 常驻。 + +### 4. 检索在紧凑索引上,绝不遍历 `records` + +`renderResults` 曾经 `records.map().filter()` —— 全量下会物化五万条。现在走 `searchDataset()`:只比对路径字符串(小写路径惰性建一次),命中后由调用方按需读那几条。实测路径检索 **9 ms**。 + +`scene.ts` 对 `records` 的引用数现在是 **0**;`main.ts` 只剩 4 处 `records.length`(O(1))与一处位于 curated 分支的 `forEach`。 + +### 5. 收藏改按路径键 + +编号是数组下标,归档新增文件会整体位移,按编号存会让旧收藏指到别的档案。改为 `recordKey()`(优先归档相对路径,退化为编号),`indexOfKey()` 用惰性 Map 反查。 + +### 6. 刻度条窗口化 + +`#file-ticks` 原先每条档案一个按钮;20,675 条会渲染两万个 DOM 节点。改为固定 8 个按钮的滑动窗口(`TICK_WINDOW`),随选中位置移动、按循环语义环绕。 + +### 7. 列数参数化 + +| 位置 | 改动 | +| --- | --- | +| `archive-loop.ts` | 新增 `configureLoop(columnCount)`;`LOOP_COLUMNS` / `POOL_LANES` 改为 live binding。前五项恒为真实列(列数不足时回绕),保证固定 160 布局永远成立 | +| `archive-visibility.ts:36-37,42` | `+2` / `(lane-2)` → `laneCenter()` | +| `scene.ts` | 四处 lane 原点改用 `laneCenter()`;rebase 周期 `(lane-2)/5` → `/(列数)`、`(row-12)/8` → `/rowPeriod()`;`laneFocus` 与 `selectedSlot` 初值随列数;`Math.floor(selectedSlot/32)` → `ROW_STRIDE` | +| `motion.ts` | `archiveWave` / `cinematicField` 增加 `laneOrigin` 可选参数(默认 2,保持签名兼容,也不把数据层拖进纯数学模块) | +| `main.ts` | `COLUMN 03 / 05` 的 /05 改为动态;`columnMemory` 改为可重建;新增 `applyColumnChrome()` | + +`scene.ts:1596` 的 `fixed` 路径(boot + 逐帧对照)**保持不变**:仍是 `Array.from({length:160})` 的 5 列 × 32 行,因为它是原片对照基准。正常入场走的是动态路径(`responsiveOpening` 令 `fixed=false`),所以 boot → archive 之间不会跳变,只是入场画面从 5 列变成 7 列。 + +### 8. 远距离跳转直接落位 + +`selectionCell` 的 `nearestOccurrence` 会把目标行放到离当前位置最近的同现点,最大距离是**列长的一半**——最长列 20,675 条,即最远 10,337 行。照阻尼飞过去镜头会横穿整列。 + +`scene.ts` 因此加了 `ROW_SNAP_DISTANCE = 64`(可视窗口 32 行的两倍):行距超过它就认定是跳转而非移动,`shoulder` 与 `rail` 直接赋值、速度清零。 + +- 只作用于**行轴**。列轴最远只有半个列数(`nearestOccurrence` 对 7 列最多 3 列),原动画保留,不介入。 +- 拖动、惯性、抽取期间由它们接管轨道,条件里显式排除,不干扰已确认的交互。 +- 正常导航不会误触发:方向键与滚轮的连按滞后远小于 64 行(`check-array` 断言相邻移动的行距恒为 1)。 + +阈值两侧都由 `npm run check:array` 用真实数据钉住:相邻移动距离必须为 1,半列之外的跳转距离必须大于 64。 + +## 刻意没有改的东西 + +- **校验器 `archive-content.mjs` 与 `check-content.mjs` 未放宽**。因为 `content/archives.json` 仍是 curated 数据集、仍是 5 列 × 8 条 × 40 条,校验契约依旧成立 —— 原计划要放宽,架构定下来后发现没必要。 +- **`export-records.mjs` / `build-pwa.mjs` 未改**。TXT 导出仍只针对 40 条精选档案(40 个文件),不会线性膨胀。 + +## 盘的粒度:一个项目一张盘(2026-09-12 修正) + +第一版把**每个文件**都做成一张盘(49,997 张),这是错的。用户指出正确模型是"**一个项目就是一个 3D 盘,在盘内的页面可以自由访问所有文件**"。错误版本有两个可见后果: + +1. 检索列表一打开就列 `records` 的前 60 条,而索引按路径排序,头 60 条正好是 `00_索引` 下的 CSV / MD / TXT 元数据文件; +2. 这些条目 `laneOf < 0`(不在任何列里),选中时 `fileLocation` 退回 `{lane: 0, row: 12}`,**抽出的是第一列的第一张盘**——完全错位。 + +现在的规则: + +- **盘 = 分类下的第 1 层目录**(项目单元)。文件不是盘,更深的目录也不是盘。 +- 列外条目不再参与 `searchDataset`,也不会出现在检索列表里;未输入关键字时列的是各列的盘。 +- 详情页新增 **04 盘内文件** 页签:列当前盘的直接子项(目录在前),可逐层进入并带面包屑,文件可【打开】【定位】。 + +列分布:`4 / 2 / 11 / 3 / 4 / 4 / 4 = 32 张盘`,`unlisted` 49,965(盘内文件与更深结构)。 + +**副作用**:`ROW_SNAP_DISTANCE = 64` 在盘模型下**休眠**——最长列 11 张盘,最近同现点最远 5 行。阈值保留以应对粒度变化;`check-array` 会报告 `snapEngaged`,并在列长超过阈值时自动改断言为"必须触发"。 + +## 证据 + +``` +$ npm run check:array +{ + "entries": 49997, + "columns": 7, + "counts": [2340, 14797, 20675, 11901, 200, 15, 32], + "unlisted": 37, + "rowPeriod": 20675, + "roundTripsChecked": 89, + "searchMs": 9, + "addressMsPerMillion": 23, + "checks": "passed" +} + +$ npx tsc --noEmit → exit 0 +$ npm run check:content → 18 pass / 0 fail +$ node scripts/check-archive.mjs → checks: passed(40 条 / 每列 8 / 帧 760 波峰等高) +$ node scripts/check-loop.mjs → checks: passed(poolSize 288 / referenceInstances 160) +$ npm run build → exit 0,离线包 818 files / 33.9 MiB +``` + +行为等价性(对既有 40 条,与旧实现逐位对比):`fileLocation` / `fileAtSlot` / `columnFiles` 差异均为 **0**。 + +## 尚未验证与已知取舍 + +- **浏览器内的实际观感**:项目无 Playwright / Puppeteer,也没有无头 WebGL 环境。7 列下的构图、卡片观感、`archiveCandidates` / `archiveCulled` 的真实数值,都需要人工在 `npm run dev:archive` 下确认。剔除遥测可从 + `JSON.parse(document.querySelector('#three-scene').dataset.renderStats)` 读取。 + - 相机位置与 fov 是按 5 列调的,但交互阵列本就只显示约 5 列、靠 `columnCamera` 横向平移到达其余列,因此**预期**只是横向可视列数不变、需要平移才能看到边缘列,而非取景错误。这一点未经实机确认。 +- **列极不均衡**:`03_代码与项目` 20,675 条 vs `06_下载与安装包` 15 条。列数已参数化,若要改成"大分区拆子列"只需换列规则。 +- **行坐标量级**:rebase 前行可达 20,675,z 约 12,800 单位。相机相对矩阵下精度约 0.001(卡片尺寸约 5),预期无可见影响,但未实测。 +- **旧收藏**:早先按 `X-0NN` 编号存的收藏不会解析到路径键,会从收藏列表消失(仍留在 localStorage)。按编号存的语义本就与全量归档冲突,取舍是明确的。 + diff --git a/verification/ARCHIVE-INTEGRATION.md b/verification/ARCHIVE-INTEGRATION.md new file mode 100644 index 0000000..78f6d06 --- /dev/null +++ b/verification/ARCHIVE-INTEGRATION.md @@ -0,0 +1,199 @@ +# 本地归档接入验证(2026-09-12) + +本轮把 `E:\归档`(约 90 GB / 45,055 个文件 / 4,937 个目录)接入本项目:四十条精选档案改为由真实归档生成,新增 **LOCAL ARCHIVE** 面板做全量检索与安全写操作。 + +实现说明见 [`../docs/LOCAL-ARCHIVE.md`](../docs/LOCAL-ARCHIVE.md),数据维护见 [`../content/README.md`](../content/README.md)。 + +## 新增与改动 + +| 文件 | 类型 | 说明 | +| --- | --- | --- | +| `archive.config.json` | 新增 | 归档根目录、端口、索引与日志位置 | +| `content/curated.mjs` | 新增 | 四十条档案的手写部分与对应的真实 `path` | +| `scripts/archive-source.mjs` | 新增 | 扫描归档,生成 `archives.json`、`archive-index.json`、40 个 TXT | +| `scripts/archive-service.mjs` | 新增 | 回环服务:令牌、路径围栏、检索、读写操作、目录监视 | +| `scripts/archive-dev.mjs` | 新增 | 一条命令同时启动服务与 Vite | +| `src/archive-service.ts` | 新增 | 浏览器端客户端,含静态快照降级 | +| `src/archive-panel.ts` | 新增 | 检索与归档操作面板 | +| `src/archive-panel.css` | 新增 | 面板样式,全部走 `--theme-*` 变量 | +| `scripts/archive-content.mjs` | 改动 | `source` 接受 `file:`;TXT 模板改为归档口径 | +| `src/main.ts` | 改动 | 导航新增 LOCAL ARCHIVE 按钮、点击分支、面板实例化;详情页 EXPORT 下载改为 SHOW IN FOLDER 定位 | +| `src/data.ts` | 改动 | `ArchiveRecord` 增加可选的 `path` 与 `stats`(生成物写入的真实归档信息) | +| `src/style.css`、`src/responsive.css` | 改动 | `.export-button` 改为双语按钮并加宽;新增被截断的 `.detail-path` | +| `vite.config.ts` | 改动 | `server`/`preview` 增加 `/archive-api` 代理并注入令牌 | +| `package.json` | 改动 | 新增 `dev:archive`、`archive:snapshot`、`archive:serve`;钩子改跑 `archive-source.mjs` | +| `.gitignore` | 改动 | 忽略 `.archive-token` | +| `content/README.md` | 改动 | 说明 `archives.json` 现在是生成物;`path` 的两个用途 | +| `docs/LOCAL-ARCHIVE.md`、`verification/ARCHIVE-INTEGRATION.md` | 新增 | 用法、安全边界与实测记录 | + +**没有改动的**:`src/scene.ts`、`src/archive-loop.ts` 与阵列数学。五列、每列八份、`X-0NN` 稳定编号的约束原样保留,`archive-content.mjs` 的三条硬校验(五分类 / 四十条 / 每类八条)未放宽。 + +## 证据 + +### 1. 数据校验与构建 + +``` +$ npm run check:content +ℹ tests 18 ℹ pass 18 ℹ fail 0 + +$ npx tsc --noEmit +exit=0 + +$ npm run build +归档快照已更新:40 条精选档案,45055 个文件 / 4937 个目录 / 90.03 GB +dist/assets/index-KZVOVeb_.js 993.68 kB │ gzip: 301.66 kB +✓ built in 2.48s +Offline release 22cf358e6368842a: 818 files, 33.8 MiB. +build exit=0 +``` + +`check:content` 的 18 项包含"下载 TXT 与 `archiveText` 逐字节一致(含 BOM)",即 40 个 TXT 与 `archives.json` 同步;放宽 `source` 协议后,`rejects unsafe URL`(`javascript:`)与 `rejects invalid URL`(`example.com`)两项仍如期失败,说明放宽没有引入注入面。 + +### 2. 服务接口实测 + +| 用例 | 结果 | +| --- | --- | +| 无令牌 `GET /status` | `401`,正确拒绝 | +| `GET /status` | `{"root":"E:\\归档","watching":true,"totals":{"files":45055,"dirs":4937,...},"sevenZip":true}` | +| `POST /open {"path":"../Windows/system.ini"}` | `403`(越界拦截) | +| `POST /reveal {"path":"C:\\Windows\\win.ini"}` | `403`(绝对路径拦截) | +| `POST /open {"path":"不存在的目录/foo.txt"}` | `404` | +| `POST /plan {"op":"mkdir"}` → `apply` | `blocked=null`,目录创建成功 | +| `POST /plan {"op":"move"}` → `apply` | `files=1 bytes=18`,移动成功,源消失 | +| 目标已存在时 `plan move` | `blocked="目标已存在,请换一个名字"` | +| `plan move` 到 `D:\` | `403`(目标越界拦截) | +| `POST /plan {"op":"extract","from":"07_日志与排查/LOG.7z"}` → `apply` | 解出 4 个文件,与包内条目数一致 | +| `POST /apply` 缺少 `confirm` | `400`,且未创建任何目录 | +| `GET /index`(gzip) | 7,767,411 字节 → 698,629 字节(约 9 倍) | +| `POST /refresh` | 返回新的快照时间 | + +操作日志按预期落盘: + +```json +{"at":"2026-09-12T02:10:44.855Z","op":"mkdir","from":null,"to":"_测试_归档操作","result":"ok"} +{"at":"2026-09-12T02:10:44.872Z","op":"move","from":"_测试源文件.txt","to":"_测试_归档操作/测试源文件.txt","result":"ok"} +{"at":"2026-09-12T02:10:44.946Z","op":"extract","from":"07_日志与排查/LOG.7z","to":"_测试_归档操作/LOG_解压","result":"ok"} +``` + +测试产物已全部删除,归档中已无 `_测试_*` 条目。 + +### 3. 自动更新 + +在归档根新建 `_自动更新测试.txt`,**不调用任何刷新接口**,等待 8 秒后检索: + +``` +索引命中 1 条:_自动更新测试.txt +最近一次快照:09/12/2026 02:11:06 +``` + +删除该文件后再次等待,快照随之更新为 `02:11:14`。递归监视器与防抖重生生效。 + +### 4. 开发链路 + +``` +$ npm run dev +归档快照已更新:40 条精选档案,45055 个文件 / 4937 个目录 / 90.03 GB +VITE v7.3.6 ready in 426 ms +➜ Local: http://127.0.0.1:5174/ (5173 已被占用) + +GET http://127.0.0.1:5174/ → 200, text/html +GET http://127.0.0.1:5174/archive-api/status → 200, 返回真实 status(令牌由代理注入) +GET http://127.0.0.1:5174/archive-api/search?q=runs.7z → 命中 2 条,含 01_人工智能/01_训练产物/runs.7z +GET http://127.0.0.1:5174/archive-index.json → 200(静态降级路径可用) +GET http://127.0.0.1:5174/src/archive-panel.ts → 200, 57,329 字节(Vite 转译无错) +GET http://127.0.0.1:5174/src/archive-service.ts → 200, 15,727 字节 +``` + +### 5. 产物复核 + +``` +dist/assets/index-KZVOVeb_.js : LOCAL ARCHIVE / local-archive / la-surface / + archive-index.json / archive-api / 正在检测归档服务 全部命中 +dist/assets/index-DeuQtmYI.css : .la-surface 命中 +dist/archives/*.txt : 40 个 +dist/archive-index.json : 7.41 MB(未进入 PWA 预缓存,818 files / 33.8 MiB 与改动前同量级) +``` + +### 6. 详情页 EXPORT 改为定位文件位置 + +按用户要求,详情页右下角的 EXPORT(下载 TXT)改为 **SHOW IN FOLDER / 文件位置**:直接用该档案的真实 `path` 调 `/reveal`,在资源管理器中打开其所在位置。 + +改动点: + +- `src/main.ts:513` 的 `` 换成 `