+19
app.js
+19
app.js
···
2
document.addEventListener("alpine:init", () => {
3
Alpine.data("app", () => ({
4
// State
5
serverUrl: "https://knot.srv.rbrt.fr",
6
isConnected: false,
7
status: {
···
34
35
// Initialization
36
init() {
37
// Make this component globally accessible for onclick handlers
38
window.appInstance = this;
39
···
65
66
// Restore from URL on load
67
this.restoreFromURL();
68
},
69
70
// Connection
···
2
document.addEventListener("alpine:init", () => {
3
Alpine.data("app", () => ({
4
// State
5
+
darkTheme: localStorage.getItem("darkTheme") !== "false",
6
serverUrl: "https://knot.srv.rbrt.fr",
7
isConnected: false,
8
status: {
···
35
36
// Initialization
37
init() {
38
+
// Apply saved theme
39
+
this.applyTheme();
40
+
41
// Make this component globally accessible for onclick handlers
42
window.appInstance = this;
43
···
69
70
// Restore from URL on load
71
this.restoreFromURL();
72
+
},
73
+
74
+
// Theme
75
+
toggleTheme() {
76
+
this.darkTheme = !this.darkTheme;
77
+
localStorage.setItem("darkTheme", this.darkTheme);
78
+
this.applyTheme();
79
+
},
80
+
81
+
applyTheme() {
82
+
if (this.darkTheme) {
83
+
document.body.classList.add("dark-theme");
84
+
} else {
85
+
document.body.classList.remove("dark-theme");
86
+
}
87
},
88
89
// Connection
+70
-6
index.html
+70
-6
index.html
···
22
<body>
23
<div class="container" x-data="app" x-init="init()">
24
<header>
25
-
<h1>KnotView</h1>
26
<div class="connection-panel">
27
<input
28
type="text"
···
129
<!-- Empty State -->
130
<div
131
x-show="!loading && !error && !state.currentRepo && view === 'empty'"
132
-
class="empty-state"
133
>
134
<svg
135
xmlns="http://www.w3.org/2000/svg"
···
144
d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z"
145
/>
146
</svg>
147
-
<h3>No Repository Selected</h3>
148
-
<p>
149
-
Connect to a server and select a repository to
150
-
browse its contents.
151
</p>
152
</div>
153
154
<!-- Users/Repos List -->
···
302
</div>
303
</main>
304
</div>
305
</div>
306
</body>
307
</html>
···
22
<body>
23
<div class="container" x-data="app" x-init="init()">
24
<header>
25
+
<div class="header-top">
26
+
<h1>KnotView</h1>
27
+
<button class="theme-toggle" @click="toggleTheme">
28
+
<span x-show="!darkTheme">๐</span>
29
+
<span x-show="darkTheme">โ๏ธ</span>
30
+
<span x-text="darkTheme ? 'Light' : 'Dark'"></span>
31
+
</button>
32
+
</div>
33
<div class="connection-panel">
34
<input
35
type="text"
···
136
<!-- Empty State -->
137
<div
138
x-show="!loading && !error && !state.currentRepo && view === 'empty'"
139
+
class="welcome-hero"
140
>
141
<svg
142
xmlns="http://www.w3.org/2000/svg"
···
151
d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z"
152
/>
153
</svg>
154
+
<h2>Welcome to KnotView</h2>
155
+
<p class="subtitle">
156
+
A web-based repository browser for Tangled Knots.
157
+
Browse, explore, and download content from Knot
158
+
servers.
159
</p>
160
+
161
+
<div class="feature-list">
162
+
<div class="feature-item">
163
+
<h4>๐๏ธ Browse Repositories</h4>
164
+
<p>
165
+
Navigate through files and folders with an
166
+
intuitive interface
167
+
</p>
168
+
</div>
169
+
<div class="feature-item">
170
+
<h4>๐ฟ Branch Support</h4>
171
+
<p>
172
+
Switch between different branches seamlessly
173
+
</p>
174
+
</div>
175
+
<div class="feature-item">
176
+
<h4>๐ File Viewer</h4>
177
+
<p>
178
+
View files with syntax highlighting and
179
+
markdown rendering
180
+
</p>
181
+
</div>
182
+
<div class="feature-item">
183
+
<h4>๐ฆ Download Archives</h4>
184
+
<p>Download entire repositories as archives</p>
185
+
</div>
186
+
</div>
187
+
188
+
<div class="getting-started">
189
+
<h3>Getting Started</h3>
190
+
<ol>
191
+
<li>
192
+
Enter your Knot server URL in the input
193
+
above
194
+
</li>
195
+
<li>
196
+
Click "Connect" to connect to the server
197
+
</li>
198
+
<li>Select a repository from the list</li>
199
+
<li>
200
+
Browse files, switch branches, and explore
201
+
even if Tangled AppView is down!
202
+
</li>
203
+
</ol>
204
+
</div>
205
</div>
206
207
<!-- Users/Repos List -->
···
355
</div>
356
</main>
357
</div>
358
+
<footer>
359
+
<p>
360
+
<a
361
+
href="https://tangled.org/julien.rbrt.fr/knotview"
362
+
target="_blank"
363
+
rel="noopener noreferrer"
364
+
>
365
+
Fork me on Tangled.
366
+
</a>
367
+
</p>
368
+
</footer>
369
</div>
370
</body>
371
</html>
+2
readme.md
+2
readme.md
···
9
Once PR [#903](https://tangled.org/tangled.org/core/pulls/903) is merged in tangled/core, the UX of browsing a knot will be better.
10
In the meantime, you need to know the did and the repo name of the repository you want to browse.
11
12
+
In case of CORS issues, configure the Knot server to allow CORS requests from the domain hosting KnotView. Or simply use a browser extension to disable CORS check on the domain.
13
+
14
## License
15
16
[MIT](license)
+353
-116
styles.css
+353
-116
styles.css
···
4
box-sizing: border-box;
5
}
6
7
body {
8
-
font-family:
9
-
-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu,
10
-
Cantarell, sans-serif;
11
-
background: #f1f5f9;
12
-
color: #1e293b;
13
font-size: 14px;
14
line-height: 1.5;
15
}
16
17
.container {
···
21
}
22
23
header {
24
-
background: white;
25
padding: 24px;
26
border-radius: 8px;
27
-
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
28
margin-bottom: 20px;
29
-
border: 1px solid #e2e8f0;
30
}
31
32
h1 {
33
font-size: 24px;
34
-
margin-bottom: 20px;
35
-
color: #0f172a;
36
font-weight: 600;
37
}
38
39
.connection-panel {
40
display: flex;
41
gap: 12px;
···
47
flex: 1;
48
min-width: 300px;
49
padding: 10px 14px;
50
-
border: 1px solid #cbd5e1;
51
border-radius: 6px;
52
font-size: 14px;
53
-
background: white;
54
transition: all 0.15s;
55
}
56
57
.connection-panel input:focus {
58
outline: none;
59
-
border-color: #3b82f6;
60
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
61
}
62
63
button {
64
padding: 10px 20px;
65
-
background: #3b82f6;
66
color: white;
67
border: none;
68
border-radius: 6px;
···
70
font-size: 14px;
71
font-weight: 500;
72
transition: all 0.15s;
73
-
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
74
}
75
76
button:hover {
77
-
background: #2563eb;
78
}
79
80
button:active {
···
82
}
83
84
button:disabled {
85
-
background: #94a3b8;
86
cursor: not-allowed;
87
transform: none;
88
}
89
90
button.secondary {
91
-
background: white;
92
-
color: #475569;
93
-
border: 1px solid #cbd5e1;
94
}
95
96
button.secondary:hover {
97
-
background: #f8fafc;
98
}
99
100
.status {
101
-
padding: 10px 14px;
102
border-radius: 6px;
103
font-size: 13px;
104
display: none;
105
-
border: 1px solid transparent;
106
}
107
108
.status.success {
109
-
background: #dcfce7;
110
-
color: #166534;
111
-
border-color: #bbf7d0;
112
}
113
114
.status.error {
115
-
background: #fee2e2;
116
-
color: #991b1b;
117
-
border-color: #fecaca;
118
}
119
120
.main-content {
···
125
126
.sidebar {
127
width: 300px;
128
-
background: white;
129
border-radius: 8px;
130
-
border: 1px solid #e2e8f0;
131
-
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
132
flex-shrink: 0;
133
}
134
···
136
font-size: 16px;
137
font-weight: 600;
138
padding: 16px 20px;
139
-
border-bottom: 1px solid #e2e8f0;
140
-
color: #0f172a;
141
display: flex;
142
align-items: center;
143
justify-content: space-between;
···
145
146
.repo-info {
147
padding: 20px;
148
-
background: white;
149
-
border-radius: 8px;
150
-
border: 1px solid #e2e8f0;
151
-
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
152
-
margin-bottom: 20px;
153
}
154
155
.repo-info h3 {
156
font-size: 14px;
157
font-weight: 600;
158
margin-bottom: 12px;
159
-
color: #64748b;
160
}
161
162
.repo-info .label {
163
font-size: 12px;
164
-
color: #64748b;
165
margin-bottom: 4px;
166
font-weight: 500;
167
text-transform: uppercase;
···
170
171
.repo-info .value {
172
font-size: 13px;
173
-
color: #1e293b;
174
margin-bottom: 12px;
175
-
font-family: monospace;
176
}
177
178
.clone-url {
179
display: flex;
180
align-items: center;
181
gap: 8px;
182
-
background: #f8fafc;
183
padding: 8px 12px;
184
border-radius: 6px;
185
-
border: 1px solid #e2e8f0;
186
margin-bottom: 12px;
187
}
188
189
.clone-url code {
190
flex: 1;
191
font-size: 12px;
192
-
color: #475569;
193
overflow: hidden;
194
text-overflow: ellipsis;
195
}
···
201
}
202
203
.copy-btn:hover {
204
-
background: #2563eb;
205
}
206
207
.branches-section {
208
-
border-top: 1px solid #e2e8f0;
209
}
210
211
.branch-list {
···
217
padding: 10px 20px;
218
cursor: pointer;
219
transition: background 0.15s;
220
-
border-bottom: 1px solid #f1f5f9;
221
font-size: 13px;
222
-
color: #475569;
223
display: flex;
224
align-items: center;
225
gap: 8px;
226
}
227
228
.branch-item:hover {
229
-
background: #f8fafc;
230
}
231
232
.branch-item.active {
233
-
background: #eff6ff;
234
-
color: #1e40af;
235
font-weight: 500;
236
}
237
238
.viewer {
239
flex: 1;
240
-
background: white;
241
border-radius: 8px;
242
-
border: 1px solid #e2e8f0;
243
-
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
244
overflow: hidden;
245
}
246
247
.breadcrumb {
248
padding: 16px 20px;
249
-
border-bottom: 1px solid #e2e8f0;
250
font-size: 13px;
251
-
color: #64748b;
252
-
background: #f8fafc;
253
display: flex;
254
align-items: center;
255
flex-wrap: wrap;
256
}
257
258
.breadcrumb a {
259
-
color: #3b82f6;
260
text-decoration: none;
261
transition: color 0.15s;
262
cursor: pointer;
263
}
264
265
.breadcrumb a:hover {
266
-
color: #2563eb;
267
text-decoration: underline;
268
}
269
···
272
}
273
274
.breadcrumb .current {
275
-
color: #1e293b;
276
font-weight: 500;
277
}
278
···
287
display: flex;
288
align-items: center;
289
gap: 12px;
290
-
border-bottom: 1px solid #f1f5f9;
291
cursor: pointer !important;
292
}
293
···
296
}
297
298
.file-item:hover {
299
-
background: #f8fafc;
300
}
301
302
.file-icon {
303
width: 20px;
304
height: 20px;
305
flex-shrink: 0;
306
-
color: #64748b;
307
cursor: pointer;
308
}
309
310
.file-name {
311
flex: 1;
312
-
color: #1e293b;
313
font-size: 14px;
314
cursor: pointer;
315
}
316
317
.file-size {
318
-
color: #64748b;
319
font-size: 12px;
320
cursor: pointer;
321
}
···
323
.file-content {
324
padding: 0;
325
overflow-x: auto;
326
-
background: #0d1117;
327
-
font-family: "Monaco", "Menlo", "Ubuntu Mono", monospace;
328
font-size: 13px;
329
}
330
···
347
.line-numbers {
348
display: flex;
349
gap: 0;
350
-
background: #0d1117;
351
}
352
353
.line-numbers .numbers {
354
-
color: #6e7681;
355
text-align: right;
356
user-select: none;
357
min-width: 50px;
358
padding: 20px 16px 20px 20px;
359
-
border-right: 1px solid #30363d;
360
-
background: #0d1117;
361
line-height: 1.5;
362
}
363
···
369
.loading {
370
padding: 40px;
371
text-align: center;
372
-
color: #64748b;
373
}
374
375
.spinner {
376
width: 40px;
377
height: 40px;
378
margin: 0 auto 16px;
379
-
border: 3px solid #e2e8f0;
380
-
border-top-color: #3b82f6;
381
border-radius: 50%;
382
animation: spin 0.8s linear infinite;
383
}
···
397
width: 64px;
398
height: 64px;
399
margin: 0 auto 20px;
400
-
color: #cbd5e1;
401
display: block;
402
}
403
404
.empty-state h3 {
405
font-size: 18px;
406
-
color: #475569;
407
margin-bottom: 8px;
408
}
409
410
.empty-state p {
411
-
color: #64748b;
412
font-size: 14px;
413
}
414
415
.error-message {
416
padding: 40px;
417
text-align: center;
418
-
color: #991b1b;
419
-
background: #fee2e2;
420
margin: 20px;
421
border-radius: 8px;
422
-
border: 1px solid #fecaca;
423
}
424
425
.file-header {
426
padding: 16px 20px;
427
-
border-bottom: 1px solid #e2e8f0;
428
-
background: #f8fafc;
429
display: flex;
430
justify-content: space-between;
431
align-items: center;
···
433
434
.file-header h3 {
435
font-size: 15px;
436
-
color: #1e293b;
437
font-weight: 600;
438
}
439
···
454
.user-header {
455
font-size: 16px;
456
font-weight: 600;
457
-
color: #0f172a;
458
margin-bottom: 12px;
459
padding: 12px;
460
-
background: #f8fafc;
461
border-radius: 6px;
462
}
463
464
.repo-item {
465
padding: 12px;
466
margin-bottom: 8px;
467
-
background: white;
468
-
border: 1px solid #e2e8f0;
469
border-radius: 6px;
470
cursor: pointer;
471
transition: all 0.15s;
472
}
473
474
.repo-item:hover {
475
-
border-color: #3b82f6;
476
box-shadow: 0 2px 4px rgba(59, 130, 246, 0.1);
477
}
478
479
.repo-item strong {
480
display: block;
481
font-size: 14px;
482
-
color: #1e293b;
483
margin-bottom: 4px;
484
}
485
486
.repo-item small {
487
font-size: 12px;
488
-
color: #64748b;
489
-
font-family: monospace;
490
}
491
492
.markdown-content {
493
padding: 20px 40px;
494
-
background: white;
495
-
font-family:
496
-
-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu,
497
-
Cantarell, sans-serif;
498
font-size: 15px;
499
line-height: 1.6;
500
-
color: #1e293b;
501
}
502
503
.markdown-content h1,
···
510
margin-bottom: 16px;
511
font-weight: 600;
512
line-height: 1.25;
513
-
color: #0f172a;
514
}
515
516
.markdown-content h1 {
517
font-size: 2em;
518
padding-bottom: 0.3em;
519
-
border-bottom: 1px solid #e2e8f0;
520
}
521
522
.markdown-content h2 {
523
font-size: 1.5em;
524
padding-bottom: 0.3em;
525
-
border-bottom: 1px solid #e2e8f0;
526
}
527
528
.markdown-content h3 {
···
539
540
.markdown-content h6 {
541
font-size: 0.85em;
542
-
color: #64748b;
543
}
544
545
.markdown-content p {
···
562
padding: 0.2em 0.4em;
563
margin: 0;
564
font-size: 85%;
565
-
background: #f1f5f9;
566
border-radius: 6px;
567
-
font-family: "Monaco", "Menlo", "Ubuntu Mono", monospace;
568
-
color: #e11d48;
569
}
570
571
.markdown-content pre {
···
573
overflow: auto;
574
font-size: 85%;
575
line-height: 1.45;
576
-
background: #0d1117;
577
border-radius: 6px;
578
margin-bottom: 16px;
579
}
···
587
word-wrap: normal;
588
background: transparent;
589
border: 0;
590
-
color: #c9d1d9;
591
}
592
593
.markdown-content pre code.hljs {
···
596
597
.markdown-content blockquote {
598
padding: 0 1em;
599
-
color: #64748b;
600
-
border-left: 0.25em solid #cbd5e1;
601
margin: 0 0 16px 0;
602
}
603
···
620
.markdown-content table th,
621
.markdown-content table td {
622
padding: 6px 13px;
623
-
border: 1px solid #e2e8f0;
624
}
625
626
.markdown-content table th {
627
font-weight: 600;
628
-
background: #f8fafc;
629
}
630
631
.markdown-content table tr {
632
-
background: white;
633
-
border-top: 1px solid #e2e8f0;
634
}
635
636
.markdown-content table tr:nth-child(2n) {
637
-
background: #f8fafc;
638
}
639
640
.markdown-content img {
···
644
}
645
646
.markdown-content a {
647
-
color: #3b82f6;
648
text-decoration: none;
649
}
650
···
656
height: 0.25em;
657
padding: 0;
658
margin: 24px 0;
659
-
background-color: #e2e8f0;
660
border: 0;
661
}
662
663
@media (max-width: 768px) {
664
.main-content {
665
flex-direction: column;
666
}
667
668
.connection-panel {
669
flex-direction: column;
670
}
···
675
676
.markdown-content {
677
padding: 20px;
678
}
679
}
···
4
box-sizing: border-box;
5
}
6
7
+
:root {
8
+
/* Light theme colors */
9
+
--bg-primary: #f9fafb;
10
+
--bg-secondary: #ffffff;
11
+
--bg-tertiary: #f3f4f6;
12
+
--bg-hover: #f3f4f6;
13
+
--bg-active: #dbeafe;
14
+
15
+
--text-primary: #111827;
16
+
--text-secondary: #4b5563;
17
+
--text-tertiary: #6b7280;
18
+
--text-heading: #111827;
19
+
20
+
--border-primary: #e5e7eb;
21
+
--border-secondary: #d1d5db;
22
+
--border-light: #f3f4f6;
23
+
24
+
--accent-primary: #3b82f6;
25
+
--accent-hover: #2563eb;
26
+
--accent-light: #dbeafe;
27
+
28
+
--success-bg: #dcfce7;
29
+
--success-text: #166534;
30
+
--success-border: #bbf7d0;
31
+
32
+
--error-bg: #fee2e2;
33
+
--error-text: #991b1b;
34
+
--error-border: #fecaca;
35
+
36
+
--code-bg: #f3f4f6;
37
+
--code-text: #111827;
38
+
--code-block-bg: #1f2937;
39
+
--code-block-text: #e5e7eb;
40
+
--code-block-border: #374151;
41
+
--code-block-line-numbers: #9ca3af;
42
+
43
+
--shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05);
44
+
--shadow-md:
45
+
0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
46
+
}
47
+
48
+
body.dark-theme {
49
+
/* Dark theme colors */
50
+
--bg-primary: #111827;
51
+
--bg-secondary: #1f2937;
52
+
--bg-tertiary: #374151;
53
+
--bg-hover: #374151;
54
+
--bg-active: #1e3a8a;
55
+
56
+
--text-primary: #f3f4f6;
57
+
--text-secondary: #d1d5db;
58
+
--text-tertiary: #9ca3af;
59
+
--text-heading: #ffffff;
60
+
61
+
--border-primary: #374151;
62
+
--border-secondary: #4b5563;
63
+
--border-light: #374151;
64
+
65
+
--accent-primary: #3b82f6;
66
+
--accent-hover: #60a5fa;
67
+
--accent-light: #1e3a8a;
68
+
69
+
--success-bg: #14532d;
70
+
--success-text: #86efac;
71
+
--success-border: #166534;
72
+
73
+
--error-bg: #7f1d1d;
74
+
--error-text: #fecaca;
75
+
--error-border: #991b1b;
76
+
77
+
--code-bg: #374151;
78
+
--code-text: #d1d5db;
79
+
--code-block-bg: #1f2937;
80
+
--code-block-text: #e5e7eb;
81
+
--code-block-border: #4b5563;
82
+
--code-block-line-numbers: #9ca3af;
83
+
84
+
--shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.3);
85
+
--shadow-md:
86
+
0 4px 6px -1px rgb(0 0 0 / 0.4), 0 2px 4px -2px rgb(0 0 0 / 0.4);
87
+
}
88
+
89
body {
90
+
font-family: InterVariable, system-ui, sans-serif, ui-sans-serif;
91
+
background: var(--bg-primary);
92
+
color: var(--text-primary);
93
font-size: 14px;
94
line-height: 1.5;
95
+
transition:
96
+
background-color 0.3s ease,
97
+
color 0.3s ease;
98
}
99
100
.container {
···
104
}
105
106
header {
107
+
background: var(--bg-secondary);
108
padding: 24px;
109
border-radius: 8px;
110
+
box-shadow: var(--shadow-md);
111
+
margin-bottom: 20px;
112
+
border: 1px solid var(--border-primary);
113
+
}
114
+
115
+
.header-top {
116
+
display: flex;
117
+
justify-content: space-between;
118
+
align-items: center;
119
margin-bottom: 20px;
120
}
121
122
h1 {
123
font-size: 24px;
124
+
margin: 0;
125
+
color: var(--text-heading);
126
font-weight: 600;
127
}
128
129
+
.theme-toggle {
130
+
background: var(--bg-tertiary);
131
+
border: 1px solid var(--border-primary);
132
+
color: var(--text-primary);
133
+
padding: 8px 16px;
134
+
border-radius: 6px;
135
+
cursor: pointer;
136
+
font-size: 14px;
137
+
display: flex;
138
+
align-items: center;
139
+
gap: 8px;
140
+
transition: all 0.15s;
141
+
}
142
+
143
+
.theme-toggle:hover {
144
+
background: var(--bg-hover);
145
+
border-color: var(--border-secondary);
146
+
}
147
+
148
.connection-panel {
149
display: flex;
150
gap: 12px;
···
156
flex: 1;
157
min-width: 300px;
158
padding: 10px 14px;
159
+
border: 1px solid var(--border-secondary);
160
border-radius: 6px;
161
font-size: 14px;
162
+
background: var(--bg-secondary);
163
+
color: var(--text-primary);
164
transition: all 0.15s;
165
}
166
167
.connection-panel input:focus {
168
outline: none;
169
+
border-color: var(--accent-primary);
170
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
171
}
172
173
button {
174
padding: 10px 20px;
175
+
background: var(--accent-primary);
176
color: white;
177
border: none;
178
border-radius: 6px;
···
180
font-size: 14px;
181
font-weight: 500;
182
transition: all 0.15s;
183
+
box-shadow: var(--shadow-sm);
184
}
185
186
button:hover {
187
+
background: var(--accent-hover);
188
}
189
190
button:active {
···
192
}
193
194
button:disabled {
195
+
background: var(--text-tertiary);
196
cursor: not-allowed;
197
transform: none;
198
}
199
200
button.secondary {
201
+
background: var(--bg-secondary);
202
+
color: var(--text-secondary);
203
+
border: 1px solid var(--border-secondary);
204
}
205
206
button.secondary:hover {
207
+
background: var(--bg-hover);
208
}
209
210
.status {
211
+
padding: 12px 16px;
212
border-radius: 6px;
213
font-size: 13px;
214
+
border: 1px solid transparent;
215
+
margin-top: 12px;
216
+
font-weight: 500;
217
+
line-height: 1.5;
218
+
}
219
+
220
+
.status:empty {
221
display: none;
222
}
223
224
.status.success {
225
+
background: var(--success-bg);
226
+
color: var(--success-text);
227
+
border-color: var(--success-border);
228
}
229
230
.status.error {
231
+
background: var(--error-bg);
232
+
color: var(--error-text);
233
+
border-color: var(--error-border);
234
+
font-weight: 600;
235
+
}
236
+
237
+
.status.error::before {
238
+
content: "โ ๏ธ ";
239
+
margin-right: 4px;
240
+
}
241
+
242
+
.status.success::before {
243
+
content: "โ ";
244
+
margin-right: 4px;
245
}
246
247
.main-content {
···
252
253
.sidebar {
254
width: 300px;
255
+
background: var(--bg-secondary);
256
border-radius: 8px;
257
+
border: 1px solid var(--border-primary);
258
+
box-shadow: var(--shadow-md);
259
flex-shrink: 0;
260
}
261
···
263
font-size: 16px;
264
font-weight: 600;
265
padding: 16px 20px;
266
+
border-bottom: 1px solid var(--border-primary);
267
+
color: var(--text-heading);
268
display: flex;
269
align-items: center;
270
justify-content: space-between;
···
272
273
.repo-info {
274
padding: 20px;
275
}
276
277
.repo-info h3 {
278
font-size: 14px;
279
font-weight: 600;
280
margin-bottom: 12px;
281
+
color: var(--text-tertiary);
282
}
283
284
.repo-info .label {
285
font-size: 12px;
286
+
color: var(--text-tertiary);
287
margin-bottom: 4px;
288
font-weight: 500;
289
text-transform: uppercase;
···
292
293
.repo-info .value {
294
font-size: 13px;
295
+
color: var(--text-primary);
296
margin-bottom: 12px;
297
+
font-family: IBMPlexMono, ui-monospace, monospace;
298
}
299
300
.clone-url {
301
display: flex;
302
align-items: center;
303
gap: 8px;
304
+
background: var(--bg-tertiary);
305
padding: 8px 12px;
306
border-radius: 6px;
307
+
border: 1px solid var(--border-primary);
308
margin-bottom: 12px;
309
}
310
311
.clone-url code {
312
flex: 1;
313
font-size: 12px;
314
+
color: var(--text-secondary);
315
overflow: hidden;
316
text-overflow: ellipsis;
317
}
···
323
}
324
325
.copy-btn:hover {
326
+
background: var(--accent-hover);
327
}
328
329
.branches-section {
330
+
border-top: 1px solid var(--border-primary);
331
}
332
333
.branch-list {
···
339
padding: 10px 20px;
340
cursor: pointer;
341
transition: background 0.15s;
342
+
border-bottom: 1px solid var(--border-light);
343
font-size: 13px;
344
+
color: var(--text-secondary);
345
display: flex;
346
align-items: center;
347
gap: 8px;
348
}
349
350
.branch-item:hover {
351
+
background: var(--bg-hover);
352
}
353
354
.branch-item.active {
355
+
background: var(--accent-light);
356
+
color: var(--accent-primary);
357
font-weight: 500;
358
}
359
360
.viewer {
361
flex: 1;
362
+
background: var(--bg-secondary);
363
border-radius: 8px;
364
+
border: 1px solid var(--border-primary);
365
+
box-shadow: var(--shadow-md);
366
overflow: hidden;
367
}
368
369
.breadcrumb {
370
padding: 16px 20px;
371
+
border-bottom: 1px solid var(--border-primary);
372
font-size: 13px;
373
+
color: var(--text-tertiary);
374
+
background: var(--bg-tertiary);
375
display: flex;
376
align-items: center;
377
flex-wrap: wrap;
378
}
379
380
.breadcrumb a {
381
+
color: var(--accent-primary);
382
text-decoration: none;
383
transition: color 0.15s;
384
cursor: pointer;
385
}
386
387
.breadcrumb a:hover {
388
+
color: var(--accent-hover);
389
text-decoration: underline;
390
}
391
···
394
}
395
396
.breadcrumb .current {
397
+
color: var(--text-primary);
398
font-weight: 500;
399
}
400
···
409
display: flex;
410
align-items: center;
411
gap: 12px;
412
+
border-bottom: 1px solid var(--border-light);
413
cursor: pointer !important;
414
}
415
···
418
}
419
420
.file-item:hover {
421
+
background: var(--bg-hover);
422
}
423
424
.file-icon {
425
width: 20px;
426
height: 20px;
427
flex-shrink: 0;
428
+
color: var(--text-tertiary);
429
cursor: pointer;
430
}
431
432
.file-name {
433
flex: 1;
434
+
color: var(--text-primary);
435
font-size: 14px;
436
cursor: pointer;
437
}
438
439
.file-size {
440
+
color: var(--text-tertiary);
441
font-size: 12px;
442
cursor: pointer;
443
}
···
445
.file-content {
446
padding: 0;
447
overflow-x: auto;
448
+
background: var(--code-block-bg);
449
+
font-family: IBMPlexMono, Monaco, Menlo, monospace;
450
font-size: 13px;
451
}
452
···
469
.line-numbers {
470
display: flex;
471
gap: 0;
472
+
background: var(--code-block-bg);
473
}
474
475
.line-numbers .numbers {
476
+
color: var(--code-block-line-numbers);
477
text-align: right;
478
user-select: none;
479
min-width: 50px;
480
padding: 20px 16px 20px 20px;
481
+
border-right: 1px solid var(--code-block-border);
482
+
background: var(--code-block-bg);
483
line-height: 1.5;
484
}
485
···
491
.loading {
492
padding: 40px;
493
text-align: center;
494
+
color: var(--text-tertiary);
495
}
496
497
.spinner {
498
width: 40px;
499
height: 40px;
500
margin: 0 auto 16px;
501
+
border: 3px solid var(--border-primary);
502
+
border-top-color: var(--accent-primary);
503
border-radius: 50%;
504
animation: spin 0.8s linear infinite;
505
}
···
519
width: 64px;
520
height: 64px;
521
margin: 0 auto 20px;
522
+
color: var(--text-tertiary);
523
display: block;
524
}
525
526
.empty-state h3 {
527
font-size: 18px;
528
+
color: var(--text-secondary);
529
margin-bottom: 8px;
530
}
531
532
.empty-state p {
533
+
color: var(--text-tertiary);
534
+
font-size: 14px;
535
+
}
536
+
537
+
.welcome-hero {
538
+
padding: 80px 40px;
539
+
text-align: center;
540
+
max-width: 700px;
541
+
margin: 0 auto;
542
+
}
543
+
544
+
.welcome-hero svg {
545
+
width: 96px;
546
+
height: 96px;
547
+
margin: 0 auto 32px;
548
+
color: var(--accent-primary);
549
+
display: block;
550
+
}
551
+
552
+
.welcome-hero h2 {
553
+
font-size: 32px;
554
+
color: var(--text-heading);
555
+
margin-bottom: 16px;
556
+
font-weight: 700;
557
+
}
558
+
559
+
.welcome-hero .subtitle {
560
+
font-size: 18px;
561
+
color: var(--text-secondary);
562
+
margin-bottom: 48px;
563
+
line-height: 1.6;
564
+
}
565
+
566
+
.feature-list {
567
+
display: grid;
568
+
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
569
+
gap: 24px;
570
+
margin-top: 48px;
571
+
text-align: left;
572
+
}
573
+
574
+
.feature-item {
575
+
padding: 20px;
576
+
background: var(--bg-tertiary);
577
+
border-radius: 8px;
578
+
border: 1px solid var(--border-primary);
579
+
}
580
+
581
+
.feature-item h4 {
582
+
font-size: 16px;
583
+
color: var(--text-heading);
584
+
margin-bottom: 8px;
585
+
display: flex;
586
+
align-items: center;
587
+
gap: 8px;
588
+
}
589
+
590
+
.feature-item p {
591
font-size: 14px;
592
+
color: var(--text-tertiary);
593
+
line-height: 1.5;
594
+
margin: 0;
595
+
}
596
+
597
+
.getting-started {
598
+
margin-top: 48px;
599
+
padding: 24px;
600
+
background: var(--bg-tertiary);
601
+
border-radius: 8px;
602
+
border: 1px solid var(--border-primary);
603
+
text-align: left;
604
+
}
605
+
606
+
.getting-started h3 {
607
+
font-size: 18px;
608
+
color: var(--text-heading);
609
+
margin-bottom: 16px;
610
+
}
611
+
612
+
.getting-started ol {
613
+
margin-left: 20px;
614
+
color: var(--text-secondary);
615
+
}
616
+
617
+
.getting-started li {
618
+
margin-bottom: 8px;
619
+
line-height: 1.6;
620
}
621
622
.error-message {
623
padding: 40px;
624
text-align: center;
625
+
color: var(--error-text);
626
+
background: var(--error-bg);
627
margin: 20px;
628
border-radius: 8px;
629
+
border: 1px solid var(--error-border);
630
}
631
632
.file-header {
633
padding: 16px 20px;
634
+
border-bottom: 1px solid var(--border-primary);
635
+
background: var(--bg-tertiary);
636
display: flex;
637
justify-content: space-between;
638
align-items: center;
···
640
641
.file-header h3 {
642
font-size: 15px;
643
+
color: var(--text-primary);
644
font-weight: 600;
645
}
646
···
661
.user-header {
662
font-size: 16px;
663
font-weight: 600;
664
+
color: var(--text-heading);
665
margin-bottom: 12px;
666
padding: 12px;
667
+
background: var(--bg-tertiary);
668
border-radius: 6px;
669
}
670
671
.repo-item {
672
padding: 12px;
673
margin-bottom: 8px;
674
+
background: var(--bg-secondary);
675
+
border: 1px solid var(--border-primary);
676
border-radius: 6px;
677
cursor: pointer;
678
transition: all 0.15s;
679
}
680
681
.repo-item:hover {
682
+
border-color: var(--accent-primary);
683
box-shadow: 0 2px 4px rgba(59, 130, 246, 0.1);
684
}
685
686
.repo-item strong {
687
display: block;
688
font-size: 14px;
689
+
color: var(--text-primary);
690
margin-bottom: 4px;
691
}
692
693
.repo-item small {
694
font-size: 12px;
695
+
color: var(--text-tertiary);
696
+
font-family: IBMPlexMono, monospace;
697
}
698
699
.markdown-content {
700
padding: 20px 40px;
701
+
background: var(--bg-secondary);
702
+
font-family: InterVariable, system-ui, sans-serif;
703
font-size: 15px;
704
line-height: 1.6;
705
+
color: var(--text-primary);
706
}
707
708
.markdown-content h1,
···
715
margin-bottom: 16px;
716
font-weight: 600;
717
line-height: 1.25;
718
+
color: var(--text-heading);
719
}
720
721
.markdown-content h1 {
722
font-size: 2em;
723
padding-bottom: 0.3em;
724
+
border-bottom: 1px solid var(--border-primary);
725
}
726
727
.markdown-content h2 {
728
font-size: 1.5em;
729
padding-bottom: 0.3em;
730
+
border-bottom: 1px solid var(--border-primary);
731
}
732
733
.markdown-content h3 {
···
744
745
.markdown-content h6 {
746
font-size: 0.85em;
747
+
color: var(--text-tertiary);
748
}
749
750
.markdown-content p {
···
767
padding: 0.2em 0.4em;
768
margin: 0;
769
font-size: 85%;
770
+
background: var(--code-bg);
771
border-radius: 6px;
772
+
font-family: IBMPlexMono, Monaco, Menlo, monospace;
773
+
color: var(--code-text);
774
}
775
776
.markdown-content pre {
···
778
overflow: auto;
779
font-size: 85%;
780
line-height: 1.45;
781
+
background: var(--code-block-bg);
782
border-radius: 6px;
783
margin-bottom: 16px;
784
}
···
792
word-wrap: normal;
793
background: transparent;
794
border: 0;
795
+
color: var(--code-block-text);
796
}
797
798
.markdown-content pre code.hljs {
···
801
802
.markdown-content blockquote {
803
padding: 0 1em;
804
+
color: var(--text-tertiary);
805
+
border-left: 0.25em solid var(--border-secondary);
806
margin: 0 0 16px 0;
807
}
808
···
825
.markdown-content table th,
826
.markdown-content table td {
827
padding: 6px 13px;
828
+
border: 1px solid var(--border-primary);
829
}
830
831
.markdown-content table th {
832
font-weight: 600;
833
+
background: var(--bg-tertiary);
834
}
835
836
.markdown-content table tr {
837
+
background: var(--bg-secondary);
838
+
border-top: 1px solid var(--border-primary);
839
}
840
841
.markdown-content table tr:nth-child(2n) {
842
+
background: var(--bg-tertiary);
843
}
844
845
.markdown-content img {
···
849
}
850
851
.markdown-content a {
852
+
color: var(--accent-primary);
853
text-decoration: none;
854
}
855
···
861
height: 0.25em;
862
padding: 0;
863
margin: 24px 0;
864
+
background-color: var(--border-primary);
865
border: 0;
866
}
867
868
+
footer {
869
+
padding: 20px;
870
+
text-align: center;
871
+
border-top: 1px solid var(--border-primary);
872
+
margin-top: 40px;
873
+
}
874
+
875
+
footer p {
876
+
margin: 0;
877
+
color: var(--text-secondary);
878
+
font-size: 0.9rem;
879
+
}
880
+
881
+
footer a {
882
+
color: var(--accent-primary);
883
+
text-decoration: none;
884
+
font-weight: 500;
885
+
}
886
+
887
+
footer a:hover {
888
+
text-decoration: underline;
889
+
color: var(--accent-hover);
890
+
}
891
+
892
@media (max-width: 768px) {
893
.main-content {
894
flex-direction: column;
895
}
896
897
+
.sidebar {
898
+
width: 100%;
899
+
}
900
+
901
.connection-panel {
902
flex-direction: column;
903
}
···
908
909
.markdown-content {
910
padding: 20px;
911
+
}
912
+
913
+
.welcome-hero {
914
+
padding: 40px 20px;
915
}
916
}