diff --git a/.gitignore b/.gitignore index 73bb17e..63a3a5a 100644 --- a/.gitignore +++ b/.gitignore @@ -28,3 +28,6 @@ __pycache__/ Thumbs.db results/local-*.json prompt0.md + +# Agent instructions belong in the user-level DSH home, not in this repo. +AGENTS.md diff --git a/docs/dan.md b/docs/dan.md index 6a488d3..a7ccf0f 100644 --- a/docs/dan.md +++ b/docs/dan.md @@ -156,23 +156,70 @@ active ──四道全部通关──→ cleared(通过) **第一层是单题结算**,在每道题出成绩时弹出:用的就是普通计时练习那张结算皮(`public/dx-result.css` 的 `.dx-result-sheet`),显示达成率、此前最佳、评级、用时、判定计数、单题 rating,以及结算那一刻的 DX Rating 总分与增量。这段对比读的是 `practice_timers.settlement_json` 里冻结的那一份 —— 段位認定不另算一套分。 -**第二层是整轮结算页**,四道打完(或中途失败)后出现,版式照原作的段位認定结算(`Process/Course/CourseResultProcess`): - -- **奶油色纸盘**:橙红双圈描边,两侧各一条水引红绳(蝴蝶结 + 竖绳 + 流苏)。 -- **逐曲卡**:每道一张。左侧一枚八角的「可」印(没通关是「不可」,未开始是灰的);右侧是难度配色的横卡 —— 封面那一格放题号(我们抽的是 CF 题、没有曲绘,放题号比放装饰诚实),然后是 `STAGE 0n` / 难度 / 题目名,下面一行「达成率」+ 金色描边的大数字 + 判定(评级,或超时/中断),最右边是 `LV`、`DX分数`(该题算出的单题 rating)与这道题的限时。 -- **底部一行**:左边判定框写「合格」/「不合格」与通关题数;中间一颗心;右边「总达成率」与 `DX分数`。 -- **右下角**一个「好」按钮,旁边是「再来一轮 · 档位」。 +**第二层是整轮结算页**,四道打完(或中途失败)后出现,直接上原作的画面素材(`Process/Course/CourseResultProcess` 那一屏): + +段位認定结算页,虚拟演示数据 + +- **底图**:整屏就是原作那张 980×928 的成品图 —— `UI_DNM_Result_Base_01`(「段位認定」)与 `UI_DNM_Result_Base_03`(「ランダム段位認定」)。页头文字、格纹页眉、两侧水引(蝴蝶结 + 竖绳 + 流苏)、下半部色带与左下角那格白框**全部烘焙在底图里**,所以档位挑战与随机段位各用自己那一版,标题不再由页面画。 +- **底图上那几块白不补**:下半部留了三处**挖白的空面板** —— 量下来是「标签白格」`(503,663) 354×50`(中间被折页的斜边切开,上半段 `503–708`、下半段被切成两小段)与「DX 分数白牌」`(608,810) 360×80`(左端还有个空着的圆白圆头)。那是原作留给运行时文字的位置,运行时会被盖住。我们一开始照「露白像空框」的思路用 `scripts/patch-maimai-result-band.py` 把这几块续画填掉了 —— 那是错的:官方版式本来就要在这三处写字,填掉等于自己把位置抹了。现在补丁脚本已删、底图回到原始素材,标签与大数字就摆回这三格(见下「底部色带」)。 +- **组件按官方坐标拼**:底图之上每一件都放在原作预制体(`Process/Course/CourseResultProcess`、逐题行的 `UI_CMN_KOP_TrackData`)量出来的位置上,尺寸也照官方:逐题行 `x 267–843`、第 1 行 `y 152`、行距 `122`(576 宽 = 官方 640 × 0.9);行首的印是 `108×108`(官方原始尺寸,不是缩过的);达成率数字字高 `44`;行内的每一项(边框本体、曲绘槽、歌名条、白盒、底带、右下白牌、徽章)另外照官方贴图 `UI_CMN_RSL_KopMBase_*` 逐像素量了一遍,换算与数字写在 `public/dan.css` 的「逐题行」一节,`scripts/dan-settlement-e2e.mjs` 里逐条量它们(`边框本体照原版量出来的比例摆`、`歌名条占顶部一整行`、`DX 分数在卡片右下角`、`限时写进底带左侧`、`评级徽章放进右上那块紫场`),底部白框、白牌的坐标也是拿底图逐行数像素量出来的。 +- **逐题卡**:每道一张。左侧一枚原作印(`UI_DNM_Icon_Result_01` 可 / `_02` 不可,未抽到的变灰);右侧横卡用原作底板 `UI_DNM_Result_musicBase_01` 撑起来,上面再压一层**乐曲边框** —— 版式照官方 `UI_CMN_RSL_KopMBase_*` 逐像素量出来重画:外圈 **2px 主色环** + 一块平的主色场 + 底部**浅色带** + 左侧方曲绘槽 + **深蓝歌名条(占顶部一整行)** + 白色达成率盒(**右下角一道大 R 弧** —— 原版那块「右侧带缺口的面板」)+ **右下角白牌**(原版烘的是日文 `でらっくスコア`)。**换算**:导出的原版是 636×136 的精裁版(官方设计 640×140,左右上下各被紧裁掉 2px 透明边),我们的行内区 576×126 = 官方 ×0.9,所以 `行内坐标 = (原版坐标 + 2) × 0.9`;边框本体落在行内 `(3.6, 3.6)`、尺寸 `568.8×118.8`,边框内的组件再减去这 3.6。**颜色不烘死**:官方那五张(BSC/ADV/EXP/MST/MST_Re)把难度色和难度名都印在图里,套不上我们自己的 CF 分档,所以主色场取 `--tc1`、外圈环取 `--tc2`(`cfRatingColor(...).tone` → `TRACK_TONE`)—— **一块平色**,不做上下渐变;曲绘槽与底部浅色带取主色的**浅色版本**(`color-mix(in srgb, var(--tc1) 42% / 55%, #fff)`),原版那格是曲绘,换成纯白牌会像在卡上挖了个洞。 +- **信息分配(照 2026-10-10 的口径重排)**:**最上面歌名占一整行**(原版左上那块难度名牌删掉,见下);左侧曲绘槽里是完整题号(`1554C` 这种「场次 + 小问」)与这题在 CF 上的 rating;达成率大数字 + 官方彩色「%」字形排在**白盒里**,数字**右对齐**:满 100 的那串右端固定在同一个位置,不到 100 的少一位数就少在左边(`96.0000%` 的右端仍与 `100.8000%` 齐平);**DX 分数挪到卡片右下角那块白牌**(行内 `450.9, 97.2`、`119.7×23.4` 胶囊,原版 `でらっくスコア` 那一格);**限时写进底带的左侧**(原版那条浅色带是空的一块,行内从 `122.4` 起);逐题评级徽章放进**右上那块紫场**(白盒右端…边框内右沿 / 歌名条下沿…右下白牌上沿 = `119.5×68.4`,原版空着,白盒右下角那道大 R 弧就是给它让位的)。 + - 官方那一格难度名牌(烘的是 `MASTER 大師` 这类难度名)**整个删掉了**:同一张卡上题号、达成率、单题 rating 已经把信息说全,再写一遍档位名既重复、又占掉半个标题条。歌名条因此从行内 `x 122.4` 一路铺到 `565.2`(边框内 `118.8 + 442.8`,右端离边框外沿 7.2px,与原版一致)。 +- **评级徽章用游戏内那套**:`UI_GAM_Rank_*`(`D`/`C`/`B`/`BB`/`BBB`/`A`/`AA`/`AAA`/`S`/`S+`/`SS`/`SS+`/`SSS`/`SSS+` 单级各一张,源图紧裁 36–110 × 42–44)。早先用的是 `UI_CMN_TabTitle_Rank_*` —— 那是**页签标题**用的区间图:AAA 那张画的是「A~AAA」、BBB 那张是「~BBB」,摆在逐题卡片上根本不是那一级的评级,而且它没有 D/C/B/BB/A/AA 这些级,低分全挤在同一张牌上。换过来之后 14 级一级一张,框按 `99×40`(官方 SSS+ 那张紧裁图 110×44 ×0.9)按高度贴齐,摆在右上那块紫场里。 +- **底部色带**:左下白框那一格原作放**段位名牌**,我们发档位不发段位名,就把档位名(`初级`/`中级`/`上级`/`超上级`/…)照原版那样写进去 —— 深棕重字 + 很淡的米白描边(`-webkit-text-stroke` + `paint-order: stroke fill`)+ 一点投影。原版那两个字是**烘死的毛笔字贴图**(`UI_DNM_DaniTitle_*` 只有日文段位名,套不上我们的档位),只能用系统字体近似:字体栈取 `Noto Serif SC` 的 **900** 字重(次选系统宋体/楷体那一类衬线),SimSun 那种细宋笔画只有原版一半粗。名字超过两个字自动缩号(`data-len`)。名字下面一行小字写「合格 · 4 / 4 道通关」。中间是原作的剩余生命底盘(`UI_DNM_Base_Life_01` 绿 / `_03` 红),照官方放大到 `200×200`,心里那个数字是「四道里过了几道」(数字图集里没有「/」字形,退回普通文字会被两边数字的描边吃掉,所以不写 `4/4`)。右边两块**照底图预留的白格**摆:`总达成率` 写进上面那格白(实测白区是 `x 503..708`,右边那截 `753..856` 被折页斜边切开,所以四个字按 `503..708` **居中**、字号 23),官方数字图集拼的大数字落在下面的色带上(`y 727`、字高 66,右端对齐 `x 950`);`DX分数` 与它的数字写进下面那条白牌(`(608,810) 360×80`,标签在左、数字在右,两端各留 46px,左端那个空的白圆头按原样空着,文字靠上排 —— 白牌下半截也被折页切掉一块),两处都是和标题条同一个深蓝 `#16306b`。 +- 数字不是 CSS 字体画的:**官方数字图集逐格切**。图集是规整 4×4 网格,格子由同名 Sprite 定 rect;白字族每格拆成「官方 `_Outline` 剪影当遮罩上深色(描边)+ 官方填充图集」两层 —— 图集本身是**纯白剪影**(游戏运行时才染色),background 层没法单独染色,所以用两个真元素叠。这套「剪影 + 运行时染色」的能力也直接用在了 DX 分数上(染成帧上的深蓝 `#0d2a63`,见下)。对照表(一张张看出来的):白字族 `[0-9]` `[10]'+'` `[11]'-'` `[12]','` `[13]'·'`(居中的圆点,就是这套字体的小数点)`[14]'%'`;分数族 `[10]'+'` `[11]'/'` `[12]'%'` `[13]'.'`。 +- **数字怎么排**:**一个字形一格,格子之间按 `advance` 等距** —— 就是图集自己的排法,小数点、逗号这些跟数字走同一条路,不开特例。唯一要做的是把每格右边的留白收掉:图集格宽比字形宽(分数族格宽 74、字形只有 60–63),照格宽排会读成「一个字一个字分开」,所以每格收回 `格宽 − advance`(分数族 74→67、90p 89→67、26p 34→24,见 `NUM_FONTS.advance`),最后一格不收回。 + - **小数点**:白字族 `[13]` 那一格是**画在字高中线上的方块**(26p 只有 8×8、90p 24×24),直接排会浮在数字中间像断字符;官方那只方块的**底边压在数字基线上**(在 `official-dani.png` 上量过)。所以这一格照用,只按 `dotBaseline`(该格墨迹底线在格高里的比例:26p 0.775 / 90p 0.6)把它压到基线上 —— 字形来自同一个族,粗细和数字一致,不需要另画也不需要借用别的族。 + - 旁边有小字标签时(两处 `DX分数`)用 `numText` 的 `baseline` 选项:整块数字往下补 `(1 − baseline) × 字高`。flex 认的基线是这一格的**底边**,而图集里数字的墨迹底线在格高的 `baseline` 处,不补这一下数字就浮在标签上面;两处都用 `align-items: baseline` 排,实测标签与数字的基线都落在同一条 y 上。 + - 末尾那个「%」是独立图片(80×80、墨迹 `x 8..70`,比图集里的 `%` 格大一号),按 `` 直排会在数字和 `%` 之间多出一截,所以放大到 `1.1 × 字高` 再用负边距把左边的留白抵掉。 +- **达成率按分数换色**:官方那套 `UI_NUM_Score_0001111_{Blue,Gold,Red}` 是同布局的三色分数图集(自带颜色、不叠描边),末尾的大「%」字形 `UI_RSL_Score_Per_{Blue,Gold,Red}` 同样三色 —— 所以达成率的档位就是三档:`≥100%` 金(SSS/SSS+)、`≥97%` 蓝(S~SS+)、再低红。`scoreTone()` 在 `public/dan.js` 里,换色在同一条 `achievementNumber()` 里做。**官方那张 `UI_NUM_Score_0001111_Base` 是一张空图集**(16 格里一个字形都没有,整张只有几列杂散像素),照它渲染出来就是一片看不见的灰影,别用。三档同时出现的样子: + +四道达成率分别为 100.8 / 98.5 / 96.0 / 92.0 时的结算页:金、蓝、红三色 +- **DX 分数不上分数色**:那一格显示的是单题 rating、不是 DX 分数比率,所以三色专门留给达成率;它用白字族剪影染深蓝(跟标题条、标签同一个深蓝)。**所有「没有成绩」的占位「—」也走这套染色**:白字族是纯白剪影,直接铺在白色达成率框或浅色底图上等于看不见。 +- **说明与按钮在画面之外**(深色底上):「好」按钮与「再来一轮 · 档位」不往那张成品屏里塞;这一轮的范围说明(档位区间、题数、限时、抽题方式、标签)也挪到了成品屏下面 —— 原作那张纸上没有放这行字的位置,硬塞只能压住页头或第一行。 两处刻意的改写: -- 原作那一格写的是**段位名**(例如「十段」)。随机段位認定不发段位名,所以改成写判定结果「合格」/「不合格」。 -- 原作那颗心是**剩余生命**。我们没有生命值这个概念,用它显示「通关几道」(合格绿、失败灰)。 +- 原作那一格写的是**段位名**(例如「十段」)。我们不发段位名(挑战模式只发档位),所以那一格写**档位名**、字重描边照原版那一路走;「合格/不合格」不另盖印,改成名字下面一行小字。 +- 原作那颗心是**剩余生命**。我们没有生命值这个概念,用它显示「四道里过了几道」(全过绿底、失败红底)。 + +两处数字口径与原来的对照:原作的「总达成率」是四首歌达成率之和,这里就是各道达成率之和;原作的 `DX分数` 是四首 DX 分数之和,这里对应各道单题 rating 之和(也就是本轮的 `totalRating`)。**这是展示口径**:那一格不等于 `B50` 里这道题贡献的分数增量,真实增量写在结算屏下面那行说明里。 + +### 结算页的素材来源 + +`public/assets/maimai/` 下那 31 张 PNG 是从 maimai DX 客户端资源里直接提取的,不是重画的: + +| 仓库文件 | 客户端贴图 | 用途 | +| --- | --- | --- | +| `result-dani.png` | `UI_DNM_Result_Base_01` | 「段位認定」整屏底图 980×928 | +| `result-random.png` | `UI_DNM_Result_Base_03` | 「ランダム段位認定」整屏底图 | +| `track-plate.png` | `UI_DNM_Result_musicBase_01` | 逐题行底板 576×124 | +| `stamp-clear.png` / `stamp-fail.png` | `UI_DNM_Icon_Result_01` / `_02` | 逐题「可」/「不可」印 | +| `num-26p.png` / `num-90p.png` | `UI_CMN_Num_26p` / `90p` | 小 / 大号白字图集(136×160 · 356×420,4×4 格) | +| `num-26p-outline.png` / `num-90p-outline.png` | `UI_CMN_Num_26p_Outline` / `90p` | 同布局的描边剪影(上深色当描边用) | +| `num-score-blue.png` / `-gold.png` / `-red.png` | `UI_NUM_Score_0001111_Blue` / `_Gold` / `_Red` | 达成率的三色分数图集(自带颜色,296×392) | +| `score-per-blue.png` / `-gold.png` / `-red.png` | `UI_RSL_Score_Per_Blue` / `_Gold` / `_Red` | 达成率末尾那个大「%」字形(80×80,同样三色) | +| `gam-rank-sssp.png` … `gam-rank-d.png`(14 张) | `UI_GAM_Rank_SSSp` … `_D` | 逐题评级徽章(游戏内那套,单级各一张) | +| `life-base-green.png` / `life-base-red.png` | `UI_DNM_Base_Life_01` / `_03` | 剩余生命底盘(绿 = 通关 / 红 = 未通关) | + +**故意不搬的一件**:逐题行的乐曲边框 `UI_CMN_RSL_KopMBase_{BSC,ADV,EXP,MST,MST_Re}`(640×140,五种难度各一张)。它是逐题行那一圈底色,但难度色与难度名(BASIC/ADVANCED/EXPERT/MASTER/Re:MASTER)都烘死在图里,套不上我们的 CF 分档,所以照它的版式用 CSS 画一层、颜色交给 `--tc1/--tc2`(见 `public/dan.css` 的「逐题行」一节,尺寸全按那张贴图量出来)。 + +提取脚本留在 `scripts/extract-maimai-assets.py`(开发工具,需要 `UnityPy` + `texture2ddecoder` + `Pillow`,不是应用依赖): + +```bash +python scripts/extract-maimai-assets.py "<含 resources.assets 的游戏目录>" +``` + +贴图对应的**贴图名**是拿 UnityPy 枚举客户端 `resources.assets` 的 `Texture2D` 得到的(那一个文件里有 5916 张,`UI_DNM_*` 家族 159 张)。数字图集的**格位**不用量:每格都有同名 Sprite(`UI_CMN_Num_90p_0 … _14`),网格正好 4×4,`下标 × 格宽/格高` 就是裁切位置;`[12]` 是逗号、`[13]` 是居中的圆点(原作这套字体的小数点就画在字高中线上,不是落在基线上的点),这两格是放大到 6 倍 + 量墨迹包围盒确认的。分数族那三套的格位同样按 4×4 / 74×98 切;**同名的 `Base` 那张是空图集**,是数每格不透明像素数(16 格全是 0)发现的 —— 只看缩略图会以为它有内容。 + +版式数字不是估的:它们是拿 `UnityPy` 读 `CourseResultProcess` / `UI_CMN_KOP_TrackData` 预制体里每个 `RectTransform` 的 anchors / anchoredPosition / pivot / sizeDelta,再按「美术坐标 = 屏幕中心 + 局部位置」换算到 980×928 底图上(逐题行 576×126 = 官方 640×140 × 0.9)。乐曲边框那几块(边框本体、外圈环、歌名条、曲绘槽、白盒、底带、右下 DX 白牌)另外拿官方贴图 `UI_CMN_RSL_KopMBase_MST.png` 逐行扫像素复核过一遍(每块的首像素…末像素,再按 `(坐标 + 2) × 0.9` 换算),两边对得上。早先那版是按底图像素分区(纸区上边界 `y=140/928=15.1%`、底部色带从 `71.6%` 起、左下白框 `x 8.7–31.6% / y 73.3–94.8%`)粗摆的,现在换成官方坐标后位置差了几十像素 —— 也就是「组件没对齐」的那几十像素。 -两处数字口径与原来的对照:原作的「总达成率」是四首歌达成率之和,这里就是各道达成率之和;原作的 `DX分数` 是四首 DX 分数之和,这里对应各道单题 rating 之和(也就是本轮的 `totalRating`)。段位名那一格改成写判定结果,所以「总达成率」下面不再有段位线。 +盘面锁死 `980×928`(固定宽高,不再用 `aspect-ratio` 缩放),窄屏让盘面横向滚动而不是压扁 —— 内部一律绝对像素,缩放交给浏览器等比处理,这样「一个数字多宽」这类关系可以直接抄官方数字。 -对照的素材包元素(用 UnityPy 解 `resources.assets` 的 GameObject / RectTransform 得到):那枚「可」印是 `UI_DNM_Stamp` 的 `Clear` / `NoClear`,两侧绳子是 `UI_DNM_Separator`,底部那块是 `UI_DNM_DaniData`。 +一处已知的将就:**单题模式(自定义条件/每日一题)也复用这张结算盘**,它们是 1 道题,底图借的是档位那一版,所以页头会写着「段位認定」。这不是这轮引入的 —— 改造前那张 CSS 皮的标题也是写死「段位認定」(`#danRunTitle` 一直没按 `kind` 变过),这一版只是把标题从 CSS 文字换成了烘进底图的字。单题模式要专属皮的话,得另找一张不带页头的纸底图,或者按 `kind` 裁掉底图上那 15.1% 的页头带。 -**贴图没有从素材包里搬**:全部用 CSS 重画。游戏美术有版权,打进一个 MIT 仓库、再提到上游是不合适的;这里只沿用版式与构成。盘面用大圆角而不是正圆 —— 四道题卡竖排进正圆,在常见宽度下会挤到不可读。 +**这些美术素材有版权**:它们不属于这个仓库的 MIT 许可范围,也不随上游项目走。只在自用/自部署的实例里使用;要不要把它们留在分支上,由部署的人自己判断。这一段替换了早先「用 CSS 重画、不搬游戏美术」的做法,是按 2026-10-09 的明确要求改的。 ### 下一道由你点,不由轮询抽 diff --git a/docs/development.md b/docs/development.md index 0ee4a95..1a6d7fb 100644 --- a/docs/development.md +++ b/docs/development.md @@ -49,7 +49,14 @@ public/ index.html 面板骨架(含账号管理区) app.js Feed 渲染、筛选器、折叠、账号管理与同步进度 dx.html / dx.js DX Rating 榜(b35 + b15),填写完成用时 - styles.css 深色主题,无外部字体或 CDN + styles.css / ui.css 重做前那一版的配色与组件样式,现在是皮肤的浅色底子 + theme.css maimai 官网皮肤层,参数取自 vendor/maimai/style.css + theme-dark.css 深色模式层,规则一律带 [data-mm-theme="dark"] 前缀 + theme-night.css 夜版背景,只换 /assets/night/ 那套贴图 + theme.js 主题开关、移动端菜单、回到顶部(换主题的唯一写入口) + bg.html 背景 iframe 的宿主页:官网那份动态背景 + vendor/maimai/** 官网素材原样快照(style.css / img / fonts,见其 README) + assets/night/** 夜版贴图,由官方素材离线重上色生成 tests/ phase1.test.ts 旧接口回归测试 phase2.test.ts 迁移、适配器、同步和调度测试 @@ -68,6 +75,21 @@ data/ 首次运行生成,本地数据不提交 data/backups/ npm run backup 生成的快照目录 ``` +## 前端皮肤与深色模式 + +皮肤复刻 maimai DX 国际版官网 PRiSM PLUS 时期的样式:`public/vendor/maimai/` 是原样快照(只改过 URL), +落到本站组件上的那一层全在 `public/theme.css`,与官方的有意偏差集中在它末尾那节,改之前先看那节。 + +- 主题落在 ``,偏好存在 `localStorage['mm-theme']`,没手动选过就跟系统。 +- 各页 `` 里有一小段脚本在首帧前定好主题(六页重复同一段是有意的:抽成外链会先亮后暗)。 +- 载入顺序 `fonts.css → styles.css → 各页自己的 CSS → ui.css → theme.css → theme-dark.css`, + 深色层一律带 `[data-mm-theme="dark"]` 前缀,所以浅色那套不用改。 +- 画布里的图是 JS 画的,CSS 管不到:`theme.js` 换主题时发 `mm-theme-change`,`stat-charts.js` 收到就按 + 当前主题重画(配色在它自己的 PALETTE 里)。 +- 页面不引外部字体或 CDN,离线可用:M PLUS 1p 只自托管 latin / latin-ext 子集,中文回退系统字体。 +- `bg.html` 是背景 iframe 的宿主页(结构取自归档快照,另加三行重置与夜版切换脚本),放在 `public/` 根下, + 好让 `vendor/maimai/` 只留原样素材。 + ## 数据模型 - `users`:一个本地人物。`is_self` 标记自己(最多一个,是主视图与 DX 榜的锚点);`is_followed`(v7)标记**关注** —— 只决定「出不出在主视图」,不限数量,且与 `is_self` 是两层。详见下文「关注 ≠ 存在」。 diff --git a/docs/images/dan-card-vs-original.png b/docs/images/dan-card-vs-original.png new file mode 100644 index 0000000..0d1363f Binary files /dev/null and b/docs/images/dan-card-vs-original.png differ diff --git a/docs/images/dan-card.png b/docs/images/dan-card.png new file mode 100644 index 0000000..597e3b0 Binary files /dev/null and b/docs/images/dan-card.png differ diff --git a/docs/images/dan-card2.png b/docs/images/dan-card2.png new file mode 100644 index 0000000..1f93601 Binary files /dev/null and b/docs/images/dan-card2.png differ diff --git a/docs/images/dan-dialog-mixed.png b/docs/images/dan-dialog-mixed.png new file mode 100644 index 0000000..63b188b Binary files /dev/null and b/docs/images/dan-dialog-mixed.png differ diff --git a/docs/images/dan-page-mixed.png b/docs/images/dan-page-mixed.png new file mode 100644 index 0000000..889d7a5 Binary files /dev/null and b/docs/images/dan-page-mixed.png differ diff --git a/docs/images/dan-page-perfect.png b/docs/images/dan-page-perfect.png new file mode 100644 index 0000000..f6e38f6 Binary files /dev/null and b/docs/images/dan-page-perfect.png differ diff --git a/docs/images/dan-settlement-mixed.jpg b/docs/images/dan-settlement-mixed.jpg new file mode 100644 index 0000000..8c74b83 Binary files /dev/null and b/docs/images/dan-settlement-mixed.jpg differ diff --git a/docs/images/dan-settlement.jpg b/docs/images/dan-settlement.jpg new file mode 100644 index 0000000..5e95901 Binary files /dev/null and b/docs/images/dan-settlement.jpg differ diff --git a/docs/images/original-card/UI_CMN_RSL_AllScorekBase.png b/docs/images/original-card/UI_CMN_RSL_AllScorekBase.png new file mode 100644 index 0000000..243782f Binary files /dev/null and b/docs/images/original-card/UI_CMN_RSL_AllScorekBase.png differ diff --git a/docs/images/original-card/UI_CMN_RSL_JaketTrack.png b/docs/images/original-card/UI_CMN_RSL_JaketTrack.png new file mode 100644 index 0000000..217d5db Binary files /dev/null and b/docs/images/original-card/UI_CMN_RSL_JaketTrack.png differ diff --git a/docs/images/original-card/UI_CMN_RSL_JaketTrackNo_0.png b/docs/images/original-card/UI_CMN_RSL_JaketTrackNo_0.png new file mode 100644 index 0000000..00df414 Binary files /dev/null and b/docs/images/original-card/UI_CMN_RSL_JaketTrackNo_0.png differ diff --git a/docs/images/original-card/UI_CMN_RSL_KopMBase_ADV.png b/docs/images/original-card/UI_CMN_RSL_KopMBase_ADV.png new file mode 100644 index 0000000..805dfa1 Binary files /dev/null and b/docs/images/original-card/UI_CMN_RSL_KopMBase_ADV.png differ diff --git a/docs/images/original-card/UI_CMN_RSL_KopMBase_BSC.png b/docs/images/original-card/UI_CMN_RSL_KopMBase_BSC.png new file mode 100644 index 0000000..752cde2 Binary files /dev/null and b/docs/images/original-card/UI_CMN_RSL_KopMBase_BSC.png differ diff --git a/docs/images/original-card/UI_CMN_RSL_KopMBase_EXP.png b/docs/images/original-card/UI_CMN_RSL_KopMBase_EXP.png new file mode 100644 index 0000000..3af4b0d Binary files /dev/null and b/docs/images/original-card/UI_CMN_RSL_KopMBase_EXP.png differ diff --git a/docs/images/original-card/UI_CMN_RSL_KopMBase_MST.png b/docs/images/original-card/UI_CMN_RSL_KopMBase_MST.png new file mode 100644 index 0000000..be5f3f9 Binary files /dev/null and b/docs/images/original-card/UI_CMN_RSL_KopMBase_MST.png differ diff --git a/docs/images/original-card/UI_CMN_RSL_KopMBase_MST_Re.png b/docs/images/original-card/UI_CMN_RSL_KopMBase_MST_Re.png new file mode 100644 index 0000000..e649a75 Binary files /dev/null and b/docs/images/original-card/UI_CMN_RSL_KopMBase_MST_Re.png differ diff --git a/docs/images/original-card/UI_DNM_Result_musicBase_01.png b/docs/images/original-card/UI_DNM_Result_musicBase_01.png new file mode 100644 index 0000000..034a719 Binary files /dev/null and b/docs/images/original-card/UI_DNM_Result_musicBase_01.png differ diff --git a/public/algorithm-dx-icon-light.svg b/public/algorithm-dx-icon-light.svg new file mode 100644 index 0000000..c359168 --- /dev/null +++ b/public/algorithm-dx-icon-light.svg @@ -0,0 +1 @@ + diff --git a/public/assets/maimai/gam-rank-a.png b/public/assets/maimai/gam-rank-a.png new file mode 100644 index 0000000..2e3a5f6 Binary files /dev/null and b/public/assets/maimai/gam-rank-a.png differ diff --git a/public/assets/maimai/gam-rank-aa.png b/public/assets/maimai/gam-rank-aa.png new file mode 100644 index 0000000..1574915 Binary files /dev/null and b/public/assets/maimai/gam-rank-aa.png differ diff --git a/public/assets/maimai/gam-rank-aaa.png b/public/assets/maimai/gam-rank-aaa.png new file mode 100644 index 0000000..d2e16e4 Binary files /dev/null and b/public/assets/maimai/gam-rank-aaa.png differ diff --git a/public/assets/maimai/gam-rank-b.png b/public/assets/maimai/gam-rank-b.png new file mode 100644 index 0000000..d2bb9a8 Binary files /dev/null and b/public/assets/maimai/gam-rank-b.png differ diff --git a/public/assets/maimai/gam-rank-bb.png b/public/assets/maimai/gam-rank-bb.png new file mode 100644 index 0000000..248c4b7 Binary files /dev/null and b/public/assets/maimai/gam-rank-bb.png differ diff --git a/public/assets/maimai/gam-rank-bbb.png b/public/assets/maimai/gam-rank-bbb.png new file mode 100644 index 0000000..63ab8ee Binary files /dev/null and b/public/assets/maimai/gam-rank-bbb.png differ diff --git a/public/assets/maimai/gam-rank-c.png b/public/assets/maimai/gam-rank-c.png new file mode 100644 index 0000000..4bf5f27 Binary files /dev/null and b/public/assets/maimai/gam-rank-c.png differ diff --git a/public/assets/maimai/gam-rank-d.png b/public/assets/maimai/gam-rank-d.png new file mode 100644 index 0000000..b8e69ae Binary files /dev/null and b/public/assets/maimai/gam-rank-d.png differ diff --git a/public/assets/maimai/gam-rank-s.png b/public/assets/maimai/gam-rank-s.png new file mode 100644 index 0000000..23a458a Binary files /dev/null and b/public/assets/maimai/gam-rank-s.png differ diff --git a/public/assets/maimai/gam-rank-sp.png b/public/assets/maimai/gam-rank-sp.png new file mode 100644 index 0000000..aa152fa Binary files /dev/null and b/public/assets/maimai/gam-rank-sp.png differ diff --git a/public/assets/maimai/gam-rank-ss.png b/public/assets/maimai/gam-rank-ss.png new file mode 100644 index 0000000..107b8d4 Binary files /dev/null and b/public/assets/maimai/gam-rank-ss.png differ diff --git a/public/assets/maimai/gam-rank-ssp.png b/public/assets/maimai/gam-rank-ssp.png new file mode 100644 index 0000000..7eb5baf Binary files /dev/null and b/public/assets/maimai/gam-rank-ssp.png differ diff --git a/public/assets/maimai/gam-rank-sss.png b/public/assets/maimai/gam-rank-sss.png new file mode 100644 index 0000000..a2e97e5 Binary files /dev/null and b/public/assets/maimai/gam-rank-sss.png differ diff --git a/public/assets/maimai/gam-rank-sssp.png b/public/assets/maimai/gam-rank-sssp.png new file mode 100644 index 0000000..92734a5 Binary files /dev/null and b/public/assets/maimai/gam-rank-sssp.png differ diff --git a/public/assets/maimai/life-base-green.png b/public/assets/maimai/life-base-green.png new file mode 100644 index 0000000..0e98d0d Binary files /dev/null and b/public/assets/maimai/life-base-green.png differ diff --git a/public/assets/maimai/life-base-red.png b/public/assets/maimai/life-base-red.png new file mode 100644 index 0000000..7903fec Binary files /dev/null and b/public/assets/maimai/life-base-red.png differ diff --git a/public/assets/maimai/num-26p-outline.png b/public/assets/maimai/num-26p-outline.png new file mode 100644 index 0000000..4b1c14c Binary files /dev/null and b/public/assets/maimai/num-26p-outline.png differ diff --git a/public/assets/maimai/num-26p.png b/public/assets/maimai/num-26p.png new file mode 100644 index 0000000..23d4313 Binary files /dev/null and b/public/assets/maimai/num-26p.png differ diff --git a/public/assets/maimai/num-90p-outline.png b/public/assets/maimai/num-90p-outline.png new file mode 100644 index 0000000..4376753 Binary files /dev/null and b/public/assets/maimai/num-90p-outline.png differ diff --git a/public/assets/maimai/num-90p.png b/public/assets/maimai/num-90p.png new file mode 100644 index 0000000..ad1078c Binary files /dev/null and b/public/assets/maimai/num-90p.png differ diff --git a/public/assets/maimai/num-score-blue.png b/public/assets/maimai/num-score-blue.png new file mode 100644 index 0000000..342b9d9 Binary files /dev/null and b/public/assets/maimai/num-score-blue.png differ diff --git a/public/assets/maimai/num-score-gold.png b/public/assets/maimai/num-score-gold.png new file mode 100644 index 0000000..049d37a Binary files /dev/null and b/public/assets/maimai/num-score-gold.png differ diff --git a/public/assets/maimai/num-score-red.png b/public/assets/maimai/num-score-red.png new file mode 100644 index 0000000..9d7ceea Binary files /dev/null and b/public/assets/maimai/num-score-red.png differ diff --git a/public/assets/maimai/result-dani.png b/public/assets/maimai/result-dani.png new file mode 100644 index 0000000..abb5cf8 Binary files /dev/null and b/public/assets/maimai/result-dani.png differ diff --git a/public/assets/maimai/result-random.png b/public/assets/maimai/result-random.png new file mode 100644 index 0000000..71dea32 Binary files /dev/null and b/public/assets/maimai/result-random.png differ diff --git a/public/assets/maimai/score-per-blue.png b/public/assets/maimai/score-per-blue.png new file mode 100644 index 0000000..bd0ae7f Binary files /dev/null and b/public/assets/maimai/score-per-blue.png differ diff --git a/public/assets/maimai/score-per-gold.png b/public/assets/maimai/score-per-gold.png new file mode 100644 index 0000000..707bdf8 Binary files /dev/null and b/public/assets/maimai/score-per-gold.png differ diff --git a/public/assets/maimai/score-per-red.png b/public/assets/maimai/score-per-red.png new file mode 100644 index 0000000..8e43e27 Binary files /dev/null and b/public/assets/maimai/score-per-red.png differ diff --git a/public/assets/maimai/stamp-clear.png b/public/assets/maimai/stamp-clear.png new file mode 100644 index 0000000..b877a64 Binary files /dev/null and b/public/assets/maimai/stamp-clear.png differ diff --git a/public/assets/maimai/stamp-fail.png b/public/assets/maimai/stamp-fail.png new file mode 100644 index 0000000..3c6d8d3 Binary files /dev/null and b/public/assets/maimai/stamp-fail.png differ diff --git a/public/assets/maimai/track-plate.png b/public/assets/maimai/track-plate.png new file mode 100644 index 0000000..7515a67 Binary files /dev/null and b/public/assets/maimai/track-plate.png differ diff --git a/public/assets/night/MANIFEST.txt b/public/assets/night/MANIFEST.txt new file mode 100644 index 0000000..c26c8aa --- /dev/null +++ b/public/assets/night/MANIFEST.txt @@ -0,0 +1,20 @@ +source: public/vendor/maimai/img/prism/bg/ (PRiSM PLUS era, recoloured offline) +generated: one-off local tool, not in the repo (see README.md) +aurora-pc.png 197598 18a6832610a9e21f3a3bb1a4c7e185b7 +aurora-sp.png 145513 3cde15f7d5434c57ec879bb231f97d03 +cloud-back-c.png 8409 e531b5609f02a493988a909c86bbefd8 +cloud-back-l.png 9202 6115d9c264af03399bae8424e31b2942 +cloud-back-r.png 24061 e94f971ef5118e59a200ff43f92acd1a +cloud-front-c.png 12656 3092cc9a619adcff2af05e40d8ca8438 +cloud-front-l.png 14177 cd7ad6c0810d073624ff7bbc4f3b907c +cloud-front-r.png 14604 80ad1fd499ea7cfccdbdacf9501cc958 +pattern.png 391 b6d4555a3ef84dc7254f0a2fae1ea292 +rainbow-back-sp.png 20168 4f87fa4e84e99c13cb2e5eeb8c5ae34f +rainbow-bottom-pc.png 45950 d8030f1827eb986ba60d92bc14be7c60 +rainbow-bottom-sp.png 28625 828af06998ae40d221717ddf672d2746 +rainbow-pc.png 30680 ef0394046890f7ada7c6fe734739cbf8 +rainbow-sp.png 27202 96b51196cc94ca6fff993d745cc750e5 +shines-pc.png 13572 632fde21eee7ebdb3e66847f92f2d7a2 +shines-sp.png 12218 8c6c281587dc92f6b232d7996dc804fb +sky-frame.png 449 9fb8230b6de747cbe4476fe9058b0221 +sky.png 405 863652de17136067c9ac1dbc6032111a diff --git a/public/assets/night/README.md b/public/assets/night/README.md new file mode 100644 index 0000000..9ecc79e --- /dev/null +++ b/public/assets/night/README.md @@ -0,0 +1,34 @@ +# assets/night —— 夜版背景贴图 + +夜版背景用的素材:结构和参数都还是官网那份 `style.css`,只是把 `vendor/maimai/img/prism/bg/` +里那几张贴图按夜色重画了一遍。谁换哪一张、在哪个断点换,写在 `public/theme-night.css` 里。 + +## 来源与做法 + +- 底图来自 `public/vendor/maimai/img/prism/bg/**`(官方原图,未改动;字节与 MD5 见那份 MANIFEST)。 +- 重上色是离线做的一次性步骤:按原图的亮度分层,把白/亮部压成夜蓝、保留原来的粉/橙描边。 + 做法与每个参数的来由写在生成脚本的注释里;生成脚本是一次性的本地工具,没有进仓库。 +- **带 `-sp` / `-pc` 的素材必须跟着官方的断点走**:官方在基础规则里用 `_sp`、在 + `@media (min-width:769px)` 里换成 `_pc`(尺寸也各不相同)。只出一份的话,宽屏会拿手机那张 + 顶 PC 那格 —— 彩虹两端那片云的 SP 版是被图片边界切平的窄裁版(左右两边 85% / 44% 的像素 + 是实心的),铺进 PC 那格就会在画面中间露出两条平直的切边。 +- 三处贴图在夜里额外淡了边,都是「官方素材被图片边界切平、白天靠底色看不出来、夜里露出来」这一类: + 彩虹两端那片云的 PC 版底边淡 12%、曙光两端各淡 4%(`repeat-x` 的接缝)、彩虹后面那片云两端各淡 6%。 +- 月亮和流星直接用官方素材,没有派生版本(夜里它们本来就该是最亮的)。 + +## 文件 + +| 文件 | 对应官方贴图 | 说明 | +| --- | --- | --- | +| `sky-frame.png` | — | `.bg` 圆角外那圈底色(官方是 CSS 渐变,这里做成一条竖向贴图) | +| `sky.png` | — | `.bg--gradient` 的内层渐变,同上 | +| `pattern.png` | `bg_pattern.png` | 渐变上的图案叠加 | +| `shines-sp.png` / `shines-pc.png` | `bg_shines_sp/_pc.png` | 闪点(两张长宽比完全不同) | +| `cloud-back-{l,c,r}.png` | `bg_cloud_back_*.png` | 三片远云:白体换夜蓝,暖色描边保留 | +| `cloud-front-{l,c,r}.png` | `bg_cloud_front_*.png` | 三片近云,同上 | +| `rainbow-bottom-sp.png` / `-pc.png` | `bg_rainbow_btm_sp/_pc.png` | 彩虹两端那两片云(PC 版底边淡出) | +| `rainbow-back-sp.png` | `bg_rainbow_back_sp.png` | 彩虹后面那片云(官方只有一张,两端淡出) | +| `rainbow-sp.png` / `rainbow-pc.png` | `bg_rainbow_sp/_pc.png` | 月虹(降饱和) | +| `aurora-sp.png` / `aurora-pc.png` | `bg_aurora_sp/_pc.png` | 曙光,1:1 重上色,两端各淡 4% | + +`MANIFEST.txt` 记录每个文件的字节数与 MD5,跟 `vendor/maimai/img/MANIFEST.txt` 同一个格式。 \ No newline at end of file diff --git a/public/assets/night/aurora-pc.png b/public/assets/night/aurora-pc.png new file mode 100644 index 0000000..08e0fb8 Binary files /dev/null and b/public/assets/night/aurora-pc.png differ diff --git a/public/assets/night/aurora-sp.png b/public/assets/night/aurora-sp.png new file mode 100644 index 0000000..649001b Binary files /dev/null and b/public/assets/night/aurora-sp.png differ diff --git a/public/assets/night/cloud-back-c.png b/public/assets/night/cloud-back-c.png new file mode 100644 index 0000000..24d7145 Binary files /dev/null and b/public/assets/night/cloud-back-c.png differ diff --git a/public/assets/night/cloud-back-l.png b/public/assets/night/cloud-back-l.png new file mode 100644 index 0000000..4388e46 Binary files /dev/null and b/public/assets/night/cloud-back-l.png differ diff --git a/public/assets/night/cloud-back-r.png b/public/assets/night/cloud-back-r.png new file mode 100644 index 0000000..17cd71d Binary files /dev/null and b/public/assets/night/cloud-back-r.png differ diff --git a/public/assets/night/cloud-front-c.png b/public/assets/night/cloud-front-c.png new file mode 100644 index 0000000..4eced6b Binary files /dev/null and b/public/assets/night/cloud-front-c.png differ diff --git a/public/assets/night/cloud-front-l.png b/public/assets/night/cloud-front-l.png new file mode 100644 index 0000000..bd9bb01 Binary files /dev/null and b/public/assets/night/cloud-front-l.png differ diff --git a/public/assets/night/cloud-front-r.png b/public/assets/night/cloud-front-r.png new file mode 100644 index 0000000..b284226 Binary files /dev/null and b/public/assets/night/cloud-front-r.png differ diff --git a/public/assets/night/pattern.png b/public/assets/night/pattern.png new file mode 100644 index 0000000..7a605d4 Binary files /dev/null and b/public/assets/night/pattern.png differ diff --git a/public/assets/night/rainbow-back-sp.png b/public/assets/night/rainbow-back-sp.png new file mode 100644 index 0000000..9b11630 Binary files /dev/null and b/public/assets/night/rainbow-back-sp.png differ diff --git a/public/assets/night/rainbow-bottom-pc.png b/public/assets/night/rainbow-bottom-pc.png new file mode 100644 index 0000000..1c94d0e Binary files /dev/null and b/public/assets/night/rainbow-bottom-pc.png differ diff --git a/public/assets/night/rainbow-bottom-sp.png b/public/assets/night/rainbow-bottom-sp.png new file mode 100644 index 0000000..fcc718b Binary files /dev/null and b/public/assets/night/rainbow-bottom-sp.png differ diff --git a/public/assets/night/rainbow-pc.png b/public/assets/night/rainbow-pc.png new file mode 100644 index 0000000..3e7b308 Binary files /dev/null and b/public/assets/night/rainbow-pc.png differ diff --git a/public/assets/night/rainbow-sp.png b/public/assets/night/rainbow-sp.png new file mode 100644 index 0000000..b91d9e4 Binary files /dev/null and b/public/assets/night/rainbow-sp.png differ diff --git a/public/assets/night/shines-pc.png b/public/assets/night/shines-pc.png new file mode 100644 index 0000000..c287668 Binary files /dev/null and b/public/assets/night/shines-pc.png differ diff --git a/public/assets/night/shines-sp.png b/public/assets/night/shines-sp.png new file mode 100644 index 0000000..3a49169 Binary files /dev/null and b/public/assets/night/shines-sp.png differ diff --git a/public/assets/night/sky-frame.png b/public/assets/night/sky-frame.png new file mode 100644 index 0000000..0fa05d8 Binary files /dev/null and b/public/assets/night/sky-frame.png differ diff --git a/public/assets/night/sky.png b/public/assets/night/sky.png new file mode 100644 index 0000000..96a03ab Binary files /dev/null and b/public/assets/night/sky.png differ diff --git a/public/bg.html b/public/bg.html new file mode 100644 index 0000000..bacd4d4 --- /dev/null +++ b/public/bg.html @@ -0,0 +1,95 @@ + + + + + +maimai DX · PRiSM PLUS 动态背景 + + + + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + \ No newline at end of file diff --git a/public/circle.html b/public/circle.html index 479cc26..b8ad56b 100644 --- a/public/circle.html +++ b/public/circle.html @@ -1,14 +1,16 @@ -题友圈 · Algorithm DX +题友圈 · Algorithm DX + - + +
TOGETHER, ONE PROBLEM AT A TIME

每一份坚持,都有回响。

看看关注的人在挑战什么,也为自己的下一题寻找灵感。

+ 添加题友
@@ -24,4 +30,6 @@

题友的新鲜事

-
+TOP + + diff --git a/public/dan.css b/public/dan.css index fd3d0bc..6eddaaa 100644 --- a/public/dan.css +++ b/public/dan.css @@ -138,144 +138,261 @@ .dx-result-time .dx-result-limit { color: var(--muted); font-size: .62rem; } .dx-result-time { flex-direction: column; gap: 1px; } -/* ---------- 段位認定结算页 ---------- - * 版式照原作段位認定结算(Process/Course/CourseResultProcess): - * 奶油色纸盘 + 两侧水引红绳 + 每道一个「可」印 + 竖排逐曲卡 - * (封面 / TRACK 0n / 难度 / 曲名 / 达成率 / LV / DX分数) - * + 底部「十段」框 · 绿心 · 总达成率 · DX分数 + 「好」按钮。 - * 逐个对得上素材包 resources.assets 里的 UI_DNM_*:那枚「可」印是 UI_DNM_Stamp 的 - * Clear / NoClear,两侧绳子是 UI_DNM_Separator,底部那块是 UI_DNM_DaniData。 - * 贴图没有从素材包里搬:全部用 CSS 重画,避免把游戏美术带进这个 MIT 仓库。 - * 盘面用大圆角而不是正圆 —— 三道题卡竖排进正圆,在常见宽度下会挤到不可读。 +/* ---------- 段位認定结算页(原作底图 + 官方组件按官方坐标拼) ---------- + * 版式不是照感觉摆的,是从 resources.assets 的 CourseResultProcess 预制体里量出来的 + * (dump-layout.py 读每个 RectTransform 的 anchors / anchoredPosition / pivot / sizeDelta, + * 再按「美术坐标 = 屏幕中心 + 局部位置」换成 980×928 底图上的像素)。关键坐标: + * 逐题行 x 267–843(576 = 官方的 640 × 0.9),第 1 行 y 152–276,行距 122,共四行 + * 可/不可印 108×108(UI_DNM_Icon_Result 的原始尺寸),圆心 x 203、第 1 行圆心 y 212 + * 底部白框(原作放段位名牌那一格)x 85–310 / y 680–880 + * 心(原作剩余生命那一格)100×100,圆心 (434, 790) + * 总达成率 / DX分数右下角一区:x ~470–965 / y ~620–845 + * 组件也全是官方素材,不用 CSS 画: + * 数字 = UI_CMN_Num_26p/70p/90p 图集逐格切,配同布局的 _Outline 剪影层叠出描边 + * 达成率 = UI_NUM_Score_0001111_{Blue,Gold,Red} 三色分数图集(按分数换色)+ 官方彩色「%」 + * (同名那套 Base 是空图集,不能用;「%」字形也正好只有三张 ⇒ 官方就是三档) + * 评级 = UI_CMN_TabTitle_Rank_*,心 = UI_DNM_Base_Life_01/03,DX 标签 = UI_RSL_DXScore_Base + * 底图 = UI_DNM_Result_Base_01(段位認定)/ _03(ランダム段位認定),行底板 = ..._musicBase_01 + * 唯一的例外是逐题行的**乐曲边框**:官方那张 `UI_CMN_RSL_KopMBase_*` 把难度色与难度名 + * 烘死在图里(五种难度各一张),套不上我们自己的 CF 分档,所以照它逐像素量出来的版式 + * 重画一层(见下面的「逐题行」一节),颜色由 CF 分档给。 + * 素材来源、量法与重新提取见 docs/dan.md「结算页的素材来源」与 + * scripts/extract-maimai-assets.py。 + * + * 盘面锁死 980×928、内部一律绝对像素:缩放交给浏览器对整张图等比缩放, + * 于是「一个数字多宽」这类关系到还原度的结论可以直接抄官方数字。 */ -/* dialog 默认是 shrink-to-fit,只写 max-width 撑不宽,这里必须给 width。 */ -.dan-result { border: 0; padding: 0; background: transparent; width: min(1040px, 96vw); max-width: none; color: #2b1c12; } -.dan-result::backdrop { background: #05070ce0; } -.dan-result-sheet { - position: relative; overflow: auto; max-height: 90vh; - background: radial-gradient(120% 80% at 50% -10%, #fffaf0, #f7e7cd 55%, #eed7b4), #f7e7cd; - border: 9px solid #d8461f; border-radius: 46px; - box-shadow: inset 0 0 0 3px #f7b23f, inset 0 0 0 7px #d8461f, inset 0 0 60px #e8b98155, 0 22px 60px #000b; - padding: 20px 46px 22px; display: grid; gap: 11px; justify-items: center; -} -.dan-result[data-verdict="failed"] .dan-result-sheet { border-color: #8c2b1a; } -.dan-result[data-verdict="abandoned"] .dan-result-sheet { border-color: #8a8578; } -.dan-result-close { position: absolute; top: 12px; right: 18px; padding: 0; font-size: 1.5rem; line-height: 1; color: #a8623a; background: none; border: 0; cursor: pointer; z-index: 2; } -.dan-result-close:hover { color: #d8461f; } -.dan-result-title { margin: 2px 0 0; font-size: 1.85rem; font-weight: 900; letter-spacing: .22em; color: #33200f; text-shadow: 0 1px 0 #fff; } -.dan-result-sub { margin: 0 0 2px; font-size: .8rem; color: #9a7550; text-align: center; } +/* dialog 默认 shrink-to-fit,只写 max-width 撑不宽,这里必须给 width。 */ +.dan-result { border: 0; padding: 0; background: transparent; width: min(1000px, 96vw); max-width: none; color: #23324c; } +.dan-result::backdrop { background: #05070ce8; } +/* 窄屏不把 980px 的成品屏压扁,而是让盘面横向滚动:压扁了字没法读,也谈不上还原。 */ +.dan-result-sheet { overflow: auto; max-height: 92vh; border-radius: 18px; } +.dan-result-body { display: grid; gap: 11px; width: 980px; } -/* 两侧水引(UI_DNM_Separator):上端蝴蝶结 + 竖绳 + 下端流苏 */ -.dan-cord { position: absolute; top: 16px; bottom: 16px; width: 30px; pointer-events: none; } -.dan-cord.left { left: 14px; } -.dan-cord.right { right: 14px; } -.dan-cord::before { - content: ''; position: absolute; left: 50%; top: 34px; bottom: 36px; width: 5px; transform: translateX(-50%); - background: linear-gradient(#ff9d7a, #e0402a 40%, #c9381f); border-radius: 3px; -} -.dan-cord::after { - content: ''; position: absolute; left: 50%; top: 8px; width: 22px; height: 22px; - transform: translateX(-50%) rotate(45deg); background: #f2603c; border-radius: 4px 13px 6px 13px; - box-shadow: inset 0 0 0 2px #ffd2bd, 0 2px 5px #0004; -} -.dan-cord i { - position: absolute; left: 50%; bottom: 6px; width: 20px; height: 30px; transform: translateX(-50%); - background: repeating-linear-gradient(90deg, #e0402a 0 2px, transparent 2px 5px); +.dan-result-art { + position: relative; width: 980px; height: 928px; overflow: hidden; border-radius: 18px; + background: #f7f1e7 url('/assets/maimai/result-dani.png') 0 0 / 100% 100% no-repeat; + box-shadow: 0 18px 50px #000a; } +.dan-result-art[data-art="random"] { background-image: url('/assets/maimai/result-random.png'); } +/* 980×928 的设计尺寸舞台:所有组件在这层里用官方像素坐标绝对定位。 */ +.dan-result-stage { position: absolute; inset: 0; } + +.dan-result-close { position: absolute; top: 10px; right: 16px; z-index: 3; padding: 0; font-size: 1.5rem; line-height: 1; color: #2b6cb8; background: none; border: 0; cursor: pointer; } +.dan-result-close:hover { color: #d8461f; } +/* 页头那行字烘焙在底图里,这个 h2 只留给读屏(id 仍挂在 dialog 的 aria-labelledby 上)。 */ +.dan-result-title { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; } +/* 这一轮的范围说明放在成品屏外面(深色底上):原作那张纸上没有放这行字的位置, + * 硬塞进去就只能压在页头或第一行上。 */ +.dan-result-sub { margin: 0; justify-self: center; text-align: center; font-size: .8rem; line-height: 1.6; color: #c9d2de; } -/* 逐曲卡:左边「可」印(UI_DNM_Stamp),右边一条难度配色的横卡 */ -.dan-result-tracks { display: grid; gap: 7px; width: 100%; margin: 0; padding: 0; list-style: none; } -.dan-track { display: grid; grid-template-columns: 46px minmax(0, 1fr); gap: 9px; align-items: center; } +/* ---------- 官方数字贴图 ---------- + * 每格是一个 ,里面两层 :官方 _Outline 剪影(当遮罩上深色 = 描边)+ 官方填充图集。 + * 两层都是绝对定位,DOM 顺序决定填充盖在描边之上。图集本身是纯白剪影, + * 颜色是运行时给的 —— 和游戏里一样。 */ +.dan-num { display: inline-flex; align-items: center; } +/* 图集每格都留了空白边,照格宽排会读成「一个字一个字分开」。官方数字是密排的, + * 所以 numText 会把「格宽 − 步进」写成 --dan-num-trim,每格右边收回这么多; + * 最后一格不收回,免得数字尾巴多出一段空白。 */ +.dan-num-char { position: relative; display: block; flex: none; margin-right: calc(-1 * var(--dan-num-trim, 0px)); } +.dan-num-char.dan-num-last { margin-right: 0; } +.dan-num-char > b { position: absolute; inset: 0; display: block; background-repeat: no-repeat; } +.dan-num-plain { display: grid; place-items: center; color: #4a3a2a; font-family: var(--mono); font-weight: 900; line-height: 1; } + +/* ---------- 逐题行 ---------- + * 一行 = 原作的行底板(UI_DNM_Result_musicBase_01,576×124)+ 一层**乐曲边框**。 + * 边框照原作 `UI_CMN_RSL_KopMBase_*` 逐像素量出来重画 —— 官方那五张的颜色与难度名 + * (BASiC/ADVANCED/EXPERT/MASTER/Re:MASTER)都烘死在图里,套不上我们自己的 CF 分档, + * 所以只抄版式:主色场交给 --tc1、外圈主色环交给 --tc2(**一块平色**,不做上下渐变)。 + * + * **换算**(下面每个数字的唯一来源):素材包里的 Sprite 精裁版是 636×136,官方设计尺寸 + * 640×140(左右上下各被紧裁掉 2px 透明边,`docs/images/original-card/UI_CMN_RSL_KopMBase_MST.png` + * 就是精裁版);而我们的行内区是 576×126 = 官方 640×140 × 0.9。于是 + * 行内坐标 = (原版文件坐标 + 2) × 0.9 + * 「边框内坐标」= 行内坐标 − 3.6(边框本体左上角落在行内 (3.6, 3.6))。 + * 量法是逐行/逐列扫那张贴图,取每块的首像素…末像素,再把「末像素 + 1」当开区间边界代入: + * 边框本体 原版 2…634 × 2…134 → 行内 (3.6, 3.6) 568.8×118.8;外圈主色环 2px、圆角 ~8 + * 曲绘槽 原版 x 10…127 / y 10…127 → 行内 (10.8, 10.8) 105.3×105.3(白槽、圆角 ~3) + * 歌名条 原版 y 10…30 / 右端 626 → 行内 (122.4, 10.8) 442.8×18 深蓝 #012268 胶囊 + * (左端本来接着难度名牌那一格,名牌删掉后由歌名条顶满上面一整行) + * 白盒 原版 x 134…499 / y 38…106 → 行内 (122.4, 36) 328.5×61.2 + * (原版圆角:左上 9 / 右上 17 / 右下 17 / 左下 0 —— 右下那道大弧就是原版 + * 那块「右侧带缺口的面板」,它一路弯到底带里) + * 底带 原版 x 134…518 / y 106…132 → 行内 (122.4, 97.2) 345.6×23.4 浅色带 + * 右下白牌 原版 x 499…632 / y 106…132 → 行内 (450.9, 97.2) 119.7×23.4 胶囊(两端圆头) + * (原版烘的是日文 `でらっくスコア`;我们寄不出那套字,写「DX分数」) + * 右上紫场 白盒右端…边框内右沿 / 歌名条下沿…白牌上沿 = 119.5×68.4,原版空着, + * 我们拿它放逐题评级徽章(原版白盒那个大 R 弧就是为它让位的)。 + * + * 信息分配(照用户口径):**最上面歌名占一整行**(难度名牌删掉)、曲绘槽在左、 + * 达成率大数字排在白盒里、**DX 分数挪到卡片右下角那块白牌**、**限时写进底带左侧**。 + */ +.dan-result-tracks { position: absolute; inset: 0; margin: 0; padding: 0; list-style: none; } +.dan-track { position: absolute; left: 267px; width: 576px; height: 126px; } +/* 印在行左边、行外那一条(原作里印和底板本来就不在同一个容器里)。 */ .dan-track-stamp { - box-sizing: border-box; width: 46px; height: 50px; display: grid; place-items: center; - font-size: 1.15rem; font-weight: 900; color: #e0402a; background: #fff8ec; - border: 3px solid #e0402a; border-radius: 2px; box-shadow: 0 2px 6px #0002; - clip-path: polygon(29% 0, 71% 0, 100% 29%, 100% 71%, 71% 100%, 29% 100%, 0 71%, 0 29%); -} -.dan-track[data-outcome="timeout"] .dan-track-stamp, -.dan-track[data-outcome="interrupted"] .dan-track-stamp { color: #8f8f8f; border-color: #9d9d9d; background: #f4f1ec; } -.dan-track[data-outcome="pending"] .dan-track-stamp { color: #b9b1a6; border-color: #bcb4a8; background: #f4f1ec; } -.dan-track-card { - display: grid; grid-template-columns: 96px minmax(0, 1fr) auto; gap: 10px; align-items: center; - padding: 6px 12px 6px 6px; border-radius: 9px; border: 2px solid #ffffff59; - background: linear-gradient(100deg, var(--tc1, #8b6cf0), var(--tc2, #4a2fb0)); - box-shadow: 0 3px 10px #0005, inset 0 1px #ffffff40; + position: absolute; left: -118px; top: 8px; width: 108px; height: 108px; + object-fit: contain; filter: drop-shadow(0 2px 3px #0003); +} +/* 还没抽到的那道不盖章 —— 印是真的,只是这一格还没轮到他。 */ +.dan-track[data-outcome="pending"] .dan-track-stamp { opacity: .22; filter: grayscale(1); } +.dan-track-plate { position: absolute; inset: 0; background: url('/assets/maimai/track-plate.png') 0 0 / 100% 100% no-repeat; } + +/* 乐曲边框:**单色**主色场(原版那块底板就是一块平色)+ 外圈 2px 主色环 + 底部浅色带。 + * 环用 inset box-shadow 画、带用子元素画,都不占盒模型 —— 边框**不是**真 border, + * 真 border 会把子元素坐标系推进边框宽度。 */ +.dan-track-frame { + position: absolute; left: 3.6px; top: 3.6px; width: 568.8px; height: 118.8px; border-radius: 7px; + overflow: hidden; + background: var(--tc1, #8b6cf0); + box-shadow: inset 0 0 0 2px var(--tc2, #4a2fb0); } +/* 底部浅色带(原版那一块:从曲绘槽右边一直铺到右下白牌底下)。它是**主色的浅色版本**, + * 不是白色 —— 白色留给白盒与右下白牌。`overflow: hidden` 让它的左下角跟着边框圆弧收掉。 */ +.dan-track-band { + position: absolute; left: 118.8px; top: 93.6px; width: 345.6px; height: 23.4px; + background: color-mix(in srgb, var(--tc1, #8b6cf0) 55%, #ffffff); +} +/* 边框里的组件用**边框内坐标**(= 行内坐标 − 3.6)。 */ +/* 深蓝歌名条:删掉难度名牌之后它**占满上面一整行** —— 左端与白盒左端对齐(边框内 118.8), + * 右端贴到边框内右沿(118.8+442.8=561.6,离主色环还有 ~7px,与原版一致)。 */ +.dan-track-titlebar { + position: absolute; left: 118.8px; top: 7.2px; width: 442.8px; height: 18px; + border-radius: 9px; background: #012268; +} +/* 行首那块「STAGE 01」小牌删掉了:官方那张牌压在曲绘上,我们没有曲绘,它就只是在左上角 + * 多挂一块深蓝方块。样式留成 display:none(不再渲染这个节点,这条只是兜底)。 */ +.dan-track-no { display: none; } +/* 官方的曲绘槽(JacketImage_S):原版是一块 105×105 的**方槽**(圆角只有 ~3px), + * 从边框左上角一直占到接近底部。我们抽的是 CF 题、没有曲绘 —— 槽里放题号本身 + * (`1554C` 这种「题号 + 小问」的写法)与这题在 CF 上的 rating。 + * 底色是**卡片主色的浅色版本**(原版那格是曲绘,换成白牌会像挖了个洞)。 */ .dan-track-jacket { - width: 96px; height: 68px; border-radius: 5px; display: grid; place-items: center; - border: 2px solid #ffffff66; box-shadow: inset 0 0 22px #00000066; - background: linear-gradient(140deg, #ffffff2e, #00000059); -} -.dan-track-jacket b { font-family: var(--mono); font-size: 1.5rem; font-weight: 900; line-height: 1; color: #fff; text-shadow: 0 2px 5px #000b; } -.dan-track-jacket small { font-family: var(--mono); font-size: .58rem; color: #ffffffcc; } -.dan-track-info { display: grid; gap: 3px; min-width: 0; } -.dan-track-head { display: flex; gap: 7px; align-items: baseline; flex-wrap: nowrap; min-width: 0; } -.dan-track-no { font-family: var(--mono); font-size: .66rem; font-weight: 700; letter-spacing: .1em; color: #ffe0b0; white-space: nowrap; } -.dan-track-diff { font-family: var(--mono); font-size: .64rem; color: #fff; background: #0000004d; border-radius: 3px; padding: 1px 6px; white-space: nowrap; } -.dan-track-name { flex: 1; min-width: 0; font-size: .76rem; color: #ffffffeb; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } -.dan-track-ach { display: flex; align-items: baseline; gap: 8px; } -.dan-track-achlabel { font-size: .68rem; color: #ffffffd0; } -.dan-track-achvalue { font-family: var(--mono); font-size: 1.8rem; font-weight: 900; letter-spacing: -.02em; } -.dan-track-judge { font-size: .62rem; color: #3a2418; background: #ffd76a; border-radius: 999px; padding: 1px 9px; white-space: nowrap; } + position: absolute; left: 10.8px; top: 10.8px; width: 105.3px; height: 105.3px; + border-radius: 3px; display: flex; flex-direction: column; + justify-content: center; align-items: center; gap: 2px; + background: color-mix(in srgb, var(--tc1, #8b6cf0) 42%, #ffffff); + box-shadow: inset 0 0 0 2px #ffffff4d, 0 2px 6px #00000024; +} +.dan-track-jacket b { + font-family: var(--mono); font-size: 1.3rem; font-weight: 900; line-height: 1; color: #17265c; +} +.dan-track-jacket small { font-family: var(--mono); font-size: .85rem; font-weight: 700; color: #3d4a75; } +/* 单题 DX 分数(= 该题算出的 rating):原版那块牌烘着日文 `でらっくスコア`,压在**右下角**、 + * 底带的右端上(它就是原版「右下角是 dx 分数」那一格)。两端是圆头 ⇒ 胶囊。 + * `align-items: baseline`:小字标签与数字按**同一条基线**排(数字那一侧由 numText 的 + * `baseline` 选项按图集基线补一个相对偏移,见 dan.js)。 */ +.dan-track-dxbox { + position: absolute; left: 450.9px; top: 97.2px; width: 119.7px; height: 23.4px; + border-radius: 11.7px; background: #fff; display: flex; align-items: baseline; padding: 0 13px; + box-shadow: 0 1px 4px #00000033; +} +.dan-track-dxlabel { flex: none; font-size: .54rem; font-weight: 900; color: #0d2a63; } +.dan-track-dxnum { margin-left: auto; } +.dan-track-name { + position: absolute; left: 128.8px; top: 7.2px; width: 422.8px; height: 18px; + font-size: .68rem; line-height: 18px; color: #fff; + overflow: hidden; text-overflow: ellipsis; white-space: nowrap; +} +/* 达成率白盒:原版那一格是 `UI_AchivementCounter`,白底、**右下角一道大 R 弧**(连续到 + * 底带上,为右上那块紫场让位)。左上角烘着原版的 `ACHIEVEMENT` 小字,我们照位置写 + * 「達成率」。圆角按原版量到再 ×0.9:左上 8.1、右上 15.3,**下面两角都是直角** —— 白盒的 + * 底边和下面那条底带连成一整块,中间不该出现圆角缺口(左下、右下都为 0)。 */ +.dan-track-achbox { + position: absolute; left: 118.8px; top: 32.4px; width: 328.5px; height: 61.2px; + border-radius: 8.1px 15.3px 0 0; background: #fff; +} +.dan-track-achlabel { + position: absolute; left: 10px; top: 4px; font-size: .6rem; font-weight: 900; letter-spacing: .08em; color: #0d2a63; +} +/* 达成率数字**右对齐**:满 100 的那串(`100.8000%`)右端永远贴在同一个位置,不到 100 的 + * 少一位数就少在**左边**(`96.0000%` 的右端仍与 100 的齐平)。左对齐时短的那串右端会缩回来。 + * 垂直上排在「達成率」那行小字**下面**:盒内 y 13…61(盒高 61.2),字高 44 居中, + * 整串右端离盒右沿 8px。 */ +.dan-track-achvalue { position: absolute; left: 9px; top: 13px; width: 311.5px; height: 48px; display: flex; align-items: center; justify-content: flex-end; } +.dan-per { display: block; } +/* 右上那块紫场:白盒右端…边框内右沿 / 歌名条下沿…右下白牌上沿 = 行内 450.9…570.4 / + * 28.8…97.2,原版空着(白盒那道大 R 弧就是给它让位的)。逐题评级徽章放这里 —— + * 99×40 是官方 SSS+ 那张紧裁图(110×44)×0.9,摆进去离白盒 13px、离边框内右沿 7.4px。 + * 徽章源图是紧裁的(SSS+ 110×44、D 38×42),给 99×40 的框按高度贴齐。 */ +.dan-track-badge { position: absolute; left: 464px; top: 43px; width: 99px; height: 40px; object-fit: contain; filter: drop-shadow(0 2px 5px #0000003d); } +.dan-track-judge { + position: absolute; left: 464px; top: 43px; width: 99px; height: 40px; + display: grid; place-items: center; font-size: .72rem; font-weight: 900; color: #0d2a63; + background: #ffd76a; border-radius: 10px; box-shadow: inset 0 0 0 2px #ffffffa6, 0 2px 5px #0000003d; +} .dan-track[data-outcome="timeout"] .dan-track-judge, .dan-track[data-outcome="interrupted"] .dan-track-judge, -.dan-track[data-outcome="pending"] .dan-track-judge { background: #d9d2c8; } -.dan-track-side { display: grid; gap: 4px; justify-items: end; } -.dan-track-lv { - font-family: var(--mono); font-size: .95rem; font-weight: 900; color: #fff; padding: 0 8px; - border: 2px solid #ffffff8c; border-radius: 6px; background: linear-gradient(#7b5cff, #4628b8); -} -.dan-track-score { display: flex; align-items: baseline; gap: 5px; } -.dan-track-score small { font-size: .58rem; color: #ffffffb8; } -.dan-track-score b { font-family: var(--mono); font-size: .9rem; color: #fff; } -.dan-track-limit { font-size: .56rem; color: #ffffff9e; } - -/* 金色描边数字:白→金→橙渐变填充 + 深棕描边(原作那套 DX 数字,用 CSS 重画)。 - * 必须分两层,试过两条捷径都不行: - * - 八向 text-shadow:偏移副本会把渐变填充整个盖住,只剩一圈棕边; - * - -webkit-text-stroke + paint-order: stroke fill:Chromium 的 paint-order 只对 - * SVG 生效,HTML 文字上描边照样盖在填充之上,6px 描边直接吃掉整个笔画。 - * 所以描边交给 ::before、渐变填充交给 ::after,两层叠在同一段文字(data-text)上。 - * 这两点是抠图像素数数出来的,DOM 层面完全看不出来。 */ -.dan-gold { position: relative; display: inline-block; color: transparent; } -.dan-gold::before, .dan-gold::after { - content: attr(data-text); position: absolute; left: 0; top: 0; white-space: nowrap; pointer-events: none; -} -.dan-gold::before { color: #5a2a10; -webkit-text-stroke: 6px #5a2a10; } -.dan-gold::after { - background: linear-gradient(#fffce8, #ffd85c 42%, #ff9a1f 68%, #ffd85c); - -webkit-background-clip: text; background-clip: text; color: transparent; -} -.dan-gold { filter: drop-shadow(0 4px 7px #00000059); } +.dan-track[data-outcome="pending"] .dan-track-judge { background: #e8e2d6; } +/* 限时写进**底带的左侧空白**(原版这条底带是空的一块浅色,右下角才是 DX 分数):所以 + * 它自己没有白牌面,只把字压在浅色带上,左端与底带左端对齐。 */ +.dan-track-limit { + position: absolute; left: 122.4px; top: 97.2px; width: 328.5px; height: 23.4px; + display: flex; align-items: center; padding-left: 12px; + font-size: .58rem; font-weight: 700; color: #0d2a63; +} -/* 底部:判定框 · 心 · 总达成率 / DX分数 */ -.dan-result-bottomrow { display: grid; grid-template-columns: minmax(140px, 210px) auto minmax(0, 1fr); gap: 16px; align-items: center; width: 100%; margin-top: 3px; } -.dan-result-rankbox { - display: grid; place-items: center; gap: 1px; padding: 9px 12px; background: #fffdf6; - border: 3px solid #e6d3b4; border-radius: 13px; box-shadow: 0 3px 10px #00000030, inset 0 1px #fff; -} -.dan-result-rankbox strong { font-size: 2.35rem; font-weight: 900; letter-spacing: .05em; line-height: 1.1; color: #2b1c12; } -.dan-result[data-verdict="failed"] .dan-result-rankbox strong { color: #b32a15; } -.dan-result[data-verdict="abandoned"] .dan-result-rankbox strong { color: #7d8794; } -.dan-result-rankbox small { font-size: .68rem; color: #9a7550; } -.dan-result-life { position: relative; width: 88px; height: 88px; display: grid; place-items: center; } -.dan-result-life::before { - content: '♥'; position: absolute; inset: 0; display: grid; place-items: center; - font-size: 92px; line-height: 1; color: #35c257; - -webkit-text-stroke: 5px #e6fbec; text-shadow: 0 4px 10px #0000004d; -} -.dan-result-life[data-state="bad"]::before { color: #9aa3ae; } -.dan-result-life span { position: relative; z-index: 1; font-family: var(--mono); font-size: 1.2rem; font-weight: 900; color: #fff; text-shadow: 0 1px 4px #000000b3; } -.dan-result-life span small { font-size: .62rem; opacity: .9; } -.dan-result-totalblock { display: grid; gap: 2px; min-width: 0; } -.dan-total-label { font-size: .74rem; font-weight: 700; letter-spacing: .1em; color: #8a5a2a; } -.dan-total-ach { font-family: var(--mono); font-size: 2.5rem; font-weight: 900; letter-spacing: -.02em; } -.dan-total-score { display: inline-flex; align-items: baseline; gap: 7px; justify-self: start; margin-top: 2px; padding: 2px 12px; border-radius: 6px; background: #fffdf699; border: 2px solid #e6d3b4; } -.dan-total-score small { font-size: .64rem; color: #9a7550; } -.dan-total-score b { font-family: var(--mono); font-size: 1rem; color: #2b1c12; } +/* ---------- 底部:段位名牌 · 心 · 总达成率 / DX分数 ---------- + * 底图在这一块**预留了三处白**(官方在那儿写运行时文字):(503,663) 与 (753,677) 是 + * 「総達成率」那个标签的白格(中间被折页的斜边切开),(608,810) 360×80 是「DX分数」 + * 那条白牌(左端还有个空的圆白圆头)。所以这三块的**位置不用猜**,照白格摆: + * 标签进白格 → 大数字落在下面的色带上 → DX 分数进下面那条白牌。 */ +.dan-bottom { position: absolute; inset: 0; } +/* 左下白框(底图 (74,672) 240×230):原版这里写段位名(十段/皆伝…)。 + * 我们发的是档位、不发段位名,所以照原版把**档位名**写进去: + * 深棕重字 + 米白描边 + 一点投影(原版那一路毛笔字我们寄不出字体,用系统里的重明朝近似)。 */ +.dan-verdict { + position: absolute; left: 74px; top: 672px; width: 240px; height: 230px; + display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 6px; +} +.dan-plate-name { + /* 原版那两个字是**重毛笔字**。客户端里它是烘死的贴图,我们只能用系统字体近似: + * 先取思源宋体的 900(这台机器上是 `NotoSerifSC-VF`,最重的一档),再退回系统宋体/ + * 楷体那一类衬线。SimSun 那种细宋差得太远(笔画只有原版一半粗)。 */ + font-family: "Noto Serif SC", "Source Han Serif SC", "Songti SC", "STKaiti", "KaiTi", "SimSun", serif; + font-size: 104px; font-weight: 900; line-height: 1.08; letter-spacing: .01em; + color: #3a2114; paint-order: stroke fill; + /* 原版那两个字是深棕重字 + 很淡的米白边缘 + 一点投影;描边给厚了会像贴纸。 */ + -webkit-text-stroke: 7px #fff6e6; + text-shadow: 0 3px 0 #00000024; +} +/* 名字长的档位(小随机段位 / 大随机段位)自动缩号,别顶穿白框。 */ +.dan-plate-name[data-len="3"] { font-size: 88px; } +.dan-plate-name[data-len="4"] { font-size: 68px; } +.dan-plate-name[data-len="5"], .dan-plate-name[data-len="6"] { font-size: 54px; } +.dan-verdict-count { font-size: .76rem; font-weight: 800; letter-spacing: .04em; color: #7a5a34; } +/* 原作这一格是剩余生命的圆心。素材本身是 272×272,之前压到 104 —— 照官方放大到 200, + * 位置保持原来那个圆心(434,790)。 */ +.dan-life { position: absolute; left: 334px; top: 690px; width: 200px; height: 200px; } +.dan-life-base { width: 100%; height: 100%; object-fit: contain; filter: drop-shadow(0 3px 8px #00000038); } +.dan-life-count { position: absolute; inset: 0; display: grid; place-items: center; } +/* 总达成率:标签写进底图预留的白格,大数字落在下面的色带上,右端对齐 x 945 + * (与下面那条 DX 分数白牌的右端同一条边)。 */ +.dan-total-row { position: absolute; inset: 0; } +/* 「总达成率」四个字写在白格**偏左**的位置(官方那四个字不在白格正中),字号压到 23。 + * 底图这块白实测是 x 503..708(右边那截 753..856 被折页斜边切开),所以按 503..708 居中。 */ +.dan-total-label { + position: absolute; left: 503px; top: 663px; width: 354px; height: 52px; + display: grid; place-items: center; padding-right: 140px; padding-bottom: 12px; + font-size: 23px; font-weight: 800; letter-spacing: .1em; color: #16306b; +} +.dan-total-num { + position: absolute; left: 490px; top: 727px; width: 460px; height: 66px; + display: flex; align-items: center; justify-content: flex-end; +} +/* DX 分数:官方这条白牌就是底图预留的 (608,810) 360×80,标签在左、数字在右, + * 都是深蓝(和每题那张卡上的 DX 分数同一个色)。白牌下半截被折页斜切掉一块, + * 所以文字靠上排(顶到 y 816),不跟着整块矩形居中。 */ +.dan-dxscore-row { + position: absolute; left: 608px; top: 810px; width: 360px; height: 80px; + display: flex; align-items: baseline; justify-content: space-between; padding: 0 46px 0; +} +.dan-dxscore-label { font-size: 30px; font-weight: 800; letter-spacing: .06em; color: #16306b; } +.dan-dxscore-num { display: flex; align-items: center; } -.dan-result-note { margin: 2px 0 0; max-width: 72ch; text-align: center; font-size: .78rem; line-height: 1.7; color: #96714b; } -.dan-result-actions { display: flex; gap: 10px; flex-wrap: wrap; justify-content: flex-end; align-items: center; width: 100%; } +/* 说明与按钮在成品屏之外(深色底上),不往那张纸上塞东西。 */ +.dan-result-note { margin: 0; max-width: 92ch; justify-self: center; text-align: center; font-size: .78rem; line-height: 1.7; color: #a9b4c4; } +.dan-result-actions { display: flex; gap: 10px; flex-wrap: wrap; justify-content: flex-end; align-items: center; } .dan-result-ok { min-width: 112px; padding: 7px 26px; font-size: 1.25rem; font-weight: 900; color: #fff; cursor: pointer; background: linear-gradient(#ff9a4d, #e8542a); border: 3px solid #c9381f; border-radius: 11px; @@ -286,17 +403,8 @@ @media (max-width: 860px) { .dan-grid { grid-template-columns: 1fr; } - .dan-result-sheet { padding: 16px 22px 18px; border-radius: 30px; } - .dan-result-title { font-size: 1.4rem; } - .dan-cord { display: none; } - .dan-track-card { grid-template-columns: 62px minmax(0, 1fr); row-gap: 6px; } - .dan-track-jacket { width: 62px; height: 56px; } - .dan-track-jacket b { font-size: 1.15rem; } - .dan-track-side { grid-column: 2; grid-auto-flow: column; align-items: baseline; gap: 8px; justify-items: start; } - .dan-track-achvalue { font-size: 1.35rem; } - .dan-track-name { flex-basis: 8ch; } - .dan-result-bottomrow { grid-template-columns: 1fr; justify-items: center; } - .dan-total-ach { font-size: 2rem; } - .dan-total-score { justify-self: center; } + /* 结算盘是一张 980×928 的成品屏,窄屏不重排、只横向滚动(见 .dan-result-body 的 + * min-width)—— 把四行字和页头压进 360px 宽只会压成不可读。 */ + .dan-result-close { top: 6px; right: 10px; font-size: 1.25rem; } .dan-result-actions { justify-content: center; } } \ No newline at end of file diff --git a/public/dan.html b/public/dan.html index 5cc330c..c67b0d8 100644 --- a/public/dan.html +++ b/public/dan.html @@ -1,14 +1,16 @@ -随机抽题 · Algorithm DX +随机抽题 · Algorithm DX + - + +
@@ -75,4 +81,6 @@

抽一道,别挑。

口径:这是自测,不是防作弊。开始做题之后你照样可以查题解、问 AI —— 这一点拦不住,和「辅助解题」自报标签是同一条现实。它唯一能保证的是你没法挑题。
另外,抽到的题如果正好是你在 Codeforces 上已经提交过的(含只有 WA 的)、或者在你已归档账号上做过的、或者是你正在计时的那道,都会被自动排除。排除依据是本机已同步的记录,同步不完整时(比如刚 AC 还没同步)可能仍有漏网。

- \ No newline at end of file +TOP + + \ No newline at end of file diff --git a/public/dan.js b/public/dan.js index 4cc9861..361d4d2 100644 --- a/public/dan.js +++ b/public/dan.js @@ -1,4 +1,4 @@ -import { startAutoSync } from './auto-sync.js'; +import { startAutoSync } from './auto-sync.js'; import { cfRatingColor } from './cf-rating-colors.js'; /* @@ -590,7 +590,8 @@ function drainSheet() { /* ---------------- 段位認定结算页(对齐原作 UI_DNM_Result_* 的构成) ---------------- */ -/** 难度色号 → 逐曲卡的渐变(与 CF 分色同一套边界;violet 是原作 MASTER 的紫)。 */ +/** 难度色号 → 逐曲卡的 [主色场, 外圈主色环](与 CF 分色同一套边界;violet 是原作 MASTER 的紫)。 + * 两个色都只当**平色**用,不做上下渐变 —— 原版那块乐曲底板就是一块平色。 */ const TRACK_TONE = { red: ['#e8564f', '#a3211c'], orange: ['#f08c3a', '#a85a12'], violet: ['#8b6cf0', '#4a2fb0'], blue: ['#4a7fe0', '#1e3f96'], @@ -602,26 +603,45 @@ let resultDialog = null; function buildResultDialog() { const dialog = el('dialog', 'dan-result'); dialog.setAttribute('aria-labelledby', 'danRunTitle'); + // 盘面是一张 980×928 的固定设计尺寸舞台,里面所有组件都用**官方预制体量出来的像素坐标** + // (见 dan.css 顶部注释)。窄屏横向滚动,不做响应式重排 —— 重排就还原不出来了。 dialog.innerHTML = `
- - -

段位認定

-

-
    -
    -
    -
    -
    - 总达成率 - - DX分数 +
    +
    +
    +

    段位認定

    +
      +
      + +
      + + +
      +
      + + +
      + +
      + 总达成率 + +
      +
      + DX分数 + +
      +
      +
      +
      +

      +

      +
      + +
      -
      -

      -
      - -
      `; document.body.append(dialog); field(dialog, '.dan-result-close').addEventListener('click', () => dialog.close()); @@ -629,13 +649,204 @@ function buildResultDialog() { return dialog; } -/** `.dan-gold` 要两层(描边 ::before + 渐变填充 ::after),两层都读 `data-text`。 */ -function goldNode(cls, text) { - const node = el('strong', cls, text); - node.dataset.text = text; - return node; +/* ---------- 官方数字字体(图集逐格切) ---------- + * 图集是规整的 4×4 网格,每格的位置由素材包里同名的 Sprite 给出 + * (UI_CMN_Num_90p_0 … _14),不用自己量。 + * 「下标 → 字符」是拿对照表一张张看出来的: + * 白字族(UI_CMN_Num_26p/90p)0-9 · [10]'+' · [11]'-' · [12]',' · [13]'.' · [14]'%' + * 分数族(UI_NUM_Score_0001111_*)0-9 · [10]'+' · [11]'/' · [12]'%' · [13]'.' + * 白字族各带一层 _Outline(同布局的实心剪影)—— 官方就是这么叠出描边数字的: + * 两个背景层同一位置,填充层画在描边层之上。 + */ +const NUM_INDEX = { + '0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9, + '+': 10, '-': 11, ',': 12, '.': 13, '%': 14, +}; +const SCORE_INDEX = { + '0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9, + '+': 10, '/': 11, '%': 12, '.': 13, +}; +/** 白字族借用的小数点已经不需要了:注释见 NUM_FONTS 的 `dotBaseline`。 */ +/** + * 图集每格留了空白边(90p 那格 89px 宽、字形只有 63px),照格宽排字会读起来「一个字一个字 + * 分开」。官方是等距密排(字与字几乎贴上),所以每格按 `advance` 收回去一点,见 numText。 + * + * `baseline`:数字墨迹底线在格高里的比例(量出来的)—— 旁边有小字标签时用它把两边按同一条 + * 基线排(见 numText 的 `baseline` 选项)。 + * + * `decimal`:**借来的小数点**。白字族里没有句点这一格 —— `[13]` 是中黑点(画在字高中线上)、 + * `[12]` 是逗号,都不是小数点该有的样子(小字号下读出来像断字符)。分数族 `[13]` 才是这套 + * 数字里真正的句点(方形、压在基线附近),官方达成率图里的「101.0000%」用的就是它。 + * `dotBaseline`:小数点那一格(`[13]`)墨迹底线在格高里的比例。这一格在字高中线上 + * (26p 只有 8×8、90p 24×24),照原样排出来是浮在数字中间的一粒、像断字符 —— 官方那只 + * 方块的底边压在数字基线上,所以按它压下去(字号小的那两个族才需要,见 numText)。 + */ +const NUM_FONTS = { + n26: { + fill: 'num-26p', outline: 'num-26p-outline', atlas: [136, 160], cell: [34, 40], advance: 24, + baseline: 0.825, dotBaseline: 0.775, chars: NUM_INDEX, + }, + n90: { + fill: 'num-90p', outline: 'num-90p-outline', atlas: [356, 420], cell: [89, 105], advance: 67, + baseline: 0.886, dotBaseline: 0.6, chars: NUM_INDEX, + }, + // 分数数字是**三套**同布局不同颜色的图集(自带颜色,不再叠描边层): +// blue / gold / red,按达成率高低换。官方还有一张 `UI_NUM_Score_0001111_Base`, +// 但那是一张**空图集**(整张几乎全透明,量下来 16 格一个字形都没有),不能用; +// 「%」字形也正好只有蓝/金/红三张 —— 所以官方的档位就是三档,不是四档。 +'score-blue': { fill: 'num-score-blue', outline: '', atlas: [296, 392], cell: [74, 98], advance: 67, baseline: 0.949, dotBaseline: 0.827, chars: SCORE_INDEX }, + 'score-gold': { fill: 'num-score-gold', outline: '', atlas: [296, 392], cell: [74, 98], advance: 67, baseline: 0.949, dotBaseline: 0.827, chars: SCORE_INDEX }, + 'score-red': { fill: 'num-score-red', outline: '', atlas: [296, 392], cell: [74, 98], advance: 67, baseline: 0.949, dotBaseline: 0.827, chars: SCORE_INDEX }, +}; +const NUM_COLS = 4; +/** 达成率末尾那个大「%」字形:官方按分数分色,蓝/金/红三张。 */ +const PERCENT_ART = { gold: 'score-per-gold.png', blue: 'score-per-blue.png', red: 'score-per-red.png' }; + +/** + * 达成率取哪一档颜色 —— 原作就是按分数换 `UI_NUM_Score_0001111_*` 那一套。 + * 三档的界限(2026-10-10 用户给的官方口径):**97% 以上金、80% 以上红、再低蓝**。 + * 注意低分段是**蓝**、不是红 —— 别按「高金/中蓝/低红」的直觉写。 + */ +function scoreTone(achievement) { + if (achievement >= 97) return 'gold'; + if (achievement >= 80) return 'red'; + return 'blue'; +} + +/** 达成率数字 + 官方「%」字形。 */ +function achievementNumber(achievement, height) { + const tone = scoreTone(achievement); + const art = PERCENT_ART[tone]; + const text = achievement.toFixed(4); + const wrap = numText(text, `score-${tone}`, height); + // 「%」是独立字形(img 的 alt 为空),读屏那行 aria-label 要把它补回去。 + wrap.setAttribute('aria-label', `${text}%`); + const percent = el('img', 'dan-per'); + percent.src = `/assets/maimai/${art}`; + percent.alt = ''; + // 官方那个「%」跟数字差不多高,而且**贴着**最后一个数字。`score-per-*` 是 80×80 的方图、 + // 四周留白(墨迹 x 8..70 / y 10..69),直排会在数字和 % 之间多出一截空隙,所以放大一点 + // 再把左边那圈留白用负边距抵掉。 + percent.style.height = `${Math.round(height * 1.1)}px`; + percent.style.marginLeft = `${-Math.round(height * 0.16)}px`; + wrap.append(percent); + return wrap; } +/** DX 分数(其实是单题 rating)用的深蓝:和边框里的标题条、标签同一个色 —— 不走分数色, + * 那套三色专门留给达成率。 */ +const DX_NUM_COLOR = '#0d2a63'; + +/** + * 把一串字符渲染成官方数字贴图。`height` 是字高(px,按 980×928 的设计尺寸)。 + * 每格拆成两个叠起来的层:官方 `_Outline` 剪影上深色(描边)+ 官方填充图集。 + * 之所以要两个真元素而不是两个 background 层:图集是**纯白剪影**(游戏运行时才上色), + * background 层没法单独染色,而伪元素又只能画在宿主背景之上(描边会盖住填充)。 + * `color` 给定时就用图集当**遮罩**把数字染成这个颜色 —— 这正是游戏里给白字族上色的做法 + * (分数族自带颜色,不走这条路;注意 `UI_NUM_Score_0001111_Base` 是张空图集,不能用)。 + * 字体里没有的字符(例如数字族没有 '/')退回普通文字,不会静默丢字符。 + * + * `opts.outline === false`:**不叠描边层**。小号的 DX 分数在官方那里是干净的深蓝数字, + * 叠一层 4px 描边会糊成一团 —— 小字一律不用描边。 + * `opts.trim === false`:不收紧字距(留给需要等宽对齐的场景)。 + * + * 排版就一件事:**一个字形一格**,格子之间按 `advance` 等距。小数点(含逗号、加号这些) + * 跟数字走同一条路 —— 它本来就是图集里的一格,官方也是这么排的,别为它开小灶。 + * + * `opts.baseline`:把整块数字往下补 `(1 − baseline) × 字高`。旁边有小字标签、两边用 + * `align-items: baseline` 排的时候要用它 —— flex 认的基线是这一格的**底边**,而图集里数字的 + * 墨迹底线在格高的 `baseline` 处,不补这一下,数字就会浮在标签上面。 + */ +const NUM_OUTLINE_COLOR = '#2b3350'; +function numText(text, fontKey, height, color, opts = {}) { + const font = NUM_FONTS[fontKey]; + const scale = height / font.cell[1]; + const wrap = el('span', `dan-num dan-num-${fontKey}`); + wrap.setAttribute('role', 'img'); + wrap.setAttribute('aria-label', text); + wrap.style.height = `${height}px`; + if (opts.baseline) { + wrap.style.position = 'relative'; + wrap.style.top = `${((1 - font.baseline) * height).toFixed(2)}px`; + } + const chars = [...text]; + if (opts.trim !== false) { + // 图集每格右边都留了空(分数族格宽 74、字形只有 60 左右),照格宽排会把一串数字 + // 读成「一个字一个字分开」。收回 (格宽 − 步进):注意 CSS 里是**负**边距。 + wrap.style.setProperty('--dan-num-trim', `${(font.cell[0] - (font.advance ?? font.cell[0])) * scale}px`); + } + chars.forEach((ch, index) => { + const cell = el('i', 'dan-num-char'); + cell.style.height = `${height}px`; + if (index === chars.length - 1) cell.classList.add('dan-num-last'); + const idx = font.chars[ch]; + if (idx === undefined) { + cell.textContent = ch; + cell.classList.add('dan-num-plain'); + if (color) cell.style.color = color; + wrap.append(cell); + return; + } + const sx = (idx % NUM_COLS) * font.cell[0]; + const sy = Math.floor(idx / NUM_COLS) * font.cell[1]; + const size = `${font.atlas[0] * scale}px ${font.atlas[1] * scale}px`; + // 小数点那一格画在字高中线上(26p 只有 8×8、90p 24×24),照原样排出来就是浮在数字中间 + // 的一粒,像断字符。官方那只方块是**底边压在数字基线上**的,所以按 `dotBaseline` + // (那一格墨迹底线在格高里的比例)把它压下去 —— 字体自己的字形,只是摆到该在的位置。 + // + // 注意:**不能**用挪 `background-position` 的办法下压 —— 图集是整张背景,往下挪 dy 就会把 + // 上一格(同一列、上一行)的下半截露在这一格里(用户提过两次的那个 bug)。裁剪也救不了, + // 因为承载背景的那个 `b` 就是格子本身、并没有溢出。改成把**整格**往下挪:图集位置不动, + // 字形还是它自己那一格,只是这一格整体下沉 dy。 + const dy = ch === '.' && font.dotBaseline ? (font.baseline - font.dotBaseline) * height : 0; + if (dy) cell.style.top = `${dy}px`; + const pos = `${-sx * scale}px ${-sy * scale}px`; + cell.style.width = `${font.cell[0] * scale}px`; + if (font.outline && opts.outline !== false) { + const outline = el('b', 'dan-num-outline'); + const url = `url(/assets/maimai/${font.outline}.png)`; + outline.style.setProperty('background-color', NUM_OUTLINE_COLOR); + for (const prop of ['mask-image', '-webkit-mask-image']) outline.style.setProperty(prop, url); + for (const prop of ['mask-size', '-webkit-mask-size']) outline.style.setProperty(prop, size); + for (const prop of ['mask-position', '-webkit-mask-position']) outline.style.setProperty(prop, pos); + for (const prop of ['mask-repeat', '-webkit-mask-repeat']) outline.style.setProperty(prop, 'no-repeat'); + cell.append(outline); + } + const fill = el('b', 'dan-num-fill'); + const fillUrl = `url(/assets/maimai/${font.fill}.png)`; + if (color) { + // 白字族是纯白剪影:当遮罩 + 纯色背景 = 想染什么色就什么色(游戏里也是运行时染色)。 + fill.style.setProperty('background-color', color); + for (const prop of ['mask-image', '-webkit-mask-image']) fill.style.setProperty(prop, fillUrl); + for (const prop of ['mask-size', '-webkit-mask-size']) fill.style.setProperty(prop, size); + for (const prop of ['mask-position', '-webkit-mask-position']) fill.style.setProperty(prop, pos); + for (const prop of ['mask-repeat', '-webkit-mask-repeat']) fill.style.setProperty(prop, 'no-repeat'); + } else { + fill.style.backgroundImage = fillUrl; + fill.style.backgroundSize = size; + fill.style.backgroundPosition = pos; + } + cell.append(fill); + wrap.append(cell); + }); + return wrap; +} + +/** + * 官方评级徽章:`UI_GAM_Rank_*` —— 游戏内那套**单级全套**(D/C/B/BB/BBB/A/AA/AAA/S/S+/SS/SS+/SSS/SSS+)。 + * + * 之前用的是 `UI_CMN_TabTitle_Rank_*`,但那是**页签标题**用的区间图:AAA 那张画的是 + * 「A~AAA」、BBB 那张是「~BBB」,摆在逐题卡片上根本不是那一级的评级(而且没有 + * D/C/B/BB/A/AA 这些级,低分全挤到同一张牌上)。换成游戏内那套之后 14 级各有一张。 + */ +const RANK_ART = { + 'SSS+': 'gam-rank-sssp.png', SSS: 'gam-rank-sss.png', 'SS+': 'gam-rank-ssp.png', SS: 'gam-rank-ss.png', + 'S+': 'gam-rank-sp.png', S: 'gam-rank-s.png', + AAA: 'gam-rank-aaa.png', AA: 'gam-rank-aa.png', A: 'gam-rank-a.png', + BBB: 'gam-rank-bbb.png', BB: 'gam-rank-bb.png', B: 'gam-rank-b.png', + C: 'gam-rank-c.png', D: 'gam-rank-d.png', +}; + function openRunResult(run) { resultDialog ??= buildResultDialog(); const dialog = resultDialog; @@ -644,6 +855,22 @@ function openRunResult(run) { const scored = cleared.filter((stage) => stage.achievement !== null); dialog.dataset.verdict = verdict; + // 底图用原作素材,按模式选:档位挑战是「段位認定」那一版, + // 小/大随机段位是「ランダム段位認定」那一版(原作本来就把这两件事分成两块屏)。 + // 页头文字与两侧水引都烘焙在底图里,所以标题 h2 只留给读屏。 + field(dialog, '.dan-result-art').dataset.art = + run.tier === 'small_random' || run.tier === 'big_random' ? 'random' : 'dani'; + const clearedRun = run.status === 'cleared'; + // 左下那格是原版的段位名牌:照原版写**名字**(我们发档位不发段位名),不盖合格印。 + // 通关几道另起一行小字放在名字下面,别把名字挤掉。 + const plateName = field(dialog, '.dan-plate-name'); + plateName.textContent = run.tierName; + plateName.dataset.len = String([...run.tierName].length); + // 结论(合格/不合格/中断)从原来那枚印改成名字下面的小字:印让位给段位名, + // 但「到底过没过」这句话还是得留着。 + field(dialog, '.dan-verdict-count').textContent = + `${VERDICT_TEXT[run.status] ?? '中断'} · ${cleared.length} / ${run.stageCount} 道通关`; + const limitText = run.perStageLimit ? '每道限时按当题难度' : `每道限时 ${fmtLimit(run.limitSeconds)}`; field(dialog, '.dan-result-sub').textContent = `${run.tierName} · ${run.minRating}–${run.maxRating} · 共 ${run.stageCount} 道 · ${limitText}` @@ -653,66 +880,118 @@ function openRunResult(run) { const tracks = field(dialog, '.dan-result-tracks'); tracks.replaceChildren(); for (const stage of run.stages) { - const tone = TRACK_TONE[cfRatingColor(stage.difficulty).tone] ?? TRACK_TONE.violet; + const color = cfRatingColor(stage.difficulty); + const tone = TRACK_TONE[color.tone] ?? TRACK_TONE.violet; const item = el('li', 'dan-track'); item.dataset.outcome = stage.outcome ?? 'pending'; - item.append(el('span', 'dan-track-stamp', stage.outcome === 'cleared' ? '可' : '不可')); - - const card = el('div', 'dan-track-card'); - card.style.setProperty('--tc1', tone[0]); - card.style.setProperty('--tc2', tone[1]); - - // 「封面」那一格放题号:我们抽的是 CF 题、没有曲绘,放题号比放装饰诚实。 + // 逐题行的纵向节距是官方的 122px(见 dan.css)。 + item.style.top = `${152 + 122 * (stage.index - 1)}px`; + // 底色变量挂在这一行上:乐曲边框读它(--tc1 主色场 / --tc2 外圈主色环)。 + // 官方那五张边框的颜色与难度名是烘死的,我们按自己的 CF 分档上色。 + item.style.setProperty('--tc1', tone[0]); + item.style.setProperty('--tc2', tone[1]); + + // 可 / 不可 用原作那两枚印(UI_DNM_Icon_Result_01/02)。 + const stamp = el('img', 'dan-track-stamp'); + stamp.src = stage.outcome === 'cleared' ? '/assets/maimai/stamp-clear.png' : '/assets/maimai/stamp-fail.png'; + stamp.alt = stage.outcome === 'cleared' ? '通过' : '未通过'; + item.append(stamp); + + // 行底板还是原作那张(UI_DNM_Result_musicBase_01);边框本体比行内区小 3.6px, + // 四条边会露出一点底板(原版贴图本来就留了这圈透明边)。 + item.append(el('div', 'dan-track-plate')); + + // 乐曲边框:照原作 UI_CMN_RSL_KopMBase_* 的版式画一层(尺寸全是从那张贴图量出来的比例, + // 见 dan.css 的「逐题行」一节)—— 外圈主色环 + 平色主色场 + 底部浅色带 + 左侧曲绘槽 + // + 深蓝歌名条(占顶部一整行)+ 白色达成率盒 + 右下角 DX 分数白牌。 + // 官方那五张(BSC/ADV/EXP/MST/MST_Re)颜色与难度名都烘死在图里,套不上我们的 CF 分档。 + const frame = el('div', 'dan-track-frame'); + // 底带先 append:它在白盒与右下白牌**下面**(原版就是这样一层浅色)。 + frame.append(el('div', 'dan-track-band')); + // 歌名条:原版左上那格是难度名牌(MASTER 那块),删掉之后歌名条顶满上面一整行。 + frame.append(el('span', 'dan-track-titlebar')); + // 题目名压在歌名条上(两者框内坐标相同)。抽到的题在本人提交里还没有记录时,退回题号 —— + // 空着比写「—」更难看,题号至少能对上。 + frame.append(el('span', 'dan-track-name', stage.title ?? stage.problemId ?? '—')); + const achBox = el('div', 'dan-track-achbox'); + // 官方白盒左上角那行小字(原作烘在边框贴图里,我们画边框所以自己写)。 + // 用原版那三个字:`達成率` —— 别自己写英文,官方结算这一行就是日文。 + achBox.append(el('small', 'dan-track-achlabel', '達成率')); + // 官方分数图集(按分数分色)+ 官方彩色「%」字形 —— 这是这一行最大的一块数字。 + // 没有成绩时用白字族的「—」,**染深蓝**:白字族是纯白剪影,落在达成率那个白框上会看不见 + // (numText 里没这个字形,会退回普通文字,不会静默丢字符)。 + const achValue = stage.achievement === null + ? numText('—', 'n90', 44, DX_NUM_COLOR) + : achievementNumber(stage.achievement, 44); + achValue.classList.add('dan-track-achvalue'); + achBox.append(achValue); + frame.append(achBox); + item.append(frame); + + // 单题 DX 分数(= 该题算出的 rating)挪到**卡片右下角**:原版那块白牌就压在底带右端, + // 上面烘的是日文 `でらっくスコア`(我们写「DX分数」)。底带的左半边留给限时。 + const dxBox = el('div', 'dan-track-dxbox'); + dxBox.append(el('small', 'dan-track-dxlabel', 'DX分数')); + // 这一格是**小字**:不叠描边层 —— 官方那处也是干净的深蓝数字,叠上去只会糊。 + const dx = stage.rating === null + ? numText('—', 'n26', 16, DX_NUM_COLOR, { outline: false, baseline: true }) + : numText(String(stage.rating.toFixed(1)), 'n26', 16, DX_NUM_COLOR, { outline: false, baseline: true }); + dx.classList.add('dan-track-dxnum'); + dxBox.append(dx); + item.append(dxBox); + + // 官方的 JacketImage_S 那一格:我们抽的是 CF 题、没有曲绘,槽里放**题号**(`1554C`: + // 场次 + 小问)与这题在 CF 上的 rating。原来那版把场次和小问拆成两行、题号只留个小问 + // 字母,一屏四张卡看不出是哪道题 —— 现在一行题号、一行 rating。 const [contest, index] = (stage.problemId ?? '').split(':'); const jacket = el('div', 'dan-track-jacket'); - jacket.append(el('b', '', index || '?'), el('small', '', contest || '未开始')); - card.append(jacket); - - const info = el('div', 'dan-track-info'); - const head = el('div', 'dan-track-head'); - head.append(el('span', 'dan-track-no', `STAGE ${String(stage.index).padStart(2, '0')}`)); - head.append(el('span', 'dan-track-diff', `${stage.difficulty} 分`)); - head.append(el('span', 'dan-track-name', stage.title ?? '—')); - info.append(head); - - const achievement = el('div', 'dan-track-ach'); - achievement.append(el('span', 'dan-track-achlabel', '达成率')); - achievement.append(goldNode('dan-track-achvalue dan-gold', - stage.achievement === null ? '—' : `${stage.achievement.toFixed(4)}%`)); - achievement.append(el('span', 'dan-track-judge', - stage.outcome === 'cleared' ? (stage.rank ?? '通关') - : stage.outcome === 'timeout' ? '超时' : stage.outcome === 'interrupted' ? '中断' : '未开始')); - info.append(achievement); - card.append(info); - - const side = el('div', 'dan-track-side'); - side.append(el('span', 'dan-track-lv', String(stage.difficulty))); - const score = el('span', 'dan-track-score'); - score.append(el('small', '', 'DX分数'), el('b', '', stage.rating === null ? '—' : stage.rating.toFixed(1))); - side.append(score); - side.append(el('small', 'dan-track-limit', `限时 ${fmtLimit(stage.limitSeconds)}`)); - card.append(side); - item.append(card); + jacket.append( + el('b', '', stage.problemId ? `${contest}${index}` : '未开始'), + // 题号没下发(还没开始做)时 rating 也不写:那边 `problemId` 是 null,这一格照同一口径。 + el('small', '', stage.problemId && stage.difficulty != null ? String(stage.difficulty) : '—'), + ); + item.append(jacket); + + // 题目名已经在上面跟歌名条一起 append 了(两者框内坐标相同)。原来标题条左边还有一格 + // **档位名牌**(写「上级」「初级」),删掉了:同一张卡上达成率、单题分、题号已经把信息 + // 说全,档位名重复而且占了半个标题条 —— 歌名条因此顶满上面一整行。 + + // 评级用官方徽章;超时/中断/未开始没有评级,退回文字。徽章放在**右上那块紫场**里 + // (白盒右端…边框内右沿 / 歌名条下沿…右下白牌上沿):原版那一块本来就是空的。 + if (stage.outcome === 'cleared' && RANK_ART[stage.rank]) { + const badge = el('img', 'dan-track-badge'); + badge.src = `/assets/maimai/${RANK_ART[stage.rank]}`; + badge.alt = stage.rank; + item.append(badge); + } else { + item.append(el('span', 'dan-track-judge', + stage.outcome === 'timeout' ? '超时' : stage.outcome === 'interrupted' ? '中断' : '未开始')); + } + + // 限时写在**底带的左侧**(原版这条底带是空的一块浅色,右端才是 DX 分数那块白牌)。 + item.append(el('small', 'dan-track-limit', `限时 ${fmtLimit(stage.limitSeconds)}`)); + tracks.append(item); } - field(dialog, '.dan-result-rankbox strong').textContent = VERDICT_TEXT[run.status] ?? '中断'; - field(dialog, '.dan-result-rankbox small').textContent = `${cleared.length} / ${run.stageCount} 道通关`; - - // 原作这一格是显示剩余生命的心;我们没有生命值,用「通关几道」占这一格。 - const life = field(dialog, '.dan-result-life'); + // 原作这一格是显示剩余生命的心;我们没有生命值,用心里的那个数字表示「四道里过了几道」。 + // 不写 4/4:数字图集里没有「/」这个字形,退回普通文字会被两边数字的描边吃掉看不见; + // 过没过、过几道,白框名字下面那行小字写得更清楚。 + const life = field(dialog, '.dan-life'); life.dataset.state = run.status === 'cleared' ? 'ok' : 'bad'; - field(dialog, '.dan-result-life span').replaceChildren( - document.createTextNode(String(cleared.length)), el('small', '', `/${run.stageCount}`)); - - // 原作的「总达成率」是四首歌达成率之和、「DX分数」是四首 DX 分数之和; - // 这里对应各道达成率之和与各道单题 rating 之和。 + field(dialog, '.dan-life-base').src = clearedRun + ? '/assets/maimai/life-base-green.png' : '/assets/maimai/life-base-red.png'; + field(dialog, '.dan-life-base').alt = clearedRun ? '通关' : '未通关'; + field(dialog, '.dan-life-count').replaceChildren(numText(String(cleared.length), 'n26', 46)); + + // 原作的「总达成率」是四首歌达成率之和、「でらっくスコア」是四首单曲 DX 分数之和。 + // 我们这边对应:各道达成率之和,以及**各道单题 rating 之和**(同一道题进 B50 用的就是 + // 这个单题 rating;之和只是展示,不等于 B50 总分真的涨这么多)。 const totalAchievement = scored.reduce((sum, stage) => sum + stage.achievement, 0); - const totalText = scored.length ? `${totalAchievement.toFixed(4)}%` : '—'; - const totalNode = field(dialog, '.dan-total-ach'); - totalNode.textContent = totalText; - totalNode.dataset.text = totalText; - field(dialog, '.dan-total-score b').textContent = run.totalRating === null ? '—' : run.totalRating.toFixed(1); + field(dialog, '.dan-total-num').replaceChildren( + scored.length ? achievementNumber(totalAchievement, 60) : numText('—', 'n90', 60, DX_NUM_COLOR)); + field(dialog, '.dan-dxscore-num').replaceChildren( + numText(run.totalRating === null ? '—' : String(run.totalRating.toFixed(1)), 'n90', 30, DX_NUM_COLOR, { outline: false, baseline: true })); const deltas = run.stages.map((stage) => stage.comparison?.ratingDelta).filter((value) => typeof value === 'number'); let note = run.status === 'cleared' diff --git a/public/dx.html b/public/dx.html index 86b02f5..f07c7ca 100644 --- a/public/dx.html +++ b/public/dx.html @@ -4,19 +4,21 @@ Algorithm DX · Rating 总览 - + + - + - + - + +
      @@ -292,5 +298,7 @@

      支持项目

      微信扫一扫,或点击图片查看原图。

      +TOP + diff --git a/public/help.html b/public/help.html index b22843c..bb9847c 100644 --- a/public/help.html +++ b/public/help.html @@ -1,13 +1,15 @@ -使用指南 · Algorithm DX + +使用指南 · Algorithm DX - + +
      GETTING STARTED

      使用指南

      从绑定第一个账号,到记录练习、看懂成绩与统计。

      前往账号管理 ↗
      @@ -136,4 +142,6 @@

      2. 取得 API key / secret 后配置

      - +TOP + + diff --git a/public/index.html b/public/index.html index 8445a4b..6ff52d7 100644 --- a/public/index.html +++ b/public/index.html @@ -4,18 +4,22 @@ Algorithm DX · AC 记录 - + + + + - + +
      -
      PRACTICE JOURNAL

      每一次通过,都值得记录

      汇集各平台的解题足迹,回看尝试,也发现下一步。

      +
      PRACTICE JOURNAL

      每一次通过,都值得记录

      汇集各平台的解题足迹,回看尝试,也发现下一步。

      —
      + 练习历史 ↗ + 查看我的 DX Rating ↗
      @@ -210,6 +220,8 @@

      这个筛选条件下没有记录

      +TOP + diff --git a/public/stat-charts.js b/public/stat-charts.js index 14f7410..3e76789 100644 --- a/public/stat-charts.js +++ b/public/stat-charts.js @@ -1,7 +1,15 @@ const colors = ['#5bceac','#62a8ff','#ad93ff','#f2c66d','#f38a96','#68d5e7','#c1cbdd']; const node = (tag,text,cls) => { const n=document.createElement(tag); if(text)n.textContent=text; if(cls)n.className=cls;return n; }; -const font = 'system-ui, "Microsoft YaHei", sans-serif'; +// 画布里的字也要跟官方那套字体(@font-face 已加载,render 前会 await document.fonts.ready) +const font = '"M PLUS 1p", "M PLUS 1p Symbols", "Microsoft YaHei", "PingFang SC", sans-serif'; function text(ctx, value, x, y, size=12, color='#a9b8cd',align='left') { ctx.font=`${size}px ${font}`;ctx.fillStyle=color;ctx.textAlign=align;ctx.fillText(String(value),x,y); } +// 画布上的底色、网格和字号不跟 CSS 变量走,得按主题自己取一套; +// 切换主题时由 stats-page 重新画一遍(导出图片始终用深色海报底,不受这里影响) +const PALETTE = { + light: { plot:'#f2eefb', grid:'#ded6ee', track:'#e7e1f4', text:'#5a5470', muted:'#7b7490', value:'#2f2a44', stroke:'#00000022' }, + dark: { plot:'#141e2e', grid:'#27364b', track:'#233147', text:'#a9b8cd', muted:'#8798b2', value:'#e0eafb', stroke:'#ffffff55' }, +}; +const pal = () => PALETTE[document.documentElement.dataset.mmTheme === 'dark' ? 'dark' : 'light']; function shorten(value,n=24) { return value.length>n ? value.slice(0,n-1)+'…' : value; } export function chart(title, input, note='', type='bar') { @@ -11,16 +19,16 @@ export function chart(title, input, note='', type='bar') { const height=type==='bar' ? Math.max(160,rows.length*29+30) : type==='donut' ? Math.max(250,rows.length*27+40) : 260; const canvas=node('canvas',null,'stat-canvas');canvas.width=1040;canvas.height=height*2;canvas.setAttribute('role','img'); canvas.setAttribute('aria-label',`${title}。${rows.map(r=>`${r.label}:${r.count}`).join(';')}`); - const ctx=canvas.getContext('2d');ctx.scale(2,2);ctx.fillStyle='#141e2e';ctx.fillRect(0,0,520,height); - if (!total) {text(ctx,'此范围暂无数据',260,height/2,15,'#8798b2','center');} + const ctx=canvas.getContext('2d');ctx.scale(2,2);const p=pal();ctx.fillStyle=p.plot;ctx.fillRect(0,0,520,height); + if (!total) {text(ctx,'此范围暂无数据',260,height/2,15,p.muted,'center');} else if (type==='donut') { let angle=-Math.PI/2; rows.forEach((r,i)=> {const end=angle+r.count/total*Math.PI*2;ctx.beginPath();ctx.arc(115,125,76,angle,end);ctx.strokeStyle=colors[i%colors.length];ctx.lineWidth=24;ctx.stroke();angle=end; - ctx.fillStyle=colors[i%colors.length];ctx.fillRect(225,22+i*27,9,9);text(ctx,shorten(r.label,23),243,31+i*27);text(ctx,`${r.count} · ${(r.count/total*100).toFixed(1)}%`,505,31+i*27,11,'#e0eafb','right');}); - text(ctx,total,115,127,30,'#e6edf3','center');text(ctx,'次提交',115,151,12,'#8798b2','center'); + ctx.fillStyle=colors[i%colors.length];ctx.fillRect(225,22+i*27,9,9);text(ctx,shorten(r.label,23),243,31+i*27,12,p.text);text(ctx,`${r.count} · ${(r.count/total*100).toFixed(1)}%`,505,31+i*27,11,p.value,'right');}); + text(ctx,total,115,127,30,p.value,'center');text(ctx,'次提交',115,151,12,p.muted,'center'); } else if(type==='line' || type==='columns') { const left=42,right=500,top=28,bottom=210,axisMax=Math.ceil(max/4)*4; - for(let i=0;i<=4;i++){const y=bottom-(bottom-top)*i/4;ctx.strokeStyle='#27364b';ctx.beginPath();ctx.moveTo(left,y);ctx.lineTo(right,y);ctx.stroke();text(ctx,axisMax*i/4,left-8,y+4,10,'#8798b2','right');} + for(let i=0;i<=4;i++){const y=bottom-(bottom-top)*i/4;ctx.strokeStyle=p.grid;ctx.beginPath();ctx.moveTo(left,y);ctx.lineTo(right,y);ctx.stroke();text(ctx,axisMax*i/4,left-8,y+4,10,p.muted,'right');} const x=i=>left+(right-left)*(rows.length===1?0.5:i/(rows.length-1)); const y=r=>bottom-r.count/axisMax*(bottom-top); if(type==='line') { @@ -28,10 +36,10 @@ export function chart(title, input, note='', type='bar') { ctx.lineTo(x(rows.length-1),bottom);ctx.lineTo(x(0),bottom);ctx.closePath();const g=ctx.createLinearGradient(0,top,0,bottom);g.addColorStop(0,'#5bceac55');g.addColorStop(1,'#5bceac00');ctx.fillStyle=g;ctx.fill(); rows.forEach((r,i)=>{if(rows.length<=40){ctx.beginPath();ctx.arc(x(i),y(r),3,0,Math.PI*2);ctx.fillStyle=colors[0];ctx.fill();}}); } else rows.forEach((r,i)=>{const w=(right-left)/rows.length;ctx.fillStyle=colors[1];ctx.fillRect(left+i*w+2,y(r),Math.max(1,w-4),bottom-y(r));}); - rows.forEach((r,i)=>{if(i===0||i===rows.length-1||i%Math.max(1,Math.ceil(rows.length/6))===0) text(ctx,r.label.length>7?r.label.slice(5):r.label,type==='line'?x(i):left+(i+.5)*(right-left)/rows.length,233,10,'#8798b2','center');}); - text(ctx,'提交次数',left,14,10); + rows.forEach((r,i)=>{if(i===0||i===rows.length-1||i%Math.max(1,Math.ceil(rows.length/6))===0) text(ctx,r.label.length>7?r.label.slice(5):r.label,type==='line'?x(i):left+(i+.5)*(right-left)/rows.length,233,10,p.muted,'center');}); + text(ctx,'提交次数',left,14,10,p.muted); } else { - rows.forEach((r,i)=>{const y=20+i*29; text(ctx,shorten(r.label,25),5,y+12,12);ctx.fillStyle='#233147';ctx.fillRect(195,y,250,12);ctx.fillStyle=r.color||colors[i%colors.length];ctx.fillRect(195,y,250*r.count/max,12);if(r.color){ctx.strokeStyle='#ffffff55';ctx.lineWidth=1;ctx.strokeRect(195,y,250*r.count/max,12);}text(ctx,r.count,508,y+12,12,'#e0eafb','right');}); + rows.forEach((r,i)=>{const y=20+i*29; text(ctx,shorten(r.label,25),5,y+12,12,p.text);ctx.fillStyle=p.track;ctx.fillRect(195,y,250,12);ctx.fillStyle=r.color||colors[i%colors.length];ctx.fillRect(195,y,250*r.count/max,12);if(r.color){ctx.strokeStyle=p.stroke;ctx.lineWidth=1;ctx.strokeRect(195,y,250*r.count/max,12);}text(ctx,r.count,508,y+12,12,p.value,'right');}); } const viewport=node('div',null,'stat-canvas-frame');viewport.append(canvas);box.append(viewport); const detail=node('details',null,'chart-values');detail.append(node('summary','查看完整数据')); diff --git a/public/stats-page.js b/public/stats-page.js index c4b2455..6f0cabc 100644 --- a/public/stats-page.js +++ b/public/stats-page.js @@ -12,6 +12,9 @@ const heatmap = initHeatmap(document.getElementById('heatmapHost'), syncUrl); let revision = 0; let heatRevision = 0; let detailReady = false, heatReady = false; +// 图表是画布画的,换主题得按新配色重画一遍(数据没变,直接用上一次的响应) +let lastAnalytics = null; +document.addEventListener('mm-theme-change', () => { if (lastAnalytics) analytics.render(lastAnalytics); }); const saveButton = document.getElementById('saveStatsImage'); const updateSave = () => { saveButton.disabled = !detailReady || !heatReady; }; saveButton.addEventListener('click', async () => { @@ -51,6 +54,7 @@ async function refresh(includeHeat = true, background = false) { if (platform.value) params.platform = platform.value; const detail = get('/api/stats', params).then(body => { if (current !== revision) return; + lastAnalytics = body.analytics; analytics.render(body.analytics); renderToday(body.today); detailReady = true; updateSave(); diff --git a/public/stats.html b/public/stats.html index 173686e..4784c9e 100644 --- a/public/stats.html +++ b/public/stats.html @@ -3,19 +3,21 @@ - + + Algorithm DX · 练习数据 - - + + - + +
      @@ -40,5 +46,7 @@
      +TOP + diff --git a/public/theme-dark.css b/public/theme-dark.css new file mode 100644 index 0000000..a2621fb --- /dev/null +++ b/public/theme-dark.css @@ -0,0 +1,383 @@ +/* Algorithm DX · 深色模式 + * + * 只在 时生效,作为 theme.css 之后的一层「同结构换材质」: + * 选择器都带 [data-mm-theme="dark"] 前缀,权重高于 theme.css 的 body.mm-theme 规则, + * 所以浅色那一套一个字节都不用改。 + * + * 背景同样是官方那份动态背景,只是换成夜版贴图:theme.js 给 iframe 发 + * postMessage({mmTheme:'dark'}),bg.html 挂上 theme-night.css(见那两个文件)。 + * + * 底色沿用夜景:深靛蓝面板 + 官方那套紫/粉横条与 14px 硬阴影;卡片、输入框、气泡、 + * 表格、说明卡全部转深色,正文色跟着 token 一起翻。 + * + * 不翻的两类「成品屏」:段位卡/题库卡的卡面(styles.css 里写死的深色渐变)和点开后的 + * 成绩单(.dx-result / .dan-result-*)—— 它们本来就是一张画好的屏,明暗两版都保留自己的脸, + * 翻成夜版反而两不像;外壳(弹窗遮罩、关闭钮)照常跟主题。 + */ + +/* ============ 1. 调色 ============ */ + +[data-mm-theme="dark"] { + color-scheme: dark; + + /* 夜景里直接用的几档色(与 assets/night/sky.png 同源) */ + --mm-night: #101538; + --mm-night-plate: #1b2148; /* 顶栏胶囊 */ + --mm-night-surface: #191e42; /* 大盒子 */ + --mm-night-surface-2: #1d2348; /* 盒子里的块、输入框 */ + --mm-night-surface-3: #232a55; /* 再抬一层:hover、选中 */ + --mm-night-line: rgba(255, 255, 255, .12); + --mm-night-line-soft: rgba(255, 255, 255, .08); + --mm-night-ink: #eceefb; + --mm-night-dim: #c6cbe8; + --mm-night-muted: #9aa0c4; + + /* 官方调色在深色底上的几处调整 */ + --mm-navy: #e3e7ff; /* 这个 token 在本站当「标题色」用 */ + --mm-ink: #eceefb; + --mm-grey: rgba(255, 255, 255, .18); + --mm-purple-tint: #231f4c; + --mm-purple-deep: #d3a1ff; + /* 只改硬阴影的颜色,偏移留给 theme.css 里那三档 vw 阶梯 */ + --mm-card-shadow-color: rgba(0, 0, 0, .45); + --mm-brand-mark: url(/algorithm-dx-icon.svg); + --mm-nav-shadow: 10px 10px 0 rgba(0, 0, 0, .45); + --mm-soft: 0 10px 26px -14px rgba(0, 0, 0, .65); + --mm-gradient: linear-gradient(0deg, #1a1f45 6%, #1d2150 16%, #262a63 30%, #332c72 46%, + #402f80 56%, #453a8f 67%, #2a3a74 85%, #151f47 95%); + + /* 仓库既有 token 一并翻过来(styles.css:131-154 那套深色语义在这里复活) */ + --bg: var(--mm-night); + --bg-soft: #141a3c; + --panel: var(--mm-night-surface); + --panel-2: var(--mm-night-surface-2); + --border: var(--mm-night-line); + --border-soft: var(--mm-night-line-soft); + --text: var(--mm-night-ink); + --text-dim: var(--mm-night-dim); + --muted: var(--mm-night-muted); + --accent: var(--mm-purple-soft); + --accent-soft: rgba(186, 94, 234, .18); + --ac: #58e08a; + --ac-soft: rgba(75, 224, 112, .16); + --warn: #ffc266; + --warn-soft: rgba(255, 151, 42, .16); + --bad: #ff7d92; + --bad-soft: rgba(231, 33, 57, .18); + --shadow: 8px 8px 0 0 rgba(0, 0, 0, .45); + --surface-gradient: var(--mm-night-surface-2); + --focus-ring: var(--mm-purple-soft); +} + +html[data-mm-theme="dark"], +[data-mm-theme="dark"] body { background: var(--mm-night); } + +[data-mm-theme="dark"] body.mm-theme { + background: var(--mm-night); + background-image: var(--mm-gradient); + color: var(--mm-night-ink); +} + +[data-mm-theme="dark"] body.mm-theme ::selection { background: var(--mm-purple); color: #fff; } + +/* ============ 2. 顶栏与开关 ============ */ + +[data-mm-theme="dark"] body.mm-theme .site-header { + background: var(--mm-night-plate); + border-color: var(--mm-night-line-soft); + box-shadow: var(--mm-nav-shadow); +} +[data-mm-theme="dark"] body.mm-theme .site-header .brand, +[data-mm-theme="dark"] body.mm-theme .site-header .brand-text h1 { color: var(--mm-night-ink); } +[data-mm-theme="dark"] body.mm-theme .site-header .nav .btn { color: var(--mm-night-dim); } +[data-mm-theme="dark"] body.mm-theme .site-header .nav .btn:hover { color: var(--mm-purple-soft); } +[data-mm-theme="dark"] body.mm-theme .site-header .global-sync-bar { + background: var(--mm-night-surface-2); + border-color: var(--mm-night-line-soft); +} + +[data-mm-theme="dark"] .mm-menu-btn { background: var(--mm-night-plate); } +[data-mm-theme="dark"] .mm-menu-btn span { background: var(--mm-pink); } +[data-mm-theme="dark"] body.mm-menu-open .mm-menu-btn { background: var(--mm-purple); border-color: var(--mm-purple); } +[data-mm-theme="dark"] body.mm-menu-open .mm-menu-btn span { background: #fff; } +@media (max-width: 1000px) { + [data-mm-theme="dark"] body.mm-theme .site-header .nav { background: var(--mm-night); } + [data-mm-theme="dark"] body.mm-theme .site-header .nav .btn:not(:last-child) { border-bottom-color: var(--mm-night-line); } +} + +/* 开关本身:深色下换成太阳,底色转深 */ +[data-mm-theme="dark"] body.mm-theme .mm-theme-btn { + border-color: var(--mm-purple-soft); + background: var(--mm-night-surface-3); + box-shadow: 4px 4px 0 rgba(0, 0, 0, .45); +} +[data-mm-theme="dark"] body.mm-theme .mm-theme-btn__moon { opacity: 0; transform: scale(.4) rotate(90deg); } +[data-mm-theme="dark"] body.mm-theme .mm-theme-btn__sun { opacity: 1; transform: none; } +[data-mm-theme="dark"] body.mm-theme .mm-theme-btn:hover { background: var(--mm-purple); } +[data-mm-theme="dark"] body.mm-theme .mm-theme-btn:hover .mm-theme-btn__sun { fill: #fff; } + +/* ============ 3. 文字:白色描边换成暗色投影 ============ */ + +[data-mm-theme="dark"] body.mm-theme .ac-page-heading h2, +[data-mm-theme="dark"] body.mm-theme .stats-toolbar h2, +[data-mm-theme="dark"] body.mm-theme .circle-hero h2, +[data-mm-theme="dark"] body.mm-theme .dan-hero h2, +[data-mm-theme="dark"] body.mm-theme .help-heading h2, +[data-mm-theme="dark"] body.mm-theme .dx-page-intro h2, +[data-mm-theme="dark"] body.mm-theme .circle-feed-head h2, +[data-mm-theme="dark"] body.mm-theme .today-section-title { + color: var(--mm-navy); + text-shadow: 0 2px 8px rgba(0, 0, 0, .55); +} +[data-mm-theme="dark"] body.mm-theme .analytics-note, +[data-mm-theme="dark"] body.mm-theme #message, +[data-mm-theme="dark"] body.mm-theme .dan-msg, +[data-mm-theme="dark"] body.mm-theme .dan-disclaimer, +[data-mm-theme="dark"] body.mm-theme .circle-note, +[data-mm-theme="dark"] body.mm-theme .pager-hint { + color: var(--mm-night-dim); + text-shadow: 0 1px 4px rgba(0, 0, 0, .6); +} +[data-mm-theme="dark"] body.mm-theme .help-page > p { + color: var(--mm-night-dim); + text-shadow: 0 1px 4px rgba(0, 0, 0, .6); +} +[data-mm-theme="dark"] body.mm-theme .help-page .help-heading p, +[data-mm-theme="dark"] body.mm-theme .help-page > section p, +[data-mm-theme="dark"] body.mm-theme .help-page > section li, +[data-mm-theme="dark"] body.mm-theme .help-page details p { color: var(--mm-night-dim); } +[data-mm-theme="dark"] body.mm-theme .circle-hero p, +[data-mm-theme="dark"] body.mm-theme .ac-page-heading p, +[data-mm-theme="dark"] body.mm-theme .stats-toolbar p, +[data-mm-theme="dark"] body.mm-theme .dx-page-intro p { color: var(--mm-night-muted); } +/* 数据条里两个写死的深色数字(§9) */ +[data-mm-theme="dark"] body.mm-theme .stat.is-ac .stat-value { color: #5fe08f; } +[data-mm-theme="dark"] body.mm-theme .stat.is-unac .stat-value { color: #ffc266; } + +/* ============ 4. 玻璃板(页头/工具条/侧栏这些浮在背景上的块) ============ */ + +[data-mm-theme="dark"] body.mm-theme .ac-page-heading, +[data-mm-theme="dark"] body.mm-theme .stats-toolbar, +[data-mm-theme="dark"] body.mm-theme .circle-hero, +[data-mm-theme="dark"] body.mm-theme .dan-hero, +[data-mm-theme="dark"] body.mm-theme .help-heading, +[data-mm-theme="dark"] body.mm-theme .help-toc, +[data-mm-theme="dark"] body.mm-theme .pager, +[data-mm-theme="dark"] body.mm-theme .dx-page-intro, +[data-mm-theme="dark"] body.mm-theme .circle-feed-head { + background: rgba(19, 24, 52, .88); + border: 1px solid var(--mm-night-line); + box-shadow: 0 18px 40px -30px rgba(0, 0, 0, .8); +} +@supports ((backdrop-filter: blur(2px)) or (-webkit-backdrop-filter: blur(2px))) { + [data-mm-theme="dark"] body.mm-theme .ac-page-heading, + [data-mm-theme="dark"] body.mm-theme .stats-toolbar, + [data-mm-theme="dark"] body.mm-theme .circle-hero, + [data-mm-theme="dark"] body.mm-theme .dan-hero, + [data-mm-theme="dark"] body.mm-theme .help-heading, + [data-mm-theme="dark"] body.mm-theme .help-toc, + [data-mm-theme="dark"] body.mm-theme .pager, + [data-mm-theme="dark"] body.mm-theme .dx-page-intro, + [data-mm-theme="dark"] body.mm-theme .circle-feed-head { + background: rgba(19, 24, 52, .88); + -webkit-backdrop-filter: blur(12px) saturate(1.2); + backdrop-filter: blur(12px) saturate(1.2); + } +} + +/* 落在背景花纹上的那几处小字:浅色层给的是白底板,这里换成夜色底板 */ +[data-mm-theme="dark"] body.mm-theme .app > .analytics-note, +[data-mm-theme="dark"] body.mm-theme .app > #message, +[data-mm-theme="dark"] body.mm-theme .app > .dan-msg, +[data-mm-theme="dark"] body.mm-theme .circle-main > #message, +[data-mm-theme="dark"] body.mm-theme .heatmap-top > .analytics-note, +[data-mm-theme="dark"] body.mm-theme .help-page > p { + background: var(--mm-night-surface-2); + box-shadow: 0 8px 20px -14px rgba(0, 0, 0, .9); + text-shadow: none; +} + +/* ============ 5. 官方盒子与通用块 ============ */ + +[data-mm-theme="dark"] body.mm-theme .filters, +[data-mm-theme="dark"] body.mm-theme .admin, +[data-mm-theme="dark"] body.mm-theme .foot, +[data-mm-theme="dark"] body.mm-theme .section, +[data-mm-theme="dark"] body.mm-theme .stat-row, +[data-mm-theme="dark"] body.mm-theme .ac-platform-strip, +[data-mm-theme="dark"] body.mm-theme .placeholder, +[data-mm-theme="dark"] body.mm-theme .empty, +[data-mm-theme="dark"] body.mm-theme.ac-page .filters, +[data-mm-theme="dark"] body.mm-theme .analytics, +[data-mm-theme="dark"] body.mm-theme .today-panel, +[data-mm-theme="dark"] body.mm-theme .dx-timer, +[data-mm-theme="dark"] body.mm-theme .circle-sidebar, +[data-mm-theme="dark"] body.mm-theme .circle-filters, +[data-mm-theme="dark"] body.mm-theme .dx-rules { + background: var(--mm-night-surface); + color: var(--mm-night-ink); +} +[data-mm-theme="dark"] body.mm-theme .analytics-chart, +[data-mm-theme="dark"] body.mm-theme .today-card, +[data-mm-theme="dark"] body.mm-theme .dx-pages, +[data-mm-theme="dark"] body.mm-theme .dx-records-filters, +[data-mm-theme="dark"] body.mm-theme .circle-card, +[data-mm-theme="dark"] body.mm-theme .help-page > section { + background: var(--mm-night-surface-2); + border-color: var(--mm-night-line-soft); + color: var(--mm-night-ink); +} +/* 这块左侧有官方绿条,别被上面的 border-color 一起抹掉 */ +[data-mm-theme="dark"] body.mm-theme .analytics-metrics > div { + background: var(--mm-night-surface-2); + border-color: var(--mm-night-line-soft); + border-left: 4px solid var(--mm-green); + color: var(--mm-night-ink); +} +/* 空格子原来和面板几乎同色(#1b2145 对 #191e42),整年没练的日子直接看不见, + 图例最短的一格也是空的:抬亮一点,靠白描边分层 */ +[data-mm-theme="dark"] body.mm-theme .heat-cell { border-color: rgba(255, 255, 255, .14); } +[data-mm-theme="dark"] body.mm-theme .level-0 { background: rgba(255, 255, 255, .09); } +[data-mm-theme="dark"] body.mm-theme .level-1 { background: #1d4a37; } +[data-mm-theme="dark"] body.mm-theme .level-2 { background: #256f4a; } +[data-mm-theme="dark"] body.mm-theme .level-3 { background: #2f9d62; } +[data-mm-theme="dark"] body.mm-theme .level-4 { background: var(--mm-green); } + +[data-mm-theme="dark"] body.mm-theme .foot-note { color: var(--mm-night-muted); } +[data-mm-theme="dark"] body.mm-theme.ac-page .card:hover { background: var(--mm-night-surface-3); } +[data-mm-theme="dark"] body.mm-theme .ac-page .card-stats .k { color: var(--mm-night-muted); } +[data-mm-theme="dark"] body.mm-theme .ac-page .card-stats .v { color: var(--mm-night-ink); } +[data-mm-theme="dark"] body.mm-theme .dx-card.is-empty { color: var(--mm-night-muted); } +[data-mm-theme="dark"] body.mm-theme .dx-more a { + background: var(--mm-night-surface-2); + border-color: var(--mm-night-line); + color: var(--mm-navy); +} +[data-mm-theme="dark"] body.mm-theme .dx-ranked, +[data-mm-theme="dark"] body.mm-theme .dx-rank, +[data-mm-theme="dark"] body.mm-theme .today-total { color: #7ef0a4; } + +/* ============ 6. 控件:输入、按钮、气泡、表格、弹窗 ============ */ + +[data-mm-theme="dark"] body.mm-theme .input, +[data-mm-theme="dark"] body.mm-theme .select { + background: var(--mm-night-surface-2); + border-color: var(--mm-night-line); + color: var(--mm-night-ink); + color-scheme: dark; +} +[data-mm-theme="dark"] body.mm-theme .input::placeholder { color: #8f94b4; } +[data-mm-theme="dark"] body.mm-theme .analytics-controls .input { color: var(--mm-night-ink); color-scheme: dark; } +[data-mm-theme="dark"] body.mm-theme .btn-ghost { + background: var(--mm-night-surface-2); + border-color: var(--mm-night-line); + color: var(--mm-night-dim); +} +[data-mm-theme="dark"] body.mm-theme .btn-ghost:hover { background: var(--mm-night-surface-3); color: var(--mm-navy); } +[data-mm-theme="dark"] body.mm-theme .chip { + background: var(--mm-night-surface-2); + border-color: var(--mm-night-line); + color: var(--mm-night-dim); +} +/* 浅色里这组是「淡紫底 + 白药丸」,深色照搬会变成「紫底 + 深蓝药丸」,看着像反的。 + 夜里的对应关系是「比盒子更深一层的底 + 比底更亮的药丸」 */ +[data-mm-theme="dark"] body.mm-theme .tabs { background: #151a3c; } +[data-mm-theme="dark"] body.mm-theme .tab.is-active { background: #262d5c; color: var(--mm-navy); box-shadow: 0 2px 0 rgba(0, 0, 0, .35); } +[data-mm-theme="dark"] body.mm-theme .db-chip { + background: var(--mm-night-surface-2); + border-color: var(--mm-night-line); + color: #a8e6c8; +} +[data-mm-theme="dark"] body.mm-theme .ac-page .db-chip { color: #a8e6c8; border-color: var(--mm-night-line); } +[data-mm-theme="dark"] body.mm-theme .expand-btn { + background: var(--mm-night-surface-2); + border-color: var(--mm-night-line); + color: var(--mm-night-dim); +} +[data-mm-theme="dark"] body.mm-theme .rt-table { background: var(--mm-night-surface-2); } +[data-mm-theme="dark"] body.mm-theme .rt-table th { background: var(--mm-purple-tint); color: var(--mm-navy); } +[data-mm-theme="dark"] body.mm-theme .rt-table td { border-color: var(--mm-night-line-soft); } +[data-mm-theme="dark"] body.mm-theme .modal-card, +[data-mm-theme="dark"] body.mm-theme dialog { + background: var(--mm-night-surface); + border-color: var(--mm-night-line); + color: var(--mm-night-ink); +} +[data-mm-theme="dark"] body.mm-theme dialog::backdrop { background: rgba(6, 9, 26, .62); } +[data-mm-theme="dark"] body.mm-theme .account-owner { background: var(--mm-night-surface-2); } +[data-mm-theme="dark"] body.mm-theme .account-owner-content { background: #1c224a; } +[data-mm-theme="dark"] body.mm-theme .help-inline { background: var(--mm-night-surface-2); border-color: var(--mm-night-line-soft); } +[data-mm-theme="dark"] body.mm-theme .help-inline summary { color: var(--mm-navy); } +[data-mm-theme="dark"] body.mm-theme .account-owner-avatar { background: var(--mm-purple-tint); color: var(--mm-purple-soft); } + +/* 气泡:底色转深,字色跟着暗色不刺眼 */ +[data-mm-theme="dark"] body.mm-theme .badge, +[data-mm-theme="dark"] body.mm-theme .ac-card-tags .badge { background: rgba(255, 255, 255, .1); color: #cdd3f2; border-color: transparent; } +[data-mm-theme="dark"] body.mm-theme .badge.ac { background: rgba(75, 224, 112, .16); color: #7ef0a4; } +[data-mm-theme="dark"] body.mm-theme .badge.warn { background: rgba(255, 151, 42, .16); color: #ffc266; } +[data-mm-theme="dark"] body.mm-theme .badge.bad { background: rgba(231, 33, 57, .18); color: #ff93a5; } +[data-mm-theme="dark"] body.mm-theme .badge.diff { background: rgba(53, 132, 254, .18); color: #9cc6ff; } +[data-mm-theme="dark"] body.mm-theme .badge.contest { background: rgba(186, 94, 234, .18); color: #d7aeff; } +[data-mm-theme="dark"] body.mm-theme .badge.platform { background: rgba(2, 200, 211, .16); color: #7fe6ee; } +[data-mm-theme="dark"] body.mm-theme .badge.score { background: rgba(255, 151, 42, .16); color: #ffc266; } + +/* 提示条:浅色那版是实心暖白底,所以深色也必须是实心底 —— 只铺一层 10% 的色纱的话, + 夜里的云和彩虹会直接透到字缝里(这块正好压在彩虹上)。色纱按同一个比例调进实心底里, + 颜色关系不变,字下面有底。 */ +[data-mm-theme="dark"] body.mm-theme .notice { background: #2e283c; color: #f0dcb4; } +[data-mm-theme="dark"] body.mm-theme .notice strong { color: #ffd79a; } +[data-mm-theme="dark"] body.mm-theme .notice-warn { background: #2e283c; color: #ecd9a4; } +[data-mm-theme="dark"] body.mm-theme .notice-warn strong { color: #f6dfa8; } +[data-mm-theme="dark"] body.mm-theme .notice-warn span { color: #d9c48d; } +[data-mm-theme="dark"] body.mm-theme .notice-ac { background: #1c2f43; color: #b6e3bc; } +[data-mm-theme="dark"] body.mm-theme .notice-ac strong { color: #d6f0da; } +[data-mm-theme="dark"] body.mm-theme .notice-ac.is-bad, +[data-mm-theme="dark"] body.mm-theme .notice-danger { background: #301c3d; color: #ffb9b4; } +[data-mm-theme="dark"] body.mm-theme .notice-ac.is-bad strong, +[data-mm-theme="dark"] body.mm-theme .notice-danger strong { color: #ff9d97; } +[data-mm-theme="dark"] body.mm-theme .notice-neutral { background: #25294a; color: var(--mm-night-muted); } + +/* ============ 7. 题友圈 ============ */ + +[data-mm-theme="dark"] body.mm-theme .circle-eyebrow { color: var(--mm-purple-soft); } +[data-mm-theme="dark"] body.mm-theme .circle-person:hover { background: var(--mm-night-surface-3); } +[data-mm-theme="dark"] body.mm-theme .circle-person[aria-pressed="true"] { + background: rgba(186, 94, 234, .22); + border-color: var(--mm-purple-soft); + color: #e3c6ff; +} +[data-mm-theme="dark"] body.mm-theme .circle-avatar { + background: hsl(var(--hue, 160) 34% 26%); + color: hsl(var(--hue, 160) 70% 84%); +} +[data-mm-theme="dark"] body.mm-theme .circle-result { background: rgba(255, 255, 255, .08); color: #c3c9e6; } +[data-mm-theme="dark"] body.mm-theme .circle-result.ac { background: rgba(75, 224, 112, .14); color: #7ef0a4; } +[data-mm-theme="dark"] body.mm-theme .circle-coverage-status.needs-attention { + background: rgba(255, 151, 42, .14); + color: #ffc266; +} +[data-mm-theme="dark"] body.mm-theme .circle-card-head, +[data-mm-theme="dark"] body.mm-theme .circle-card-foot { border-color: var(--mm-night-line-soft); } + +/* ============ 8. 使用指南 ============ */ + +[data-mm-theme="dark"] body.mm-theme .help-page .help-toc a { + background: var(--mm-night-surface-2); + border-color: var(--mm-night-line); + color: var(--mm-navy); +} +[data-mm-theme="dark"] body.mm-theme .help-page .help-toc a:hover { + background: var(--mm-purple-tint); + border-color: var(--mm-purple-soft); + color: #e3c6ff; +} +[data-mm-theme="dark"] body.mm-theme kbd { + background: var(--mm-purple-tint); + border-color: var(--mm-purple-soft); + color: var(--mm-navy); +} + +/* ============ 9. 底图之外还需要单独一提的两处 ============ */ + +/* 背景 iframe 之上、正文之下的兜底层,深色下别闪白 */ +[data-mm-theme="dark"] body.mm-theme .app { position: relative; } \ No newline at end of file diff --git a/public/theme-night.css b/public/theme-night.css new file mode 100644 index 0000000..dfe6618 --- /dev/null +++ b/public/theme-night.css @@ -0,0 +1,81 @@ +/* ============ 夜版背景 ============ + * + * 结构、层级、尺寸、动画全部沿用官方 style.css,这里只做一件事:换贴图素材 + * (全部在 /assets/night/,由官方素材重上色生成,官方文件一个没动)。 + * + * 用哪一层: + * sky / sky-frame 天空渐变(内层渐变块 / 圆角外那圈) + * pattern 渐变上的图案叠加 + * shines 闪点(官方在 ≥769px 换成 PC 那张,这里跟着换) + * cloud-* 六片云(白体换成夜蓝,原来的粉/橙暖色描边按原色留着) + * rainbow-back 彩虹后面那片云 + * rainbow-bottom 彩虹两端那两片云(同样分 SP / PC 两份) + * rainbow 彩虹(降成月虹) + * aurora 曙光(配合 screen 叠加 + 模糊化成柔光) + * 月亮和流星保持官方素材:夜里它们本来就该是最亮的。 + * + * **带 -sp / -pc 的素材必须跟着官方的断点走**:官方在基础规则里用 `_sp`、在 + * `@media (min-width:769px)` 里换成 `_pc`。只挂一份的话,宽屏会拿手机那张顶 PC 那格 —— + * 彩虹两端那片云的 SP 版是被图片边界切平的窄裁版(左右两边 85% / 44% 的像素是实心的), + * 铺进 PC 那格就会在画面中间露出两条平直的切边。下面每个断点都跟官方一一对应。 + * + * 换素材之外还有四处按夜版需要动了样式,都在下面并注明原因: + * html/body 底色、彩虹底边淡出、曙光的叠加方式与柔光、月亮那圈月光。 + */ + +/* iframe 底色(渐变块圆角之外露出的那一圈由 .bg 的贴图负责) */ +html, body { background: #0e1234; } + +/* —— 天空 —— */ +.bg { background-image: url(/assets/night/sky-frame.png); } +.bg--gradient { background-image: url(/assets/night/sky.png); } +.bg--gradient:before { background-image: url(/assets/night/pattern.png); } + +/* —— 手机档(官方基础规则那套素材)—— */ +.bg--shine { background-image: url(/assets/night/shines-sp.png); } +.bg--aurora, +.bg--aurora:before, +.bg--aurora:after { background-image: url(/assets/night/aurora-sp.png); } +.bg--rainbow:before { background-image: url(/assets/night/rainbow-sp.png); } +.bg--rainbow__cloud { background-image: url(/assets/night/rainbow-bottom-sp.png); } +.bg--rainbow__cloud:after { background-image: url(/assets/night/rainbow-back-sp.png); } + +/* —— 云(两张云官方不分档,同一张素材)—— */ +.bg--cloud__item.i1 { background-image: url(/assets/night/cloud-back-l.png); } +.bg--cloud__item.i2 { background-image: url(/assets/night/cloud-back-r.png); } +.bg--cloud__item.i3 { background-image: url(/assets/night/cloud-back-c.png); } +.bg--cloud__item.i4 { background-image: url(/assets/night/cloud-front-l.png); } +.bg--cloud__item.i5 { background-image: url(/assets/night/cloud-front-r.png); } +.bg--cloud__item.i6 { background-image: url(/assets/night/cloud-front-c.png); } + +/* —— 宽屏档:官方在这里把闪点、曙光、彩虹、彩虹两端那片云都换成 PC 素材 —— + 曙光的 :before/:after 官方在宽屏没有换图(还是 SP 那张),所以它们只在上面那一档里换。 */ +@media only screen and (min-width:769px) { + .bg--shine { background-image: url(/assets/night/shines-pc.png); } + .bg--aurora { background-image: url(/assets/night/aurora-pc.png); } + .bg--rainbow:before { background-image: url(/assets/night/rainbow-pc.png); } + .bg--rainbow__cloud { background-image: url(/assets/night/rainbow-bottom-pc.png); } +} + +/* —— 彩虹:弧贴近地平线处淡出 —— + 官方那张彩虹贴图的两个「脚」是被图片边界切平的。白天那里正好是渐变底部的白色地平光, + 切边看不出来;夜里背景压深之后,平直的切边就露出来了,所以让弧在贴近地平线处淡出。 + 两端那片云的底边同理,那一刀直接做在 rainbow-bottom-pc 素材里(见生成脚本)。 */ +.bg--rainbow:before { + -webkit-mask-image: linear-gradient(0deg, transparent 0, #000 13%); + mask-image: linear-gradient(0deg, transparent 0, #000 13%); +} + +/* —— 曙光:换素材之外还要叠加方式,这两行没法省 —— + 官方那几道竖向光幕在深色天空上会显出硬边方块,所以夜里用 screen 只加光、再模糊成柔光; + 贴图两端本来是硬接缝(官方自己在浅色天空上被背景亮度盖住了),夜版素材两端各淡了 4%。 */ +.bg--aurora, +.bg--aurora:before, +.bg--aurora:after { + mix-blend-mode: screen; + filter: blur(1.1vw) saturate(1.2) brightness(.95); +} +.bg--aurora { opacity: .32; } + +/* —— 月亮:素材保持官方,这里只加一圈月光(夜里唯一需要「加光」的地方)—— */ +.bg--moon { filter: drop-shadow(0 0 2.6vw rgba(168, 205, 255, .5)); } \ No newline at end of file diff --git a/public/theme.css b/public/theme.css new file mode 100644 index 0000000..50582a7 --- /dev/null +++ b/public/theme.css @@ -0,0 +1,1482 @@ +/* Algorithm DX · 前端皮肤层:maimai DX 国际版官网(PRiSM PLUS 时期) + * + * 参数来源:`public/vendor/maimai/style.css`(2025-08-04 归档快照里的 + * `https://maimai.sega.com/assets/css/style.css`)。每条关键数值后面标了官方选择器, + * 以后要调整就照着官方那行改。 + * + * 载入顺序:fonts.css → styles.css → 各页自己的 CSS(circle/dx-result/dan…)→ ui.css + * → theme.css → theme-dark.css。所以在同权重下本文件覆盖前面几个; + * 少数带 #id 或 .ac-page 前缀的旧规则用 `body.mm-theme` 提权重压过去。 + * + * 深色模式是叠加在后面的 theme-dark.css:里面的规则都带 [data-mm-theme="dark"] 前缀, + * 只覆盖材质与配色,改浅色那套不用动这个文件。 + * + * 背景整页复用官网那份动态背景,见 public/bg.html。 + * + * 与官方的八处有意偏差,见文件末尾「与官方的偏差」。 + */ + +/* ============ 1. 调色与尺度(数值全部取自官方) ============ */ + +:root { + color-scheme: light; + font-synthesis: none; + + --mm-purple: #ba5eea; /* .common--box:before、.header--nav__link.on::before */ + --mm-purple-deep: #a051dc; + --mm-purple-soft: #d3a1ff; + --mm-purple-tint: #f6ecff; /* 由主紫派生的浅底,官方只有纯色,用于表格/内嵌块 */ + --mm-pink: #ffcedf; /* .common--box:after */ + --mm-pink-soft: #ffd5cf; /* .bg--gradient */ + --mm-pink-hot: #ff699d; /* .header--nav__language__link:hover */ + --mm-magenta: #dc39b8; + --mm-red: #e72139; /* .common--btn */ + --mm-red-dark: #a51426; /* .common--btn 的 3D 底影 */ + --mm-navy: #0b3871; /* .footer--bottom color、导航字形 fill */ + --mm-ink: #000; /* body,html color */ + --mm-grey: #e5e5e5; /* .header--nav__item:not(:last-child)::after */ + --mm-green: #4be070; + --mm-cyan: #02c8d3; + --mm-blue: #3584fe; + --mm-orange: #ff972a; + --mm-yellow: #f8b73d; + + /* 背景渐变:官方 .bg--gradient 的 linear-gradient(0deg,#fff 14%,…) 原样搬过来 */ + --mm-gradient: linear-gradient(0deg, #fff 14%, #ffd5cf 24%, #ffd5cf 46%, #ffc5d5 56%, + #eaabff 67%, #72bcfe 85%, #65f2df 95%); + + /* 卡片:官方 .common--box 是三档 vw 阶梯,下面按档照搬(默认这档就是官方 @min-width:1100px + 的 900px / 30px / 14px 14px 0 / 600×8),另两档写在后面的媒体查询里 */ + --mm-card-radius: 30px; + /* 阴影拆成「偏移」与「颜色」两段:窄屏只改偏移(官方那三档),深色只改颜色 */ + --mm-card-shadow-x: 14px; + --mm-card-shadow-color: rgba(0, 0, 0, .2); + --mm-card-shadow: var(--mm-card-shadow-x) var(--mm-card-shadow-x) 0 0 var(--mm-card-shadow-color); + --mm-bar-w: min(66.6667%, 600px); + --mm-bar-h: 8px; + + /* 品牌图标:浅色用浅色版,深色版在 theme-dark.css 里换掉(首帧前由 上的主题决定, + 所以不会先亮后暗) */ + --mm-brand-mark: url(/algorithm-dx-icon-light.svg); + + /* 顶栏胶囊:官方 .header--nav @min-width:1400px → 1200×100 / 50px / 10px 10px 0 */ + --mm-nav-h: clamp(64px, 7.1429vw, 100px); + --mm-nav-radius: 50px; + --mm-nav-shadow: 10px 10px 0 rgba(0, 0, 0, .2); + /* 版心:官方顶栏 1200px、内容盒 900px。顶栏保持 1200,正文收窄一点(见「偏差」) */ + --mm-shell: min(1200px, 91.4286vw); + --mm-content: min(1080px, 91.4286vw); + /* 区块之间的呼吸:官方盒子内部 padding 是 50px,这里按同一量级放宽 */ + --mm-gap: clamp(26px, 2.8vw, 44px); + --mm-pad: clamp(20px, 2.8vw, 40px); + /* 缓动:UI 交互统一用一条强 ease-out(技能库给的曲线) */ + --mm-ease-out: cubic-bezier(.23, 1, .32, 1); + + /* 字体:官方 `body,html{font-family:"M PLUS 1p",sans-serif}` */ + --mm-sans: "M PLUS 1p Symbols", "M PLUS 1p", "Segoe UI", "Hiragino Kaku Gothic Pro", "Yu Gothic", + "Microsoft YaHei", "PingFang SC", "Noto Sans SC", sans-serif; + --mm-mono: "M PLUS 1p Symbols", "M PLUS 1p", ui-monospace, SFMono-Regular, Consolas, monospace; + + /* —— 仓库既有 token 的重映射:暗色 → 官方亮色(styles.css:131-154、ui.css:2-6) —— */ + --bg: #fff; + --bg-soft: #fffafd; + --panel: #fff; + --panel-2: #fbf3ff; + --border: var(--mm-grey); + --border-soft: #efe7f7; + --text: var(--mm-ink); + --text-dim: var(--mm-navy); + --muted: #6f6a7a; + --accent: var(--mm-purple); + --accent-soft: #f3e6ff; + --ac: #16883f; + --ac-soft: #e6fbe9; + --warn: #a35c00; + --warn-soft: #fff3e2; + --bad: #b3122a; + --bad-soft: #ffe9ec; + --radius: var(--mm-card-radius); + --radius-sm: 16px; + --shadow: 8px 8px 0 0 rgba(0, 0, 0, .2); + /* 小东西(行、内嵌块)用柔和投影,硬阴影只留给官方大盒子 */ + --mm-soft: 0 10px 26px -14px rgba(11, 56, 113, .35); + --surface-gradient: #fff; + --focus-ring: var(--mm-purple); + --sans: var(--mm-sans); + --mono: var(--mm-mono); +} + +/* —— 官方 .common--box 的另外两档(原样取自 style.css,数值一个没改)—— + :root 里那档是 ≥1100px;下面两档在窄屏把圆角、硬阴影与横条一起缩小。 + 少了它们,手机上会一直用 30px 圆角 + 14px 阴影,比官方重得多。 */ +@media only screen and (max-width: 768.98px) { + :root { + --mm-card-radius: 3.75vw; + --mm-card-shadow-x: 1.75vw; + --mm-bar-w: 75vw; + --mm-bar-h: 1.5vw; + } +} +@media only screen and (min-width: 769px) and (max-width: 1099.98px) { + :root { + --mm-card-radius: 2.7272727273vw; + --mm-card-shadow-x: 1.2727272727vw; + --mm-bar-w: 54.5454545455vw; + --mm-bar-h: .7272727273vw; + } +} + +/* ============ 2. 全局 ============ */ + +html, body { background: #fff; } + +body.mm-theme { + background: #fff; + color: var(--mm-ink); + font-family: var(--mm-sans); + /* 官方 body 是 line-height:1(lib.css 的重置),这里保留可读行高(见「偏差」) */ + overflow-x: hidden; +} + +body.mm-theme a { color: var(--mm-purple-deep); } +body.mm-theme a:hover { color: var(--mm-pink-hot); } + +body.mm-theme ::selection { background: var(--mm-purple-soft); color: var(--mm-navy); } + +/* 背景:官网那份动态背景整页复用(./bg.html), + 官方 CSS 里的全局选择器关在 iframe 里,不会碰到本站布局。 + body 自己只留一层兜底渐变,iframe 没加载出来时不至于白屏。 */ +body.mm-theme { + background-image: var(--mm-gradient); + background-attachment: fixed; + background-size: cover; +} + +.mm-bg-frame { + position: fixed; + inset: 0; + z-index: 0; + width: 100%; + height: 100%; + border: 0; + pointer-events: none; +} + +@media (prefers-reduced-motion: reduce) { + .mm-bg-frame { display: none; } +} + +/* 官方 .wrapper{position:relative;z-index:2}:内容压在背景层之上 */ +body.mm-theme .site-header, +body.mm-theme .app { position: relative; z-index: 2; } + +/* ============ 3. 顶栏(官方 .header--nav 桌面胶囊 / .header--btn 移动汉堡) ============ */ + +body.mm-theme .site-header { + display: flex; + align-items: center; + gap: 18px; + max-width: var(--mm-shell); + /* 顶栏往下挪一档:官方背景是内缩 3.14vw 的圆角渐变块,顶栏压到渐变开始线以下, + 上面这一段就露出动态背景 */ + margin: clamp(26px, 3.8vw, 58px) auto 0; + min-height: var(--mm-nav-h); + padding: 0 clamp(14px, 1.7143vw, 24px) 0 clamp(18px, 2.3571428571vw, 33px); + background: #fff; + border: 1px solid transparent; + border-radius: var(--mm-nav-radius); + box-shadow: var(--mm-nav-shadow); +} + +body.mm-theme .site-header .brand { gap: 12px; color: var(--mm-navy); text-decoration: none; } +body.mm-theme .site-header .brand .mark { + display: block; + width: 56px; + height: 34px; + /* 图标走 CSS 背景而不是 :主题在 上,首帧前就定好了, + 换成图片标签的话浅色主题会先闪一下深色版图标 */ + background: var(--mm-brand-mark) center / contain no-repeat; +} +body.mm-theme .site-header .brand-text h1 { + margin: 0; + font-size: 21px; + font-weight: 800; + letter-spacing: .2px; + color: var(--mm-navy); +} +body.mm-theme .site-header .brand-text p { margin: 3px 0 0; font-size: 12px; color: var(--muted); } + +/* 导航项:官方 .header--nav__link(字色 #0b3871、选中态紫色下横条 5px) */ +body.mm-theme .site-header .nav { + justify-self: auto; + margin-left: auto; + display: flex; + align-items: center; + gap: clamp(12px, 1.7143vw, 24px); + padding: 4px 0; + max-width: 100%; + /* 这里不能开 overflow:当前页的紫色下横条画在项框之外,会被裁掉 */ + overflow: visible; +} + +body.mm-theme .site-header .nav .btn, +body.mm-theme .site-header .nav .btn.btn-primary, +body.mm-theme .site-header .nav .btn.btn-ghost { + position: relative; + flex: 0 0 auto; + min-height: 0; + padding: 8px 2px; + border: 0; + border-radius: 0; + background: none; + box-shadow: none; + color: var(--mm-navy); + font-family: var(--mm-sans); + font-size: 17px; + font-weight: 800; + line-height: 1.55; + letter-spacing: .2px; + text-decoration: none; + transition: color .15s; +} +body.mm-theme .site-header .nav .btn:hover { color: var(--mm-purple); transform: none; box-shadow: none; } + +/* 分隔线:官方 .header--nav__item:not(:last-child)::after → 1px × 14px #e5e5e5 */ +body.mm-theme .site-header .nav .btn:not(:last-child)::after { + content: ""; + position: absolute; + top: 50%; + right: calc(-1 * clamp(6px, .8571428571vw, 12px)); + width: 1px; + height: 14px; + background: var(--mm-grey); + transform: translateY(-50%); +} + +/* 当前页 / 悬停:官方 .header--nav__link.on::before → 高 5px、圆角 100px 100px 0 0 */ +body.mm-theme .site-header .nav .btn[aria-current="page"]::before, +body.mm-theme .site-header .nav .btn:hover::before { + content: ""; + position: absolute; + left: 0; + right: 0; + bottom: calc((var(--mm-nav-h) - 42px) / -2); + height: 5px; + border-radius: 100px 100px 0 0; + background: var(--mm-purple); +} + +/* 汉堡按钮:官方 .header--btn(白底 + 1.25vw 红边 + 10px 硬阴影),开合时变 .header--nav__btn 的紫底叉 */ +.mm-menu-btn { + display: none; + position: relative; + flex: 0 0 auto; + width: 44px; + height: 44px; + padding: 0; + border: 3px solid var(--mm-red); + border-radius: 100px; + background: #fff; + box-shadow: 4px 4px 0 rgba(0, 0, 0, .2); + cursor: pointer; +} +.mm-menu-btn span { + display: block; + width: 20px; + height: 3px; + margin: 4px auto; + border-radius: 100px; + background: var(--mm-red); + transition: transform .2s, background .2s; +} +body.mm-menu-open .mm-menu-btn { border-color: var(--mm-purple); background: var(--mm-purple); } +body.mm-menu-open .mm-menu-btn span { background: #fff; } +body.mm-menu-open .mm-menu-btn span:first-child { transform: translateY(3.5px) rotate(45deg); } +body.mm-menu-open .mm-menu-btn span:last-child { transform: translateY(-3.5px) rotate(-45deg); } + +/* 窄一点的桌面端:七项中文标签会顶到胶囊右沿,先收字号与间距。 + 1001~1015px 是最挤的一档,收不够就会折成两层(开关掉到第二行),所以这里收得狠一点 */ +@media (max-width: 1200px) and (min-width: 1001px) { + body.mm-theme .site-header { gap: 12px; } + body.mm-theme .site-header .nav { gap: 12px; } + body.mm-theme .site-header .nav .btn, + body.mm-theme .site-header .nav .btn.btn-primary, + body.mm-theme .site-header .nav .btn.btn-ghost { font-size: 15px; } +} + +@media (max-width: 1000px) { + body.mm-theme .site-header { + justify-content: space-between; + padding: 0 clamp(14px, 3vw, 20px); + min-height: 68px; + gap: 10px; + } + .mm-menu-btn { display: block; } + /* 窄屏要同时站下品牌、深色开关和汉堡:品牌收一档,否则 header 会折行成两层 + (开关自己的尺寸在 §16 的媒体查询里,因为那节的规则排在更后面) */ + body.mm-theme .site-header { gap: 12px; } + body.mm-theme .site-header .brand .mark { width: 46px; height: 28px; } + body.mm-theme .site-header .brand-text h1 { font-size: 18px; } + /* 49px 的圆圈才够手指点:两个圆钮各 44px,中间留 12px,别再挤在一起 */ + .mm-menu-btn { width: 44px; height: 44px; } + /* 菜单展开时整屏铺的是 .nav(z-index:40),品牌、开关、汉堡要浮在它上面, + 否则开着菜单就够不到深浅色开关了 */ + body.mm-theme .site-header .brand, + body.mm-theme .site-header .mm-theme-btn, + body.mm-theme .site-header .mm-menu-btn { position: relative; z-index: 41; } + body.mm-theme .site-header .nav { + position: fixed; + inset: 0; + z-index: 40; + display: none; + flex-direction: column; + align-items: stretch; + gap: 0; + margin: 0; + padding: 84px 24px 40px; + overflow: auto; + background: #fff; + box-shadow: var(--mm-nav-shadow); + } + body.mm-menu-open .site-header .nav { display: flex; } + /* 顶栏平时和 .app 同层(都是 z-index:2),展开菜单时必须抬到 .app 之上, + 否则白色面板会被正文盖住 */ + body.mm-menu-open .site-header { z-index: 30; } + /* html 上也锁一份:只锁 body 拦不住 html 那只滚动条,深色面板右上角会露一截浅色滑块。 + 这两条都必须留在媒体查询里 —— 拉宽窗口后菜单按钮已经隐藏,锁还在就谁也解不开了 */ + body.mm-menu-open, + html.mm-menu-open { overflow: hidden; } + body.mm-theme .site-header .nav .btn, + body.mm-theme .site-header .nav .btn.btn-primary, + body.mm-theme .site-header .nav .btn.btn-ghost { + margin: 0; + padding: 16px 4px; + font-size: 20px; + text-align: center; + } + body.mm-theme .site-header .nav .btn::after { display: none; } + body.mm-theme .site-header .nav .btn:not(:last-child) { + border-bottom: 3px solid var(--mm-grey); + } + body.mm-theme .site-header .nav .btn[aria-current="page"]::before, + body.mm-theme .site-header .nav .btn:hover::before { display: none; } + body.mm-theme .site-header .nav .btn[aria-current="page"] { color: var(--mm-purple); } + .mm-menu-btn { position: relative; z-index: 50; } + body.mm-theme .site-header .global-sync-bar { + left: clamp(8px, 2vw, 16px); + right: clamp(8px, 2vw, 16px); + justify-content: space-between; + } + body.mm-theme .site-header .global-sync-bar .auto-sync-status { max-width: none; } +} + +/* 再窄一点(320~380px)品牌、开关、汉堡才挤得下同一行 */ +@media (max-width: 380px) { + body.mm-theme .site-header { padding: 0 10px; } + body.mm-theme .site-header .brand .mark { width: 40px; height: 24px; } + body.mm-theme .site-header .brand-text h1 { font-size: 17px; } +} +/* 到 344px 以下只留图标当品牌,把宽度全让给开关和汉堡 */ +@media (max-width: 344px) { + body.mm-theme .site-header .brand-text { display: none; } +} + +/* 后台同步状态:绑在胶囊右下角外侧(app.js/auto-sync.js 把它塞进
      ), + 不占导航的位置,也不改变胶囊的官方比例 */ +body.mm-theme .site-header .global-sync-bar { + position: absolute; + top: calc(100% + 8px); + right: clamp(6px, 1.4286vw, 16px); + z-index: 6; + display: flex; + align-items: center; + justify-content: flex-end; + gap: 8px; + margin: 0; + padding: 3px 5px 3px 14px; + max-width: calc(100% - 28px); + background: #fff; + border-radius: 100px; + box-shadow: 4px 4px 0 rgba(0, 0, 0, .15); +} +body.mm-theme .site-header .global-sync-bar:has(.auto-sync-status[hidden]):has(button[hidden]) { display: none; } +body.mm-theme .site-header .global-sync-bar .auto-sync-status { + position: static; + margin: 0; + max-width: min(420px, 46vw); + font-size: 12px; + line-height: 1.6; + color: var(--mm-navy); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} +body.mm-theme .site-header .global-sync-bar .btn { min-height: 30px; padding: 4px 14px; font-size: 12px; } + +/* 回到顶部:官方 .common--top(右下 120px,桌面 1.4286vw 边距) */ +.mm-top { + position: fixed; + right: 2.5vw; + bottom: 2.5vw; + z-index: 3; + display: block; + width: 15vw; + max-width: 120px; + transition: transform .3s, opacity .3s; +} +.mm-top.off { opacity: 0; transform: translateY(30%); pointer-events: none; } +.mm-top:hover { transform: translateY(-5%); } +.mm-top img { display: block; width: 100%; height: auto; } + +/* ============ 4. 版心与页面节奏 ============ */ + +body.mm-theme .app, +body.mm-theme.ac-page .app { + max-width: var(--mm-content); + margin: 0 auto; + padding: clamp(26px, 3vw, 46px) clamp(16px, 1.6vw, 22px) clamp(76px, 7vw, 120px); +} + +/* 页头:左边标题块、右边主按钮,一行放下;剩余的次级操作归到下面的工具行 */ +body.mm-theme .ac-page-heading { + display: flex; + align-items: flex-end; + justify-content: space-between; + gap: clamp(20px, 2.4vw, 36px); + flex-wrap: wrap; + margin-bottom: clamp(24px, 2.6vw, 38px); +} + +/* 背景太花,文字不能直接落在上面:页面顶部这几块统一加一层「玻璃板」。 + 不支持的浏览器回退成更实的白(见下面的 @supports)。 */ +body.mm-theme .ac-page-heading, +body.mm-theme .stats-toolbar, +body.mm-theme .circle-hero, +body.mm-theme .dan-hero, +body.mm-theme .help-heading, +body.mm-theme .help-toc, +body.mm-theme .pager { + position: relative; + background: rgba(255, 255, 255, .92); + border: 1px solid rgba(255, 255, 255, .75); + border-radius: 28px; + padding: clamp(18px, 2.2vw, 32px) clamp(20px, 2.6vw, 38px); + box-shadow: 0 20px 44px -30px rgba(11, 56, 113, .6); +} +@supports ((backdrop-filter: blur(2px)) or (-webkit-backdrop-filter: blur(2px))) { + body.mm-theme .ac-page-heading, + body.mm-theme .stats-toolbar, + body.mm-theme .circle-hero, + body.mm-theme .dan-hero, + body.mm-theme .help-heading, + body.mm-theme .help-toc, + body.mm-theme .pager { + background: rgba(255, 255, 255, .88); + -webkit-backdrop-filter: blur(12px) saturate(1.2); + backdrop-filter: blur(12px) saturate(1.2); + } +} +body.mm-theme .help-toc { padding: 12px 18px; border-radius: 100px; } +body.mm-theme .pager { border-radius: 100px; padding: 10px 18px; } +/* 玻璃板里的标题再加一圈细白描边,字更贴地 */ +body.mm-theme .ac-page-heading h2, +body.mm-theme .stats-toolbar h2, +body.mm-theme .circle-hero h2, +body.mm-theme .dan-hero h2, +body.mm-theme .help-heading h2 { + text-shadow: 0 1px 0 rgba(255, 255, 255, .9), 0 0 14px rgba(255, 255, 255, .8); +} + +/* 小字:在卡片或玻璃板里的留一圈很淡的白描边就够了;直接落在背景花纹上的那几处 + (.app 的直接子元素、题友动态的状态行、说明页正文、热力图加载提示)另给一块 + 不透明底板,否则花哨图案会从字底下透出来 */ +body.mm-theme .analytics-note, +body.mm-theme #message, +body.mm-theme .dan-msg, +body.mm-theme .circle-note, +body.mm-theme .pager-hint { + text-shadow: 0 1px 0 rgba(255, 255, 255, .95), 0 0 10px rgba(255, 255, 255, .9); +} +body.mm-theme .app > .analytics-note, +body.mm-theme .app > #message, +body.mm-theme .app > .dan-msg, +body.mm-theme .circle-main > #message, +body.mm-theme .heatmap-top > .analytics-note, +body.mm-theme .help-page > p { + background: #fff; + border-radius: 12px; + padding: 8px 14px; + box-shadow: 0 8px 20px -14px rgba(11, 56, 113, .55); + text-shadow: none; +} +body.mm-theme .stats-eyebrow { display: block; margin-bottom: 14px; } +body.mm-theme .ac-page-heading h2 { margin: 0 0 14px; } +body.mm-theme .ac-page-heading p { margin: 0; } +body.mm-theme .ac-page-heading .form-actions { flex-wrap: nowrap; } + +body.mm-theme .page-toolbar { + justify-content: flex-end; + gap: 12px; + margin-bottom: clamp(28px, 3.2vw, 48px); +} + +/* ============ 5. 官方大盒子 .common--box(白底 + 上紫下粉横条 + 14px 硬阴影) ============ */ + +body.mm-theme .filters, +body.mm-theme .admin, +body.mm-theme .foot, +body.mm-theme .section, +body.mm-theme .stat-row, +body.mm-theme .ac-platform-strip, +body.mm-theme .placeholder, +body.mm-theme .empty { + position: relative; + background: #fff; + border: 0; + color: var(--mm-ink); + border-radius: var(--mm-card-radius); + box-shadow: var(--mm-card-shadow); + overflow: visible; +} + +/* 横条只画在该有横条的盒子上:概览、平台、筛选、记录、页脚、账号管理 */ +body.mm-theme .filters::before, +body.mm-theme .admin::before, +body.mm-theme .foot::before, +body.mm-theme .section::before, +body.mm-theme .stat-row::before, +body.mm-theme .ac-platform-strip::before, +body.mm-theme .analytics::before, +body.mm-theme .today-panel::before { + content: ""; + position: absolute; + top: 0; + left: 50%; + width: var(--mm-bar-w); + height: var(--mm-bar-h); + border-radius: 0 0 var(--mm-bar-h) var(--mm-bar-h); + background: var(--mm-purple); + transform: translateX(-50%); +} +body.mm-theme .filters::after, +body.mm-theme .admin::after, +body.mm-theme .foot::after, +body.mm-theme .section::after, +body.mm-theme .stat-row::after, +body.mm-theme .ac-platform-strip::after, +body.mm-theme .analytics::after, +body.mm-theme .today-panel::after { + content: ""; + position: absolute; + bottom: 0; + left: 50%; + width: var(--mm-bar-w); + height: var(--mm-bar-h); + border-radius: var(--mm-bar-h) var(--mm-bar-h) 0 0; + background: var(--mm-pink); + transform: translateX(-50%); +} + +/* 大盒子统一的内边距与节奏 */ +body.mm-theme .section, +body.mm-theme .stat-row, +body.mm-theme .ac-platform-strip { + margin-bottom: var(--mm-gap); + padding: calc(var(--mm-bar-h) + var(--mm-pad)) var(--mm-pad); +} + +body.mm-theme.ac-page .filters { + display: grid; + gap: clamp(22px, 2.6vw, 34px) clamp(20px, 2.4vw, 32px); + padding: calc(var(--mm-bar-h) + var(--mm-pad)) var(--mm-pad); + background: #fff; + border: 0; + border-radius: var(--mm-card-radius); + box-shadow: var(--mm-card-shadow); + margin-bottom: var(--mm-gap); +} +/* 官方三栏只在大屏成立。这条 grid-template-columns 原来没带媒体查询,把 styles.css / + ui.css 里窄屏的 1~2 栏折叠全压掉了:手机上硬排三栏,每栏只剩 70~100px, + 平台 chips 竖着堆、状态三连被挤成两行、搜索框只剩一半宽 */ +@media (min-width: 1001px) { + body.mm-theme.ac-page .filters { grid-template-columns: 1fr 1.4fr 1fr; } +} +@media (max-width: 1000px) { + body.mm-theme.ac-page .filters { gap: 16px; } +} + +/* ============ 6. 按钮(官方 .common--btn:红底 + 同色系 3D 底影,按下时沉到阴影上) ============ */ + +body.mm-theme .btn { + border: 0; + border-radius: 100px; + font-family: var(--mm-sans); + font-size: 14px; + font-weight: 800; + letter-spacing: .2px; + min-height: 40px; + padding: 8px 22px; + box-shadow: 0 5px 0 0 rgba(11, 56, 113, .18); + transition: transform .15s, box-shadow .15s, background .15s, color .15s; +} + +body.mm-theme .btn-primary { + background: var(--mm-red); + color: #fff; + box-shadow: 0 5px 0 0 var(--mm-red-dark); +} +body.mm-theme .btn-primary:hover { background: var(--mm-red); color: #fff; transform: translateY(5px); box-shadow: none; } + +body.mm-theme .btn-ghost { + background: #fff; + color: var(--mm-navy); + border: 1px solid var(--mm-grey); +} +body.mm-theme .btn-ghost:hover { + background: #fff; + color: var(--mm-purple); + border-color: var(--mm-purple-soft); + transform: translateY(5px); + box-shadow: none; +} + +body.mm-theme .btn:active { transform: translateY(5px); box-shadow: none; } +body.mm-theme .btn:focus-visible { outline: 3px solid var(--mm-purple-soft); outline-offset: 2px; } +body.mm-theme .btn[disabled] { opacity: .45; box-shadow: none; transform: none; } + +body.mm-theme .btn-small { min-height: 32px; padding: 6px 16px; font-size: 12.5px; } +body.mm-theme .btn-mini { min-height: 28px; padding: 4px 12px; font-size: 12px; } + +/* ============ 7. 表单 ============ */ + +body.mm-theme .input, +body.mm-theme .select { + font-family: var(--mm-sans); + font-size: 14px; + color: var(--mm-ink); + background: #fff; + border: 1px solid var(--mm-grey); + border-radius: 100px; + min-height: 44px; + padding: 8px 18px; + box-shadow: none; +} +body.mm-theme .input::placeholder { color: #a8a2b4; } +body.mm-theme .input:focus, +body.mm-theme .select:focus { + outline: none; + border-color: var(--mm-purple); + box-shadow: 0 0 0 3px rgba(186, 94, 234, .2); +} +body.mm-theme .select { cursor: pointer; } + +body.mm-theme .field-label, +body.mm-theme .filter-label { + color: var(--mm-navy); + font-weight: 800; + font-size: 13px; + letter-spacing: .2px; +} +body.mm-theme .field-hint, +body.mm-theme .section-hint { color: var(--muted); } + +/* ============ 8. 标签、胶囊、分页 ============ */ + +body.mm-theme .chip { + font-family: var(--mm-sans); + font-size: 13px; + font-weight: 700; + color: var(--mm-navy); + background: #fff; + border: 1px solid var(--mm-grey); + border-radius: 100px; + padding: 6px 14px; +} +body.mm-theme .chip:hover { border-color: var(--mm-purple-soft); color: var(--mm-purple); } +body.mm-theme .chip.is-on { + background: var(--mm-purple); + border-color: var(--mm-purple); + color: #fff; + box-shadow: 0 3px 0 0 var(--mm-purple-deep); +} +body.mm-theme .chip.is-on .chip-n { color: rgba(255, 255, 255, .85); } +body.mm-theme .chip .chip-n { color: var(--muted); font-family: var(--mm-mono); } + +body.mm-theme .tabs { + background: var(--mm-purple-tint); + border: 0; + border-radius: 100px; + padding: 3px; +} +body.mm-theme .tab { + font-family: var(--mm-sans); + font-size: 13px; + font-weight: 700; + color: var(--mm-navy); + background: transparent; + border-radius: 100px; + padding: 7px 18px; +} +body.mm-theme .tab:hover { color: var(--mm-purple); } +body.mm-theme .tab.is-active { + background: #fff; + color: var(--mm-purple); + box-shadow: 0 2px 0 rgba(11, 56, 113, .12); +} + +body.mm-theme .badge { + font-family: var(--mm-sans); + font-size: 11px; + font-weight: 700; + padding: 3px 10px; + border: 0; + border-radius: 100px; + background: #f2eef7; + color: #5f5870; +} +body.mm-theme .badge.ac { background: #e6fbe9; color: #137a3a; } +body.mm-theme .badge.warn { background: #fff2e0; color: #a35c00; } +body.mm-theme .badge.bad { background: #ffe9ec; color: #b3122a; } +body.mm-theme .badge.diff { background: #e8f0ff; color: #1c53b8; } +body.mm-theme .badge.contest { background: #f3e6ff; color: #7d2fb0; } +body.mm-theme .badge.platform { background: #e9f6ff; color: #135a8f; text-transform: none; } +body.mm-theme .badge.score { background: #fff2e0; color: #a35c00; } + +body.mm-theme .db-chip { + font-family: var(--mm-mono); + font-size: 12px; + color: var(--mm-navy); + background: #fff; + border: 1px solid var(--mm-grey); + border-radius: 100px; + padding: 6px 14px; +} +body.mm-theme.ac-page .db-chip { color: var(--mm-navy); border-color: var(--mm-grey); max-width: 260px; } + +body.mm-theme .pager { padding: 12px 0 4px; } +body.mm-theme .pager-hint { color: var(--muted); font-family: var(--mm-mono); } + +/* ============ 9. 概览数字(盒子是官方的,格子是平的) ============ */ + +body.mm-theme .stat-row, +body.mm-theme.ac-page #statRow { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(158px, 1fr)); + gap: 6px; + align-items: stretch; +} +/* 六个指标就该是一行:auto-fit 在 1000px 出头会掉成 5+1,很难看 */ +@media (min-width: 1080px) { + body.mm-theme .stat-row, + body.mm-theme.ac-page #statRow { grid-template-columns: repeat(6, minmax(0, 1fr)); } +} +@media (max-width: 1079px) and (min-width: 760px) { + body.mm-theme .stat-row, + body.mm-theme.ac-page #statRow { grid-template-columns: repeat(3, minmax(0, 1fr)); } +} +@media (max-width: 759px) { + body.mm-theme .stat-row, + body.mm-theme.ac-page #statRow { grid-template-columns: repeat(2, minmax(0, 1fr)); } +} + +body.mm-theme .stat, +body.mm-theme.ac-page #statRow .stat { + background: transparent; + border: 0; + border-radius: 20px; + padding: 14px 18px; + box-shadow: none; + transition: background .2s var(--mm-ease-out); +} +body.mm-theme .stat:hover, +body.mm-theme.ac-page #statRow .stat:hover { background: var(--mm-purple-tint); } + +body.mm-theme .stat-label { color: var(--mm-navy); font-weight: 700; font-size: 12.5px; } +body.mm-theme .stat-value { + font-family: var(--mm-sans); + font-size: clamp(28px, 2.4vw, 34px); + font-weight: 800; + color: var(--mm-navy); + letter-spacing: -.5px; +} +body.mm-theme .stat-sub { + color: var(--muted); + font-size: 11.5px; + line-height: 1.5; + text-wrap: pretty; +} +body.mm-theme .stat.is-ac .stat-value { color: #16883f; } +body.mm-theme .stat.is-unac .stat-value { color: #c47f00; } +body.mm-theme .stat.is-sub .stat-value { color: var(--mm-purple-deep); } + +body.mm-theme.ac-page #statRow .stat.is-ac { border: 0; background: transparent; } +body.mm-theme.ac-page #statRow .stat:last-child .stat-value { font-size: 20px; line-height: 1.8; } + +/* 平台条:同样是「一个白盒子 + 平铺的格子」 */ +body.mm-theme .ac-platform-strip { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(232px, 1fr)); + gap: 6px; +} +body.mm-theme .ac-platform-strip .stat { + background: transparent; + border: 0; + border-radius: 18px; + box-shadow: none; + padding: 12px 16px; + transition: background .2s var(--mm-ease-out); +} +body.mm-theme .ac-platform-strip .stat:hover { background: var(--mm-purple-tint); } +body.mm-theme .ac-platform-strip .stat-value { font: 800 18px var(--mm-sans); color: var(--mm-navy); } +body.mm-theme .ac-platform-strip .stat-sub { color: var(--muted); } + +/* ============ 10. 记录列表(区块是官方盒子,每条记录是盒子里的一行) ============ */ + +/* 区块标题占一行,下面用发丝线接记录行 —— 与官网 top--update 的列表同一套做法 */ +body.mm-theme.ac-page .section-head { + margin-bottom: 6px; + padding-bottom: 20px; + border-bottom: 1px solid var(--border-soft); +} + +body.mm-theme.ac-page .cards { display: block; } + +body.mm-theme.ac-page .card { + display: grid; + grid-template-columns: minmax(0, 1.6fr) minmax(150px, .62fr) auto; + grid-template-areas: + "top stats foot" + "context stats foot"; + align-items: center; + column-gap: clamp(14px, 1.6vw, 28px); + row-gap: 2px; + padding: 12px 4px; + background: transparent; + border: 0; + border-bottom: 1px solid var(--border-soft); + border-radius: 0; + box-shadow: none; + transform: none; + transition: background .18s var(--mm-ease-out); +} +body.mm-theme.ac-page .card:last-child { border-bottom: 0; } +body.mm-theme.ac-page .card.is-ac, +body.mm-theme.ac-page .card.is-unac { background: transparent; border-left: 0; } +@media (hover: hover) and (pointer: fine) { + body.mm-theme.ac-page .card:hover { background: #fdf8ff; } +} + +body.mm-theme.ac-page .card .card-top { grid-area: top; min-height: 0; justify-content: flex-start; align-items: center; gap: 10px; } +body.mm-theme.ac-page .card .ac-card-context { grid-area: context; gap: 3px; } +body.mm-theme.ac-page .card .card-stats { grid-area: stats; } +body.mm-theme.ac-page .card .card-foot { grid-area: foot; } +/* 题号/平台/难度挤在一行里就别铺开了:小一号、间距收掉,一行读完 */ +body.mm-theme.ac-page .card-meta > .badge { padding: 2px 8px; font-size: 11px; } +body.mm-theme.ac-page .ac-card-identity { padding-top: 0; font-size: 11.5px; } + +body.mm-theme.ac-page .card-title { + font-size: 16px; + font-weight: 800; + line-height: 1.35; + color: var(--mm-navy); +} +body.mm-theme .card-title a:hover { color: var(--mm-purple); border-bottom-color: var(--mm-purple-soft); } +body.mm-theme .card-meta { color: var(--muted); } + +body.mm-theme.ac-page .card-stats { + background: transparent; + border: 0; + border-radius: 0; + padding: 0; + gap: 2px 14px; +} +/* 「通过时间 2026-10-10 14:50」并成一行,右侧那一栏才不空得发慌 */ +body.mm-theme.ac-page .card-stats > span { display: flex; align-items: baseline; gap: 8px; } +body.mm-theme.ac-page .card-stats .k { color: var(--muted); font-size: 11.5px; } +body.mm-theme.ac-page .card-stats .v { color: var(--mm-navy); font-weight: 700; font-size: 14px; } +body.mm-theme.ac-page .card-stats > span:first-child { border-bottom: 0; padding-bottom: 0; } + +body.mm-theme.ac-page .card-foot { + border-top: 0; + padding-top: 0; + flex-direction: column; + align-items: flex-end; + gap: 5px; + flex-wrap: nowrap; + color: var(--muted); + text-align: right; +} +body.mm-theme .ac-card-tags .badge { background: #f2eef7; border: 0; color: #5f5870; } + +/* 窄屏回到「一张一张卡片」的堆叠形态(与汉堡菜单同一个断点) */ +@media (max-width: 1000px) { + /* 工具行排成两列网格:旧样式在窄屏会一行一个按钮、还全部右对齐,边缘很毛 */ + body.mm-theme .page-toolbar { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 10px; + } + body.mm-theme .page-toolbar .db-chip { grid-column: 1 / -1; justify-self: start; max-width: 100%; } + body.mm-theme .page-toolbar .btn { + width: 100%; + justify-content: center; + padding: 8px 12px; + font-size: 13px; + white-space: nowrap; + } + body.mm-theme .ac-page-heading .form-actions, + body.mm-theme .ac-page-heading .btn-primary { width: 100%; } + + body.mm-theme.ac-page .section-head { padding-bottom: 14px; } + body.mm-theme.ac-page .cards { display: flex; flex-direction: column; gap: 16px; } + body.mm-theme.ac-page .card { + display: flex; + flex-direction: column; + align-items: stretch; + gap: 14px; + padding: 18px; + border: 1px solid var(--border-soft); + border-radius: 20px; + box-shadow: var(--mm-soft); + } + body.mm-theme.ac-page .card:last-child { border-bottom: 1px solid var(--border-soft); } + body.mm-theme.ac-page .card-foot { justify-content: flex-start; text-align: left; align-items: flex-start; } + + /* 状态三连(全部/已AC/未AC)在窄屏只有 100px 出头,会被挤成两行、 + 右半边还被圆形的选中块盖住:让每组平分整行、文字不许折行,选中块改成圆角矩形 */ + body.mm-theme.ac-page .filters .tabs { display: flex; width: 100%; } + body.mm-theme.ac-page .filters .tab { + flex: 1 1 0; + min-width: 0; + padding: 9px 6px; + border-radius: 12px; + font-size: 13px; + white-space: nowrap; + } + + /* 点得着的目标:手机上一行的操作按钮别只有 23px 高 */ + body.mm-theme .collapse-toggle, + body.mm-theme .expand-btn, + body.mm-theme .dx-more a, + body.mm-theme button.circle-name { min-height: 40px; } + + /* 全年热力图一行放不下(53 列),给它一句滑动提示,别让人以为只有半年 */ + body.mm-theme #heatmapHost::before { + content: '热力图一行放不下一年,左右滑动查看 →'; + display: block; + margin-bottom: 8px; + font-size: 11.5px; + color: var(--muted); + } +} + +/* 手机上按钮不排到半格,直接一列到底 */ +@media (max-width: 560px) { + body.mm-theme .page-toolbar { grid-template-columns: minmax(0, 1fr); } +} + +/* 卡片页脚的小操作:旧样式是深蓝实心小块,这里换成官方语言的白底胶囊 */ +body.mm-theme.ac-page .expand-btn { + font-family: var(--mm-sans); + font-size: 12px; + font-weight: 700; + color: var(--mm-navy); + background: #fff; + border: 1px solid var(--mm-grey); + border-radius: 100px; + padding: 6px 14px; + gap: 6px; + box-shadow: 0 3px 0 0 rgba(11, 56, 113, .15); + transition: transform .15s, box-shadow .15s, color .15s, border-color .15s; +} +body.mm-theme.ac-page .expand-btn:hover { + color: var(--mm-purple); + border-color: var(--mm-purple-soft); + text-decoration: none; + transform: translateY(3px); + box-shadow: none; +} + +/* ============ 11. 区块标题与提示 ============ */ + +body.mm-theme .section-head h2, +body.mm-theme.ac-page .section-head h2 { + font-size: 20px; + font-weight: 800; + color: var(--mm-navy); +} +body.mm-theme .section-count { + display: inline-block; + margin-left: 8px; + padding: 2px 12px; + background: var(--mm-purple-tint); + border-radius: 100px; + color: var(--mm-purple-deep); + font-family: var(--mm-sans); + font-size: 12px; + font-weight: 800; +} +body.mm-theme .collapse-toggle { color: var(--mm-navy); font-weight: 800; background: none; border: 0; } +body.mm-theme .collapse-toggle:hover { color: var(--mm-purple); } +body.mm-theme .caret { color: var(--mm-purple); } +body.mm-theme .timeline { border-top: 1px dashed var(--border-soft); } +body.mm-theme .tl-time { color: var(--muted); } +body.mm-theme .tl-row { border-color: var(--border-soft); } + +body.mm-theme .ac-page-heading h2, +body.mm-theme .stats-toolbar h2 { font-size: 30px; font-weight: 800; color: var(--mm-navy); } +body.mm-theme .ac-page-heading p, +body.mm-theme .stats-toolbar p { color: #4b4b4b; } +body.mm-theme .stats-eyebrow { + color: var(--mm-purple); + letter-spacing: 3px; + font-weight: 800; +} + +/* 通知:不抢戏 —— 一点暖底 + 一条状态色 + 发丝分隔行 */ +body.mm-theme .notice { + background: #fffcf7; + border: 0; + border-left: 5px solid var(--mm-orange); + border-radius: 14px; + padding: 14px 18px; + box-shadow: none; + color: var(--mm-ink); + font-size: 12.5px; + line-height: 1.7; +} +body.mm-theme .notice + .notice { margin-top: 10px; } +body.mm-theme .notice > div + div { + margin-top: 8px; + padding-top: 8px; + border-top: 1px solid var(--border-soft); +} +body.mm-theme .notice strong { color: var(--mm-navy); } +body.mm-theme .notice-ac { background: #f7fdf9; border-left-color: var(--mm-green); } +body.mm-theme .notice-ac strong { color: var(--mm-navy); } +body.mm-theme .notice-ac.is-bad, +body.mm-theme .notice-danger { background: #fff8f9; border-left-color: var(--mm-red); } +body.mm-theme .notice-ac.is-bad strong, +body.mm-theme .notice-danger strong { color: var(--mm-red-dark); } +body.mm-theme .notice-warn { background: #fffcf7; border-left-color: var(--mm-orange); } +body.mm-theme .notice-warn strong { color: var(--mm-navy); } +body.mm-theme .notice-warn span { color: #6f6a7a; } +body.mm-theme .notice-neutral { background: #fcfcfc; border-left-color: var(--mm-grey); color: var(--muted); } + +body.mm-theme .placeholder, +body.mm-theme .empty { + border-radius: var(--mm-card-radius); + box-shadow: var(--mm-card-shadow); + padding: 56px 20px; + color: var(--muted); +} +body.mm-theme .empty h3 { color: var(--mm-navy); font-weight: 800; } +body.mm-theme .spinner { border-color: var(--mm-purple-tint); border-top-color: var(--mm-purple); } + +/* ============ 12. 页脚(官方 .footer--bottom:白底、#0b3871 字色、上沿软阴影) ============ */ + +body.mm-theme .foot { + margin-top: clamp(30px, 3.4vw, 52px); + padding: calc(var(--mm-bar-h) + clamp(20px, 2.4vw, 34px)) var(--mm-pad); + color: var(--mm-navy); + font-size: 12.5px; + box-shadow: var(--mm-card-shadow); +} +body.mm-theme .foot-note { color: var(--muted); } +body.mm-theme .foot a { color: var(--mm-purple-deep); } + +/* ============ 13. 暗色残留(旧规则里写死的浅色字 / 深色底,逐条扳回亮色) ============ */ + +body.mm-theme .ac-card-identity, +body.mm-theme.ac-page .ac-card-identity { color: var(--muted); } +body.mm-theme .form-status { color: var(--mm-navy); } +body.mm-theme .form-status.is-bad { color: var(--bad); } +body.mm-theme .form-status.is-ok { color: var(--ac); } +body.mm-theme .form-status.is-warn { color: var(--warn); } + +body.mm-theme .stat-row { gap: 18px; } + +body.mm-theme .admin-head h2 { color: var(--mm-navy); font-weight: 800; font-size: 16px; } +body.mm-theme .admin-block { background: var(--mm-purple-tint); border: 1px solid var(--border-soft); } +body.mm-theme .admin-block h3 { color: var(--mm-navy); font-weight: 800; } + +body.mm-theme .account-owner { background: #fff; border: 1px solid var(--border-soft); } +body.mm-theme .account-owner > summary:hover { background: var(--mm-purple-tint); } +body.mm-theme .account-owner-avatar { color: var(--mm-purple-deep); background: var(--mm-purple-tint); } +body.mm-theme .account-owner-content { background: #fffafd; } +body.mm-theme .account-row { border-color: var(--border-soft); } + +body.mm-theme .help-inline { background: #fff; border-color: var(--border-soft); } +body.mm-theme .help-inline summary { color: var(--mm-navy); } + +@media (max-width: 600px) { + body.mm-theme .ac-page-heading h2, + body.mm-theme .stats-toolbar h2 { font-size: 26px; } +} + +/* ============ 14. 表格与弹窗 ============ */ + +body.mm-theme .rt-table { + background: #fff; + border-radius: 18px; + overflow: hidden; +} +body.mm-theme .rt-table th { color: var(--mm-navy); font-weight: 800; background: var(--mm-purple-tint); } +body.mm-theme .rt-table td, +body.mm-theme .rt-table th { border-color: var(--border-soft); } +body.mm-theme .rt-table tbody th { color: var(--mm-navy); font-weight: 700; } + +body.mm-theme .modal-card, +body.mm-theme dialog { + background: #fff; + color: var(--mm-ink); + border: 0; + border-radius: var(--mm-card-radius); + box-shadow: var(--mm-card-shadow); +} +body.mm-theme dialog::backdrop { background: rgba(11, 56, 113, .35); } +body.mm-theme .modal-card h2, +body.mm-theme dialog h2, +body.mm-theme dialog h3 { color: var(--mm-navy); } + +/* ============ 15. 其余页面自带组件(旧文件里写死的深色,逐条扳回官方亮色) ============ + * + * styles.css / ui.css / circle.css 里有一批硬编码的深色面(分析卡、今日面板、热力图、 + * DX 计时器、题友圈人物、帮助目录…),token 重映射管不到,只能按选择器覆盖。 + * 另:这些页面原本没有「盒子」,正文直接落在动态背景上,所以这里一并给它们官方白盒。 + */ + +/* —— 统计页:分析卡 / 今日面板 / 热力图 —— */ +body.mm-theme .analytics { + position: relative; + background: #fff; + border: 0; + border-radius: var(--mm-card-radius); + padding: calc(var(--mm-bar-h) + var(--mm-pad)) var(--mm-pad); +} +body.mm-theme .analytics-chart { + background: #fff; + border: 1px solid var(--border-soft); + border-radius: 22px; + padding: 22px 24px; + box-shadow: var(--mm-soft); +} +/* 热力图那块是直接挂在 #heatmapHost 下的一级面板(不像别的图表嵌在 .analytics 里), + 所以它也得算一个官方大盒子:同样的圆角、硬阴影和上下横条 */ +body.mm-theme .heatmap-top > .analytics-chart { + position: relative; + border: 0; + border-radius: var(--mm-card-radius); + padding: calc(var(--mm-bar-h) + var(--mm-pad)) var(--mm-pad); + box-shadow: var(--mm-card-shadow); +} +body.mm-theme .heatmap-top > .analytics-chart::before, +body.mm-theme .heatmap-top > .analytics-chart::after { + content: ""; + position: absolute; + left: 50%; + width: var(--mm-bar-w); + height: var(--mm-bar-h); + background: var(--mm-purple); + transform: translateX(-50%); +} +body.mm-theme .heatmap-top > .analytics-chart::before { + top: 0; + border-radius: 0 0 var(--mm-bar-h) var(--mm-bar-h); +} +body.mm-theme .heatmap-top > .analytics-chart::after { + bottom: 0; + border-radius: var(--mm-bar-h) var(--mm-bar-h) 0 0; + background: var(--mm-pink); +} +body.mm-theme .analytics-chart h3 { color: var(--mm-navy); font-weight: 800; } +body.mm-theme .analytics-chart h4 { color: var(--muted); } +body.mm-theme .analytics-head { gap: 12px; } +body.mm-theme .analytics-controls { + background: var(--mm-purple-tint); + border: 0; + border-radius: 100px; + padding: 8px 10px; + gap: 8px; +} +body.mm-theme .analytics-controls .input { color: var(--mm-ink); color-scheme: light; } +body.mm-theme .chart-values { color: var(--muted); } +body.mm-theme .chart-values dl { background: var(--mm-purple-tint); border-radius: 14px; } +body.mm-theme .chart-values dd { color: var(--mm-navy); } +body.mm-theme .analytics-metrics > div { + background: #fff; + border: 1px solid var(--border-soft); + border-radius: 18px; + border-left: 4px solid var(--mm-green); + padding: 16px 18px; + box-shadow: var(--mm-soft); +} +body.mm-theme .analytics-metrics strong { color: var(--mm-navy); } +body.mm-theme .analytics-metrics span { color: var(--muted); } +body.mm-theme .analytics-track { background: var(--mm-purple-tint); height: 10px; } +body.mm-theme .analytics-track i { background: linear-gradient(90deg, #38c46b, var(--mm-green)); } +body.mm-theme .analytics-bar-row b { color: var(--mm-navy); } + +body.mm-theme .today-panel { + position: relative; + background: #fff; + border: 0; + border-radius: var(--mm-card-radius); + padding: calc(var(--mm-bar-h) + var(--mm-pad)) var(--mm-pad); + box-shadow: var(--mm-card-shadow); +} +body.mm-theme .today-metrics > div, +body.mm-theme .today-rhythm { + background: var(--mm-purple-tint); + border: 0; + border-radius: 18px; +} +body.mm-theme .today-metrics strong { color: var(--mm-navy); } +body.mm-theme .today-metrics span, +body.mm-theme .today-metrics small, +body.mm-theme .today-performance span { color: var(--muted); } +body.mm-theme .today-card { background: #fff; border-color: var(--border-soft); box-shadow: var(--mm-soft); } + +/* 热力图格子:深色系 → 官方绿渐变 */ +body.mm-theme .heat-cell { border: 1px solid rgba(11, 56, 113, .08); border-radius: 4px; } +body.mm-theme .level-0 { background: #eeeaf5; } +body.mm-theme .level-1 { background: #d8f6e2; } +body.mm-theme .level-2 { background: #a9eebf; } +body.mm-theme .level-3 { background: #74e49b; } +body.mm-theme .level-4 { background: var(--mm-green); } +body.mm-theme .heat-weekdays, +body.mm-theme .heat-months, +body.mm-theme .heat-legend { color: var(--muted); } +body.mm-theme .heat-cell:hover, +body.mm-theme .heat-cell:focus-visible { outline: 2px solid var(--mm-purple); outline-offset: 1px; } + +/* —— DX Rating 页 —— */ +/* 旧样式把 .dx-eyebrow 固定成浅蓝 #8e9fff,白底上只有 2.46 的对比度, + 和另外两个 eyebrow 一样收进官方紫里 */ +body.mm-theme .dx-eyebrow { color: var(--mm-purple); letter-spacing: 2px; font-weight: 800; } +body.mm-theme .dx-pages { + background: #fff; + border: 1px solid var(--border-soft); + border-radius: 100px; + padding: 8px 10px; + box-shadow: var(--mm-soft); +} +body.mm-theme .dx-timer { + background: #fff; + border: 0; + border-radius: var(--mm-card-radius); + box-shadow: var(--mm-card-shadow); + padding: var(--mm-pad); +} +body.mm-theme .dx-timer h2 { color: var(--mm-navy); } +body.mm-theme .dx-more a { background: #fff; color: var(--mm-navy); border: 1px solid var(--mm-grey); } +body.mm-theme .dx-ranked { color: #a35c00; } +body.mm-theme .dx-records-filters { background: #fff; border: 0; box-shadow: var(--mm-soft); } + +/* —— 题友圈 —— */ +body.mm-theme .circle-eyebrow { color: var(--mm-purple); letter-spacing: 2px; font-weight: 800; } +body.mm-theme .circle-hero h2 { color: var(--mm-navy); } +body.mm-theme .circle-hero p { color: #4b4b4b; } +body.mm-theme .circle-sidebar, +body.mm-theme .circle-main { color: var(--mm-ink); } +body.mm-theme .circle-sidebar { + background: #fff; + border: 0; + border-radius: var(--mm-card-radius); + box-shadow: var(--mm-card-shadow); + padding: var(--mm-pad); +} +body.mm-theme .circle-person { color: var(--mm-navy); border-radius: 18px; } +/* 窄屏原样式把「我的关注」排成一条横向滚动条:卡片里看不出还能滑, + 第二个人就停在卡片边缘,名字后面的通过数被切一半。干脆换行排成格子,全都看得见 */ +@media (max-width: 850px) { + body.mm-theme #people { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(146px, 1fr)); + gap: 8px; + overflow: visible; + } + body.mm-theme #people .circle-person { min-width: 0; width: 100%; } +} +body.mm-theme .circle-person:hover { background: var(--mm-purple-tint); } +body.mm-theme .circle-person[aria-pressed="true"] { + background: var(--mm-purple-tint); + border-color: var(--mm-purple-soft); + color: var(--mm-purple-deep); +} +body.mm-theme .circle-avatar { + background: hsl(var(--hue, 160) 62% 92%); + color: hsl(var(--hue, 160) 48% 30%); +} +body.mm-theme .circle-name:hover { color: var(--mm-purple); } +body.mm-theme .circle-result { background: #f2eef7; color: #5f5870; } +body.mm-theme .circle-result.ac { background: #e6fbe9; color: #137a3a; } +body.mm-theme .circle-coverage-status.needs-attention { color: #a35c00; background: #fff3e2; } +body.mm-theme .circle-card { + background: #fff; + border: 1px solid var(--border-soft); + border-radius: 22px; + box-shadow: var(--mm-soft); +} +body.mm-theme .circle-filters { + background: #fff; + border: 0; + border-radius: var(--mm-card-radius); + box-shadow: var(--mm-card-shadow); + padding: var(--mm-pad); +} +body.mm-theme .circle-card-head, +body.mm-theme .circle-card-foot { border-color: var(--border-soft); } + +/* —— 使用指南:正文直接落在背景上,给每节一个白盒 —— */ +body.mm-theme .help-page > section { + background: #fff; + border-radius: 24px; + padding: clamp(20px, 2.2vw, 30px) clamp(20px, 2.4vw, 34px); + margin-bottom: 18px; + box-shadow: var(--mm-soft); +} +body.mm-theme .help-page > section h2 { color: var(--mm-navy); } +body.mm-theme .help-page > section > p, +body.mm-theme .help-page > section li { color: #3d3a46; } +body.mm-theme .help-page .help-toc a { + background: #fff; + border: 1px solid var(--mm-grey); + border-radius: 100px; + color: var(--mm-navy); +} +body.mm-theme .help-page .help-toc a:hover { background: var(--mm-purple-tint); color: var(--mm-purple-deep); border-color: var(--mm-purple-soft); } +body.mm-theme kbd { + background: var(--mm-purple-tint); + border: 1px solid var(--mm-purple-soft); + border-radius: 6px; + color: var(--mm-navy); +} + +/* 扫描剩下的浅色字(深色面已经处理完,这几处是字色本身写死的浅色) */ +body.mm-theme .today-total { color: #16883f; } +body.mm-theme .dx-rank { color: #a35c00; } +/* 段位卡本身是深色卡面(深蓝底 + 位阶色描边),和主题无关;浅色主题把页面刷白以后, + 卡面上的字必须跟着卡面走 —— 否则黑字压深蓝,题名/完成度/难度全看不见 */ +body.mm-theme .dx-card { color: #eef1fb; } +body.mm-theme .dx-card .dx-library-card-foot, +body.mm-theme .dx-card .dx-library-metrics span, +body.mm-theme .dx-card .dx-card-id, +body.mm-theme .dx-card .dx-card-meta, +body.mm-theme .dx-card .dx-card-name, +body.mm-theme .dx-card .dx-card-time, +body.mm-theme .dx-card .dx-pending-id { color: #bcc4dc; } +body.mm-theme .dx-card .dx-rank { color: var(--rank, #ffd166); } +body.mm-theme .dx-card .dx-library-title a { color: #f4f6ff; } +body.mm-theme .dx-card .dx-library-title a:hover { color: #d3a1ff; } +body.mm-theme .help-page .help-heading p, +body.mm-theme .help-page > section p, +body.mm-theme .help-page > section li, +body.mm-theme .help-page details p { color: #3d3a46; } +body.mm-theme .help-page #message { color: var(--muted); } +/* 还有几处页内标题/说明直接落在背景上(不在上面的页头列表里) */ +body.mm-theme .dx-page-intro, +body.mm-theme .circle-feed-head { + background: rgba(255, 255, 255, .92); + border: 1px solid rgba(255, 255, 255, .75); + border-radius: 24px; + padding: clamp(16px, 1.8vw, 24px) clamp(20px, 2.4vw, 32px); + box-shadow: 0 18px 40px -30px rgba(11, 56, 113, .55); +} +@supports ((backdrop-filter: blur(2px)) or (-webkit-backdrop-filter: blur(2px))) { + body.mm-theme .dx-page-intro, + body.mm-theme .circle-feed-head { + background: rgba(255, 255, 255, .88); + -webkit-backdrop-filter: blur(12px) saturate(1.2); + backdrop-filter: blur(12px) saturate(1.2); + } +} +body.mm-theme .dx-page-intro h2, +body.mm-theme .circle-feed-head h2 { color: var(--mm-navy); text-shadow: 0 1px 0 rgba(255, 255, 255, .9); } +body.mm-theme .dx-page-intro p { color: #4b4b4b; } +body.mm-theme .dx-rules { + background: #fff; + border: 0; + border-radius: 24px; + padding: 20px 26px; + box-shadow: var(--mm-soft); + color: var(--mm-ink); +} +body.mm-theme .dan-disclaimer, +body.mm-theme .dan-msg { color: var(--muted); } +body.mm-theme .today-section-title { color: var(--mm-navy); } +body.mm-theme .help-page > p { color: #3d3a46; } +/* ============ 16. 深色模式开关(顶栏右端,官方 .header--btn 的圆钮语言) ============ */ + +body.mm-theme .mm-theme-btn { + position: relative; + flex: 0 0 auto; + width: 44px; + height: 44px; + margin-left: 6px; + padding: 0; + border: 3px solid var(--mm-purple); + border-radius: 100px; + background: #fff; + box-shadow: 4px 4px 0 rgba(0, 0, 0, .2); + cursor: pointer; + transition: background .2s, border-color .2s, box-shadow .2s; +} +body.mm-theme .mm-theme-btn svg { display: block; width: 22px; height: 22px; margin: 0 auto; } +body.mm-theme .mm-theme-btn svg > * { + transform-origin: 50% 50%; + transition: opacity .2s, transform .3s var(--mm-ease-out); +} +body.mm-theme .mm-theme-btn__moon { fill: var(--mm-navy); } +body.mm-theme .mm-theme-btn__sun { fill: var(--mm-yellow); } +/* 浅色模式显示月亮(点了变深色);深色模式的另一半在 theme-dark.css */ +body.mm-theme .mm-theme-btn__sun { opacity: 0; transform: scale(.4) rotate(-90deg); } +body.mm-theme .mm-theme-btn:hover { border-color: var(--mm-purple); background: var(--mm-purple); } +body.mm-theme .mm-theme-btn:hover .mm-theme-btn__moon, +body.mm-theme .mm-theme-btn:hover .mm-theme-btn__sun { fill: #fff; } +body.mm-theme .mm-theme-btn:focus-visible { outline: 3px solid var(--mm-pink-hot); outline-offset: 3px; } + +@media (max-width: 1000px) { + body.mm-theme .mm-theme-btn { width: 44px; height: 44px; margin-left: auto; } + body.mm-theme .mm-theme-btn svg { width: 21px; height: 21px; } +} + +/* ============ 17. 换主题的那一帧 ============ + 页面里只有一部分元件带 transition(卡片、按钮、输入框),换主题时它们会慢半拍淡过去, + 看上去就像「有的卡片没跟着换皮」。theme.js 在改 data-mm-theme 之前先加上 + .mm-theme-switching,两帧后摘掉 —— 这一帧内所有过渡停用,整页同时换色。 */ +html.mm-theme-switching, +html.mm-theme-switching *, +html.mm-theme-switching *::before, +html.mm-theme-switching *::after { transition: none !important; } +/* 只有开关自己该动:月亮沉下去、太阳升起来,这一下是要给人看的 */ +html.mm-theme-switching .mm-theme-btn { transition: background .2s, border-color .2s, box-shadow .2s !important; } +html.mm-theme-switching .mm-theme-btn svg > * { transition: opacity .2s, transform .3s var(--mm-ease-out) !important; } + +/* ============ 18. 与官方的偏差(改这里之前先想清楚) ============ + * + * 1) 中日韩字形:官方 `font-family:"M PLUS 1p",sans-serif`,但 M PLUS 1p 没有简体字, + * 所以中文回落到系统字体(雅黑/苹方/思源)。数字与拉丁字母仍是官方的 M PLUS 1p —— + * 这一层观感(大数字、编号、DX Rating)已经和官网一致。 + * 2) 版心宽度:官方顶栏 1200px、内容盒 900px;本站顶栏保持 1200,正文收到 1080, + * 否则表格与列表会被压得太窄。 + * 3) 版式:官网没有等价的界面,这里按同一套语言重新编排 —— 每块内容是一个官方 + * `.common--box`(白底 + 上紫下粉横条 + 14px 硬影),记录列表照官网 top--update + * 的做法排成「盒子里的行 + 发丝分隔」,不再是一堆各自带阴影的小卡片。 + * 4) 按钮尺寸:官方 .common--btn 是 60px 高、28px 字、10px 底影;列表里的操作按钮 + * 按同一比例缩到 40px 高、5px 底影,避免一行按钮撑满半屏。 + * 5) 移动端断点:官方在 769px 就收成汉堡 + 语言胶囊;本站导航是七个中文词,比官方的 + * SVG 字标更宽,所以断点挪到 1000px(769~1000px 之间也走汉堡菜单)。 + * 6) 记录列表密度:官方 top--update 一行 157px 高,一屏只看得到三条;本站记录里 + * 题名、平台、通过时间都是短字段,行高收到 95px(一屏六条),大盒子的外观不变。 + * 7) 数据页的图表:官方没有等价界面,图表由 stat-charts.js 用画布画,配色按明暗两套 + * 写在脚本里(CSS 变量管不到 canvas),换主题时由 stats-page.js 重画。 + * 8) 符号字形:官方那套 webfont 自己画箭头、方块、✓ 这类符号;M PLUS 1p 的 latin 子集 + * 没覆盖它们(会被画成一根没有箭头的斜线),所以 fonts.css 另外声明了一个只圈这些码位、 + * 指向系统符号字体的 "M PLUS 1p Symbols",排在 M PLUS 1p 之后。 + * + * 背景没有偏差:整页复用官网那份动态背景(public/bg.html, + * 结构与样式都是归档快照原文),用 iframe 铺在内容下面,官方 CSS 的全局选择器 + * (html/body/.wrapper…)不会碰到本站布局。夜版只是换 /assets/night/ 那套贴图, + * 结构与参数不变(见 theme-night.css)。 + */ \ No newline at end of file diff --git a/public/theme.js b/public/theme.js new file mode 100644 index 0000000..a7b42f1 --- /dev/null +++ b/public/theme.js @@ -0,0 +1,124 @@ +/** + * maimai 官网皮肤的行为层:深色模式开关、移动端菜单开关、回到顶部。 + * + * 只做这一层需要的行为,不碰 app.js 的数据逻辑;无依赖、原生 ESM。 + * + * 深色模式(主题的唯一写入口,其它模块只读 dataset.mmTheme): + * · 主题落在 ,首屏由各页 里的一小段脚本先定好, + * 这里负责开关、记忆(localStorage)、跟随系统,以及下面这些跟着主题走的东西: + * 开关的 aria-checked 与 title、iframe 背景的主题、 + * 画布重画(发 mm-theme-change)、换皮那一帧的过渡开关。 + * 品牌图标不用 JS:它读 上的主题走 CSS 背景,首帧就是对的。 + * · 背景是 iframe 里的官方动态页,换主题不重载它,改发 postMessage 就地换贴图, + * 官方那套入场动画不会重播。 + */ + +const THEME_KEY = 'mm-theme'; + +const prefersDark = () => window.matchMedia('(prefers-color-scheme: dark)').matches; + +function storedTheme() { + try { + const value = localStorage.getItem(THEME_KEY); + return value === 'dark' || value === 'light' ? value : null; + } catch { + return null; + } +} + +const currentTheme = () => (document.documentElement.dataset.mmTheme === 'dark' ? 'dark' : 'light'); + +const bgFrame = document.querySelector('.mm-bg-frame'); + +function tellBackground(theme) { + if (!bgFrame || !bgFrame.contentWindow) return; + // 背景页认的就是 data-mm-theme 那两个词,见 bg.html 的 message 监听 + bgFrame.contentWindow.postMessage({ mmTheme: theme }, location.origin); +} + +function applyTheme(theme, { remember = true, notify = true } = {}) { + // 换皮那一帧先把过渡关掉:只有一部分卡片/按钮带 transition,不关的话它们会慢半拍, + // 看上去像「有的卡片没跟着换主题」。两帧后摘掉,之后 hover 之类的过渡照旧。 + const root = document.documentElement; + root.classList.add('mm-theme-switching'); + root.dataset.mmTheme = theme; + root.style.colorScheme = theme; + const button = document.getElementById('mmThemeBtn'); + if (button) { + button.setAttribute('aria-checked', String(theme === 'dark')); + button.title = theme === 'dark' ? '切换到浅色模式' : '切换到深色模式'; + } + if (remember) { + try { + localStorage.setItem(THEME_KEY, theme); + } catch { + /* 隐私模式下写不进去就算了,当次会话照样能用 */ + } + } + if (notify) tellBackground(theme); + // 画布里的图形是 JS 画的,CSS 换皮管不到,交给需要重画的模块自己接 + document.dispatchEvent(new CustomEvent('mm-theme-change', { detail: { theme } })); + requestAnimationFrame(() => requestAnimationFrame(() => root.classList.remove('mm-theme-switching'))); +} + +// 首屏脚本已经定过主题,这里补齐图标/开关状态,并把主题同步给背景 +applyTheme(storedTheme() ?? (prefersDark() ? 'dark' : 'light'), { remember: false }); + +const themeBtn = document.getElementById('mmThemeBtn'); + +if (themeBtn) { + themeBtn.addEventListener('click', () => { + applyTheme(currentTheme() === 'dark' ? 'light' : 'dark'); + }); +} + +// 用户没手动选过时,跟随系统切换 +window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (event) => { + if (!storedTheme()) applyTheme(event.matches ? 'dark' : 'light', { remember: false }); +}); + +// iframe 的 src 放在 data-src 里,这里按当前主题挂上(深色直接带 ?night=1, +// 免得先亮后暗地闪一下);之后切换主题只发消息,不重载,官方入场动画不会重播。 +if (bgFrame) { + const baseSrc = bgFrame.getAttribute('data-src') || '/bg.html'; + if (!bgFrame.getAttribute('src')) { + bgFrame.setAttribute('src', currentTheme() === 'dark' ? `${baseSrc}?night=1` : baseSrc); + } + bgFrame.addEventListener('load', () => tellBackground(currentTheme())); +} + +const menuBtn = document.getElementById('mmMenuBtn'); + +if (menuBtn) { + const setOpen = (open) => { + document.body.classList.toggle('mm-menu-open', open); + // html 上也挂一份:只锁 body 拦不住 html 那只滚动条,深色面板右上角会露一截浅色滑块 + document.documentElement.classList.toggle('mm-menu-open', open); + menuBtn.setAttribute('aria-expanded', String(open)); + }; + menuBtn.addEventListener('click', () => setOpen(!document.body.classList.contains('mm-menu-open'))); + document.addEventListener('keydown', (event) => { + if (event.key === 'Escape') setOpen(false); + }); + // 窄屏展开、随后把窗口拉宽的话:菜单按钮这时已经隐藏,锁滚动的那条规则也出了媒体查询, + // 不收起来就会卡在一个看不见、又关不掉的展开态上 + window.matchMedia('(min-width: 1001px)').addEventListener('change', (event) => { + if (event.matches) setOpen(false); + }); + // 菜单里点走一项就收起来,否则新页面加载前会停在展开态 + document.querySelectorAll('.site-header .nav a').forEach((link) => { + link.addEventListener('click', () => setOpen(false)); + }); +} + +const topLink = document.querySelector('.mm-top'); + +if (topLink) { + const sync = () => topLink.classList.toggle('off', window.scrollY < 600); + sync(); + window.addEventListener('scroll', sync, { passive: true }); + topLink.addEventListener('click', (event) => { + event.preventDefault(); + window.scrollTo({ top: 0, behavior: 'smooth' }); + }); +} \ No newline at end of file diff --git a/public/vendor/maimai/README.md b/public/vendor/maimai/README.md new file mode 100644 index 0000000..75bab1d --- /dev/null +++ b/public/vendor/maimai/README.md @@ -0,0 +1,37 @@ +# vendor/maimai —— maimai DX 国际版官网素材(PRiSM PLUS 时期) + +前端重构的参照物:`maimai DX International ver.` 官网在 **PRiSM PLUS** 时期的样式与皮肤素材。 +这里只放「原样搬过来」的东西;把它落到本站组件上的那一层在 [`public/theme.css`](../../theme.css)。 + +## 来源 + +| 文件 | 原始地址 | 快照 | +| --- | --- | --- | +| `style.css` | `https://maimai.sega.com/assets/css/style.css` | `web.archive.org/web/20250804084236` | +| `img/**`(92 个) | `https://maimai.sega.com/assets/img/**` | 同上(线上仍在,直接抓线上) | +| `fonts/**` + `fonts.css` | `https://fonts.googleapis.com/css2?family=M+PLUS+1p:wght@400;500;700;800;900` | 2026-10-10 抓取 | + +归档页面本身(`https://web.archive.org/web/20250804084236/https://maimai.sega.com/`)与线上现站 +(已更新到 CiRCLE PLUS)**样式表不同**:PRiSM PLUS 版的 `.common--box` 是上紫 `#ba5eea` / +下粉 `#ffcedf`,CiRCLE PLUS 版换成了 `#f93eac` / `#6dbefe`。所以这里以归档快照为准。 + +## 对原文件做了什么 + +- `style.css`:只改了 URL。wayback 前缀与 `https://maimai.sega.com/assets/img/` 全部改写成 + `/vendor/maimai/img/`(73 处),Google Fonts 的 `@import` 换成注释(字体改为自托管, + 见 `fonts.css`)。**选择器与数值一个都没动**,要核对官方参数直接在这个文件里查。 + 归档页还在文件末尾追加了两段 wayback 自己的注释(版权声明与 playback timings), + 不属于官方文件,已删掉。 +- `img/**`:原始字节,未做任何压缩或转码。`img/MANIFEST.txt` 记录每个文件的字节数与 MD5。 +- `fonts.css`:只自托管 latin / latin-ext 子集(5 个字重 × 2 子集,共 10 个 woff2)。 + M PLUS 1p 没有简体字,中文由 `theme.css` 里的系统字体栈回退。另外补了一个 `"M PLUS 1p Symbols"` + 家族:M PLUS 1p 的 latin 子集只覆盖 ↑↓ 两个箭头,↗ ✓ ▲ 这类符号会被画成一根没有箭头的 + 斜线,所以只把那些码位圈出来交给系统符号字体(`theme.css` 的 `--mm-sans` 排在 M PLUS 1p 之后)。 +- 本站的背景宿主页 `public/bg.html` 不放在这里:它带着自己那三行 reset 和夜版切换脚本, + 放在 `public/` 根下,好让这个目录只留「原样搬过来」的素材。 + +## 授权 + +- `M PLUS 1p` 是 SIL Open Font License 1.1,可自由再分发。 +- `style.css` 与 `img/**` 是 **SEGA 的版权素材**,这里作为个人自用项目的样式参照/复刻保留, + 不属于 MIT 授权范围,也不代表 SEGA 授权。若要公开发布,请自行替换或取得许可。 \ No newline at end of file diff --git a/public/vendor/maimai/fonts.css b/public/vendor/maimai/fonts.css new file mode 100644 index 0000000..9545824 --- /dev/null +++ b/public/vendor/maimai/fonts.css @@ -0,0 +1,103 @@ +/* maimai DX International 官网(PRiSM PLUS 时期)使用的字体:M PLUS 1p(SIL OFL 1.1)。 + 这里只自托管 latin / latin-ext 子集(数字与拉丁字形是官方观感的关键), + 中日韩字形由 theme.css 里的系统字体栈回退。 + 来源: https://fonts.googleapis.com/css2?family=M+PLUS+1p:wght@400;500;700;800;900 抓取于 2026-10-10 */ + +/* M PLUS 1p 的 latin 子集只覆盖 U+2191/U+2193 两个箭头,↗ ✓ ▲ 这类符号会被它画成一根 + 没有箭头的斜线。这里单独声明一个只圈「M PLUS 1p 没覆盖的符号码位」的家族, + 交给系统符号字体渲染;其余字符仍旧走 M PLUS 1p。 */ +@font-face { + font-family: "M PLUS 1p Symbols"; + src: local("Segoe UI Symbol"), local("Segoe UI"), local("Apple Symbols"), local("Noto Sans Symbols 2"); + unicode-range: U+2190, U+2192, U+2194-21FF, U+25A0-27BF, U+2B00-2BFF, U+FE0F; +} + +@font-face { + font-family: "M PLUS 1p"; + font-style: normal; + font-weight: 400; + font-display: swap; + src: url("/vendor/maimai/fonts/mplus1p-400-latin-ext.woff2") format("woff2"); + unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C4, U+2113, U+2C60-2C7F, U+A720-A7FF; +} + +@font-face { + font-family: "M PLUS 1p"; + font-style: normal; + font-weight: 400; + font-display: swap; + src: url("/vendor/maimai/fonts/mplus1p-400-latin.woff2") format("woff2"); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; +} + +@font-face { + font-family: "M PLUS 1p"; + font-style: normal; + font-weight: 500; + font-display: swap; + src: url("/vendor/maimai/fonts/mplus1p-500-latin-ext.woff2") format("woff2"); + unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C4, U+2113, U+2C60-2C7F, U+A720-A7FF; +} + +@font-face { + font-family: "M PLUS 1p"; + font-style: normal; + font-weight: 500; + font-display: swap; + src: url("/vendor/maimai/fonts/mplus1p-500-latin.woff2") format("woff2"); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; +} + +@font-face { + font-family: "M PLUS 1p"; + font-style: normal; + font-weight: 700; + font-display: swap; + src: url("/vendor/maimai/fonts/mplus1p-700-latin-ext.woff2") format("woff2"); + unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C4, U+2113, U+2C60-2C7F, U+A720-A7FF; +} + +@font-face { + font-family: "M PLUS 1p"; + font-style: normal; + font-weight: 700; + font-display: swap; + src: url("/vendor/maimai/fonts/mplus1p-700-latin.woff2") format("woff2"); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; +} + +@font-face { + font-family: "M PLUS 1p"; + font-style: normal; + font-weight: 800; + font-display: swap; + src: url("/vendor/maimai/fonts/mplus1p-800-latin-ext.woff2") format("woff2"); + unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C4, U+2113, U+2C60-2C7F, U+A720-A7FF; +} + +@font-face { + font-family: "M PLUS 1p"; + font-style: normal; + font-weight: 800; + font-display: swap; + src: url("/vendor/maimai/fonts/mplus1p-800-latin.woff2") format("woff2"); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; +} + +@font-face { + font-family: "M PLUS 1p"; + font-style: normal; + font-weight: 900; + font-display: swap; + src: url("/vendor/maimai/fonts/mplus1p-900-latin-ext.woff2") format("woff2"); + unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C4, U+2113, U+2C60-2C7F, U+A720-A7FF; +} + +@font-face { + font-family: "M PLUS 1p"; + font-style: normal; + font-weight: 900; + font-display: swap; + src: url("/vendor/maimai/fonts/mplus1p-900-latin.woff2") format("woff2"); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; +} diff --git a/public/vendor/maimai/fonts/mplus1p-400-latin-ext.woff2 b/public/vendor/maimai/fonts/mplus1p-400-latin-ext.woff2 new file mode 100644 index 0000000..4e06334 Binary files /dev/null and b/public/vendor/maimai/fonts/mplus1p-400-latin-ext.woff2 differ diff --git a/public/vendor/maimai/fonts/mplus1p-400-latin.woff2 b/public/vendor/maimai/fonts/mplus1p-400-latin.woff2 new file mode 100644 index 0000000..ac77e19 Binary files /dev/null and b/public/vendor/maimai/fonts/mplus1p-400-latin.woff2 differ diff --git a/public/vendor/maimai/fonts/mplus1p-500-latin-ext.woff2 b/public/vendor/maimai/fonts/mplus1p-500-latin-ext.woff2 new file mode 100644 index 0000000..636c799 Binary files /dev/null and b/public/vendor/maimai/fonts/mplus1p-500-latin-ext.woff2 differ diff --git a/public/vendor/maimai/fonts/mplus1p-500-latin.woff2 b/public/vendor/maimai/fonts/mplus1p-500-latin.woff2 new file mode 100644 index 0000000..cfa6e04 Binary files /dev/null and b/public/vendor/maimai/fonts/mplus1p-500-latin.woff2 differ diff --git a/public/vendor/maimai/fonts/mplus1p-700-latin-ext.woff2 b/public/vendor/maimai/fonts/mplus1p-700-latin-ext.woff2 new file mode 100644 index 0000000..1b8d09e Binary files /dev/null and b/public/vendor/maimai/fonts/mplus1p-700-latin-ext.woff2 differ diff --git a/public/vendor/maimai/fonts/mplus1p-700-latin.woff2 b/public/vendor/maimai/fonts/mplus1p-700-latin.woff2 new file mode 100644 index 0000000..b2eb083 Binary files /dev/null and b/public/vendor/maimai/fonts/mplus1p-700-latin.woff2 differ diff --git a/public/vendor/maimai/fonts/mplus1p-800-latin-ext.woff2 b/public/vendor/maimai/fonts/mplus1p-800-latin-ext.woff2 new file mode 100644 index 0000000..d0c3b48 Binary files /dev/null and b/public/vendor/maimai/fonts/mplus1p-800-latin-ext.woff2 differ diff --git a/public/vendor/maimai/fonts/mplus1p-800-latin.woff2 b/public/vendor/maimai/fonts/mplus1p-800-latin.woff2 new file mode 100644 index 0000000..cc8f341 Binary files /dev/null and b/public/vendor/maimai/fonts/mplus1p-800-latin.woff2 differ diff --git a/public/vendor/maimai/fonts/mplus1p-900-latin-ext.woff2 b/public/vendor/maimai/fonts/mplus1p-900-latin-ext.woff2 new file mode 100644 index 0000000..66f2b0e Binary files /dev/null and b/public/vendor/maimai/fonts/mplus1p-900-latin-ext.woff2 differ diff --git a/public/vendor/maimai/fonts/mplus1p-900-latin.woff2 b/public/vendor/maimai/fonts/mplus1p-900-latin.woff2 new file mode 100644 index 0000000..8f3a39e Binary files /dev/null and b/public/vendor/maimai/fonts/mplus1p-900-latin.woff2 differ diff --git a/public/vendor/maimai/img/MANIFEST.txt b/public/vendor/maimai/img/MANIFEST.txt new file mode 100644 index 0000000..5326d0a --- /dev/null +++ b/public/vendor/maimai/img/MANIFEST.txt @@ -0,0 +1,94 @@ +source: https://maimai.sega.com/assets/img/ (PRiSM PLUS era) +captured: 2026-10-10 +common/arrow.svg 329 cd00d3b2a1aa2e2b5949f52dad24a558 +common/arrow_double.svg 446 0b5879f64d05aa1dc3e9fdc23ad55348 +common/download.png 4205 827094c65cef3383d9bb6dec7d876227 +common/icon_blank.png 175 89c27d1b7cd4fc7bb82ff39610665523 +common/icon_download.png 1258 b86c9e5c949b768dccc6d02da8e76838 +common/icon_next.png 667 705ee4253d5288fb297c76901452ecd4 +common/icon_select.png 3199 c03b0478d7a33f1bb4207464f26a610e +common/top.png 27682 00c901d61c45b23c51b37d87edcb91ca +favicon.png 6608 ef1d521081485dcd0d243f5bf923b217 +footer/footer_banner.jpg 39146 13a49137d35613da2cbb54a7ca36c47f +footer/footer_logo.png 5910 44e2b3c77749e055c5715474dd9c9e23 +footer/footer_sns_fb.png 2601 2882e1d277bb11d37fc2e68fc56cbf83 +footer/footer_sns_li.png 3497 19b49a359ac818230aff99911d17dfda +footer/footer_sns_x.png 3556 9f88bbfd72fee27ad8125598dd58897a +header/btn_arrow.png 1062 a6668541db9e0c9ca0a032181a06e372 +header/btn_arrow.svg 257 1c4a56dc66e329e5a6ea4985215d931d +ogp.png 308605 539bb531128070267670e0ccb8bb9148 +play/dash.png 214 0f646c1459e9c36b7cc3c1c43480f3ba +prism/bg/bg_aurora_pc.png 77561 5497b5bd7905e9fd74b587068cb860a7 +prism/bg/bg_aurora_sp.png 57495 e97a42bf83603df79ca0915345cfc4e2 +prism/bg/bg_cloud_back_c.png 2481 9911f23ca057640c1edc7ea15d027e50 +prism/bg/bg_cloud_back_l.png 3159 f00ef39719406641db4d8d3e9d991461 +prism/bg/bg_cloud_back_r.png 6380 2323f4753d9236f02bde7c878911dcb1 +prism/bg/bg_cloud_front_c.png 4211 11fba18997bdc5926abeb6cc4049c49c +prism/bg/bg_cloud_front_l.png 4314 a6f46856c6b759ad45a37dd483b4bc8c +prism/bg/bg_cloud_front_r.png 4107 f8f985b0df2581ca61485c3ce990a631 +prism/bg/bg_diamond_pink.png 293 48ba7b62ea9c739446893df834f5ff49 +prism/bg/bg_diamond_white.png 207 a6b102103edae42cd6329c2791e3c25b +prism/bg/bg_diamond_yellow.png 207 bbcc23a8e51be8b5e432614d70ce31a5 +prism/bg/bg_moon.png 14556 f939430c51d1544b10a5dabba4540d60 +prism/bg/bg_pattern.png 227 2fd46e9c9ddcec95cff99f0414791b2e +prism/bg/bg_rainbow_back_sp.png 6257 02c4872894e69365e2a4a7a97df14032 +prism/bg/bg_rainbow_btm_pc.png 13015 e6da0e89ec1d0fcbb0f4f3ca9cd3b522 +prism/bg/bg_rainbow_btm_sp.png 7951 634fef106b9c6cd363087d36beec21de +prism/bg/bg_rainbow_pc.png 14960 809799f6ed17a1c12d1926515672a16d +prism/bg/bg_rainbow_sp.png 13011 63207806f9db589b0df05ff34e1271b0 +prism/bg/bg_shines_pc.png 6610 b1450d76088e46622af0d689ad6ce979 +prism/bg/bg_shines_sp.png 5787 8d4c977e41e5f171db8ef7eb6301e72a +prism/bg/bg_shootingstar_head.png 1150 43eb091b339f87e0bbeec39cb70fe8a0 +prism/bg/bg_shootingstar_head_green.png 2442 6e60786ee48c82228519b0def2f16337 +prism/bg/bg_shootingstar_head_pink.png 2436 55e939f2f8a2ee7741d0d980c7e0d735 +prism/bg/bg_shootingstar_tail.png 3563 945fa11339fbff7bafb853fecba7eb59 +prism/bg/bg_shootingstar_tail_green.png 7624 65ac85eeba3c2aa6feb62b1a7ba525b4 +prism/bg/bg_shootingstar_tail_pink.png 8409 c091f0c3f4631212e3a111712ca91d30 +prism/bg/bg_star_white.png 2350 ce64b60ef33d71493b63a677744cdb1b +prism/bg/bg_star_yellow.png 2347 43b38e1237f59cb20a886528e69e0440 +prism/common/icon_header_left.svg 594 4fea9e2436d2fd407ea5c17553d90a76 +prism/common/icon_header_right.svg 592 f0ce643895d280ace29012e1a36c3a53 +prism/common/logo.png 25044 30e42459cafa4f48238b2666b37f1419 +prism/top/kv.png 227129 dac17b4963c92b8189bc7cd0a4386eaf +prism/top/kv_logo.png 88199 0bd8f99caf8b1ef1f7d405a9c33ae2de +prism/top/kv_logo_pc.png 188016 e76e9cb6373a9d0c2abc7a1fe0820c6a +prism/top/kv_pc.png 392031 8d611c4e3891b3d494003f1f631b3bbd +ranking/line_dashed_pc.png 2954 01ae4b1f39717dbc571ce45d293505e3 +ranking/line_dashed_sp.png 2890 984331ca21584984a04f7b0d040e8a2d +ranking/rank_num_bz0.png 5181 bef6dddc56330c8b4fec77536783e62e +ranking/rank_num_bz1.png 3237 03ad6e320c0f8d764b65691283efd548 +ranking/rank_num_bz100.png 6445 3509e9bb8f3222ab9408c6c5a0b0f3ff +ranking/rank_num_bz2.png 4748 2ecbfca66420d7201f573e84e26ae9b6 +ranking/rank_num_bz3.png 5352 a7ed94f7217f12a6e90a4be4794085f1 +ranking/rank_num_bz4.png 3870 ad8faeaf926db2a73e004e369deb6045 +ranking/rank_num_bz5.png 4771 cdf683c607f29f4d0e14f303edba88bf +ranking/rank_num_bz6.png 5388 28f21950f383f668b274e235cb7c4405 +ranking/rank_num_bz7.png 4152 7fa594a19a9b17cdcdf006fdd02c4e44 +ranking/rank_num_bz8.png 5407 2b07f8d98eab2b6382404297fee0afeb +ranking/rank_num_bz9.png 5477 a7765a53524058e5761c0f0f22ba63ed +ranking/rank_num_gold1.png 3490 a993aae223f0614911c8e72cb91448d0 +ranking/rank_num_sv0.png 5575 29ece737de4f96c10342b1cbc21ff122 +ranking/rank_num_sv1.png 3561 b756bc38f3dc43859e16698dd40b1444 +ranking/rank_num_sv2.png 5113 24bc53a9202110055c1da4d71f56b903 +ranking/rank_num_sv3.png 5719 72ba8459a334b1cee688eb6c664193d5 +ranking/rank_num_sv4.png 4293 5c80541f3d06205c4628542b35118b72 +ranking/rank_num_sv5.png 5092 a8780b5c47e721e7469d4f1bc37879be +ranking/rank_num_sv6.png 5779 86a8bf17c35a2ee159c07fe67ea7e511 +ranking/rank_num_sv7.png 4481 4a069b41c5cab1418528b4779b096f2a +ranking/rank_num_sv8.png 5822 22e8fc1381aefc6b512d8b3d63aded97 +ranking/rank_num_sv9.png 5802 f8da52b0d1b47620b628e7251ae86de4 +song/arrow.svg 255 29e1504d49b8b0da15a9a6b3266f4c93 +song/check.svg 214 24c8fa70832962e419b805d7daf04e7d +song/inner_dx.png 3740 0b100bf7f84ca28288f6ffccfa8886c4 +song/inner_st.png 3400 9a579e3a5bdc52a9840851aa20c2331d +song/lock.png 8026 baa6e48d518823bc1106c55c289d1f95 +song/piapro_logo.png 1650 8f4b97ddf187f2bd78cda20d5239752f +song/plus.png 913 ca9172a51a3dc49351097141bdaa0280 +song/search_del.png 1082 196405ba565bcc07f1fff571c8f98d5c +song/search_lens.svg 549 543411a2724886c04b9ed7a481d1af57 +song/utage/icon_buddy.png 3312 bf55c0de2f42a4d7515e874fe1504c94 +top/btn_chu.png 69468 48331d4bb9cd1da95b399cc1ab212024 +top/btn_top_1.png 12883 474aa55b03dd50d68166ceb0a105af39 +top/btn_top_2.png 13054 52cdb1892cc968c74e9b728aa46a6d90 +top/btn_top_4.png 21799 2e730a41deca13012068fdf12d5c9c5d +top/btn_top_kop.png 50820 b9ab4e5b5bb9dbbaeb49afd0af70745b diff --git a/public/vendor/maimai/img/common/arrow.svg b/public/vendor/maimai/img/common/arrow.svg new file mode 100644 index 0000000..085ef2d --- /dev/null +++ b/public/vendor/maimai/img/common/arrow.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/vendor/maimai/img/common/arrow_double.svg b/public/vendor/maimai/img/common/arrow_double.svg new file mode 100644 index 0000000..202e592 --- /dev/null +++ b/public/vendor/maimai/img/common/arrow_double.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/vendor/maimai/img/common/download.png b/public/vendor/maimai/img/common/download.png new file mode 100644 index 0000000..ffbc8b0 Binary files /dev/null and b/public/vendor/maimai/img/common/download.png differ diff --git a/public/vendor/maimai/img/common/icon_blank.png b/public/vendor/maimai/img/common/icon_blank.png new file mode 100644 index 0000000..d337eb3 Binary files /dev/null and b/public/vendor/maimai/img/common/icon_blank.png differ diff --git a/public/vendor/maimai/img/common/icon_download.png b/public/vendor/maimai/img/common/icon_download.png new file mode 100644 index 0000000..0110133 Binary files /dev/null and b/public/vendor/maimai/img/common/icon_download.png differ diff --git a/public/vendor/maimai/img/common/icon_next.png b/public/vendor/maimai/img/common/icon_next.png new file mode 100644 index 0000000..ccc9dcb Binary files /dev/null and b/public/vendor/maimai/img/common/icon_next.png differ diff --git a/public/vendor/maimai/img/common/icon_select.png b/public/vendor/maimai/img/common/icon_select.png new file mode 100644 index 0000000..5d3f7d8 Binary files /dev/null and b/public/vendor/maimai/img/common/icon_select.png differ diff --git a/public/vendor/maimai/img/common/top.png b/public/vendor/maimai/img/common/top.png new file mode 100644 index 0000000..3ded60e Binary files /dev/null and b/public/vendor/maimai/img/common/top.png differ diff --git a/public/vendor/maimai/img/favicon.png b/public/vendor/maimai/img/favicon.png new file mode 100644 index 0000000..1d5328f Binary files /dev/null and b/public/vendor/maimai/img/favicon.png differ diff --git a/public/vendor/maimai/img/footer/footer_banner.jpg b/public/vendor/maimai/img/footer/footer_banner.jpg new file mode 100644 index 0000000..2fa547c Binary files /dev/null and b/public/vendor/maimai/img/footer/footer_banner.jpg differ diff --git a/public/vendor/maimai/img/footer/footer_logo.png b/public/vendor/maimai/img/footer/footer_logo.png new file mode 100644 index 0000000..ddbbf88 Binary files /dev/null and b/public/vendor/maimai/img/footer/footer_logo.png differ diff --git a/public/vendor/maimai/img/footer/footer_sns_fb.png b/public/vendor/maimai/img/footer/footer_sns_fb.png new file mode 100644 index 0000000..9598222 Binary files /dev/null and b/public/vendor/maimai/img/footer/footer_sns_fb.png differ diff --git a/public/vendor/maimai/img/footer/footer_sns_li.png b/public/vendor/maimai/img/footer/footer_sns_li.png new file mode 100644 index 0000000..1e90faf Binary files /dev/null and b/public/vendor/maimai/img/footer/footer_sns_li.png differ diff --git a/public/vendor/maimai/img/footer/footer_sns_x.png b/public/vendor/maimai/img/footer/footer_sns_x.png new file mode 100644 index 0000000..0eaa1e2 Binary files /dev/null and b/public/vendor/maimai/img/footer/footer_sns_x.png differ diff --git a/public/vendor/maimai/img/header/btn_arrow.png b/public/vendor/maimai/img/header/btn_arrow.png new file mode 100644 index 0000000..9b64476 Binary files /dev/null and b/public/vendor/maimai/img/header/btn_arrow.png differ diff --git a/public/vendor/maimai/img/header/btn_arrow.svg b/public/vendor/maimai/img/header/btn_arrow.svg new file mode 100644 index 0000000..9492485 --- /dev/null +++ b/public/vendor/maimai/img/header/btn_arrow.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/vendor/maimai/img/ogp.png b/public/vendor/maimai/img/ogp.png new file mode 100644 index 0000000..5a1ae75 Binary files /dev/null and b/public/vendor/maimai/img/ogp.png differ diff --git a/public/vendor/maimai/img/play/dash.png b/public/vendor/maimai/img/play/dash.png new file mode 100644 index 0000000..6d3d38e Binary files /dev/null and b/public/vendor/maimai/img/play/dash.png differ diff --git a/public/vendor/maimai/img/prism/bg/bg_aurora_pc.png b/public/vendor/maimai/img/prism/bg/bg_aurora_pc.png new file mode 100644 index 0000000..fc884b2 Binary files /dev/null and b/public/vendor/maimai/img/prism/bg/bg_aurora_pc.png differ diff --git a/public/vendor/maimai/img/prism/bg/bg_aurora_sp.png b/public/vendor/maimai/img/prism/bg/bg_aurora_sp.png new file mode 100644 index 0000000..95971e1 Binary files /dev/null and b/public/vendor/maimai/img/prism/bg/bg_aurora_sp.png differ diff --git a/public/vendor/maimai/img/prism/bg/bg_cloud_back_c.png b/public/vendor/maimai/img/prism/bg/bg_cloud_back_c.png new file mode 100644 index 0000000..32552b9 Binary files /dev/null and b/public/vendor/maimai/img/prism/bg/bg_cloud_back_c.png differ diff --git a/public/vendor/maimai/img/prism/bg/bg_cloud_back_l.png b/public/vendor/maimai/img/prism/bg/bg_cloud_back_l.png new file mode 100644 index 0000000..eb0efe3 Binary files /dev/null and b/public/vendor/maimai/img/prism/bg/bg_cloud_back_l.png differ diff --git a/public/vendor/maimai/img/prism/bg/bg_cloud_back_r.png b/public/vendor/maimai/img/prism/bg/bg_cloud_back_r.png new file mode 100644 index 0000000..078b9a2 Binary files /dev/null and b/public/vendor/maimai/img/prism/bg/bg_cloud_back_r.png differ diff --git a/public/vendor/maimai/img/prism/bg/bg_cloud_front_c.png b/public/vendor/maimai/img/prism/bg/bg_cloud_front_c.png new file mode 100644 index 0000000..bbc3017 Binary files /dev/null and b/public/vendor/maimai/img/prism/bg/bg_cloud_front_c.png differ diff --git a/public/vendor/maimai/img/prism/bg/bg_cloud_front_l.png b/public/vendor/maimai/img/prism/bg/bg_cloud_front_l.png new file mode 100644 index 0000000..242e7ab Binary files /dev/null and b/public/vendor/maimai/img/prism/bg/bg_cloud_front_l.png differ diff --git a/public/vendor/maimai/img/prism/bg/bg_cloud_front_r.png b/public/vendor/maimai/img/prism/bg/bg_cloud_front_r.png new file mode 100644 index 0000000..fb437a2 Binary files /dev/null and b/public/vendor/maimai/img/prism/bg/bg_cloud_front_r.png differ diff --git a/public/vendor/maimai/img/prism/bg/bg_diamond_pink.png b/public/vendor/maimai/img/prism/bg/bg_diamond_pink.png new file mode 100644 index 0000000..6bead5c Binary files /dev/null and b/public/vendor/maimai/img/prism/bg/bg_diamond_pink.png differ diff --git a/public/vendor/maimai/img/prism/bg/bg_diamond_white.png b/public/vendor/maimai/img/prism/bg/bg_diamond_white.png new file mode 100644 index 0000000..0ce4a74 Binary files /dev/null and b/public/vendor/maimai/img/prism/bg/bg_diamond_white.png differ diff --git a/public/vendor/maimai/img/prism/bg/bg_diamond_yellow.png b/public/vendor/maimai/img/prism/bg/bg_diamond_yellow.png new file mode 100644 index 0000000..acd09a4 Binary files /dev/null and b/public/vendor/maimai/img/prism/bg/bg_diamond_yellow.png differ diff --git a/public/vendor/maimai/img/prism/bg/bg_moon.png b/public/vendor/maimai/img/prism/bg/bg_moon.png new file mode 100644 index 0000000..1e29b93 Binary files /dev/null and b/public/vendor/maimai/img/prism/bg/bg_moon.png differ diff --git a/public/vendor/maimai/img/prism/bg/bg_pattern.png b/public/vendor/maimai/img/prism/bg/bg_pattern.png new file mode 100644 index 0000000..110d6ec Binary files /dev/null and b/public/vendor/maimai/img/prism/bg/bg_pattern.png differ diff --git a/public/vendor/maimai/img/prism/bg/bg_rainbow_back_sp.png b/public/vendor/maimai/img/prism/bg/bg_rainbow_back_sp.png new file mode 100644 index 0000000..7e56299 Binary files /dev/null and b/public/vendor/maimai/img/prism/bg/bg_rainbow_back_sp.png differ diff --git a/public/vendor/maimai/img/prism/bg/bg_rainbow_btm_pc.png b/public/vendor/maimai/img/prism/bg/bg_rainbow_btm_pc.png new file mode 100644 index 0000000..148fea7 Binary files /dev/null and b/public/vendor/maimai/img/prism/bg/bg_rainbow_btm_pc.png differ diff --git a/public/vendor/maimai/img/prism/bg/bg_rainbow_btm_sp.png b/public/vendor/maimai/img/prism/bg/bg_rainbow_btm_sp.png new file mode 100644 index 0000000..8be6192 Binary files /dev/null and b/public/vendor/maimai/img/prism/bg/bg_rainbow_btm_sp.png differ diff --git a/public/vendor/maimai/img/prism/bg/bg_rainbow_pc.png b/public/vendor/maimai/img/prism/bg/bg_rainbow_pc.png new file mode 100644 index 0000000..959322b Binary files /dev/null and b/public/vendor/maimai/img/prism/bg/bg_rainbow_pc.png differ diff --git a/public/vendor/maimai/img/prism/bg/bg_rainbow_sp.png b/public/vendor/maimai/img/prism/bg/bg_rainbow_sp.png new file mode 100644 index 0000000..02eceb0 Binary files /dev/null and b/public/vendor/maimai/img/prism/bg/bg_rainbow_sp.png differ diff --git a/public/vendor/maimai/img/prism/bg/bg_shines_pc.png b/public/vendor/maimai/img/prism/bg/bg_shines_pc.png new file mode 100644 index 0000000..5ac4fb6 Binary files /dev/null and b/public/vendor/maimai/img/prism/bg/bg_shines_pc.png differ diff --git a/public/vendor/maimai/img/prism/bg/bg_shines_sp.png b/public/vendor/maimai/img/prism/bg/bg_shines_sp.png new file mode 100644 index 0000000..006cf75 Binary files /dev/null and b/public/vendor/maimai/img/prism/bg/bg_shines_sp.png differ diff --git a/public/vendor/maimai/img/prism/bg/bg_shootingstar_head.png b/public/vendor/maimai/img/prism/bg/bg_shootingstar_head.png new file mode 100644 index 0000000..71c9e4f Binary files /dev/null and b/public/vendor/maimai/img/prism/bg/bg_shootingstar_head.png differ diff --git a/public/vendor/maimai/img/prism/bg/bg_shootingstar_head_green.png b/public/vendor/maimai/img/prism/bg/bg_shootingstar_head_green.png new file mode 100644 index 0000000..89a9870 Binary files /dev/null and b/public/vendor/maimai/img/prism/bg/bg_shootingstar_head_green.png differ diff --git a/public/vendor/maimai/img/prism/bg/bg_shootingstar_head_pink.png b/public/vendor/maimai/img/prism/bg/bg_shootingstar_head_pink.png new file mode 100644 index 0000000..4e42d4b Binary files /dev/null and b/public/vendor/maimai/img/prism/bg/bg_shootingstar_head_pink.png differ diff --git a/public/vendor/maimai/img/prism/bg/bg_shootingstar_tail.png b/public/vendor/maimai/img/prism/bg/bg_shootingstar_tail.png new file mode 100644 index 0000000..80496f4 Binary files /dev/null and b/public/vendor/maimai/img/prism/bg/bg_shootingstar_tail.png differ diff --git a/public/vendor/maimai/img/prism/bg/bg_shootingstar_tail_green.png b/public/vendor/maimai/img/prism/bg/bg_shootingstar_tail_green.png new file mode 100644 index 0000000..247c9d8 Binary files /dev/null and b/public/vendor/maimai/img/prism/bg/bg_shootingstar_tail_green.png differ diff --git a/public/vendor/maimai/img/prism/bg/bg_shootingstar_tail_pink.png b/public/vendor/maimai/img/prism/bg/bg_shootingstar_tail_pink.png new file mode 100644 index 0000000..c08c4e8 Binary files /dev/null and b/public/vendor/maimai/img/prism/bg/bg_shootingstar_tail_pink.png differ diff --git a/public/vendor/maimai/img/prism/bg/bg_star_white.png b/public/vendor/maimai/img/prism/bg/bg_star_white.png new file mode 100644 index 0000000..f35da4c Binary files /dev/null and b/public/vendor/maimai/img/prism/bg/bg_star_white.png differ diff --git a/public/vendor/maimai/img/prism/bg/bg_star_yellow.png b/public/vendor/maimai/img/prism/bg/bg_star_yellow.png new file mode 100644 index 0000000..dad9408 Binary files /dev/null and b/public/vendor/maimai/img/prism/bg/bg_star_yellow.png differ diff --git a/public/vendor/maimai/img/prism/common/icon_header_left.svg b/public/vendor/maimai/img/prism/common/icon_header_left.svg new file mode 100644 index 0000000..014f7dc --- /dev/null +++ b/public/vendor/maimai/img/prism/common/icon_header_left.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/vendor/maimai/img/prism/common/icon_header_right.svg b/public/vendor/maimai/img/prism/common/icon_header_right.svg new file mode 100644 index 0000000..1ab4991 --- /dev/null +++ b/public/vendor/maimai/img/prism/common/icon_header_right.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/vendor/maimai/img/prism/common/logo.png b/public/vendor/maimai/img/prism/common/logo.png new file mode 100644 index 0000000..74ae960 Binary files /dev/null and b/public/vendor/maimai/img/prism/common/logo.png differ diff --git a/public/vendor/maimai/img/prism/top/kv.png b/public/vendor/maimai/img/prism/top/kv.png new file mode 100644 index 0000000..a58b99c Binary files /dev/null and b/public/vendor/maimai/img/prism/top/kv.png differ diff --git a/public/vendor/maimai/img/prism/top/kv_logo.png b/public/vendor/maimai/img/prism/top/kv_logo.png new file mode 100644 index 0000000..68b81cd Binary files /dev/null and b/public/vendor/maimai/img/prism/top/kv_logo.png differ diff --git a/public/vendor/maimai/img/prism/top/kv_logo_pc.png b/public/vendor/maimai/img/prism/top/kv_logo_pc.png new file mode 100644 index 0000000..7255a63 Binary files /dev/null and b/public/vendor/maimai/img/prism/top/kv_logo_pc.png differ diff --git a/public/vendor/maimai/img/prism/top/kv_pc.png b/public/vendor/maimai/img/prism/top/kv_pc.png new file mode 100644 index 0000000..57bd82c Binary files /dev/null and b/public/vendor/maimai/img/prism/top/kv_pc.png differ diff --git a/public/vendor/maimai/img/ranking/line_dashed_pc.png b/public/vendor/maimai/img/ranking/line_dashed_pc.png new file mode 100644 index 0000000..18f61dc Binary files /dev/null and b/public/vendor/maimai/img/ranking/line_dashed_pc.png differ diff --git a/public/vendor/maimai/img/ranking/line_dashed_sp.png b/public/vendor/maimai/img/ranking/line_dashed_sp.png new file mode 100644 index 0000000..9ce5c33 Binary files /dev/null and b/public/vendor/maimai/img/ranking/line_dashed_sp.png differ diff --git a/public/vendor/maimai/img/ranking/rank_num_bz0.png b/public/vendor/maimai/img/ranking/rank_num_bz0.png new file mode 100644 index 0000000..b104593 Binary files /dev/null and b/public/vendor/maimai/img/ranking/rank_num_bz0.png differ diff --git a/public/vendor/maimai/img/ranking/rank_num_bz1.png b/public/vendor/maimai/img/ranking/rank_num_bz1.png new file mode 100644 index 0000000..9f42309 Binary files /dev/null and b/public/vendor/maimai/img/ranking/rank_num_bz1.png differ diff --git a/public/vendor/maimai/img/ranking/rank_num_bz100.png b/public/vendor/maimai/img/ranking/rank_num_bz100.png new file mode 100644 index 0000000..31c333b Binary files /dev/null and b/public/vendor/maimai/img/ranking/rank_num_bz100.png differ diff --git a/public/vendor/maimai/img/ranking/rank_num_bz2.png b/public/vendor/maimai/img/ranking/rank_num_bz2.png new file mode 100644 index 0000000..f08a927 Binary files /dev/null and b/public/vendor/maimai/img/ranking/rank_num_bz2.png differ diff --git a/public/vendor/maimai/img/ranking/rank_num_bz3.png b/public/vendor/maimai/img/ranking/rank_num_bz3.png new file mode 100644 index 0000000..cbbc01c Binary files /dev/null and b/public/vendor/maimai/img/ranking/rank_num_bz3.png differ diff --git a/public/vendor/maimai/img/ranking/rank_num_bz4.png b/public/vendor/maimai/img/ranking/rank_num_bz4.png new file mode 100644 index 0000000..75cad0f Binary files /dev/null and b/public/vendor/maimai/img/ranking/rank_num_bz4.png differ diff --git a/public/vendor/maimai/img/ranking/rank_num_bz5.png b/public/vendor/maimai/img/ranking/rank_num_bz5.png new file mode 100644 index 0000000..ab16905 Binary files /dev/null and b/public/vendor/maimai/img/ranking/rank_num_bz5.png differ diff --git a/public/vendor/maimai/img/ranking/rank_num_bz6.png b/public/vendor/maimai/img/ranking/rank_num_bz6.png new file mode 100644 index 0000000..3109408 Binary files /dev/null and b/public/vendor/maimai/img/ranking/rank_num_bz6.png differ diff --git a/public/vendor/maimai/img/ranking/rank_num_bz7.png b/public/vendor/maimai/img/ranking/rank_num_bz7.png new file mode 100644 index 0000000..b56b39c Binary files /dev/null and b/public/vendor/maimai/img/ranking/rank_num_bz7.png differ diff --git a/public/vendor/maimai/img/ranking/rank_num_bz8.png b/public/vendor/maimai/img/ranking/rank_num_bz8.png new file mode 100644 index 0000000..642c8c1 Binary files /dev/null and b/public/vendor/maimai/img/ranking/rank_num_bz8.png differ diff --git a/public/vendor/maimai/img/ranking/rank_num_bz9.png b/public/vendor/maimai/img/ranking/rank_num_bz9.png new file mode 100644 index 0000000..33cd78e Binary files /dev/null and b/public/vendor/maimai/img/ranking/rank_num_bz9.png differ diff --git a/public/vendor/maimai/img/ranking/rank_num_gold1.png b/public/vendor/maimai/img/ranking/rank_num_gold1.png new file mode 100644 index 0000000..eed8b6b Binary files /dev/null and b/public/vendor/maimai/img/ranking/rank_num_gold1.png differ diff --git a/public/vendor/maimai/img/ranking/rank_num_sv0.png b/public/vendor/maimai/img/ranking/rank_num_sv0.png new file mode 100644 index 0000000..3b994c4 Binary files /dev/null and b/public/vendor/maimai/img/ranking/rank_num_sv0.png differ diff --git a/public/vendor/maimai/img/ranking/rank_num_sv1.png b/public/vendor/maimai/img/ranking/rank_num_sv1.png new file mode 100644 index 0000000..4816375 Binary files /dev/null and b/public/vendor/maimai/img/ranking/rank_num_sv1.png differ diff --git a/public/vendor/maimai/img/ranking/rank_num_sv2.png b/public/vendor/maimai/img/ranking/rank_num_sv2.png new file mode 100644 index 0000000..c469e24 Binary files /dev/null and b/public/vendor/maimai/img/ranking/rank_num_sv2.png differ diff --git a/public/vendor/maimai/img/ranking/rank_num_sv3.png b/public/vendor/maimai/img/ranking/rank_num_sv3.png new file mode 100644 index 0000000..ffb29b6 Binary files /dev/null and b/public/vendor/maimai/img/ranking/rank_num_sv3.png differ diff --git a/public/vendor/maimai/img/ranking/rank_num_sv4.png b/public/vendor/maimai/img/ranking/rank_num_sv4.png new file mode 100644 index 0000000..9cd5dc1 Binary files /dev/null and b/public/vendor/maimai/img/ranking/rank_num_sv4.png differ diff --git a/public/vendor/maimai/img/ranking/rank_num_sv5.png b/public/vendor/maimai/img/ranking/rank_num_sv5.png new file mode 100644 index 0000000..212ed60 Binary files /dev/null and b/public/vendor/maimai/img/ranking/rank_num_sv5.png differ diff --git a/public/vendor/maimai/img/ranking/rank_num_sv6.png b/public/vendor/maimai/img/ranking/rank_num_sv6.png new file mode 100644 index 0000000..4f703e6 Binary files /dev/null and b/public/vendor/maimai/img/ranking/rank_num_sv6.png differ diff --git a/public/vendor/maimai/img/ranking/rank_num_sv7.png b/public/vendor/maimai/img/ranking/rank_num_sv7.png new file mode 100644 index 0000000..c9a5d77 Binary files /dev/null and b/public/vendor/maimai/img/ranking/rank_num_sv7.png differ diff --git a/public/vendor/maimai/img/ranking/rank_num_sv8.png b/public/vendor/maimai/img/ranking/rank_num_sv8.png new file mode 100644 index 0000000..9b19a10 Binary files /dev/null and b/public/vendor/maimai/img/ranking/rank_num_sv8.png differ diff --git a/public/vendor/maimai/img/ranking/rank_num_sv9.png b/public/vendor/maimai/img/ranking/rank_num_sv9.png new file mode 100644 index 0000000..280574d Binary files /dev/null and b/public/vendor/maimai/img/ranking/rank_num_sv9.png differ diff --git a/public/vendor/maimai/img/song/arrow.svg b/public/vendor/maimai/img/song/arrow.svg new file mode 100644 index 0000000..7b5af85 --- /dev/null +++ b/public/vendor/maimai/img/song/arrow.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/vendor/maimai/img/song/check.svg b/public/vendor/maimai/img/song/check.svg new file mode 100644 index 0000000..1bb791f --- /dev/null +++ b/public/vendor/maimai/img/song/check.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/vendor/maimai/img/song/inner_dx.png b/public/vendor/maimai/img/song/inner_dx.png new file mode 100644 index 0000000..ae4a54a Binary files /dev/null and b/public/vendor/maimai/img/song/inner_dx.png differ diff --git a/public/vendor/maimai/img/song/inner_st.png b/public/vendor/maimai/img/song/inner_st.png new file mode 100644 index 0000000..774dd42 Binary files /dev/null and b/public/vendor/maimai/img/song/inner_st.png differ diff --git a/public/vendor/maimai/img/song/lock.png b/public/vendor/maimai/img/song/lock.png new file mode 100644 index 0000000..9937467 Binary files /dev/null and b/public/vendor/maimai/img/song/lock.png differ diff --git a/public/vendor/maimai/img/song/piapro_logo.png b/public/vendor/maimai/img/song/piapro_logo.png new file mode 100644 index 0000000..7777559 Binary files /dev/null and b/public/vendor/maimai/img/song/piapro_logo.png differ diff --git a/public/vendor/maimai/img/song/plus.png b/public/vendor/maimai/img/song/plus.png new file mode 100644 index 0000000..6f7cc03 Binary files /dev/null and b/public/vendor/maimai/img/song/plus.png differ diff --git a/public/vendor/maimai/img/song/search_del.png b/public/vendor/maimai/img/song/search_del.png new file mode 100644 index 0000000..f1f3736 Binary files /dev/null and b/public/vendor/maimai/img/song/search_del.png differ diff --git a/public/vendor/maimai/img/song/search_lens.svg b/public/vendor/maimai/img/song/search_lens.svg new file mode 100644 index 0000000..5eeac2e --- /dev/null +++ b/public/vendor/maimai/img/song/search_lens.svg @@ -0,0 +1,14 @@ + + + + + + diff --git a/public/vendor/maimai/img/song/utage/icon_buddy.png b/public/vendor/maimai/img/song/utage/icon_buddy.png new file mode 100644 index 0000000..5390767 Binary files /dev/null and b/public/vendor/maimai/img/song/utage/icon_buddy.png differ diff --git a/public/vendor/maimai/img/top/btn_chu.png b/public/vendor/maimai/img/top/btn_chu.png new file mode 100644 index 0000000..1f6ee2c Binary files /dev/null and b/public/vendor/maimai/img/top/btn_chu.png differ diff --git a/public/vendor/maimai/img/top/btn_top_1.png b/public/vendor/maimai/img/top/btn_top_1.png new file mode 100644 index 0000000..0346248 Binary files /dev/null and b/public/vendor/maimai/img/top/btn_top_1.png differ diff --git a/public/vendor/maimai/img/top/btn_top_2.png b/public/vendor/maimai/img/top/btn_top_2.png new file mode 100644 index 0000000..ea8efeb Binary files /dev/null and b/public/vendor/maimai/img/top/btn_top_2.png differ diff --git a/public/vendor/maimai/img/top/btn_top_4.png b/public/vendor/maimai/img/top/btn_top_4.png new file mode 100644 index 0000000..8126ef7 Binary files /dev/null and b/public/vendor/maimai/img/top/btn_top_4.png differ diff --git a/public/vendor/maimai/img/top/btn_top_kop.png b/public/vendor/maimai/img/top/btn_top_kop.png new file mode 100644 index 0000000..6587cba Binary files /dev/null and b/public/vendor/maimai/img/top/btn_top_kop.png differ diff --git a/public/vendor/maimai/style.css b/public/vendor/maimai/style.css new file mode 100644 index 0000000..94e2e35 --- /dev/null +++ b/public/vendor/maimai/style.css @@ -0,0 +1 @@ +@charset "UTF-8";/* 字体自托管:见 /vendor/maimai/fonts.css */input[type=text]{margin:0;padding:0;border:0;background-color:#fff;outline:0}.common--box{position:relative;width:87.5vw;border-radius:3.75vw;box-shadow:1.75vw 1.75vw 0 0 rgba(0,0,0,.2);background-color:#fff;margin:0 auto;padding:8.125vw 0}@media only screen and (min-width:769px){.common--box{width:81.8181818182vw;border-radius:2.7272727273vw;box-shadow:1.2727272727vw 1.2727272727vw 0 0 rgba(0,0,0,.2);padding:4.5454545455vw 0}}@media only screen and (min-width:1100px){.common--box{width:900px;border-radius:30px;box-shadow:14px 14px 0 0 rgba(0,0,0,.2);padding:50px 0}}.common--box:after,.common--box:before{position:absolute;display:block;content:"";width:75vw;height:1.5vw;left:50%;transform:translateX(-50%)}@media only screen and (min-width:769px){.common--box:after,.common--box:before{width:54.5454545455vw;height:.7272727273vw}}@media only screen and (min-width:1100px){.common--box:after,.common--box:before{width:600px;height:8px}}.common--box:before{top:0;border-radius:0 0 1.5vw 1.5vw;background-color:#ba5eea}@media only screen and (min-width:769px){.common--box:before{border-radius:0 0 .7272727273vw .7272727273vw}}@media only screen and (min-width:1100px){.common--box:before{border-radius:0 0 8px 8px}}.common--box:after{bottom:0;border-radius:1.5vw 1.5vw 0 0;background-color:#ffcedf}@media only screen and (min-width:769px){.common--box:after{border-radius:.7272727273vw .7272727273vw 0 0}}@media only screen and (min-width:1400px){.common--box:after{border-radius:8px 8px 0 0}}.common--top{position:fixed;z-index:100;display:block;bottom:2.5vw;right:2.5vw;width:15vw;transform:translateY(0);transition:.3s}.common--top.off{opacity:0;transform:translateY(30%);pointer-events:none}@media only screen and (min-width:769px){.common--top{transition:transform .3s}.common--top:hover{transform:translateY(-5%)}}@media only screen and (min-width:769px){.common--top{bottom:1.4285714286vw;right:1.4285714286vw;width:8.5714285714vw;transition:.3s}}@media only screen and (min-width:1400px){.common--top{bottom:20px;right:20px;width:120px}}.common--share{position:relative;width:87.5vw;border-radius:3.75vw;box-shadow:1.75vw 1.75vw 0 0 rgba(0,0,0,.2);background-color:#fff;margin:0 auto;padding:8.125vw 0}@media only screen and (min-width:769px){.common--share{width:81.8181818182vw;border-radius:2.7272727273vw;box-shadow:1.2727272727vw 1.2727272727vw 0 0 rgba(0,0,0,.2);padding:4.5454545455vw 0}}@media only screen and (min-width:1100px){.common--share{width:900px;border-radius:30px;box-shadow:14px 14px 0 0 rgba(0,0,0,.2);padding:50px 0}}.common--share:after,.common--share:before{position:absolute;display:block;content:"";width:75vw;height:1.5vw;left:50%;transform:translateX(-50%)}@media only screen and (min-width:769px){.common--share:after,.common--share:before{width:54.5454545455vw;height:.7272727273vw}}@media only screen and (min-width:1100px){.common--share:after,.common--share:before{width:600px;height:8px}}.common--share:before{top:0;border-radius:0 0 1.5vw 1.5vw;background-color:#ba5eea}@media only screen and (min-width:769px){.common--share:before{border-radius:0 0 .7272727273vw .7272727273vw}}@media only screen and (min-width:1100px){.common--share:before{border-radius:0 0 8px 8px}}.common--share:after{bottom:0;border-radius:1.5vw 1.5vw 0 0;background-color:#ffcedf}@media only screen and (min-width:769px){.common--share:after{border-radius:.7272727273vw .7272727273vw 0 0}}@media only screen and (min-width:1400px){.common--share:after{border-radius:8px 8px 0 0}}.common--header{display:flex;justify-content:center;align-items:center;margin:0 auto}.common--header__txt{margin:0 1.125vw}@media only screen and (min-width:769px){.common--header__txt{margin:0 1.8181818182vw}}@media only screen and (min-width:1400px){.common--header__txt{margin:0 20px}}.common--header__txt svg{width:100%;fill:#0b3871;fill-rule:evenodd}.common--header__icon{background-image:url(/vendor/maimai/img/prism/common/icon_header_left.svg);background-size:contain;background-repeat:no-repeat;background-position:center center;display:block;width:14vw;height:7vw}@media only screen and (min-width:769px){.common--header__icon{width:8vw;height:4vw;margin-bottom:0}}@media only screen and (min-width:1400px){.common--header__icon{width:112px;height:56px}}.common--header__icon.right{background-image:url(/vendor/maimai/img/prism/common/icon_header_right.svg)}.common--header__icon svg{width:100%;height:100%;fill:#f8b73d;fill-rule:evenodd}.common--btn{display:flex;align-items:center;justify-content:center;color:#fff;margin:0 auto;width:61.25vw;height:11.25vw;font-size:4.25vw;border-radius:12.5vw;font-weight:900;background-color:#e72139;box-shadow:0 2vw 0 0 #a51426;position:relative;word-break:break-all;white-space:nowrap}@media only screen and (min-width:769px){.common--btn{width:36.3636363636vw;height:5.4545454545vw;font-size:2.5454545455vw;box-shadow:0 .9090909091vw 0 0 #a51426;transition:.3s}.common--btn:hover{box-shadow:none;transform:translateY(.9090909091vw)}}@media only screen and (min-width:1100px){.common--btn{width:400px;height:60px;font-size:28px;box-shadow:0 10px 0 0 #a51426}.common--btn:hover{transform:translateY(10px)}}.common--btn:after{right:4.375vw}@media only screen and (min-width:769px){.common--btn:after{right:1.8181818182vw}}@media only screen and (min-width:1100px){.common--btn:after{right:20px}}.common--btn.next:after{position:absolute;display:block;content:"";width:2.25vw;height:2.5vw;background-image:url(/vendor/maimai/img/common/icon_next.png);background-size:cover;background-repeat:no-repeat}@media only screen and (min-width:769px){.common--btn.next:after{width:1.6363636364vw;height:1.8181818182vw}}@media only screen and (min-width:1100px){.common--btn.next:after{width:18px;height:20px}}.common--btn.blank:after{position:absolute;display:block;content:"";width:3.25vw;height:2.5vw;background-image:url(/vendor/maimai/img/common/icon_blank.png);background-size:cover;background-repeat:no-repeat}@media only screen and (min-width:769px){.common--btn.blank:after{width:2.3636363636vw;height:1.8181818182vw}}@media only screen and (min-width:1100px){.common--btn.blank:after{width:26px;height:20px}}.common--btn.download:after{position:absolute;display:block;content:"";width:4.5vw;height:5vw;background-image:url(/vendor/maimai/img/common/icon_download.png);background-size:cover;background-repeat:no-repeat}@media only screen and (min-width:769px){.common--btn.download:after{width:1.8181818182vw;height:2vw}}@media only screen and (min-width:1100px){.common--btn.download:after{width:20px;height:22px}}.common--title{padding-top:21.25vw;width:88.25vw;margin:0 auto}@media only screen and (min-width:769px){.common--title{padding-top:0;margin:6.3636363636vw auto 0;width:82.3636363636vw}}@media only screen and (min-width:1100px){.common--title{width:906px;margin:70px auto 0}}@keyframes gradient{0%{background-position:0 0}50%{background-position:0 100%}100%{background-position:0 0}}@keyframes pattern{0%{background-position:0 0}100%{background-position:-20% 20%}}@keyframes diamond{0%{transform:scaleX(1)}80%{transform:scaleX(1)}90%{transform:scaleX(0)}100%{transform:scaleX(1)}}@keyframes star{0%{opacity:0;transform:rotate(0)}20%{opacity:1}40%{opacity:0;transform:rotate(1turn)}100%{opacity:0;transform:rotate(1turn)}}@keyframes auroras{0%{opacity:1}20%{opacity:.6}80%{opacity:.6}100%{opacity:1}}@keyframes aurora{0%{opacity:0;transform:translateX(0)}1%{opacity:.8}80%{opacity:.8}99%{opacity:0;transform:translateX(-100%)}100%{opacity:0;transform:translateX(0)}}@keyframes cloud{0%{opacity:0;transform:translateX(0)}20%{opacity:1}80%{opacity:1}90%{opacity:0;transform:translateX(100%)}100%{opacity:0;transform:translateX(0)}}@keyframes moon{0%{transform:rotate(0)}100%{transform:rotate(30deg)}}@keyframes shooting-star{0%{transform:translateX(0) translateY(0);opacity:0}20%{opacity:1}40%{opacity:1}60%{transform:translateX(-100%) translateY(100%);opacity:0}to{transform:translateX(0) translateY(0);opacity:0}}@keyframes shooting-star-head{0%{transform:rotate(0)}to{transform:rotate(-1turn)}}@keyframes rainbow{0%{-webkit-clip-path:polygon(0 0,0 0,0 100%,0 100%);clip-path:polygon(0 0,0 0,0 100%,0 100%);transform:scale(.8)}to{-webkit-clip-path:polygon(0 0,100% 0,100% 100%,0 100%);clip-path:polygon(0 0,100% 0,100% 100%,0 100%);transform:scale(1)}}@keyframes rainbow-bottom{0%{transform:scale(.8)}to{transform:scale(1)}}.bg{pointer-events:none;background-image:linear-gradient(0deg,#45c7e3,#7dc0fd 10%,#d3a1ff 25%,#ff99c0 50%,#d3a1ff 75%,#7dc0fd 90%,#45c7e3)}.bg.prism{width:100%;height:100%;position:fixed;top:0;left:0;z-index:0;overflow:hidden}.bg--wrap{position:relative;z-index:1;width:100%;height:100%;overflow:hidden}.bg--gradient{position:relative;top:50%;left:50%;width:calc(100% - 5.5vw);height:calc(100% - 5.5vw);border-radius:5vw;background-size:cover;background-image:linear-gradient(0deg,#fff 14%,#ffd5cf 24%,#ffd5cf 46%,#ffc5d5 56%,#eaabff 67%,#72bcfe 85%,#65f2df 95%);overflow:hidden;z-index:-99;transform:translate3d(-50%,-50%,0);animation:gradient 10s infinite linear}@media only screen and (min-width:769px){.bg--gradient{width:calc(100% - 3.1428571429vw);height:calc(100% - 3.1428571429vw);border-radius:2.8571428571vw}}.bg--gradient:before{content:"";display:block;background:transparent url(/vendor/maimai/img/prism/bg/bg_pattern.png) repeat;width:100%;height:100%;background-size:6.5vw auto;background-position:50%;position:fixed;top:0;left:0;mix-blend-mode:overlay;animation:pattern 10s infinite linear}@media only screen and (min-width:769px){.bg--gradient:before{background-size:3.7142857143vw auto}}@media only screen and (min-width:1400px){.bg--gradient:before{background-size:52px auto}}.bg--shine{position:fixed;top:0;left:0;width:100%;height:100%;z-index:3;background-image:url(/vendor/maimai/img/prism/bg/bg_shines_sp.png);background-repeat:repeat;background-size:95vw auto;background-position:center 7vw}@media only screen and (min-width:769px){.bg--shine{background-image:url(/vendor/maimai/img/prism/bg/bg_shines_pc.png);background-size:95.1428571429vw;background-position:center 1.7142857143vw}}@media only screen and (min-width:1400px){.bg--shine{background-size:1332px;background-position:center 24px}}.bg--shine__wrap{position:relative;width:100%;height:100%}.bg--shine__item{position:absolute;width:5vw;height:5vw;top:0;left:0;background-size:contain;background-repeat:no-repeat}@media only screen and (min-width:769px){.bg--shine__item{width:1.4285714286vw;height:1.4285714286vw}}@media only screen and (min-width:1400px){.bg--shine__item{width:20px;height:20px}}.bg--shine__item.diamond-pink{background-image:url(/vendor/maimai/img/prism/bg/bg_diamond_pink.png);top:9vw;left:68.25vw;animation:diamond 3s infinite 1s linear}@media only screen and (max-width:769px){.bg--shine__item.diamond-pink{width:3vw;height:3vw}}@media only screen and (min-width:769px){.bg--shine__item.diamond-pink{top:2.2857142857vw;left:23.1428571429vw}}@media only screen and (min-width:1400px){.bg--shine__item.diamond-pink{top:32px;left:324px}}.bg--shine__item.diamond-yellow{background-image:url(/vendor/maimai/img/prism/bg/bg_diamond_yellow.png);top:6.5vw;left:74.5vw;animation:diamond 3s infinite 2s linear}@media only screen and (min-width:769px){.bg--shine__item.diamond-yellow{top:21.8571428571vw;left:9.7142857143vw}}@media only screen and (min-width:1400px){.bg--shine__item.diamond-yellow{top:306px;left:136px}}.bg--shine__item.diamond-white{background-image:url(/vendor/maimai/img/prism/bg/bg_diamond_white.png);top:16.5vw;left:50.5vw;animation:diamond 3s infinite linear}@media only screen and (min-width:769px){.bg--shine__item.diamond-white{top:18.2857142857vw;left:84.2857142857vw}}@media only screen and (min-width:1400px){.bg--shine__item.diamond-white{top:256px;left:1180px}}.bg--shine__item.star-white{background-image:url(/vendor/maimai/img/prism/bg/bg_star_white.png);width:7.5vw;height:7.5vw;top:21vw;left:79.5vw;opacity:0;animation:star 3s infinite linear}@media only screen and (min-width:769px){.bg--shine__item.star-white{width:2.1428571429vw;height:2.1428571429vw;top:16.4285714286vw;left:39.4285714286vw}}@media only screen and (min-width:1400px){.bg--shine__item.star-white{width:30px;height:30px;top:230px;left:552px}}.bg--shine__item.star-yellow{width:7.5vw;height:7.5vw;opacity:0;animation:star 3s infinite 1s linear}@media only screen and (min-width:769px){.bg--shine__item.star-yellow{width:2.1428571429vw;height:2.1428571429vw;top:16.4285714286vw;left:39.4285714286vw}}@media only screen and (min-width:1400px){.bg--shine__item.star-yellow{width:30px;height:30px;top:230px;left:552px}}.bg--shine__item.star-yellow.i1{background-image:url(/vendor/maimai/img/prism/bg/bg_star_yellow.png);top:28.5vw;left:3vw}@media only screen and (min-width:769px){.bg--shine__item.star-yellow.i1{top:8.1428571429vw;left:.8571428571vw}}@media only screen and (min-width:1400px){.bg--shine__item.star-yellow.i1{top:114px;left:12px}}.bg--shine__item.star-yellow.i2{background-image:url(/vendor/maimai/img/prism/bg/bg_star_yellow.png);top:5vw;left:30vw}@media only screen and (min-width:769px){.bg--shine__item.star-yellow.i2{top:28.8571428571vw;left:96.8571428571vw}}@media only screen and (min-width:1400px){.bg--shine__item.star-yellow.i2{top:404px;left:1356px}}.bg--aurora{position:absolute;top:0;left:0;width:100%;height:61.5vw;background-image:url(/vendor/maimai/img/prism/bg/bg_aurora_sp.png);background-repeat:repeat-x;background-size:415vw;background-position:left bottom 37.5vw;animation:auroras 40s infinite linear;z-index:2}@media only screen and (min-width:769px){.bg--aurora{background-image:url(/vendor/maimai/img/prism/bg/bg_aurora_pc.png);height:17.5714285714vw;background-size:228.2857142857vw auto;background-position:0 100%;animation:auroras 50s infinite linear}}@media only screen and (min-width:1400px){.bg--aurora{height:246px;background-size:3196px auto}}.bg--aurora:after,.bg--aurora:before{content:"";display:block;background-image:url(/vendor/maimai/img/prism/bg/bg_aurora_sp.png);position:absolute;top:0;left:0;background-size:contain;background-repeat:no-repeat;width:200%}.bg--aurora:before{height:42vw;top:0;background-repeat:repeat-x;opacity:0;animation:aurora 120s infinite linear}@media only screen and (min-width:769px){.bg--aurora:before{height:33.7857142857vw;top:-17.8571428571vw;left:0}}@media only screen and (min-width:1400px){.bg--aurora:before{height:473px;top:-250px}}.bg--aurora:after{height:101.75vw;top:-16.5vw;background-repeat:repeat-x;opacity:0;animation:aurora 90s infinite linear reverse}@media only screen and (min-width:769px){.bg--aurora:after{height:12vw;top:-19.2857142857vw;left:0}}@media only screen and (min-width:1400px){.bg--aurora:after{height:168px;top:-270px}}.bg--cloud{position:absolute;width:100%;height:100%;top:0;left:0}.bg--cloud__item{display:block;position:absolute;top:0;left:0;background-size:contain;background-repeat:no-repeat;z-index:1}.bg--cloud__item.i1{background-image:url(/vendor/maimai/img/prism/bg/bg_cloud_back_l.png);top:.5vw;left:-20.5vw;width:32.75vw;height:17.5vw;animation:cloud 12s infinite linear}@media only screen and (min-width:769px){.bg--cloud__item.i1{top:16.8571428571vw;left:-4.1428571429vw;width:18.7142857143vw;height:10vw}}@media only screen and (min-width:1400px){.bg--cloud__item.i1{top:236px;left:-58px;width:262px;height:140px}}.bg--cloud__item.i2{background-image:url(/vendor/maimai/img/prism/bg/bg_cloud_back_r.png);top:28.5vw;left:48vw;width:67vw;height:35vw;animation:cloud 14s infinite linear}@media only screen and (min-width:769px){.bg--cloud__item.i2{top:11.7142857143vw;left:71.4285714286vw;width:38.2857142857vw;height:20vw}}@media only screen and (min-width:1400px){.bg--cloud__item.i2{top:164px;left:1000px;width:536px;height:280px}}.bg--cloud__item.i3{background-image:url(/vendor/maimai/img/prism/bg/bg_cloud_back_c.png);top:81vw;left:75vw;width:30.25vw;height:16.125vw;animation:cloud 13s infinite linear}@media only screen and (min-width:769px){.bg--cloud__item.i3{top:29.2857142857vw;left:81.4285714286vw;width:17.2857142857vw;height:9.2142857143vw}}@media only screen and (min-width:1400px){.bg--cloud__item.i3{top:410px;left:1140px;width:242px;height:129px}}.bg--cloud__item.i4{background-image:url(/vendor/maimai/img/prism/bg/bg_cloud_front_l.png);top:67vw;left:4.5vw;width:37.375vw;height:17.25vw;animation:cloud 18s infinite linear}@media only screen and (min-width:769px){.bg--cloud__item.i4{top:26.4285714286vw;left:7.4285714286vw;width:21.3571428571vw;height:9.8571428571vw}}@media only screen and (min-width:1400px){.bg--cloud__item.i4{top:370px;left:104px;width:299px;height:138px}}.bg--cloud__item.i5{background-image:url(/vendor/maimai/img/prism/bg/bg_cloud_front_r.png);top:36.5vw;left:66.5vw;width:35.5vw;height:20vw;animation:cloud 20s infinite linear}@media only screen and (min-width:769px){.bg--cloud__item.i5{top:16.5vw;left:82.2857142857vw;width:20.3571428571vw;height:11.4285714286vw}}@media only screen and (min-width:1400px){.bg--cloud__item.i5{top:231px;left:1152px;width:185px;height:160px}}.bg--cloud__item.i6{background-image:url(/vendor/maimai/img/prism/bg/bg_cloud_front_c.png);top:90vw;left:59vw;width:32.375vw;height:15.375vw;animation:cloud 19s infinite linear}@media only screen and (min-width:769px){.bg--cloud__item.i6{top:34.5714285714vw;left:72.8571428571vw;width:18.5vw;height:8.7857142857vw}}@media only screen and (min-width:1400px){.bg--cloud__item.i6{top:484px;left:1020px;width:259px;height:123px}}.bg--moon{position:absolute;top:4vw;left:4vw;width:24.75vw;height:24.75vw;z-index:2;background-size:contain;background-repeat:no-repeat;background-image:url(/vendor/maimai/img/prism/bg/bg_moon.png)}@media only screen and (min-width:769px){.bg--moon{top:0;left:1.4285714286vw;width:14.1428571429vw;height:14.1428571429vw;animation:moon 6s infinite linear alternate}}@media only screen and (min-width:1400px){.bg--moon{left:20px;width:198px;height:198px}}.bg--star__item{position:absolute;top:0;left:0;z-index:0;animation:shooting-star 2s infinite linear;opacity:0}.bg--star__item:after,.bg--star__item:before{content:"";display:block;position:absolute;background-size:contain;background-repeat:no-repeat}.bg--star__item:before{top:0;right:0;width:91%;height:91%;background-image:url(/vendor/maimai/img/prism/bg/bg_shootingstar_tail.png);background-size:contain}.bg--star__item:after{position:absolute;bottom:0;left:0;width:14%;height:14%;background-image:url(/vendor/maimai/img/prism/bg/bg_shootingstar_head.png);background-size:contain;background-repeat:no-repeat;transform-origin:50% 50%;animation:shooting-star-head 2.2s infinite linear}.bg--star__item.green:before{background-image:url(/vendor/maimai/img/prism/bg/bg_shootingstar_tail_green.png)}.bg--star__item.green:after{background-image:url(/vendor/maimai/img/prism/bg/bg_shootingstar_head_green.png)}.bg--star__item.pink:before{background-image:url(/vendor/maimai/img/prism/bg/bg_shootingstar_tail_pink.png)}.bg--star__item.pink:after{background-image:url(/vendor/maimai/img/prism/bg/bg_shootingstar_head_pink.png)}.bg--star__item.i1{width:19.25vw;height:19.25vw;top:-11.5vw;left:4vw}@media only screen and (min-width:769px){.bg--star__item.i1{width:11vw;height:11vw;top:5.7857142857vw;left:10.5vw}}@media only screen and (min-width:1400px){.bg--star__item.i1{width:154px;height:154px;top:81px;left:147px}}.bg--star__item.i2{width:19.25vw;height:19.25vw;top:-11vw;left:88vw;animation:shooting-star 2s infinite .8s linear}@media only screen and (min-width:769px){.bg--star__item.i2{width:11vw;height:11vw;top:5.2857142857vw;left:34.7142857143vw}}@media only screen and (min-width:1400px){.bg--star__item.i2{width:154px;height:154px;top:74px;left:486px}}.bg--star__item.i3{width:38.5vw;height:38.5vw;top:11.5vw;left:9vw}@media only screen and (min-width:769px){.bg--star__item.i3{width:22vw;height:22vw;top:1.4285714286vw;left:31.1428571429vw}}@media only screen and (min-width:1400px){.bg--star__item.i3{width:308px;height:308px;top:20px;left:436px}}.bg--star__item.i4{width:38.5vw;height:38.5vw;top:3vw;left:313vw;animation:shooting-star 2s infinite 1s linear}@media only screen and (min-width:769px){.bg--star__item.i4{width:11vw;height:11vw;top:.8571428571vw;left:89.4285714286vw}}@media only screen and (min-width:1400px){.bg--star__item.i4{width:154px;height:154px;top:12px;left:1252px}}.bg--star__item.i5{display:none}@media only screen and (min-width:769px){.bg--star__item.i5{display:block;width:22vw;height:22vw;top:-4.2857142857vw;left:87.1428571429vw;animation:shooting-star 2s infinite .8s linear}}@media only screen and (min-width:1400px){.bg--star__item.i5{width:308px;height:308px;top:-60px;left:1220px}}.bg--star__item.i6{display:none}@media only screen and (min-width:769px){.bg--star__item.i6{display:block;width:11vw;height:11vw;top:21.7142857143vw;left:87.8571428571vw;animation:shooting-star 2s infinite 1s linear}}@media only screen and (min-width:1400px){.bg--star__item.i6{width:154px;height:154px;top:304px;left:1230px}}.bg--star__item.i7{display:none}@media only screen and (min-width:769px){.bg--star__item.i7{display:block;width:18.1428571429vw;height:18.1428571429vw;top:14.5714285714vw;left:110vw;animation:shooting-star 2s infinite 1s linear}}@media only screen and (min-width:1400px){.bg--star__item.i7{width:254px;height:254px;top:204px;left:1540px}}.bg--rainbow:before{content:"";display:block;position:absolute;background-repeat:no-repeat;width:100%;height:90vw;bottom:44vw;background-image:url(/vendor/maimai/img/prism/bg/bg_rainbow_sp.png);background-size:100%;background-repeat:no-repeat;background-position:50%;-webkit-clip-path:polygon(0 0,0 0,0 100%,0 100%);clip-path:polygon(0 0,0 0,0 100%,0 100%);animation:rainbow 2s ease forwards;z-index:3}@media only screen and (min-width:769px){.bg--rainbow:before{background-image:url(/vendor/maimai/img/prism/bg/bg_rainbow_pc.png);background-size:54.5vw 28.5vw;height:28.5vw;bottom:10vw}}@media only screen and (min-width:1400px){.bg--rainbow:before{background-size:763px 399px;height:399px;bottom:140px}}.bg--rainbow__cloud{position:absolute;background-repeat:no-repeat;width:100%;height:57.5vw;bottom:35vw;background-image:url(/vendor/maimai/img/prism/bg/bg_rainbow_btm_sp.png);background-size:100vw 27vw;background-position:50%;animation:rainbow-bottom 2s ease forwards;z-index:3}@media only screen and (min-width:769px){.bg--rainbow__cloud{background-image:url(/vendor/maimai/img/prism/bg/bg_rainbow_btm_pc.png);background-size:98.5714285714vw 16.4285714286vw;height:16.4285714286vw;bottom:2.8571428571vw}}@media only screen and (min-width:1400px){.bg--rainbow__cloud{background-size:1380px 230px;height:230px;bottom:40px}}.bg--rainbow__cloud:after{content:"";display:block;position:absolute;background-repeat:no-repeat;width:100%;height:100%;top:-26vw;background-image:url(/vendor/maimai/img/prism/bg/bg_rainbow_back_sp.png);background-size:119%;background-position:0;z-index:-4}@media only screen and (min-width:769px){.bg--rainbow__cloud:after{display:none}}.header{position:relative;z-index:1000000}.header--language{width:93.125vw;height:10vw;top:3.75vw;border-radius:100px;background-color:#fff;position:absolute;left:50%;transform:translateX(-50%);box-shadow:1.25vw 1.25vw 0 rgba(0,0,0,.2);display:flex;align-items:center;justify-content:space-between;padding-left:3.75vw}@media only screen and (min-width:769px){.header--language{box-shadow:.7142857143vw .7142857143vw 0 rgba(0,0,0,.2)}}@media only screen and (min-width:1400px){.header--language{box-shadow:10px 10px 0 rgba(0,0,0,.2)}}@media only screen and (min-width:769px){.header--language{display:none}}.header--language__link{background-color:#ba5eea;border-radius:0 100px 100px 0;width:23.875vw;padding-left:2.5vw;height:100%;display:flex;align-items:center}.header--language__text{font-size:2.5vw;font-weight:800;flex:1}.header--language__svg{width:15vw;margin-top:.5vw}.header--language__svg .svg{width:100%;height:auto;fill:#fff;fill-rule:evenodd}.header--language__arrow{width:3.75vw;margin-left:1.25vw;font-size:0;position:relative}.header--language__arrow img{position:relative;z-index:10}.header--language__arrow::after{display:block;content:"";position:absolute;background-color:#ba5eea;width:1.875vw;height:1.875vw;top:50%;left:50%;transform:translate(-50%,-50%);z-index:0}.header--btn{width:15vw;height:15vw;background-color:#fff;border:1.25vw solid #e72139;position:fixed;z-index:10;top:7.5vw;right:2.5vw;border-radius:100px;box-shadow:1.25vw 1.25vw 0 rgba(0,0,0,.2);display:flex;align-items:center;justify-content:center;flex-direction:column;transition:.3s;transform:translateY(0)}@media only screen and (min-width:769px){.header--btn{box-shadow:.7142857143vw .7142857143vw 0 rgba(0,0,0,.2)}}@media only screen and (min-width:1400px){.header--btn{box-shadow:10px 10px 0 rgba(0,0,0,.2)}}.header--btn.off{opacity:0;transform:translateY(-100%);pointer-events:none}@media only screen and (min-width:769px){.header--btn{display:none}}.header--btn span{display:block;width:5.625vw;height:1vw;background-color:#e72139;border-radius:100px;position:relative}.header--btn span:first-child{top:-1vw}.header--btn span:last-child{top:1vw}.header--nav{padding-top:7.5vw;position:fixed;top:-100%;left:0;width:100%;height:100%;background-color:#fff;z-index:1000;transition:all .3s;opacity:0;box-shadow:1.25vw 1.25vw 0 rgba(0,0,0,.2)}@media only screen and (min-width:769px){.header--nav{box-shadow:.7142857143vw .7142857143vw 0 rgba(0,0,0,.2)}}@media only screen and (min-width:1400px){.header--nav{box-shadow:10px 10px 0 rgba(0,0,0,.2)}}@media only screen and (min-width:769px){.header--nav{display:flex;align-items:flex-end;justify-content:space-between;opacity:1;margin:1.4285714286vw auto 0;width:85.7142857143vw;height:7.1428571429vw;border-radius:3.5714285714vw;padding-top:0;padding-left:2.3571428571vw;position:relative;top:auto;left:auto;transition:none}}@media only screen and (min-width:1400px){.header--nav{margin:20px auto 0;width:1200px;height:100px;border-radius:50px;padding-left:33px}}.header--nav.open{top:0;opacity:1}.header--nav__btn{width:15vw;height:15vw;background-color:#ba5eea;position:absolute;top:7.5vw;right:2.5vw;border-radius:100px;display:flex;align-items:center;justify-content:center;flex-direction:column}@media only screen and (min-width:769px){.header--nav__btn{display:none}}.header--nav__btn span{display:block;width:6.25vw;height:1vw;background-color:#fff;border-radius:100px;position:relative}.header--nav__btn span:first-child{top:.5vw;transform:rotate(45deg)}.header--nav__btn span:last-child{top:-.5vw;transform:rotate(-45deg)}.header--nav__logo{display:block;width:52.625vw;transform:translate3d(0,0,0)}@media only screen and (max-width:769px){.header--nav__logo{position:relative;z-index:2;left:50%;transform:translate3d(-50%,0,0)}}@media only screen and (min-width:769px){.header--nav__logo{width:12.7857142857vw;margin:0 0 1.5vw;transition:opacity .3s}.header--nav__logo:hover{opacity:.7}}@media only screen and (min-width:1400px){.header--nav__logo{width:179px;margin:0 0 21px}}.header--nav__inner{margin:6.5vw 0 0 0}@media only screen and (min-width:769px){.header--nav__inner{margin:0 3.5714285714vw 1.4285714286vw 0}}@media only screen and (min-width:1400px){.header--nav__inner{margin:0 50px 20px 0}}@media only screen and (min-width:769px){.header--nav__list{display:flex}}.header--nav__item{margin:5vw 0 0}@media only screen and (min-width:769px){.header--nav__item{display:flex;align-items:center;margin:0}}@media only screen and (min-width:769px){.header--nav__item:not(:last-child){margin:0 .8571428571vw 0 0}}@media only screen and (min-width:1400px){.header--nav__item:not(:last-child){margin:0 12px 0 0}}.header--nav__item:not(:last-child)::after{display:block;content:"";width:3.75vw;height:.5vw;background-color:#e5e5e5;margin:5vw auto 0}@media only screen and (min-width:769px){.header--nav__item:not(:last-child)::after{width:1px;height:1vw;margin:0 0 0 1vw}}@media only screen and (min-width:1400px){.header--nav__item:not(:last-child)::after{height:14px;margin:0 0 0 14px}}.header--nav__link{display:flex;align-items:center;justify-content:center;margin:0 auto;position:relative}@media only screen and (min-width:769px){.header--nav__link.on::before,.header--nav__link:hover::before{position:absolute;bottom:-1.4285714286vw;content:"";display:block;width:100%;background-color:#ba5eea;height:.3571428571vw;border-radius:100px 100px 0 0}}@media only screen and (min-width:1400px){.header--nav__link.on::before,.header--nav__link:hover::before{height:5px;bottom:-20px}}.header--nav__link.blank:after{display:block;content:"";background-image:url('data:image/svg+xml;utf8,');background-size:contain;background-repeat:no-repeat;width:3.25vw;height:2.5vw;margin-left:1.75vw}@media only screen and (min-width:769px){.header--nav__link.blank:after{width:.7857142857vw;height:.5714285714vw;margin-left:.4285714286vw}}@media only screen and (min-width:1400px){.header--nav__link.blank:after{width:11px;height:8px;margin-left:6px}}.header--nav__link svg{fill:#0b3871;fill-rule:evenodd;height:6.25vw}@media only screen and (min-width:769px){.header--nav__link svg{height:1.2857142857vw}}@media only screen and (min-width:1400px){.header--nav__link svg{height:18px}}.header--nav__link.l01{width:15.375vw}@media only screen and (min-width:769px){.header--nav__link.l01{width:4.5vw}}@media only screen and (min-width:1400px){.header--nav__link.l01{width:63px}}.header--nav__link.l02{width:31.75vw}@media only screen and (min-width:769px){.header--nav__link.l02{width:9.1428571429vw}}@media only screen and (min-width:1400px){.header--nav__link.l02{width:128px}}.header--nav__link.l03{width:24.875vw}@media only screen and (min-width:769px){.header--nav__link.l03{width:7.2142857143vw}}@media only screen and (min-width:1400px){.header--nav__link.l03{width:101px}}.header--nav__link.l04{width:21.25vw}@media only screen and (min-width:769px){.header--nav__link.l04{width:6.0714285714vw}}@media only screen and (min-width:1400px){.header--nav__link.l04{width:85px}}.header--nav__link.l05{width:25.875vw}@media only screen and (min-width:769px){.header--nav__link.l05{width:7.5vw}}@media only screen and (min-width:1400px){.header--nav__link.l05{width:105px}}.header--nav__link.l06{width:19.5vw}@media only screen and (min-width:769px){.header--nav__link.l06{width:5.4285714286vw}}@media only screen and (min-width:1400px){.header--nav__link.l06{width:76px}}.header--nav__link.l07{width:42.625vw}@media only screen and (min-width:769px){.header--nav__link.l07{width:12vw}}@media only screen and (min-width:1400px){.header--nav__link.l07{width:168px}}.header--nav__language{display:none}@media only screen and (min-width:769px){.header--nav__language{display:flex;align-items:center;justify-content:flex-end;margin-bottom:1.4285714286vw}}@media only screen and (min-width:1400px){.header--nav__language{margin-bottom:20px}}@media only screen and (min-width:769px){.header--nav__language__text{font-size:1vw;font-weight:800}}@media only screen and (min-width:1400px){.header--nav__language__text{font-size:14px}}@media only screen and (min-width:769px){.header--nav__language__link{display:flex;align-items:center;justify-content:center;position:relative;background-color:#ba5eea;width:13.5714285714vw;height:2.1428571429vw;border-radius:1000px}}.header--nav__language__link:hover{background-color:#ff699d}.header--nav__language__link:hover:before{background-color:#ff699d}.header--nav__language__link:hover .header--nav__language__linkText svg{fill:#fff;fill-rule:evenodd}@media only screen and (min-width:1400px){.header--nav__language__link{width:190px;height:30px}}@media only screen and (min-width:769px){.header--nav__language__btn{position:relative;width:13.5714285714vw;height:2.1428571429vw;display:flex;align-items:center;justify-content:center;margin-left:1.0714285714vw}}@media only screen and (min-width:1400px){.header--nav__language__btn{width:190px;height:30px;margin-left:15px}}@media only screen and (min-width:769px){.header--nav__language__btn:after{display:block;content:"";background-image:url(/vendor/maimai/img/header/btn_arrow.svg);background-size:contain;background-repeat:no-repeat;position:absolute;width:1vw;height:1vw;right:.7142857143vw}}@media only screen and (min-width:1400px){.header--nav__language__btn:after{width:15px;height:15px;right:10px}}@media only screen and (min-width:769px){.header--nav__language__btn:before{position:absolute;display:block;content:"";background-color:#ba5eea;width:.7142857143vw;height:.7142857143vw;right:.8571428571vw;border-radius:100px}}@media only screen and (min-width:1400px){.header--nav__language__btn:before{width:10px;height:10px;right:12px}}@media only screen and (min-width:769px){.header--nav__language__linkText{width:9.7857142857vw;margin-left:-.4285714286vw}}@media only screen and (min-width:1400px){.header--nav__language__linkText{width:137px;margin-left:-6px}}.header--nav__language__linkText svg{fill:#fff;fill-rule:evenodd}.song .header--btn{top:17.5vw}.footer--share{width:87.5vw;border-radius:12.5vw;background-color:#fff;margin:17.5vw auto 0;box-shadow:1.25vw 1.25vw 0 rgba(0,0,0,.2);position:relative;padding:4vw 0 5vw}@media only screen and (min-width:769px){.footer--share{box-shadow:.7142857143vw .7142857143vw 0 rgba(0,0,0,.2)}}@media only screen and (min-width:1400px){.footer--share{box-shadow:10px 10px 0 rgba(0,0,0,.2)}}@media only screen and (min-width:769px){.footer--share{width:54.5454545455vw;border-radius:9.0909090909vw;margin:7.2727272727vw auto 0;padding:1.8181818182vw 0 2.7272727273vw}}@media only screen and (min-width:1100px){.footer--share{width:600px;border-radius:100px;margin:80px auto 0;padding:20px 0 30px}}.footer--share:after,.footer--share:before{position:absolute;display:block;content:"";width:50vw;height:1.5vw;left:50%;transform:translateX(-50%)}@media only screen and (min-width:769px){.footer--share:after,.footer--share:before{width:31.8181818182vw;height:.7272727273vw}}@media only screen and (min-width:1100px){.footer--share:after,.footer--share:before{width:350px;height:8px}}.footer--share:before{top:0;border-radius:0 0 1.5vw 1.5vw;background-color:#ba5eea}@media only screen and (min-width:769px){.footer--share:before{border-radius:0 0 .7272727273vw .7272727273vw}}@media only screen and (min-width:1100px){.footer--share:before{border-radius:0 0 8px 8px}}.footer--share:after{bottom:0;border-radius:1.5vw 1.5vw 0 0;background-color:#ffcedf}@media only screen and (min-width:769px){.footer--share:after{border-radius:.7272727273vw .7272727273vw 0 0}}@media only screen and (min-width:1400px){.footer--share:after{border-radius:8px 8px 0 0}}.footer--share__header{width:18.25vw;margin:0 auto}@media only screen and (min-width:769px){.footer--share__header{width:11.8181818182vw}}@media only screen and (min-width:1400px){.footer--share__header{width:130px}}.footer--share__header svg{width:100%;fill:#0b3871;fill-rule:evenodd}.footer--sns{display:flex;justify-content:center;align-items:center;margin-top:2.5vw}@media only screen and (min-width:769px){.footer--sns{margin-top:.9090909091vw}}@media only screen and (min-width:1100px){.footer--sns{margin-top:10px}}.footer--sns__link{display:block;width:10.125vw}@media only screen and (min-width:769px){.footer--sns__link{width:6.3636363636vw;transition:.3s}.footer--sns__link:hover{opacity:.7}}@media only screen and (min-width:1100px){.footer--sns__link{width:70px}}.footer--sns__item:nth-child(2){margin:0 2.5vw}@media only screen and (min-width:769px){.footer--sns__item:nth-child(2){margin:0 1.8181818182vw}}@media only screen and (min-width:1100px){.footer--sns__item:nth-child(2){margin:0 20px}}.footer--bottom{background-color:#fff;width:100%;height:106.5vw;margin-top:10vw;padding:18.75vw 6.25vw;color:#0b3871;position:relative}@media only screen and (min-width:769px){.footer--bottom{height:25vw;margin-top:6.4285714286vw;padding:5.9285714286vw 0 0}}@media only screen and (min-width:1400px){.footer--bottom{height:350px;margin-top:90px;padding:83px 0 0}}.footer--bottom:before{content:"";display:block;background-color:#fff;box-shadow:0 -1.25vw 1.25vw -.5vw rgba(28,112,176,.7);height:2.5vw;width:100%;opacity:.5;position:absolute;top:-2.5vw;left:0;right:0}@media only screen and (min-width:769px){.footer--bottom:before{box-shadow:0 -.2857142857vw .3571428571vw -.1428571429vw rgba(28,112,176,.8);height:.7142857143vw;top:-.7142857143vw}}@media only screen and (min-width:1400px){.footer--bottom:before{box-shadow:0 -4px 5px -2px rgba(28,112,176,.8);height:10px;top:-10px}}@media only screen and (min-width:769px){.footer--bottom__bnr{transition:transform .3s}.footer--bottom__bnr:hover{transform:translateY(-5%)}}@media only screen and (min-width:769px){.footer--bottom__bnr{display:block;width:35.7142857143vw;margin:0 auto}}@media only screen and (min-width:1400px){.footer--bottom__bnr{width:500px}}.footer--bottom__wrapper{margin-top:13.625vw}@media only screen and (min-width:769px){.footer--bottom__wrapper{width:70vw;margin:3.8571428571vw auto 0;display:flex;justify-content:space-between;align-items:flex-start}}@media only screen and (min-width:1400px){.footer--bottom__wrapper{margin:54px auto 0;width:980px}}.footer--bottom__text{font-size:2.75vw}@media only screen and (min-width:769px){.footer--bottom__text{font-size:.7857142857vw}}@media only screen and (min-width:1400px){.footer--bottom__text{font-size:11px}}.footer--bottom__logo{width:31.25vw;margin:7.5vw auto;display:block}@media only screen and (min-width:769px){.footer--bottom__logo{width:8vw;margin:0;transition:.3s}.footer--bottom__logo:hover{opacity:.7}}@media only screen and (min-width:1400px){.footer--bottom__logo{width:112px}}.top--kv{width:100vw;margin:0 auto 1.5vw;position:relative;height:143.875vw;z-index:2;overflow:hidden}@media only screen and (max-width:769px){.top--kv{margin-left:0;margin-right:0;padding-top:3.75vw}}@media only screen and (min-width:769px){.top--kv{margin:0 auto;height:auto;margin-top:-.8571428571vw;padding-left:.7142857143vw;padding-right:11.5vw;width:68.4285714286vw;padding-bottom:9.3571428571vw;margin-bottom:-.8571428571vw}}@media only screen and (min-width:1400px){.top--kv{margin-top:-12px;padding-left:10px;padding-right:161px;width:958px;padding-bottom:131px;margin-bottom:-12px}}.top--kv__logo{position:absolute;bottom:0vw;left:calc(50% - 50vw);width:100vw}@media only screen and (min-width:769px){.top--kv__logo{width:49.2857142857vw;bottom:0;left:calc(50% - 24.1428571429vw)}}@media only screen and (min-width:1400px){.top--kv__logo{width:690px;left:calc(50% - 338px)}}.top--link{display:flex;width:91.25vw;flex-wrap:wrap;margin:-2.5vw auto 0;justify-content:space-between;align-items:flex-end;position:relative;z-index:100}@media only screen and (min-width:769px){.top--link{width:78.5714285714vw;margin-top:0}}@media only screen and (min-width:1400px){.top--link{width:1100px}}@media only screen and (max-width:769px){.top--link__item:nth-child(n+3){margin-top:2.5vw}}.top--link__link{display:block;width:45vw;font-size:0}@media only screen and (min-width:769px){.top--link__link{transition:transform .3s}.top--link__link:hover{transform:translateY(-5%)}}@media only screen and (min-width:769px){.top--link__link{width:18.8571428571vw}}@media only screen and (min-width:1400px){.top--link__link{width:264px}}.top--info{margin-top:6.25vw}@media only screen and (min-width:769px){.top--info{margin-top:4.2857142857vw}}@media only screen and (min-width:1400px){.top--info{margin-top:60px}}.top--info__header--txt{width:50vw;height:3.75vw}@media only screen and (min-width:769px){.top--info__header--txt{width:32.7272727273vw;height:2.5454545455vw}}@media only screen and (min-width:1100px){.top--info__header--txt{width:360px;height:28px}}.top--info__wrapper{position:relative}.top--info__container{width:75vw;margin-top:6.25vw}@media only screen and (min-width:769px){.top--info__container{width:63.6363636364vw;margin-top:3.6363636364vw}}@media only screen and (min-width:1100px){.top--info__container{width:700px;margin-top:40px}}.top--info__arrow{width:8.75vw;height:8.75vw;background-color:#ff5668;border-radius:12.5vw;transition:.3s}@media only screen and (min-width:769px){.top--info__arrow{width:4.5454545455vw;height:4.5454545455vw}.top--info__arrow:hover{background-color:#ccc}}@media only screen and (min-width:1100px){.top--info__arrow{width:50px;height:50px}}.top--info__arrow.prev{transform:scale(-1,1);left:1.25vw}@media only screen and (min-width:769px){.top--info__arrow.prev{left:2.7272727273vw}}@media only screen and (min-width:1100px){.top--info__arrow.prev{left:30px}}.top--info__arrow.next{right:1.25vw}@media only screen and (min-width:769px){.top--info__arrow.next{right:2.7272727273vw}}@media only screen and (min-width:1400px){.top--info__arrow.next{right:30px}}.top--info__arrow:after{display:block;content:"";background-image:url(/vendor/maimai/img/common/arrow.svg);background-size:cover;width:6.25vw;height:6.25vw}@media only screen and (min-width:769px){.top--info__arrow:after{width:3.6363636364vw;height:3.6363636364vw}}@media only screen and (min-width:1100px){.top--info__arrow:after{width:40px;height:40px}}.top--info__pagination{position:relative;display:flex;justify-content:center;margin-top:5vw}@media only screen and (min-width:769px){.top--info__pagination{margin-top:3.6363636364vw}}@media only screen and (min-width:1100px){.top--info__pagination{margin-top:40px}}.top--info__pagination .swiper-pagination-bullet{background-color:#ccc;opacity:1;width:2vw;height:2vw;margin:0 .625vw}@media only screen and (min-width:769px){.top--info__pagination .swiper-pagination-bullet{width:1.4545454545vw;height:1.4545454545vw;margin:0 .4545454545vw}}@media only screen and (min-width:1100px){.top--info__pagination .swiper-pagination-bullet{width:16px;height:16px;margin:0 5px}}.top--info__pagination .swiper-pagination-bullet-active{background-color:#e72139}.top--info__btn{margin-top:5vw}@media only screen and (max-width:769px){.top--info__btn{width:57.5vw}}@media only screen and (min-width:769px){.top--info__btn{margin-top:3.6363636364vw}}@media only screen and (min-width:1100px){.top--info__btn{margin-top:40px}}.top--info__btn.blank:after{right:4.375vw}@media only screen and (min-width:769px){.top--info__btn.blank:after{right:2.7272727273vw}}@media only screen and (min-width:1100px){.top--info__btn.blank:after{right:30px}}.top--info__btn.customer{justify-content:flex-start;padding-left:2.875vw}@media only screen and (min-width:769px){.top--info__btn.customer{padding-left:2.9090909091vw}}@media only screen and (min-width:1100px){.top--info__btn.customer{padding-left:32px}}.top--update{margin-top:6.25vw}@media only screen and (min-width:769px){.top--update{margin-top:4.2857142857vw}}@media only screen and (min-width:1400px){.top--update{margin-top:60px}}.top--update__header--txt{width:40.625vw;height:4.375vw}@media only screen and (min-width:769px){.top--update__header--txt{width:26.1818181818vw;height:2.5454545455vw}}@media only screen and (min-width:1100px){.top--update__header--txt{width:288px;height:28px}}.top--update__box{width:75vw;height:65vw;margin:8.75vw auto 0;overflow:scroll;padding-right:2.5vw}@media only screen and (min-width:769px){.top--update__box{width:56.3636363636vw;height:17.2727272727vw;margin:3.6363636364vw auto 0;overflow:scroll;padding-right:1.8181818182vw}}@media only screen and (min-width:1100px){.top--update__box{width:620px;height:190px;margin:40px auto 0;overflow:scroll;padding-right:20px}}.top--update__box::-webkit-scrollbar{width:2.5vw}@media only screen and (min-width:769px){.top--update__box::-webkit-scrollbar{width:.9090909091vw}}@media only screen and (min-width:1400px){.top--update__box::-webkit-scrollbar{width:10px}}.top--update__box::-webkit-scrollbar-track{background:0 0}.top--update__box::-webkit-scrollbar-thumb{background:#e72139;border-radius:100px}.top--update__item{width:100%;padding-bottom:5vw;border-bottom:1px solid #000}@media only screen and (min-width:769px){.top--update__item{display:flex;padding-bottom:1.8181818182vw}}@media only screen and (min-width:1400px){.top--update__item{padding-bottom:20px}}.top--update__item:last-child{border:none}.top--update__item:not(:first-child){margin-top:3.75vw}@media only screen and (min-width:769px){.top--update__item:not(:first-child){margin-top:2.7272727273vw}}@media only screen and (min-width:1400px){.top--update__item:not(:first-child){margin-top:30px}}.top--update__time{color:#e72139;font-size:3.5vw;font-weight:900}@media only screen and (min-width:769px){.top--update__time{font-size:1.4545454545vw;margin-top:.4545454545vw}}@media only screen and (min-width:1100px){.top--update__time{font-size:16px;margin-top:5px}}.top--update__text{font-size:3vw;margin-top:1.875vw;line-height:1.5;color:#0b3871;font-weight:500}@media only screen and (min-width:769px){.top--update__text{font-size:1.2727272727vw;margin-left:2.7272727273vw;margin-top:0}}@media only screen and (min-width:1100px){.top--update__text{font-size:14px;margin-left:30px}}.top--update__text>a{text-decoration:underline;color:#e72139}.top--update__text>a:hover{text-decoration:none}.top--fb{margin-top:6.25vw}@media only screen and (min-width:769px){.top--fb{margin-top:4.2857142857vw}}@media only screen and (min-width:1400px){.top--fb{margin-top:60px}}.top--fb__header--txt{width:25.5vw;height:4.375vw}@media only screen and (min-width:769px){.top--fb__header--txt{width:16.3636363636vw;height:2.5454545455vw}}@media only screen and (min-width:1100px){.top--fb__header--txt{width:180px;height:28px}}.top--fb__page{margin:6.25vw auto 0;width:62.5vw;max-width:340px}@media only screen and (min-width:769px){.top--fb__page{margin-top:4.5454545455vw;width:45.4545454545vw;max-width:500px}}@media only screen and (min-width:1100px){.top--fb__page{margin-top:50px}}.top--chu{display:block;width:78.75vw;margin:10vw auto 0;padding-left:1.25vw;font-size:0}@media only screen and (min-width:769px){.top--chu{transition:transform .3s}.top--chu:hover{transform:translateY(-5%)}}@media only screen and (min-width:769px){.top--chu{width:23.4285714286vw;margin:5.7142857143vw auto 0;padding-left:.7142857143vw}}@media only screen and (min-width:1400px){.top--chu{width:328px;margin:80px auto 0;padding-left:10px}}.play--nav{display:flex;justify-content:center;flex-wrap:wrap;width:88.75vw;margin:5vw auto 0}@media only screen and (min-width:769px){.play--nav{width:64.2857142857vw;margin:3.5714285714vw auto 0}}@media only screen and (min-width:1400px){.play--nav{width:915px;margin:50px auto 0}}.play--nav__link{display:block;box-shadow:.625vw .625vw 0 rgba(0,0,0,.2);width:42.75vw;height:10.625vw;border-radius:5.25vw;background-color:#4b4b4b;display:flex;align-items:center;justify-content:center}.play--nav__link.on{background-color:#ba5eea}@media only screen and (min-width:769px){.play--nav__link{transition:.3s;width:20.8571428571vw;height:4.2857142857vw;border-radius:3vw;box-shadow:.3571428571vw .3571428571vw 0 rgba(0,0,0,.2)}.play--nav__link:hover{background-color:#ba5eea}}@media only screen and (min-width:1400px){.play--nav__link{width:292px;height:60px;border-radius:42px;box-shadow:5px 5px 0 rgba(0,0,0,.2)}}@media only screen and (max-width:768px){.play--nav__item:nth-child(n+3){margin-top:2vw}.play--nav__item:nth-of-type(n+2){margin-left:3.25vw}.play--nav__item:nth-of-type(2n+1){margin-left:0}}@media only screen and (min-width:769px){.play--nav__item:nth-of-type(n+2){margin-left:.8571428571vw}.play--nav__item:nth-child(n+4){margin-top:.8571428571vw}.play--nav__item:nth-of-type(4n+0){margin-left:0}}@media only screen and (min-width:1400px){.play--nav__item:nth-of-type(n+3){margin-left:12px}.play--nav__item:nth-child(n+4){margin-top:12px}.play--nav__item:nth-of-type(4n+0){margin-left:0}}.play--nav__text.l1{width:22.25vw;margin-bottom:.625vw}@media only screen and (min-width:769px){.play--nav__text.l1{width:10.3571428571vw;margin-bottom:0}}@media only screen and (min-width:1400px){.play--nav__text.l1{width:145px}}.play--nav__text.l2{width:26.125vw}@media only screen and (min-width:769px){.play--nav__text.l2{width:12.1428571429vw;margin-top:.2857142857vw}}@media only screen and (min-width:1400px){.play--nav__text.l2{width:170px;margin-top:4px}}.play--nav__text.l3{width:31vw;margin-top:.5vw}@media only screen and (min-width:769px){.play--nav__text.l3{width:13.2857142857vw;margin-top:.1428571429vw}}@media only screen and (min-width:1400px){.play--nav__text.l3{width:186px;margin-top:2px}}.play--nav__text.l4{width:30.25vw;margin-bottom:.2857142857vw}@media only screen and (min-width:769px){.play--nav__text.l4{width:13.1428571429vw;margin-bottom:0}}@media only screen and (min-width:1400px){.play--nav__text.l4{width:184px}}.play--nav__text.l5{width:30.875vw;margin-bottom:.2857142857vw}@media only screen and (min-width:769px){.play--nav__text.l5{width:13.2857142857vw;margin-bottom:0}}@media only screen and (min-width:1400px){.play--nav__text.l5{width:186px}}.play--nav__text.l6{width:25vw}@media only screen and (min-width:769px){.play--nav__text.l6{width:10.9285714286vw}}@media only screen and (min-width:1400px){.play--nav__text.l6{width:158px}}.play--text{font-size:4.75vw;text-align:center;color:#e72139;font-weight:900;margin-top:12.5vw;line-height:1.5}@media only screen and (min-width:769px){.play--text{font-size:2.5454545455vw;margin-top:4.5454545455vw}}@media only screen and (min-width:1100px){.play--text{font-size:28px;margin-top:50px}}.play--text:after{display:block;content:"";width:5vw;height:1vw;margin:4vw auto 0;background-color:#bebebe;border-radius:100px}@media only screen and (min-width:769px){.play--text:after{width:3.6363636364vw;height:.7272727273vw;margin:3.6363636364vw auto 0}}@media only screen and (min-width:1100px){.play--text:after{width:40px;height:8px;margin:40px auto 0}}.play--text.first{margin-top:5vw}@media only screen and (min-width:769px){.play--text.first{margin-top:4.5454545455vw}}@media only screen and (min-width:1100px){.play--text.first{margin-top:50px}}.play--text.second{margin-top:12vw}@media only screen and (min-width:769px){.play--text.second{margin-top:4.5454545455vw}}@media only screen and (min-width:1100px){.play--text.second{margin-top:50px}}.play--text.thrd{margin-top:10vw}@media only screen and (min-width:769px){.play--text.thrd{margin-top:4.5454545455vw}}@media only screen and (min-width:1100px){.play--text.thrd{margin-top:50px}}.play--text.thrd.chinese{font-family:ヒラギノ角ゴ Pro W3,Hiragino Kaku Gothic Pro,メイリオ,Meiryo,sans-serif;font-weight:700}.play--about{margin-top:6.25vw}@media only screen and (min-width:769px){.play--about{margin-top:4.5454545455vw}}@media only screen and (min-width:1100px){.play--about{margin-top:50px}}.play--about__header .common--header__txt{width:36.25vw;height:4vw}@media only screen and (min-width:769px){.play--about__header .common--header__txt{width:23.2727272727vw;height:2.5454545455vw}}@media only screen and (min-width:1100px){.play--about__header .common--header__txt{width:256px;height:28px}}.play--about__img.i1{width:100%}.play--about__img.i2{width:81.875vw;margin:10.75vw auto 0}@media only screen and (min-width:769px){.play--about__img.i2{width:69.4545454545vw;margin:5.4545454545vw auto 0}}@media only screen and (min-width:1100px){.play--about__img.i2{width:764px;margin:60px auto 0}}.play--about__img.i3{width:84vw}@media only screen and (min-width:769px){.play--about__img.i3{width:72.4545454545vw;margin:3.6363636364vw auto}}@media only screen and (min-width:1100px){.play--about__img.i3{width:797px;margin:40px auto}}.play--about__img.i4{width:82.125vw;margin-left:2.25vw}@media only screen and (min-width:769px){.play--about__img.i4{width:76.8181818182vw;margin:3.6363636364vw auto}}@media only screen and (min-width:1100px){.play--about__img.i4{width:845px;margin:40px auto}}.play--about__border{width:74.375vw;height:.75vw;background-image:url(/vendor/maimai/img/play/dash.png);background-repeat:repeat-x;background-size:3vw auto;margin:12.5vw auto 0}@media only screen and (min-width:769px){.play--about__border{width:74.9090909091vw;height:.6363636364vw;margin:6.3636363636vw auto 0;background-size:2.1818181818vw auto}}@media only screen and (min-width:1100px){.play--about__border{width:824px;height:7px;margin:70px auto 0;background-size:24px auto}}.play--about__btn{margin-top:5vw;width:75vw}@media only screen and (min-width:769px){.play--about__btn{margin-top:4.0909090909vw;width:47.2727272727vw}}@media only screen and (min-width:1100px){.play--about__btn{margin-top:45px;width:520px}}.play--about__btn.bt1{height:15vw;margin-top:-11.5vw}@media only screen and (min-width:769px){.play--about__btn.bt1{margin-top:-12.7272727273vw;margin-bottom:4.5454545455vw;margin-left:2.7272727273vw;width:54.5454545455vw;height:5.4545454545vw}}@media only screen and (min-width:1100px){.play--about__btn.bt1{margin-top:-140px;margin-bottom:50px;margin-left:30px;width:600px;height:60px}}.play--about__btn.bt1>span{width:57.875vw;margin-left:-5vw}@media only screen and (min-width:769px){.play--about__btn.bt1>span{width:45.0909090909vw;margin-left:-1.8181818182vw}}@media only screen and (min-width:1100px){.play--about__btn.bt1>span{width:496px;margin-left:-20px}}.play--about__btn.bt2{margin-top:5vw}@media only screen and (max-width:769px){.play--about__btn.bt2.next::after{right:2.25vw}}@media only screen and (min-width:769px){.play--about__btn.bt2{margin-top:0;margin-bottom:10vw}}@media only screen and (min-width:1100px){.play--about__btn.bt2{margin-bottom:110px}}.play--howto{margin-top:6.25vw}@media only screen and (min-width:769px){.play--howto{margin-top:4.5454545455vw}}@media only screen and (min-width:1100px){.play--howto{margin-top:50px}}.play--howto__header .common--header__txt{width:42.25vw;height:4.5vw}@media only screen and (min-width:769px){.play--howto__header .common--header__txt{width:27.5454545455vw;height:3vw}}@media only screen and (min-width:1100px){.play--howto__header .common--header__txt{width:303px;height:33px}}.play--howto__img.i1{width:87.5vw;margin:6.25vw auto 0}@media only screen and (min-width:769px){.play--howto__img.i1{width:73.8181818182vw;margin:3.6363636364vw auto 0}}@media only screen and (min-width:1100px){.play--howto__img.i1{width:812px;margin-top:40px}}.play--howto__img.i2{width:82.5vw;margin:12.5vw auto 0 3.5vw}@media only screen and (min-width:769px){.play--howto__img.i2{width:66.5454545455vw;margin:10.3636363636vw auto 0}}@media only screen and (min-width:1100px){.play--howto__img.i2{width:732px;margin:114px auto 0}}.play--howto__border{width:74.375vw;height:.75vw;background-image:url(/vendor/maimai/img/play/dash.png);background-repeat:repeat-x;margin:12.5vw auto 0}@media only screen and (min-width:769px){.play--howto__border{width:74.9090909091vw;height:.6363636364vw;margin:6.3636363636vw auto 0}}@media only screen and (min-width:1100px){.play--howto__border{width:824px;height:7px;margin:70px auto 0}}.play--function{margin-top:6.25vw;padding-top:9.625vw;padding-bottom:12.5vw}@media only screen and (min-width:769px){.play--function{margin-top:4.5454545455vw;padding-top:6.1818181818vw;padding-bottom:9.0909090909vw}}@media only screen and (min-width:1100px){.play--function{margin-top:50px;padding-top:68px;padding-bottom:100px}}.play--function__header{position:relative}@media only screen and (max-width:768px){.play--function__header .common--header__icon{position:absolute;top:3.75vw;left:calc(50% - 40.875vw)}.play--function__header .common--header__icon.right{left:calc(50% + 27.125vw)}}.play--function__header .common--header__txt{width:55.25vw;height:11.875vw;margin:0 2vw}@media only screen and (min-width:769px){.play--function__header .common--header__txt{width:39.9090909091vw;height:7.7272727273vw;margin:0 1vw}}@media only screen and (min-width:1100px){.play--function__header .common--header__txt{width:439px;height:85px;margin:0 11px}}.play--function__header .common--header__txt svg{height:100%}.play--function__subheader{background-color:#ba5eea;font-size:4.75vw;text-align:center;color:#fff;font-weight:800;margin-top:8.75vw;margin-bottom:8.75vw;margin-inline:auto;line-height:1.3421052632;display:flex;align-items:center;justify-content:center;width:81.25vw;height:19.375vw;border-radius:9.375vw}@media only screen and (min-width:769px){.play--function__subheader{font-size:2.5454545455vw;margin-top:6.3636363636vw;margin-bottom:6.3636363636vw;line-height:1.2857142857;width:63.6363636364vw;height:9.0909090909vw;border-radius:4.5454545455vw}}@media only screen and (min-width:1100px){.play--function__subheader{font-size:28px;margin-top:70px;margin-bottom:70px;width:700px;height:100px;border-radius:50px}}@media only screen and (min-width:769px){.play--function .play--text{font-size:2vw;margin-top:6.3636363636vw}}@media only screen and (min-width:1100px){.play--function .play--text{font-size:22px;margin-top:70px}}.play--function__img.i1{width:100%;margin:2.5vw auto 0}@media only screen and (min-width:769px){.play--function__img.i1{margin:4.0909090909vw auto 0}}@media only screen and (min-width:1100px){.play--function__img.i1{margin:45px auto 0}}.play--function__img.i2{width:100%;margin:5vw auto 0}@media only screen and (min-width:769px){.play--function__img.i2{margin:5.4545454545vw auto 0}}@media only screen and (min-width:1100px){.play--function__img.i2{margin:60px auto 0}}.play--function__border{width:74.375vw;height:.75vw;background-image:url(/vendor/maimai/img/play/dash.png);background-repeat:repeat-x;margin:12.5vw auto 0}@media only screen and (min-width:769px){.play--function__border{width:74.9090909091vw;height:.6363636364vw;margin:6.3636363636vw auto 0}}@media only screen and (min-width:1100px){.play--function__border{width:824px;height:7px;margin:70px auto 0}}.play--function__btn{width:80.5vw;height:15.5vw;box-shadow:none}@media only screen and (min-width:769px){.play--function__btn{width:59.0909090909vw;height:7.2727272727vw;transition:opacity .3s}.play--function__btn:hover{transform:none;opacity:80%}}@media only screen and (min-width:1100px){.play--function__btn{width:650px;height:80px}}.play--function__btn span{display:inline-block;width:80%;text-align:center;line-height:1.2;word-break:keep-all}@media only screen and (min-width:769px){.play--function__btn span{width:100%}}.play--function__btn.blank:after{width:5.75vw;height:4.25vw;right:5.75vw}@media only screen and (min-width:769px){.play--function__btn.blank:after{width:2.7272727273vw;height:2vw;right:2.6363636364vw}}@media only screen and (min-width:1100px){.play--function__btn.blank:after{width:30px;height:22px;right:29px}}.play--function__btn.btn-pink{background-color:#e84472}.play--function__btn.btn-yellow{background-color:#ff699d;margin-top:7.75vw}@media only screen and (min-width:769px){.play--function__btn.btn-yellow{margin-top:5.4545454545vw}}@media only screen and (min-width:1100px){.play--function__btn.btn-yellow{margin-top:60px}}.play--function2{margin-top:6.25vw}@media only screen and (min-width:769px){.play--function2{margin-top:4.5454545455vw}}@media only screen and (min-width:1100px){.play--function2{margin-top:50px}}.play--function2__header .common--header__txt{width:45.5vw;height:4vw}@media only screen and (min-width:769px){.play--function2__header .common--header__txt{width:29.5454545455vw;height:2.7272727273vw}}@media only screen and (min-width:1100px){.play--function2__header .common--header__txt{width:325px;height:30px}}.play--function2__img.i1{width:100%;margin:2.5vw auto 0}@media only screen and (min-width:769px){.play--function2__img.i1{margin:4.0909090909vw auto 0}}@media only screen and (min-width:1100px){.play--function2__img.i1{margin:45px auto 0}}.play--function2__img.i2{width:81vw;margin:5vw auto 0 3.375vw}@media only screen and (min-width:769px){.play--function2__img.i2{width:73.2727272727vw;margin:3.6363636364vw auto 0 3.2727272727vw}}@media only screen and (min-width:1100px){.play--function2__img.i2{width:806px;margin:40px auto 0 36px}}.play--function2__img.i3{width:86.375vw;margin:5vw auto 0 1vw}@media only screen and (min-width:769px){.play--function2__img.i3{width:77.2727272727vw;margin:3.6363636364vw auto 0 4.0909090909vw}}@media only screen and (min-width:1100px){.play--function2__img.i3{width:850px;margin-top:40px;margin-left:45px}}.play--function2__img.i4{width:82vw;margin:5vw auto 0 2.75vw}@media only screen and (min-width:769px){.play--function2__img.i4{width:72.3636363636vw;margin:3.6363636364vw auto 0 4.5454545455vw}}@media only screen and (min-width:1100px){.play--function2__img.i4{width:796px;margin:40px auto 0 50px}}.play--function2__img.i5{width:100%;margin:2.5vw auto 3.75vw 0}@media only screen and (min-width:769px){.play--function2__img.i5{margin:3.6363636364vw auto 4.0909090909vw 0}}@media only screen and (min-width:1100px){.play--function2__img.i5{margin:40px auto 45px 0}}.play--function2__img.i6{width:100%;margin:2.5vw auto 0}@media only screen and (min-width:769px){.play--function2__img.i6{margin:3.6363636364vw auto 0}}@media only screen and (min-width:1100px){.play--function2__img.i6{margin:40px auto 0}}.play--function2__img.i7{width:87.5vw;margin:5vw auto 6.25vw 0}@media only screen and (min-width:769px){.play--function2__img.i7{width:81.8181818182vw;margin:3.6363636364vw auto 4.5454545455vw 0}}@media only screen and (min-width:1100px){.play--function2__img.i7{width:900px;margin:40px auto 50px 0}}.play--function2__img.i8{width:87.5vw;margin:5vw auto 0}@media only screen and (min-width:769px){.play--function2__img.i8{width:81.8181818182vw;margin:0 auto}}@media only screen and (min-width:1100px){.play--function2__img.i8{width:900px;margin:0 auto}}.play--function2__img.i9{width:100%;margin-top:3.5vw}@media only screen and (min-width:769px){.play--function2__img.i9{margin-top:4vw;margin-bottom:5.8181818182vw}}@media only screen and (min-width:1100px){.play--function2__img.i9{margin-top:44px;margin-bottom:64px}}.play--function2__border{width:74.375vw;height:.75vw;background-image:url(/vendor/maimai/img/play/dash.png);background-repeat:repeat-x;margin:12.5vw auto 0}@media only screen and (min-width:769px){.play--function2__border{width:74.9090909091vw;height:.6363636364vw;margin:6.3636363636vw auto 0}}@media only screen and (min-width:1100px){.play--function2__border{width:824px;height:7px;margin:70px auto 0}}.play--function3{margin-top:6.25vw}@media only screen and (min-width:769px){.play--function3{margin-top:4.5454545455vw}}@media only screen and (min-width:1100px){.play--function3{margin-top:50px}}.play--function3__header .common--header__txt{width:46.375vw;height:4vw}@media only screen and (min-width:769px){.play--function3__header .common--header__txt{width:29.8181818182vw;height:2.7272727273vw}}@media only screen and (min-width:1100px){.play--function3__header .common--header__txt{width:328px;height:30px}}.play--function3__img.i1{width:100%;margin:3vw auto 12.5vw 0}@media only screen and (min-width:769px){.play--function3__img.i1{margin:4.0909090909vw auto 5.0909090909vw 0}}@media only screen and (min-width:1100px){.play--function3__img.i1{margin:45px 0 56px 0}}.play--function3__img.i2{width:100%;margin:1.25vw auto 0}@media only screen and (min-width:769px){.play--function3__img.i2{margin:0 auto}}.play--function3__img.i3{width:100%}.play--function3__img.i4{width:86.25vw;margin:5vw auto 0 1.25vw}@media only screen and (min-width:769px){.play--function3__img.i4{width:81.8181818182vw;margin:3.6363636364vw auto 0}}@media only screen and (min-width:1100px){.play--function3__img.i4{width:900px;margin:40px auto 0}}.play--function3__img.i5{width:82.25vw;margin:5vw auto 0 2.625vw}@media only screen and (min-width:769px){.play--function3__img.i5{width:81.8181818182vw;margin:3.6363636364vw auto 0}}@media only screen and (min-width:1100px){.play--function3__img.i5{width:900px;margin:40px auto 0}}.play--function3__img.i6{width:82vw;margin:5vw auto 0 2.75vw}@media only screen and (min-width:769px){.play--function3__img.i6{width:81.8181818182vw;margin:3.6363636364vw auto 0}}@media only screen and (min-width:1100px){.play--function3__img.i6{width:900px;margin:40px auto 0}}.play--function3__img.i7{width:81.875vw;margin:5vw auto 7.5vw}@media only screen and (min-width:769px){.play--function3__img.i7{width:81.8181818182vw;margin:3.6363636364vw auto 5.0909090909vw}}@media only screen and (min-width:1100px){.play--function3__img.i7{margin:40px auto 56px;width:900px}}.play--function3__border{width:74.375vw;height:.75vw;background-image:url(/vendor/maimai/img/play/dash.png);background-repeat:repeat-x;margin:12.5vw auto 0}@media only screen and (min-width:769px){.play--function3__border{width:74.9090909091vw;height:.6363636364vw;margin:6.3636363636vw auto 0}}@media only screen and (min-width:1100px){.play--function3__border{width:824px;height:7px;margin:70px auto 0}}.play--function4{margin-top:6.25vw}@media only screen and (min-width:769px){.play--function4{margin-top:4.5454545455vw}}@media only screen and (min-width:1100px){.play--function4{margin-top:50px}}.play--function4__header span{width:68.875vw;height:10.5vw}@media only screen and (min-width:769px){.play--function4__header span{width:44.3636363636vw;height:6.7272727273vw}}@media only screen and (min-width:1100px){.play--function4__header span{width:488px;height:74px}}.play--function4__img.i1{width:87.5vw;margin:5vw auto 0 0vw}@media only screen and (min-width:769px){.play--function4__img.i1{width:81.8181818182vw;margin:3.6363636364vw auto 0 0vw}}@media only screen and (min-width:1100px){.play--function4__img.i1{width:900px;margin-top:40px;margin-left:0}}.play--function4__img.i2{width:81.875vw;margin:5vw auto 0 2.75vw}@media only screen and (min-width:769px){.play--function4__img.i2{width:81.8181818182vw;margin:3.6363636364vw auto 0 0vw}}@media only screen and (min-width:1100px){.play--function4__img.i2{width:900px;margin:40px auto 0 0}}.play--function4__border{width:74.375vw;height:.75vw;background-image:url(/vendor/maimai/img/play/dash.png);background-repeat:repeat-x;margin:12.5vw auto 0}@media only screen and (min-width:769px){.play--function4__border{width:74.9090909091vw;height:.6363636364vw;margin:6.3636363636vw auto 0}}@media only screen and (min-width:1100px){.play--function4__border{width:824px;height:7px;margin:70px auto 0}}.play--terms{margin-top:7.5vw;padding-bottom:8.75vw}@media only screen and (min-width:769px){.play--terms{margin-top:4.5454545455vw;padding-bottom:8.75vw}}@media only screen and (min-width:1100px){.play--terms{margin-top:50px;padding-bottom:70px}}.play--terms__header .common--header__txt{width:46.375vw;height:4vw}@media only screen and (min-width:769px){.play--terms__header .common--header__txt{width:27.0909090909vw;height:2.7272727273vw}}@media only screen and (min-width:1100px){.play--terms__header .common--header__txt{width:298px;height:30px}}.play--terms__text{font-size:3.5vw;text-align:left;color:#000;font-weight:500;margin:5vw 6.25vw 0 6.25vw;line-height:1.5}@media only screen and (min-width:769px){.play--terms__text{font-size:1.8181818182vw;margin:3.75vw 5vw 0 5vw;text-align:justify}}@media only screen and (min-width:1100px){.play--terms__text{font-size:20px;margin:40px 50px 0 50px}}.play--terms__text li{margin-left:1vw;list-style:"• ";text-align:justify}.play--terms__border{width:74.375vw;height:.75vw;background-image:url(/vendor/maimai/img/play/dash.png);background-repeat:repeat-x;margin:12.5vw auto 0}@media only screen and (min-width:769px){.play--terms__border{width:74.9090909091vw;height:.6363636364vw;margin:6.3636363636vw auto 0}}@media only screen and (min-width:1100px){.play--terms__border{width:824px;height:7px;margin:70px auto 0}}.song--search{margin-top:6.25vw;position:sticky;top:-75vw;z-index:100}@media only screen and (min-width:769px){.song--search{margin-top:4.5454545455vw;top:-41.8181818182vw}}@media only screen and (min-width:1100px){.song--search{margin-top:50px;top:calc(-460px - .2vw)}}@media only screen and (min-width:1400px){.song--search{top:-458px}}.song--search:after,.song--search:before{display:none}.song--search__text{font-size:2.5vw;text-align:center;line-height:1.5}@media only screen and (min-width:769px){.song--search__text{font-size:1.2727272727vw}}@media only screen and (min-width:1100px){.song--search__text{font-size:14px}}.song--search__btn{width:75vw;height:11.875vw;margin:5vw auto;border-radius:12.5vw;background-color:#4b4b4b;display:flex;align-items:center;justify-content:center;position:relative}.song--search__btn.new{background-color:#fb5f99}.song--search__btn.pops_anime{background-color:#ff972a}.song--search__btn.niconico{background-color:#02c8d3}.song--search__btn.toho{background-color:#ad59ee}.song--search__btn.variety{background-color:#4be070}.song--search__btn.maimai{background-color:#f64849}.song--search__btn.gekichu{background-color:#3584fe}.song--search__btn.utage{background-color:#dc39b8}.song--search__btn.new .song--search__btn__text{width:29.375vw}.song--search__btn.pops_anime .song--search__btn__text{width:33.375vw}.song--search__btn.niconico .song--search__btn__text{width:29.25vw}.song--search__btn.toho .song--search__btn__text{width:27.25vw}.song--search__btn.variety .song--search__btn__text{width:20vw}.song--search__btn.maimai .song--search__btn__text{width:17.375vw}.song--search__btn.gekichu .song--search__btn__text{width:25vw}.song--search__btn.utage .song--search__btn__text{width:13.5vw}.song--search__btn.on:after{transform:scale(1,-1)}.song--search__btn:after{position:absolute;display:block;content:"";background-image:url(/vendor/maimai/img/song/arrow.svg);background-size:contain;background-repeat:no-repeat;width:2.5vw;height:2.25vw;right:4.5vw}@media only screen and (min-width:769px){.song--search__btn{display:none}}.song--search__btn__text svg{fill:#fff;fill-rule:evenodd;height:4vw}@media only screen and (min-width:769px){.song--search__btn__text svg{height:1.6363636364vw}}@media only screen and (min-width:1100px){.song--search__btn__text svg{height:18px}}.song--search__list{display:none;width:75vw;margin-top:-2.5vw!important;left:50%;transform:translateX(-50%);position:absolute;box-sizing:border-box;background-color:#242424;z-index:200;text-align:center;color:#fff;font-size:3.5vw;font-weight:900;padding:2.5vw 6.25vw}.song--search__item{padding:5vw 0}.song--search__item:not(:first-child){border-top:1px solid #707070}.song--search__check{display:flex;align-items:center;justify-content:center;flex-wrap:wrap}@media only screen and (min-width:769px){.song--search__check{margin-top:2.7272727273vw}}@media only screen and (min-width:1100px){.song--search__check{margin-top:30px}}@media only screen and (min-width:769px){.song--search__check.bt{width:54.5454545455vw;margin:0 auto}}@media only screen and (min-width:1100px){.song--search__check.bt{width:500px}}.song--search__check.bt>label:nth-child(n+4){margin-top:3.75vw}@media only screen and (min-width:769px){.song--search__check.bt>label:nth-child(n+4){margin-top:1.3636363636vw}}.song--search__check.bt>label:not(:first-child){margin-left:2.5vw}@media only screen and (min-width:769px){.song--search__check.bt>label:not(:first-child){margin-left:2.7272727273vw}}@media only screen and (min-width:1100px){.song--search__check.bt>label:not(:first-child){margin-left:30px}}.song--search__check>label:not(:first-child){margin-left:4.375vw}@media only screen and (min-width:769px){.song--search__check>label:not(:first-child){margin-left:4.0909090909vw}}@media only screen and (min-width:1100px){.song--search__check>label:not(:first-child){margin-left:45px}}.song--search__check__input{display:none}.song--search__check__input:checked+.song--search__check__text:before{background-color:#e72139;border:.75vw solid #e72139}.song--search__check__input:checked+.song--search__check__text:after{display:block}.song--search__check__text{display:flex;align-items:center;position:relative}.song--search__check__text::before{box-sizing:border-box;content:"";display:block;width:5vw;height:5vw;border:.75vw solid #e5e5e5;border-radius:.625vw}@media only screen and (min-width:769px){.song--search__check__text::before{width:2.7272727273vw;height:2.7272727273vw;border:.3636363636vw solid #e5e5e5;border-radius:.4545454545vw}}@media only screen and (min-width:1100px){.song--search__check__text::before{width:30px;height:30px;border-width:4px;border-radius:5px}}.song--search__check__text:after{box-sizing:border-box;content:"";display:none;position:absolute;left:.75vw;background-image:url(/vendor/maimai/img/song/check.svg);background-size:contain;background-repeat:no-repeat;width:3.375vw;height:2.875vw}@media only screen and (min-width:769px){.song--search__check__text:after{width:1.8181818182vw;height:1.4545454545vw;left:.5454545455vw}}@media only screen and (min-width:1100px){.song--search__check__text:after{width:20px;height:16px;left:6px}}.song--search__check__text>span{display:flex;align-items:center;width:20vw;height:4.125vw;margin-left:2.5vw}@media only screen and (min-width:769px){.song--search__check__text>span{width:10.6363636364vw;height:2.7272727273vw;margin-left:.9090909091vw}}@media only screen and (min-width:1100px){.song--search__check__text>span{width:117px;height:30px;margin-left:10px}}.song--search__check__text>span.re{width:22.375vw}@media only screen and (min-width:769px){.song--search__check__text>span.re{width:11.2727272727vw}}@media only screen and (min-width:1100px){.song--search__check__text>span.re{width:124px}}.song--search__check__text>span.ba{width:12.5vw;height:3.75vw;margin-left:1.25vw}@media only screen and (min-width:769px){.song--search__check__text>span.ba{width:7vw;height:1.8181818182vw;margin-left:.9090909091vw}}@media only screen and (min-width:1100px){.song--search__check__text>span.ba{width:77px;height:20px;margin-left:10px}}.song--search__check__text>span.bas{width:22.5vw;height:4vw;margin-left:1.25vw}@media only screen and (min-width:769px){.song--search__check__text>span.bas{width:12.8181818182vw;height:1.8181818182vw;margin-left:.9090909091vw}}@media only screen and (min-width:1100px){.song--search__check__text>span.bas{width:141px;height:20px;margin-left:10px}}.song--search__check__text>span.ex{width:16.25vw;height:3.75vw;margin-left:1.25vw}@media only screen and (min-width:769px){.song--search__check__text>span.ex{width:9.0909090909vw;height:1.8181818182vw;margin-left:.9090909091vw}}@media only screen and (min-width:1100px){.song--search__check__text>span.ex{width:100px;height:20px;margin-left:10px}}.song--search__check__text>span.ma{width:17.5vw;height:3.75vw;margin-left:1.25vw}@media only screen and (min-width:769px){.song--search__check__text>span.ma{width:9.0909090909vw;height:1.8181818182vw;margin-left:.9090909091vw}}@media only screen and (min-width:1100px){.song--search__check__text>span.ma{width:100px;height:20px;margin-left:10px}}.song--search__check__text>span.re{width:26.25vw;height:3.75vw;margin-left:1.25vw}@media only screen and (min-width:769px){.song--search__check__text>span.re{width:12.7272727273vw;height:1.8181818182vw;margin-left:.9090909091vw}}@media only screen and (min-width:1100px){.song--search__check__text>span.re{width:140px;height:20px;margin-left:10px}}.song--search__nav{display:none}@media only screen and (min-width:769px){.song--search__nav{width:77.2727272727vw;margin:2.7272727273vw auto;display:flex;align-items:center;justify-content:center;flex-wrap:wrap}}@media only screen and (min-width:1100px){.song--search__nav{width:850px;margin:30px auto 30px}}@media only screen and (min-width:769px){.song--search__nav__item{display:flex;align-items:center;justify-content:center;width:18.1818181818vw;height:7.2727272727vw;background-color:#4b4b4b;border-radius:100px;transition:.3s;cursor:pointer}}@media only screen and (min-width:1100px){.song--search__nav__item{width:200px;height:80px}}.song--search__nav__item.on.new,.song--search__nav__item:hover.new{background-color:#fb5f99}.song--search__nav__item.on.pops_anime,.song--search__nav__item:hover.pops_anime{background-color:#ff972a}.song--search__nav__item.on.niconico,.song--search__nav__item:hover.niconico{background-color:#02c8d3}.song--search__nav__item.on.toho,.song--search__nav__item:hover.toho{background-color:#ad59ee}.song--search__nav__item.on.variety,.song--search__nav__item:hover.variety{background-color:#4be070}.song--search__nav__item.on.maimai,.song--search__nav__item:hover.maimai{background-color:#f64849}.song--search__nav__item.on.gekichu,.song--search__nav__item:hover.gekichu{background-color:#3584fe}.song--search__nav__item.on.utage,.song--search__nav__item:hover.utage{background-color:#dc39b8}@media only screen and (min-width:769px){.song--search__nav__item:nth-child(n+5){margin-top:.9090909091vw}}@media only screen and (min-width:1100px){.song--search__nav__item:nth-child(n+5){margin-top:10px}}@media only screen and (min-width:769px){.song--search__nav__item:not(:nth-child(4n+1)){margin-left:1.3636363636vw}}@media only screen and (min-width:1100px){.song--search__nav__item:not(:nth-child(4n+1)){margin-left:15px}}@media only screen and (min-width:769px){.song--search__nav__img{height:1.7272727273vw}}@media only screen and (min-width:1100px){.song--search__nav__img{height:19px}}@media only screen and (min-width:769px){.song--search__nav__img.i01{width:11.8181818182vw}}@media only screen and (min-width:1100px){.song--search__nav__img.i01{width:130px}}@media only screen and (min-width:769px){.song--search__nav__img.i02{width:13.5454545455vw}}@media only screen and (min-width:1100px){.song--search__nav__img.i02{width:149px}}@media only screen and (min-width:769px){.song--search__nav__img.i03{width:12.9090909091vw;height:4.1818181818vw}}@media only screen and (min-width:1100px){.song--search__nav__img.i03{width:142px;height:46px}}@media only screen and (min-width:769px){.song--search__nav__img.i04{width:11vw;height:2vw}}@media only screen and (min-width:1100px){.song--search__nav__img.i04{width:121px;height:22px}}@media only screen and (min-width:769px){.song--search__nav__img.i05{width:9.1818181818vw;height:4.1818181818vw}}@media only screen and (min-width:1100px){.song--search__nav__img.i05{width:101px;height:46px}}@media only screen and (min-width:769px){.song--search__nav__img.i06{width:7vw;height:1.5454545455vw}}@media only screen and (min-width:1100px){.song--search__nav__img.i06{width:77px;height:17px}}@media only screen and (min-width:769px){.song--search__nav__img.i07{width:11.1818181818vw;height:4.1818181818vw}}@media only screen and (min-width:1100px){.song--search__nav__img.i07{width:123px;height:46px}}@media only screen and (min-width:769px){.song--search__nav__img.i08{width:5.1818181818vw;height:1.9090909091vw}}@media only screen and (min-width:1100px){.song--search__nav__img.i08{width:57px;height:21px}}.song--search__input{position:relative;display:flex;align-items:center;justify-content:center;margin-top:5.625vw}@media only screen and (min-width:769px){.song--search__input{margin-top:0}}.song--search__input:before{display:block;content:"";background-image:url(/vendor/maimai/img/song/search_lens.svg);background-size:contain;background-repeat:no-repeat;width:6.25vw;height:6.25vw;left:8.75vw;position:absolute;z-index:10}@media only screen and (min-width:769px){.song--search__input:before{width:2.7272727273vw;height:2.7272727273vw;left:.9090909091vw}}@media only screen and (min-width:1100px){.song--search__input:before{width:30px;height:30px;left:10px}}.song--search__input .clear{display:block;content:"";background-image:url(/vendor/maimai/img/song/search_del.png);background-size:contain;background-repeat:no-repeat;width:6.25vw;height:6.25vw;position:absolute;right:8.75vw;z-index:10;cursor:pointer}@media only screen and (min-width:769px){.song--search__input .clear{width:2.7272727273vw;height:2.7272727273vw;right:.9090909091vw}}@media only screen and (min-width:1100px){.song--search__input .clear{width:30px;height:30px;right:10px}}.song--search__input input{position:relative;width:75vw;height:11.25vw;border-radius:100px;background-color:#eee;margin:0 auto;font-size:4.5vw;padding:0 10vw;display:block;font-weight:900}.song--search__input input::placeholder{font-size:2.25vw;font-weight:400}@media only screen and (min-width:769px){.song--search__input input{width:40.6363636364vw;height:3.6363636364vw;font-size:1.8181818182vw;font-weight:900;padding:0 4.5454545455vw}.song--search__input input::placeholder{font-size:1.2727272727vw;font-weight:400}}@media only screen and (min-width:1100px){.song--search__input input{width:447px;height:40px;font-weight:900;font-size:20px;padding:0 50px}.song--search__input input::placeholder{font-size:14px;font-weight:400}}.song--search__attention{margin:2.5vw auto 0;padding:0 6.25vw}@media only screen and (min-width:769px){.song--search__attention{margin:0;padding:0;margin-left:2.2727272727vw}}@media only screen and (min-width:1100px){.song--search__attention{margin-left:25px}}.song--search__attention__text{font-size:2.5vw;padding-left:1em;text-indent:-1em;display:flex;align-items:center;line-height:1.5}.song--search__attention__text:not(:first-child){margin-top:1.25vw}@media only screen and (min-width:769px){.song--search__attention__text:not(:first-child){margin-top:.1818181818vw}}@media only screen and (min-width:1100px){.song--search__attention__text:not(:first-child){margin-top:2px}}@media only screen and (min-width:769px){.song--search__attention__text{font-size:1.0909090909vw}}@media only screen and (min-width:1100px){.song--search__attention__text{font-size:12px}}.song--search__attention__key{display:flex;align-items:center;justify-content:center;width:3.75vw;margin:0 3.25vw 0 .5vw}@media only screen and (min-width:769px){.song--search__attention__key{width:1.8181818182vw;margin:0 1.2727272727vw 0 .1818181818vw}}@media only screen and (min-width:1100px){.song--search__attention__key{width:20px;margin:0 14px 0 2px}}@media only screen and (min-width:769px){.song--search__box{display:flex;align-items:center;justify-content:center;margin-top:2.2727272727vw}}@media only screen and (min-width:1100px){.song--search__box{margin-top:25px}}.song--search__open{position:absolute;width:100%;margin:5vw 0 -8.25vw;height:11.25vw;font-size:4.5vw;display:flex;align-items:center;justify-content:center;color:#fff;font-weight:900;background-color:#e72139;position:relative;border-radius:0 0 3.75vw 3.75vw;z-index:100;transition:.3s;cursor:pointer}.song--search__open.open{opacity:0;pointer-events:none}@media only screen and (min-width:769px){.song--search__open{margin:3.6363636364vw 0 -4.6363636364vw;height:5.4545454545vw;font-size:2.3636363636vw;border-radius:0 0 2.7272727273vw 2.7272727273vw}}@media only screen and (min-width:1100px){.song--search__open{margin:40px 0 -52px;height:60px;font-size:26px;border-radius:0 0 30px 30px}}.song--search__open:after{display:block;content:"";position:absolute;right:3.75vw;width:6.25vw;height:6.25vw;background-image:url(/vendor/maimai/img/song/plus.png);background-size:cover}@media only screen and (min-width:769px){.song--search__open:after{right:26.3636363636vw;width:2.7272727273vw;height:2.7272727273vw}}@media only screen and (min-width:1100px){.song--search__open:after{right:290px;width:30px;height:30px}}.song--search__detail{padding:0 6.25vw;position:relative;display:none}@media only screen and (min-width:769px){.song--search__detail{padding:0 2.2727272727vw}}@media only screen and (min-width:1100px){.song--search__detail{padding:0 25px}}.song--search__border{width:100%;height:.75vw;border-radius:100px;background-color:#e5e5e5}@media only screen and (min-width:769px){.song--search__border{height:.5454545455vw}}@media only screen and (min-width:1100px){.song--search__border{height:6px}}.song--search__inner{margin-top:5vw}@media only screen and (min-width:769px){.song--search__inner{margin-top:3.6363636364vw}}@media only screen and (min-width:1100px){.song--search__inner{margin-top:40px}}.song--search__lv{margin-top:3.75vw;display:flex;flex-wrap:wrap;justify-content:flex-start;align-items:flex-start}@media only screen and (min-width:769px){.song--search__lv{margin-top:2.7272727273vw}}@media only screen and (min-width:1100px){.song--search__lv{margin-top:30px}}.song--search__lv__btn{display:flex;align-items:center;justify-content:center;color:#fff;font-weight:900;width:13.75vw;height:11.25vw;border-radius:1.25vw;background-color:#4b4b4b;font-size:3.75vw}@media only screen and (min-width:769px){.song--search__lv__btn{width:5vw;height:4.0909090909vw;border-radius:.9090909091vw;font-size:1.6363636364vw}}@media only screen and (min-width:1100px){.song--search__lv__btn{width:55px;height:45px;border-radius:10px;font-size:18px}}.song--search__lv__btn.on{background-color:#e72139}@media only screen and (max-width:769px){.song--search__lv__btn:not(:nth-child(5n+1)){margin-left:1.5vw}.song--search__lv__btn:nth-child(n+6){margin-top:1.875vw}}@media only screen and (min-width:769px){.song--search__lv__btn:not(:nth-child(14n+1)){margin-left:.5454545455vw}.song--search__lv__btn:nth-child(n+15){margin-top:1.0909090909vw}}@media only screen and (min-width:1100px){.song--search__lv__btn:not(:nth-child(14n+1)){margin-left:6px}.song--search__lv__btn:nth-child(n+15){margin-top:12px}}.song--search__select{margin-top:3.75vw;display:flex;justify-content:space-between}@media only screen and (min-width:769px){.song--search__select{width:39.0909090909vw;margin:3.75vw auto 0}}@media only screen and (min-width:1100px){.song--search__select{width:430px;margin:30px auto 0}}.song--search__select__btn{box-sizing:border-box;font-size:3vw;color:#e72139;font-weight:900;width:36.25vw;height:10vw;border:1vw solid #e5e5e5;display:flex;align-items:center;justify-content:center;border-radius:100px}@media only screen and (min-width:769px){.song--search__select__btn{width:18.1818181818vw;height:3.6363636364vw;font-size:1.6363636364vw;border:.3636363636vw solid #e5e5e5;border-radius:1.8181818182vw}}@media only screen and (min-width:1100px){.song--search__select__btn{width:200px;height:40px;font-size:18px;border-width:4px;border-radius:20px}}.song--search__count{width:75vw;height:15vw;margin:3.75vw auto;border:3px solid #e72139;border-radius:125vw;background:linear-gradient(90deg,#e72139 0,#e72139 50%,#eee 50%,#eee 100%);display:flex;overflow:hidden;box-sizing:border-box}@media only screen and (min-width:769px){.song--search__count{background:linear-gradient(90deg,#e72139 0,#e72139 40%,#eee 40%,#eee 100%);width:37.8181818182vw;height:7.2727272727vw;margin:2.7272727273vw auto}}@media only screen and (min-width:1100px){.song--search__count{width:416px;height:80px;margin:30px auto}}.song--search__count__text{color:#fff;width:37.5vw;height:110%;margin-left:-.625vw;margin-top:-.625vw;font-size:3vw;background-color:#e72139;display:flex;justify-content:center;align-items:center;font-weight:700;text-align:center}@media only screen and (min-width:769px){.song--search__count__text{width:18.1818181818vw;margin-left:-.4545454545vw;margin-top:-.2727272727vw;font-size:1.6363636364vw}}@media only screen and (min-width:1100px){.song--search__count__text{width:200px;margin-left:-5px;margin-top:-3px;font-size:18px}}.song--search__count__num{width:37.5vw;height:13.75vw;font-size:5vw;display:flex;justify-content:center;align-items:center;font-weight:900;text-align:center;color:#e72139}@media only screen and (min-width:769px){.song--search__count__num{width:19.6363636364vw;height:6.3636363636vw;font-size:2.9090909091vw}}@media only screen and (min-width:1100px){.song--search__count__num{width:216px;height:70px;font-size:32px}}.song--search__count__num>.small{margin-top:1.25vw;margin-left:1.25vw;font-size:2.5vw}@media only screen and (min-width:769px){.song--search__count__num>.small{margin-top:.9090909091vw;margin-left:.9090909091vw;font-size:1.6363636364vw}}@media only screen and (min-width:1100px){.song--search__count__num>.small{margin-top:10px;margin-left:10px;font-size:18px}}.song--search__close{width:6.25vw;position:absolute;right:2.5vw;bottom:-9.375vw}@media only screen and (min-width:769px){.song--search__close{width:3.6363636364vw;right:.9090909091vw;bottom:-6.3636363636vw}}@media only screen and (min-width:1100px){.song--search__close{width:40px;right:10px;bottom:-70px}}.song--box{margin-top:6.25vw;position:relative;transform:translate3d(0,0,0)}.song--box__title{display:flex;flex-wrap:wrap;align-items:center;justify-content:center}.song--box__title--icon{background-image:none;width:13.5vw;height:7vw}@media only screen and (min-width:769px){.song--box__title--icon{width:12.5454545455vw;height:6.1818181818vw}}@media only screen and (min-width:1100px){.song--box__title--icon{width:138px;height:68px}}.song--box__tit{margin:0 1.25vw;display:flex;align-items:center;justify-content:center}@media only screen and (min-width:769px){.song--box__tit{margin:0 1.2727272727vw}}@media only screen and (min-width:1100px){.song--box__tit{margin:0 14px}}.song--box.new .song--box__title--icon svg{fill:#fb5f99}.song--box.new .song--box__tit{width:28.625vw}@media only screen and (min-width:769px){.song--box.new .song--box__tit{width:19.6363636364vw}}@media only screen and (min-width:1100px){.song--box.new .song--box__tit{width:216px}}.song--box.new:after,.song--box.new:before{background-color:#fb5f99}.song--box.pops_anime .song--box__title--icon svg{fill:#ff972a}.song--box.pops_anime .song--box__tit{width:36.125vw}@media only screen and (min-width:769px){.song--box.pops_anime .song--box__tit{width:24.3636363636vw}}@media only screen and (min-width:1100px){.song--box.pops_anime .song--box__tit{width:268px}}.song--box.pops_anime:after,.song--box.pops_anime:before{background-color:#ff972a}.song--box.niconico .song--box__title--icon svg{fill:#02c8d3}.song--box.niconico .song--box__tit{width:48.875vw}@media only screen and (min-width:769px){.song--box.niconico .song--box__tit{width:40.7272727273vw}}@media only screen and (min-width:1100px){.song--box.niconico .song--box__tit{width:448px}}.song--box.niconico:after,.song--box.niconico:before{background-color:#02c8d3}.song--box.toho .song--box__title--icon svg{fill:#ad59ee}.song--box.toho .song--box__tit{width:28.625vw}@media only screen and (min-width:769px){.song--box.toho .song--box__tit{width:20.3636363636vw}}@media only screen and (min-width:1100px){.song--box.toho .song--box__tit{width:224px}}.song--box.toho:after,.song--box.toho:before{background-color:#ad59ee}.song--box.maimai .song--box__title--icon svg{fill:#f64849}.song--box.maimai .song--box__tit{width:18.25vw;margin-bottom:.75vw}@media only screen and (min-width:769px){.song--box.maimai .song--box__tit{width:13.5454545455vw;margin-bottom:.7272727273vw}}@media only screen and (min-width:1100px){.song--box.maimai .song--box__tit{width:149px;margin-bottom:8px}}.song--box.maimai:after,.song--box.maimai:before{background-color:#f64849}.song--box.variety .song--box__title--icon svg{fill:#4be070}.song--box.variety .song--box__tit{width:43.75vw}@media only screen and (min-width:769px){.song--box.variety .song--box__tit{width:29.2727272727vw}}@media only screen and (min-width:1100px){.song--box.variety .song--box__tit{width:322px}}.song--box.variety:after,.song--box.variety:before{background-color:#4be070}.song--box.gekichu .song--box__title--icon svg{fill:#3584fe}.song--box.gekichu .song--box__tit{width:45.125vw}@media only screen and (min-width:769px){.song--box.gekichu .song--box__tit{width:36.3636363636vw}}@media only screen and (min-width:1100px){.song--box.gekichu .song--box__tit{width:400px}}.song--box.gekichu:after,.song--box.gekichu:before{background-color:#3584fe}.song--box.utage .song--box__title--icon svg{fill:#dc39b8}.song--box.utage .song--box__tit{width:11.25vw}@media only screen and (min-width:769px){.song--box.utage .song--box__tit{width:8.6363636364vw}}@media only screen and (min-width:1100px){.song--box.utage .song--box__tit{width:95px}}.song--box.utage:after,.song--box.utage:before{background-color:#dc39b8}.song--box__none{font-size:3.75vw;margin-top:5vw;line-height:2;font-weight:900;text-align:center}@media only screen and (min-width:769px){.song--box__none{font-size:1.6363636364vw;margin-top:3.6363636364vw}}@media only screen and (min-width:1100px){.song--box__none{font-size:18px;margin-top:40px}}.song--item{padding:6.25vw 6.25vw 0}.song--item:last-child .song--item__border{display:none}@media only screen and (min-width:769px){.song--item{padding:4.5454545455vw 3.6363636364vw 0;display:flex;align-items:flex-start;justify-content:space-between}}@media only screen and (min-width:1100px){.song--item{padding:50px 40px 0}}@media only screen and (min-width:769px){.song--item__box{width:53.4545454545vw}}@media only screen and (min-width:1100px){.song--item__box{width:588px}}.song--item__top{display:flex;align-items:center;justify-content:space-between}.song--item__top.lock:after{display:block;content:"";background-image:url(/vendor/maimai/img/song/lock.png);background-size:contain;width:6.125vw;height:6.125vw}@media only screen and (min-width:769px){.song--item__top.lock:after{width:4.4545454545vw;height:4.4545454545vw}}@media only screen and (min-width:1100px){.song--item__top.lock:after{width:49px;height:49px}}.song--item__genre.pops_anime{width:35.25vw}@media only screen and (min-width:769px){.song--item__genre.pops_anime{width:15.5454545455vw}}@media only screen and (min-width:1100px){.song--item__genre.pops_anime{width:171px}}.song--item__genre.toho{width:29.875vw}@media only screen and (min-width:769px){.song--item__genre.toho{width:13.4545454545vw}}@media only screen and (min-width:1100px){.song--item__genre.toho{width:148px}}.song--item__genre.maimai{width:21.125vw}@media only screen and (min-width:769px){.song--item__genre.maimai{width:9.4545454545vw}}@media only screen and (min-width:1100px){.song--item__genre.maimai{width:104px}}.song--item__genre.variety{width:41.875vw}@media only screen and (min-width:769px){.song--item__genre.variety{width:18.8181818182vw}}@media only screen and (min-width:1100px){.song--item__genre.variety{width:207px}}.song--item__genre.niconico{width:54.875vw}@media only screen and (min-width:769px){.song--item__genre.niconico{width:24.8181818182vw}}@media only screen and (min-width:1100px){.song--item__genre.niconico{width:273px}}.song--item__genre.gekichu{width:51.25vw}@media only screen and (min-width:769px){.song--item__genre.gekichu{width:23.1818181818vw}}@media only screen and (min-width:1100px){.song--item__genre.gekichu{width:255px}}.song--item__genre.utage{width:18vw}@media only screen and (min-width:769px){.song--item__genre.utage{width:8.3636363636vw}}@media only screen and (min-width:1100px){.song--item__genre.utage{width:92px}}.song--item__middle{display:flex;margin-top:3.75vw}@media only screen and (min-width:769px){.song--item__middle{margin-top:.9090909091vw}}@media only screen and (min-width:1100px){.song--item__middle{margin-top:10px}}.song--item__info{width:46.25vw;margin-left:3.75vw}@media only screen and (min-width:769px){.song--item__info{margin-left:0;width:100%}}.song--item__info.utage{position:relative}.song--item__info.utage .song--item__icon{background:transparent url(/vendor/maimai/img/song/utage/icon_buddy.png) no-repeat;background-size:contain;width:8.625vw;height:6.625vw;position:absolute;right:0;bottom:0;pointer-events:none}@media only screen and (min-width:769px){.song--item__info.utage .song--item__icon{width:5vw;height:4.4545454545vw}}@media only screen and (min-width:1100px){.song--item__info.utage .song--item__icon{width:55px;height:49px}}.song--item__thumb{width:25vw;font-size:0}@media only screen and (min-width:769px){.song--item__thumb{width:18.1818181818vw}}@media only screen and (min-width:1100px){.song--item__thumb{width:200px}}.song--item__tit{font-size:4vw;font-weight:900;margin-top:-1vw;padding-bottom:1.25vw;border-bottom:1px solid #e5e5e5;line-height:1.5}@media only screen and (min-width:769px){.song--item__tit{margin:0;padding:0;font-size:2.5454545455vw;border:none}}@media only screen and (min-width:1100px){.song--item__tit{font-size:28px}}.song--item__artist{font-size:2.5vw;font-weight:900;margin-top:2.5vw}@media only screen and (min-width:769px){.song--item__artist{font-size:1.2727272727vw;margin-top:1.0909090909vw}}@media only screen and (min-width:1100px){.song--item__artist{font-size:14px;margin-top:12px}}.song--item__artistName{font-size:3vw;font-weight:900;margin-top:1.25vw}@media only screen and (min-width:769px){.song--item__artistName{font-size:1.4545454545vw;margin-top:.9090909091vw}}@media only screen and (min-width:1100px){.song--item__artistName{font-size:16px;margin-top:10px}}.song--item__bottom{margin-top:3.75vw}@media only screen and (min-width:769px){.song--item__bottom{margin-top:1.8181818182vw}}@media only screen and (min-width:1100px){.song--item__bottom{margin-top:20px}}.song--item__lv{display:flex;align-items:center;justify-content:space-between}.song--item__lv:not(:first-child){margin-top:1.25vw}@media only screen and (min-width:769px){.song--item__lv:not(:first-child){margin-top:.9090909091vw}}@media only screen and (min-width:1100px){.song--item__lv:not(:first-child){margin-top:10px}}.song--item__lv:after{display:block;content:"";background-size:contain;background-repeat:no-repeat;width:19vw;height:3.75vw}@media only screen and (min-width:769px){.song--item__lv:after{width:13.6363636364vw;height:2.7272727273vw}}@media only screen and (min-width:1100px){.song--item__lv:after{width:150px;height:30px}}.song--item__lv.st:after{background-image:url(/vendor/maimai/img/song/inner_st.png)}.song--item__lv.dx:after{background-image:url(/vendor/maimai/img/song/inner_dx.png)}.song--item__lv.utage .song--item__lv__inner{width:100%;flex-wrap:wrap}.song--item__lv.utage .song--item__lv__num{background-color:#dc39b8;font-size:4vw;width:37.125vw}@media only screen and (min-width:769px){.song--item__lv.utage .song--item__lv__num{font-size:2.5454545455vw;width:26.5454545455vw}}@media only screen and (min-width:1100px){.song--item__lv.utage .song--item__lv__num{font-size:28px;width:292px}}.song--item__lv.utage:after{display:none}.song--item__lv__inner{width:53.75vw;display:flex;justify-content:space-between}@media only screen and (min-width:769px){.song--item__lv__inner{width:39.0909090909vw}}@media only screen and (min-width:1100px){.song--item__lv__inner{width:430px}}.song--item__lv__num{width:10vw;height:7.5vw;border-radius:1.25vw;font-size:4.25vw;font-weight:900;display:flex;align-items:center;justify-content:center;color:#fff}@media only screen and (min-width:769px){.song--item__lv__num{width:7.2727272727vw;height:5.4545454545vw;border-radius:.9090909091vw;font-size:3.0909090909vw}}@media only screen and (min-width:1100px){.song--item__lv__num{width:80px;height:60px;border-radius:10px;font-size:34px}}.song--item__lv__num:nth-child(1){background-color:#6ed43e}.song--item__lv__num:nth-child(2){background-color:#f7b807}.song--item__lv__num:nth-child(3){background-color:#ff828d}.song--item__lv__num:nth-child(4){background-color:#a051dc}.song--item__lv__num:nth-child(5){border:.5vw solid #a051dc;color:#a051dc;background-color:#fff}@media only screen and (min-width:769px){.song--item__lv__num:nth-child(5){border-width:.3636363636vw}}@media only screen and (min-width:1100px){.song--item__lv__num:nth-child(5){border-width:4px}}.song--item__lv__comment{background-color:#dc39b8;border-radius:1.25vw;display:flex;align-items:center;justify-content:center;color:#fff;width:100%;font-size:3vw;font-weight:900;margin-top:1.25vw;padding:2.25vw 1.25vw}@media only screen and (min-width:769px){.song--item__lv__comment{border-radius:.9090909091vw;justify-content:flex-start;font-size:2.1818181818vw;margin-top:.9090909091vw;padding:1.6363636364vw 1.8181818182vw}}@media only screen and (min-width:1100px){.song--item__lv__comment{border-radius:10px;font-size:24px;margin-top:10px;padding:18px 20px}}.song--item__copy{font-size:2.25vw;margin-top:2.5vw;line-height:2}@media only screen and (min-width:769px){.song--item__copy{font-size:.9090909091vw;margin-top:.9090909091vw}}@media only screen and (min-width:1100px){.song--item__copy{font-size:10px;margin-top:10px}}.song--item__border{width:100%;height:.75vw;border-radius:100px;background-color:#e5e5e5;margin-top:6.25vw}@media only screen and (min-width:769px){.song--item__border{height:.5454545455vw;margin-top:4.5454545455vw;width:140%;margin-left:-40%}}@media only screen and (min-width:1100px){.song--item__border{height:6px;margin-top:50px}}.song--copy{position:relative;width:87.5vw;border-radius:3.75vw;box-shadow:1.75vw 1.75vw 0 0 rgba(0,0,0,.2);background-color:rgba(255,255,255,.7);margin:8.75vw auto 0;padding:5.625vw 5vw;font-size:2.5vw;line-height:1.8}@media only screen and (min-width:769px){.song--copy{width:81.8181818182vw;border-radius:2.7272727273vw;box-shadow:1.2727272727vw 1.2727272727vw 0 0 rgba(0,0,0,.2);padding:4.0909090909vw get(40,1100);margin-top:7.2727272727vw;font-size:1.0909090909vw}}@media only screen and (min-width:1100px){.song--copy{width:900px;border-radius:30px;box-shadow:14px 14px 0 0 rgba(0,0,0,.2);padding:45px 40px;margin-top:80px;font-size:12px}}.song--copy .piapro{width:11.875vw;height:2.75vw;display:inline-block;margin:0 .625vw -.625vw;-webkit-backface-visibility:hidden;background-image:url(/vendor/maimai/img/song/piapro_logo.png);background-size:contain;background-repeat:no-repeat}@media only screen and (min-width:769px){.song--copy .piapro{width:6.8181818182vw;height:1.6363636364vw;margin:0 .4545454545vw -.4545454545vw}}@media only screen and (min-width:1100px){.song--copy .piapro{width:75px;height:18px;margin:0 5px -5px}}#song--search.m_fixed{position:fixed;left:50%;transform:translateX(-50%);z-index:200;margin-top:0}.dl--nav{display:flex;justify-content:space-between;flex-wrap:wrap;width:88.75vw;margin:5vw auto 0}@media only screen and (min-width:769px){.dl--nav{width:64.2857142857vw;margin:3.5714285714vw auto 0}}@media only screen and (min-width:1100px){.dl--nav{width:900px;margin:50px auto 0}}.dl--nav__link{display:block;box-shadow:.625vw .625vw 0 rgba(0,0,0,.2);width:42.75vw;height:10.625vw;border-radius:5.25vw;background-color:#4b4b4b;display:flex;align-items:center;justify-content:center}.dl--nav__link.on{background-color:#e72139}@media only screen and (min-width:769px){.dl--nav__link{transition:.3s;width:31.7142857143vw;height:4.2857142857vw;border-radius:3vw;box-shadow:.3571428571vw .3571428571vw 0 rgba(0,0,0,.2)}.dl--nav__link:hover{background-color:#e72139}}@media only screen and (min-width:1100px){.dl--nav__link{width:444px;height:60px;border-radius:42px;box-shadow:5px 5px 0 rgba(0,0,0,.2)}}@media only screen and (max-width:769px){.dl--nav__item:nth-child(n+3){margin-top:2vw}}@media only screen and (min-width:769px){.dl--nav__item:nth-child(n+4){margin-top:.8571428571vw}}@media only screen and (min-width:1100px){.dl--nav__item:nth-child(n+4){margin-top:12px}}.dl--nav__text.l1{width:20.25vw}@media only screen and (min-width:769px){.dl--nav__text.l1{width:9.5vw}}@media only screen and (min-width:1100px){.dl--nav__text.l1{width:133px}}.dl--nav__text.l2{width:31.5vw}@media only screen and (min-width:769px){.dl--nav__text.l2{width:14.7857142857vw}}@media only screen and (min-width:1100px){.dl--nav__text.l2{width:207px}}.dl--pop{margin-top:6.25vw}@media only screen and (min-width:769px){.dl--pop{margin-top:4.5454545455vw}}@media only screen and (min-width:1100px){.dl--pop{margin-top:50px}}.dl--pop__header .common--header__txt{width:33.25vw;height:4vw}@media only screen and (min-width:769px){.dl--pop__header .common--header__txt{width:21.6363636364vw;height:2.4545454545vw}}@media only screen and (min-width:1100px){.dl--pop__header .common--header__txt{width:238px;height:27px}}.dl--pop__wrapper{width:75vw;margin:8.75vw auto 0}@media only screen and (min-width:769px){.dl--pop__wrapper{display:flex;flex-wrap:wrap;width:72.7272727273vw;margin:6.3636363636vw auto 0}}@media only screen and (min-width:1100px){.dl--pop__wrapper{width:800px;margin:70px auto 0}}@media only screen and (max-width:769px){.dl--pop__item:not(:first-child){margin-top:5.625vw}}@media only screen and (min-width:769px){.dl--pop__item{width:34.5454545455vw}.dl--pop__item:nth-child(even){margin-left:3.6363636364vw}.dl--pop__item:nth-child(n+3){margin-top:4.5454545455vw}}@media only screen and (min-width:1100px){.dl--pop__item{width:380px}.dl--pop__item:nth-child(even){margin-left:40px}.dl--pop__item:nth-child(n+3){margin-top:50px}}.dl--pop__head{width:100%;height:5vw;font-size:3.5vw;font-weight:900;background-color:#e72139;justify-content:center;color:#fff;border-radius:100px;display:flex;align-items:center}@media only screen and (min-width:769px){.dl--pop__head{height:2.7272727273vw;font-size:1.8181818182vw}}@media only screen and (min-width:1100px){.dl--pop__head{height:30px;font-size:20px}}.dl--pop__thumb{display:block;width:100%;margin-top:2.5vw;font-size:0;position:relative}@media only screen and (min-width:769px){.dl--pop__thumb{margin-top:1.8181818182vw;transition:.3s}.dl--pop__thumb:hover{opacity:.7}}@media only screen and (min-width:1100px){.dl--pop__thumb{margin-top:20px}}.dl--pop__thumb::after{display:block;content:"";position:absolute;width:10vw;height:10vw;background-image:url(/vendor/maimai/img/common/download.png);background-size:contain;right:3.75vw;bottom:3.75vw}@media only screen and (min-width:769px){.dl--pop__thumb::after{width:4.5454545455vw;height:4.5454545455vw;right:1.8181818182vw;bottom:1.8181818182vw}}@media only screen and (min-width:1100px){.dl--pop__thumb::after{width:50px;height:50px;right:20px;bottom:20px}}.dl--pop__inner{width:75vw;margin:7.5vw auto 0}@media only screen and (min-width:769px){.dl--pop__inner{width:72.7272727273vw;margin:3.6363636364vw auto 0}}@media only screen and (min-width:1100px){.dl--pop__inner{width:800px;margin:40px auto 0}}.dl--pop__text{font-size:2.5vw;padding-left:1em;text-indent:-1em;line-height:1.5}@media only screen and (min-width:769px){.dl--pop__text{font-size:1.0909090909vw}}@media only screen and (min-width:1100px){.dl--pop__text{font-size:12px}}.dl--pn{display:flex;margin:7.5vw auto 0;width:87.5vw;justify-content:space-between;align-items:center}@media only screen and (min-width:769px){.dl--pn{margin-top:5.4545454545vw;width:73.6363636364vw}}@media only screen and (min-width:1100px){.dl--pn{margin-top:60px;width:810px}}.dl--pn__inner{display:flex}.dl--pn__inner.dl--pn__left .dl--pn__arrow:before{transform:scale(-1,1)}.dl--pn__center{width:40.25vw;display:flex}@media only screen and (min-width:769px){.dl--pn__center{width:48.1818181818vw}}@media only screen and (min-width:1100px){.dl--pn__center{width:530px}}.dl--pn__num{display:flex;align-items:center;justify-content:center;width:8.75vw;height:8.75vw;border-radius:12.5vw;background-color:#4b4b4b;box-shadow:.625vw .625vw 0 0 rgba(0,0,0,.2);cursor:pointer;color:#fff;font-size:3.5vw;font-weight:900}.dl--pn__num.on,.dl--pn__num:hover{background-color:#e72139}.dl--pn__num:not(:first-child){margin-left:1.75vw}@media only screen and (min-width:769px){.dl--pn__num{width:4.5454545455vw;height:4.5454545455vw;box-shadow:.4545454545vw .4545454545vw 0 0 rgba(0,0,0,.2);font-size:1.8181818182vw;font-weight:900}.dl--pn__num:not(:first-child){margin-left:.9090909091vw}}@media only screen and (min-width:1100px){.dl--pn__num{width:50px;height:50px;box-shadow:5px 5px 0 0 rgba(0,0,0,.2);font-size:20px}.dl--pn__num:not(:first-child){margin-left:10px}}.dl--pn__arrow{display:flex;align-items:center;justify-content:center;width:8.75vw;height:8.75vw;border-radius:12.5vw;background-color:#e72139;box-shadow:.625vw .625vw 0 0 rgba(0,0,0,.2);cursor:pointer}@media only screen and (min-width:769px){.dl--pn__arrow{transition:transform .3s}.dl--pn__arrow:hover{transform:translateY(-5%)}}.dl--pn__arrow.off{background-color:#dcdcdc;pointer-events:none}@media only screen and (min-width:769px){.dl--pn__arrow{width:4.5454545455vw;height:4.5454545455vw;box-shadow:.4545454545vw .4545454545vw 0 0 rgba(0,0,0,.2)}}@media only screen and (min-width:1100px){.dl--pn__arrow{width:50px;height:50px;box-shadow:5px 5px 0 0 rgba(0,0,0,.2)}}.dl--pn__arrow:nth-child(2){margin-left:1.75vw}@media only screen and (min-width:769px){.dl--pn__arrow:nth-child(2){margin-left:.9090909091vw}}@media only screen and (min-width:1100px){.dl--pn__arrow:nth-child(2){margin-left:10px}}.dl--pn__arrow:before{display:block;content:"";width:6.875vw;height:6.875vw;background-image:url(/vendor/maimai/img/common/arrow.svg);background-size:contain;background-repeat:no-repeat}@media only screen and (min-width:769px){.dl--pn__arrow:before{width:3.6363636364vw;height:3.6363636364vw}}@media only screen and (min-width:1100px){.dl--pn__arrow:before{width:40px;height:40px}}.dl--pn__arrow.double:before{background-image:url(/vendor/maimai/img/common/arrow_double.svg)}.dl--event{margin-top:6.25vw}@media only screen and (min-width:769px){.dl--event{margin-top:4.5454545455vw}}@media only screen and (min-width:1100px){.dl--event{margin-top:50px}}.dl--event__header .common--header__txt{width:51.625vw;height:3.75vw}@media only screen and (min-width:769px){.dl--event__header .common--header__txt{width:33.4545454545vw;height:2.4545454545vw}}@media only screen and (min-width:1100px){.dl--event__header .common--header__txt{width:368px;height:27px}}.dl--event__img.i1{width:75vw;margin:5vw auto 0}@media only screen and (min-width:769px){.dl--event__img.i1{width:57.7272727273vw;margin:3.6363636364vw auto 0}}@media only screen and (min-width:1100px){.dl--event__img.i1{width:635px;margin:40px auto 0}}.dl--event__img.i2{width:75vw;margin:5vw auto 0}@media only screen and (min-width:769px){.dl--event__img.i2{width:74.5454545455vw;margin:3.6363636364vw auto 0}}@media only screen and (min-width:1100px){.dl--event__img.i2{width:820px;margin:40px auto 0}}.dl--event__img.i3{width:75vw;margin:5vw auto 0}@media only screen and (min-width:769px){.dl--event__img.i3{width:54.9090909091vw;margin:3.6363636364vw auto 0}}@media only screen and (min-width:1100px){.dl--event__img.i3{width:604px;margin:40px auto 0}}.dl--event__border{width:74.375vw;height:.75vw;background-image:url(/vendor/maimai/img/play/dash.png);background-repeat:repeat-x;margin:12.5vw auto 0}@media only screen and (min-width:769px){.dl--event__border{width:74.9090909091vw;height:.6363636364vw;margin:6.3636363636vw auto 0}}@media only screen and (min-width:1100px){.dl--event__border{width:824px;height:7px;margin:70px auto 0}}.dl--event__inner{width:75vw;margin:7.5vw auto 0}@media only screen and (min-width:769px){.dl--event__inner{width:72.7272727273vw;margin:3.6363636364vw auto 0}}@media only screen and (min-width:1100px){.dl--event__inner{width:800px;margin:40px auto 0}}.dl--event__text{font-size:2.5vw;padding-left:1em;text-indent:-1em;line-height:1.5}@media only screen and (min-width:769px){.dl--event__text{font-size:1.0909090909vw}}@media only screen and (min-width:1100px){.dl--event__text{font-size:12px}}.dl--event__btn{margin-top:7.5vw;width:75vw}@media only screen and (min-width:769px){.dl--event__btn{margin-top:5.9090909091vw;width:47.2727272727vw}}@media only screen and (min-width:1100px){.dl--event__btn{margin-top:65px;width:520px}}.dl--event__btn.bt1{height:15vw;margin-top:-11.25vw}@media only screen and (min-width:769px){.dl--event__btn.bt1{margin-top:-12.7272727273vw;margin-bottom:4.5454545455vw;margin-left:2.7272727273vw;width:54.5454545455vw;height:5.4545454545vw}}@media only screen and (min-width:1100px){.dl--event__btn.bt1{margin-top:-140px;margin-bottom:50px;margin-left:30px;width:600px;height:60px}}.dl--event__btn.bt1>span{width:57.875vw;margin-left:-5vw}@media only screen and (min-width:769px){.dl--event__btn.bt1>span{width:45.0909090909vw;margin-left:-1.8181818182vw}}@media only screen and (min-width:1100px){.dl--event__btn.bt1>span{width:496px;margin-left:-20px}}.ranking--box{margin-top:4.375vw}@media only screen and (min-width:769px){.ranking--box{margin-top:2.7272727273vw}}@media only screen and (min-width:1100px){.ranking--box{margin-top:30px}}.ranking--box__header--txt{color:#0b3871;font-size:3vw;font-weight:900;text-align:center;line-height:1.5;width:12.5vw}@media only screen and (min-width:769px){.ranking--box__header--txt{font-size:1.2857142857vw;width:9.0909090909vw}}@media only screen and (min-width:1100px){.ranking--box__header--txt{font-size:18px;width:100px}}.ranking--season{margin:2.5vw auto 0;width:75vw;position:relative;overflow:hidden;border-radius:45px}@media only screen and (min-width:769px){.ranking--season{margin-top:1.2727272727vw;width:76.7272727273vw;border-radius:2.7272727273vw}}@media only screen and (min-width:1100px){.ranking--season{margin-top:14px;width:844px;border-radius:30px}}.ranking--season__selectbox{overflow:hidden;text-align:center;width:100%;max-width:100%}.ranking--season__selectbox select{background:0 0;background-image:none;border:none;box-shadow:none;appearance:none;cursor:pointer;padding-right:1em;text-indent:.01px;text-overflow:ellipsis;outline:0;width:100%}.ranking--season__selectbox select::-ms-expand{display:none}.ranking--season__selectbox.slb{background:0 0;border:1vw solid #ececec;border-radius:45px;position:relative}@media only screen and (min-width:769px){.ranking--season__selectbox.slb{border-width:.3636363636vw;border-radius:2.7272727273vw}}@media only screen and (min-width:1100px){.ranking--season__selectbox.slb{border-width:4px;border-radius:30px}}.ranking--season__selectbox.slb select{color:#0b3871;font-size:3vw;font-weight:900;padding:2.75vw 0 2.75vw 16.25vw}@media only screen and (min-width:769px){.ranking--season__selectbox.slb select{font-size:2.1818181818vw;padding:1.1818181818vw 0 1.0909090909vw 26.3636363636vw}}@media only screen and (min-width:1100px){.ranking--season__selectbox.slb select{font-size:24px;padding:13px 0 12px 290px}}.ranking--season__btn{background-color:#e72139;box-sizing:border-box;pointer-events:none;position:absolute;top:0;right:0;height:110%;width:10.75vw;padding:0;z-index:2}@media only screen and (min-width:769px){.ranking--season__btn{width:5.4545454545vw}}@media only screen and (min-width:1100px){.ranking--season__btn{width:60px}}.ranking--season__btn:after{content:"";display:block;background:url(/vendor/maimai/img/common/icon_select.png) no-repeat;background-size:contain;position:absolute;top:4.625vw;right:4.5vw;height:2.25vw;width:2.5vw;z-index:3}@media only screen and (min-width:769px){.ranking--season__btn:after{top:2.1818181818vw;right:2.1818181818vw;height:1.2727272727vw;width:1.3636363636vw}}@media only screen and (min-width:1100px){.ranking--season__btn:after{top:24px;right:24px;height:14px;width:15px}}.ranking--date{font-size:2.75vw;margin-top:3.5vw;text-align:center}@media only screen and (min-width:769px){.ranking--date{font-size:1.4545454545vw;margin-top:1.6363636364vw}}@media only screen and (min-width:1100px){.ranking--date{font-size:16px;margin-top:18px}}.ranking--list{margin:2vw auto 0;width:75vw}@media only screen and (min-width:769px){.ranking--list{margin-top:1.2727272727vw;width:74.5454545455vw}}@media only screen and (min-width:1100px){.ranking--list{margin-top:14px;width:820px}}.ranking--list__item{padding:4.5vw 0;width:100%;position:relative}@media only screen and (min-width:769px){.ranking--list__item{padding:2.7272727273vw 0 2.4545454545vw}}@media only screen and (min-width:1100px){.ranking--list__item{padding:30px 0 27px}}.ranking--list__item:nth-of-type(n+2):before{content:"";display:block;background-color:#e5e5e5;border-radius:.5vw;height:1vw;width:100%;position:absolute;top:0;left:0;right:0}@media only screen and (min-width:769px){.ranking--list__item:nth-of-type(n+2):before{border-radius:.2727272727vw;height:.5454545455vw}}@media only screen and (min-width:1100px){.ranking--list__item:nth-of-type(n+2):before{border-radius:3px;height:6px}}.ranking--list__wrap{display:flex;flex-wrap:wrap;align-items:center;justify-content:center}.ranking--list__rank{display:flex;flex-wrap:wrap;justify-content:center;width:12.5vw}@media only screen and (min-width:769px){.ranking--list__rank{width:13.5454545455vw;padding-right:3.8181818182vw}}@media only screen and (min-width:1100px){.ranking--list__rank{width:149px;padding-right:42px}}.ranking--list__rank .ranking--list__num{background-repeat:no-repeat;background-size:100% auto;user-select:none;height:6.25vw;width:5.25vw}@media only screen and (min-width:769px){.ranking--list__rank .ranking--list__num{height:4.5454545455vw;width:3.8181818182vw}}@media only screen and (min-width:1100px){.ranking--list__rank .ranking--list__num{height:50px;width:42px}}.ranking--list__rank .ranking--list__num:nth-of-type(2n){margin-left:-.5vw}@media only screen and (min-width:769px){.ranking--list__rank .ranking--list__num:nth-of-type(2n){margin-left:-.3636363636vw}}@media only screen and (min-width:1100px){.ranking--list__rank .ranking--list__num:nth-of-type(2n){margin-left:-4px}}.ranking--list__rank .ranking--list__num.num0{background-image:url(/vendor/maimai/img/ranking/rank_num_sv0.png);height:6.25vw;width:5.375vw}@media only screen and (min-width:769px){.ranking--list__rank .ranking--list__num.num0{height:4.5454545455vw;width:3.7272727273vw}}@media only screen and (min-width:1100px){.ranking--list__rank .ranking--list__num.num0{height:50px;width:41px}}.ranking--list__rank .ranking--list__num.num1{background-image:url(/vendor/maimai/img/ranking/rank_num_sv1.png);height:6.375vw;width:5.125vw}@media only screen and (min-width:769px){.ranking--list__rank .ranking--list__num.num1{height:4.6363636364vw;width:3.7272727273vw}}@media only screen and (min-width:1100px){.ranking--list__rank .ranking--list__num.num1{height:51px;width:41px}}.ranking--list__rank .ranking--list__num.num2{background-image:url(/vendor/maimai/img/ranking/rank_num_sv2.png)}.ranking--list__rank .ranking--list__num.num3{background-image:url(/vendor/maimai/img/ranking/rank_num_sv3.png)}.ranking--list__rank .ranking--list__num.num4{background-image:url(/vendor/maimai/img/ranking/rank_num_sv4.png)}.ranking--list__rank .ranking--list__num.num5{background-image:url(/vendor/maimai/img/ranking/rank_num_sv5.png)}.ranking--list__rank .ranking--list__num.num6{background-image:url(/vendor/maimai/img/ranking/rank_num_sv6.png)}.ranking--list__rank .ranking--list__num.num7{background-image:url(/vendor/maimai/img/ranking/rank_num_sv7.png)}.ranking--list__rank .ranking--list__num.num8{background-image:url(/vendor/maimai/img/ranking/rank_num_sv8.png)}.ranking--list__rank .ranking--list__num.num9{background-image:url(/vendor/maimai/img/ranking/rank_num_sv9.png)}.ranking--list__rank.gold .ranking--list__num.num1{background-image:url(/vendor/maimai/img/ranking/rank_num_gold1.png);height:6.125vw;width:5vw}@media only screen and (min-width:769px){.ranking--list__rank.gold .ranking--list__num.num1{height:4.5454545455vw;width:3.7272727273vw}}@media only screen and (min-width:1100px){.ranking--list__rank.gold .ranking--list__num.num1{height:50px;width:41px}}.ranking--list__rank.bronze .ranking--list__num.num0{background-image:url(/vendor/maimai/img/ranking/rank_num_bz0.png);height:6.25vw;width:5.375vw}@media only screen and (min-width:769px){.ranking--list__rank.bronze .ranking--list__num.num0{height:4.5454545455vw;width:3.9090909091vw}}@media only screen and (min-width:1100px){.ranking--list__rank.bronze .ranking--list__num.num0{height:50px;width:43px}}.ranking--list__rank.bronze .ranking--list__num.num1{background-image:url(/vendor/maimai/img/ranking/rank_num_bz1.png);width:5.125vw}@media only screen and (min-width:769px){.ranking--list__rank.bronze .ranking--list__num.num1{width:3.7272727273vw}}@media only screen and (min-width:1100px){.ranking--list__rank.bronze .ranking--list__num.num1{width:41px}}.ranking--list__rank.bronze .ranking--list__num.num2{background-image:url(/vendor/maimai/img/ranking/rank_num_bz2.png)}.ranking--list__rank.bronze .ranking--list__num.num3{background-image:url(/vendor/maimai/img/ranking/rank_num_bz3.png)}.ranking--list__rank.bronze .ranking--list__num.num4{background-image:url(/vendor/maimai/img/ranking/rank_num_bz4.png)}.ranking--list__rank.bronze .ranking--list__num.num5{background-image:url(/vendor/maimai/img/ranking/rank_num_bz5.png)}.ranking--list__rank.bronze .ranking--list__num.num6{background-image:url(/vendor/maimai/img/ranking/rank_num_bz6.png)}.ranking--list__rank.bronze .ranking--list__num.num7{background-image:url(/vendor/maimai/img/ranking/rank_num_bz7.png)}.ranking--list__rank.bronze .ranking--list__num.num8{background-image:url(/vendor/maimai/img/ranking/rank_num_bz8.png)}.ranking--list__rank.bronze .ranking--list__num.num9{background-image:url(/vendor/maimai/img/ranking/rank_num_bz9.png)}.ranking--list__rank.bronze .ranking--list__num.num100{background-image:url(/vendor/maimai/img/ranking/rank_num_bz100.png);width:14.125vw}@media only screen and (min-width:769px){.ranking--list__rank.bronze .ranking--list__num.num100{height:4.6363636364vw;width:10.2727272727vw}}@media only screen and (min-width:1100px){.ranking--list__rank.bronze .ranking--list__num.num100{height:51px;width:113px}}.ranking--list__dtl{margin-left:3.75vw;width:57.5vw}@media only screen and (min-width:769px){.ranking--list__dtl{display:flex;flex-wrap:wrap;justify-content:flex-start;align-items:baseline;margin-left:0;width:60.9090909091vw}}@media only screen and (min-width:1100px){.ranking--list__dtl{width:670px}}.ranking--list__dtl__txt{display:flex;flex-wrap:wrap;align-items:baseline;font-weight:800}@media only screen and (min-width:769px){.ranking--list__dtl__txt{align-items:center}}.ranking--list__dtl__txt .tag{background-color:#0b3871;border-radius:1.375vw;color:#fff;display:inline-block;font-size:2.5vw;padding:.125vw 0 .25vw;text-align:center;width:10.5vw}@media only screen and (min-width:769px){.ranking--list__dtl__txt .tag{border-radius:.6363636364vw;font-size:1.0909090909vw;padding:.0909090909vw 0;margin-top:.4545454545vw;width:4.5454545455vw}}@media only screen and (min-width:1100px){.ranking--list__dtl__txt .tag{border-radius:7px;font-size:12px;padding:1px 0;margin-top:5px;width:50px}}.ranking--list__dtl__txt .name,.ranking--list__dtl__txt .point{font-size:3.5vw;margin-left:1.875vw}@media only screen and (min-width:769px){.ranking--list__dtl__txt .name,.ranking--list__dtl__txt .point{font-size:2.1818181818vw;margin-left:1.8181818182vw}}@media only screen and (min-width:1100px){.ranking--list__dtl__txt .name,.ranking--list__dtl__txt .point{font-size:24px;margin-left:20px}}.ranking--list__dtl__txt:last-of-type{margin-top:2.75vw;padding-top:2.375vw;position:relative}@media only screen and (min-width:769px){.ranking--list__dtl__txt:last-of-type{margin-top:0;padding:1.6363636364vw 1.0909090909vw 1.6363636364vw 2.5454545455vw}}@media only screen and (min-width:1100px){.ranking--list__dtl__txt:last-of-type{padding:18px 12px 18px 28px}}.ranking--list__dtl__txt:last-of-type:before{content:"";display:block;background:transparent url(/vendor/maimai/img/ranking/line_dashed_sp.png) no-repeat;background-size:57.5vw auto;height:.5vw;width:57.5vw;position:absolute;top:0;right:-.625vw}@media only screen and (min-width:769px){.ranking--list__dtl__txt:last-of-type:before{background-image:url(/vendor/maimai/img/ranking/line_dashed_pc.png);background-size:.3636363636vw auto;height:5.4545454545vw;width:.3636363636vw;right:initial;left:0}}@media only screen and (min-width:1100px){.ranking--list__dtl__txt:last-of-type:before{background-size:4px auto;height:60px;width:4px}}@media only screen and (min-width:769px){.ranking--list__dtl__txt:first-of-type{width:35.4545454545vw}}@media only screen and (min-width:1100px){.ranking--list__dtl__txt:first-of-type{width:390px}}::selection{background:#001518;color:#ffcedf}body,html{height:100%;color:#000;background-color:#fff;font-family:"M PLUS 1p",sans-serif;-webkit-font-smoothing:antialiased}:focus{outline:0}*{box-sizing:border-box}body{width:100%}a{color:#000;text-decoration:none}h1,h2,h3{line-height:1}li,ul{list-style:none}td,th,tr{font-weight:400;text-align:left}img,picture{width:100%}.pc-none{display:none}@media only screen and (max-width:769px){.pc-none{display:block}}@media only screen and (max-width:769px){.sp-none{display:none}}.wrapper{position:relative;z-index:2} diff --git a/scripts/dan-settlement-e2e.mjs b/scripts/dan-settlement-e2e.mjs new file mode 100644 index 0000000..5fba8f4 --- /dev/null +++ b/scripts/dan-settlement-e2e.mjs @@ -0,0 +1,588 @@ +/** + * 段位認定结算页的浏览器 E2E:真的起一个 headless Edge,打开 /dan.html, + * 把整轮结算那一屏量一遍,顺手出三张图给 paint-probe.py 数像素: + * + * 1. 四道都打满(SSS+,达成率全金)—— 版式与着色是否照官方那套 + * 2. 四道达成率故意拉开(金 / 蓝 / 红)—— 分数分色到底有没有落实 + * 3. 一道超时取消 —— 不合格那一屏的占位符与红心 + * + * 需要本地已经跑着一个服务(默认 `127.0.0.1:8789`,库 `data/dan-ui.sqlite`): + * + * node scripts/dan-settlement-e2e.mjs + * DAN_BASE=http://127.0.0.1:8788 DAN_DB=data/demo-b50.sqlite node scripts/dan-settlement-e2e.mjs + * + * 这不是 `node --test` 那套单测(那些不碰浏览器);这个脚本要 Edge、要一个跑着的服务, + * 所以不进 CI,只当本地验收用。段位规则本身由 tests/dan.test.ts 覆盖。 + */ +import { spawn } from 'node:child_process'; +import { existsSync, mkdirSync, mkdtempSync, readFileSync, writeFileSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { dirname, join, resolve } from 'node:path'; +import { setTimeout as sleep } from 'node:timers/promises'; +import { DatabaseSync } from 'node:sqlite'; +import { fileURLToPath, pathToFileURL } from 'node:url'; + +const REPO = resolve(dirname(fileURLToPath(import.meta.url)), '..'); +const DB = process.env.DAN_DB ? resolve(process.env.DAN_DB) : join(REPO, 'data', 'dan-ui.sqlite'); +const BASE = process.env.DAN_BASE ?? 'http://127.0.0.1:8789'; +const SHOTS = process.env.DAN_SHOTS ?? join(tmpdir(), 'dan-e2e-shots'); +const EDGE = process.env.DAN_EDGE ?? 'C:/Program Files (x86)/Microsoft/Edge/Application/msedge.exe'; +const USER = Number(process.env.DAN_USER ?? 1); + +/** 结算盘坐标:底图 980×928,逐题行与底部三块都按底图里量出来的位置。 */ +const ROW = { left: 267, width: 576, tops: [152, 274, 396, 518] }; +const PLATE = { left: 74, top: 672, w: 240, h: 230 }; // 左下段位名牌 +const LIFE = { left: 334, top: 690, w: 200, h: 200 }; +const TOTAL_LABEL = { left: 503, top: 663, w: 354, h: 52 }; // 底图预留的白格 +const DX_ROW = { left: 608, top: 810, w: 360, h: 80 }; // 底图预留的白牌 +const RIGHT_EDGE = 950; // 大数字的右端(与 DX 白牌右端同一条边) +/** + * 逐题行内部的版式(**行内坐标**)。每个数字都是从原版 `UI_CMN_RSL_KopMBase_MST.png` + * (636×136 的精裁版,官方设计尺寸 640×140)逐行扫像素量出来的,换算见 public/dan.css: + * `行内坐标 = (原版文件坐标 + 2) × 0.9`(我们的行内区 576×126 = 官方 640×140 × 0.9)。 + */ +const CARD = { + frame: { left: 3.6, top: 3.6, w: 568.8, h: 118.8 }, // 边框本体(外圈 2px 主色环) + slot: { left: 10.8, top: 10.8, w: 105.3, h: 105.3 }, // 左上的曲绘槽(原版是方槽) + titlebar: { left: 122.4, top: 10.8, w: 442.8, h: 18 }, // 深蓝歌名条(占顶部一整行) + achbox: { left: 122.4, top: 36, w: 328.5, h: 61.2 }, // 白色达成率盒(右下角一道大 R 弧) + band: { left: 122.4, top: 97.2, w: 345.6, h: 23.4 }, // 底部浅色带 + dxbox: { left: 450.9, top: 97.2, w: 119.7, h: 23.4 }, // 右下角 DX 分数白牌(胶囊) + badge: { left: 464, top: 43, w: 99, h: 40 }, // 右上紫场里的评级徽章 +}; +const near = (value, want, tol = 2) => Math.abs(value - want) <= tol; +const rectNear = (rect, want, tol = 2, keys = ['left', 'top', 'w', 'h']) => Boolean(rect) + && keys.every((key) => near(rect[key], want[key], tol)); +const rectIn = (inner, outer, slack = 2) => Boolean(inner && outer) + && inner.left >= outer.left - slack && inner.top >= outer.top - slack + && inner.left + inner.w <= outer.left + outer.w + slack + && inner.top + inner.h <= outer.top + outer.h + slack; +const fmtRect = (rect) => (rect ? `${rect.left},${rect.top} ${rect.w}×${rect.h}` : '缺失'); +/** 等级 → 素材:官方 `UI_GAM_Rank_*`(游戏内那套单级全套)。 */ +const RANK_FILE = { + 'SSS+': 'gam-rank-sssp.png', SSS: 'gam-rank-sss.png', 'SS+': 'gam-rank-ssp.png', SS: 'gam-rank-ss.png', + 'S+': 'gam-rank-sp.png', S: 'gam-rank-s.png', AAA: 'gam-rank-aaa.png', AA: 'gam-rank-aa.png', + A: 'gam-rank-a.png', BBB: 'gam-rank-bbb.png', BB: 'gam-rank-bb.png', B: 'gam-rank-b.png', + C: 'gam-rank-c.png', D: 'gam-rank-d.png', +}; + +let failures = 0; +const check = (label, ok, extra = '') => { + console.log(`${ok ? ' PASS' : ' FAIL'} ${label}${extra ? ` — ${extra}` : ''}`); + if (!ok) failures += 1; +}; +const open = () => { + const db = new DatabaseSync(DB); + db.exec('PRAGMA busy_timeout = 8000'); + return db; +}; +const call = async (path, body) => { + const res = await fetch(`${BASE}${path}`, body + ? { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) } + : { cache: 'no-store' }); + return { status: res.status, body: await res.json() }; +}; +const run = async (path, body) => { + const res = await call(path, body); + if (res.status !== 200) throw new Error(`${path} → HTTP ${res.status} ${JSON.stringify(res.body)}`); + return res.body; +}; + +if (!existsSync(EDGE)) { + console.log(`Edge 不在 ${EDGE},跳过浏览器验收`); + process.exit(0); +} +if (!existsSync(SHOTS)) mkdirSync(SHOTS, { recursive: true }); + +/** 题目名:先看本人提交里的真名,没有就尽力从 CF 全量清单里查一次,都没有就退回题号。 */ +let cfNames = null; +const problemName = async (problemId) => { + const known = open().prepare( + "SELECT MAX(problem_title) AS title FROM submissions WHERE platform='codeforces' AND problem_id=?").get(problemId); + if (known?.title) return known.title; + if (cfNames === null) { + cfNames = new Map(); + try { + const res = await fetch('https://codeforces.com/api/problemset.problems', { signal: AbortSignal.timeout(20000) }); + const body = await res.json(); + for (const p of body?.result?.problems ?? []) cfNames.set(`${p.contestId}:${p.index}`, p.name); + } catch { /* 离线就用题号,不影响验收 */ } + } + return cfNames.get(problemId) ?? problemId; +}; + +const { secondsForAchievement } = await import(pathToFileURL(join(REPO, 'src', 'dx', 'rating.ts')).href); + +/* ---------- 造一轮:四道都按指定达成率通关 ---------- */ + +const seeded = []; + +async function clearRun({ tier, kind, achievements }) { + seeded.length = 0; + const started = await run('/api/dan/start', { userId: USER, kind, tier, tz: 480 }); + const sessionId = started.session.id; + const stageCount = started.session.stageCount; + for (let round = 1; round <= stageCount; round += 1) { + await run('/api/dan/claim', { userId: USER, sessionId }); + const timer = open().prepare( + "SELECT id, problem_id FROM practice_timers WHERE status='running' ORDER BY started_at DESC LIMIT 1").get(); + const stage = open().prepare( + 'SELECT difficulty FROM dan_stages WHERE session_id=? AND stage_index=?').get(sessionId, round); + const now = Math.floor(Date.now() / 1000); + const target = achievements[round - 1]; + // `SLOW_SECONDS_FACTOR[i]` 把第 i 道的时间乘一个系数 —— 评分曲线只在 ≥97% 那段可逆, + const seconds = Math.round(secondsForAchievement(stage.difficulty, target).seconds); + const title = await problemName(timer.problem_id); + const write = open(); + write.prepare('UPDATE practice_timers SET started_at=? WHERE id=?').run(now - seconds, timer.id); + write.prepare(`INSERT INTO submissions(account_id,platform,submission_id,problem_id,problem_title,status,submitted_at,difficulty) + VALUES(?,?,?,?,?,?,?,?)`).run(accountId, 'codeforces', `dan-e2e-${sessionId.slice(0, 6)}-${round}-${now}`, + timer.problem_id, title, 'AC', now, stage.difficulty); + write.close(); + seeded.push({ sessionId, index: round, title }); + await run('/api/dan/settle', { userId: USER, tz: 480 }); + if (round < stageCount) await run('/api/dan/next', { userId: USER, tz: 480 }); + } + const row = (await call(`/api/dan?user=${USER}&tz=480`)).body.history.find((r) => r.id === sessionId); + return row; +} + +// 清场:收掉遗留的活动会话、清掉 dan 记录,从零开始。 +const overview = (await call(`/api/dan?user=${USER}&tz=480`)).body; +if (overview.active) await call('/api/dan/abandon', { userId: USER, sessionId: overview.active.id }); +{ + const clean = open(); + clean.exec('DELETE FROM dan_stages; DELETE FROM dan_sessions; DELETE FROM practice_timers;'); + clean.close(); +} +const accountId = Number(open().prepare( + "SELECT id FROM accounts WHERE platform='codeforces' AND is_archived=0 ORDER BY id LIMIT 1").get().id); +console.log(`账号 id=${accountId},库 ${DB},服务 ${BASE},截图 ${SHOTS}`); + +console.log('\n=== 1) 四道都打满(SSS+) ==='); +const perfect = await clearRun({ tier: 'advanced', kind: 'challenge', achievements: [100.8, 100.8, 100.8, 100.8] }); +check('这一轮判为 cleared', perfect?.status === 'cleared', `${perfect?.status}`); +check('四道都记了达成率与单题 rating', + perfect?.stages.every((s) => typeof s.achievement === 'number' && typeof s.rating === 'number'), + perfect?.stages.map((s) => `${s.difficulty}:${s.achievement.toFixed(2)}%/${s.rating}`).join(' ')); +const expectAch = perfect.stages.reduce((sum, s) => sum + s.achievement, 0); +const expectRating = Math.round(perfect.stages.reduce((sum, s) => sum + s.rating, 0) * 10) / 10; +check('本轮总分 = 四道单题 rating 之和', Math.abs(perfect.totalRating - expectRating) < 0.05, + `${perfect.totalRating} vs ${expectRating}`); + +/* ---------- 起浏览器 ---------- */ + +const dir = mkdtempSync(join(tmpdir(), 'dan-e2e-')); +const edge = spawn(EDGE, ['--headless=new', '--remote-debugging-port=0', `--user-data-dir=${dir}`, + '--no-first-run', '--no-default-browser-check', '--window-size=1440,1400', 'about:blank'], + { stdio: 'ignore', windowsHide: true }); + +let cdp = null; +const problems = []; +const badResponses = []; +try { + for (let i = 0; i < 40 && !cdp; i += 1) { + await sleep(250); + try { + const [port, wsPath] = readFileSync(join(dir, 'DevToolsActivePort'), 'utf8').trim().split(/\r?\n/); + cdp = await (await import(pathToFileURL(join(REPO, 'src', 'fetchers', 'cf-browser.ts')).href)) + .Cdp.connect(`ws://127.0.0.1:${port}${wsPath}`); + } catch { /* Edge 还没起来 */ } + } + if (!cdp) throw new Error('没连上 Edge 的 CDP'); + const { targetId } = await cdp.call('Target.createTarget', { url: 'about:blank' }); + const { sessionId } = await cdp.call('Target.attachToTarget', { targetId, flatten: true }); + cdp.socket.addEventListener('message', (event) => { + const message = JSON.parse(String(event.data)); + if (message.method === 'Runtime.exceptionThrown') { + problems.push(`异常: ${message.params.exceptionDetails.text} ${message.params.exceptionDetails.exception?.description ?? ''}`); + } + if (message.method === 'Runtime.consoleAPICalled' && ['error', 'warning'].includes(message.params.type)) { + problems.push(`console.${message.params.type}: ${message.params.args.map((a) => a.value ?? a.description).join(' ')}`); + } + if (message.method === 'Network.responseReceived') { + const { url, status } = message.params.response; + if (status >= 400 && url.includes('127.0.0.1')) badResponses.push(`${status} ${url}`); + } + }); + for (const domain of ['Runtime', 'Log', 'Page', 'Network']) await cdp.call(`${domain}.enable`, {}, sessionId); + await cdp.call('Emulation.setDeviceMetricsOverride', + { width: 1440, height: 1400, deviceScaleFactor: 1, mobile: false }, sessionId); + + const evaluate = async (expression) => { + const result = await cdp.call('Runtime.evaluate', { expression, returnByValue: true, awaitPromise: true }, sessionId); + if (result.exceptionDetails) throw new Error(`页面内异常 ${JSON.stringify(result.exceptionDetails)}`); + return result.result.value; + }; + const openPage = async (url, wait = 3200) => { await cdp.call('Page.navigate', { url }, sessionId); await sleep(wait); }; + const shot = async (name) => { + const { data } = await cdp.call('Page.captureScreenshot', { format: 'png', captureBeyondViewport: true }, sessionId); + writeFileSync(join(SHOTS, name), Buffer.from(data, 'base64')); + console.log(` 截图 → ${join(SHOTS, name)}`); + }; + /** 只拍结算盘本身(文档插图用,不带上页面下那堆说明)。 */ + const shotDialog = async (name) => { + const rect = JSON.parse(await evaluate(`(() => { + const d = document.querySelector('dialog.dan-result'); + if (!d) return 'null'; + const r = d.getBoundingClientRect(); + return JSON.stringify({ x: r.left + window.scrollX, y: r.top + window.scrollY, + width: r.width, height: r.height }); + })()`)); + if (!rect) return; + const { data } = await cdp.call('Page.captureScreenshot', + { format: 'png', clip: { ...rect, scale: 2 }, captureBeyondViewport: true }, sessionId); + writeFileSync(join(SHOTS, name), Buffer.from(data, 'base64')); + console.log(` 截图 → ${join(SHOTS, name)}`); + }; + const clickByText = (selector, text) => evaluate(`(() => { + const node = [...document.querySelectorAll(${JSON.stringify(selector)})].find((n) => n.textContent.includes(${JSON.stringify(text)})); + if (!node) return 'NOT_FOUND'; + node.click(); + return 'CLICKED'; + })()`); + /** 打开最新一轮的结算页:认定记录里那条「查看结算」。 */ + const openLatestResult = async () => { + await openPage(`${BASE}/dan.html`); + const clicked = await clickByText('.dan-actions .btn', '查看结算'); + await sleep(1500); + return clicked; + }; + /** 读结算盘:行内组件用**行底板**做原点,底部组件用底图做原点(都是底图 980×928 那一套坐标)。 */ + const readLayout = () => evaluate(`(() => { + const dialog = document.querySelector('dialog.dan-result'); + const art = dialog?.querySelector('.dan-result-art'); + if (!art) return JSON.stringify({ open: false, rows: [] }); + const box = art.getBoundingClientRect(); + const relTo = (origin, node) => { + if (!node) return null; + const r = node.getBoundingClientRect(); + return { left: Math.round(r.left - origin.left), top: Math.round(r.top - origin.top), + w: Math.round(r.width), h: Math.round(r.height) }; + }; + const rel = (node) => relTo(box, node); + const text = (root, sel) => root.querySelector(sel)?.textContent ?? null; + // aria-label 挂在最里面那层 .dan-num 上;选择器可能指的是包着它的容器。 + const numNode = (root, sel) => { + const node = root.querySelector(sel); + if (!node) return null; + return node.classList.contains('dan-num') ? node : node.querySelector('.dan-num'); + }; + const shown = (root, sel) => numNode(root, sel)?.getAttribute('aria-label') ?? null; + const font = (root, sel) => { + const node = numNode(root, sel); + if (!node) return null; + // 真正上色/上贴图的是里面那两层 :分数族走 background-image(自带颜色), + // 白字族走 mask + background-color(游戏里就是这样运行时染色的)。 + const fill = node.querySelector('.dan-num-fill'); + const style = getComputedStyle(fill ?? node); + const cell = node.querySelector('.dan-num-char'); + const cellStyle = cell ? getComputedStyle(cell) : null; + const advance = cell + ? cell.getBoundingClientRect().width + (parseFloat(cellStyle.marginRight) || 0) : 0; + // 图集里没有的字符(「—」这种占位符)走普通文字那条路:颜色落在文字本身上。 + const plain = node.querySelector('.dan-num-plain'); + return { + fill: plain ? getComputedStyle(plain).color : style.backgroundColor, + mask: style.maskImage || style.webkitMaskImage, + atlas: style.backgroundImage, + outline: Boolean(node.querySelector('.dan-num-outline')), + label: node.getAttribute('aria-label'), + height: Math.round(node.getBoundingClientRect().height), + advance: Math.round(advance * 100) / 100, + width: Math.round(node.getBoundingClientRect().width), + }; + }; + return JSON.stringify({ + open: Boolean(dialog?.open), + plate: { rect: rel(dialog.querySelector('.dan-verdict')), name: text(dialog, '.dan-plate-name'), + nameLen: dialog.querySelector('.dan-plate-name')?.dataset.len ?? null, + nameFont: (() => { const n = dialog.querySelector('.dan-plate-name'); if (!n) return null; + const s = getComputedStyle(n); return { size: s.fontSize, weight: s.fontWeight, fill: s.color }; })(), + count: text(dialog, '.dan-verdict-count'), + legacyStamp: Boolean(dialog.querySelector('.dan-verdict-img')), + nameRect: rel(dialog.querySelector('.dan-plate-name')) }, + life: { rect: rel(dialog.querySelector('.dan-life')), label: shown(dialog, '.dan-life-count'), + base: dialog.querySelector('.dan-life-base')?.getAttribute('src')?.split('/').pop() ?? null }, + total: { label: text(dialog, '.dan-total-label'), labelRect: rel(dialog.querySelector('.dan-total-label')), + num: shown(dialog, '.dan-total-num'), rect: rel(dialog.querySelector('.dan-total-num')), + font: font(dialog, '.dan-total-num') }, + dx: { label: text(dialog, '.dan-dxscore-label'), num: shown(dialog, '.dan-dxscore-num'), + rect: rel(numNode(dialog, '.dan-dxscore-num')), rowRect: rel(dialog.querySelector('.dan-dxscore-row')), + labelRect: rel(dialog.querySelector('.dan-dxscore-label')), font: font(dialog, '.dan-dxscore-num') }, + rows: [...dialog.querySelectorAll('.dan-track')].map((track) => { + const rowBox = track.getBoundingClientRect(); + return { + rect: rel(track), + no: text(track, '.dan-track-no'), name: text(track, '.dan-track-name'), + // 档位名牌已经删掉;这两项留着是为了断言它**不在**(并且标题条左移顶上那一格)。 + diff: text(track, '.dan-track-diff'), diffLines: track.querySelectorAll('.dan-track-diff > *').length, + titlebar: relTo(rowBox, track.querySelector('.dan-track-titlebar')), + jacket: text(track, '.dan-track-jacket'), jacketBig: text(track, '.dan-track-jacket b'), + jacketSmall: text(track, '.dan-track-jacket small'), + ach: shown(track, '.dan-track-achvalue'), achFont: font(track, '.dan-track-achvalue'), + // 达成率那串数字自己的框(取里面那层 .dan-num,不是外面那个定宽容器)。 + achNumRect: relTo(rowBox, numNode(track, '.dan-track-achvalue')), + dx: shown(track, '.dan-track-dxnum'), dxFont: font(track, '.dan-track-dxnum'), + limit: text(track, '.dan-track-limit'), judge: text(track, '.dan-track-judge'), + badge: track.querySelector('.dan-track-badge')?.getAttribute('src')?.split('/').pop() ?? '', + // badgeRect / stamp 是**底图坐标**(抠图用),badgeRow 才是行内坐标。 + badgeRect: rel(track.querySelector('.dan-track-badge')), + badgeRow: relTo(rowBox, track.querySelector('.dan-track-badge')), + stamp: rel(track.querySelector('.dan-track-stamp')), + boxes: ['no', 'jacket', 'dxbox', 'dxnum', 'titlebar', 'name', 'diff', 'achbox', 'achvalue', + 'badge', 'limit', 'frame', 'band'] + .map((key) => [key, relTo(rowBox, track.querySelector('.dan-track-' + key))]).filter(([, r]) => r), + }; + }), + }); + })()`); + + console.log('\n=== 2) 结算页版式(四道满分成 → 金) ==='); + check('认定记录里点得到「查看结算」', await openLatestResult() === 'CLICKED'); + const page = JSON.parse(await readLayout()); + check('结算页弹出来了', page.open === true); + const rows = page.rows ?? []; + check('四道各一张卡', rows.length === 4, `${rows.length} 张`); + check('四张卡按官方坐标摆:x 267 宽 576,行距 122', + rows.every((row, i) => Math.abs(row.rect.left - ROW.left) <= 2 && Math.abs(row.rect.w - ROW.width) <= 2 + && Math.abs(row.rect.top - ROW.tops[i]) <= 2), + rows.map((row) => `${row.rect.top}@${row.rect.w}`).join(' ')); + const outOfRow = []; + for (const row of rows) { + for (const [key, rect] of row.boxes) { + if (rect.left < -2 || rect.top < -2 || rect.left + rect.w > row.rect.w + 2 || rect.top + rect.h > row.rect.h + 2) { + outOfRow.push(`${row.no}:${key}`); + } + } + } + check('行内组件都待在底板里', outOfRow.length === 0, outOfRow.join(',')); + /** 行内子组件的 rect(`boxes` 里量的,全部是**行内坐标**)。 */ + const boxOf = (row, key) => (row.boxes.find(([name]) => name === key) ?? [])[1]; + check('边框本体照原版量出来的比例摆(行内 3.6,3.6 / 568.8×118.8)', + rows.every((row) => rectNear(boxOf(row, 'frame'), CARD.frame)), + fmtRect(boxOf(rows[0], 'frame'))); + check('曲绘槽是原版那块方槽(行内 10.8,10.8 / 105.3×105.3),里面写完整题号 + 这一题的 CF rating', + rows.every((row) => rectNear(boxOf(row, 'jacket'), CARD.slot) + && /^\d+[A-Z]+\d*$/.test(row.jacketBig) && /^\d+$/.test(row.jacketSmall)), + `${fmtRect(boxOf(rows[0], 'jacket'))} · ${rows[0].jacketBig}/${rows[0].jacketSmall}`); + check('难度名牌已删掉,歌名条占顶部一整行(左端与白盒左端对齐、右端贴到边框内右沿)', + rows.every((row) => !row.diff && row.diffLines === 0 + && rectNear(row.titlebar, CARD.titlebar) + // 「占一整行」= 左端跟白盒左端对齐(原版那格难度名牌的位置被歌名条顶掉), + // 右端离边框外沿只剩 7.2px(原版 8px × 0.9)。 + && near(row.titlebar.left, boxOf(row, 'achbox').left) + && near(row.titlebar.left + row.titlebar.w, CARD.frame.left + CARD.frame.w - 7.2) + && near(row.titlebar.top, CARD.frame.top + 7.2)), + rows.map((row) => `diff=${row.diff || '无'} 条 ${row.titlebar?.left}…${row.titlebar?.left + row.titlebar?.w}`).join(' ')); + // 达成率那串数字必须待在白盒里,而且白盒不能碰到右边那块紫场里的评级徽章 + // (原版白盒右下角那道大 R 弧就是给徽章让位的)。 + const spill = rows.map((row) => { + const box = boxOf(row, 'achbox'); + const num = row.achNumRect; + if (!box || !num) return 'no-box'; + const right = num.left + num.w; + if (num.left < box.left) return `数字出盒左 ${num.left} < ${box.left}`; + if (right > box.left + box.w - 6) return `数字出盒右 ${right} > ${box.left + box.w - 6}`; + // 右对齐:整串的右端固定在离白盒右沿 6…20px 的地方(短一位数的串不许往左缩)。 + if (right < box.left + box.w - 20) return `数字没右对齐 ${right} < ${box.left + box.w - 20}`; + if (num.top < box.top || num.top + num.h > box.top + box.h + 1) { + return `数字出盒高 ${num.top}..${num.top + num.h} vs ${box.top}..${box.top + box.h}`; + } + if (row.badgeRow && box.left + box.w + 10 > row.badgeRow.left) { + return `白盒碰徽章 ${Math.round(box.left + box.w + 10 - row.badgeRow.left)}px`; + } + return ''; + }).filter(Boolean); + check('达成率数字不溢出白盒、白盒与右侧徽章留 10px 以上空隙', + spill.length === 0, + spill.join(',') || `数字 ${fmtRect(rows[0].achNumRect)} 盒 ${fmtRect(boxOf(rows[0], 'achbox'))}` + + ` 徽章左 ${rows[0].badgeRow?.left}`); + check('DX 分数在卡片右下角、右端贴到边框内右沿(与底带同高的那块白牌)', + rows.every((row) => { + const dx = boxOf(row, 'dxbox'); + const box = boxOf(row, 'achbox'); + return rectNear(dx, CARD.dxbox) + && near(dx.left + dx.w, CARD.frame.left + CARD.frame.w - 1.8) + && dx.top >= box.top + box.h - 2 && dx.left >= box.left + box.w; + }), + fmtRect(boxOf(rows[0], 'dxbox'))); + check('限时写进底带左侧(原版那条浅色带,右端才是 DX 分数白牌)', + rows.every((row) => { + const band = boxOf(row, 'band'); + const limit = boxOf(row, 'limit'); + const dx = boxOf(row, 'dxbox'); + return rectNear(band, CARD.band) && rectIn(limit, band, 0) && near(limit.left, band.left) + && limit.left + limit.w <= dx.left + 1; + }), + `带 ${fmtRect(boxOf(rows[0], 'band'))} · 限时 ${fmtRect(boxOf(rows[0], 'limit'))}`); + check('评级徽章放进右上那块紫场(白盒右边 · 歌名条下边 · 白牌上边)', + rows.every((row) => { + if (!row.badgeRow) return true; // 超时/未开始那一屏用文字判定,没有徽章 + const box = boxOf(row, 'achbox'); + const dx = boxOf(row, 'dxbox'); + return rectNear(row.badgeRow, CARD.badge) + && row.badgeRow.left >= box.left + box.w + && row.badgeRow.top >= row.titlebar.top + row.titlebar.h + && row.badgeRow.top + row.badgeRow.h <= dx.top; + }), + fmtRect(rows[0].badgeRow)); + check('标题条写题目名,不是「段位認定第 N 道」', + rows.every((row, i) => row.name && row.name === seeded[i]?.title && !/段位認定第/.test(row.name)), + rows.map((row) => row.name).join(' | ')); + check('评级徽章是游戏内那套单级素材且按原版比例放到 99×40(不再拿「A~AAA」区间图充数)', + rows.every((row) => row.badge === 'gam-rank-sssp.png' && Math.abs(row.badgeRect.w - CARD.badge.w) <= 2 + && Math.abs(row.badgeRect.h - CARD.badge.h) <= 2), + `${rows[0]?.badge} ${rows[0]?.badgeRect?.w}×${rows[0]?.badgeRect?.h}`); + check('四道达成率都上了官方金色图集', + rows.every((row) => /num-score-gold/.test(row.achFont?.atlas ?? '')), rows.map((row) => row.achFont?.atlas).join(' | ')); + check('单题 DX 分数不叠描边层(小字要干净)', + rows.every((row) => row.dxFont?.outline === false && /rgb\(13, 42, 99\)/.test(row.dxFont?.fill ?? '')), + `描边层 ${rows[0]?.dxFont?.outline} / ${rows[0]?.dxFont?.fill}`); + + check('左下白框里写档位名(不再是合格印)', + page.plate.name === '上级' && page.plate.legacyStamp === false, `${page.plate.name} · 旧印 ${page.plate.legacyStamp}`); + check('段位名落在白框里、字重够重', + page.plate.rect.left === PLATE.left && page.plate.rect.top === PLATE.top + && page.plate.rect.w === PLATE.w && page.plate.rect.h === PLATE.h && Number(page.plate.nameFont.weight) >= 800, + `${page.plate.rect.left},${page.plate.rect.top} ${page.plate.rect.w}×${page.plate.rect.h} · ${page.plate.nameFont.size}/${page.plate.nameFont.weight}`); + check('结论变成名字下面一行小字', /合格 · 4 \/ 4 道通关/.test(page.plate.count ?? ''), page.plate.count); + check('心放大到 200×200 且用官方绿色底盘,心里写通关道数', + page.life.rect.w === LIFE.w && page.life.rect.h === LIFE.h + && page.life.base === 'life-base-green.png' && page.life.label === '4', + `${page.life.rect.w}×${page.life.rect.h} · ${page.life.base} · ${page.life.label}`); + + check('「总达成率」标签落进底图预留的白格', + Math.abs(page.total.labelRect.left - TOTAL_LABEL.left) <= 2 && Math.abs(page.total.labelRect.top - TOTAL_LABEL.top) <= 2 + && Math.abs(page.total.labelRect.w - TOTAL_LABEL.w) <= 2 && Math.abs(page.total.labelRect.h - TOTAL_LABEL.h) <= 2, + `${page.total.labelRect.left},${page.total.labelRect.top} ${page.total.labelRect.w}×${page.total.labelRect.h}`); + check('总达成率 = 四道达成率之和(官方金色图集)', + Math.abs(Number(String(page.total.num).replace('%', '')) - expectAch) < 0.01 && /%$/.test(page.total.num ?? ''), + `${page.total.num} vs ${expectAch.toFixed(4)}%`); + const totalRight = page.total.rect.left + page.total.rect.w; + check('大数字落在色带上、右端对齐 x 950', + Math.abs(totalRight - RIGHT_EDGE) <= 6 && page.total.rect.top >= 700 && page.total.rect.top + page.total.rect.h <= DX_ROW.top, + `右 ${totalRight} · y ${page.total.rect.top}..${page.total.rect.top + page.total.rect.h}`); + const glyphs = String(page.total.num).length + 1; + // 官方是密排:分数族那套图集格宽 74 / 格高 98,密排后每字步进 67 ⇒ 0.68 上下。 + check('数字密排(官方字距,不再一个字一个字分开)', + page.total.font.advance / page.total.font.height <= 0.69, + `每字步进 ${page.total.font.advance}px / 字高 ${page.total.font.height} = ${(page.total.font.advance / page.total.font.height).toFixed(3)}(占位 ${glyphs} 个字形,整串宽 ${page.total.font.width})`); + + check('DX 分数条就是底图预留的那条白牌', + Math.abs(page.dx.rowRect.left - DX_ROW.left) <= 2 && Math.abs(page.dx.rowRect.top - DX_ROW.top) <= 2 + && Math.abs(page.dx.rowRect.w - DX_ROW.w) <= 2 && Math.abs(page.dx.rowRect.h - DX_ROW.h) <= 2, + `${page.dx.rowRect.left},${page.dx.rowRect.top} ${page.dx.rowRect.w}×${page.dx.rowRect.h}`); + check('标签写「DX分数」(照官方,不再自造「合计」)', page.dx.label === 'DX分数', page.dx.label); + check('DX 分数 = 四道单题 rating 之和(深蓝、不带描边)', + Math.abs(Number(page.dx.num) - expectRating) < 0.05 && /rgb\(13, 42, 99\)/.test(page.dx.font?.fill ?? '') + && page.dx.font?.outline === false, + `${page.dx.num} vs ${expectRating} · ${page.dx.font?.fill} · 描边层 ${page.dx.font?.outline}`); + check('DX 数字在白牌右半边、且没出牌面', + page.dx.rect.left >= DX_ROW.left + 120 && page.dx.rect.left + page.dx.rect.w <= DX_ROW.left + DX_ROW.w + 2, + `${page.dx.rect.left}..${page.dx.rect.left + page.dx.rect.w} 牌面 ${DX_ROW.left}..${DX_ROW.left + DX_ROW.w}`); + + await shot('E-整轮结算页.png'); + await shotDialog('D-结算盘.png'); + // 给 paint-probe.py 数像素用的小图(按底图坐标抠) + const origin = JSON.parse(await evaluate(`(() => { + const b = document.querySelector('dialog.dan-result .dan-result-art').getBoundingClientRect(); + return JSON.stringify({ x: b.left + window.scrollX, y: b.top + window.scrollY }); + })()`)); + const row0 = rows[0]; + // 行内那些小图是**行内相对坐标**,抠图要加上第 1 行在底图上的原点,否则会拍错地方; + // 印与徽章本来就是底图坐标(readLayout 里用 rel(art) 量的),不能再加一次。 + const inRow = (rel) => (rel ? { left: rel.left + row0.rect.left, top: rel.top + row0.rect.top, w: rel.w, h: rel.h } : null); + const rectOf = (key) => inRow((row0.boxes.find(([name]) => name === key) ?? [])[1]); + const clipTargets = { + ach: rectOf('achvalue'), achbox: rectOf('achbox'), score: rectOf('dxnum'), + stamp: row0.stamp, badge: row0.badgeRect, + total: page.total.rect, dxtotal: page.dx.rect, life: page.life.rect, + }; + const rectsOut = {}; + for (const [name, rect] of Object.entries(clipTargets)) { + if (!rect) continue; + const clip = { x: origin.x + rect.left, y: origin.y + rect.top, width: rect.w, height: rect.h, scale: 1 }; + rectsOut[name] = clip; + const { data } = await cdp.call('Page.captureScreenshot', { format: 'png', clip, captureBeyondViewport: true }, sessionId); + writeFileSync(join(SHOTS, `H-${name}.png`), Buffer.from(data, 'base64')); + } + writeFileSync(join(SHOTS, 'H-rects.json'), JSON.stringify(rectsOut, null, 2)); + + /* ---------- 3) 达成率拉开:金 / 蓝 / 红 ---------- */ + + console.log('\n=== 3) 四道达成率拉开(金 / 蓝 / 红) ==='); + /* 注:达成率是由目标值反推秒数再提交的,而评分曲线只在 ≥97% 段可逆 —— 想看到 <80% 的 + * 蓝档只能从秒数那头放大(见 SLOW_SECONDS_FACTOR)。第 4 道 ×1.6 落到 60% 上下 ⇒ 蓝。 */ + let slowSeconds = []; + const mixed = await clearRun({ tier: 'advanced', kind: 'challenge', achievements: [100.8, 98.5, 96.0, 92.0] }); + check('这一轮同样判为 cleared(限时内 AC 就算过,跟达成率高低无关)', mixed?.status === 'cleared', `${mixed?.status}`); + check('四道达成率确实拉开了', + new Set(mixed.stages.map((s) => Math.round(s.achievement))).size === 4, + mixed.stages.map((s) => `${s.achievement.toFixed(2)}%`).join(' ')); + await openLatestResult(); + const mixedPage = JSON.parse(await readLayout()); + const tones = mixedPage.rows.map((row) => (/num-score-(\w+)\.png/.exec(row.achFont?.atlas ?? '') ?? [])[1]); + check('颜色按达成率换:≥97 金、80~96.99 红(合成用例只够得到这两档)', + tones.join(',') === 'gold,gold,red,red', `${tones.join(',')} · ${mixed.stages.map((s) => `${s.achievement.toFixed(2)}%`).join(' ')}`); + check('评级徽章跟着达成率走(逐级对应,不再共用一个区间图)', + mixedPage.rows.every((row, i) => row.badge === RANK_FILE[mixed.stages[i].rank]), + mixedPage.rows.map((row, i) => `${mixed.stages[i].rank}=${row.badge}`).join(' ')); + check('总达成率是四道之和 ⇒ 仍然 ≥100% ⇒ 金', + /num-score-gold/.test(mixedPage.total.font?.atlas ?? '') && /%$/.test(mixedPage.total.num ?? ''), + `${mixedPage.total.num} · ${mixedPage.total.font?.atlas}`); + await shot('F-分数分色结算页.png'); + await shotDialog('D-结算盘-分色.png'); + + /* ---------- 4) 超时取消 ---------- */ + + console.log('\n=== 4) 超时取消那一轮 ==='); + const timedId = `e2e-timeout-${Date.now()}`; + const timerId = `${timedId}-1`; + { + const t = Math.floor(Date.now() / 1000); + const write = open(); + write.prepare(`INSERT INTO dan_sessions(id,user_id,tier,kind,stage_count,limit_seconds,min_rating,max_rating,status,started_at) + VALUES(?,?,?,?,?,?,?,?,?,?)`).run(timedId, USER, 'beginner', 'challenge', 4, 1800, 800, 1100, 'active', t + 1); + write.prepare(`INSERT INTO practice_timers(id,user_id,account_id,handle_key,problem_id,practice_kind,status,started_at,ended_at) + VALUES(?,?,?,?,?,?,?,?,?)`).run(timerId, USER, accountId, 'dan-e2e', '559:A', 'unknown', 'completed', t - 4000, t - 1000); + write.prepare(`INSERT INTO dan_stages(session_id,stage_index,problem_id,difficulty,drawn_at,claimed_at,timer_id,limit_seconds) + VALUES(?,?,?,?,?,?,?,?)`).run(timedId, 1, '559:A', 800, t - 4000, t - 4000, timerId, 1800); + write.close(); + } + await call('/api/dan/settle', { userId: USER, tz: 480 }); + const failed = (await call(`/api/dan?user=${USER}&tz=480`)).body.history.find((r) => r.id === timedId); + check('超时那一道被记为 timeout', failed?.stages[0]?.outcome === 'timeout', `${failed?.stages[0]?.outcome}`); + check('这一轮判为 failed,且没有继续抽第 2 道', failed?.status === 'failed' && failed?.stages.length === 1, + `${failed?.status} / ${failed?.stages.length} 道`); + await openLatestResult(); + const failPage = JSON.parse(await readLayout()); + check('失败那一屏也弹得出来', failPage.open === true); + check('档位名照样写在白框里,结论是小字「不合格」', + failPage.plate.name === '初级' && /不合格 · 0 \/ 4 道通关/.test(failPage.plate.count ?? ''), + `${failPage.plate.name} · ${failPage.plate.count}`); + check('只有一张卡、没有评级徽章、判定写「超时」', + failPage.rows.length === 1 && failPage.rows[0].badge === '' && failPage.rows[0].judge === '超时', + `${failPage.rows.length} 张 / 徽章「${failPage.rows[0]?.badge}」/ 判定「${failPage.rows[0]?.judge}」`); + check('没有成绩的两处都是占位「—」', failPage.total.num === '—' && failPage.dx.num === '—', + `${failPage.total.num} / ${failPage.dx.num}`); + check('占位「—」也染了深蓝(白字族是纯白剪影,白底上会看不见)', + /rgb\(13, 42, 99\)/.test(failPage.total.font?.fill ?? '') && /rgb\(13, 42, 99\)/.test(failPage.dx.font?.fill ?? ''), + `${failPage.total.font?.fill} / ${failPage.dx.font?.fill}`); + check('心换成红色底盘,写 0', failPage.life.base === 'life-base-red.png' && failPage.life.label === '0', + `${failPage.life.base} / ${failPage.life.label}`); + await shot('G-不合格结算页.png'); + + console.log('\n=== 5) 控制台与请求 ==='); + check('页面没有抛异常 / console.error', problems.length === 0, problems.slice(0, 3).join(' | ')); + check('没有指向本服务的 4xx/5xx', badResponses.length === 0, badResponses.slice(0, 3).join(' | ')); +} catch (error) { + console.log(`\n出错:${error instanceof Error ? error.message : String(error)}`); + failures += 1; +} finally { + try { cdp?.socket.close(); } catch { /* 已经关了 */ } + edge.kill(); +} + +console.log(`\n结果:${failures === 0 ? '全部通过' : `${failures} 项失败`}`); +process.exit(failures === 0 ? 0 : 1); \ No newline at end of file diff --git a/scripts/export-original-card.py b/scripts/export-original-card.py new file mode 100644 index 0000000..cbafa9b --- /dev/null +++ b/scripts/export-original-card.py @@ -0,0 +1,79 @@ +#!/usr/bin/env python3 +"""把**原版**的乐曲卡片素材原样导出来,供对照。 + +用途:现在卡片的边框是照着官方贴图的版式用 CSS 画的(`.dan-track-frame`),用户要直接看 +原版素材本身。这里把逐题行那一圈的原图导到 `docs/images/original-card/`,并顺手把当前渲染 +出来的第 1 张卡裁成一张独立小图 `docs/images/dan-card.png`。 + +用法: + python scripts/export-original-card.py "<游戏目录>" # 含 resources.assets 的那一层 +""" +from __future__ import annotations + +import sys +from pathlib import Path + +import UnityPy + +# 逐题行那一圈的原版贴图。`UI_CMN_RSL_KopMBase_*` 就是官方乐曲卡片的边框(640×140, +# 五种难度各一张,颜色与难度名 BASiC/ADVANCED/EXPERT/MASTER/Re:MASTER 都烘在图里)。 +WANTED = [ + "UI_CMN_RSL_KopMBase_BSC", + "UI_CMN_RSL_KopMBase_ADV", + "UI_CMN_RSL_KopMBase_EXP", + "UI_CMN_RSL_KopMBase_MST", + "UI_CMN_RSL_KopMBase_MST_Re", + "UI_CMN_RSL_JacketImage_S", + "UI_CMN_RSL_MusicJacket_Base", + "UI_CMN_RSL_JaketTrack", + "UI_CMN_RSL_JaketTrackNo_0", + "UI_CMN_RSL_AllScorekBase", + "UI_DNM_Result_musicBase_01", + "UI_RSL_AchivementCounter", + "UI_DNM_AchivementCounter", +] +PREFIXES = ("UI_CMN_RSL_", "UI_DNM_Result_music", "UI_RSL_Achivement", "UI_DNM_Achivement") + + +def main() -> int: + if len(sys.argv) < 2: + print(__doc__) + return 2 + game = Path(sys.argv[1]) + out = Path(__file__).resolve().parent.parent / "docs" / "images" / "original-card" + out.mkdir(parents=True, exist_ok=True) + env = UnityPy.load(str(game / "resources.assets")) + saved, extra = [], [] + for obj in env.objects: + if obj.type.name not in ("Texture2D", "Sprite"): + continue + try: + data = obj.read() + name = getattr(data, "m_Name", "") or "" + if not name: + continue + if name in WANTED: + image = data.image + image.save(out / f"{name}.png") + saved.append((name, image.size)) + elif any(name.startswith(p) for p in PREFIXES): + extra.append(name) + except Exception as exc: # 有的贴图数据在别的 bundle 里,读不到就跳过 + if getattr(obj, "read", None): + try: + name = obj.read().m_Name + except Exception: + name = "?" + if name in WANTED: + print(f" 跳过 {name}:{type(exc).__name__}") + for name, size in sorted(saved): + print(f" 导出 {name} {size[0]}x{size[1]}") + if extra: + print("同前缀但未导出的(想看哪个再说):") + for name in sorted(extra): + print(f" {name}") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) \ No newline at end of file diff --git a/scripts/extract-maimai-assets.py b/scripts/extract-maimai-assets.py new file mode 100644 index 0000000..061ace6 --- /dev/null +++ b/scripts/extract-maimai-assets.py @@ -0,0 +1,114 @@ +#!/usr/bin/env python3 +"""从 maimai DX 客户端资源里提取段位認定(Dani Mode)结算页用到的贴图。 + +这是**开发工具**,不是应用依赖:仓库里已经放好了提取结果 +(public/assets/maimai/*.png),只有要重新提一遍时才跑它。 + +用法: + pip install UnityPy texture2ddecoder Pillow + python scripts/extract-maimai-assets.py "<游戏目录>" [输出目录] + +`<游戏目录>` 是客户端里含 `resources.assets` 的那一层,例如 + E:/Downloads/156Sinmai_Data/156Sinmai_Data/156Sinmai_Data + +为什么要这么提:段位認定结算那整屏是**成品图**(页头文字、格纹页眉、两侧水引、 +下半部色带与左下角段位名牌白框全部烘焙在底图里),运行时只是把逐题行、达成率数字、 +段位名与总达成率盖上去。所以页面直接用这张底图 + 几件小素材,比自己用 CSS 画近似得多。 +注意:底图下半部那两三块**挖白的空面板**是原作留给运行时文字的位置,不要再拿别的脚本 +把它们续画填掉(第 12 轮干过一次,第 13 轮撤了)。 +""" +from __future__ import annotations + +import sys +from pathlib import Path + +import UnityPy + +# 仓库里用的文件名 ← 游戏里的贴图名。只搬这几件,不是整个 UI_DNM 家族(159 张)。 +# +# 故意**不**搬的一件:逐题行的乐曲边框 `UI_CMN_RSL_KopMBase_{BSC,ADV,EXP,MST,MST_Re}` +# (640×140,五种难度各一张)。它是逐题行那一圈底色,但颜色与难度名(BASIC/ADVANCED/ +# EXPERT/MASTER/Re:MASTER)都烘死在图里,套不上我们自己的 CF 难度分档。所以边框按它的 +# 版式用 CSS 画一层(见 dan.css 的 .dan-track-frame):外圈浅色环 + 主色场 + 底部浅色带 +# + 左侧白曲绘槽 + 深蓝标题条 + 白色达成率框,尺寸全按这张贴图量出来的像素。 +WANTED: dict[str, str] = { + "UI_DNM_Result_Base_01": "result-dani.png", # 「段位認定」整屏底图 980×928 + "UI_DNM_Result_Base_03": "result-random.png", # 「ランダム段位認定」整屏底图 + "UI_DNM_Result_musicBase_01": "track-plate.png", # 逐题行底板 576×124 + "UI_DNM_Icon_Result_01": "stamp-clear.png", # 逐题「可」印 108×108 + "UI_DNM_Icon_Result_02": "stamp-fail.png", # 逐题「不可」印 + # 数字图集:规整 4×4 网格,逐格由同名 Sprite 定 rect(见 dan.js 的 NUM_FONTS)。 + # 图集里全是**纯白剪影**(游戏运行时才染色),所以描边要另外提一层 _Outline。 + "UI_CMN_Num_26p": "num-26p.png", # 小号数字 136×160,格 34×40 + "UI_CMN_Num_26p_Outline": "num-26p-outline.png", # 同布局的描边剪影 + "UI_CMN_Num_90p": "num-90p.png", # 没有成绩时的占位「—」用,356×420,格 89×105 + "UI_CMN_Num_90p_Outline": "num-90p-outline.png", + # 分数数字:同布局的三套(blue / gold / red)296×392,格 74×98,自带颜色、 + # 不用描边层 —— 原作就是按达成率高低换这一套的颜色。 + # 同名的 `UI_NUM_Score_0001111_Base` 故意不搬:那是一张**空图集**(16 格里一个字形都没有, + # 整张只有几列杂散像素),照它渲染数字会得到一片看不见的灰影。 + "UI_NUM_Score_0001111_Blue": "num-score-blue.png", + "UI_NUM_Score_0001111_Gold": "num-score-gold.png", + "UI_NUM_Score_0001111_Red": "num-score-red.png", + # 达成率末尾那个大「%」字形:同样按分数分色(没有 base 那版,低档用图集里的 % 格) + "UI_RSL_Score_Per_Gold": "score-per-gold.png", # 80×80 + "UI_RSL_Score_Per_Blue": "score-per-blue.png", + "UI_RSL_Score_Per_Red": "score-per-red.png", + # 逐题评级徽章:`UI_GAM_Rank_*` 是**游戏内**那套,单级各一张(源图紧裁,36–110 × 42–44)。 + # 故意不用 `UI_CMN_TabTitle_Rank_*`:那是**页签标题**上的区间图 —— AAA 那张画的是 + # 「A~AAA」、BBB 是「~BBB」,而且它没有 D/C/B/BB/A/AA,低分全挤在同一张牌上。 + "UI_GAM_Rank_D": "gam-rank-d.png", + "UI_GAM_Rank_C": "gam-rank-c.png", + "UI_GAM_Rank_B": "gam-rank-b.png", + "UI_GAM_Rank_BB": "gam-rank-bb.png", + "UI_GAM_Rank_BBB": "gam-rank-bbb.png", + "UI_GAM_Rank_A": "gam-rank-a.png", + "UI_GAM_Rank_AA": "gam-rank-aa.png", + "UI_GAM_Rank_AAA": "gam-rank-aaa.png", + "UI_GAM_Rank_S": "gam-rank-s.png", + "UI_GAM_Rank_Sp": "gam-rank-sp.png", + "UI_GAM_Rank_SS": "gam-rank-ss.png", + "UI_GAM_Rank_SSp": "gam-rank-ssp.png", + "UI_GAM_Rank_SSS": "gam-rank-sss.png", + "UI_GAM_Rank_SSSp": "gam-rank-sssp.png", + # 右下角那个组件:剩余生命底盘(绿 = 通关 / 红 = 未通关)。 + # `UI_RSL_DXScore_Base`(「でらっくスコア」标签)**故意不搬**:官方那格是单曲按打击 + # 精度算的 DX 分数,我们显示的是四道单题 rating 之和,口径不同,套那个框等于撒谎。 + "UI_DNM_Base_Life_01": "life-base-green.png", + "UI_DNM_Base_Life_03": "life-base-red.png", +} + + +def main(argv: list[str]) -> int: + if len(argv) < 2: + print(__doc__) + return 2 + game_dir = Path(argv[1]) + out_dir = Path(argv[2]) if len(argv) > 2 else Path("public/assets/maimai") + assets = game_dir / "resources.assets" + if not assets.exists(): + print(f"找不到 {assets}") + return 1 + + out_dir.mkdir(parents=True, exist_ok=True) + env = UnityPy.load(str(assets)) + written: dict[str, str] = {} + for obj in env.objects: + if obj.type.name != "Texture2D": + continue + data = obj.read() + name = getattr(data, "m_Name", None) or getattr(data, "name", "") or "" + target = WANTED.get(name) + if target is None or target in written: + continue + image = data.image + image.save(out_dir / target) + written[target] = f"{name} ({image.width}x{image.height})" + + for target in WANTED.values(): + print(f" {written.get(target, '缺失!'):<34} → {target}") + return 0 if len(written) == len(WANTED) else 1 + + +if __name__ == "__main__": + raise SystemExit(main(sys.argv)) \ No newline at end of file