ghost-bridge 0.3.0 → 0.5.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.
@@ -3,158 +3,489 @@
3
3
  <head>
4
4
  <meta charset="UTF-8">
5
5
  <style>
6
+ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap');
7
+
6
8
  * {
7
9
  margin: 0;
8
10
  padding: 0;
9
11
  box-sizing: border-box;
10
12
  }
13
+ html {
14
+ height: fit-content;
15
+ width: fit-content;
16
+ }
17
+
11
18
  body {
12
- width: 280px;
13
- padding: 16px;
14
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
15
- background: #1a1a2e;
16
- color: #eee;
17
- border-radius: 12px;
19
+ width: 320px;
20
+ height: fit-content;
21
+ margin: 0;
22
+ padding: 20px;
23
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
24
+ background: #1f1f3a;
25
+ background: radial-gradient(circle at top right, #1f1f3a, #0b0c10);
26
+ color: #e0e6ed;
18
27
  overflow: hidden;
19
- box-shadow: inset 0 0 0 1px rgba(255,255,255,0.1);
28
+ position: relative;
29
+ }
30
+
31
+ body::before {
32
+ content: '';
33
+ position: absolute;
34
+ top: 0;
35
+ left: 0;
36
+ width: 100%;
37
+ height: 100%;
38
+ background: radial-gradient(circle at center, rgba(74, 158, 255, 0.05) 0%, transparent 80%);
39
+ pointer-events: none;
40
+ z-index: 0;
41
+ }
42
+
43
+ /* Full-screen ripple background container */
44
+ .bg-ripple-container {
45
+ position: absolute;
46
+ top: 0;
47
+ left: 0;
48
+ width: 100%;
49
+ height: 100%;
50
+ overflow: hidden;
51
+ pointer-events: none;
52
+ z-index: 0;
53
+ }
54
+
55
+ .bg-ripple-container svg {
56
+ width: 100%;
57
+ height: 100%;
58
+ }
59
+
60
+ .container {
61
+ position: relative;
62
+ z-index: 1;
20
63
  }
64
+
21
65
  .header {
22
66
  display: flex;
23
67
  align-items: center;
24
- gap: 10px;
25
- margin-bottom: 16px;
68
+ gap: 12px;
69
+ margin-bottom: 20px;
70
+ }
71
+ .ghost-wrapper {
72
+ position: relative;
73
+ width: 48px;
74
+ height: 48px;
75
+ flex-shrink: 0;
26
76
  }
27
- .header img {
28
- width: 32px;
29
- height: 32px;
77
+
78
+ .ghost-wrapper svg {
79
+ width: 100%;
80
+ height: 100%;
81
+ overflow: visible;
82
+ filter: drop-shadow(0 0 8px rgba(255, 255, 255, 0.2));
83
+ }
84
+
85
+ .ghost-body {
86
+ fill: #fff;
87
+ transition: fill 0.4s ease;
88
+ }
89
+
90
+ .bg-ripple {
91
+ fill: none;
92
+ stroke-width: 2;
93
+ opacity: 0;
94
+ }
95
+
96
+ .ghost-eyes-normal, .ghost-eyes-closed, .ghost-eyes-star {
97
+ transition: opacity 0.3s ease;
98
+ opacity: 0;
30
99
  }
100
+
101
+ /* Body/Ghost State Binding (Ripples now target .bg-ripple within body classes) */
102
+ .ghost-disconnected .ghost-body { fill: #d1d5db; }
103
+ .ghost-disconnected .ghost-eyes-closed { opacity: 1; }
104
+ .ghost-disconnected .ghost-mouth { opacity: 0.5; }
105
+
106
+ .ghost-connecting .ghost-body { fill: #a252ff; }
107
+ .ghost-connecting .ghost-eyes-normal { opacity: 1; }
108
+
109
+ body.connecting-state .bg-ripple {
110
+ stroke: #a252ff;
111
+ animation: bg-ripple-scan 2s infinite cubic-bezier(0, 0, 0.2, 1);
112
+ }
113
+ body.connecting-state .bg-ripple-2 { animation-delay: 1s; }
114
+
115
+ .ghost-connected .ghost-body { fill: #00d2ff; }
116
+ .ghost-connected .ghost-eyes-star { opacity: 1; }
117
+
118
+ body.connected-state .bg-ripple {
119
+ stroke: #00d2ff;
120
+ animation: bg-ripple-neon 2.5s infinite cubic-bezier(0, 0, 0.2, 1);
121
+ }
122
+ body.connected-state .bg-ripple-2 { animation-delay: 1.25s; }
123
+
124
+ @keyframes bg-ripple-scan {
125
+ 0% { r: 10%; opacity: 0.8; stroke-width: 3; }
126
+ 100% { r: 80%; opacity: 0; stroke-width: 0; }
127
+ }
128
+
129
+ @keyframes bg-ripple-neon {
130
+ 0% { r: 10%; opacity: 0.6; stroke-width: 4; }
131
+ 100% { r: 100%; opacity: 0; stroke-width: 0; }
132
+ }
133
+
31
134
  .header h1 {
32
- font-size: 16px;
135
+ font-size: 18px;
33
136
  font-weight: 600;
137
+ letter-spacing: 0.5px;
138
+ background: linear-gradient(90deg, #fff, #a0b0d0);
139
+ -webkit-background-clip: text;
140
+ background-clip: text;
141
+ -webkit-text-fill-color: transparent;
34
142
  }
143
+
35
144
  .status-card {
36
- background: #16213e;
37
- border-radius: 8px;
38
- padding: 14px;
39
- margin-bottom: 12px;
145
+ background: rgba(22, 33, 62, 0.6);
146
+ backdrop-filter: blur(12px);
147
+ -webkit-backdrop-filter: blur(12px);
148
+ border: 1px solid rgba(255, 255, 255, 0.08);
149
+ border-radius: 12px;
150
+ padding: 16px;
151
+ margin-bottom: 16px;
152
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.2);
153
+ transition: all 0.3s ease;
40
154
  }
155
+
41
156
  .status-row {
42
157
  display: flex;
43
158
  align-items: center;
44
- gap: 10px;
159
+ gap: 12px;
45
160
  }
161
+
162
+ /* Modern Ripple/Pulse Animations */
163
+ .status-dot-wrapper {
164
+ position: relative;
165
+ width: 12px;
166
+ height: 12px;
167
+ flex-shrink: 0;
168
+ }
169
+
46
170
  .status-dot {
47
- width: 10px;
48
- height: 10px;
171
+ width: 100%;
172
+ height: 100%;
49
173
  border-radius: 50%;
50
- flex-shrink: 0;
174
+ transition: background 0.3s ease, box-shadow 0.3s ease;
175
+ position: relative;
176
+ z-index: 2;
177
+ }
178
+
179
+ .status-ping {
180
+ position: absolute;
181
+ top: 0;
182
+ left: 0;
183
+ width: 100%;
184
+ height: 100%;
185
+ border-radius: 50%;
186
+ z-index: 1;
187
+ opacity: 0;
188
+ }
189
+
190
+ /* Connected State */
191
+ .connected .status-dot { background: #00d2ff; box-shadow: 0 0 10px #00d2ff; }
192
+ .connected .status-ping {
193
+ background: #00d2ff;
194
+ animation: ping-slow 3s cubic-bezier(0, 0, 0.2, 1) infinite;
195
+ }
196
+
197
+ /* Connecting/Scanning State */
198
+ .connecting .status-dot { background: #a252ff; box-shadow: 0 0 12px #a252ff; }
199
+ .connecting .status-ping {
200
+ background: #a252ff;
201
+ animation: ping-fast 1.5s cubic-bezier(0, 0, 0.2, 1) infinite;
51
202
  }
52
- .status-dot.connected { background: #34c759; box-shadow: 0 0 8px #34c759; }
53
- .status-dot.connecting { background: #ff9f0a; animation: pulse 1s infinite; }
54
- .status-dot.disconnected { background: #666; }
55
- .status-dot.error { background: #ff3b30; }
56
- @keyframes pulse {
57
- 0%, 100% { opacity: 1; }
58
- 50% { opacity: 0.4; }
203
+
204
+ /* Disconnected/Error State */
205
+ .disconnected .status-dot { background: #4b5563; }
206
+ .error .status-dot { background: #ef4444; box-shadow: 0 0 8px #ef4444; }
207
+
208
+ @keyframes ping-fast {
209
+ 0% { transform: scale(1); opacity: 0.8; }
210
+ 75%, 100% { transform: scale(2.5); opacity: 0; }
59
211
  }
212
+ @keyframes ping-slow {
213
+ 0% { transform: scale(1); opacity: 0.6; }
214
+ 75%, 100% { transform: scale(2.2); opacity: 0; }
215
+ }
216
+
60
217
  .status-text {
61
- font-size: 14px;
62
- font-weight: 500;
218
+ font-size: 15px;
219
+ font-weight: 600;
220
+ letter-spacing: 0.3px;
221
+ transition: color 0.3s ease;
63
222
  }
64
- .status-detail {
223
+
224
+ .status-detail-container {
225
+ margin-top: 12px;
226
+ padding-top: 12px;
227
+ border-top: 1px solid rgba(255, 255, 255, 0.05);
228
+ display: flex;
229
+ flex-direction: column;
230
+ gap: 6px;
231
+ max-height: 150px;
232
+ opacity: 1;
233
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
234
+ overflow: hidden;
235
+ }
236
+
237
+ .status-detail-container.collapsed {
238
+ max-height: 0;
239
+ opacity: 0;
240
+ margin-top: 0;
241
+ padding-top: 0;
242
+ border-top-color: transparent;
243
+ }
244
+
245
+ .detail-row {
246
+ display: flex;
247
+ justify-content: space-between;
65
248
  font-size: 12px;
66
- color: #888;
67
- margin-top: 8px;
68
- padding-left: 20px;
69
249
  }
70
- .config-section {
71
- background: #16213e;
250
+
251
+ .detail-row.clickable {
252
+ cursor: pointer;
253
+ transition: background 0.2s ease;
254
+ padding: 2px 4px;
255
+ margin: -2px -4px;
256
+ border-radius: 4px;
257
+ }
258
+ .detail-row.clickable:hover {
259
+ background: rgba(255, 255, 255, 0.05);
260
+ }
261
+
262
+ .detail-label {
263
+ color: #8b949e;
264
+ }
265
+
266
+ .detail-value {
267
+ color: #c9d1d9;
268
+ font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
269
+ max-width: 180px;
270
+ white-space: nowrap;
271
+ overflow: hidden;
272
+ text-overflow: ellipsis;
273
+ text-align: right;
274
+ }
275
+
276
+ .detail-value.highlight {
277
+ color: #00d2ff;
278
+ }
279
+
280
+ .detail-value.warning {
281
+ color: #ff9f0a;
282
+ }
283
+
284
+ .error-list {
285
+ max-height: 180px;
286
+ overflow-y: auto;
287
+ margin-top: 12px;
288
+ padding: 8px;
289
+ background: rgba(0, 0, 0, 0.4);
72
290
  border-radius: 8px;
73
- padding: 14px;
74
- margin-bottom: 12px;
291
+ border: 1px solid rgba(255, 59, 48, 0.2);
292
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
293
+ opacity: 1;
75
294
  }
76
- .config-label {
77
- font-size: 12px;
78
- color: #888;
79
- margin-bottom: 6px;
295
+ .error-list.collapsed {
296
+ max-height: 0;
297
+ opacity: 0;
298
+ margin-top: 0;
299
+ padding-top: 0;
300
+ padding-bottom: 0;
301
+ border-color: transparent;
302
+ overflow: hidden;
80
303
  }
81
- .config-input {
82
- width: 100%;
83
- padding: 8px 10px;
84
- border: 1px solid #333;
85
- border-radius: 6px;
86
- background: #0f0f23;
87
- color: #eee;
88
- font-size: 13px;
89
- outline: none;
304
+
305
+ .error-item {
306
+ font-size: 11px;
307
+ font-family: ui-monospace, SFMono-Regular, Consolas, monospace;
308
+ padding: 6px;
309
+ border-bottom: 1px solid rgba(255, 255, 255, 0.05);
310
+ word-break: break-work;
311
+ color: #e2e8f0;
312
+ display: flex;
313
+ flex-direction: column;
314
+ gap: 4px;
315
+ }
316
+ .error-item:last-child {
317
+ border-bottom: none;
90
318
  }
91
- .config-input:focus {
92
- border-color: #4a9eff;
319
+ .error-item .err-msg {
320
+ color: #ff8a8a;
321
+ white-space: pre-wrap;
322
+ }
323
+ .error-item .err-loc {
324
+ color: #64748b;
325
+ font-size: 10px;
93
326
  }
94
327
 
95
328
  .btn-row {
96
329
  display: flex;
97
- gap: 8px;
330
+ gap: 10px;
98
331
  }
332
+
99
333
  .btn {
100
334
  flex: 1;
101
- padding: 10px;
335
+ padding: 12px;
102
336
  border: none;
103
- border-radius: 6px;
104
- font-size: 13px;
105
- font-weight: 500;
337
+ border-radius: 8px;
338
+ font-size: 14px;
339
+ font-weight: 600;
106
340
  cursor: pointer;
107
- transition: all 0.2s;
341
+ transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
342
+ position: relative;
343
+ overflow: hidden;
108
344
  }
345
+
109
346
  .btn-primary {
110
- background: #4a9eff;
347
+ background: linear-gradient(135deg, #00d2ff 0%, #3a7bd5 100%);
111
348
  color: #fff;
349
+ box-shadow: 0 4px 12px rgba(0, 210, 255, 0.2);
112
350
  }
113
- .btn-primary:hover {
114
- background: #3a8eef;
351
+
352
+ .btn-primary:hover:not(:disabled) {
353
+ transform: translateY(-1px);
354
+ box-shadow: 0 6px 16px rgba(0, 210, 255, 0.3);
355
+ background: linear-gradient(135deg, #1ae0ff 0%, #4a8be5 100%);
115
356
  }
357
+
358
+ .btn-primary:active:not(:disabled) {
359
+ transform: translateY(1px);
360
+ box-shadow: 0 2px 8px rgba(0, 210, 255, 0.2);
361
+ }
362
+
116
363
  .btn-primary:disabled {
117
- background: #333;
364
+ background: linear-gradient(135deg, #374151 0%, #1f2937 100%);
365
+ color: #9ca3af;
366
+ box-shadow: none;
118
367
  cursor: not-allowed;
119
368
  }
369
+
120
370
  .btn-secondary {
121
- background: #333;
122
- color: #ccc;
371
+ background: rgba(45, 55, 72, 0.5);
372
+ border: 1px solid rgba(255, 255, 255, 0.1);
373
+ color: #d1d5db;
123
374
  }
124
- .btn-secondary:hover {
125
- background: #444;
375
+
376
+ .btn-secondary:hover:not(:disabled) {
377
+ background: rgba(74, 85, 104, 0.8);
378
+ color: #fff;
126
379
  }
380
+
127
381
  .scan-info {
128
- font-size: 11px;
129
- color: #666;
382
+ font-size: 12px;
383
+ color: #8b949e;
130
384
  text-align: center;
131
- margin-top: 8px;
385
+ margin-top: 12px;
386
+ max-height: 40px;
387
+ opacity: 1;
388
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
389
+ overflow: hidden;
390
+ }
391
+
392
+ .scan-info.collapsed {
393
+ max-height: 0;
394
+ opacity: 0;
395
+ margin-top: 0;
396
+ }
397
+
398
+ .hidden {
399
+ display: none !important;
400
+ }
401
+
402
+ .fade-enter {
403
+ opacity: 0;
404
+ }
405
+
406
+ .fade-enter-active {
407
+ opacity: 1;
408
+ transition: opacity 300ms ease-in;
132
409
  }
133
410
  </style>
134
411
  </head>
135
- <body>
136
- <div class="header">
137
- <img src="icon-32.png" alt="Ghost Bridge">
138
- <h1>Ghost Bridge</h1>
412
+ <body class="disconnected-state">
413
+ <div class="bg-ripple-container">
414
+ <svg xmlns="http://www.w3.org/2000/svg">
415
+ <!-- Centered on the Ghost Icon (20px padding + 24px icon center = 44px) -->
416
+ <circle class="bg-ripple bg-ripple-1" cx="44" cy="44" r="0" />
417
+ <circle class="bg-ripple bg-ripple-2" cx="44" cy="44" r="0" />
418
+ </svg>
139
419
  </div>
140
420
 
141
- <div class="status-card">
142
- <div class="status-row">
143
- <div class="status-dot disconnected" id="statusDot"></div>
144
- <span class="status-text" id="statusText">检查连接状态...</span>
421
+ <div class="container">
422
+ <div class="header">
423
+ <div id="headerGhost" class="ghost-wrapper ghost-disconnected">
424
+ <svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
425
+ <!-- Ghost Body -->
426
+ <path class="ghost-body" d="M 25 50 C 25 36.19 36.19 25 50 25 C 63.81 25 75 36.19 75 50 V 75 Q 70 80 65 75 Q 60 70 55 75 Q 50 80 45 75 Q 40 70 35 75 Q 30 80 25 75 Z" />
427
+
428
+ <!-- Eyes Container -->
429
+ <g class="ghost-eyes-normal">
430
+ <!-- Normal Eyes -->
431
+ <circle cx="40" cy="45" r="4" fill="#0b0c10" />
432
+ <circle cx="60" cy="45" r="4" fill="#0b0c10" />
433
+ </g>
434
+ <g class="ghost-eyes-closed">
435
+ <!-- Closed Eyes (Arcs) -->
436
+ <path d="M 36 45 Q 40 48 44 45" stroke="#0b0c10" stroke-width="2.5" fill="none" stroke-linecap="round"/>
437
+ <path d="M 56 45 Q 60 48 64 45" stroke="#0b0c10" stroke-width="2.5" fill="none" stroke-linecap="round"/>
438
+ </g>
439
+ <g class="ghost-eyes-star">
440
+ <!-- Star Eyes -->
441
+ <path d="M 40 41 L 41.5 44 L 45 44.5 L 42 46.5 L 43 50 L 40 48 L 37 50 L 38 46.5 L 35 44.5 L 38.5 44 Z" fill="#0b0c10" />
442
+ <path d="M 60 41 L 61.5 44 L 65 44.5 L 62 46.5 L 63 50 L 60 48 L 57 50 L 58 46.5 L 55 44.5 L 58.5 44 Z" fill="#0b0c10" />
443
+ </g>
444
+
445
+ <!-- Mouth -->
446
+ <path class="ghost-mouth" d="M 46 55 H 54 V 60 H 46 Z" fill="#0b0c10" />
447
+ </svg>
448
+ </div>
449
+ <h1>Ghost Bridge</h1>
145
450
  </div>
146
- <div class="status-detail" id="statusDetail"></div>
147
- </div>
148
451
 
452
+ <div class="status-card" id="statusCard">
453
+ <div class="status-row">
454
+ <div class="status-dot-wrapper" id="dotWrapper">
455
+ <div class="status-ping"></div>
456
+ <div class="status-dot"></div>
457
+ </div>
458
+ <span class="status-text" id="statusText">Checking...</span>
459
+ </div>
460
+
461
+ <div class="status-detail-container" id="detailContainer">
462
+ <div class="detail-row">
463
+ <span class="detail-label">Service</span>
464
+ <span class="detail-value" id="portVal">-</span>
465
+ </div>
466
+ <div class="detail-row" id="tabRow">
467
+ <span class="detail-label">Target Tab</span>
468
+ <span class="detail-value" id="tabVal" title="">-</span>
469
+ </div>
470
+ <div class="detail-row clickable" id="errorRow" title="Click to view recent errors">
471
+ <span class="detail-label">Rec. Errors</span>
472
+ <span class="detail-value warning" id="errorVal">0</span>
473
+ </div>
474
+ </div>
475
+
476
+ <div id="errorList" class="error-list collapsed">
477
+ <!-- JS dynamically injects errors here -->
478
+ </div>
479
+ </div>
149
480
 
481
+ <div class="btn-row">
482
+ <button class="btn btn-primary" id="connectBtn">Connect</button>
483
+ <button class="btn btn-secondary" id="disconnectBtn">Stop</button>
484
+ </div>
150
485
 
151
- <div class="btn-row">
152
- <button class="btn btn-primary" id="connectBtn">连接</button>
153
- <button class="btn btn-secondary" id="disconnectBtn">断开</button>
486
+ <div class="scan-info" id="scanInfo"></div>
154
487
  </div>
155
488
 
156
- <div class="scan-info" id="scanInfo"></div>
157
-
158
489
  <script src="popup.js"></script>
159
490
  </body>
160
491
  </html>