prts-moonlit 1.0.0
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.
- package/assets/boy.jpg +0 -0
- package/assets/card.jpg +0 -0
- package/assets/crew.jpg +0 -0
- package/assets/face.jpg +0 -0
- package/assets/fig.jpg +0 -0
- package/assets/grn.jpg +0 -0
- package/assets/hero.jpg +0 -0
- package/assets/hero.png +0 -0
- package/assets/scene.jpg +0 -0
- package/assets/taro.jpg +0 -0
- package/assets/taro78/k00.jpg +0 -0
- package/assets/taro78/k01.jpg +0 -0
- package/assets/taro78/k02.jpg +0 -0
- package/assets/taro78/k03.jpg +0 -0
- package/assets/taro78/k04.jpg +0 -0
- package/assets/taro78/k05.jpg +0 -0
- package/assets/taro78/k06.jpg +0 -0
- package/assets/taro78/k07.jpg +0 -0
- package/assets/taro78/k08.jpg +0 -0
- package/assets/taro78/k09.jpg +0 -0
- package/assets/taro78/k10.jpg +0 -0
- package/assets/taro78/k11.jpg +0 -0
- package/assets/taro78/k12.jpg +0 -0
- package/assets/taro78/k13.jpg +0 -0
- package/assets/taro78/k14.jpg +0 -0
- package/assets/taro78/k15.jpg +0 -0
- package/assets/taro78/k16.jpg +0 -0
- package/assets/taro78/k17.jpg +0 -0
- package/assets/taro78/k18.jpg +0 -0
- package/assets/taro78/k19.jpg +0 -0
- package/assets/taro78/k20.jpg +0 -0
- package/assets/taro78/k21.jpg +0 -0
- package/assets/taro78/k22.jpg +0 -0
- package/assets/taro78/k23.jpg +0 -0
- package/assets/taro78/k24.jpg +0 -0
- package/assets/taro78/k25.jpg +0 -0
- package/assets/taro78/k26.jpg +0 -0
- package/assets/taro78/k27.jpg +0 -0
- package/assets/taro78/k28.jpg +0 -0
- package/assets/taro78/k29.jpg +0 -0
- package/assets/taro78/k30.jpg +0 -0
- package/assets/taro78/k31.jpg +0 -0
- package/assets/taro78/k32.jpg +0 -0
- package/assets/taro78/k33.jpg +0 -0
- package/assets/taro78/k34.jpg +0 -0
- package/assets/taro78/k35.jpg +0 -0
- package/assets/taro78/k36.jpg +0 -0
- package/assets/taro78/k37.jpg +0 -0
- package/assets/taro78/k38.jpg +0 -0
- package/assets/taro78/k39.jpg +0 -0
- package/assets/taro78/k40.jpg +0 -0
- package/assets/taro78/k41.jpg +0 -0
- package/assets/taro78/k42.jpg +0 -0
- package/assets/taro78/k43.jpg +0 -0
- package/assets/taro78/k44.jpg +0 -0
- package/assets/taro78/k45.jpg +0 -0
- package/assets/taro78/k46.jpg +0 -0
- package/assets/taro78/k47.jpg +0 -0
- package/assets/taro78/k48.jpg +0 -0
- package/assets/taro78/k49.jpg +0 -0
- package/assets/taro78/k50.jpg +0 -0
- package/assets/taro78/k51.jpg +0 -0
- package/assets/taro78/k52.jpg +0 -0
- package/assets/taro78/k53.jpg +0 -0
- package/assets/taro78/k54.jpg +0 -0
- package/assets/taro78/k55.jpg +0 -0
- package/assets/taro78/k56.jpg +0 -0
- package/assets/taro78/k57.jpg +0 -0
- package/assets/taro78/k58.jpg +0 -0
- package/assets/taro78/k59.jpg +0 -0
- package/assets/taro78/k60.jpg +0 -0
- package/assets/taro78/k61.jpg +0 -0
- package/assets/taro78/k62.jpg +0 -0
- package/assets/taro78/k63.jpg +0 -0
- package/assets/taro78/k64.jpg +0 -0
- package/assets/taro78/k65.jpg +0 -0
- package/assets/taro78/k66.jpg +0 -0
- package/assets/taro78/k67.jpg +0 -0
- package/assets/taro78/k68.jpg +0 -0
- package/assets/taro78/k69.jpg +0 -0
- package/assets/taro78/k70.jpg +0 -0
- package/assets/taro78/k71.jpg +0 -0
- package/assets/taro78/k72.jpg +0 -0
- package/assets/taro78/k73.jpg +0 -0
- package/assets/taro78/k74.jpg +0 -0
- package/assets/taro78/k75.jpg +0 -0
- package/assets/taro78/k76.jpg +0 -0
- package/assets/taro78/k77.jpg +0 -0
- package/bgm.mp3 +0 -0
- package/gacha.html +1043 -0
- package/index.html +2757 -0
- package/ledger.html +1003 -0
- package/package.json +6 -0
package/ledger.html
ADDED
|
@@ -0,0 +1,1003 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="zh-CN">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover, maximum-scale=1, user-scalable=no">
|
|
6
|
+
<meta name="theme-color" content="#04060f">
|
|
7
|
+
<meta name="color-scheme" content="dark">
|
|
8
|
+
<title>小账本</title>
|
|
9
|
+
<!--BUNDLED: data.js + app.js 已内联,改数据请编辑 data.js 后重跑 sync_from_photos.py-->
|
|
10
|
+
<style>
|
|
11
|
+
:root{
|
|
12
|
+
--blue:#1b5cff; --blue-m:#0b2a8f; --blue-d:#061748; --ink:#04060f;
|
|
13
|
+
--paper:#f2f7ff; --lime:#d9f838; --out:#ff5c5c; --in:#5fe08a;
|
|
14
|
+
--dim:rgba(242,247,255,.62); --line:rgba(242,247,255,.12);
|
|
15
|
+
color-scheme:dark;
|
|
16
|
+
}
|
|
17
|
+
*{box-sizing:border-box;-webkit-tap-highlight-color:transparent}
|
|
18
|
+
html,body{margin:0;padding:0}
|
|
19
|
+
body{
|
|
20
|
+
font-family:-apple-system,BlinkMacSystemFont,"PingFang SC","Helvetica Neue",Arial,sans-serif;
|
|
21
|
+
background:var(--ink);color:var(--paper);font-size:15px;line-height:1.4;
|
|
22
|
+
padding-bottom:112px;overflow-x:hidden;-webkit-font-smoothing:antialiased;
|
|
23
|
+
}
|
|
24
|
+
/* 背景层:斜向蓝渐变 + 放射线 + 网点 */
|
|
25
|
+
.bg{position:fixed;inset:0;z-index:0;pointer-events:none;
|
|
26
|
+
background:linear-gradient(160deg,var(--blue-m) 0%,var(--blue) 34%,var(--blue-d) 62%,var(--ink) 100%)}
|
|
27
|
+
.bg:after{content:'';position:absolute;inset:-20%;
|
|
28
|
+
background:repeating-conic-gradient(from 0deg at 82% 4%, rgba(0,0,0,.42) 0 .55deg, transparent .55deg 6.2deg)}
|
|
29
|
+
.dots{position:fixed;inset:0;z-index:1;pointer-events:none;
|
|
30
|
+
background-image:radial-gradient(circle,#04123c 1.3px,transparent 1.4px);background-size:7px 7px;opacity:.45}
|
|
31
|
+
.wrap{position:relative;z-index:2;max-width:560px;margin:0 auto;padding:0 16px}
|
|
32
|
+
|
|
33
|
+
/* ===== 标题 ===== */
|
|
34
|
+
.hd{display:flex;align-items:flex-end;justify-content:space-between;gap:10px;padding:calc(16px + env(safe-area-inset-top)) 2px 10px}
|
|
35
|
+
.hd h1{margin:0;font-size:32px;font-weight:900;font-style:italic;letter-spacing:-.04em;line-height:1.15}
|
|
36
|
+
.hd h1 span{display:inline-block;background:var(--paper);color:var(--ink);padding:2px 12px 5px;
|
|
37
|
+
transform:skewX(-10deg);box-shadow:5px 5px 0 var(--ink), 7px 7px 0 var(--lime)}
|
|
38
|
+
.hd h1 span i{font-style:normal;display:inline-block;transform:skewX(10deg)}
|
|
39
|
+
.sound{width:42px;height:42px;border:2px solid var(--ink);background:var(--paper);color:var(--ink);
|
|
40
|
+
transform:skewX(-10deg);font-size:16px;display:grid;place-items:center;cursor:pointer;
|
|
41
|
+
box-shadow:4px 4px 0 var(--ink);flex:none}
|
|
42
|
+
.sound i{font-style:normal;transform:skewX(10deg);display:inline-block}
|
|
43
|
+
.hd-r{display:flex;align-items:center;gap:10px}
|
|
44
|
+
.homeb{height:42px;padding:0 13px;border:2px solid var(--ink);background:var(--paper);color:var(--ink);
|
|
45
|
+
transform:skewX(-10deg);font:inherit;font-weight:900;font-size:13px;letter-spacing:.05em;
|
|
46
|
+
display:grid;place-items:center;cursor:pointer;box-shadow:4px 4px 0 var(--ink);flex:none}
|
|
47
|
+
.homeb i{font-style:normal;transform:skewX(10deg);display:inline-block}
|
|
48
|
+
.homeb:active{transform:skewX(-10deg) translate(2px,2px);box-shadow:2px 2px 0 var(--ink)}
|
|
49
|
+
|
|
50
|
+
/* ===== 汇总卡 ===== */
|
|
51
|
+
.summary{
|
|
52
|
+
background:linear-gradient(150deg,rgba(4,6,15,.94),rgba(6,23,72,.94));
|
|
53
|
+
border:2px solid var(--ink);box-shadow:6px 6px 0 var(--ink);
|
|
54
|
+
clip-path:polygon(0 0,100% 0,100% calc(100% - 20px),calc(100% - 20px) 100%,0 100%);
|
|
55
|
+
padding:14px 16px 0;margin:6px 0 14px;
|
|
56
|
+
}
|
|
57
|
+
.sum-top{display:flex;align-items:baseline;justify-content:space-between;gap:10px}
|
|
58
|
+
.sum-top .k{font-size:11px;letter-spacing:.24em;color:var(--lime);font-weight:800;font-style:italic;flex:none}
|
|
59
|
+
.bal{margin:0;font-size:42px;font-weight:900;font-style:italic;letter-spacing:-.045em;
|
|
60
|
+
font-variant-numeric:tabular-nums;text-align:right;word-break:break-all;
|
|
61
|
+
text-shadow:3px 3px 0 rgba(4,6,15,.9)}
|
|
62
|
+
.bal small{font-size:22px;font-weight:800;color:var(--dim);margin-right:2px;letter-spacing:0;font-style:normal}
|
|
63
|
+
.sum-row{display:flex;margin-top:12px;border-top:1px solid var(--line)}
|
|
64
|
+
.sum-row>div{flex:1;min-width:0;padding:11px 0 14px}
|
|
65
|
+
.sum-row>div+div{border-left:1px solid var(--line);padding-left:12px}
|
|
66
|
+
.sum-row>div:not(:last-child){padding-right:10px}
|
|
67
|
+
.sum-row .k{display:block;font-size:10px;color:var(--dim);font-weight:700;letter-spacing:.1em;margin-bottom:2px}
|
|
68
|
+
.sum-row b{font-size:16px;font-weight:800;font-style:italic;font-variant-numeric:tabular-nums;letter-spacing:-.02em}
|
|
69
|
+
|
|
70
|
+
/* ===== 主卡片(tabs + 内容) ===== */
|
|
71
|
+
.card{
|
|
72
|
+
background:rgba(4,6,15,.86);border:2px solid var(--ink);box-shadow:6px 6px 0 var(--ink);
|
|
73
|
+
clip-path:polygon(0 0,100% 0,100% calc(100% - 20px),calc(100% - 20px) 100%,0 100%);
|
|
74
|
+
padding:14px 14px 16px;
|
|
75
|
+
}
|
|
76
|
+
.card section{display:none}
|
|
77
|
+
.card section.on{display:block}
|
|
78
|
+
|
|
79
|
+
/* ===== 顶部 Tab ===== */
|
|
80
|
+
.tabs{display:flex;gap:6px;margin-bottom:14px}
|
|
81
|
+
.tabs button{
|
|
82
|
+
flex:1;border:2px solid var(--ink);background:rgba(242,247,255,.08);color:var(--dim);
|
|
83
|
+
padding:9px 0;font-size:13px;font-weight:800;font-style:italic;font-family:inherit;
|
|
84
|
+
cursor:pointer;transform:skewX(-8deg);letter-spacing:.04em;
|
|
85
|
+
}
|
|
86
|
+
.tabs button span{display:inline-block;transform:skewX(8deg)}
|
|
87
|
+
.tabs button.on{background:var(--paper);color:var(--ink);box-shadow:3px 3px 0 var(--lime)}
|
|
88
|
+
|
|
89
|
+
/* ===== 支出/收入 分段 ===== */
|
|
90
|
+
.seg{display:flex;gap:6px;margin:0 0 14px}
|
|
91
|
+
.seg button{
|
|
92
|
+
flex:1;border:2px solid var(--ink);background:rgba(242,247,255,.08);color:var(--dim);
|
|
93
|
+
padding:10px 0;font-size:15px;font-weight:900;font-style:italic;font-family:inherit;
|
|
94
|
+
cursor:pointer;transform:skewX(-8deg);
|
|
95
|
+
}
|
|
96
|
+
.seg button span{display:inline-block;transform:skewX(8deg)}
|
|
97
|
+
.seg button.on-out{background:var(--out);color:#fff;box-shadow:3px 3px 0 var(--ink)}
|
|
98
|
+
.seg button.on-in{background:var(--in);color:#04130a;box-shadow:3px 3px 0 var(--ink)}
|
|
99
|
+
|
|
100
|
+
/* ===== 金额输入 ===== */
|
|
101
|
+
.amt-card{display:flex;align-items:baseline;gap:6px;border-bottom:3px solid var(--lime);
|
|
102
|
+
padding:2px 2px 8px;margin:0 0 2px}
|
|
103
|
+
.amt-card>span{font-size:26px;font-weight:900;font-style:italic;color:var(--lime);flex:none}
|
|
104
|
+
.amt-card input{
|
|
105
|
+
flex:1;min-width:0;border:0;outline:0;background:transparent;font-size:38px;font-weight:900;
|
|
106
|
+
font-style:italic;letter-spacing:-.03em;font-family:inherit;color:var(--paper);
|
|
107
|
+
font-variant-numeric:tabular-nums;padding:2px 0;
|
|
108
|
+
}
|
|
109
|
+
.amt-card input::placeholder{color:rgba(242,247,255,.25)}
|
|
110
|
+
|
|
111
|
+
.lbl{font-size:10.5px;letter-spacing:.18em;font-weight:800;font-style:italic;color:var(--lime);
|
|
112
|
+
margin:16px 2px 9px;text-transform:uppercase}
|
|
113
|
+
|
|
114
|
+
/* ===== 分类 chips ===== */
|
|
115
|
+
.chips{display:flex;flex-wrap:wrap;gap:7px}
|
|
116
|
+
.chips button{
|
|
117
|
+
border:2px solid var(--ink);background:rgba(242,247,255,.1);color:var(--paper);
|
|
118
|
+
padding:9px 12px;font-size:13px;font-weight:800;font-style:italic;font-family:inherit;
|
|
119
|
+
cursor:pointer;box-shadow:3px 3px 0 rgba(4,6,15,.9);
|
|
120
|
+
}
|
|
121
|
+
.chips button.on{background:var(--lime);color:var(--ink);box-shadow:3px 3px 0 var(--ink)}
|
|
122
|
+
|
|
123
|
+
/* ===== 备注/日期 分组 ===== */
|
|
124
|
+
.group{background:rgba(242,247,255,.06);border:2px solid var(--ink);margin-top:14px;
|
|
125
|
+
box-shadow:3px 3px 0 rgba(4,6,15,.9)}
|
|
126
|
+
.row{display:flex;align-items:center;gap:10px;padding:0 12px;min-height:50px}
|
|
127
|
+
.row+.row{border-top:1px solid var(--line)}
|
|
128
|
+
.row .rk{font-size:13px;color:var(--dim);font-weight:700;flex:none;letter-spacing:.05em}
|
|
129
|
+
input.txt{flex:1;min-width:0;border:0;outline:0;background:transparent;font-size:15px;
|
|
130
|
+
font-family:inherit;color:var(--paper);padding:13px 0}
|
|
131
|
+
input.txt::placeholder{color:rgba(242,247,255,.4)}
|
|
132
|
+
input.rdate{margin-left:auto;width:auto;max-width:60%;flex:0 0 auto;border:0;outline:0;
|
|
133
|
+
background:transparent;font-size:15px;font-family:inherit;color:var(--lime);
|
|
134
|
+
text-align:right;padding:13px 0;-webkit-appearance:none;appearance:none;font-weight:700}
|
|
135
|
+
input.rdate::-webkit-date-and-time-value{text-align:right}
|
|
136
|
+
|
|
137
|
+
/* ===== 保存 ===== */
|
|
138
|
+
.save{width:100%;margin:18px 0 2px;border:3px solid var(--ink);padding:15px;
|
|
139
|
+
background:var(--lime);color:var(--ink);font-size:17px;font-weight:900;font-style:italic;
|
|
140
|
+
font-family:inherit;cursor:pointer;letter-spacing:.08em;transform:skewX(-6deg);
|
|
141
|
+
box-shadow:5px 5px 0 var(--ink)}
|
|
142
|
+
.save span{display:inline-block;transform:skewX(6deg)}
|
|
143
|
+
|
|
144
|
+
/* ===== 月份栏 ===== */
|
|
145
|
+
.month-bar{display:flex;align-items:center;justify-content:space-between;margin:2px 0 10px}
|
|
146
|
+
.month-bar b{font-size:15px;font-weight:900;font-style:italic;letter-spacing:.02em}
|
|
147
|
+
.month-bar button{
|
|
148
|
+
border:2px solid var(--ink);background:var(--lime);color:var(--ink);width:34px;height:32px;
|
|
149
|
+
font-size:16px;font-weight:900;font-family:inherit;cursor:pointer;transform:skewX(-10deg);
|
|
150
|
+
box-shadow:3px 3px 0 var(--ink);line-height:1;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/* ===== 明细 ===== */
|
|
154
|
+
.day-h{display:flex;justify-content:space-between;gap:10px;font-size:11.5px;color:var(--dim);
|
|
155
|
+
font-weight:800;font-style:italic;letter-spacing:.03em;margin:16px 2px 6px;
|
|
156
|
+
border-left:4px solid var(--lime);padding-left:8px}
|
|
157
|
+
.item{display:flex;align-items:center;gap:11px;padding:10px 2px;position:relative;
|
|
158
|
+
border-bottom:1px solid var(--line)}
|
|
159
|
+
.item:last-child{border-bottom:none}
|
|
160
|
+
.ico{width:36px;height:36px;flex:none;display:flex;align-items:center;justify-content:center;
|
|
161
|
+
font-size:17px;background:var(--paper);box-shadow:3px 3px 0 var(--ink)}
|
|
162
|
+
.item .mid{flex:1;min-width:0}
|
|
163
|
+
.item .t{font-size:15px;font-weight:800;font-style:italic;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
|
164
|
+
.item .n{font-size:11px;color:var(--dim);margin-top:2px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;letter-spacing:.03em}
|
|
165
|
+
.item .v{font-size:16px;font-weight:900;font-style:italic;font-variant-numeric:tabular-nums;flex:none;letter-spacing:-.02em}
|
|
166
|
+
.tag{display:inline-block;background:rgba(217,248,56,.16);color:var(--lime);padding:1px 6px;
|
|
167
|
+
font-size:10px;font-weight:800;font-style:italic;margin-right:2px}
|
|
168
|
+
.del{width:28px;height:28px;flex:none;border:2px solid var(--ink);cursor:pointer;
|
|
169
|
+
background:rgba(242,247,255,.08);color:var(--dim);font-size:13px;line-height:1;
|
|
170
|
+
font-family:inherit;padding:0}
|
|
171
|
+
.del:active{background:rgba(255,92,92,.2);color:var(--out)}
|
|
172
|
+
|
|
173
|
+
.empty{text-align:center;color:var(--dim);font-size:14px;padding:40px 10px;font-style:italic}
|
|
174
|
+
.empty div{font-size:36px;margin-bottom:8px;opacity:.7}
|
|
175
|
+
|
|
176
|
+
/* ===== 统计 ===== */
|
|
177
|
+
.stat-grid{display:grid;grid-template-columns:1fr 1fr;gap:8px;margin:4px 0 2px}
|
|
178
|
+
.stat{background:rgba(242,247,255,.06);border:2px solid var(--ink);padding:12px 13px;
|
|
179
|
+
box-shadow:3px 3px 0 rgba(4,6,15,.9)}
|
|
180
|
+
.stat .k{font-size:10px;color:var(--dim);font-weight:700;letter-spacing:.08em}
|
|
181
|
+
.stat .v{font-size:20px;font-weight:900;font-style:italic;margin-top:5px;
|
|
182
|
+
font-variant-numeric:tabular-nums;letter-spacing:-.03em}
|
|
183
|
+
.catrow{margin:14px 2px}
|
|
184
|
+
.catrow .top{display:flex;justify-content:space-between;gap:10px;font-size:13.5px;
|
|
185
|
+
font-weight:700;margin-bottom:7px}
|
|
186
|
+
.catrow .top i{font-style:italic;color:var(--dim);font-weight:600}
|
|
187
|
+
.bar{height:9px;background:rgba(242,247,255,.12);overflow:hidden;transform:skewX(-14deg)}
|
|
188
|
+
.bar i{display:block;height:100%}
|
|
189
|
+
|
|
190
|
+
/* ===== 回收站 ===== */
|
|
191
|
+
.trashbar{margin-top:16px;border-top:1px solid var(--line);padding-top:12px}
|
|
192
|
+
.trashbar .lbl{margin:0 0 9px}
|
|
193
|
+
.tb-row{display:flex;flex-wrap:wrap;gap:8px}
|
|
194
|
+
.ghost{border:2px solid var(--ink);background:rgba(242,247,255,.08);color:var(--lime);
|
|
195
|
+
padding:9px 13px;font-size:12.5px;font-weight:800;font-style:italic;font-family:inherit;
|
|
196
|
+
cursor:pointer;box-shadow:3px 3px 0 rgba(4,6,15,.9)}
|
|
197
|
+
|
|
198
|
+
/* ===== 底部工具栏 ===== */
|
|
199
|
+
.dock{position:fixed;left:0;right:0;bottom:0;display:flex;gap:8px;
|
|
200
|
+
padding:8px 12px calc(8px + env(safe-area-inset-bottom));background:rgba(4,6,15,.95);
|
|
201
|
+
border-top:3px solid var(--ink);z-index:9;max-width:560px;margin:0 auto}
|
|
202
|
+
.dock button{flex:1;border:2px solid var(--ink);padding:12px 6px;font-size:14px;font-weight:900;
|
|
203
|
+
font-style:italic;color:var(--ink);background:var(--lime);font-family:inherit;cursor:pointer;
|
|
204
|
+
transform:skewX(-8deg);letter-spacing:.04em}
|
|
205
|
+
.dock button span{display:inline-block;transform:skewX(8deg)}
|
|
206
|
+
.dock button.dngr{background:var(--out);color:#fff;box-shadow:3px 3px 0 var(--ink)}
|
|
207
|
+
|
|
208
|
+
/* ===== Toast ===== */
|
|
209
|
+
.toast{position:fixed;left:50%;bottom:96px;transform:translateX(-50%) skewX(-10deg);
|
|
210
|
+
background:var(--lime);color:var(--ink);padding:10px 20px;font-size:13.5px;font-weight:900;
|
|
211
|
+
font-style:italic;opacity:0;pointer-events:none;z-index:99;max-width:80%;text-align:center;
|
|
212
|
+
white-space:nowrap;box-shadow:4px 4px 0 var(--ink)}
|
|
213
|
+
.toast.on{opacity:1}
|
|
214
|
+
|
|
215
|
+
/* ===== 确认弹窗 ===== */
|
|
216
|
+
.modal{position:fixed;inset:0;background:rgba(4,6,15,.6);display:none;align-items:center;
|
|
217
|
+
justify-content:center;padding:24px;z-index:200;backdrop-filter:blur(3px)}
|
|
218
|
+
.modal.on{display:flex}
|
|
219
|
+
.mbox{width:280px;max-width:100%;background:var(--blue-d);border:3px solid var(--ink);
|
|
220
|
+
box-shadow:7px 7px 0 var(--ink);text-align:center}
|
|
221
|
+
.mtitle{font-size:16px;font-weight:900;font-style:italic;padding:18px 16px 0;color:var(--lime)}
|
|
222
|
+
.mtext{font-size:13px;color:var(--dim);line-height:1.5;padding:8px 16px 16px;white-space:pre-line}
|
|
223
|
+
.mrow{display:flex;border-top:3px solid var(--ink)}
|
|
224
|
+
.mbtn{flex:1;border:0;background:transparent;padding:13px 6px;font-size:16px;font-weight:900;
|
|
225
|
+
font-style:italic;font-family:inherit;color:var(--paper);cursor:pointer}
|
|
226
|
+
.mbtn+.mbtn{border-left:3px solid var(--ink)}
|
|
227
|
+
.mbtn.cancel{color:var(--dim)}
|
|
228
|
+
.mbtn.danger{color:var(--out)}
|
|
229
|
+
|
|
230
|
+
/* ===== 过场 ===== */
|
|
231
|
+
.wipe{position:fixed;top:0;bottom:0;left:0;width:62%;z-index:80;display:none;pointer-events:none;
|
|
232
|
+
background:linear-gradient(90deg,rgba(217,248,56,0),var(--lime) 45%,var(--paper) 55%,rgba(27,92,255,0));
|
|
233
|
+
opacity:0}
|
|
234
|
+
.burst{position:fixed;inset:0;z-index:90;display:none;align-items:center;justify-content:center;pointer-events:none}
|
|
235
|
+
.burst .flash{position:absolute;inset:0;background:var(--paper);opacity:0}
|
|
236
|
+
.burst .word{position:relative;font-size:52px;font-weight:900;font-style:italic;letter-spacing:-.05em;
|
|
237
|
+
color:var(--lime);text-shadow:5px 5px 0 var(--ink);opacity:0;
|
|
238
|
+
transform:skewX(-9deg) rotate(-8deg);transform-origin:center}
|
|
239
|
+
</style>
|
|
240
|
+
</head>
|
|
241
|
+
<body>
|
|
242
|
+
<div class="bg"></div>
|
|
243
|
+
<div class="dots"></div>
|
|
244
|
+
|
|
245
|
+
<div class="wrap">
|
|
246
|
+
<header class="hd">
|
|
247
|
+
<h1><span><i>小账本</i></span></h1>
|
|
248
|
+
<div class="hd-r">
|
|
249
|
+
<button class="homeb" id="backBtn" title="回终端主页(PRTS)"><i>← 终端主页</i></button>
|
|
250
|
+
<button class="sound" id="soundBtn" title="音效开关"><i>♪</i></button>
|
|
251
|
+
</div>
|
|
252
|
+
</header>
|
|
253
|
+
|
|
254
|
+
<section class="summary">
|
|
255
|
+
<div class="sum-top">
|
|
256
|
+
<span class="k">本月结余</span>
|
|
257
|
+
<p class="bal" id="balTxt"><small>¥</small>0.00</p>
|
|
258
|
+
</div>
|
|
259
|
+
<div class="sum-row">
|
|
260
|
+
<div><span class="k">支出</span><b id="outTxt">¥0.00</b></div>
|
|
261
|
+
<div><span class="k">收入</span><b id="inTxt">¥0.00</b></div>
|
|
262
|
+
<div><span class="k">笔数</span><b id="cntTxt">0</b></div>
|
|
263
|
+
</div>
|
|
264
|
+
</section>
|
|
265
|
+
|
|
266
|
+
<div class="card">
|
|
267
|
+
<div class="tabs">
|
|
268
|
+
<button class="on" data-tab="add"><span>记账</span></button>
|
|
269
|
+
<button data-tab="list"><span>明细</span></button>
|
|
270
|
+
<button data-tab="stat"><span>统计</span></button>
|
|
271
|
+
</div>
|
|
272
|
+
|
|
273
|
+
<section id="tab-add" class="on">
|
|
274
|
+
<div class="seg">
|
|
275
|
+
<button id="btnOut" class="on-out"><span>支出</span></button>
|
|
276
|
+
<button id="btnIn"><span>收入</span></button>
|
|
277
|
+
</div>
|
|
278
|
+
<div class="amt-card">
|
|
279
|
+
<span>¥</span>
|
|
280
|
+
<input id="amt" type="number" inputmode="decimal" placeholder="0.00" step="0.01">
|
|
281
|
+
</div>
|
|
282
|
+
<div class="lbl">分类</div>
|
|
283
|
+
<div class="chips" id="chips"></div>
|
|
284
|
+
<div class="group">
|
|
285
|
+
<div class="row"><input class="txt" id="note" placeholder="备注(可选),如:食堂午饭" maxlength="30"></div>
|
|
286
|
+
<div class="row"><span class="rk">日期</span><input id="date" type="date" class="rdate"></div>
|
|
287
|
+
</div>
|
|
288
|
+
<button class="save" id="saveBtn"><span>保存这笔</span></button>
|
|
289
|
+
</section>
|
|
290
|
+
|
|
291
|
+
<section id="tab-list">
|
|
292
|
+
<div class="month-bar">
|
|
293
|
+
<button id="prevM">‹</button>
|
|
294
|
+
<b id="monthLbl"></b>
|
|
295
|
+
<button id="nextM">›</button>
|
|
296
|
+
</div>
|
|
297
|
+
<div id="listBox"></div>
|
|
298
|
+
</section>
|
|
299
|
+
|
|
300
|
+
<section id="tab-stat">
|
|
301
|
+
<div class="month-bar">
|
|
302
|
+
<button id="prevS">‹</button>
|
|
303
|
+
<b id="statLbl"></b>
|
|
304
|
+
<button id="nextS">›</button>
|
|
305
|
+
</div>
|
|
306
|
+
<div class="stat-grid">
|
|
307
|
+
<div class="stat"><div class="k">本月支出</div><div class="v" id="sOut" style="color:var(--out)">¥0</div></div>
|
|
308
|
+
<div class="stat"><div class="k">本月收入</div><div class="v" id="sIn" style="color:var(--in)">¥0</div></div>
|
|
309
|
+
<div class="stat"><div class="k">日均支出</div><div class="v" id="sAvg">¥0</div></div>
|
|
310
|
+
<div class="stat"><div class="k">最大单笔</div><div class="v" id="sMax">¥0</div></div>
|
|
311
|
+
</div>
|
|
312
|
+
<div class="lbl">支出构成</div>
|
|
313
|
+
<div id="catBox"></div>
|
|
314
|
+
</section>
|
|
315
|
+
</div>
|
|
316
|
+
</div>
|
|
317
|
+
|
|
318
|
+
<div class="dock">
|
|
319
|
+
<button id="expBtn"><span>导出 CSV</span></button>
|
|
320
|
+
<button id="clrBtn" class="dngr"><span>清空数据</span></button>
|
|
321
|
+
</div>
|
|
322
|
+
|
|
323
|
+
<div class="toast" id="toast"></div>
|
|
324
|
+
|
|
325
|
+
<div class="modal" id="modal">
|
|
326
|
+
<div class="mbox">
|
|
327
|
+
<div class="mtitle"></div>
|
|
328
|
+
<div class="mtext"></div>
|
|
329
|
+
<div class="mrow">
|
|
330
|
+
<button class="mbtn cancel">取消</button>
|
|
331
|
+
<button class="mbtn ok">确定</button>
|
|
332
|
+
</div>
|
|
333
|
+
</div>
|
|
334
|
+
</div>
|
|
335
|
+
|
|
336
|
+
<div class="wipe" id="wipe"></div>
|
|
337
|
+
<div class="burst" id="burst"><div class="flash"></div><div class="word">記録完了!</div></div>
|
|
338
|
+
|
|
339
|
+
<script>
|
|
340
|
+
/* ===== 内联数据(原 data.js)===== */
|
|
341
|
+
/* 小账本 · 数据文件 */
|
|
342
|
+
|
|
343
|
+
/* 自动导入源:已启用(2026-09-25 主人要求导入这些账单)。
|
|
344
|
+
每次打开账本时,这里的记录会自动并入;id 相同的不会重复;
|
|
345
|
+
已被删进回收站的 id 不会被回灌。 */
|
|
346
|
+
window.PENDING_IMPORT = [{"id": "auto_101c82976e0b", "type": "out", "amount": 39.47, "cat": "其他", "note": "京东自营旗舰店(7707)", "date": "2026-09-24", "ts": 1790254397000, "src": "银行短信", "conf": 0.8, "asset": "9D981081-C14E-4CE2-8492-6E92B4152E55/L0/001"}, {"id": "auto_e2b66a222efd", "type": "out", "amount": 506.8, "cat": "其他", "note": "支付宝-*信…息技术有限公司(7707)", "date": "2026-09-24", "ts": 1790254397000, "src": "银行短信", "conf": 0.95, "asset": "9D981081-C14E-4CE2-8492-6E92B4152E55/L0/001"}, {"id": "auto_5097e75d131e", "type": "out", "amount": 28.9, "cat": "购物", "note": "支付宝-深圳市闪魔数码科技集团有限公(7707)", "date": "2026-09-24", "ts": 1790254397000, "src": "银行短信", "conf": 0.95, "asset": "9D981081-C14E-4CE2-8492-6E92B4152E55/L0/001"}, {"id": "auto_0481a45c4e6d", "type": "out", "amount": 44.0, "cat": "娱乐", "note": "财付通-Apple(7707)", "date": "2026-09-24", "ts": 1790254397000, "src": "银行短信", "conf": 0.95, "asset": "9D981081-C14E-4CE2-8492-6E92B4152E55/L0/001"}, {"id": "auto_9f9f6d94d23c", "type": "out", "amount": 50.0, "cat": "学习", "note": "支付宝-杭州深度求索人工智能基础技术(7707)", "date": "2026-09-24", "ts": 1790254397000, "src": "银行短信", "conf": 0.95, "asset": "9D981081-C14E-4CE2-8492-6E92B4152E55/L0/001"}, {"id": "auto_277eea1c05a9", "type": "out", "amount": 39.4, "cat": "餐饮", "note": "支付宝-上海拉扎斯信息科技有限公司(7707)", "date": "2026-09-24", "ts": 1790254397000, "src": "银行短信", "conf": 0.95, "asset": "9D981081-C14E-4CE2-8492-6E92B4152E55/L0/001"}, {"id": "auto_78ca2d1e437a", "type": "in", "amount": 4.49, "cat": "其他", "note": "银联入账(7707)", "date": "2026-09-24", "ts": 1790254397000, "src": "银行短信", "conf": 0.95, "asset": "9D981081-C14E-4CE2-8492-6E92B4152E55/L0/001"}, {"id": "auto_6f35ce49913c", "type": "out", "amount": 24.0, "cat": "餐饮", "note": "俺来也点餐", "date": "2026-09-24", "ts": 1790254390000, "src": "校园卡", "conf": 0.9, "asset": "BFDD0872-9E40-4813-80F8-884223A69520/L0/001"}, {"id": "auto_da55595cdd44", "type": "out", "amount": 9.2, "cat": "餐饮", "note": "三食堂一楼", "date": "2026-09-24", "ts": 1790254390000, "src": "校园卡", "conf": 0.9, "asset": "BFDD0872-9E40-4813-80F8-884223A69520/L0/001"}, {"id": "auto_3a8c170f5877", "type": "out", "amount": 22.9, "cat": "餐饮", "note": "支付宝-北京三快在线科技有限公司(7707)", "date": "2026-09-25", "ts": 1790307300000, "src": "银行短信", "conf": 0.95, "asset": "DDB8D256-EE87-4997-AF99-29828D339255/L0/001"}, {"id": "auto_d675016b7b70", "type": "out", "amount": 30.0, "cat": "交通", "note": "支付宝-上海公共交通卡股份有限公司(7707)", "date": "2026-09-25", "ts": 1790319720000, "src": "银行短信", "conf": 0.95, "asset": "DDB8D256-EE87-4997-AF99-29828D339255/L0/001"}, {"id": "auto_d8a3be74f08e", "type": "out", "amount": 50.0, "cat": "鲸鲸", "note": "主人给鲸鲸续饭 · 支付宝-杭州深度求索(7707)", "date": "2026-09-26", "ts": 1790399100000, "src": "银行短信", "conf": 0.9, "asset": "5DDDECCA-647A-47A1-BD42-369FD42F5FF5/L0/001/L0/001"}];
|
|
347
|
+
|
|
348
|
+
/* 文件级备份:上面的记录已并入自动导入源,这里留空(原件备份见 shared/ledger/data.js.backup) */
|
|
349
|
+
window.LEDGER_BACKUP = [];
|
|
350
|
+
|
|
351
|
+
</script>
|
|
352
|
+
<script>
|
|
353
|
+
/* ===== 内联逻辑(原 app.js)===== */
|
|
354
|
+
/* 小账本 · app.js */
|
|
355
|
+
(function () {
|
|
356
|
+
'use strict';
|
|
357
|
+
|
|
358
|
+
var KEY = 'minis_ledger_v1';
|
|
359
|
+
var TRASH_KEY = 'minis_ledger_trash_v1';
|
|
360
|
+
|
|
361
|
+
var CATS = {
|
|
362
|
+
out: [
|
|
363
|
+
{ n: '餐饮', e: '🍚', c: '#f4566b' },
|
|
364
|
+
{ n: '交通', e: '🚌', c: '#f79b3e' },
|
|
365
|
+
{ n: '购物', e: '🛍️', c: '#a855f7' },
|
|
366
|
+
{ n: '学习', e: '📚', c: '#3b6cf6' },
|
|
367
|
+
{ n: '娱乐', e: '🎮', c: '#ec4899' },
|
|
368
|
+
{ n: '日用', e: '🧴', c: '#14b8a6' },
|
|
369
|
+
{ n: '医疗', e: '💊', c: '#ef4444' },
|
|
370
|
+
{ n: '鲸鲸', e: '🐋', c: '#1f9dbe' },
|
|
371
|
+
{ n: '其他', e: '📦', c: '#94a3b8' }
|
|
372
|
+
],
|
|
373
|
+
in: [
|
|
374
|
+
{ n: '生活费', e: '💰', c: '#12b886' },
|
|
375
|
+
{ n: '兼职', e: '💼', c: '#0ea5e9' },
|
|
376
|
+
{ n: '奖学金', e: '🏆', c: '#f59e0b' },
|
|
377
|
+
{ n: '红包', e: '🧧', c: '#ef4444' },
|
|
378
|
+
{ n: '其他', e: '📦', c: '#94a3b8' }
|
|
379
|
+
]
|
|
380
|
+
};
|
|
381
|
+
|
|
382
|
+
var S = {
|
|
383
|
+
records: [],
|
|
384
|
+
trash: [],
|
|
385
|
+
type: 'out',
|
|
386
|
+
cat: null,
|
|
387
|
+
tab: 'add',
|
|
388
|
+
// 明细/统计的当前月份(从今天起)
|
|
389
|
+
listYear: null,
|
|
390
|
+
listMonth: null,
|
|
391
|
+
statYear: null,
|
|
392
|
+
statMonth: null
|
|
393
|
+
};
|
|
394
|
+
|
|
395
|
+
/* ---------- 存储 ---------- */
|
|
396
|
+
function loadTrash() {
|
|
397
|
+
try {
|
|
398
|
+
var t = JSON.parse(localStorage.getItem(TRASH_KEY) || '[]');
|
|
399
|
+
S.trash = Array.isArray(t) ? t : [];
|
|
400
|
+
} catch (e) { S.trash = []; }
|
|
401
|
+
}
|
|
402
|
+
function saveTrash() {
|
|
403
|
+
try { localStorage.setItem(TRASH_KEY, JSON.stringify(S.trash)); } catch (e) {}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
function load() {
|
|
407
|
+
loadTrash();
|
|
408
|
+
try {
|
|
409
|
+
var raw = localStorage.getItem(KEY);
|
|
410
|
+
S.records = raw ? JSON.parse(raw) : [];
|
|
411
|
+
if (!Array.isArray(S.records)) S.records = [];
|
|
412
|
+
} catch (e) { S.records = []; }
|
|
413
|
+
|
|
414
|
+
// 被删过的 id(回收站里的)永不回灌
|
|
415
|
+
var dead = {};
|
|
416
|
+
S.trash.forEach(function (r) { if (r && r.id) dead[r.id] = 1; });
|
|
417
|
+
if (Object.keys(dead).length) {
|
|
418
|
+
S.records = S.records.filter(function (r) { return !dead[r.id]; });
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// 合并由脚本自动导入的账单(data.js 的 window.PENDING_IMPORT,默认为空)
|
|
422
|
+
var inc = window.PENDING_IMPORT;
|
|
423
|
+
if (Array.isArray(inc) && inc.length) {
|
|
424
|
+
var seen = {};
|
|
425
|
+
S.records.forEach(function (r) { seen[r.id] = 1; });
|
|
426
|
+
var added = 0;
|
|
427
|
+
inc.forEach(function (r) {
|
|
428
|
+
if (!r || !r.id || seen[r.id] || dead[r.id]) return;
|
|
429
|
+
if (!r.type || !r.amount || !r.date) return;
|
|
430
|
+
seen[r.id] = 1;
|
|
431
|
+
S.records.push(r);
|
|
432
|
+
added++;
|
|
433
|
+
});
|
|
434
|
+
if (added) {
|
|
435
|
+
save();
|
|
436
|
+
setTimeout(function () { toast('自动导入 ' + added + ' 笔账单'); }, 400);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
function save() {
|
|
441
|
+
try { localStorage.setItem(KEY, JSON.stringify(S.records)); } catch (e) {}
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
/* ---------- 工具 ---------- */
|
|
445
|
+
function money(n) {
|
|
446
|
+
return (Math.round(n * 100) / 100).toFixed(2);
|
|
447
|
+
}
|
|
448
|
+
function todayStr() {
|
|
449
|
+
var d = new Date();
|
|
450
|
+
return d.getFullYear() + '-' + pad(d.getMonth() + 1) + '-' + pad(d.getDate());
|
|
451
|
+
}
|
|
452
|
+
function pad(n) { return (n < 10 ? '0' : '') + n; }
|
|
453
|
+
function catOf(type, name) {
|
|
454
|
+
var arr = CATS[type];
|
|
455
|
+
for (var i = 0; i < arr.length; i++) if (arr[i].n === name) return arr[i];
|
|
456
|
+
return { n: name, e: '📦', c: '#94a3b8' };
|
|
457
|
+
}
|
|
458
|
+
function ym(dstr) { return dstr.slice(0, 7); }
|
|
459
|
+
function toast(msg) {
|
|
460
|
+
var t = document.getElementById('toast');
|
|
461
|
+
t.textContent = msg;
|
|
462
|
+
t.classList.add('on');
|
|
463
|
+
clearTimeout(t._h);
|
|
464
|
+
t._h = setTimeout(function () { t.classList.remove('on'); }, 1500);
|
|
465
|
+
}
|
|
466
|
+
function $(id) { return document.getElementById(id); }
|
|
467
|
+
|
|
468
|
+
// 页内确认弹窗:WebView 里 confirm() 会被静默忽略,必须自绘
|
|
469
|
+
function ask(title, text, onOk, okLabel, danger) {
|
|
470
|
+
var m = document.getElementById('modal');
|
|
471
|
+
if (!m) { onOk(); return; }
|
|
472
|
+
m.querySelector('.mtitle').textContent = title;
|
|
473
|
+
m.querySelector('.mtext').textContent = text || '';
|
|
474
|
+
var ok = m.querySelector('.mbtn.ok');
|
|
475
|
+
ok.textContent = okLabel || '确定';
|
|
476
|
+
ok.className = 'mbtn ok' + (danger ? ' danger' : '');
|
|
477
|
+
m.classList.add('on');
|
|
478
|
+
m.querySelector('.mbtn.cancel').onclick = function () { m.classList.remove('on'); };
|
|
479
|
+
ok.onclick = function () { m.classList.remove('on'); onOk(); };
|
|
480
|
+
m.onclick = function (e) { if (e.target === m) m.classList.remove('on'); };
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
/* ---------- 渲染:顶部汇总(当月)---------- */
|
|
484
|
+
function renderHero() {
|
|
485
|
+
var m = ym(todayStr());
|
|
486
|
+
var out = 0, inc = 0, cnt = 0;
|
|
487
|
+
S.records.forEach(function (r) {
|
|
488
|
+
if (ym(r.date) !== m) return;
|
|
489
|
+
cnt++;
|
|
490
|
+
if (r.type === 'out') out += r.amount; else inc += r.amount;
|
|
491
|
+
});
|
|
492
|
+
$('balTxt').innerHTML = '<small>¥</small>' + money(inc - out);
|
|
493
|
+
$('outTxt').textContent = '¥' + money(out);
|
|
494
|
+
$('inTxt').textContent = '¥' + money(inc);
|
|
495
|
+
$('cntTxt').textContent = cnt;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
/* ---------- 渲染:分类 chips ---------- */
|
|
499
|
+
function renderChips() {
|
|
500
|
+
var box = $('chips');
|
|
501
|
+
box.innerHTML = '';
|
|
502
|
+
CATS[S.type].forEach(function (c) {
|
|
503
|
+
var b = document.createElement('button');
|
|
504
|
+
b.textContent = c.e + ' ' + c.n;
|
|
505
|
+
if (S.cat === c.n) b.className = 'on';
|
|
506
|
+
b.onclick = function () { S.cat = c.n; renderChips(); };
|
|
507
|
+
box.appendChild(b);
|
|
508
|
+
});
|
|
509
|
+
if (!S.cat || !CATS[S.type].some(function (x) { return x.n === S.cat; })) {
|
|
510
|
+
S.cat = CATS[S.type][0].n;
|
|
511
|
+
renderChips();
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
/* ---------- 渲染:明细 ---------- */
|
|
516
|
+
function renderList() {
|
|
517
|
+
var y = S.listYear, mo = S.listMonth;
|
|
518
|
+
$('monthLbl').textContent = y + '年' + (mo + 1) + '月';
|
|
519
|
+
var key = y + '-' + pad(mo + 1);
|
|
520
|
+
var rows = S.records.filter(function (r) { return ym(r.date) === key; })
|
|
521
|
+
.sort(function (a, b) { return a.date < b.date ? 1 : (a.date > b.date ? -1 : b.ts - a.ts); });
|
|
522
|
+
|
|
523
|
+
var box = $('listBox');
|
|
524
|
+
if (!rows.length) {
|
|
525
|
+
box.innerHTML = '<div class="empty"><div>🗒️</div>这个月还没有记录</div>' + trashHtml();
|
|
526
|
+
bindTrash();
|
|
527
|
+
return;
|
|
528
|
+
}
|
|
529
|
+
var total = 0;
|
|
530
|
+
rows.forEach(function (r) { if (r.type === 'out') total += r.amount; else total -= r.amount; });
|
|
531
|
+
|
|
532
|
+
var html = '';
|
|
533
|
+
var curDay = '';
|
|
534
|
+
rows.forEach(function (r) {
|
|
535
|
+
if (r.date !== curDay) {
|
|
536
|
+
curDay = r.date;
|
|
537
|
+
var d = new Date(r.date + 'T00:00:00');
|
|
538
|
+
var wd = '日一二三四五六'[d.getDay()];
|
|
539
|
+
html += '<div class="day-h"><span>' + r.date.slice(5).replace('-', '月') + '日 周' + wd +
|
|
540
|
+
'</span><span>' + daySum(r.date) + '</span></div>';
|
|
541
|
+
}
|
|
542
|
+
var c = catOf(r.type, r.cat);
|
|
543
|
+
var sign = r.type === 'out' ? '-' : '+';
|
|
544
|
+
var col = r.type === 'out' ? 'var(--out)' : 'var(--in)';
|
|
545
|
+
var meta = [];
|
|
546
|
+
if (r.src) meta.push('<span class="tag">' + esc(r.src) + '</span>');
|
|
547
|
+
if (r.note) meta.push(esc(r.note)); else meta.push(c.n);
|
|
548
|
+
html += '<div class="item">' +
|
|
549
|
+
'<div class="ico" style="background:' + c.c + '22;color:' + c.c + '">' + c.e + '</div>' +
|
|
550
|
+
'<div class="mid"><div class="t">' + esc(r.cat) + '</div>' +
|
|
551
|
+
'<div class="n">' + meta.join(' · ') + '</div></div>' +
|
|
552
|
+
'<div class="v" style="color:' + col + '">' + sign + '¥' + money(r.amount) + '</div>' +
|
|
553
|
+
'<button class="del" data-id="' + r.id + '">✕</button>' +
|
|
554
|
+
'</div>';
|
|
555
|
+
});
|
|
556
|
+
html += '<div class="day-h" style="margin-top:18px"><span>本月结余</span><span style="color:' +
|
|
557
|
+
(total >= 0 ? 'var(--in)' : 'var(--out)') + '">¥' + money(-total) + '</span></div>';
|
|
558
|
+
|
|
559
|
+
var bakn = (window.LEDGER_BACKUP && window.LEDGER_BACKUP.length) || 0;
|
|
560
|
+
if (S.trash.length || bakn) {
|
|
561
|
+
html += '<div class="trashbar"><div class="lbl">回收站</div><div class="tb-row">';
|
|
562
|
+
if (S.trash.length) {
|
|
563
|
+
html += '<button class="ghost" id="undoBtn">恢复已删除的 ' + S.trash.length + ' 笔</button>';
|
|
564
|
+
html += '<button class="ghost" id="purgeBtn">彻底清空</button>';
|
|
565
|
+
}
|
|
566
|
+
if (bakn) html += '<button class="ghost" id="bakBtn">从文件备份恢复</button>';
|
|
567
|
+
html += '</div></div>';
|
|
568
|
+
}
|
|
569
|
+
box.innerHTML = html;
|
|
570
|
+
bindTrash();
|
|
571
|
+
|
|
572
|
+
box.querySelectorAll('.del').forEach(function (b) {
|
|
573
|
+
b.onclick = function () {
|
|
574
|
+
var id = b.getAttribute('data-id');
|
|
575
|
+
var rec = null;
|
|
576
|
+
S.records = S.records.filter(function (r) {
|
|
577
|
+
if (r.id === id) { rec = r; return false; }
|
|
578
|
+
return true;
|
|
579
|
+
});
|
|
580
|
+
if (rec) { S.trash.push(rec); saveTrash(); }
|
|
581
|
+
save(); renderAll(); toast('已删除(可在回收站恢复)');
|
|
582
|
+
};
|
|
583
|
+
});
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
function trashHtml() {
|
|
587
|
+
var bakn = (window.LEDGER_BACKUP && window.LEDGER_BACKUP.length) || 0;
|
|
588
|
+
if (!S.trash.length && !bakn) return '';
|
|
589
|
+
var h = '<div class="trashbar"><div class="lbl">回收站</div><div class="tb-row">';
|
|
590
|
+
if (S.trash.length) {
|
|
591
|
+
h += '<button class="ghost" id="undoBtn">恢复已删除的 ' + S.trash.length + ' 笔</button>';
|
|
592
|
+
h += '<button class="ghost" id="purgeBtn">彻底清空</button>';
|
|
593
|
+
}
|
|
594
|
+
if (bakn) h += '<button class="ghost" id="bakBtn">从文件备份恢复</button>';
|
|
595
|
+
return h + '</div></div>';
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
function bindTrash() {
|
|
599
|
+
var ub = document.getElementById('undoBtn');
|
|
600
|
+
if (ub) ub.onclick = restoreTrash;
|
|
601
|
+
var pb = document.getElementById('purgeBtn');
|
|
602
|
+
if (pb) pb.onclick = purgeTrash;
|
|
603
|
+
var bb = document.getElementById('bakBtn');
|
|
604
|
+
if (bb) bb.onclick = restoreBackup;
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
function daySum(date) {
|
|
608
|
+
var t = 0;
|
|
609
|
+
S.records.forEach(function (r) {
|
|
610
|
+
if (r.date !== date) return;
|
|
611
|
+
if (r.type === 'out') t += r.amount; else t -= r.amount;
|
|
612
|
+
});
|
|
613
|
+
return (t > 0 ? '支出 ¥' : (t < 0 ? '收入 ¥' : '')) + (t === 0 ? '' : money(Math.abs(t)));
|
|
614
|
+
}
|
|
615
|
+
function esc(s) {
|
|
616
|
+
return String(s).replace(/[&<>"]/g, function (m) {
|
|
617
|
+
return { '&': '&', '<': '<', '>': '>', '"': '"' }[m];
|
|
618
|
+
});
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
/* ---------- 渲染:统计 ---------- */
|
|
622
|
+
function renderStat() {
|
|
623
|
+
var y = S.statYear, mo = S.statMonth;
|
|
624
|
+
$('statLbl').textContent = y + '年' + (mo + 1) + '月';
|
|
625
|
+
var key = y + '-' + pad(mo + 1);
|
|
626
|
+
var rows = S.records.filter(function (r) { return ym(r.date) === key; });
|
|
627
|
+
|
|
628
|
+
var out = 0, inc = 0, max = 0, byCat = {};
|
|
629
|
+
rows.forEach(function (r) {
|
|
630
|
+
if (r.type === 'out') {
|
|
631
|
+
out += r.amount;
|
|
632
|
+
byCat[r.cat] = (byCat[r.cat] || 0) + r.amount;
|
|
633
|
+
if (r.amount > max) max = r.amount;
|
|
634
|
+
} else inc += r.amount;
|
|
635
|
+
});
|
|
636
|
+
var days = new Date(y, mo + 1, 0).getDate();
|
|
637
|
+
var isThisMonth = (todayStr().slice(0, 7) === key);
|
|
638
|
+
var used = isThisMonth ? new Date().getDate() : days;
|
|
639
|
+
|
|
640
|
+
$('sOut').textContent = '¥' + money(out);
|
|
641
|
+
$('sIn').textContent = '¥' + money(inc);
|
|
642
|
+
$('sAvg').textContent = '¥' + money(used ? out / used : 0);
|
|
643
|
+
$('sMax').textContent = '¥' + money(max);
|
|
644
|
+
|
|
645
|
+
var list = Object.keys(byCat).map(function (k) {
|
|
646
|
+
return { n: k, v: byCat[k] };
|
|
647
|
+
}).sort(function (a, b) { return b.v - a.v; });
|
|
648
|
+
|
|
649
|
+
var box = $('catBox');
|
|
650
|
+
if (!list.length) {
|
|
651
|
+
box.innerHTML = '<div class="empty"><div>📊</div>这个月还没有支出</div>';
|
|
652
|
+
return;
|
|
653
|
+
}
|
|
654
|
+
var mx = list[0].v;
|
|
655
|
+
var html = '';
|
|
656
|
+
list.forEach(function (it) {
|
|
657
|
+
var c = catOf('out', it.n);
|
|
658
|
+
var pct = out ? (it.v / out * 100).toFixed(0) : 0;
|
|
659
|
+
html += '<div class="catrow"><div class="top"><span>' + c.e + ' ' + esc(it.n) +
|
|
660
|
+
' <i>' + pct + '%</i></span><span>¥' + money(it.v) + '</span></div>' +
|
|
661
|
+
'<div class="bar"><i style="width:' + Math.max(4, it.v / mx * 100) + '%;background:' + c.c + '"></i></div></div>';
|
|
662
|
+
});
|
|
663
|
+
box.innerHTML = html;
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
function renderAll() { renderHero(); renderList(); renderStat(); }
|
|
667
|
+
|
|
668
|
+
/* ---------- 事件 ---------- */
|
|
669
|
+
function bindTabs() {
|
|
670
|
+
document.querySelectorAll('.tabs button').forEach(function (b) {
|
|
671
|
+
b.onclick = function () {
|
|
672
|
+
document.querySelectorAll('.tabs button').forEach(function (x) { x.classList.remove('on'); });
|
|
673
|
+
b.classList.add('on');
|
|
674
|
+
S.tab = b.getAttribute('data-tab');
|
|
675
|
+
document.querySelectorAll('section').forEach(function (s) { s.classList.remove('on'); });
|
|
676
|
+
$('tab-' + S.tab).classList.add('on');
|
|
677
|
+
};
|
|
678
|
+
});
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
function bindForm() {
|
|
682
|
+
$('btnOut').onclick = function () { setType('out'); };
|
|
683
|
+
$('btnIn').onclick = function () { setType('in'); };
|
|
684
|
+
function setType(t) {
|
|
685
|
+
S.type = t;
|
|
686
|
+
S.cat = CATS[t][0].n;
|
|
687
|
+
$('btnOut').className = t === 'out' ? 'on-out' : '';
|
|
688
|
+
$('btnIn').className = t === 'in' ? 'on-in' : '';
|
|
689
|
+
renderChips();
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
$('saveBtn').onclick = function () {
|
|
693
|
+
var v = parseFloat($('amt').value);
|
|
694
|
+
if (!v || v <= 0) { toast('请填写金额'); $('amt').focus(); return; }
|
|
695
|
+
if (v > 9999999) { toast('金额太大啦'); return; }
|
|
696
|
+
S.records.push({
|
|
697
|
+
id: Date.now() + '' + Math.floor(Math.random() * 1000),
|
|
698
|
+
type: S.type,
|
|
699
|
+
amount: Math.round(v * 100) / 100,
|
|
700
|
+
cat: S.cat,
|
|
701
|
+
note: $('note').value.trim(),
|
|
702
|
+
date: $('date').value || todayStr(),
|
|
703
|
+
ts: Date.now()
|
|
704
|
+
});
|
|
705
|
+
save();
|
|
706
|
+
$('amt').value = '';
|
|
707
|
+
$('note').value = '';
|
|
708
|
+
renderAll();
|
|
709
|
+
toast('已记录 ✓');
|
|
710
|
+
$('amt').focus();
|
|
711
|
+
};
|
|
712
|
+
$('amt').addEventListener('keydown', function (e) {
|
|
713
|
+
if (e.key === 'Enter') $('saveBtn').click();
|
|
714
|
+
});
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
function stepMonth(which, dir) {
|
|
718
|
+
var yk = which === 'list' ? 'listYear' : 'statYear';
|
|
719
|
+
var mk = which === 'list' ? 'listMonth' : 'statMonth';
|
|
720
|
+
var y = S[yk], m = S[mk] + dir;
|
|
721
|
+
if (m < 0) { m = 11; y--; }
|
|
722
|
+
if (m > 11) { m = 0; y++; }
|
|
723
|
+
S[yk] = y; S[mk] = m;
|
|
724
|
+
if (which === 'list') renderList(); else renderStat();
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
function bindMonths() {
|
|
728
|
+
$('prevM').onclick = function () { stepMonth('list', -1); };
|
|
729
|
+
$('nextM').onclick = function () { stepMonth('list', 1); };
|
|
730
|
+
$('prevS').onclick = function () { stepMonth('stat', -1); };
|
|
731
|
+
$('nextS').onclick = function () { stepMonth('stat', 1); };
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
function exportCSV() {
|
|
735
|
+
if (!S.records.length) { toast('还没有数据'); return; }
|
|
736
|
+
var rows = [['日期', '类型', '分类', '金额', '备注']];
|
|
737
|
+
S.records.slice().sort(function (a, b) { return a.date < b.date ? -1 : 1; })
|
|
738
|
+
.forEach(function (r) {
|
|
739
|
+
rows.push([r.date, r.type === 'out' ? '支出' : '收入', r.cat, r.amount.toFixed(2), r.note || '']);
|
|
740
|
+
});
|
|
741
|
+
var csv = '\ufeff' + rows.map(function (r) {
|
|
742
|
+
return r.map(function (x) { return '"' + String(x).replace(/"/g, '""') + '"'; }).join(',');
|
|
743
|
+
}).join('\r\n');
|
|
744
|
+
|
|
745
|
+
var blob = new Blob([csv], { type: 'text/csv;charset=utf-8' });
|
|
746
|
+
var a = document.createElement('a');
|
|
747
|
+
a.href = URL.createObjectURL(blob);
|
|
748
|
+
a.download = '小账本_' + todayStr() + '.csv';
|
|
749
|
+
document.body.appendChild(a);
|
|
750
|
+
a.click();
|
|
751
|
+
setTimeout(function () { URL.revokeObjectURL(a.href); a.remove(); }, 500);
|
|
752
|
+
toast('已导出 CSV');
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
/* ---------- 回收站 ---------- */
|
|
756
|
+
function mergeIn(list, msg) {
|
|
757
|
+
var seen = {};
|
|
758
|
+
S.records.forEach(function (r) { seen[r.id] = 1; });
|
|
759
|
+
var n = 0;
|
|
760
|
+
(list || []).forEach(function (r) {
|
|
761
|
+
if (!r || !r.id || !r.type || !r.amount || !r.date || seen[r.id]) return;
|
|
762
|
+
seen[r.id] = 1; S.records.push(r); n++;
|
|
763
|
+
});
|
|
764
|
+
save(); renderAll();
|
|
765
|
+
toast(n ? msg + ' ' + n + ' 笔' : '没有可恢复的记录');
|
|
766
|
+
}
|
|
767
|
+
function restoreTrash() {
|
|
768
|
+
if (!S.trash.length) return;
|
|
769
|
+
var t = S.trash;
|
|
770
|
+
S.trash = []; saveTrash();
|
|
771
|
+
mergeIn(t, '已恢复');
|
|
772
|
+
}
|
|
773
|
+
function purgeTrash() {
|
|
774
|
+
if (!S.trash.length) return;
|
|
775
|
+
ask('彻底清空回收站?', '共 ' + S.trash.length + ' 笔,删除后无法恢复。', function () {
|
|
776
|
+
S.trash = []; saveTrash(); renderAll(); toast('回收站已清空');
|
|
777
|
+
}, '彻底删除', true);
|
|
778
|
+
}
|
|
779
|
+
function restoreBackup() {
|
|
780
|
+
mergeIn(window.LEDGER_BACKUP, '已从文件备份恢复');
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
function clearAll() {
|
|
784
|
+
if (!S.records.length) { toast('没有数据可清空'); return; }
|
|
785
|
+
ask('清空全部记录?', '共 ' + S.records.length + ' 笔,会移入回收站,\n可在「明细」页恢复。', function () {
|
|
786
|
+
S.trash = S.trash.concat(S.records);
|
|
787
|
+
S.records = [];
|
|
788
|
+
saveTrash(); save(); renderAll(); toast('已清空(回收站可恢复)');
|
|
789
|
+
}, '清空', true);
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
/* ---------- 回终端主页 ---------- */
|
|
793
|
+
function goHome() {
|
|
794
|
+
try{window.__bgmFade&&window.__bgmFade()}catch(e){}
|
|
795
|
+
var p = location.pathname || '';
|
|
796
|
+
var u = 'index.html';
|
|
797
|
+
setTimeout(function () { location.href = u; }, 380);
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
/* ---------- 初始化 ---------- */
|
|
801
|
+
function init() {
|
|
802
|
+
var d = new Date();
|
|
803
|
+
S.listYear = S.statYear = d.getFullYear();
|
|
804
|
+
S.listMonth = S.statMonth = d.getMonth();
|
|
805
|
+
S.type = 'out';
|
|
806
|
+
S.cat = CATS.out[0].n;
|
|
807
|
+
|
|
808
|
+
$('date').value = todayStr();
|
|
809
|
+
// 明细/统计默认展示当前月
|
|
810
|
+
$('monthLbl').textContent = '';
|
|
811
|
+
$('statLbl').textContent = '';
|
|
812
|
+
|
|
813
|
+
load();
|
|
814
|
+
bindTabs(); bindForm(); bindMonths();
|
|
815
|
+
$('expBtn').onclick = exportCSV;
|
|
816
|
+
$('clrBtn').onclick = clearAll;
|
|
817
|
+
var bb = $('backBtn'); if (bb) bb.onclick = goHome;
|
|
818
|
+
renderChips();
|
|
819
|
+
renderAll();
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
if (document.readyState === 'loading') {
|
|
823
|
+
document.addEventListener('DOMContentLoaded', init);
|
|
824
|
+
} else init();
|
|
825
|
+
})();
|
|
826
|
+
|
|
827
|
+
</script>
|
|
828
|
+
<script>
|
|
829
|
+
/* ===== 附加增强:音效 + 过场(不改 app.js 逻辑,纯叠加) ===== */
|
|
830
|
+
(function () {
|
|
831
|
+
var AC = null, soundOn = true;
|
|
832
|
+
function ac() {
|
|
833
|
+
if (!AC) { try { AC = new (window.AudioContext || window.webkitAudioContext)(); } catch (e) { AC = null; } }
|
|
834
|
+
if (AC && AC.state === 'suspended') AC.resume();
|
|
835
|
+
return AC;
|
|
836
|
+
}
|
|
837
|
+
function tone(f, d, type, vol) {
|
|
838
|
+
var c = ac(); if (!c || !soundOn) return;
|
|
839
|
+
var o = c.createOscillator(), g = c.createGain();
|
|
840
|
+
o.type = type || 'triangle'; o.frequency.value = f; g.gain.value = 0;
|
|
841
|
+
o.connect(g); g.connect(c.destination);
|
|
842
|
+
var t = c.currentTime;
|
|
843
|
+
g.gain.linearRampToValueAtTime(vol || 0.06, t + 0.01);
|
|
844
|
+
g.gain.exponentialRampToValueAtTime(0.0001, t + (d || 0.08));
|
|
845
|
+
o.start(t); o.stop(t + (d || 0.08) + 0.03);
|
|
846
|
+
}
|
|
847
|
+
function sting() { tone(660, .09, 'triangle', .06); setTimeout(function () { tone(990, .09); }, 90); setTimeout(function () { tone(1320, .16); }, 180); }
|
|
848
|
+
function tick() { tone(1150, .03, 'square', .025); }
|
|
849
|
+
function tw(dur, cb, done) {
|
|
850
|
+
var t0 = Date.now(), id = setInterval(function () {
|
|
851
|
+
var p = Math.min(1, (Date.now() - t0) / dur); cb(p);
|
|
852
|
+
if (p >= 1) { clearInterval(id); if (done) done(); }
|
|
853
|
+
}, 12);
|
|
854
|
+
}
|
|
855
|
+
function easeOut(p) { return 1 - Math.pow(1 - p, 3); }
|
|
856
|
+
|
|
857
|
+
function sweep() {
|
|
858
|
+
var w = document.getElementById('wipe');
|
|
859
|
+
w.style.display = 'block';
|
|
860
|
+
tw(360, function (p) {
|
|
861
|
+
w.style.transform = 'translateX(' + (-140 + p * 280).toFixed(1) + '%) skewX(-14deg)';
|
|
862
|
+
w.style.opacity = (p < .12 ? p * 8.3 : (p > .88 ? (1 - p) * 8.3 : 1)).toFixed(2);
|
|
863
|
+
}, function () { w.style.display = 'none'; });
|
|
864
|
+
}
|
|
865
|
+
function burst() {
|
|
866
|
+
var b = document.getElementById('burst'), flash = b.querySelector('.flash'), word = b.querySelector('.word');
|
|
867
|
+
b.style.display = 'flex';
|
|
868
|
+
tw(180, function (p) { flash.style.opacity = (0.8 * (1 - p)).toFixed(3); });
|
|
869
|
+
tw(280, function (p) {
|
|
870
|
+
var e = easeOut(p);
|
|
871
|
+
word.style.opacity = Math.min(1, p * 3).toFixed(2);
|
|
872
|
+
word.style.transform = 'skewX(-9deg) rotate(' + (-30 + e * 22).toFixed(1) + 'deg) scale(' + (0.5 + e * 0.6).toFixed(3) + ')';
|
|
873
|
+
}, function () {
|
|
874
|
+
setTimeout(function () {
|
|
875
|
+
tw(320, function (p) {
|
|
876
|
+
var e = easeOut(p);
|
|
877
|
+
word.style.opacity = (1 - p).toFixed(2);
|
|
878
|
+
word.style.transform = 'skewX(-9deg) rotate(' + (-8 + e * 9).toFixed(1) + 'deg) scale(' + (1.1 + e * 0.3).toFixed(3) + ') translateY(' + (-e * 30).toFixed(1) + 'px)';
|
|
879
|
+
}, function () {
|
|
880
|
+
b.style.display = 'none'; word.style.opacity = '0'; flash.style.opacity = '0';
|
|
881
|
+
});
|
|
882
|
+
}, 240);
|
|
883
|
+
});
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
var soundBtn = document.getElementById('soundBtn');
|
|
887
|
+
soundBtn.addEventListener('click', function () {
|
|
888
|
+
soundOn = !soundOn;
|
|
889
|
+
soundBtn.innerHTML = '<i>' + (soundOn ? '♪' : '✕') + '</i>';
|
|
890
|
+
if (soundOn) tone(880, .06, 'triangle', .05);
|
|
891
|
+
});
|
|
892
|
+
|
|
893
|
+
document.addEventListener('click', function (e) {
|
|
894
|
+
var el = e.target;
|
|
895
|
+
if (!el || !el.closest) return;
|
|
896
|
+
if (el.closest('.tabs button')) { tick(); sweep(); }
|
|
897
|
+
else if (el.closest('.seg button')) { tick(); }
|
|
898
|
+
else if (el.closest('.chips button')) { tick(); }
|
|
899
|
+
else if (el.closest('.month-bar button')) { tick(); }
|
|
900
|
+
else if (el.closest('.del')) { tick(); }
|
|
901
|
+
else if (el.closest('#saveBtn')) {
|
|
902
|
+
var v = parseFloat(document.getElementById('amt').value);
|
|
903
|
+
if (v > 0 && v <= 9999999) { sting(); setTimeout(burst, 60); }
|
|
904
|
+
}
|
|
905
|
+
}, true);
|
|
906
|
+
})();
|
|
907
|
+
</script>
|
|
908
|
+
|
|
909
|
+
<audio id="bgm" loop preload="none"></audio>
|
|
910
|
+
<div id="bgmBtn" title="BGM">♪</div>
|
|
911
|
+
<style>#bgmBtn{position:fixed;left:14px;bottom:14px;z-index:120;width:38px;height:38px;display:flex;align-items:center;justify-content:center;border:2px solid #4cc9ff;background:rgba(7,12,24,.92);color:#4cc9ff;font-size:18px;cursor:pointer;user-select:none;-webkit-user-select:none;box-shadow:3px 3px 0 rgba(0,0,0,.45);font-family:ui-monospace,Menlo,monospace}#bgmBtn.on{background:#4cc9ff;color:#071018}</style>
|
|
912
|
+
<script>
|
|
913
|
+
/* ===== BGM ENGINE v2 · Color Your Night · 跨页续播 ===== */
|
|
914
|
+
(function(){
|
|
915
|
+
var au=document.getElementById('bgm');
|
|
916
|
+
var btn=document.querySelector('#bgmBtn,[data-bgm],#ci-bgm');
|
|
917
|
+
if(!au)return;
|
|
918
|
+
var K='whale.bgm',KP='whale.bgm.pos';
|
|
919
|
+
var want=localStorage.getItem(K)!=='off';
|
|
920
|
+
var pos0=parseFloat(localStorage.getItem(KP)||'0');if(isNaN(pos0)||pos0<0)pos0=0;
|
|
921
|
+
var abuf=null,dbuf=null,bURL='',node=null,ctx=null,gn=null,fbT=0,pend=0,decoding=0,warmOnly=0;
|
|
922
|
+
var elemTried=sessionStorage.getItem('whale.bgm.ef')==='1'?1:0;
|
|
923
|
+
var waT0=0,waOff=0;
|
|
924
|
+
function lit(s){if(btn)btn.classList.toggle('on',!!s)}
|
|
925
|
+
function playing(){return !!node||(!au.paused&&au.readyState>0)}
|
|
926
|
+
function curPos(){
|
|
927
|
+
if(node&&ctx&&dbuf)return (((ctx.currentTime-waT0)+waOff)%dbuf.duration)||0;
|
|
928
|
+
return au.currentTime||0;
|
|
929
|
+
}
|
|
930
|
+
function savePos(){try{localStorage.setItem(KP,curPos().toFixed(2))}catch(e){}}
|
|
931
|
+
window.addEventListener('pagehide',savePos);
|
|
932
|
+
setInterval(function(){if(playing())savePos()},1000);
|
|
933
|
+
if(btn){btn.addEventListener('click',function(e){
|
|
934
|
+
if(e&&e.preventDefault)e.preventDefault();
|
|
935
|
+
if(playing()){want=false;localStorage.setItem(K,'off');stopAll()}
|
|
936
|
+
else{want=true;localStorage.setItem(K,'on');ensure()}
|
|
937
|
+
});}
|
|
938
|
+
function fadeElem(){try{au.volume=0}catch(e){}var v=0,iv=setInterval(function(){v=Math.min(.6,v+.05);try{au.volume=v}catch(e){};if(v>=.6)clearInterval(iv)},70)}
|
|
939
|
+
function startElem(){
|
|
940
|
+
au.src=bURL;au.load();
|
|
941
|
+
var ap=function(){try{au.currentTime=pos0}catch(e){pos0=0}};
|
|
942
|
+
if(au.readyState>=1)ap();else au.addEventListener('loadedmetadata',ap,{once:true});
|
|
943
|
+
var p=au.play();if(p&&p.catch)p.catch(function(){});
|
|
944
|
+
clearTimeout(fbT);
|
|
945
|
+
fbT=setTimeout(function(){
|
|
946
|
+
if(au.readyState<3||au.paused){try{au.pause()}catch(e){};elemTried=1;try{sessionStorage.setItem('whale.bgm.ef','1')}catch(e){};toWA()}
|
|
947
|
+
},3200);
|
|
948
|
+
}
|
|
949
|
+
function toWA(){
|
|
950
|
+
if(!dbuf){
|
|
951
|
+
if(!abuf||decoding){pend=1;return}
|
|
952
|
+
try{
|
|
953
|
+
var AC=window.AudioContext||window.webkitAudioContext;if(!AC)return;
|
|
954
|
+
ctx=ctx||new AC();if(ctx.resume)ctx.resume();
|
|
955
|
+
decoding=1;
|
|
956
|
+
ctx.decodeAudioData(abuf.slice(0),function(b){decoding=0;dbuf=b;if(warmOnly){warmOnly=0;if(pend&&want){pend=0;ensure()}}else{startNode()}},function(){decoding=0});
|
|
957
|
+
}catch(e){decoding=0}
|
|
958
|
+
return;
|
|
959
|
+
}
|
|
960
|
+
startNode();
|
|
961
|
+
}
|
|
962
|
+
function startNode(){
|
|
963
|
+
try{
|
|
964
|
+
if(!gn){gn=ctx.createGain();gn.connect(ctx.destination)}
|
|
965
|
+
if(ctx.state!=='running'&&ctx.resume)ctx.resume();
|
|
966
|
+
if(node){try{node.stop()}catch(e){};node=null}
|
|
967
|
+
if(dbuf.duration&&pos0>dbuf.duration-0.6)pos0=0;
|
|
968
|
+
node=ctx.createBufferSource();node.buffer=dbuf;node.loop=true;
|
|
969
|
+
node.connect(gn);gn.gain.value=0;
|
|
970
|
+
waT0=ctx.currentTime;waOff=pos0||0;
|
|
971
|
+
node.start(0,waOff);
|
|
972
|
+
var v=0,iv=setInterval(function(){v=Math.min(.6,v+.05);gn.gain.value=v;if(v>=.6)clearInterval(iv)},70);
|
|
973
|
+
lit(1);try{sessionStorage.setItem('whale.bgm.ef','1')}catch(e){}
|
|
974
|
+
}catch(e){}
|
|
975
|
+
}
|
|
976
|
+
function stopAll(){clearTimeout(fbT);savePos();
|
|
977
|
+
try{au.pause()}catch(e){}
|
|
978
|
+
try{if(node){node.stop();node=null}}catch(e){}
|
|
979
|
+
lit(0);
|
|
980
|
+
}
|
|
981
|
+
function ensure(){
|
|
982
|
+
if(playing())return;
|
|
983
|
+
if(abuf&&elemTried){toWA();return}
|
|
984
|
+
if(bURL){startElem();return}
|
|
985
|
+
pend=1;
|
|
986
|
+
}
|
|
987
|
+
au.addEventListener('playing',function(){lit(1);clearTimeout(fbT);fadeElem()});
|
|
988
|
+
au.addEventListener('pause',function(){if(!node)lit(0)});
|
|
989
|
+
document.addEventListener('visibilitychange',function(){if(!document.hidden){if(ctx&&ctx.state!=='running'&&ctx.resume){try{ctx.resume()}catch(e){}}if(want&&!playing())ensure()}});
|
|
990
|
+
function kick(){if(ctx&&ctx.state!=='running'&&ctx.resume){try{ctx.resume()}catch(e){}}if(!want||playing())return;if(bURL)ensure();else pend=1}
|
|
991
|
+
document.addEventListener('pointerdown',kick,true);
|
|
992
|
+
au.addEventListener('playing',function(){document.removeEventListener('pointerdown',kick,true)});
|
|
993
|
+
fetch('bgm.mp3').then(function(r){return r.arrayBuffer()}).then(function(a){
|
|
994
|
+
abuf=a;bURL=URL.createObjectURL(new Blob([a],{type:'audio/mpeg'}));
|
|
995
|
+
if(elemTried&&want){warmOnly=1;toWA()}
|
|
996
|
+
if(want&&!playing()){pend=0;ensure()}
|
|
997
|
+
}).catch(function(){});
|
|
998
|
+
if(want){setTimeout(function(){if(!playing()&&bURL){ensure()}},400)}
|
|
999
|
+
window.__bgmFade=function(){savePos();try{if(node&&gn){var g0=gn.gain.value,t0=Date.now();var iv=setInterval(function(){var k=Math.max(0,1-(Date.now()-t0)/500);gn.gain.value=g0*k;if(k<=0)clearInterval(iv)},60)}}catch(e){}};
|
|
1000
|
+
})();
|
|
1001
|
+
</script>
|
|
1002
|
+
</body>
|
|
1003
|
+
</html>
|