Compare changes

Choose any two refs to compare.

-9
README.md
··· 1 - # KnotView 2 - 3 - A lightweight web-based browser for exploring Git repositories hosted on KnotServer instances. 4 - 5 - Selfhost it, or use the hosted version at [`knotview.srv.rbrt.fr`](https://knotview.srv.rbrt.fr) 6 - 7 - ## License 8 - 9 - [MIT](license)
···
+20 -1
app.js
··· 2 document.addEventListener("alpine:init", () => { 3 Alpine.data("app", () => ({ 4 // State 5 - serverUrl: window.location.origin, 6 isConnected: false, 7 status: { 8 message: "", ··· 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: { 9 message: "", ··· 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
··· 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>
+16
readme.md
···
··· 1 + # KnotView 2 + 3 + A lightweight web-based browser for exploring Git repositories hosted on KnotServer instances. 4 + 5 + Selfhost it, or use the hosted version at [`knotview.srv.rbrt.fr`](https://knotview.srv.rbrt.fr) 6 + 7 + ## Note 8 + 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
··· 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 }