block-proxy 0.1.12 → 0.1.13

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 (58) hide show
  1. package/.claude/settings.local.json +26 -1
  2. package/.claude/skills/build-client/skill.md +24 -0
  3. package/.claude/skills/release-client/skill.md +68 -0
  4. package/CLAUDE.md +69 -67
  5. package/Dockerfile +1 -1
  6. package/README.md +38 -24
  7. package/build/asset-manifest.json +6 -6
  8. package/build/index.html +1 -1
  9. package/build/static/css/main.3f317ce6.css +2 -0
  10. package/build/static/css/main.3f317ce6.css.map +1 -0
  11. package/build/static/js/{main.2247fb80.js → main.68f66be0.js} +3 -3
  12. package/build/static/js/main.68f66be0.js.map +1 -0
  13. package/client/app.py +312 -0
  14. package/client/build.sh +84 -0
  15. package/client/config.py +49 -0
  16. package/client/config_window.py +155 -0
  17. package/client/icons/app.icns +0 -0
  18. package/client/icons/app_example.png +0 -0
  19. package/client/icons/app_icon.png +0 -0
  20. package/client/icons/backup/app_example.png +0 -0
  21. package/client/icons/backup/christmas-sock_dark.png +0 -0
  22. package/client/icons/backup/christmas-sock_light.png +0 -0
  23. package/client/icons/backup/socks_on_G.png +0 -0
  24. package/client/icons/backup/socks_on_M.png +0 -0
  25. package/client/icons/christmas-sock_dark.png +0 -0
  26. package/client/icons/christmas-sock_light.png +0 -0
  27. package/client/icons/christmas-sock_light_bar.png +0 -0
  28. package/client/icons/socks_on_G.png +0 -0
  29. package/client/icons/socks_on_G_bar.png +0 -0
  30. package/client/icons/socks_on_M.png +0 -0
  31. package/client/icons/socks_on_M_bar.png +0 -0
  32. package/client/main.py +28 -0
  33. package/client/proxy_core.py +475 -0
  34. package/client/requirements.txt +3 -0
  35. package/client/scripts/download_xray.sh +30 -0
  36. package/client/setup.py +30 -0
  37. package/client/system_proxy.py +94 -0
  38. package/client/tests/__init__.py +0 -0
  39. package/client/tests/test_config.py +72 -0
  40. package/client/tests/test_system_proxy.py +69 -0
  41. package/client/watch-icons.js +31 -0
  42. package/config.json +28 -3
  43. package/docs/superpowers/plans/2026-05-27-blockproxyclient.md +1274 -0
  44. package/docs/superpowers/specs/2026-05-27-blockproxyclient-design.md +264 -0
  45. package/package.json +10 -4
  46. package/proxy/proxy.js +19 -15
  47. package/server/express.js +17 -1
  48. package/src/App.css +596 -276
  49. package/src/App.js +25 -22
  50. package/src/index.css +3 -4
  51. package/test/lib/mock-server.js +133 -0
  52. package/test/proxy-tests.js +708 -0
  53. package/test/run.js +330 -0
  54. package/build/static/css/main.8bfa3d5f.css +0 -2
  55. package/build/static/css/main.8bfa3d5f.css.map +0 -1
  56. package/build/static/js/main.2247fb80.js.map +0 -1
  57. package/hack-of-anyproxy/lib/requestHandler.js +0 -1060
  58. /package/build/static/js/{main.2247fb80.js.LICENSE.txt → main.68f66be0.js.LICENSE.txt} +0 -0
package/src/App.css CHANGED
@@ -1,207 +1,261 @@
1
- /* 服务器信息样式 */
2
- .server-info {
3
- /*padding: 10px 0;*/
4
- }
5
-
6
- .server-info p {
7
- margin: 5px 0;
1
+ /* ===== 基础变量与全局 ===== */
2
+ :root {
3
+ --primary: #4f46e5;
4
+ --primary-hover: #4338ca;
5
+ --primary-light: #eef2ff;
6
+ --success: #059669;
7
+ --success-hover: #047857;
8
+ --danger: #dc2626;
9
+ --danger-hover: #b91c1c;
10
+ --warning: #d97706;
11
+ --warning-hover: #b45309;
12
+ --info: #0284c7;
13
+ --info-hover: #0369a1;
14
+ --gray-50: #f9fafb;
15
+ --gray-100: #f3f4f6;
16
+ --gray-200: #e5e7eb;
17
+ --gray-300: #d1d5db;
18
+ --gray-400: #9ca3af;
19
+ --gray-500: #6b7280;
20
+ --gray-600: #4b5563;
21
+ --gray-700: #374151;
22
+ --gray-800: #1f2937;
23
+ --gray-900: #111827;
24
+ --radius: 8px;
25
+ --radius-sm: 6px;
26
+ --radius-xs: 4px;
27
+ --shadow-sm: 0 1px 2px rgba(0,0,0,0.05);
28
+ --shadow: 0 1px 3px rgba(0,0,0,0.1), 0 1px 2px rgba(0,0,0,0.06);
29
+ --shadow-md: 0 4px 6px -1px rgba(0,0,0,0.1), 0 2px 4px -2px rgba(0,0,0,0.1);
30
+ --shadow-lg: 0 10px 15px -3px rgba(0,0,0,0.1), 0 4px 6px -4px rgba(0,0,0,0.1);
31
+ --transition: 150ms cubic-bezier(0.4, 0, 0.2, 1);
32
+ }
33
+
34
+ * {
35
+ box-sizing: border-box;
36
+ }
37
+
38
+ body {
39
+ margin: 0;
40
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
41
+ 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;
42
+ -webkit-font-smoothing: antialiased;
43
+ -moz-osx-font-smoothing: grayscale;
8
44
  }
9
45
 
10
- .ip-list {
11
- list-style: none;
12
- padding: 0;
13
- margin: 10px 0;
46
+ /* ===== 布局 ===== */
47
+ .App {
48
+ min-height: 100vh;
49
+ background: linear-gradient(135deg, #f0f4ff 0%, #f8fafc 30%, #f0fdf4 70%, #fefce8 100%);
50
+ padding: 24px 16px 48px;
14
51
  }
15
52
 
16
- .ip-item {
17
- display: flex;
18
- padding: 8px 0;
19
- border-bottom: 1px solid #eee;
53
+ .config-container {
54
+ max-width: 960px;
55
+ margin: 0 auto;
56
+ position: relative;
20
57
  }
21
58
 
22
- .ip-item:last-child {
23
- border-bottom: none;
59
+ .config-container h1 {
60
+ text-align: center;
61
+ font-size: 26px;
62
+ font-weight: 700;
63
+ color: var(--gray-800);
64
+ margin: 0 0 28px;
65
+ letter-spacing: -0.02em;
24
66
  }
25
67
 
26
- .interface-name {
27
- font-weight: bold;
28
- color: #555;
68
+ /* ===== Section 卡片 ===== */
69
+ .config-section {
70
+ background: white;
71
+ border-radius: 12px;
72
+ padding: 24px 28px;
73
+ margin-bottom: 20px;
74
+ box-shadow: var(--shadow);
75
+ border: 1px solid var(--gray-100);
76
+ transition: box-shadow var(--transition);
29
77
  }
30
78
 
31
- .ip-address {
32
- flex: 1;
33
- font-family: 'Courier New', monospace;
34
- background-color: #f8f9fa;
35
- padding: 2px 6px;
36
- border-radius: 3px;
79
+ .config-section:hover {
80
+ box-shadow: var(--shadow-md);
37
81
  }
38
82
 
39
- /* Toast 样式 */
40
- .toast {
41
- position: fixed;
42
- top: 20px;
43
- right: 20px;
44
- padding: 16px 20px;
45
- border-radius: 4px;
46
- color: white;
47
- font-weight: 500;
48
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
49
- z-index: 1000;
83
+ .config-section h2 {
84
+ margin: 0 0 18px;
85
+ font-size: 16px;
86
+ font-weight: 600;
87
+ color: var(--gray-800);
88
+ padding-bottom: 12px;
89
+ border-bottom: 2px solid var(--gray-100);
50
90
  display: flex;
51
91
  align-items: center;
52
- animation: toastSlideIn 0.3s ease-out;
53
- min-width: 250px;
92
+ gap: 8px;
54
93
  }
55
94
 
56
- @keyframes toastSlideIn {
57
- from {
58
- transform: translateX(100%);
59
- opacity: 0;
60
- }
61
- to {
62
- transform: translateX(0);
63
- opacity: 1;
64
- }
95
+ .config-section h2::before {
96
+ content: '';
97
+ display: inline-block;
98
+ width: 4px;
99
+ height: 18px;
100
+ background: var(--primary);
101
+ border-radius: 2px;
65
102
  }
66
103
 
67
- .toast.success {
68
- background-color: #28a745;
69
- border-left: 4px solid #1e7e34;
104
+ .config-section h3 {
105
+ margin: 0 0 12px;
106
+ font-size: 15px;
107
+ font-weight: 600;
108
+ color: var(--gray-700);
70
109
  }
71
110
 
72
- .toast.error {
73
- background-color: #dc3545;
74
- border-left: 4px solid #bd2130;
111
+ /* ===== 服务器信息 ===== */
112
+ .server-info p {
113
+ margin: 4px 0;
114
+ color: var(--gray-600);
115
+ font-size: 14px;
75
116
  }
76
117
 
77
- .toast.info {
78
- background-color: #17a2b8;
79
- border-left: 4px solid #117a8b;
118
+ .server-info strong {
119
+ color: var(--gray-800);
80
120
  }
81
121
 
82
- .toast-close {
83
- background: none;
84
- border: none;
85
- color: white;
86
- font-size: 20px;
87
- font-weight: bold;
88
- margin-left: 15px;
89
- cursor: pointer;
122
+ .ip-list {
123
+ list-style: none;
90
124
  padding: 0;
91
- width: 20px;
92
- height: 20px;
93
- display: flex;
94
- align-items: center;
95
- justify-content: center;
125
+ margin: 12px 0 0;
126
+ display: grid;
127
+ grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
128
+ gap: 8px;
96
129
  }
97
130
 
98
- .toast-close:hover {
99
- opacity: 0.7;
131
+ .ip-item {
132
+ display: flex;
133
+ align-items: center;
134
+ gap: 8px;
135
+ padding: 8px 12px;
136
+ background: var(--gray-50);
137
+ border-radius: var(--radius-sm);
138
+ font-size: 13px;
139
+ border: 1px solid var(--gray-100);
100
140
  }
101
141
 
102
- /* 其他原有样式保持不变 */
103
- .App {
104
- text-align: center;
105
- padding: 20px;
106
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
107
- 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
108
- sans-serif;
109
- background-color: #f5f5f5;
110
- min-height: 100vh;
142
+ .interface-name {
143
+ font-weight: 600;
144
+ color: var(--gray-700);
145
+ white-space: nowrap;
111
146
  }
112
147
 
113
- .config-container {
114
- max-width: 1220px;
115
- margin: 0 auto;
116
- background: white;
117
- border-radius: 8px;
118
- box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
119
- padding: 30px;
120
- text-align: left;
121
- position: relative;
148
+ .ip-address {
149
+ font-family: 'SF Mono', 'Fira Code', 'JetBrains Mono', 'Courier New', monospace;
150
+ font-size: 12px;
151
+ background: var(--primary-light);
152
+ color: var(--primary);
153
+ padding: 2px 8px;
154
+ border-radius: var(--radius-xs);
155
+ font-weight: 500;
122
156
  }
123
157
 
124
- .config-container h1 {
125
- text-align: center;
126
- color: #333;
127
- margin-top: 0;
158
+ .docker-info {
159
+ display: inline-block;
160
+ font-size: 12px;
161
+ font-weight: 500;
162
+ color: var(--info);
163
+ background: #e0f2fe;
164
+ padding: 2px 8px;
165
+ border-radius: 10px;
166
+ margin-left: 6px;
128
167
  }
129
168
 
130
- .config-section {
131
- margin-bottom: 30px;
132
- padding: 20px;
133
- border: 1px solid #eee;
134
- border-radius: 5px;
135
- background-color: #fafafa;
169
+ .host-ip-info {
170
+ margin-top: 12px;
171
+ padding-top: 12px;
172
+ border-top: 1px solid var(--gray-200);
136
173
  }
137
174
 
138
- .config-section h2 {
139
- margin-top: 0;
140
- color: #555;
141
- border-bottom: 1px solid #eee;
142
- padding-bottom: 10px;
175
+ .host-ip-info p {
176
+ font-size: 13px;
177
+ color: var(--gray-600);
143
178
  }
144
179
 
180
+ /* ===== 拦截主机列表 ===== */
145
181
  .host-input {
146
182
  display: flex;
147
- margin-bottom: 15px;
183
+ flex-wrap: wrap;
148
184
  gap: 10px;
149
- flex-direction: row;
185
+ margin-bottom: 16px;
186
+ align-items: flex-end;
150
187
  }
151
188
 
152
189
  .host-input input[type="text"] {
153
190
  flex: 1;
154
- padding: 10px;
155
- border: 1px solid #ddd;
156
- border-radius: 4px;
191
+ min-width: 180px;
192
+ padding: 10px 14px;
193
+ border: 1.5px solid var(--gray-300);
194
+ border-radius: var(--radius-sm);
157
195
  font-size: 14px;
196
+ transition: border-color var(--transition), box-shadow var(--transition);
197
+ background: white;
158
198
  }
159
199
 
160
- .host-input button {
161
- background-color: #007bff;
162
- color: white;
163
- border: none;
164
- border-radius: 4px;
165
- cursor: pointer;
166
- font-size: 14px;
167
- align-self: flex-start;
200
+ .host-input input[type="text"]:focus {
201
+ outline: none;
202
+ border-color: var(--primary);
203
+ box-shadow: 0 0 0 3px rgba(79, 70, 229, 0.1);
168
204
  }
169
205
 
170
- .host-input button:hover {
171
- background-color: #0069d9;
206
+ .host-input input[type="text"]::placeholder {
207
+ color: var(--gray-400);
172
208
  }
173
209
 
174
210
  .time-inputs {
175
211
  display: flex;
176
212
  gap: 10px;
177
213
  align-items: center;
214
+ flex-wrap: wrap;
178
215
  }
179
216
 
180
217
  .time-inputs label {
181
- flex-direction: column;
218
+ display: flex;
219
+ align-items: center;
220
+ gap: 4px;
221
+ font-size: 13px;
222
+ color: var(--gray-600);
223
+ }
224
+
225
+ .time-inputs label span {
226
+ white-space: nowrap;
227
+ }
228
+
229
+ .host-input button,
230
+ .time-inputs button {
231
+ padding: 10px 20px;
232
+ background: var(--primary);
233
+ color: white;
234
+ border: none;
235
+ border-radius: var(--radius-sm);
236
+ cursor: pointer;
182
237
  font-size: 14px;
238
+ font-weight: 500;
239
+ transition: background var(--transition), transform 50ms;
240
+ white-space: nowrap;
183
241
  }
184
242
 
185
- hr.simple-line {
186
- height:1px;
187
- border-width:0px;
188
- background-color:#e3e3e3
243
+ .host-input button:hover {
244
+ background: var(--primary-hover);
189
245
  }
190
246
 
191
- /* 时间控件样式 */
192
- input[type="time"] {
193
- padding: 4px;
194
- border: 1px solid #ddd;
195
- border-radius: 3px;
196
- background-color: white;
247
+ .host-input button:active {
248
+ transform: scale(0.97);
197
249
  }
198
250
 
199
- input[type="time"]:focus {
200
- outline: none;
201
- border-color: #007bff;
202
- box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.25);
251
+ hr.simple-line {
252
+ height: 1px;
253
+ border: none;
254
+ background: var(--gray-200);
255
+ margin: 0 0 12px;
203
256
  }
204
257
 
258
+ /* ===== 域名列表表头 ===== */
205
259
  .host-list {
206
260
  list-style: none;
207
261
  padding: 0;
@@ -211,9 +265,31 @@ input[type="time"]:focus {
211
265
  .host-item {
212
266
  display: flex;
213
267
  justify-content: space-between;
214
- align-items: flex-start;
215
- padding: 12px 0px;
216
- border-bottom: 1px solid #eee;
268
+ align-items: center;
269
+ gap: 12px;
270
+ padding: 14px 16px;
271
+ border-radius: var(--radius-sm);
272
+ transition: background var(--transition);
273
+ }
274
+
275
+ .host-item:first-child {
276
+ background: var(--gray-50);
277
+ border-radius: var(--radius-sm);
278
+ margin-bottom: 4px;
279
+ padding: 10px 16px;
280
+ font-weight: 600;
281
+ font-size: 13px;
282
+ color: var(--gray-500);
283
+ text-transform: uppercase;
284
+ letter-spacing: 0.05em;
285
+ }
286
+
287
+ .host-item:not(:first-child) {
288
+ border-bottom: 1px solid var(--gray-100);
289
+ }
290
+
291
+ .host-item:not(:first-child):hover {
292
+ background: var(--gray-50);
217
293
  }
218
294
 
219
295
  .host-item:last-child {
@@ -223,285 +299,529 @@ input[type="time"]:focus {
223
299
  .host-info {
224
300
  display: flex;
225
301
  flex-grow: 1;
226
- align-items:center;
227
- font-size:15px;
302
+ align-items: center;
303
+ gap: 12px;
304
+ flex-wrap: wrap;
228
305
  }
229
306
 
230
307
  span.host-text {
231
308
  font-size: 14px;
232
- flex-grow: 1;
233
- align-self: center; /* 垂直居中 */
309
+ flex: 1;
310
+ min-width: 140px;
311
+ line-height: 1.5;
312
+ }
313
+
314
+ span.host-text strong {
315
+ color: var(--gray-800);
316
+ font-weight: 600;
317
+ }
318
+
319
+ /* ===== 时间控件 ===== */
320
+ input[type="time"] {
321
+ padding: 6px 8px;
322
+ border: 1.5px solid var(--gray-300);
323
+ border-radius: var(--radius-xs);
324
+ font-size: 13px;
325
+ background: white;
326
+ transition: border-color var(--transition), box-shadow var(--transition);
327
+ font-family: inherit;
328
+ }
329
+
330
+ input[type="time"]:focus {
331
+ outline: none;
332
+ border-color: var(--primary);
333
+ box-shadow: 0 0 0 3px rgba(79, 70, 229, 0.1);
234
334
  }
235
335
 
236
336
  .time-controls {
237
337
  display: flex;
238
- gap: 8px;
338
+ gap: 6px;
239
339
  align-items: center;
240
340
  }
241
341
 
242
342
  .time-controls label {
243
343
  display: flex;
244
- flex-direction: column;
245
- font-size: 12px;
344
+ align-items: center;
345
+ font-size: 13px;
346
+ color: var(--gray-500);
246
347
  }
247
348
 
349
+ /* ===== 删除按钮 ===== */
248
350
  .remove-btn {
249
- background-color: #dc3545;
250
- color: white;
251
- border: none;
252
- padding: 5px 7px;
253
- min-width: 23px;
351
+ background: white;
352
+ color: var(--gray-400);
353
+ border: 1.5px solid var(--gray-200);
354
+ width: 30px;
355
+ height: 30px;
356
+ min-width: 30px;
254
357
  cursor: pointer;
255
- border-radius: 3px;
256
- font-size: 12px;
257
- height: fit-content;
258
- align-self: center;
259
- margin-left: 10px;
358
+ border-radius: var(--radius-xs);
359
+ font-size: 14px;
360
+ font-weight: 600;
361
+ display: flex;
362
+ align-items: center;
363
+ justify-content: center;
364
+ transition: all var(--transition);
365
+ padding: 0;
260
366
  }
261
367
 
262
368
  .remove-btn:hover {
263
- background-color: #c82333;
369
+ background: var(--danger);
370
+ color: white;
371
+ border-color: var(--danger);
264
372
  }
265
373
 
374
+ /* ===== 设置行 ===== */
266
375
  .setting-row {
267
376
  display: flex;
268
377
  align-items: center;
269
- margin-bottom: 15px;
270
- gap: 15px;
378
+ margin-bottom: 14px;
379
+ gap: 16px;
271
380
  }
272
381
 
273
382
  .setting-row label {
274
- width: 220px; /* 固定宽度,足够容纳最长的标签 */
383
+ width: 200px;
275
384
  text-align: right;
276
- font-weight: bold;
277
- color: #555;
278
- flex-shrink: 0; /* 防止被压缩 */
385
+ font-weight: 500;
386
+ color: var(--gray-700);
387
+ flex-shrink: 0;
388
+ font-size: 14px;
279
389
  }
280
390
 
281
- .setting-row input {
391
+ .setting-row input[type="text"],
392
+ .setting-row input[type="number"],
393
+ .setting-row select {
282
394
  flex: 1;
283
- padding: 8px;
284
- border: 1px solid #ddd;
285
- border-radius: 4px;
286
- }
287
-
288
- .setting-row input[type="number"] {
289
- padding: 8px;
290
- border: 1px solid #ddd;
291
- border-radius: 4px;
395
+ padding: 10px 14px;
396
+ border: 1.5px solid var(--gray-300);
397
+ border-radius: var(--radius-sm);
292
398
  font-size: 14px;
399
+ font-family: inherit;
400
+ transition: border-color var(--transition), box-shadow var(--transition);
401
+ background: white;
293
402
  }
294
403
 
295
- .setting-row input[type="number"]:focus {
404
+ .setting-row input[type="text"]:focus,
405
+ .setting-row input[type="number"]:focus,
406
+ .setting-row select:focus {
296
407
  outline: none;
297
- border-color: #007bff;
298
- box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.25);
408
+ border-color: var(--primary);
409
+ box-shadow: 0 0 0 3px rgba(79, 70, 229, 0.1);
299
410
  }
300
411
 
412
+ .setting-row select {
413
+ cursor: pointer;
414
+ appearance: none;
415
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%236b7280' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E");
416
+ background-repeat: no-repeat;
417
+ background-position: right 12px center;
418
+ padding-right: 36px;
419
+ }
420
+
421
+ .setting-row.full-width {
422
+ flex-wrap: wrap;
423
+ }
424
+
425
+ .setting-row.full-width label {
426
+ width: 200px;
427
+ }
428
+
429
+ .setting-row.full-width input {
430
+ flex: 1;
431
+ min-width: 240px;
432
+ }
433
+
434
+ .help-text {
435
+ width: 100%;
436
+ margin-left: 216px;
437
+ font-size: 12px;
438
+ color: var(--gray-400);
439
+ line-height: 1.5;
440
+ margin-top: -4px;
441
+ }
442
+
443
+ /* ===== 操作按钮行 ===== */
301
444
  .actions {
302
445
  display: flex;
303
- justify-content: space-between;
446
+ gap: 12px;
447
+ margin-top: 20px;
448
+ padding-top: 20px;
449
+ border-top: 1px solid var(--gray-100);
304
450
  }
305
451
 
306
- .save-btn, .restart-btn {
452
+ .save-btn,
453
+ .restart-btn,
454
+ .refresh-btn {
307
455
  flex: 1;
308
456
  padding: 12px 20px;
309
457
  border: none;
310
- border-radius: 4px;
458
+ border-radius: var(--radius-sm);
311
459
  cursor: pointer;
312
- font-size: 16px;
313
- font-weight: bold;
460
+ font-size: 14px;
461
+ font-weight: 600;
462
+ transition: all var(--transition);
463
+ letter-spacing: 0.01em;
314
464
  }
315
465
 
316
466
  .save-btn {
317
- background-color: #28a745;
467
+ background: var(--success);
318
468
  color: white;
319
469
  }
320
470
 
321
471
  .save-btn:hover:not(:disabled) {
322
- background-color: #218838;
472
+ background: var(--success-hover);
473
+ box-shadow: 0 2px 8px rgba(5, 150, 105, 0.3);
323
474
  }
324
475
 
325
476
  .restart-btn {
326
- background-color: #ffc107;
327
- color: #212529;
477
+ background: var(--warning);
478
+ color: white;
328
479
  }
329
480
 
330
481
  .restart-btn:hover:not(:disabled) {
331
- background-color: #e0a800;
482
+ background: var(--warning-hover);
483
+ box-shadow: 0 2px 8px rgba(217, 119, 6, 0.3);
332
484
  }
333
485
 
334
- .save-btn:disabled, .restart-btn:disabled {
335
- background-color: #6c757d;
486
+ .save-btn:disabled,
487
+ .restart-btn:disabled,
488
+ .refresh-btn:disabled {
489
+ background: var(--gray-300);
490
+ color: var(--gray-500);
336
491
  cursor: not-allowed;
492
+ box-shadow: none;
337
493
  }
338
494
 
339
- button {
340
- padding: 8px 12px;
341
- border: none;
342
- border-radius: 3px;
343
- cursor: pointer;
344
- transition: background-color 0.2s;
495
+ .save-btn:active:not(:disabled),
496
+ .restart-btn:active:not(:disabled),
497
+ .refresh-btn:active:not(:disabled),
498
+ .refresh-table-btn:active:not(:disabled) {
499
+ transform: scale(0.98);
345
500
  }
346
501
 
347
- button:hover {
348
- opacity: 0.9;
502
+ .refresh-btn {
503
+ background: var(--info);
504
+ color: white;
349
505
  }
350
506
 
351
- button:disabled {
352
- opacity: 0.6;
353
- cursor: not-allowed;
507
+ .refresh-btn:hover:not(:disabled) {
508
+ background: var(--info-hover);
509
+ box-shadow: 0 2px 8px rgba(2, 132, 199, 0.3);
354
510
  }
355
511
 
356
- /* Docker信息样式 */
357
- .docker-info {
358
- color: #007bff;
359
- font-size: 0.9em;
512
+ .refresh-table-btn {
513
+ margin-top: 12px;
360
514
  }
361
515
 
362
- .host-ip-info {
363
- margin-top: 15px;
364
- padding-top: 10px;
365
- border-top: 1px solid #eee;
516
+ /* ===== 代理设置信息区 ===== */
517
+ .config-section p {
518
+ margin: 6px 0;
519
+ font-size: 14px;
520
+ color: var(--gray-600);
521
+ line-height: 1.7;
366
522
  }
367
523
 
368
- .host-ip-list {
369
- list-style: none;
370
- padding: 0;
371
- margin: 10px 0;
524
+ .config-section p b {
525
+ color: var(--gray-800);
526
+ font-weight: 600;
372
527
  }
373
528
 
374
- .host-ip-item {
375
- display: flex;
376
- padding: 5px 0;
529
+ .config-section a {
530
+ color: var(--primary);
531
+ text-decoration: none;
532
+ font-weight: 500;
533
+ transition: color var(--transition);
377
534
  }
378
535
 
379
- .method-name {
380
- font-weight: bold;
381
- width: 150px;
382
- color: #555;
536
+ .config-section a:hover {
537
+ color: var(--primary-hover);
538
+ text-decoration: underline;
383
539
  }
384
540
 
385
- .host-ip-address {
386
- flex: 1;
387
- font-family: 'Courier New', monospace;
388
- background-color: #e9ecef;
389
- padding: 2px 6px;
390
- border-radius: 3px;
541
+ #qrcode {
542
+ margin-top: 8px;
543
+ border-radius: var(--radius-sm);
544
+ border: 1px solid var(--gray-200);
545
+ padding: 8px;
546
+ background: white;
391
547
  }
392
548
 
393
- /* 星期几控件样式 */
549
+ /* ===== 通用按钮 ===== */
550
+ button {
551
+ font-family: inherit;
552
+ }
553
+
554
+ .config-section > button {
555
+ padding: 10px 20px;
556
+ background: white;
557
+ color: var(--gray-700);
558
+ border: 1.5px solid var(--gray-300);
559
+ border-radius: var(--radius-sm);
560
+ cursor: pointer;
561
+ font-size: 14px;
562
+ font-weight: 500;
563
+ transition: all var(--transition);
564
+ }
565
+
566
+ .config-section > button:hover:not(:disabled) {
567
+ background: var(--gray-50);
568
+ border-color: var(--gray-400);
569
+ }
570
+
571
+ .config-section > button:disabled {
572
+ opacity: 0.5;
573
+ cursor: not-allowed;
574
+ }
575
+
576
+ /* ===== 星期几按钮 ===== */
394
577
  .weekday-controls {
395
578
  display: flex;
396
- /*flex-wrap: wrap;*/
579
+ gap: 2px;
397
580
  align-items: center;
398
581
  }
399
582
 
400
583
  .weekday-btn {
401
- width: 24px;
402
- height: 24px;
584
+ width: 30px;
585
+ height: 30px;
403
586
  padding: 0;
404
- /*border: 1px solid #ddd;*/
405
- background-color: #e5e5e5;
406
- color: #515b63;
587
+ background: var(--gray-100);
588
+ color: var(--gray-500);
407
589
  font-size: 12px;
408
- border-radius: 0px;
590
+ font-weight: 500;
591
+ border-radius: var(--radius-xs);
409
592
  cursor: pointer;
410
593
  display: flex;
411
594
  align-items: center;
412
595
  justify-content: center;
596
+ border: 1.5px solid transparent;
597
+ transition: all var(--transition);
413
598
  }
414
599
 
415
600
  .weekday-btn:hover {
416
- background-color: #e9ecef;
417
- border-color: #adb5bd;
601
+ background: var(--gray-200);
602
+ color: var(--gray-700);
418
603
  }
419
604
 
420
605
  .weekday-btn.active {
421
- background-color: #007bff;
606
+ background: var(--primary);
422
607
  color: white;
608
+ border-color: var(--primary);
609
+ box-shadow: 0 1px 3px rgba(79, 70, 229, 0.3);
423
610
  }
424
611
 
425
612
  .weekday-btn:focus {
426
613
  outline: none;
427
- box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.25);
614
+ box-shadow: 0 0 0 2px rgba(79, 70, 229, 0.3);
428
615
  }
429
616
 
430
- /* MAC地址输入框样式 */
617
+ /* ===== MAC 地址输入 ===== */
431
618
  .mac-input {
432
619
  display: flex;
433
620
  align-items: center;
434
- margin: 0 10px;
435
- gap: 5px;
436
- }
437
-
438
- .mac-input label {
439
- font-size: 12px;
440
- color: #555;
621
+ gap: 8px;
441
622
  }
442
623
 
443
624
  .mac-input input[type="text"] {
444
- width: 106px;
445
- padding: 4px;
446
- border: 1px solid #ddd;
447
- border-radius: 3px;
625
+ width: 130px;
626
+ padding: 6px 10px;
627
+ border: 1.5px solid var(--gray-300);
628
+ border-radius: var(--radius-xs);
448
629
  font-size: 12px;
630
+ font-family: 'SF Mono', 'Fira Code', 'JetBrains Mono', 'Courier New', monospace;
631
+ transition: border-color var(--transition), box-shadow var(--transition);
449
632
  }
450
633
 
451
634
  .mac-input input[type="text"]:focus {
452
635
  outline: none;
453
- border-color: #007bff;
454
- box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.25);
636
+ border-color: var(--primary);
637
+ box-shadow: 0 0 0 3px rgba(79, 70, 229, 0.1);
638
+ }
639
+
640
+ .mac-input input[type="text"]::placeholder {
641
+ color: var(--gray-400);
642
+ font-family: inherit;
455
643
  }
456
644
 
645
+ /* ===== 列表表头 ===== */
457
646
  .table-right-blank {
458
- min-width:30px;
647
+ min-width: 30px;
459
648
  }
460
649
 
461
650
  .title-mac-input {
462
- font-size:15px;
463
- text-align:center;
464
- min-width:131px;
651
+ font-size: 13px;
652
+ text-align: center;
653
+ min-width: 138px;
465
654
  }
655
+
466
656
  .title-time-controls {
467
- font-size:15px;
468
- text-align:center;
469
- min-width:183px;
657
+ font-size: 13px;
658
+ text-align: center;
659
+ min-width: 190px;
470
660
  }
661
+
471
662
  .title-weedkey-controls {
472
- font-size:15px;
473
- min-width:106px;
663
+ font-size: 13px;
664
+ min-width: 120px;
665
+ }
666
+
667
+ /* ===== Toast 通知 ===== */
668
+ .toast {
669
+ position: fixed;
670
+ top: 24px;
671
+ right: 24px;
672
+ padding: 14px 20px;
673
+ border-radius: 10px;
674
+ color: white;
675
+ font-weight: 500;
676
+ font-size: 14px;
677
+ box-shadow: 0 10px 25px rgba(0, 0, 0, 0.15);
678
+ z-index: 1000;
679
+ display: flex;
680
+ align-items: center;
681
+ gap: 12px;
682
+ animation: toastSlideIn 0.35s cubic-bezier(0.16, 1, 0.3, 1);
683
+ min-width: 260px;
684
+ backdrop-filter: blur(8px);
474
685
  }
475
686
 
476
- /* 响应式设计 */
477
- @media (max-width: 500px) {
478
- .config-container {
479
- margin: 10px;
480
- padding: 20px;
687
+ @keyframes toastSlideIn {
688
+ from {
689
+ transform: translateX(120%);
690
+ opacity: 0;
481
691
  }
482
-
483
- .time-controls {
692
+ to {
693
+ transform: translateX(0);
694
+ opacity: 1;
695
+ }
696
+ }
697
+
698
+ .toast.success {
699
+ background: var(--success);
700
+ box-shadow: 0 8px 20px rgba(5, 150, 105, 0.3);
701
+ }
702
+
703
+ .toast.error {
704
+ background: var(--danger);
705
+ box-shadow: 0 8px 20px rgba(220, 38, 38, 0.3);
706
+ }
707
+
708
+ .toast.info {
709
+ background: var(--info);
710
+ box-shadow: 0 8px 20px rgba(2, 132, 199, 0.3);
711
+ }
712
+
713
+ .toast-close {
714
+ background: rgba(255,255,255,0.2);
715
+ border: none;
716
+ color: white;
717
+ font-size: 18px;
718
+ font-weight: 400;
719
+ cursor: pointer;
720
+ padding: 0;
721
+ width: 24px;
722
+ height: 24px;
723
+ display: flex;
724
+ align-items: center;
725
+ justify-content: center;
726
+ border-radius: 50%;
727
+ transition: background var(--transition);
728
+ margin-left: auto;
729
+ flex-shrink: 0;
730
+ }
731
+
732
+ .toast-close:hover {
733
+ background: rgba(255,255,255,0.35);
734
+ }
735
+
736
+ /* ===== 路由表 ===== */
737
+ .config-section .ip-list {
738
+ display: flex;
739
+ flex-direction: column;
740
+ gap: 4px;
741
+ }
742
+
743
+ .config-section .ip-item {
744
+ background: var(--gray-50);
745
+ }
746
+
747
+ /* ===== 响应式 ===== */
748
+ @media (max-width: 768px) {
749
+ .App {
750
+ padding: 12px 8px 32px;
751
+ }
752
+
753
+ .config-section {
754
+ padding: 18px 16px;
755
+ }
756
+
757
+ .host-info {
758
+ flex-direction: column;
759
+ align-items: flex-start;
760
+ gap: 8px;
761
+ }
762
+
763
+ .setting-row {
484
764
  flex-direction: column;
485
765
  align-items: flex-start;
486
- gap: 5px;
766
+ gap: 6px;
767
+ }
768
+
769
+ .setting-row label {
770
+ width: 100%;
771
+ text-align: left;
772
+ }
773
+
774
+ .setting-row.full-width label {
775
+ width: 100%;
776
+ }
777
+
778
+ .help-text {
779
+ margin-left: 0;
780
+ }
781
+
782
+ .actions {
783
+ flex-direction: column;
487
784
  }
488
785
 
489
786
  .host-item {
490
- gap: 10px;
787
+ flex-direction: column;
788
+ align-items: flex-start;
789
+ gap: 8px;
491
790
  }
492
-
791
+
493
792
  .remove-btn {
494
- align-self: flex-start;
495
- margin-left: 0;
496
- margin-top: 10px;
793
+ align-self: flex-end;
794
+ }
795
+
796
+ .time-controls {
797
+ flex-wrap: wrap;
798
+ }
799
+
800
+ .host-input {
801
+ flex-direction: column;
497
802
  }
498
803
 
499
- .mac-input {
500
- margin: 5px 0;
804
+ .host-input input[type="text"] {
805
+ min-width: 100%;
501
806
  }
502
-
503
- .mac-input input[type="text"] {
504
- width: 100px;
807
+
808
+ .ip-list {
809
+ grid-template-columns: 1fr;
505
810
  }
506
811
  }
507
812
 
813
+ @media (max-width: 480px) {
814
+ .config-container h1 {
815
+ font-size: 20px;
816
+ }
817
+
818
+ .config-section h2 {
819
+ font-size: 14px;
820
+ }
821
+
822
+ .weekday-btn {
823
+ width: 26px;
824
+ height: 26px;
825
+ font-size: 11px;
826
+ }
827
+ }