-
-
-
-
总达成率
-
-
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 @@
支持项目
微信扫一扫,或点击图片查看原图。
+
+
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 使用指南 从绑定第一个账号,到记录练习、看懂成绩与统计。
前往账号管理 ↗
首次使用 名称与账号 ID 通用 Cookie 教程 洛谷数据范围 同步与历史 CF 自建比赛 码蹄集同步 用时与 B50 常见问题 评分规则 模型与验证 随机抽题
@@ -136,4 +142,6 @@ 2. 取得 API key / secret 后配置
-
+
+
+
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 每一次通过,都值得记录 汇集各平台的解题足迹,回看尝试,也发现下一步。
@@ -210,6 +220,8 @@
这个筛选条件下没有记录
+
+
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 @@
+
+
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