vg-coder-cli 2.0.5 → 2.0.7

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vg-coder-cli",
3
- "version": "2.0.5",
3
+ "version": "2.0.7",
4
4
  "description": "🚀 CLI tool to analyze projects, concatenate source files, count tokens, and export HTML with syntax highlighting and copy functionality",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -7,7 +7,7 @@
7
7
  content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">
8
8
  <meta name="apple-mobile-web-app-capable" content="yes">
9
9
  <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
10
- <title>VG Coder CLI</title>
10
+ <title>VG Coder API Dashboard</title>
11
11
  <style>
12
12
  :root {
13
13
  --ios-bg: #F2F2F7;
@@ -15,11 +15,12 @@
15
15
  --ios-blue: #007AFF;
16
16
  --ios-green: #34C759;
17
17
  --ios-red: #FF3B30;
18
- --ios-text-primary: #000000;
19
- --ios-text-secondary: #8E8E93;
18
+ --ios-gray: #8E8E93;
19
+ --ios-gray-light: #E5E5EA;
20
+ --ios-input-bg: #F2F2F7;
20
21
  --ios-separator: #C6C6C8;
21
- --ios-input-bg: #E5E5EA;
22
- --font-stack: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
22
+ --safe-area-top: env(safe-area-inset-top);
23
+ --safe-area-bottom: env(safe-area-inset-bottom);
23
24
  }
24
25
 
25
26
  * {
@@ -30,97 +31,163 @@
30
31
  }
31
32
 
32
33
  body {
33
- font-family: var(--font-stack);
34
+ font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
34
35
  background-color: var(--ios-bg);
35
- color: var(--ios-text-primary);
36
+ color: #000;
36
37
  min-height: 100vh;
37
- padding-bottom: 40px;
38
- font-size: 17px;
39
- line-height: 1.5;
38
+ padding: 0;
39
+ padding-bottom: calc(20px + var(--safe-area-bottom));
40
40
  -webkit-font-smoothing: antialiased;
41
41
  }
42
42
 
43
- /* Glass Header */
44
- .header-nav {
45
- position: sticky;
46
- top: 0;
47
- z-index: 100;
48
- background: rgba(255, 255, 255, 0.85);
49
- backdrop-filter: blur(20px);
50
- -webkit-backdrop-filter: blur(20px);
51
- border-bottom: 0.5px solid rgba(0, 0, 0, 0.1);
52
- padding: 15px 20px;
53
- display: flex;
54
- justify-content: space-between;
55
- align-items: center;
56
- padding-top: max(15px, env(safe-area-inset-top));
43
+ .container {
44
+ max-width: 800px;
45
+ margin: 0 auto;
46
+ padding: 20px;
47
+ padding-top: calc(20px + var(--safe-area-top));
48
+ }
49
+
50
+ /* Header iOS Style */
51
+ .header {
52
+ text-align: left;
53
+ margin-bottom: 24px;
54
+ padding: 0 4px;
57
55
  }
58
56
 
59
- .app-title {
57
+ .header h1 {
58
+ color: #000;
59
+ font-size: 34px;
60
60
  font-weight: 700;
61
- font-size: 1.2rem;
62
61
  letter-spacing: -0.5px;
62
+ margin-bottom: 8px;
63
63
  }
64
64
 
65
- .status-badge {
66
- font-size: 0.75rem;
67
- font-weight: 600;
68
- padding: 4px 10px;
69
- border-radius: 20px;
70
- background: var(--ios-green);
71
- color: white;
72
- box-shadow: 0 2px 4px rgba(52, 199, 89, 0.2);
73
- transition: all 0.3s ease;
65
+ .header p {
66
+ color: var(--ios-gray);
67
+ font-size: 17px;
68
+ line-height: 1.4;
69
+ margin-bottom: 12px;
74
70
  }
75
71
 
76
- .container {
77
- max-width: 800px;
78
- margin: 0 auto;
79
- padding: 20px;
80
- padding-left: max(20px, env(safe-area-inset-left));
81
- padding-right: max(20px, env(safe-area-inset-right));
72
+ .status {
73
+ display: inline-flex;
74
+ align-items: center;
75
+ padding: 6px 12px;
76
+ background: rgba(52, 199, 89, 0.15);
77
+ color: var(--ios-green);
78
+ border-radius: 20px;
79
+ font-size: 13px;
80
+ font-weight: 600;
81
+ margin-top: 5px;
82
82
  }
83
83
 
84
- /* iOS Cards */
85
- .card {
84
+ /* Card Style (Inset Grouped) */
85
+ .system-prompt-card,
86
+ .endpoint-card {
86
87
  background: var(--ios-card);
87
- border-radius: 20px;
88
- padding: 24px;
89
- margin-bottom: 24px;
90
- box-shadow: 0 4px 20px rgba(0, 0, 0, 0.03);
91
- position: relative;
88
+ border-radius: 16px;
89
+ padding: 20px;
90
+ margin-bottom: 20px;
91
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
92
92
  overflow: hidden;
93
+ position: relative;
93
94
  }
94
95
 
95
- .card-header {
96
+ /* System Prompt */
97
+ .system-prompt-header {
96
98
  display: flex;
99
+ justify-content: space-between;
97
100
  align-items: center;
98
- margin-bottom: 16px;
99
- gap: 12px;
101
+ cursor: pointer;
102
+ padding: 4px 0;
100
103
  }
101
104
 
102
- .card-icon {
103
- width: 40px;
104
- height: 40px;
105
+ .system-prompt-header h2 {
106
+ color: #000;
107
+ font-size: 20px;
108
+ font-weight: 600;
109
+ display: flex;
110
+ align-items: center;
111
+ gap: 10px;
112
+ }
113
+
114
+ .toggle-icon {
115
+ color: var(--ios-gray);
116
+ font-size: 14px;
117
+ transition: transform 0.3s cubic-bezier(0.25, 0.1, 0.25, 1);
118
+ }
119
+
120
+ .toggle-icon.open {
121
+ transform: rotate(180deg);
122
+ }
123
+
124
+ .system-prompt-content {
125
+ max-height: 0;
126
+ overflow: hidden;
127
+ transition: max-height 0.4s cubic-bezier(0.25, 0.1, 0.25, 1), opacity 0.3s;
128
+ opacity: 0;
129
+ }
130
+
131
+ .system-prompt-content.open {
132
+ max-height: 2000px;
133
+ opacity: 1;
134
+ margin-top: 16px;
135
+ }
136
+
137
+ .prompt-text {
138
+ background: var(--ios-input-bg);
139
+ border: none;
105
140
  border-radius: 12px;
106
- background: var(--ios-bg);
141
+ padding: 16px;
142
+ font-family: 'SF Mono', 'Menlo', 'Monaco', 'Courier New', monospace;
143
+ font-size: 13px;
144
+ line-height: 1.5;
145
+ white-space: pre-wrap;
146
+ max-height: 300px;
147
+ overflow-y: auto;
148
+ margin-bottom: 16px;
149
+ color: #333;
150
+ -webkit-overflow-scrolling: touch;
151
+ }
152
+
153
+ /* Endpoints */
154
+ .endpoint-header {
107
155
  display: flex;
108
156
  align-items: center;
109
- justify-content: center;
110
- font-size: 1.2rem;
111
- color: var(--ios-blue);
157
+ flex-wrap: wrap;
158
+ gap: 10px;
159
+ margin-bottom: 12px;
160
+ border-bottom: 0.5px solid var(--ios-separator);
161
+ padding-bottom: 12px;
112
162
  }
113
163
 
114
- .card-title {
115
- font-size: 1.1rem;
164
+ .method {
165
+ padding: 4px 10px;
166
+ border-radius: 6px;
116
167
  font-weight: 700;
117
- letter-spacing: -0.3px;
168
+ font-size: 12px;
169
+ text-transform: uppercase;
170
+ letter-spacing: 0.5px;
171
+ }
172
+
173
+ .method.post {
174
+ background: var(--ios-green);
175
+ color: white;
176
+ }
177
+
178
+ .endpoint-path {
179
+ font-family: 'SF Mono', 'Menlo', monospace;
180
+ color: #000;
181
+ font-size: 16px;
182
+ font-weight: 600;
183
+ word-break: break-all;
118
184
  }
119
185
 
120
- .card-desc {
121
- color: var(--ios-text-secondary);
122
- font-size: 0.9rem;
186
+ .endpoint-desc {
187
+ color: var(--ios-gray);
123
188
  margin-bottom: 20px;
189
+ font-size: 15px;
190
+ line-height: 1.4;
124
191
  }
125
192
 
126
193
  /* Forms */
@@ -128,85 +195,103 @@
128
195
  margin-bottom: 20px;
129
196
  }
130
197
 
131
- .form-label {
198
+ .form-group label {
132
199
  display: block;
133
- font-size: 0.85rem;
134
- font-weight: 600;
135
- color: var(--ios-text-secondary);
136
200
  margin-bottom: 8px;
201
+ color: #000;
202
+ font-weight: 500;
203
+ font-size: 14px;
137
204
  text-transform: uppercase;
138
- letter-spacing: 0.5px;
205
+ letter-spacing: 0.3px;
206
+ opacity: 0.6;
139
207
  }
140
208
 
141
- input[type="text"],
142
- textarea {
209
+ .form-group input,
210
+ .form-group textarea {
143
211
  width: 100%;
212
+ padding: 14px;
144
213
  background: var(--ios-input-bg);
145
214
  border: none;
146
215
  border-radius: 12px;
147
- padding: 14px 16px;
148
- font-size: 1rem;
149
- font-family: inherit;
150
- color: var(--ios-text-primary);
151
- transition: all 0.2s;
216
+ font-size: 17px;
217
+ /* Prevent zoom on iOS */
218
+ font-family: 'SF Mono', 'Menlo', monospace;
219
+ color: #000;
220
+ transition: background 0.2s;
221
+ appearance: none;
152
222
  -webkit-appearance: none;
153
223
  }
154
224
 
155
- textarea {
156
- min-height: 120px;
157
- resize: vertical;
158
- line-height: 1.4;
225
+ .form-group input:focus,
226
+ .form-group textarea:focus {
227
+ outline: none;
228
+ background: #E5E5EA;
229
+ /* Slightly darker on focus */
159
230
  }
160
231
 
161
- input:focus,
162
- textarea:focus {
163
- outline: none;
164
- background: #D1D1D6;
232
+ .form-group textarea {
233
+ min-height: 120px;
234
+ resize: none;
165
235
  }
166
236
 
167
237
  /* Buttons */
168
238
  .btn-group {
169
- display: grid;
170
- grid-template-columns: 1fr 1fr;
171
- /* 2 columns for better layout */
239
+ display: flex;
172
240
  gap: 12px;
241
+ margin-top: 24px;
242
+ flex-direction: column;
243
+ /* Stack on mobile by default */
244
+ }
245
+
246
+ @media (min-width: 640px) {
247
+ .btn-group {
248
+ flex-direction: row;
249
+ }
173
250
  }
174
251
 
175
252
  .btn {
176
- border: none;
177
253
  background: var(--ios-blue);
178
254
  color: white;
179
- padding: 14px;
180
- border-radius: 14px;
181
- font-size: 1rem;
182
- font-weight: 600;
255
+ border: none;
256
+ padding: 14px 20px;
257
+ border-radius: 12px;
183
258
  cursor: pointer;
259
+ font-size: 17px;
260
+ font-weight: 600;
184
261
  transition: transform 0.1s, opacity 0.2s;
185
262
  display: flex;
186
263
  align-items: center;
187
264
  justify-content: center;
188
265
  gap: 8px;
189
- -webkit-appearance: none;
266
+ width: 100%;
267
+ position: relative;
268
+ overflow: hidden;
190
269
  }
191
270
 
192
271
  .btn:active {
193
272
  transform: scale(0.98);
194
- opacity: 0.8;
273
+ opacity: 0.9;
274
+ }
275
+
276
+ .btn:disabled {
277
+ background: var(--ios-gray-light);
278
+ color: var(--ios-gray);
279
+ cursor: not-allowed;
280
+ transform: none;
195
281
  }
196
282
 
197
- .btn-secondary {
198
- background: rgba(0, 122, 255, 0.15);
283
+ .btn-copy {
284
+ background: rgba(0, 122, 255, 0.1);
199
285
  color: var(--ios-blue);
200
286
  }
201
287
 
202
- /* Full width button in single column if needed */
203
- .btn-full {
204
- grid-column: 1 / -1;
288
+ /* Specific override for prompt copy button to look nice */
289
+ .system-prompt-content .btn-copy {
290
+ margin-top: 8px;
205
291
  }
206
292
 
207
- .btn-copy {
208
- background: #E5E5EA;
209
- color: black;
293
+ .btn-copy:active {
294
+ background: rgba(0, 122, 255, 0.2);
210
295
  }
211
296
 
212
297
  .btn-copy.copied {
@@ -214,376 +299,389 @@
214
299
  color: white;
215
300
  }
216
301
 
217
- /* System Prompt Accordion */
218
- .prompt-accordion {
219
- cursor: pointer;
220
- user-select: none;
221
- }
222
-
223
- .prompt-header {
224
- display: flex;
225
- justify-content: space-between;
226
- align-items: center;
227
- padding: 4px 0;
228
- }
229
-
230
- .chevron {
231
- color: var(--ios-text-secondary);
232
- transition: transform 0.3s ease;
233
- font-size: 0.8rem;
234
- }
235
-
236
- .prompt-content {
237
- max-height: 0;
238
- overflow: hidden;
239
- transition: max-height 0.4s cubic-bezier(0.65, 0, 0.35, 1);
240
- opacity: 0;
241
- }
242
-
243
- .prompt-content.open {
244
- max-height: 500px;
245
- opacity: 1;
246
- margin-top: 15px;
247
- }
248
-
249
- .code-block {
250
- background: #2c2c2e;
251
- color: #fff;
252
- padding: 15px;
253
- border-radius: 12px;
254
- font-family: 'SF Mono', 'Menlo', monospace;
255
- font-size: 0.85rem;
256
- overflow-x: auto;
257
- white-space: pre-wrap;
258
- margin-bottom: 15px;
259
- border: 1px solid rgba(0, 0, 0, 0.1);
260
- }
261
-
262
302
  /* Response Area */
263
303
  .response {
264
304
  margin-top: 20px;
265
- background: #F9F9F9;
305
+ padding: 16px;
266
306
  border-radius: 12px;
267
- padding: 15px;
268
- border-left: 5px solid var(--ios-text-secondary);
307
+ background: #1C1C1E;
308
+ /* Dark background for code contrast */
309
+ color: #fff;
269
310
  display: none;
270
- animation: slideDown 0.3s ease;
311
+ font-size: 13px;
312
+ overflow-x: auto;
313
+ border-left: none;
314
+ /* Removed the side border style */
271
315
  }
272
316
 
273
317
  .response.show {
274
318
  display: block;
319
+ animation: fadeIn 0.3s ease;
275
320
  }
276
321
 
277
322
  .response.success {
278
- border-left-color: var(--ios-green);
279
- background: #F0FFF4;
323
+ border: 1px solid rgba(52, 199, 89, 0.3);
280
324
  }
281
325
 
282
326
  .response.error {
283
- border-left-color: var(--ios-red);
284
- background: #FFF5F5;
327
+ border: 1px solid rgba(255, 59, 48, 0.3);
328
+ background: #2C1515;
285
329
  }
286
330
 
287
331
  .response pre {
332
+ margin: 0;
288
333
  font-family: 'SF Mono', 'Menlo', monospace;
289
- font-size: 0.85rem;
290
334
  white-space: pre-wrap;
291
- word-break: break-all;
292
- color: #333;
335
+ word-wrap: break-word;
336
+ }
337
+
338
+ @keyframes fadeIn {
339
+ from {
340
+ opacity: 0;
341
+ transform: translateY(5px);
342
+ }
343
+
344
+ to {
345
+ opacity: 1;
346
+ transform: translateY(0);
347
+ }
348
+ }
349
+
350
+ /* Loading Spinner */
351
+ .loading {
352
+ display: inline-block;
353
+ width: 18px;
354
+ height: 18px;
355
+ border: 2px solid rgba(255, 255, 255, 0.3);
356
+ border-radius: 50%;
357
+ border-top-color: currentColor;
358
+ animation: spin 0.8s linear infinite;
359
+ }
360
+
361
+ @keyframes spin {
362
+ to {
363
+ transform: rotate(360deg);
364
+ }
293
365
  }
294
366
 
295
- /* Toast */
367
+ /* Toast - iOS Notification Style */
296
368
  .toast {
297
369
  position: fixed;
298
- top: 20px;
370
+ top: 10px;
299
371
  left: 50%;
300
372
  transform: translateX(-50%) translateY(-100px);
301
- background: rgba(0, 0, 0, 0.8);
302
- backdrop-filter: blur(10px);
303
- color: white;
304
- padding: 12px 24px;
305
- border-radius: 50px;
306
- font-weight: 600;
307
- font-size: 0.95rem;
308
- z-index: 1000;
309
- box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
310
- transition: transform 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
373
+ padding: 12px 20px;
374
+ border-radius: 25px;
375
+ /* Pill shape */
376
+ color: #000;
377
+ background: rgba(255, 255, 255, 0.85);
378
+ backdrop-filter: blur(20px);
379
+ -webkit-backdrop-filter: blur(20px);
380
+ font-weight: 500;
381
+ font-size: 15px;
382
+ box-shadow: 0 4px 15px rgba(0, 0, 0, 0.15);
383
+ transition: transform 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275), opacity 0.3s;
384
+ z-index: 2000;
311
385
  display: flex;
312
386
  align-items: center;
313
- gap: 8px;
387
+ gap: 10px;
388
+ width: auto;
389
+ max-width: 90%;
314
390
  white-space: nowrap;
391
+ opacity: 0;
392
+ pointer-events: none;
315
393
  }
316
394
 
317
395
  .toast.show {
318
- transform: translateX(-50%) translateY(0);
396
+ transform: translateX(-50%) translateY(calc(var(--safe-area-top)));
397
+ opacity: 1;
319
398
  }
320
399
 
321
- .loading-spinner {
322
- width: 18px;
323
- height: 18px;
324
- border: 2px solid rgba(255, 255, 255, 0.3);
400
+ /* Icons in toast */
401
+ .toast::before {
402
+ content: '';
403
+ display: block;
404
+ width: 20px;
405
+ height: 20px;
325
406
  border-radius: 50%;
326
- border-top-color: white;
327
- animation: spin 0.8s linear infinite;
407
+ flex-shrink: 0;
328
408
  }
329
409
 
330
- @keyframes spin {
331
- to {
332
- transform: rotate(360deg);
333
- }
410
+ .toast.success::before {
411
+ background: var(--ios-green) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' stroke='white'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M5 13l4 4L19 7'/%3E%3C/svg%3E") center/12px no-repeat;
334
412
  }
335
413
 
336
- @keyframes slideDown {
337
- from {
338
- opacity: 0;
339
- transform: translateY(-10px);
340
- }
414
+ .toast.error::before {
415
+ background: var(--ios-red) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' stroke='white'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 18L18 6M6 6l12 12'/%3E%3C/svg%3E") center/12px no-repeat;
416
+ }
341
417
 
342
- to {
343
- opacity: 1;
344
- transform: translateY(0);
345
- }
418
+ .toast.info::before {
419
+ background: var(--ios-blue);
346
420
  }
347
421
  </style>
348
422
  </head>
349
423
 
350
424
  <body>
351
- <!-- Glass Header -->
352
- <nav class="header-nav">
353
- <div class="app-title">VG Coder</div>
354
- <div id="status" class="status-badge">● Offline</div>
355
- </nav>
356
-
357
425
  <div class="container">
426
+ <div class="header">
427
+ <span class="status" id="status">● Server Starting...</span>
428
+ <div style="height: 10px;"></div>
429
+ <h1>VG Coder</h1>
430
+ <p>API Dashboard & Automation</p>
431
+ </div>
358
432
 
359
- <!-- System Prompt Card -->
360
- <div class="card prompt-accordion">
361
- <div class="card-header prompt-header" onclick="toggleSystemPrompt()">
362
- <div style="display: flex; align-items: center; gap: 12px;">
363
- <div class="card-icon" style="background: rgba(255, 59, 48, 0.1); color: var(--ios-red);">⚡</div>
364
- <div class="card-title">System Prompt</div>
365
- </div>
366
- <div class="chevron" id="toggle-icon">▼</div>
433
+ <!-- System Prompt Section -->
434
+ <div class="system-prompt-card">
435
+ <div class="system-prompt-header" onclick="toggleSystemPrompt()">
436
+ <h2>
437
+ <span>📝</span>
438
+ <span>System Prompt</span>
439
+ </h2>
440
+ <span class="toggle-icon" id="toggle-icon">▼</span>
367
441
  </div>
368
- <div class="prompt-content" id="system-prompt-content">
369
- <div class="code-block" id="prompt-text"></div>
370
- <button class="btn btn-secondary btn-full" onclick="copySystemPrompt()">
442
+ <div class="system-prompt-content" id="system-prompt-content">
443
+ <div class="prompt-text" id="prompt-text"></div>
444
+ <button class="btn btn-copy" onclick="copySystemPrompt()">
371
445
  <span id="copy-icon">📋</span>
372
- <span id="copy-text">Copy Prompt</span>
446
+ <span id="copy-text">Copy System Prompt</span>
373
447
  </button>
374
448
  </div>
375
449
  </div>
376
450
 
377
- <!-- Analyze Card -->
378
- <div class="card">
379
- <div class="card-header">
380
- <div class="card-icon">📊</div>
381
- <div>
382
- <div class="card-title">Project Analyze</div>
451
+ <div class="endpoints">
452
+ <!-- Analyze -->
453
+ <div class="endpoint-card">
454
+ <div class="endpoint-header">
455
+ <span class="method post">POST</span>
456
+ <span class="endpoint-path">/api/analyze</span>
383
457
  </div>
384
- </div>
385
- <div class="card-desc">Phân tích dự án và lấy source code</div>
386
-
387
- <div class="form-group">
388
- <label class="form-label">Project Path</label>
389
- <input type="text" id="analyze-path" value="." placeholder="/path/to/project">
390
- </div>
391
-
392
- <div class="btn-group">
393
- <button class="btn" onclick="testAnalyze()">
394
- <span>📥</span> Download
395
- </button>
396
- <button class="btn btn-secondary" onclick="copyAnalyzeResult()">
397
- <span id="analyze-copy-icon">📋</span> Copy
398
- </button>
399
- </div>
400
- <div class="response" id="analyze-response"></div>
401
- </div>
402
-
403
- <!-- Execute Card -->
404
- <div class="card">
405
- <div class="card-header">
406
- <div class="card-icon" style="background: rgba(52, 199, 89, 0.1); color: var(--ios-green);">🚀</div>
407
- <div>
408
- <div class="card-title">Execute Script</div>
458
+ <p class="endpoint-desc">Phân tích dự án và lấy toàn bộ source code.</p>
459
+ <div class="form-group">
460
+ <label>Path</label>
461
+ <input type="text" id="analyze-path" value="." placeholder="Project path (e.g. .)">
409
462
  </div>
410
- </div>
411
- <div class="card-desc">Thực thi bash script an toàn</div>
412
-
413
- <div class="form-group">
414
- <label class="form-label">Bash Script</label>
415
- <textarea id="execute-bash" placeholder="mkdir -p src..."></textarea>
463
+ <div class="btn-group">
464
+ <button class="btn" onclick="testAnalyze()">
465
+ <span>📥</span>
466
+ <span>Download</span>
467
+ </button>
468
+ <button class="btn btn-copy" onclick="copyAnalyzeResult()">
469
+ <span id="analyze-copy-icon">📋</span>
470
+ <span id="analyze-copy-text">Copy Text</span>
471
+ </button>
472
+ <button class="btn btn-copy" onclick="copyAnalyzeAsFile()">
473
+ <span id="analyze-file-icon">📄</span>
474
+ <span id="analyze-file-text">Copy as File</span>
475
+ </button>
476
+ </div>
477
+ <div class="response" id="analyze-response"></div>
416
478
  </div>
417
479
 
418
- <div class="btn-group">
419
- <button class="btn" style="background: var(--ios-green);" onclick="testExecute()">
420
- <span>▶️</span> Execute
421
- </button>
422
- <button class="btn btn-secondary" onclick="executeFromClipboard()">
423
- <span>📋</span> Paste & Run
424
- </button>
480
+ <!-- Execute Bash -->
481
+ <div class="endpoint-card">
482
+ <div class="endpoint-header">
483
+ <span class="method post">POST</span>
484
+ <span class="endpoint-path">/api/execute</span>
485
+ </div>
486
+ <p class="endpoint-desc">Thực thi bash script với syntax validation.</p>
487
+ <div class="form-group">
488
+ <label>Bash Script</label>
489
+ <textarea id="execute-bash"
490
+ placeholder="mkdir -p src/test&#10;echo 'Hello' > src/test/hello.txt"></textarea>
491
+ </div>
492
+ <div class="btn-group">
493
+ <button class="btn" onclick="testExecute()">
494
+ <span>▶️</span>
495
+ <span>Run Script</span>
496
+ </button>
497
+ <button class="btn" onclick="executeFromClipboard()">
498
+ <span>📋</span>
499
+ <span>Paste & Run</span>
500
+ </button>
501
+ </div>
502
+ <div class="response" id="execute-response"></div>
425
503
  </div>
426
- <div class="response" id="execute-response"></div>
427
504
  </div>
428
505
  </div>
429
506
 
430
- <!-- iOS Toast -->
431
507
  <div class="toast" id="toast"></div>
432
508
 
433
509
  <script>
434
510
  const API_BASE = window.location.origin;
435
511
  let lastAnalyzeResult = null;
436
512
 
513
+ // System Prompt
437
514
  const SYSTEM_PROMPT = `# VG Coder AI System Prompt
438
515
 
439
516
  ## Command Prefixes
440
- ### /ask - Q&A Mode
517
+
518
+ ### /ask - Question & Answer Mode
519
+ Khi người dùng hỏi với prefix /ask, họ đang muốn tìm hiểu hoặc được giải thích về một vấn đề.
520
+
521
+ **Response Format:** Markdown
522
+ - Trả lời chi tiết, rõ ràng
523
+ - Sử dụng code blocks, lists, tables khi cần
524
+ - Cung cấp ví dụ minh họa
525
+
526
+ ---
527
+
441
528
  ### /plan - Planning Mode
529
+ Khi người dùng muốn lên kế hoạch với prefix /plan, tạo một implementation plan chi tiết.
530
+
531
+ **Response Format:** Markdown checklist với bash commands
532
+ - Chia nhỏ thành các bước cụ thể
533
+ - Mỗi bước có bash command tương ứng
534
+ - Sắp xếp theo thứ tự logic
535
+
536
+ ---
537
+
442
538
  ### /fix - Bug Fix Mode
539
+ Khi người dùng cần fix bug với prefix /fix, phân tích lỗi và đưa ra giải pháp.
540
+
541
+ **Response Format:** Markdown + Bash script
542
+ 1. **Phân tích lỗi:** Giải thích nguyên nhân
543
+ 2. **Giải pháp:** Mô tả cách fix
544
+ 3. **Bash script:** Code để fix (nếu cần)
545
+
546
+ ---
547
+
443
548
  ### /code - Code Generation Mode
549
+ Khi người dùng hỏi với prefix /code, trả về **BASH SCRIPT DUY NHẤT** để tạo/cập nhật files.
444
550
 
445
- ## ⚠️ QUY TẮC BẮT BUỘC /code
551
+ ## ⚠️ QUY TẮC BẮT BUỘC
446
552
 
447
- 1. **Format Script Chuẩn:**
553
+ ### 1. Chỉ bao gồm files có thay đổi
554
+ - ❌ **KHÔNG** bao gồm files không có sự thay đổi nội dung
555
+ - ✅ Nếu nội dung file sau chỉnh sửa giống 100% bản cũ → **BỎ QUA**
556
+
557
+ ### 2. Format Script Chuẩn
558
+
559
+ **Mỗi file PHẢI theo cú pháp:**
448
560
  \`\`\`bash
449
561
  mkdir -p $(dirname "path/to/file.ext")
450
562
  cat <<'EOF' > path/to/file.ext
451
- ... content ...
563
+ ... toàn bộ nội dung file sau khi chỉnh sửa ...
452
564
  EOF
453
565
  \`\`\`
454
566
 
455
- 2. **Quy tắc:**
456
- - Luôn có mkdir -p
457
- - Dùng <<'EOF' (có quotes)
458
- - Chỉ include files thay đổi
459
- `;
567
+ ### 3. Chi tiết quan trọng
568
+ - **LUÔN** \`mkdir -p $(dirname "...")\` trước mỗi file
569
+ - Sử dụng \`<<'EOF'\` (có dấu nháy đơn) để tránh bash expansion
570
+ - Ghi đè hoàn toàn file bằng nội dung mới
571
+ - ✅ Tự động tạo file và thư mục cha nếu chưa tồn tại
572
+ - ✅ Đường dẫn giống với file mẫu đính kèm
573
+
574
+ ### 4. Example Output
575
+
576
+ \`\`\`bash
577
+ # Create/Update component file
578
+ mkdir -p $(dirname "src/components/Button/index.tsx")
579
+ cat <<'EOF' > src/components/Button/index.tsx
580
+ import React from 'react';
581
+
582
+ export const Button = () => {
583
+ return <button>Click me</button>;
584
+ };
585
+ EOF
586
+
587
+ # Create/Update styles
588
+ mkdir -p $(dirname "src/components/Button/styles.css")
589
+ cat <<'EOF' > src/components/Button/styles.css
590
+ .button {
591
+ padding: 10px 20px;
592
+ background: blue;
593
+ }
594
+ EOF
595
+ \`\`\`
596
+
597
+ ---
598
+
599
+ ## Integration với VG Coder CLI
600
+
601
+ Bash scripts được generate sẽ được thực thi qua:
602
+ \`\`\`bash
603
+ POST http://localhost:6868/api/execute
604
+ {
605
+ "bash": "mkdir -p $(dirname \\"src/...\\")\\\\ncat <<'EOF' > ..."
606
+ }
607
+ \`\`\`
608
+
609
+ API sẽ:
610
+ 1. ✅ Validate bash syntax trong \`.vg/temp-execute\`
611
+ 2. ✅ Execute tại working directory nếu syntax OK
612
+ 3. ✅ Trả về stdout/stderr/exitCode
613
+ 4. ✅ Auto cleanup temp directory
614
+
615
+ ---
616
+
617
+ ## Best Practices
460
618
 
619
+ ### DO ✅
620
+ - Luôn dùng \`mkdir -p $(dirname "...")\` trước mỗi file
621
+ - Sử dụng \`<<'EOF'\` để tránh variable expansion
622
+ - Ghi đè toàn bộ nội dung file
623
+ - Chỉ include files có thay đổi thực sự
624
+
625
+ ### DON'T ❌
626
+ - Không tạo file mà không tạo thư mục cha
627
+ - Không dùng \`<<EOF\` (thiếu quotes) nếu có \`$\` trong content
628
+ - Không include files không thay đổi
629
+ - Không dùng relative paths phức tạp`;
630
+
631
+ // Load system prompt on page load
461
632
  document.getElementById('prompt-text').textContent = SYSTEM_PROMPT;
462
633
 
463
634
  function toggleSystemPrompt() {
464
635
  const content = document.getElementById('system-prompt-content');
465
636
  const icon = document.getElementById('toggle-icon');
466
637
  content.classList.toggle('open');
467
- icon.style.transform = content.classList.contains('open') ? 'rotate(180deg)' : 'rotate(0deg)';
638
+ icon.classList.toggle('open');
639
+ }
640
+
641
+ function copySystemPrompt() {
642
+ const copyBtn = event.target.closest('.btn-copy');
643
+ const copyIcon = document.getElementById('copy-icon');
644
+ const copyText = document.getElementById('copy-text');
645
+
646
+ navigator.clipboard.writeText(SYSTEM_PROMPT).then(() => {
647
+ copyBtn.classList.add('copied');
648
+ copyIcon.textContent = '✓';
649
+ copyText.textContent = 'Copied';
650
+ showToast('Đã copy System Prompt', 'success');
651
+
652
+ setTimeout(() => {
653
+ copyBtn.classList.remove('copied');
654
+ copyIcon.textContent = '📋';
655
+ copyText.textContent = 'Copy System Prompt';
656
+ }, 2000);
657
+ }).catch(err => {
658
+ showToast('Lỗi copy: ' + err.message, 'error');
659
+ });
468
660
  }
469
661
 
470
662
  function showResponse(elementId, data, isError = false) {
471
663
  const el = document.getElementById(elementId);
472
664
  el.className = 'response show ' + (isError ? 'error' : 'success');
473
-
474
- // Format nice JSON
475
- if (typeof data === 'object') {
476
- if (data.stdout) data.stdout = data.stdout.trim();
477
- if (data.stderr) data.stderr = data.stderr.trim();
478
- }
479
-
480
665
  el.innerHTML = '<pre>' + JSON.stringify(data, null, 2) + '</pre>';
481
666
  }
482
667
 
483
668
  function showLoading(button, originalText) {
484
669
  button.disabled = true;
485
- button.innerHTML = '<div class="loading-spinner"></div>';
670
+ button.innerHTML = '<span class="loading"></span>';
486
671
  button.dataset.originalText = originalText;
487
672
  }
488
673
 
489
674
  function resetButton(button) {
490
675
  button.disabled = false;
491
- button.innerHTML = button.dataset.originalText;
492
- }
493
-
494
- function showToast(message, type = 'success') {
495
- const toast = document.getElementById('toast');
496
- let icon = type === 'success' ? '✅' : (type === 'error' ? '❌' : 'ℹ️');
497
- toast.innerHTML = `${icon} ${message}`;
498
- toast.classList.add('show');
499
- setTimeout(() => toast.classList.remove('show'), 3000);
500
- }
501
-
502
- // --- ROBUST COPY FUNCTION (MODERN + FALLBACK) ---
503
- function fallbackCopyTextToClipboard(text) {
504
- var textArea = document.createElement("textarea");
505
- textArea.value = text;
506
-
507
- // Ensure textarea is not visible but part of DOM
508
- textArea.style.position = "fixed";
509
- textArea.style.left = "-9999px";
510
- textArea.style.top = "0";
511
- document.body.appendChild(textArea);
512
-
513
- textArea.focus();
514
- textArea.select();
515
-
516
- try {
517
- var successful = document.execCommand('copy');
518
- return successful;
519
- } catch (err) {
520
- console.error('Fallback: Oops, unable to copy', err);
521
- return false;
522
- } finally {
523
- document.body.removeChild(textArea);
524
- }
525
- }
526
-
527
- async function copyToClipboard(text, btnElement, successIconId, successTextId) {
528
- if (!text) return showToast('Nothing to copy', 'error');
529
-
530
- const updateUI = () => {
531
- if (btnElement) {
532
- btnElement.classList.add('copied');
533
- const icon = document.getElementById(successIconId);
534
- const label = document.getElementById(successTextId);
535
-
536
- const originalIcon = icon.textContent;
537
- const originalLabel = label.textContent;
538
-
539
- icon.textContent = '✓';
540
- label.textContent = 'Copied';
541
-
542
- setTimeout(() => {
543
- btnElement.classList.remove('copied');
544
- icon.textContent = originalIcon;
545
- label.textContent = originalLabel;
546
- }, 2000);
547
- }
548
- showToast('Copied to clipboard!');
549
- };
550
-
551
- // Try Modern API first
552
- if (navigator.clipboard && navigator.clipboard.writeText) {
553
- try {
554
- await navigator.clipboard.writeText(text);
555
- updateUI();
556
- } catch (err) {
557
- console.warn('Modern copy failed, trying fallback', err);
558
- // Try fallback if modern API fails
559
- if (fallbackCopyTextToClipboard(text)) {
560
- updateUI();
561
- } else {
562
- showToast('Failed to copy', 'error');
563
- }
564
- }
565
- } else {
566
- // Use fallback immediately if API doesn't exist
567
- if (fallbackCopyTextToClipboard(text)) {
568
- updateUI();
569
- } else {
570
- showToast('Failed to copy', 'error');
571
- }
572
- }
573
- }
574
-
575
- // --- Logic Handlers ---
576
-
577
- function copySystemPrompt() {
578
- copyToClipboard(SYSTEM_PROMPT, event.target.closest('.btn'), 'copy-icon', 'copy-text');
676
+ const originalText = button.dataset.originalText;
677
+ button.innerHTML = originalText;
579
678
  }
580
679
 
581
680
  async function testAnalyze() {
582
681
  const btn = event.target.closest('.btn');
583
682
  const path = document.getElementById('analyze-path').value;
584
- const originalHTML = btn.innerHTML;
585
683
 
586
- showLoading(btn, originalHTML);
684
+ showLoading(btn, btn.innerHTML);
587
685
  try {
588
686
  const res = await fetch(`${API_BASE}/api/analyze`, {
589
687
  method: 'POST',
@@ -595,7 +693,7 @@ EOF
595
693
  const text = await res.text();
596
694
  lastAnalyzeResult = text;
597
695
 
598
- // Trigger download
696
+ // Download file
599
697
  const blob = new Blob([text], { type: 'text/plain' });
600
698
  const url = window.URL.createObjectURL(blob);
601
699
  const a = document.createElement('a');
@@ -603,34 +701,167 @@ EOF
603
701
  a.download = 'project.txt';
604
702
  a.click();
605
703
 
606
- showResponse('analyze-response', { success: true, files: text.split('===== FILE:').length - 1 });
607
- showToast('Analysis completed');
704
+ showResponse('analyze-response', {
705
+ success: true,
706
+ message: 'File downloaded!',
707
+ files: text.split('\n').filter(l => l.includes('===== FILE:')).length,
708
+ size: (text.length / 1024).toFixed(2) + ' KB'
709
+ });
710
+ showToast('Đã download file', 'success');
608
711
  } else {
609
712
  const data = await res.json();
610
713
  showResponse('analyze-response', data, true);
611
- showToast('Analysis failed', 'error');
714
+ showToast('Lỗi analyze', 'error');
612
715
  }
613
716
  } catch (err) {
614
717
  showResponse('analyze-response', { error: err.message }, true);
615
- showToast('Connection error', 'error');
718
+ showToast('Lỗi: ' + err.message, 'error');
616
719
  }
617
720
  resetButton(btn);
618
721
  }
619
722
 
620
723
  async function copyAnalyzeResult() {
621
- const btn = event.target.closest('.btn');
724
+ const copyBtn = event.target.closest('.btn-copy');
725
+ const copyIcon = document.getElementById('analyze-copy-icon');
726
+ const copyText = document.getElementById('analyze-copy-text');
727
+
622
728
  if (!lastAnalyzeResult) {
623
- // If not analyzed yet, try to analyze silently or warn
624
- showToast('Please analyze/download first', 'error');
625
- return;
729
+ // Fetch if not already analyzed
730
+ const path = document.getElementById('analyze-path').value;
731
+ showLoading(copyBtn, copyBtn.innerHTML);
732
+
733
+ try {
734
+ const res = await fetch(`${API_BASE}/api/analyze`, {
735
+ method: 'POST',
736
+ headers: { 'Content-Type': 'application/json' },
737
+ body: JSON.stringify({ path })
738
+ });
739
+
740
+ if (res.ok) {
741
+ lastAnalyzeResult = await res.text();
742
+ } else {
743
+ showToast('Lỗi analyze', 'error');
744
+ resetButton(copyBtn);
745
+ return;
746
+ }
747
+ } catch (err) {
748
+ showToast('Lỗi: ' + err.message, 'error');
749
+ resetButton(copyBtn);
750
+ return;
751
+ }
752
+ resetButton(copyBtn);
753
+ }
754
+
755
+ // Copy to clipboard using ClipboardItem
756
+ try {
757
+ const blob = new Blob([lastAnalyzeResult], { type: 'text/plain' });
758
+ const item = new ClipboardItem({ 'text/plain': blob });
759
+ await navigator.clipboard.write([item]);
760
+
761
+ copyBtn.classList.add('copied');
762
+ copyIcon.textContent = '✓';
763
+ copyText.textContent = 'Copied';
764
+ showToast('Đã copy project.txt', 'success');
765
+
766
+ setTimeout(() => {
767
+ copyBtn.classList.remove('copied');
768
+ copyIcon.textContent = '📋';
769
+ copyText.textContent = 'Copy Text';
770
+ }, 2000);
771
+ } catch (err) {
772
+ try {
773
+ await navigator.clipboard.writeText(lastAnalyzeResult);
774
+ copyBtn.classList.add('copied');
775
+ copyIcon.textContent = '✓';
776
+ copyText.textContent = 'Copied';
777
+ showToast('Đã copy project.txt', 'success');
778
+
779
+ setTimeout(() => {
780
+ copyBtn.classList.remove('copied');
781
+ copyIcon.textContent = '📋';
782
+ copyText.textContent = 'Copy Text';
783
+ }, 2000);
784
+ } catch (fallbackErr) {
785
+ showToast('Lỗi copy: ' + fallbackErr.message, 'error');
786
+ }
787
+ }
788
+ }
789
+
790
+ async function copyAsFile(filename, content) {
791
+ const blob = new Blob([content], {
792
+ type: "application/octet-stream"
793
+ });
794
+
795
+ const item = new ClipboardItem(
796
+ { [blob.type]: blob },
797
+ {
798
+ type: "application/octet-stream",
799
+ presentationStyle: "attachment",
800
+ name: filename
801
+ }
802
+ );
803
+
804
+ await navigator.clipboard.write([item]);
805
+ }
806
+
807
+ async function copyAnalyzeAsFile() {
808
+ const copyBtn = event.target.closest('.btn-copy');
809
+ const copyIcon = document.getElementById('analyze-file-icon');
810
+ const copyText = document.getElementById('analyze-file-text');
811
+
812
+ if (!lastAnalyzeResult) {
813
+ // Fetch if not already analyzed
814
+ const path = document.getElementById('analyze-path').value;
815
+ showLoading(copyBtn, copyBtn.innerHTML);
816
+
817
+ try {
818
+ const res = await fetch(`${API_BASE}/api/analyze`, {
819
+ method: 'POST',
820
+ headers: { 'Content-Type': 'application/json' },
821
+ body: JSON.stringify({ path })
822
+ });
823
+
824
+ if (res.ok) {
825
+ lastAnalyzeResult = await res.text();
826
+ } else {
827
+ showToast('Lỗi analyze', 'error');
828
+ resetButton(copyBtn);
829
+ return;
830
+ }
831
+ } catch (err) {
832
+ showToast('Lỗi: ' + err.message, 'error');
833
+ resetButton(copyBtn);
834
+ return;
835
+ }
836
+ resetButton(copyBtn);
837
+ }
838
+
839
+ try {
840
+ await copyAsFile("project.txt", lastAnalyzeResult);
841
+
842
+ copyBtn.classList.add('copied');
843
+ copyIcon.textContent = '✓';
844
+ copyText.textContent = 'Copied';
845
+ showToast('Đã copy file', 'success');
846
+
847
+ setTimeout(() => {
848
+ copyBtn.classList.remove('copied');
849
+ copyIcon.textContent = '📄';
850
+ copyText.textContent = 'Copy as File';
851
+ }, 2000);
852
+ } catch (err) {
853
+ showToast('Lỗi copy: ' + err.message, 'error');
626
854
  }
627
- copyToClipboard(lastAnalyzeResult, btn, 'analyze-copy-icon', 'analyze-copy-text');
628
855
  }
629
856
 
630
857
  async function testExecute() {
631
858
  const btn = event.target.closest('.btn');
632
859
  const bash = document.getElementById('execute-bash').value;
633
- if (!bash.trim()) return showToast('Script is empty', 'error');
860
+
861
+ if (!bash.trim()) {
862
+ showToast('Vui lòng nhập bash script', 'error');
863
+ return;
864
+ }
634
865
 
635
866
  showLoading(btn, btn.innerHTML);
636
867
  try {
@@ -640,41 +871,96 @@ EOF
640
871
  body: JSON.stringify({ bash })
641
872
  });
642
873
  const data = await res.json();
643
- showResponse('execute-response', data, !data.success);
644
- showToast(data.success ? 'Executed successfully' : 'Execution failed', data.success ? 'success' : 'error');
874
+ showResponse('execute-response', data, !res.ok || !data.success);
875
+
876
+ if (data.success) {
877
+ showToast('Thực thi thành công', 'success');
878
+ } else {
879
+ showToast('Thực thi thất bại', 'error');
880
+ }
645
881
  } catch (err) {
646
882
  showResponse('execute-response', { error: err.message }, true);
883
+ showToast('Lỗi: ' + err.message, 'error');
647
884
  }
648
885
  resetButton(btn);
649
886
  }
650
887
 
651
888
  async function executeFromClipboard() {
889
+ const btn = event.target.closest('.btn');
890
+
891
+ showLoading(btn, btn.innerHTML);
892
+
652
893
  try {
653
- const text = await navigator.clipboard.readText();
654
- if (!text.trim()) return showToast('Clipboard is empty', 'error');
894
+ const clipboardText = await navigator.clipboard.readText();
895
+
896
+ if (!clipboardText || !clipboardText.trim()) {
897
+ showToast('Clipboard trống!', 'error');
898
+ showResponse('execute-response', {
899
+ error: 'Clipboard is empty',
900
+ message: 'Please copy a bash script to clipboard first'
901
+ }, true);
902
+ resetButton(btn);
903
+ return;
904
+ }
655
905
 
656
- document.getElementById('execute-bash').value = text;
657
- showToast('Pasted from clipboard');
906
+ document.getElementById('execute-bash').value = clipboardText;
907
+
908
+ const res = await fetch(`${API_BASE}/api/execute`, {
909
+ method: 'POST',
910
+ headers: { 'Content-Type': 'application/json' },
911
+ body: JSON.stringify({ bash: clipboardText })
912
+ });
913
+ const data = await res.json();
914
+ showResponse('execute-response', data, !res.ok || !data.success);
915
+
916
+ if (data.success) {
917
+ showToast('Thực thi từ clipboard OK', 'success');
918
+ } else {
919
+ if (data.syntaxError) {
920
+ showToast('Lỗi syntax script', 'error');
921
+ } else {
922
+ showToast('Thực thi thất bại', 'error');
923
+ }
924
+ }
658
925
  } catch (err) {
659
- showToast('Clipboard permission denied', 'error');
926
+ if (err.name === 'NotAllowedError') {
927
+ showToast('Không có quyền clipboard', 'error');
928
+ } else {
929
+ showResponse('execute-response', { error: err.message }, true);
930
+ showToast('Lỗi: ' + err.message, 'error');
931
+ }
660
932
  }
933
+ resetButton(btn);
661
934
  }
662
935
 
663
- // Init Check
936
+ function showToast(message, type = 'success') {
937
+ const toast = document.getElementById('toast');
938
+ // Reset text content to remove potential icon junk
939
+ toast.textContent = message;
940
+ toast.className = `toast ${type}`;
941
+ toast.classList.add('show');
942
+
943
+ // Clear previous timeout if exists
944
+ if (toast.timeoutId) clearTimeout(toast.timeoutId);
945
+
946
+ toast.timeoutId = setTimeout(() => toast.classList.remove('show'), 3000);
947
+ }
948
+
949
+ // Check server status
664
950
  (async () => {
665
- const statusBadge = document.getElementById('status');
666
951
  try {
667
952
  const res = await fetch(`${API_BASE}/health`);
668
953
  if (res.ok) {
669
- statusBadge.textContent = '● Online';
670
- statusBadge.style.backgroundColor = 'var(--ios-green)';
954
+ document.getElementById('status').textContent = '● Online';
955
+ document.getElementById('status').style.background = 'rgba(52, 199, 89, 0.15)';
956
+ document.getElementById('status').style.color = 'var(--ios-green)';
671
957
  }
672
958
  } catch {
673
- statusBadge.textContent = '● Offline';
674
- statusBadge.style.backgroundColor = 'var(--ios-text-secondary)';
959
+ document.getElementById('status').textContent = '● Offline';
960
+ document.getElementById('status').style.background = 'rgba(255, 59, 48, 0.15)';
961
+ document.getElementById('status').style.color = 'var(--ios-red)';
675
962
  }
676
963
  })();
677
-
678
964
  </script>
679
965
  </body>
680
966
 
Binary file
Binary file