@sophiacyl/dsh-plugin-waves 0.0.0-stage → 0.1.0-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (140) hide show
  1. package/DSH-GO-VERIFICATION.md +57 -0
  2. package/ICON.png +0 -0
  3. package/LICENSE +674 -0
  4. package/README.md +251 -2
  5. package/cordis.patch.yml +8 -0
  6. package/install.ps1 +0 -0
  7. package/install.sh +209 -0
  8. package/lib/client.js +1511 -0
  9. package/lib/host/aliashint.js +70 -0
  10. package/lib/host/attachments.js +25 -0
  11. package/lib/host/command.js +34 -0
  12. package/lib/host/consts.js +16 -0
  13. package/lib/host/dataguide.js +35 -0
  14. package/lib/host/imagebridge.js +41 -0
  15. package/lib/host/native-result.js +91 -0
  16. package/lib/host/native.js +191 -0
  17. package/lib/host/proxy.js +59 -0
  18. package/lib/host/results.js +67 -0
  19. package/lib/host/service.js +263 -0
  20. package/lib/host/tool.js +185 -0
  21. package/lib/host/troubleshoot.js +31 -0
  22. package/lib/host/worldbook.js +80 -0
  23. package/lib/index.js +74 -0
  24. package/locale/en.json +6 -0
  25. package/locale/zh.json +6 -0
  26. package/package.json +136 -4
  27. package/service/ICON.png +0 -0
  28. package/service/ai/AI_DATA.md +221 -0
  29. package/service/knowledge/additional/manual.jsonl +5 -0
  30. package/service/knowledge/wiki/baike.jsonl +1231 -0
  31. package/service/knowledge/wiki/chengjiu.jsonl +36 -0
  32. package/service/knowledge/wiki/gonglve.jsonl +383 -0
  33. package/service/knowledge/wiki/huodong.jsonl +427 -0
  34. package/service/knowledge/wiki/renwu.jsonl +412 -0
  35. package/service/knowledge/wiki/tujian.jsonl +2889 -0
  36. package/service/knowledge/wiki/zixun.jsonl +47 -0
  37. package/service/plugins/__init__.py +0 -0
  38. package/service/pyproject.toml +40 -0
  39. package/service/rover/__init__.py +0 -0
  40. package/service/rover/ai_core/__init__.py +0 -0
  41. package/service/rover/ai_core/models.py +50 -0
  42. package/service/rover/ai_core/rag/__init__.py +5 -0
  43. package/service/rover/ai_core/rag/knowledge.py +23 -0
  44. package/service/rover/ai_core/register.py +86 -0
  45. package/service/rover/ai_core/resource.py +6 -0
  46. package/service/rover/ai_core/skills/__init__.py +0 -0
  47. package/service/rover/ai_core/skills/operations.py +8 -0
  48. package/service/rover/api.py +720 -0
  49. package/service/rover/api_render.py +161 -0
  50. package/service/rover/app_life.py +154 -0
  51. package/service/rover/aps.py +47 -0
  52. package/service/rover/avatar.py +85 -0
  53. package/service/rover/bot.py +133 -0
  54. package/service/rover/builtin.py +152 -0
  55. package/service/rover/cards/__init__.py +26 -0
  56. package/service/rover/cards/_shared.py +129 -0
  57. package/service/rover/cards/char_list.py +258 -0
  58. package/service/rover/cards/char_panel.py +1353 -0
  59. package/service/rover/cards/echo_list.py +206 -0
  60. package/service/rover/cards/matrix_roles.py +25 -0
  61. package/service/rover/cards/rank_boards.py +714 -0
  62. package/service/rover/cards/rank_total.py +664 -0
  63. package/service/rover/compat.py +215 -0
  64. package/service/rover/config.py +163 -0
  65. package/service/rover/custom_panel.py +220 -0
  66. package/service/rover/data_store.py +31 -0
  67. package/service/rover/gss.py +19 -0
  68. package/service/rover/handler.py +191 -0
  69. package/service/rover/help/__init__.py +0 -0
  70. package/service/rover/help/draw_new_plugin_help.py +332 -0
  71. package/service/rover/help/model.py +16 -0
  72. package/service/rover/help/utils.py +7 -0
  73. package/service/rover/hooks.py +420 -0
  74. package/service/rover/html_actions.py +236 -0
  75. package/service/rover/i18n.py +19 -0
  76. package/service/rover/knowledge.py +235 -0
  77. package/service/rover/logger.py +102 -0
  78. package/service/rover/main.py +33 -0
  79. package/service/rover/media.py +222 -0
  80. package/service/rover/message_models.py +23 -0
  81. package/service/rover/models.py +92 -0
  82. package/service/rover/native_media.py +64 -0
  83. package/service/rover/ocr.py +79 -0
  84. package/service/rover/pool.py +43 -0
  85. package/service/rover/restart.py +36 -0
  86. package/service/rover/segment.py +143 -0
  87. package/service/rover/server.py +131 -0
  88. package/service/rover/status/__init__.py +0 -0
  89. package/service/rover/status/models.py +10 -0
  90. package/service/rover/status/plugin_status.py +17 -0
  91. package/service/rover/subscribe.py +148 -0
  92. package/service/rover/sv.py +403 -0
  93. package/service/rover/templates/char_list.html +182 -0
  94. package/service/rover/templates/char_panel.html +465 -0
  95. package/service/rover/templates/echo_list.html +221 -0
  96. package/service/rover/templates/rank_all_total.html +193 -0
  97. package/service/rover/templates/rank_board.html +107 -0
  98. package/service/rover/templates/rank_level_total.html +57 -0
  99. package/service/rover/templates/rank_matrix_total.html +66 -0
  100. package/service/rover/templates/rank_phantom_total.html +180 -0
  101. package/service/rover/templates/rank_slash_total.html +38 -0
  102. package/service/rover/trigger.py +98 -0
  103. package/service/rover/utils/__init__.py +0 -0
  104. package/service/rover/utils/boardcast/__init__.py +0 -0
  105. package/service/rover/utils/boardcast/models.py +15 -0
  106. package/service/rover/utils/cookie_manager/__init__.py +0 -0
  107. package/service/rover/utils/cookie_manager/qrlogin.py +24 -0
  108. package/service/rover/utils/database/__init__.py +0 -0
  109. package/service/rover/utils/database/base_models.py +1771 -0
  110. package/service/rover/utils/database/models.py +80 -0
  111. package/service/rover/utils/database/startup.py +60 -0
  112. package/service/rover/utils/default_bg/bg.jpg +0 -0
  113. package/service/rover/utils/download_resource/__init__.py +0 -0
  114. package/service/rover/utils/download_resource/download_core.py +164 -0
  115. package/service/rover/utils/download_resource/download_file.py +46 -0
  116. package/service/rover/utils/fonts/__init__.py +0 -0
  117. package/service/rover/utils/fonts/fonts.py +33 -0
  118. package/service/rover/utils/image/__init__.py +0 -0
  119. package/service/rover/utils/image/convert.py +51 -0
  120. package/service/rover/utils/image/image_tools.py +30 -0
  121. package/service/rover/utils/image/utils.py +70 -0
  122. package/service/rover/utils/plugins_config/__init__.py +0 -0
  123. package/service/rover/utils/plugins_config/gs_config.py +506 -0
  124. package/service/rover/utils/plugins_config/models.py +182 -0
  125. package/service/rover/web_app.py +3 -0
  126. package/service/rover/webconsole/__init__.py +0 -0
  127. package/service/rover/webconsole/mount_app.py +26 -0
  128. package/service/tools/migrate.py +351 -0
  129. package/service/tools/regression.py +87 -0
  130. package/service/tools/smoke.py +63 -0
  131. package/service/tools/update_plugins.py +212 -0
  132. package/service/tools/verify_actions.py +104 -0
  133. package/service/tools/verify_card.py +82 -0
  134. package/service/tools/wwdata.py +665 -0
  135. package/tests/compat.test.js +181 -0
  136. package/tests/imagebridge.test.js +58 -0
  137. package/tests/media.test.js +68 -0
  138. package/tests/native.test.js +127 -0
  139. package/tests/runtime.test.js +69 -0
  140. package/tests/service.test.js +48 -0
@@ -0,0 +1,182 @@
1
+ <!DOCTYPE html>
2
+ <html lang="zh-CN">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <title>鸣潮练度统计</title>
6
+ <style>
7
+ @import url('{{ font_css_url }}');
8
+
9
+ * { box-sizing: border-box; }
10
+ html, body { margin: 0; padding: 0; background: #0f1115; }
11
+ body {
12
+ width: {{ page_w }}px;
13
+ font-family: 'Source Han Sans CN', 'Noto Sans CJK JP', 'Noto Sans CJK KR', sans-serif;
14
+ font-weight: 500;
15
+ color: #fff;
16
+ }
17
+
18
+ .container { position: relative; width: {{ page_w }}px; padding: 40px; overflow: hidden; }
19
+ .bg-layer { position: absolute; inset: 0; z-index: 0; }
20
+ .bg-layer .bg-image { width: 100%; height: 100%; object-fit: cover; display: block; }
21
+ .bg-layer::after { content: ""; position: absolute; inset: 0; background: rgba(0, 0, 0, .2); }
22
+ .page { position: relative; z-index: 1; display: flex; flex-direction: column; align-items: center; gap: 30px; }
23
+
24
+ /* 绝对定位文本: 锚点与画布版一致 */
25
+ .t { position: absolute; white-space: nowrap; line-height: 1; transform: translateY(-50%); }
26
+ .t.mm { transform: translate(-50%, -50%); }
27
+ .ic { position: absolute; display: block; background-repeat: no-repeat; background-size: 100% 100%; }
28
+
29
+ /* 头部 */
30
+ .header {
31
+ position: relative; width: 100%;
32
+ background:
33
+ radial-gradient(circle at 100% 0%, rgba(212, 177, 99, .15) 0%, transparent 40%),
34
+ linear-gradient(180deg, rgba(30, 34, 42, .9) 0%, rgba(15, 17, 21, .95) 100%);
35
+ border-radius: 16px; padding: 22px 40px; display: flex; align-items: center;
36
+ border: 1px solid rgba(255, 255, 255, .08);
37
+ border-top: 1px solid rgba(255, 255, 255, .2);
38
+ box-shadow: 0 10px 30px -10px rgba(0, 0, 0, .5);
39
+ }
40
+ .header .deco { position: absolute; top: 18px; right: 30px; font-family: 'Oswald', sans-serif; font-size: 14px; letter-spacing: 4px; color: rgba(255, 255, 255, .1); font-weight: 700; }
41
+ .avatar-box { position: relative; width: 110px; height: 110px; flex-shrink: 0; margin-right: 30px; border-radius: 50%; background: #222; border: 3px solid #2a2e35; box-shadow: 0 4px 10px rgba(0, 0, 0, .5); }
42
+ .avatar-box .ring { position: absolute; inset: -6px; border-radius: 50%; border: 1px solid rgba(255, 255, 255, .05); border-left: 2px solid #d4b163; transform: rotate(-45deg); }
43
+ .avatar-box .face { position: absolute; inset: 0; border-radius: 50%; background-size: cover; background-position: center; }
44
+ .user-info { flex: 1; }
45
+ .user-row { display: flex; align-items: baseline; padding-bottom: 8px; margin-bottom: 10px; border-bottom: 1px solid rgba(255, 255, 255, .08); position: relative; }
46
+ .user-row::after { content: ""; position: absolute; bottom: -1px; left: 0; width: 40px; height: 2px; background: #d4b163; box-shadow: 0 0 8px #d4b163; }
47
+ .user-name { font-size: 40px; font-weight: 800; margin-right: 20px; text-shadow: 0 4px 10px rgba(0, 0, 0, .5); }
48
+ .user-uid { font-family: 'JetBrains Mono', monospace; font-size: 20px; color: #d4b163; background: rgba(0, 0, 0, .4); padding: 4px 12px; border-radius: 6px; border: 1px solid rgba(212, 177, 99, .2); letter-spacing: 1.5px; font-weight: 700; }
49
+ .user-stats { display: flex; gap: 40px; }
50
+ .stat-item { display: flex; flex-direction: column; }
51
+ .stat-value { font-family: 'Oswald', sans-serif; font-size: 30px; font-weight: 700; line-height: 1.1; background: linear-gradient(180deg, #fff 50%, #d4b163 100%); -webkit-background-clip: text; -webkit-text-fill-color: transparent; }
52
+ .stat-label { font-size: 12px; color: #6d717a; font-weight: 700; letter-spacing: 1px; margin-top: 2px; }
53
+
54
+ /* 统计条: 底图与四项坐标照画布版 */
55
+ .stat-plate { position: relative; width: 1000px; height: 260px; background-repeat: no-repeat; background-size: 100% 100%; }
56
+ .stat-plate .stat-v { font-size: 40px; }
57
+ .stat-plate .stat-l { font-size: 20px; }
58
+
59
+ /* 角色网格: 单列纵排, 角色多时左右两列 */
60
+ .grid { position: relative; width: {{ grid_w }}px; height: {{ grid_h }}px; }
61
+ /* 点击桥接会给 [data-ww-cmd] 加 position:relative, 用更高特异性压回去 */
62
+ .grid .row { position: absolute; width: {{ bar_w }}px; height: {{ bar_h }}px; overflow: hidden; background-repeat: no-repeat; background-size: 100% 100%; }
63
+ .row .avatar { left: 60px; top: 0; width: 180px; height: 180px; }
64
+ .row .attr { left: 170px; top: 20px; width: 40px; height: 40px; }
65
+ .row .lv { left: 180px; top: 83px; font-size: 22px; color: {{ grey }}; }
66
+ .row .chain { position: absolute; left: 120px; top: 15px; width: 40px; height: 20px; display: flex; align-items: center; }
67
+ .row .chain span { padding-left: 2px; font-size: 18px; line-height: 1; white-space: nowrap; }
68
+ .row .emblem { left: 200px; top: 2px; width: 110px; height: 110px; }
69
+ .row .score-v { left: 348px; top: 42px; font-size: 30px; }
70
+ .row .score-l { left: 348px; top: 75px; font-size: 16px; color: {{ gold }}; }
71
+
72
+ /* 技能格: 原版 120x140 画好后缩到 70x82 */
73
+ .skills { position: absolute; left: 300px; top: 10px; }
74
+ .skill { position: absolute; top: 0; width: 70px; height: 82px; }
75
+ .skill .inner { position: absolute; left: 0; top: 0; width: 120px; height: 140px; transform: scale({{ skill_scale_x }}, {{ skill_scale_y }}); transform-origin: top left; }
76
+ .skill .sbg { left: 0; top: 0; width: 120px; height: 120px; background-image: url('{{ skill_bg_url }}'); }
77
+ .skill .sicon { left: 25px; top: 25px; width: 70px; height: 70px; }
78
+ .skill .slv { left: 62px; top: 120px; font-size: 38px; }
79
+
80
+ /* 武器块: 原版 600x300 画好后缩到 260x130 */
81
+ .weapon { position: absolute; left: 710px; top: 25px; width: 260px; height: 130px; overflow: hidden; }
82
+ .weapon .inner { position: absolute; left: 0; top: 0; width: 600px; height: 300px; transform: scale({{ weapon_scale }}); transform-origin: top left; }
83
+ .weapon .wbg { left: 45px; top: 0; width: 140px; height: 140px; }
84
+ .weapon .wicon { left: 55px; top: 20px; width: 110px; height: 110px; }
85
+ .weapon .wname { left: 200px; top: 30px; font-size: 40px; color: {{ gold }}; }
86
+ .weapon .wlv { left: 203px; top: 75px; font-size: 30px; }
87
+ .weapon .pip { top: 100px; width: 40px; height: 40px; background-image: url('{{ promote_url }}'); }
88
+ .weapon .reson { position: absolute; left: 103px; top: 96px; width: 58px; height: 30px; border-radius: 8px; }
89
+ .weapon .reson span { position: absolute; left: 29px; top: 15px; transform: translate(-50%, -50%); font-size: 24px; white-space: nowrap; }
90
+
91
+ .hint { font-size: 24px; color: {{ gold }}; }
92
+ .footer { width: 100%; text-align: center; opacity: .6; }
93
+ .footer img { max-width: 100%; }
94
+ </style>
95
+ <style>
96
+ {% for url in assets %}.a{{ loop.index0 }} { background-image: url('{{ url }}'); }
97
+ {% endfor %}
98
+ </style>
99
+ </head>
100
+ <body>
101
+ <div class="container">
102
+ <div class="bg-layer"><img class="bg-image" src="{{ bg_url }}"></div>
103
+ <div class="page">
104
+ <div class="header">
105
+ <div class="deco">CHARACTER LIST</div>
106
+ <div class="avatar-box">
107
+ <div class="ring"></div>
108
+ <div class="face" style="background-image: url('{{ avatar_url }}')"></div>
109
+ </div>
110
+ <div class="user-info">
111
+ <div class="user-row">
112
+ <div class="user-name">{{ user_name }}</div>
113
+ <div class="user-uid">特征码 {{ user_id }}</div>
114
+ </div>
115
+ <div class="user-stats">
116
+ {% if show_stats %}
117
+ <div class="stat-item">
118
+ <div class="stat-value">Lv.{{ level }}</div>
119
+ <div class="stat-label">账号等级</div>
120
+ </div>
121
+ <div class="stat-item">
122
+ <div class="stat-value">Lv.{{ world_level }}</div>
123
+ <div class="stat-label">世界等级</div>
124
+ </div>
125
+ {% endif %}
126
+ </div>
127
+ </div>
128
+ </div>
129
+
130
+
131
+ <div class="stat-plate" style="background-image: url('{{ info_bg_url }}')">
132
+ {% for cell in stat_cells %}
133
+ <div class="t mm stat-v" style="left:{{ cell.x }}px;top:120px">{{ cell.value }}</div>
134
+ <div class="t mm stat-l" style="left:{{ cell.x }}px;top:160px">{{ cell.label }}</div>
135
+ {% endfor %}
136
+ </div>
137
+
138
+ <div class="grid">
139
+ {% for row in rows %}
140
+ <div class="row {{ row.bar_class }}" style="left: {{ row.left }}px; top: {{ row.top }}px"{% if row.cmd %} data-ww-cmd="{{ row.cmd }}"{% endif %}>
141
+ <i class="ic avatar {{ row.avatar_class }}"></i>
142
+ <i class="ic attr {{ row.attr_class }}"></i>
143
+ <div class="chain" style="background: {{ row.chain_color }}"><span>{{ row.chain_name }}</span></div>
144
+ <div class="t mm lv">Lv.{{ row.level }}</div>
145
+ {% if row.score %}
146
+ <i class="ic emblem {{ row.score.emblem_class }}"></i>
147
+ <div class="t mm score-v">{{ row.score.value }}</div>
148
+ <div class="t mm score-l">声骸分数</div>
149
+ {% endif %}
150
+ <div class="skills">
151
+ {% for skill in row.skills %}
152
+ <div class="skill" style="left: {{ skill.left }}px">
153
+ <div class="inner">
154
+ <i class="ic sbg"></i>
155
+ {% if skill.icon_class %}<i class="ic sicon {{ skill.icon_class }}"></i>{% endif %}
156
+ <div class="t mm slv" style="color: {{ skill.color }}">{{ skill.level }}</div>
157
+ </div>
158
+ </div>
159
+ {% endfor %}
160
+ </div>
161
+ <div class="weapon">
162
+ <div class="inner">
163
+ <div class="t wname">{{ row.weapon.name }}</div>
164
+ <div class="t wlv">Lv.{{ row.weapon.level }}/90</div>
165
+ {% for pip in row.weapon.pips %}
166
+ <i class="ic pip" style="left: {{ 200 + 40 * loop.index0 }}px"></i>
167
+ {% endfor %}
168
+ <i class="ic wbg {{ row.weapon.bg_class }}"></i>
169
+ <i class="ic wicon {{ row.weapon.icon_class }}"></i>
170
+ <div class="reson" style="background: {{ row.weapon.reson_color }}"><span>{{ row.weapon.reson }}阶</span></div>
171
+ </div>
172
+ </div>
173
+ </div>
174
+ {% endfor %}
175
+ </div>
176
+
177
+ {% if hint %}<div class="hint">{{ hint }}</div>{% endif %}
178
+ {% if footer_url %}<div class="footer"><img src="{{ footer_url }}"></div>{% endif %}
179
+ </div>
180
+ </div>
181
+ </body>
182
+ </html>
@@ -0,0 +1,465 @@
1
+ <!DOCTYPE html>
2
+ <html lang="zh-CN">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <title>角色面板</title>
6
+ <style>
7
+ @import url('{{ font_css_url }}');
8
+
9
+ * { box-sizing: border-box; }
10
+ html, body { margin: 0; padding: 0; background: #0f1115; }
11
+ body {
12
+ width: 1200px;
13
+ font-family: 'Source Han Sans CN', 'Noto Sans CJK JP', 'Noto Sans CJK KR', sans-serif;
14
+ font-weight: 500;
15
+ color: #fff;
16
+ }
17
+
18
+ .container { position: relative; width: 1200px; padding-bottom: 70px; overflow: hidden; }
19
+ .bg-layer { position: absolute; inset: 0; z-index: 0; }
20
+ .bg-layer .bg-image { width: 100%; height: 100%; object-fit: cover; display: block; }
21
+ .bg-layer::after { content: ""; position: absolute; inset: 0; background: rgba(0, 0, 0, .18); }
22
+ .page { position: relative; z-index: 1; }
23
+
24
+ /* 绝对定位文本: 锚点与画布版一致 */
25
+ .t { position: absolute; white-space: nowrap; line-height: 1; }
26
+ .a-lt { transform: none; }
27
+ .a-lm { transform: translateY(-50%); }
28
+ .a-mm { transform: translate(-50%, -50%); }
29
+ .a-rm { transform: translate(-100%, -50%); }
30
+ .a-mt { transform: translateX(-50%); }
31
+ .a-rt { transform: translateX(-100%); }
32
+ .ic { position: absolute; background-repeat: no-repeat; background-size: 100% 100%; }
33
+ .bar, .card, .card .head, .rowbar, .grade-block .layer, .skills .item,
34
+ .panel-prop, .weapon, .echo .title-bar, .chains .item .ic {
35
+ background-repeat: no-repeat; background-size: 100% 100%;
36
+ }
37
+ [data-ww-cmd] { cursor: pointer; }
38
+
39
+ /* 头部 */
40
+ .header {
41
+ position: relative; width: 1140px; margin: 24px 30px 6px;
42
+ background:
43
+ radial-gradient(circle at 100% 0%, rgba(212, 177, 99, .15) 0%, transparent 40%),
44
+ linear-gradient(180deg, rgba(30, 34, 42, .9) 0%, rgba(15, 17, 21, .95) 100%);
45
+ border-radius: 16px; padding: 22px 40px; display: flex; align-items: center;
46
+ border: 1px solid rgba(255, 255, 255, .08);
47
+ border-top: 1px solid rgba(255, 255, 255, .2);
48
+ box-shadow: 0 10px 30px -10px rgba(0, 0, 0, .5);
49
+ }
50
+ .header .deco { position: absolute; top: 18px; right: 30px; font-family: 'Oswald', sans-serif; font-size: 14px; letter-spacing: 4px; color: rgba(255, 255, 255, .1); font-weight: 700; }
51
+ .avatar-box { position: relative; width: 110px; height: 110px; flex-shrink: 0; margin-right: 30px; border-radius: 50%; background: #222; border: 3px solid #2a2e35; box-shadow: 0 4px 10px rgba(0, 0, 0, .5); }
52
+ .avatar-box .ring { position: absolute; inset: -6px; border-radius: 50%; border: 1px solid rgba(255, 255, 255, .05); border-left: 2px solid #d4b163; transform: rotate(-45deg); }
53
+ .avatar-box .face { position: absolute; inset: 0; border-radius: 50%; background-size: cover; background-position: center; }
54
+ .user-info { flex: 1; }
55
+ .user-row { display: flex; align-items: baseline; padding-bottom: 8px; margin-bottom: 10px; border-bottom: 1px solid rgba(255, 255, 255, .08); position: relative; }
56
+ .user-row::after { content: ""; position: absolute; bottom: -1px; left: 0; width: 40px; height: 2px; background: #d4b163; box-shadow: 0 0 8px #d4b163; }
57
+ .user-name { font-size: 40px; font-weight: 800; margin-right: 20px; text-shadow: 0 4px 10px rgba(0, 0, 0, .5); }
58
+ .user-uid { font-family: 'JetBrains Mono', monospace; font-size: 20px; color: #d4b163; background: rgba(0, 0, 0, .4); padding: 4px 12px; border-radius: 6px; border: 1px solid rgba(212, 177, 99, .2); letter-spacing: 1.5px; font-weight: 700; }
59
+ .user-stats { display: flex; gap: 40px; }
60
+ .stat-item { display: flex; flex-direction: column; }
61
+ .stat-value { font-family: 'Oswald', sans-serif; font-size: 30px; font-weight: 700; line-height: 1.1; background: linear-gradient(180deg, #fff 50%, #d4b163 100%); -webkit-background-clip: text; -webkit-text-fill-color: transparent; }
62
+ .stat-label { font-size: 12px; color: #6d717a; font-weight: 700; letter-spacing: 1px; margin-top: 2px; }
63
+
64
+ /* 立绘 */
65
+ /* 高度贴合实际内容: 立绘 1000, 有评分时右栏被下压, 武器块底到 1105 */
66
+ .body { position: relative; }
67
+ .pile { position: absolute; left: 25px; top: 0; width: 560px; height: 1000px; }
68
+ .pile .art {
69
+ position: absolute; inset: 0; background-size: 560px 1000px; background-repeat: no-repeat;
70
+ -webkit-mask-size: 560px 1000px; -webkit-mask-repeat: no-repeat;
71
+ }
72
+ .pile .fg { position: absolute; inset: 0; background-size: 560px 1000px; background-repeat: no-repeat; }
73
+ .pile .role-name { font-size: 50px; font-weight: 700; text-shadow: 2px 2px 6px rgba(0, 0, 0, .9); }
74
+ .pile .hash { font-size: 12px; color: #b0b0b0; text-shadow: 1px 1px 0 rgba(80, 80, 80, .9); }
75
+
76
+ /* 右侧 */
77
+ .right { position: absolute; left: 570px; width: 600px; height: 1100px; }
78
+ .panel-prop { position: absolute; left: 0; width: 600px; height: 500px; }
79
+ .weapon { position: absolute; left: 0; width: 600px; height: 300px; }
80
+ .weapon-name { position: absolute; display: flex; align-items: center; gap: 18px; transform: translateY(-50%); }
81
+ .weapon-name .name { font-size: 40px; font-weight: 700; color: rgb(234, 183, 4); }
82
+ .weapon-name .reson { font-size: 24px; padding: 3px 12px; border-radius: 7px; }
83
+
84
+ /* 综合评分: 紧贴立绘下沿, 与画布版一样吃掉立绘底部的留白 */
85
+ /* 画布版各块是互相压着的绝对坐标, 这里用负边距还原:
86
+ 评分徽章 1080+shift / 技能 1150+offset+shift / 共鸣链 1080+jineng / 声骸 1320+jineng */
87
+ .score-band { position: relative; height: 200px; margin-top: -170px; }
88
+ .score-band .grade { position: absolute; left: 90px; top: 0; width: 200px; height: 200px; }
89
+ .score-band .value { font-size: 40px; font-weight: 700; }
90
+ .score-band .label { font-size: 40px; color: rgb(216, 216, 216); }
91
+
92
+ /* 技能 / 共鸣链 */
93
+ .skills { position: relative; height: 150px; margin-top: {{ -15 if score_band else -20 }}px; overflow: hidden; }
94
+ .skills .bar { position: absolute; left: 0; top: 0; width: 1200px; height: 150px; }
95
+ .skills .item { position: absolute; width: 400px; height: 200px; }
96
+ .chains { position: relative; height: 300px; margin-top: -40px; overflow: hidden; }
97
+ .chains .item { position: absolute; top: 0; width: 300px; height: 300px; }
98
+ .chains .name { font-size: 20px; text-align: center; }
99
+ .chains .name.small { font-size: 16px; }
100
+
101
+ /* 声骸 */
102
+ .echo { position: relative; margin-top: -60px; }
103
+ .echo .banner { position: absolute; left: 0; top: 0; width: 1200px; height: 100px; }
104
+ .echo .title-bar { position: absolute; left: 350px; top: 85px; width: 500px; height: 100px; }
105
+ .bar { position: absolute; width: 400px; height: 100px; }
106
+ .card { position: absolute; width: 350px; height: 550px; }
107
+ .card .head { position: absolute; left: 0; top: 0; width: 350px; height: 150px; }
108
+ .card .badge { position: absolute; height: 30px; border-radius: 8px; display: flex; align-items: center; }
109
+ .grade-block { position: absolute; width: 350px; height: 500px; }
110
+ .grade-block .layer { position: absolute; inset: 0; background-size: 350px 500px; background-repeat: no-repeat; }
111
+
112
+ /* 伤害 / 优化 / 权重 */
113
+ .rows { position: relative; }
114
+ .rows .rowbar { position: absolute; left: 0; width: 1200px; height: 100px; }
115
+ .rule-panel { position: absolute; left: 30px; width: 1140px; background: rgba(0, 0, 0, .43); border-radius: 20px; }
116
+ .rule-panel .hr { position: absolute; left: 40px; right: 40px; height: 2px; background: rgba(255, 255, 255, .18); }
117
+ .weight-table { position: relative; height: 900px; }
118
+ .weight-table .plate { position: absolute; left: 10px; top: 10px; width: 1180px; height: 860px; background: rgba(0, 0, 0, .7); border-radius: 6px; }
119
+ .weight-table .cell { position: absolute; width: 230px; height: 40px; display: flex; align-items: center; justify-content: center; font-size: 20px; }
120
+ .weight-table .cell.head { font-size: 24px; }
121
+
122
+ .footer { position: relative; height: 60px; }
123
+ .footer .mark { position: absolute; left: 300px; top: 10px; width: 600px; height: 40px; opacity: .85; }
124
+
125
+ /* 点击桥接会给 [data-ww-cmd] 加 position:relative, 这里用更高特异性压回去 */
126
+ .echo .card, .rows .rowbar { position: absolute; }
127
+
128
+ {% for name, uri in icon_css %}.{{ name }}{background-image:url('{{ uri }}')}
129
+ {% endfor %}
130
+ </style>
131
+ </head>
132
+ <body>
133
+ {% macro T(x, y, text, color='#ffffff', size=24, anchor='lm', cls='', weight=500) -%}
134
+ <div class="t a-{{ anchor }} {{ cls }}" style="left:{{ x }}px;top:{{ y }}px;color:{{ color }};font-size:{{ size }}px;font-weight:{{ weight }}">{{ text }}</div>
135
+ {%- endmacro %}
136
+
137
+ {% macro STATBAR(row, even_cls, odd_cls, x, y) -%}
138
+ <div class="bar {{ odd_cls if row.odd else even_cls }}" style="left:{{ x }}px;top:{{ y }}px">
139
+ <div class="ic {{ row.icon }}" style="left:20px;top:32px;width:40px;height:40px"></div>
140
+ {{ T(70, 50, row.name, row.color, 24) }}
141
+ {{ T(343, 50, row.value, row.color, 24, 'rm') }}
142
+ </div>
143
+ {%- endmacro %}
144
+
145
+ {% macro ECHOCARD(card, x, y, cmd) -%}
146
+ <div class="card {{ card.bg }}" style="left:{{ x }}px;top:{{ y }}px;background-size:350px 550px;background-repeat:no-repeat"
147
+ {% if cmd %}data-ww-cmd="{{ cmd }}"{% endif %}>
148
+ <div class="head {{ card.title }}" style="background-size:350px 150px;background-repeat:no-repeat"></div>
149
+ {% if card.icon %}<div class="ic {{ card.icon }}" style="left:20px;top:20px;width:100px;height:100px"></div>{% endif %}
150
+ {{ T(130, 40, card.name, 'rgb(234,183,4)', 28, 'lm', '', 700) }}
151
+ {% if card.badge %}
152
+ <div class="badge" style="left:128px;top:58px;width:{{ 175 if card.wide_score else 150 }}px;background:rgba(0,0,0,.8);justify-content:center">
153
+ <span style="font-size:18px">{{ card.badge }}</span>
154
+ </div>
155
+ {% else %}
156
+ <div class="badge" style="left:128px;top:58px;width:84px;background:rgba(0,0,0,.8);padding-left:8px">
157
+ <span style="font-size:24px">{{ card.level }}</span>
158
+ </div>
159
+ <div class="badge" style="left:223px;top:58px;width:{{ 120 if card.wide_score else 100 }}px;background:rgba(186,55,42,.8);justify-content:center">
160
+ <span style="font-size:24px">{{ card.score }}</span>
161
+ </div>
162
+ {% endif %}
163
+ {% for i in range(card.cost) %}
164
+ <div class="ic {{ card.promote }}" style="left:{{ 128 + 30 * i }}px;top:90px;width:30px;height:30px"></div>
165
+ {% endfor %}
166
+ {% for prop in card.props %}
167
+ {% if prop.entry is defined %}
168
+ {{ T(15, 187 + loop.index0 * 55, prop.name, prop.name_color, 24) }}
169
+ {{ T(273, 187 + loop.index0 * 55, prop.value, prop.value_color, 24, 'rm') }}
170
+ {{ T(343, 191 + loop.index0 * 55, prop.entry, prop.entry_color, 18, 'rm') }}
171
+ {% else %}
172
+ <div class="ic {{ prop.icon }}" style="left:15px;top:{{ 167 + loop.index0 * 55 }}px;width:40px;height:40px"></div>
173
+ {{ T(60, 187 + loop.index0 * 55, prop.name, prop.name_color, 24) }}
174
+ {{ T(343, 187 + loop.index0 * 55, prop.value, prop.value_color, 24, 'rm') }}
175
+ {% endif %}
176
+ {% endfor %}
177
+ {% if card.max_line %}{{ T(343, 576, card.max_line, 'rgb(234,183,4)', 18, 'rm') }}{% endif %}
178
+ </div>
179
+ {%- endmacro %}
180
+
181
+ {% macro GRADE(grade, x, y) -%}
182
+ <div class="grade-block" style="left:{{ x }}px;top:{{ y }}px">
183
+ <div class="layer {{ grade.bg }}"></div>
184
+ {% if grade.fg %}<div class="layer {{ grade.fg }}"></div>{% endif %}
185
+ {% if grade.empty %}
186
+ {{ T(180, 130, grade.top, '#ffffff', 40, 'mm') }}
187
+ {{ T(180, 380, grade.value, '#ffffff', 40, 'mm') }}
188
+ {% else %}
189
+ {{ T(180, 260, grade.top, 'rgb(216,216,216)', 40, 'mm') }}
190
+ {{ T(180, 380, grade.value, '#ffffff', 40, 'mm', '', 700) }}
191
+ {{ T(180, 440, grade.bottom, 'rgb(216,216,216)', 40, 'mm') }}
192
+ {% endif %}
193
+ </div>
194
+ {%- endmacro %}
195
+
196
+ {% macro DMGHEAD(bar, head, y) -%}
197
+ <div class="rowbar {{ bar }}" style="top:{{ y }}px">
198
+ {{ T(400, 50, head.name, 'rgb(234,183,4)', 24, 'rm') }}
199
+ {{ T(700, 50, head.crit, 'rgb(234,183,4)', 24, 'mm') }}
200
+ {{ T(1000, 50, head.expected, 'rgb(234,183,4)', 24, 'mm') }}
201
+ </div>
202
+ {%- endmacro %}
203
+
204
+ {% macro DMGROW(bar, row, y) -%}
205
+ <div class="rowbar {{ bar }}" style="top:{{ y }}px" {% if row.cmd %}data-ww-cmd="{{ row.cmd }}"{% endif %}>
206
+ {{ T(400, 50, row.name, '#ffffff', 24, 'rm') }}
207
+ {% if row.single %}
208
+ {{ T(850, 50, row.value if row.value is defined else row.expected, 'rgb(234,183,4)' if row.gold else '#ffffff', 24, 'mm') }}
209
+ {% else %}
210
+ {{ T(700, 50, row.crit, '#ffffff', 24, 'mm') }}
211
+ {{ T(1000, 50, row.expected, '#ffffff', 24, 'mm') }}
212
+ {% endif %}
213
+ </div>
214
+ {%- endmacro %}
215
+
216
+ <div class="container">
217
+ <div class="bg-layer"><img class="bg-image" src="{{ bg }}"></div>
218
+ <div class="page">
219
+
220
+ <div class="header">
221
+ <div class="deco">ROLE PANEL</div>
222
+ <div class="avatar-box">
223
+ <div class="ring"></div>
224
+ <div class="face {{ header.avatar }}"></div>
225
+ </div>
226
+ <div class="user-info">
227
+ <div class="user-row">
228
+ <div class="user-name">{{ header.name }}</div>
229
+ <div class="user-uid">{{ header.uid_label }} {{ header.uid }}</div>
230
+ </div>
231
+ {% if header.show_stats %}
232
+ <div class="user-stats">
233
+ <div class="stat-item">
234
+ <div class="stat-value">Lv.{{ header.level }}</div>
235
+ <div class="stat-label">{{ header.level_label }}</div>
236
+ </div>
237
+ <div class="stat-item">
238
+ <div class="stat-value">Lv.{{ header.world_level }}</div>
239
+ <div class="stat-label">{{ header.world_label }}</div>
240
+ </div>
241
+ </div>
242
+ {% endif %}
243
+ </div>
244
+ </div>
245
+
246
+ {% set shift = 25 if score_band else 0 %}
247
+ <div class="body" style="height:{{ 1105 if score_band else 1000 }}px">
248
+ <div class="pile">
249
+ <div class="art {{ pile.image }}" style="-webkit-mask-image:url('{{ mask_uri }}')"></div>
250
+ <div class="fg {{ pile.fg }}"></div>
251
+ <div class="ic {{ pile.attribute }}" style="left:434px;top:112px;width:50px;height:50px"></div>
252
+ <div class="ic {{ pile.weapon_type }}" style="left:439px;top:182px;width:40px;height:40px"></div>
253
+ <div class="t a-mm role-name" style="left:285px;top:867px">{{ pile.name }}</div>
254
+ {% if pile.hash_id %}<div class="t a-rm hash" style="left:500px;top:100px">{{ pile.hash_id }}</div>{% endif %}
255
+ </div>
256
+
257
+ {% if right %}
258
+ {% set prop_y = 100 if score_band else 80 %}
259
+ {% set weapon_banner_y = 620 if score_band else 550 %}
260
+ {% set weapon_y = 750 if score_band else 620 %}
261
+ <div class="right" style="top:{{ 30 + shift }}px">
262
+ <div class="ic {{ right.banner_top }}" style="left:-9px;top:0;width:600px;height:100px"></div>
263
+ <div class="panel-prop {{ right.prop_bg }}" style="top:{{ prop_y }}px;background-size:600px 500px;background-repeat:no-repeat">
264
+ {% if right.diff %}
265
+ {% for row in right.props %}
266
+ {% set y = 40 + loop.index0 * 55 %}
267
+ <div class="ic {{ row.icon }}" style="left:65px;top:{{ y }}px;width:40px;height:40px"></div>
268
+ {{ T(120, y + 18, row.name, '#ffffff', 24) }}
269
+ {{ T(480, y + 18, row.value, '#ffffff', 20, 'rm') }}
270
+ {% if row.delta %}{{ T(578, y + 18, row.delta, row.delta_color, 20, 'rm') }}{% endif %}
271
+ {% endfor %}
272
+ {% else %}
273
+ {% for row in right.props %}
274
+ {% set i = loop.index0 %}
275
+ {% if i < 4 %}
276
+ {% set x = 251 * (i % 2) %}
277
+ {% set y = 40 + (i // 2) * 55 %}
278
+ <div class="ic {{ row.icon }}" style="left:{{ 60 + x }}px;top:{{ y }}px;width:40px;height:40px"></div>
279
+ {{ T(115 + x, y + 18, row.name, row.color, 24) }}
280
+ {{ T(530 - 251 * ((i + 1) % 2), y + 18, row.value, row.color, 24, 'rm') }}
281
+ {% else %}
282
+ {% set y = 40 + (i - 2) * 55 %}
283
+ <div class="ic {{ row.icon }}" style="left:60px;top:{{ y }}px;width:40px;height:40px"></div>
284
+ {{ T(115, y + 18, row.name, row.color, 24) }}
285
+ {{ T(530, y + 18, row.value, row.color, 24, 'rm') }}
286
+ {% endif %}
287
+ {% endfor %}
288
+ {% endif %}
289
+ </div>
290
+
291
+ <div class="ic {{ right.banner_weapon }}" style="left:-9px;top:{{ weapon_banner_y }}px;width:600px;height:100px"></div>
292
+ {% set w = right.weapon %}
293
+ <div class="weapon {{ w.bg }}"{% if w.cmd %} data-ww-cmd="{{ w.cmd }}"{% endif %} style="top:{{ weapon_y }}px;background-size:600px 300px;background-repeat:no-repeat">
294
+ <div class="ic {{ w.icon }}" style="left:45px;top:0;width:140px;height:140px"></div>
295
+ <div class="weapon-name" style="left:200px;top:30px">
296
+ <span class="name">{{ w.name }}</span>
297
+ <span class="reson" style="background:{{ w.reson_color }}">{{ w.reson }}</span>
298
+ </div>
299
+ {{ T(203, 75, w.level, '#ffffff', 30) }}
300
+ {% for i in range(w.breach) %}
301
+ <div class="ic {{ w.promote }}" style="left:{{ 200 + 40 * i }}px;top:100px;width:40px;height:40px"></div>
302
+ {% endfor %}
303
+ {% for stat in w.stats %}
304
+ {% set y = 187 + loop.index0 * 50 %}
305
+ <div class="ic {{ stat.icon }}" style="left:65px;top:{{ y }}px;width:40px;height:40px"></div>
306
+ {{ T(115 if w.has_branch else 130, y + 20, stat.name, '#ffffff', 30) }}
307
+ {{ T(415 if w.has_branch else 500, y + 20, stat.value, '#ffffff', 30, 'rm') }}
308
+ {% endfor %}
309
+ {% if w.branch_icon %}
310
+ <div class="ic {{ w.branch_icon }}" style="left:400px;top:182px;width:100px;height:100px"></div>
311
+ {% endif %}
312
+ </div>
313
+ </div>
314
+ {% endif %}
315
+
316
+ {% if stat_column %}
317
+ <div class="right" style="left:580px;top:55px;width:500px">
318
+ {% for group in stat_column.groups %}
319
+ {% set gi = loop.index0 %}
320
+ {% for row in group %}
321
+ <div style="position:absolute;left:0;top:{{ (loop.index0 + gi * 4) * 70 }}px;transform:scale(1.25);transform-origin:top left">
322
+ {{ STATBAR(row, stat_column.bar_even, stat_column.bar_odd, 0, 0) }}
323
+ </div>
324
+ {% endfor %}
325
+ {% endfor %}
326
+ </div>
327
+ {% endif %}
328
+ {% if side_strip %}
329
+ <div class="ic {{ side_strip }}" style="left:1075px;top:50px;width:70px;height:1000px"></div>
330
+ {% endif %}
331
+ </div>
332
+
333
+ {% if score_band %}
334
+ <div class="score-band" {% if score_band.cmd %}data-ww-cmd="{{ score_band.cmd }}"{% endif %}>
335
+ <div class="ic grade {{ score_band.icon }}"></div>
336
+ <div class="t a-mm value" style="left:400px;top:60px">{{ score_band.value }}</div>
337
+ <div class="t a-mm label" style="left:400px;top:130px">{{ score_band.label }}</div>
338
+ </div>
339
+ {% endif %}
340
+
341
+ {% if skills %}
342
+ <div class="skills"{% if skills.cmd %} data-ww-cmd="{{ skills.cmd }}"{% endif %}>
343
+ <div class="ic bar {{ skills.bar }}"></div>
344
+ {% for item in skills.entries %}
345
+ <div class="item {{ skills.bg }}" style="left:{{ 20 + loop.index0 * 215 }}px;top:-20px;background-size:400px 200px;background-repeat:no-repeat">
346
+ <div class="ic {{ item.icon }}" style="left:57px;top:65px;width:70px;height:70px"></div>
347
+ {{ T(150, 83, item.type, '#ffffff', 25) }}
348
+ {{ T(150, 113, item.level, '#ffffff', 25) }}
349
+ </div>
350
+ {% endfor %}
351
+ </div>
352
+ {% endif %}
353
+
354
+ {% if chains and chains.items %}
355
+ <div class="chains"{% if chains.cmd %} data-ww-cmd="{{ chains.cmd }}"{% endif %}>
356
+ {% for chain in chains['items'] %}
357
+ <div class="item" style="left:{{ loop.index0 * 190 }}px">
358
+ <div class="ic {{ chain.image }}" style="inset:0;width:300px;height:300px"></div>
359
+ <div class="t a-mm name {{ 'small' if chain.small }}" style="left:147px;top:230px">{{ chain.name }}</div>
360
+ </div>
361
+ {% endfor %}
362
+ </div>
363
+ {% endif %}
364
+
365
+ {% if echo %}
366
+ <div class="echo" style="height:{{ echo.height }}px">
367
+ <div class="ic banner {{ echo.banner }}"></div>
368
+ {% if echo.change_command %}{{ T(50, 90, echo.change_command, 'rgb(234,183,4)', 18) }}{% endif %}
369
+ {% if echo.title %}
370
+ <div class="title-bar {{ echo.title_bar }}" style="background-size:500px 100px;background-repeat:no-repeat">
371
+ {{ T(250, 50, echo.title, 'rgb(234,183,4)', 30, 'mm', '', 700) }}
372
+ </div>
373
+ {% endif %}
374
+
375
+ {% for group in echo.stat_groups %}
376
+ {% set gi = loop.index0 %}
377
+ {% for row in group %}
378
+ {{ STATBAR(row, echo.bar_even, echo.bar_odd, 40 + gi * 370, 100 + loop.index0 * 50) }}
379
+ {% endfor %}
380
+ {% endfor %}
381
+ {% if echo.template %}{% set tb = echo.template %}
382
+ <div class="bar {{ echo.bar_odd }}" style="left:{{ tb.left }}px;top:45px;width:{{ tb.width }}px">
383
+ {{ T(20, 50, tb.label, '#ffffff', 24) }}
384
+ {{ T(tb.width - 50, 50, tb.name, 'rgb(255,255,0)', tb.size, 'rm') }}
385
+ </div>
386
+ {% endif %}
387
+
388
+ {% if echo.grade %}{{ GRADE(echo.grade, 30, echo.card_top) }}{% endif %}
389
+ {% for card in echo.cards %}
390
+ {{ ECHOCARD(card, 30 + ((loop.index0 + 1) % 3) * 385, echo.card_top + ((loop.index0 + 1) // 3) * echo.card_gap, card.cmd or echo.card_cmd) }}
391
+ {% endfor %}
392
+ </div>
393
+ {% endif %}
394
+
395
+ {% if damage %}
396
+ <div class="rows" style="height:{{ 60 * (damage.rows | length + 1) + 50 }}px">
397
+ {{ DMGHEAD(damage.bar_odd, damage.head, 10) }}
398
+ {% for row in damage.rows %}
399
+ {{ DMGROW(damage.bar_even if loop.index0 % 2 == 0 else damage.bar_odd, row, 10 + (loop.index0 + 1) * 60) }}
400
+ {% endfor %}
401
+ </div>
402
+ {% endif %}
403
+
404
+ {% if single_damage %}
405
+ <div class="rows" style="height:{{ 190 + 60 * (single_damage.buffs | length) + 40 }}px">
406
+ {{ DMGHEAD(single_damage.bar_odd, single_damage.head, 10) }}
407
+ {{ DMGROW(single_damage.bar_even, single_damage.row, 70) }}
408
+ <div class="rowbar {{ single_damage.bar_odd }}" style="top:130px">
409
+ {{ T(600, 50, single_damage.buff_title, '#ffffff', 24, 'mm') }}
410
+ </div>
411
+ {% for buff in single_damage.buffs %}
412
+ <div class="rowbar {{ single_damage.bar_even if loop.index0 % 2 == 0 else single_damage.bar_odd }}" style="top:{{ 190 + loop.index0 * 60 }}px">
413
+ {{ T(400, 50, buff.name, '#ffffff', 24, 'rm') }}
414
+ {{ T(800, 50, buff.value, '#ffffff', 24, 'mm') }}
415
+ </div>
416
+ {% endfor %}
417
+ </div>
418
+ {% endif %}
419
+
420
+ {% if optimize %}
421
+ {% set rule_h = 58 + 14 + (optimize.rule_lines | length) * 40 + 18 %}
422
+ <div class="rows" style="height:{{ 60 * (optimize.rows | length + 1) + 24 + rule_h + 60 }}px">
423
+ <div class="rowbar {{ optimize.bar_odd }}" style="top:0">
424
+ {{ T(600, 50, optimize.title, 'rgb(234,183,4)', 30, 'mm', '', 700) }}
425
+ </div>
426
+ {% for row in optimize.rows %}
427
+ <div class="rowbar {{ optimize.bar_even if loop.index0 % 2 == 0 else optimize.bar_odd }}" style="top:{{ (loop.index0 + 1) * 60 }}px">
428
+ {{ T(300, 50, row.label, '#ffffff', 24, 'rm') }}
429
+ {{ T(700, 50, row.text, '#ffffff', 24, 'mm') }}
430
+ </div>
431
+ {% endfor %}
432
+ <div class="rule-panel" style="top:{{ 60 * (optimize.rows | length) + 100 + 24 }}px;height:{{ rule_h }}px">
433
+ {{ T(40, 33, optimize.rule_title, 'rgb(234,183,4)', 28, 'lm', '', 700) }}
434
+ {{ T(1100, 33, optimize.rule_hint, 'rgb(234,183,4)', 28, 'rm') }}
435
+ <div class="hr" style="top:58px"></div>
436
+ {% for line in optimize.rule_lines %}
437
+ {{ T(40, 92 + loop.index0 * 40, line, 'rgb(216,216,216)', 22) }}
438
+ {% endfor %}
439
+ </div>
440
+ </div>
441
+ {% endif %}
442
+
443
+ {% if weight_tables %}
444
+ {% for table in weight_tables %}
445
+ <div class="weight-table">
446
+ <div class="plate"></div>
447
+ <div class="t a-lt" style="left:25px;top:20px;color:rgb(234,183,4);font-size:36px;font-weight:700">{{ table.title }}</div>
448
+ {% for row in table.rows %}
449
+ {% set y = 80 + loop.index0 * 40 %}
450
+ {% for cell in row.cells %}
451
+ <div class="cell {{ 'head' if row.head }}" style="left:{{ 25 + loop.index0 * 230 }}px;top:{{ y }}px;color:{{ cell.color }};background:{{ 'rgba(255,255,255,.12)' if row.odd and not row.head else 'rgba(0,0,0,.35)' }}">{{ cell.text }}</div>
452
+ {% endfor %}
453
+ {% endfor %}
454
+ {% for note in table.notes %}
455
+ <div class="t a-lt" style="left:25px;top:{{ 750 + loop.index0 * 50 }}px;font-size:24px">{{ note }}</div>
456
+ {% endfor %}
457
+ </div>
458
+ {% endfor %}
459
+ {% endif %}
460
+
461
+ <div class="footer"><div class="ic mark {{ footer }}"></div></div>
462
+ </div>
463
+ </div>
464
+ </body>
465
+ </html>