codenct-legacy.html (88057B)
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 6 <title>NCT Legacy Code View</title> 7 <!-- Link to Cascadia Code font from CDN --> 8 <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@xz/fonts@1/serve/cascadia-code.min.css"> 9 <style> 10 /* Base styling for the entire page */ 11 body { 12 background-color: black; 13 margin: 0; 14 padding: 20px; 15 font-family: 'Cascadia Code', 'Fira Code', 'Roboto Mono', monospace; 16 color: white; 17 display: flex; 18 flex-direction: column; 19 align-items: center; 20 min-height: 100vh; 21 } 22 23 /* --- CUSTOM SCROLLBAR --- */ 24 ::-webkit-scrollbar { width: 8px; height: 8px; } 25 ::-webkit-scrollbar-track { background: #000; } 26 ::-webkit-scrollbar-thumb { background: #333; border-radius: 4px; } 27 ::-webkit-scrollbar-thumb:hover { background: #1abc9c; } 28 29 h1 { 30 color: #f0f0f0; 31 text-align: center; 32 margin-bottom: 20px; 33 } 34 35 /* Container for the tabs */ 36 .tab-container { 37 background-color: #1a1a1a; 38 color: #f0f0f0; 39 padding: 15px; 40 border-radius: 8px; 41 width: 100%; 42 max-width: 1000px; 43 box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4); 44 position: relative; 45 overflow: hidden; 46 border: 1px solid #333; 47 } 48 49 /* Tab buttons styling */ 50 .tabs { 51 display: flex; 52 justify-content: center; 53 margin-bottom: 20px; 54 border-bottom: 1px solid #333; 55 } 56 57 .tab-button { 58 background-color: transparent; 59 border: none; 60 padding: 10px 20px; 61 font-size: 1.2em; 62 color: #f0f0f0; 63 cursor: pointer; 64 transition: color 0.3s ease, background-color 0.3s ease, transform 0.3s ease; 65 position: relative; 66 overflow: hidden; 67 border-radius: 5px 5px 0 0; 68 margin: 0 5px; 69 } 70 71 .tab-button:hover { 72 color: #1abc9c; 73 transform: translateY(-2px); 74 } 75 76 .tab-button.active { 77 color: #1abc9c; 78 font-weight: bold; 79 background-color: #333; 80 transform: translateY(-2px); 81 } 82 83 .tab-button.active::after { 84 content: ''; 85 position: absolute; 86 bottom: 0; 87 left: 0; 88 width: 100%; 89 height: 3px; 90 background-color: #1abc9c; 91 animation: tabUnderline 0.3s forwards; 92 } 93 94 @keyframes tabUnderline { 95 from { width: 0; } 96 to { width: 100%; } 97 } 98 99 /* Tab content styling */ 100 .tab-content { 101 display: none; 102 padding: 15px 0; 103 animation: fadeIn 0.5s ease-in-out; 104 } 105 106 .tab-content.active { 107 display: block; 108 } 109 110 @keyframes fadeIn { 111 from { opacity: 0; transform: translateY(10px); } 112 to { opacity: 1; transform: translateY(0); } 113 } 114 115 /* Header for Code Block */ 116 .code-header { 117 display: flex; 118 justify-content: space-between; 119 align-items: center; 120 background-color: #252525; 121 padding: 8px 15px; 122 border-radius: 8px 8px 0 0; 123 border: 1px solid #333; 124 border-bottom: none; 125 } 126 127 .file-info { font-size: 0.85em; color: #888; } 128 129 /* Copy Button Styling */ 130 .copy-button { 131 background: transparent; 132 border: 1px solid #444; 133 color: #ccc; 134 padding: 5px 12px; 135 border-radius: 4px; 136 font-size: 0.8em; 137 cursor: pointer; 138 transition: 0.2s; 139 display: flex; 140 align-items: center; 141 gap: 6px; 142 } 143 144 .copy-button:hover { 145 border-color: #1abc9c; 146 color: #1abc9c; 147 } 148 149 .copy-button.success { 150 border-color: #2ecc71; 151 color: #2ecc71; 152 } 153 154 /* Styling for the code block container */ 155 .code-block-container { 156 background-color: #1a1a1a; 157 border: 1px solid #333; 158 border-radius: 0 0 8px 8px; 159 overflow: hidden; 160 } 161 162 .code-block { 163 background-color: #1a1a1a; 164 color: #f0f0f0; 165 padding: 15px; 166 overflow-x: auto; 167 max-height: 70vh; 168 font-size: 0.95em; 169 line-height: 1.5; 170 tab-size: 4; 171 white-space: pre; 172 margin: 0; 173 } 174 175 /* Download section styling */ 176 .download-item { 177 background-color: #2a2a2a; 178 padding: 15px; 179 border-radius: 8px; 180 margin-bottom: 15px; 181 display: flex; 182 justify-content: space-between; 183 align-items: center; 184 border: 1px solid #333; 185 } 186 187 .download-info h3 { 188 color: #1abc9c; 189 margin: 0 0 5px 0; 190 } 191 192 .download-info p { 193 font-size: 0.9em; 194 color: #ccc; 195 margin: 0; 196 } 197 198 .download-button { 199 background-color: #007bff; 200 color: white; 201 border: none; 202 padding: 10px 15px; 203 border-radius: 5px; 204 cursor: pointer; 205 text-decoration: none; 206 font-size: 0.9em; 207 transition: background-color 0.3s ease, transform 0.2s ease; 208 } 209 210 .download-button:hover { 211 background-color: #0056b3; 212 transform: translateY(-1px); 213 } 214 </style> 215 </head> 216 <body> 217 218 <h1>--- NCT Legacy Explorer ---</h1> 219 220 <div class="tab-container"> 221 <div class="tabs"> 222 <button class="tab-button active" onclick="switchTab(event, 'code')">Code</button> 223 <button class="tab-button" onclick="switchTab(event, 'download')">Download</button> 224 </div> 225 226 <div id="code-tab-content" class="tab-content active"> 227 <div class="code-header"> 228 <span class="file-info">nct-legacy.html</span> 229 <button class="copy-button" onclick="copyToClipboard(this)"> 230 <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg> 231 Copy Code 232 </button> 233 </div> 234 <div class="code-block-container"> 235 <pre id="sourceCode" class="code-block"><!DOCTYPE html> 236 <html lang="en"> 237 <head> 238 <meta charset="UTF-8"> 239 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 240 <title>NCT v2.11 | Legacy Edition</title> 241 <link rel="icon" href="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIzMiIgaGVpZ2h0PSIzMiIgdmlld0JveD0iMCAwIDI0IDI0Ij48c3R5bGU+cGF0aHtmaWxsOiNEMEJDRkZ9QG1lZGlhIChwcmVmZXJzLWNvbG9yLXNjaGVtZTpkYXJrKXtwYXRoe2ZpbGw6I2ZmZnx9fTwvc3R5bGU+PHBhdGggZD0iTTcgMnYxMWgzdjlsNy0xMmgtNGw0LTh6Ii8+PC9zdmc+"> 242 <link rel="preconnect" href="https://fonts.googleapis.com"> 243 <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> 244 <link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;700&family=Google+Sans:wght@400;500;700&family=Roboto+Mono:wght@400;500&display=swap" rel="stylesheet"> 245 <script type="application/ld+json"> 246 { 247 "@context": "https://schema.org", 248 "@type": "WebApplication", 249 "name": "NCT v2.11 | Legacy Edition", 250 "url": "https://amit.is-a.dev/nct-legacy", 251 "image": "https://amit.is-a.dev/nctlogo.png", 252 "description": "Number conversion tool", 253 "applicationCategory": "EducationalApplication", 254 "operatingSystem": "Any" 255 } 256 </script> 257 <style> 258 /* --- MATERIAL DESIGN 3 TOKENS --- */ 259 :root { 260 --md-sys-color-background: #141218; 261 --md-sys-color-surface: #1D1B20; 262 --md-sys-color-surface-container: #211F26; 263 --md-sys-color-surface-container-high: #2B2930; 264 265 /* Primary */ 266 --md-sys-color-primary: #D0BCFF; 267 --md-sys-color-on-primary: #381E72; 268 --md-sys-color-primary-container: #4F378B; 269 --md-sys-color-on-primary-container: #EADDFF; 270 271 /* Text */ 272 --md-sys-color-on-surface: #E6E1E5; 273 --md-sys-color-on-surface-variant: #CAC4D0; 274 --md-sys-color-outline: #938F99; 275 276 /* Error */ 277 --md-sys-color-error: #F2B8B5; 278 --md-sys-color-error-container: #8C1D18; 279 280 /* Success/Accent */ 281 --md-sys-color-tertiary: #B6C4FF; 282 --md-sys-color-success: #4ade80; 283 284 /* Dimensions */ 285 --radius-xl: 28px; 286 --radius-l: 16px; 287 --radius-m: 12px; 288 --radius-s: 8px; 289 290 --font-main: 'Google Sans', 'Roboto', sans-serif; 291 --font-code: 'Roboto Mono', monospace; 292 293 /* Animation */ 294 --ease-elastic: cubic-bezier(0.4, 0.0, 0.2, 1); 295 } 296 297 /* --- CUSTOM SCROLLBAR --- */ 298 ::-webkit-scrollbar { width: 6px; height: 6px; } 299 ::-webkit-scrollbar-track { background: transparent; } 300 ::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.2); border-radius: 3px; } 301 ::-webkit-scrollbar-thumb:hover { background: rgba(255,255,255,0.4); } 302 303 * { margin: 0; padding: 0; box-sizing: border-box; -webkit-tap-highlight-color: transparent; } 304 305 body { 306 font-family: var(--font-main); 307 background-color: var(--md-sys-color-background); 308 color: var(--md-sys-color-on-surface); 309 min-height: 100vh; 310 display: flex; 311 flex-direction: column; 312 align-items: center; 313 overflow-x: hidden; 314 background-image: radial-gradient(circle at 50% 0%, rgba(208, 188, 255, 0.05) 0%, transparent 60%); 315 } 316 317 /* --- LAYOUT --- */ 318 .app-container { 319 width: 100%; 320 max-width: 1000px; 321 padding: 24px; 322 display: flex; 323 flex-direction: column; 324 gap: 24px; 325 flex-grow: 1; 326 } 327 328 /* --- HEADER & NAV --- */ 329 header { 330 display: flex; 331 flex-direction: column; 332 gap: 20px; 333 padding: 16px 0; 334 } 335 336 .header-top { 337 display: flex; 338 justify-content: space-between; 339 align-items: center; 340 } 341 342 .logo { 343 font-size: 1.75rem; 344 font-weight: 500; 345 color: var(--md-sys-color-on-surface); 346 display: flex; 347 align-items: center; 348 gap: 12px; 349 } 350 351 .logo span { color: var(--md-sys-color-primary); } 352 353 /* Navigation Tabs with Animation */ 354 .nav-tabs { 355 position: relative; 356 display: flex; 357 background: var(--md-sys-color-surface-container); 358 padding: 4px; 359 border-radius: 32px; 360 width: fit-content; 361 margin: 0 auto; 362 border: 1px solid rgba(255,255,255,0.05); 363 } 364 365 .nav-glider { 366 position: absolute; 367 top: 4px; 368 left: 4px; 369 height: calc(100% - 8px); 370 width: 110px; 371 background: var(--md-sys-color-primary-container); 372 border-radius: 28px; 373 transition: transform 0.3s var(--ease-elastic); 374 z-index: 1; 375 } 376 377 .nav-tab { 378 position: relative; 379 padding: 10px 24px; 380 border-radius: 24px; 381 color: var(--md-sys-color-on-surface-variant); 382 font-weight: 500; 383 cursor: pointer; 384 transition: color 0.2s; 385 border: none; 386 background: none; 387 z-index: 2; 388 width: 110px; 389 } 390 391 .nav-tab.active { color: var(--md-sys-color-on-primary-container); } 392 393 .header-actions { 394 display: flex; 395 gap: 10px; 396 } 397 398 .tools-btn { 399 background: transparent; 400 border: 1px solid var(--md-sys-color-outline); 401 color: var(--md-sys-color-on-surface); 402 padding: 8px 16px; 403 border-radius: var(--radius-l); 404 cursor: pointer; 405 font-weight: 500; 406 display: flex; 407 align-items: center; 408 gap: 8px; 409 transition: transform 0.1s; 410 } 411 .tools-btn:active { transform: scale(0.95); } 412 .tools-btn:hover { background: rgba(255,255,255,0.05); } 413 414 /* --- SECTIONS --- */ 415 .app-section { 416 display: none; 417 flex-direction: column; 418 gap: 24px; 419 animation: fadeIn 0.3s ease-out; 420 } 421 .app-section.active { display: flex; } 422 423 @keyframes fadeIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } } 424 425 /* --- CONVERTER STAGE --- */ 426 .converter-stage { 427 display: grid; 428 grid-template-columns: 1fr 64px 1fr; 429 gap: 16px; 430 align-items: stretch; 431 position: relative; 432 } 433 434 .material-card { 435 background-color: var(--md-sys-color-surface-container); 436 border-radius: var(--radius-xl); 437 padding: 24px; 438 display: flex; 439 flex-direction: column; 440 height: 380px; 441 position: relative; 442 transition: transform 0.4s var(--ease-elastic), box-shadow 0.2s; 443 border: 1px solid transparent; 444 z-index: 1; 445 } 446 447 .material-card:focus-within { 448 background-color: var(--md-sys-color-surface-container-high); 449 border-color: var(--md-sys-color-outline); 450 } 451 452 /* CARD HEADER & CUSTOM DROPDOWN */ 453 .card-header { 454 display: flex; 455 justify-content: space-between; 456 align-items: center; 457 margin-bottom: 20px; 458 border-bottom: 1px solid var(--md-sys-color-outline); 459 padding-bottom: 12px; 460 z-index: 10; 461 } 462 463 .input-label { 464 font-size: 0.875rem; 465 font-weight: 500; 466 color: var(--md-sys-color-primary); 467 text-transform: uppercase; 468 letter-spacing: 1px; 469 } 470 471 /* Custom Dropdown Styles */ 472 .custom-select-wrapper { 473 position: relative; 474 min-width: 150px; 475 user-select: none; 476 } 477 .custom-select-trigger { 478 display: flex; 479 align-items: center; 480 justify-content: flex-end; 481 gap: 8px; 482 cursor: pointer; 483 color: var(--md-sys-color-on-surface); 484 font-weight: 500; 485 padding: 4px 0; 486 transition: color 0.2s; 487 } 488 .custom-select-trigger:hover { color: var(--md-sys-color-primary); } 489 .arrow { font-size: 0.7rem; transition: transform 0.3s; } 490 491 .custom-options { 492 position: absolute; 493 top: 120%; 494 right: 0; 495 background: var(--md-sys-color-surface-container-high); 496 border-radius: var(--radius-m); 497 box-shadow: 0 8px 16px rgba(0,0,0,0.4); 498 opacity: 0; 499 visibility: hidden; 500 pointer-events: none; 501 transform: translateY(-10px); 502 transition: all 0.2s var(--ease-elastic); 503 min-width: 180px; 504 overflow: hidden; 505 border: 1px solid rgba(255,255,255,0.1); 506 z-index: 1000; 507 } 508 .custom-select-wrapper.open .custom-options { 509 opacity: 1; visibility: visible; pointer-events: all; transform: translateY(0); 510 } 511 .custom-select-wrapper.open .arrow { transform: rotate(180deg); } 512 513 .custom-option { 514 padding: 12px 16px; 515 cursor: pointer; 516 color: var(--md-sys-color-on-surface-variant); 517 transition: 0.2s; 518 } 519 .custom-option:hover { background: rgba(255,255,255,0.05); color: var(--md-sys-color-on-surface); } 520 .custom-option.selected { color: var(--md-sys-color-primary); background: rgba(208, 188, 255, 0.1); } 521 522 /* INPUT AREA */ 523 .input-area { 524 flex-grow: 1; 525 background: transparent; 526 border: none; 527 color: var(--md-sys-color-on-surface); 528 font-size: 2.2rem; 529 font-family: var(--font-main); 530 resize: none; 531 outline: none; 532 line-height: 1.2; 533 z-index: 1; 534 } 535 .input-area::placeholder { color: var(--md-sys-color-on-surface-variant); opacity: 0.3; } 536 537 /* Error Chip */ 538 .error-chip { 539 position: absolute; 540 bottom: 80px; 541 left: 24px; 542 background: var(--md-sys-color-error-container); 543 color: var(--md-sys-color-error); 544 padding: 8px 16px; 545 border-radius: 8px; 546 font-size: 0.875rem; 547 display: flex; 548 align-items: center; 549 gap: 8px; 550 opacity: 0; 551 transform: translateY(10px); 552 transition: all 0.3s; 553 pointer-events: none; 554 } 555 .error-chip.visible { opacity: 1; transform: translateY(0); } 556 557 /* Card Footer */ 558 .card-footer { 559 display: flex; 560 justify-content: space-between; 561 align-items: center; 562 margin-top: auto; 563 } 564 .char-count { font-size: 0.75rem; color: var(--md-sys-color-on-surface-variant); } 565 566 /* Icons & Buttons */ 567 .icon-btn { 568 background: transparent; 569 border: none; 570 color: var(--md-sys-color-on-surface-variant); 571 width: 40px; height: 40px; 572 border-radius: 50%; 573 cursor: pointer; 574 display: flex; align-items: center; justify-content: center; 575 transition: 0.2s; 576 } 577 .icon-btn:hover { background: rgba(255,255,255,0.1); color: var(--md-sys-color-on-surface); transform: scale(1.1); } 578 .icon-btn:active { transform: scale(0.9); } 579 580 /* Copy Button Animation */ 581 .icon-btn.success svg { 582 animation: check-pop 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275); 583 color: var(--md-sys-color-success); 584 } 585 @keyframes check-pop { 586 0% { transform: scale(0); } 587 50% { transform: scale(1.3); } 588 100% { transform: scale(1); } 589 } 590 591 /* Trash Shake Animation */ 592 @keyframes trash-shake { 593 0% { transform: rotate(0deg); } 594 25% { transform: rotate(-15deg); } 595 50% { transform: rotate(15deg); } 596 75% { transform: rotate(-15deg); } 597 100% { transform: rotate(0deg); } 598 } 599 .icon-btn.shaking { 600 animation: trash-shake 0.4s ease-in-out; 601 color: var(--md-sys-color-error); 602 } 603 604 /* Swap Button */ 605 .swap-container { display: flex; justify-content: center; align-items: center; z-index: 2; } 606 .swap-fab { 607 width: 56px; height: 56px; 608 border-radius: 16px; 609 background-color: var(--md-sys-color-surface-container-high); 610 border: 1px solid var(--md-sys-color-outline); 611 color: var(--md-sys-color-on-surface); 612 cursor: pointer; 613 display: flex; align-items: center; justify-content: center; 614 transition: all 0.3s var(--ease-elastic); 615 box-shadow: 0 4px 8px rgba(0,0,0,0.2); 616 } 617 .swap-fab:hover { 618 background-color: var(--md-sys-color-primary); 619 color: var(--md-sys-color-on-primary); 620 transform: rotate(180deg) scale(1.1); 621 border-color: transparent; 622 } 623 624 /* --- SWAP ANIMATION CLASSES --- */ 625 .anim-move-right { transform: translateX(calc(100% + 16px)); } 626 .anim-move-left { transform: translateX(calc(-100% - 16px)); } 627 .anim-move-down { transform: translateY(calc(100% + 16px)); } 628 .anim-move-up { transform: translateY(calc(-100% - 16px)); } 629 630 /* --- DYNAMIC CALCULATOR --- */ 631 .calc-wrapper { 632 background: var(--md-sys-color-surface-container); 633 padding: 24px; 634 border-radius: var(--radius-xl); 635 max-width: 800px; 636 margin: 0 auto; 637 width: 100%; 638 } 639 640 .calc-header { 641 display: flex; 642 gap: 24px; 643 margin-bottom: 24px; 644 flex-wrap: wrap; 645 } 646 647 .calc-ctrl-group { 648 flex: 1; 649 min-width: 200px; 650 display: flex; 651 align-items: center; 652 gap: 12px; 653 background: rgba(0,0,0,0.2); 654 padding: 8px 16px; 655 border-radius: 12px; 656 } 657 .calc-ctrl-group label { 658 font-size: 0.8rem; 659 color: var(--md-sys-color-primary); 660 text-transform: uppercase; 661 white-space: nowrap; 662 margin: 0; 663 } 664 665 .calc-rows { 666 display: flex; 667 flex-direction: column; 668 gap: 12px; 669 margin-bottom: 24px; 670 } 671 672 .calc-row { 673 display: flex; 674 gap: 12px; 675 align-items: center; 676 animation: fadeIn 0.3s ease-out; 677 } 678 679 /* Simple Fade Out */ 680 .calc-row.removing { 681 opacity: 0; 682 transition: opacity 0.3s ease; 683 } 684 685 .calc-input-wrapper { 686 flex-grow: 1; 687 position: relative; 688 } 689 .calc-input { 690 width: 100%; 691 background: rgba(0,0,0,0.2); 692 border: 1px solid transparent; 693 color: var(--md-sys-color-on-surface); 694 padding: 16px; 695 border-radius: 12px; 696 font-family: var(--font-code); 697 font-size: 1.1rem; 698 outline: none; 699 transition: 0.2s; 700 } 701 .calc-input:focus { border-color: var(--md-sys-color-primary); background: rgba(0,0,0,0.4); } 702 .calc-input.invalid { border-color: var(--md-sys-color-error); } 703 704 .calc-remove-btn { 705 background: var(--md-sys-color-surface-container-high); 706 border: none; 707 color: var(--md-sys-color-error); 708 width: 40px; height: 40px; 709 border-radius: 8px; 710 cursor: pointer; 711 display: flex; align-items: center; justify-content: center; 712 transition: 0.2s; 713 } 714 .calc-remove-btn:hover { background: var(--md-sys-color-error-container); color: #fff; } 715 716 /* The Divider Area */ 717 .calc-actions { 718 display: flex; 719 align-items: center; 720 border-top: 1px solid var(--md-sys-color-outline); 721 padding-top: 20px; 722 margin-top: 20px; 723 } 724 725 .add-row-btn { 726 background: rgba(255,255,255,0.05); 727 border: 1px dashed var(--md-sys-color-outline); 728 color: var(--md-sys-color-on-surface-variant); 729 padding: 10px 20px; 730 border-radius: 12px; 731 cursor: pointer; 732 font-weight: 500; 733 width: fit-content; 734 text-align: left; 735 transition: 0.2s; 736 margin-bottom: 20px; /* Space before divider */ 737 } 738 .add-row-btn:hover { border-color: var(--md-sys-color-primary); color: var(--md-sys-color-primary); background: rgba(208, 188, 255, 0.05); } 739 740 .calc-result-area { 741 display: flex; 742 align-items: center; 743 gap: 20px; 744 margin-left: auto; /* Forces result to the right */ 745 } 746 747 .visualize-btn { 748 background: var(--md-sys-color-primary-container); 749 color: var(--md-sys-color-on-primary-container); 750 border: none; 751 padding: 8px 16px; 752 border-radius: 20px; 753 font-size: 0.9rem; 754 font-weight: 500; 755 cursor: pointer; 756 display: none; /* Hidden by default */ 757 align-items: center; 758 gap: 6px; 759 transition: 0.2s; 760 animation: popIn 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275); 761 margin-right: auto; /* Keeps it left-aligned in actions */ 762 } 763 .visualize-btn:hover { filter: brightness(1.2); } 764 .visualize-btn:active { transform: scale(0.95); } 765 @keyframes popIn { from{transform: scale(0.8); opacity:0} to{transform: scale(1); opacity:1} } 766 767 .calc-result-display { text-align: right; } 768 .res-label { font-size: 0.9rem; color: var(--md-sys-color-on-surface-variant); } 769 .res-value { font-size: 2.2rem; color: var(--md-sys-color-primary); font-family: var(--font-code); margin-top: 5px; font-weight: 700; } 770 771 772 /* --- VISUALIZER PANEL --- */ 773 .calc-visualizer { 774 background: #111; 775 border-radius: var(--radius-m); 776 padding: 20px; 777 margin-top: 20px; 778 display: none; 779 border: 1px solid var(--md-sys-color-outline); 780 overflow-x: auto; 781 } 782 .calc-visualizer.active { display: block; animation: slideIn 0.4s var(--ease-elastic); } 783 784 .viz-grid { 785 display: grid; 786 font-family: var(--font-code); 787 font-size: 1.2rem; 788 color: var(--md-sys-color-on-surface); 789 gap: 4px; 790 } 791 .viz-row { display: contents; } 792 .viz-cell { 793 padding: 4px 8px; 794 text-align: center; 795 position: relative; 796 } 797 .viz-cell.carry { color: var(--md-sys-color-tertiary); font-size: 0.8rem; height: 20px; } 798 .viz-cell.operator { color: var(--md-sys-color-primary); } 799 .viz-cell.border-bottom { border-bottom: 2px solid var(--md-sys-color-outline); } 800 .viz-cell.active-col { background: rgba(208, 188, 255, 0.15); border-radius: 4px; } 801 .viz-cell.result { color: var(--md-sys-color-primary); font-weight: bold; } 802 803 .viz-info { 804 margin-top: 12px; 805 font-size: 0.9rem; 806 color: var(--md-sys-color-on-surface-variant); 807 font-family: var(--font-code); 808 min-height: 20px; 809 } 810 811 /* --- CONVERTER STEPS PANEL --- */ 812 .steps-container { 813 margin-top: 20px; 814 background: rgba(0,0,0,0.2); 815 border-radius: var(--radius-l); 816 padding: 20px; 817 border: 1px solid rgba(255,255,255,0.05); 818 display: none; 819 } 820 .steps-container.visible { display: block; animation: fadeIn 0.5s; } 821 822 .steps-header { 823 display: flex; 824 justify-content: space-between; 825 align-items: center; 826 margin-bottom: 12px; 827 border-bottom: 1px solid rgba(255,255,255,0.1); 828 padding-bottom: 8px; 829 } 830 831 .steps-content { 832 font-family: var(--font-code); 833 color: var(--md-sys-color-tertiary); 834 font-size: 0.9rem; 835 white-space: pre-wrap; 836 line-height: 1.5; 837 } 838 839 /* --- HISTORY TAPE --- */ 840 .history-section { 841 margin-top: 20px; 842 border-top: 1px solid var(--md-sys-color-outline); 843 padding-top: 20px; 844 } 845 .history-header { 846 display: flex; 847 justify-content: space-between; 848 align-items: center; 849 margin-bottom: 10px; 850 } 851 .history-list { 852 display: flex; flex-direction: column; gap: 8px; 853 max-height: 200px; overflow-y: auto; 854 transition: opacity 0.3s; 855 } 856 857 /* History Item Animation: Slide Out to Right */ 858 .history-list.clearing .history-item { 859 animation: slide-out-right 0.3s forwards; 860 } 861 @keyframes slide-out-right { 862 to { transform: translateX(50px); opacity: 0; } 863 } 864 865 .history-item { 866 display: flex; justify-content: space-between; 867 padding: 12px; 868 background: rgba(255,255,255,0.03); 869 border-radius: 8px; 870 font-family: var(--font-code); 871 font-size: 0.9rem; 872 cursor: pointer; 873 transition: 0.2s; 874 align-items: center; 875 } 876 .history-item:hover { background: rgba(255,255,255,0.08); transform: translateX(4px); } 877 .hist-val { color: var(--md-sys-color-on-surface); } 878 .hist-meta { font-size: 0.75rem; color: var(--md-sys-color-outline); margin-right: 10px; } 879 880 /* --- FOOTER --- */ 881 footer { 882 margin-top: 40px; 883 text-align: center; 884 padding: 20px; 885 color: var(--md-sys-color-on-surface-variant); 886 font-size: 0.85rem; 887 border-top: 1px solid var(--md-sys-color-outline); 888 width: 100%; 889 background: rgba(0,0,0,0.2); 890 } 891 .footer-content { 892 display: flex; 893 flex-direction: column; 894 gap: 8px; 895 } 896 .footer-links { 897 display: flex; 898 justify-content: center; 899 gap: 15px; 900 margin-top: 5px; 901 } 902 .footer-link { 903 color: var(--md-sys-color-primary); 904 text-decoration: none; 905 transition: 0.2s; 906 } 907 .footer-link:hover { text-decoration: underline; color: var(--md-sys-color-tertiary); } 908 909 /* --- MODAL (LEGACY & CHEATSHEET) --- */ 910 .modal-overlay { 911 position: fixed; 912 top: 0; left: 0; width: 100%; height: 100%; 913 background: rgba(0,0,0,0.7); 914 backdrop-filter: blur(4px); 915 z-index: 2000; 916 display: none; 917 justify-content: center; 918 align-items: center; 919 opacity: 0; 920 transition: opacity 0.3s; 921 } 922 .modal-overlay.open { display: flex; opacity: 1; } 923 924 .modal-content { 925 background: var(--md-sys-color-surface-container); 926 width: 90%; max-width: 500px; 927 border-radius: var(--radius-xl); 928 padding: 24px; 929 max-height: 80vh; 930 display: flex; 931 flex-direction: column; 932 box-shadow: 0 10px 30px rgba(0,0,0,0.5); 933 border: 1px solid var(--md-sys-color-outline); 934 transform: scale(0.9); 935 transition: transform 0.3s var(--ease-elastic); 936 } 937 .modal-overlay.open .modal-content { transform: scale(1); } 938 939 .modal-header { 940 display: flex; justify-content: space-between; align-items: center; 941 margin-bottom: 20px; 942 border-bottom: 1px solid var(--md-sys-color-outline); 943 padding-bottom: 12px; 944 flex-shrink: 0; 945 } 946 .modal-header h3 { margin: 0; color: var(--md-sys-color-primary); } 947 .close-modal { 948 background: none; border: none; color: var(--md-sys-color-on-surface); 949 font-size: 1.5rem; cursor: pointer; 950 } 951 952 .modal-body { 953 overflow-y: auto; 954 padding-right: 5px; /* Space for scrollbar */ 955 } 956 957 /* History Timeline */ 958 .timeline-item { 959 position: relative; 960 padding-left: 20px; 961 border-left: 2px solid var(--md-sys-color-outline); 962 margin-bottom: 20px; 963 } 964 .timeline-item::before { 965 content: ''; position: absolute; left: -6px; top: 0; 966 width: 10px; height: 10px; border-radius: 50%; 967 background: var(--md-sys-color-primary); 968 } 969 .version-tag { 970 font-size: 0.8rem; font-weight: bold; color: var(--md-sys-color-tertiary); 971 margin-bottom: 4px; display: inline-block; background: rgba(255,255,255,0.1); 972 padding: 2px 8px; border-radius: 4px; 973 } 974 .version-tag.current { background: var(--md-sys-color-primary); color: var(--md-sys-color-on-primary); } 975 .timeline-desc { font-size: 0.9rem; color: var(--md-sys-color-on-surface-variant); margin: 0; } 976 977 /* Sidebar (Cheat Sheet) */ 978 .sidebar { 979 position: fixed; top: 0; right: -320px; 980 width: 300px; height: 100%; 981 background: var(--md-sys-color-surface-container-high); 982 box-shadow: -5px 0 15px rgba(0,0,0,0.5); 983 padding: 24px; 984 transition: 0.3s ease; 985 z-index: 1000; 986 overflow-y: auto; 987 } 988 .sidebar.open { right: 0; } 989 .sidebar h3 { color: var(--md-sys-color-primary); margin-bottom: 16px; border-bottom: 1px solid rgba(255,255,255,0.1); padding-bottom: 8px; } 990 .cheat-table { width: 100%; border-collapse: collapse; font-family: var(--font-code); font-size: 0.8rem; } 991 .cheat-table th, .cheat-table td { text-align: left; padding: 6px; border-bottom: 1px solid rgba(255,255,255,0.05); } 992 .close-sidebar { 993 position: absolute; top: 10px; right: 10px; 994 background: none; border: none; color: var(--md-sys-color-on-surface); 995 font-size: 1.5rem; cursor: pointer; 996 } 997 998 /* --- RESPONSIVE --- */ 999 @media (max-width: 768px) { 1000 .converter-stage { grid-template-columns: 1fr; grid-template-rows: 1fr 64px 1fr; gap: 8px; } 1001 .material-card { height: 260px; } 1002 .swap-container { transform: rotate(90deg); } 1003 .swap-fab:hover { transform: rotate(180deg); } 1004 .header-top { flex-direction: column; gap: 10px; } 1005 .calc-header { flex-direction: column; gap: 10px; } 1006 .calc-result-area { margin-left: 0; justify-content: flex-end; width: 100%; } 1007 .visualize-btn { margin-right: 0; margin-bottom: 10px; } 1008 .calc-actions { flex-direction: column-reverse; align-items: stretch; } 1009 } 1010 </style> 1011 </head> 1012 <body> 1013 1014 <header class="app-container"> 1015 <div class="header-top"> 1016 <div class="logo"> 1017 <svg width="32" height="32" viewBox="0 0 24 24" fill="var(--md-sys-color-primary)"><path d="M7 2v11h3v9l7-12h-4l4-8z"/></svg> 1018 NCT <span>v2.11</span> 1019 </div> 1020 1021 <!-- Animated Navigation --> 1022 <div class="nav-tabs"> 1023 <div class="nav-glider" id="navGlider"></div> 1024 <button class="nav-tab active" onclick="switchTab('converter', 0)">Converter</button> 1025 <button class="nav-tab" onclick="switchTab('calculator', 1)">Calculator</button> 1026 </div> 1027 1028 <div class="header-actions"> 1029 <button class="tools-btn" onclick="toggleHistory()"> 1030 <svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z"/></svg> 1031 Versions 1032 </button> 1033 <button class="tools-btn" onclick="toggleSidebar()"> 1034 <svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor"><path d="M11 18h2v-2h-2v2zm1-16C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8zm0-14c-2.21 0-4 1.79-4 4h2c0-1.1.9-2 2-2s2 .9 2 2c0 2-3 1.75-3 5h2c0-2.25 3-2.5 3-5 0-2.21-1.79-4-4-4z"/></svg> 1035 Cheat Sheet 1036 </button> 1037 </div> 1038 </div> 1039 </header> 1040 1041 <main class="app-container"> 1042 1043 <!-- SECTION 1: CONVERTER --> 1044 <section id="converter-section" class="app-section active"> 1045 1046 <div class="converter-stage"> 1047 <!-- SOURCE CARD --> 1048 <div class="material-card" id="cardSource"> 1049 <div class="card-header"> 1050 <span class="input-label">Input</span> 1051 <div class="custom-select-wrapper" id="sourceSelectWrapper"> 1052 <div class="custom-select-trigger"> 1053 <span class="selection-text">Decimal</span> 1054 <span class="arrow">▼</span> 1055 </div> 1056 <div class="custom-options"> 1057 <div class="custom-option" data-value="2">Binary</div> 1058 <div class="custom-option" data-value="8">Octal</div> 1059 <div class="custom-option selected" data-value="10">Decimal</div> 1060 <div class="custom-option" data-value="16">Hexadecimal</div> 1061 </div> 1062 <select id="fromBase" style="display:none"> 1063 <option value="2">Binary</option> 1064 <option value="8">Octal</option> 1065 <option value="10" selected>Decimal</option> 1066 <option value="16">Hexadecimal</option> 1067 </select> 1068 </div> 1069 </div> 1070 1071 <textarea id="inputVal" class="input-area" placeholder="0" spellcheck="false"></textarea> 1072 <div id="errorChip" class="error-chip">⚠️ Invalid character for this base</div> 1073 1074 <div class="card-footer"> 1075 <div class="char-count" id="sourceCount">0 chars</div> 1076 <div class="icon-actions"> 1077 <button class="icon-btn" onclick="clearAll()" title="Clear"> 1078 <svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/></svg> 1079 </button> 1080 </div> 1081 </div> 1082 </div> 1083 1084 <!-- SWAP --> 1085 <div class="swap-container"> 1086 <button class="swap-fab" id="swapBtn" title="Swap (Alt+S)"> 1087 <svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor"><path d="M6.99 11L3 15l3.99 4v-3H14v-2H6.99v-3zM21 9l-3.99-4v3H10v2h7.01v3L21 9z"/></svg> 1088 </button> 1089 </div> 1090 1091 <!-- TARGET CARD --> 1092 <div class="material-card" id="cardTarget"> 1093 <div class="card-header"> 1094 <span class="input-label">Result</span> 1095 <div class="custom-select-wrapper" id="targetSelectWrapper"> 1096 <div class="custom-select-trigger"> 1097 <span class="selection-text">Binary</span> 1098 <span class="arrow">▼</span> 1099 </div> 1100 <div class="custom-options"> 1101 <div class="custom-option selected" data-value="2">Binary</div> 1102 <div class="custom-option" data-value="8">Octal</div> 1103 <div class="custom-option" data-value="10">Decimal</div> 1104 <div class="custom-option" data-value="16">Hexadecimal</div> 1105 </div> 1106 <select id="toBase" style="display:none"> 1107 <option value="2" selected>Binary</option> 1108 <option value="8">Octal</option> 1109 <option value="10">Decimal</option> 1110 <option value="16">Hexadecimal</option> 1111 </select> 1112 </div> 1113 </div> 1114 1115 <textarea id="outputVal" class="input-area" readonly placeholder="..." spellcheck="false"></textarea> 1116 1117 <div class="card-footer"> 1118 <div class="char-count">Result</div> 1119 <div class="icon-actions"> 1120 <button class="icon-btn" onclick="speakResult()" title="Speak"> 1121 <svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor"><path d="M3 9v6h4l5 5V4L7 9H3zm13.5 3c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02zM14 3.23v2.06c2.89.86 5 3.54 5 6.71s-2.11 5.85-5 6.71v2.06c4.01-.91 7-4.49 7-8.77s-2.99-7.86-7-8.77z"/></svg> 1122 </button> 1123 <button class="icon-btn" id="copyBtn" onclick="copyToClipboard('outputVal', 'copyBtn')" title="Copy (Ctrl+Enter)"> 1124 <svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor"><path d="M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z"/></svg> 1125 </button> 1126 </div> 1127 </div> 1128 </div> 1129 </div> 1130 1131 <!-- STEPS PANEL WITH COPY BUTTON --> 1132 <div class="steps-container" id="stepsContainer"> 1133 <div class="steps-header"> 1134 <h4 style="color:var(--md-sys-color-primary); margin:0;">Calculation Steps</h4> 1135 <button class="icon-btn" id="copyStepsBtn" onclick="copyStepsText()" title="Copy Steps"> 1136 <svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor"><path d="M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z"/></svg> 1137 </button> 1138 </div> 1139 <div class="steps-content" id="stepsOutput">Waiting for input...</div> 1140 </div> 1141 1142 <div class="history-section"> 1143 <div class="history-header" style="display:flex; justify-content:space-between; align-items:center; margin-bottom:10px;"> 1144 <h4 style="color:var(--md-sys-color-on-surface-variant); margin:0;">Recent History</h4> 1145 <button class="icon-btn" id="clearHistBtn" onclick="clearHistory()" title="Clear History"> 1146 <svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor"><path d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"/></svg> 1147 </button> 1148 </div> 1149 <div class="history-list" id="historyList"> 1150 <div style="padding:10px; color:#555; text-align:center;">No history yet</div> 1151 </div> 1152 </div> 1153 1154 </section> 1155 1156 <!-- SECTION 2: DYNAMIC CALCULATOR --> 1157 <section id="calculator-section" class="app-section"> 1158 <div class="calc-wrapper"> 1159 1160 <div class="calc-header"> 1161 <div class="calc-ctrl-group"> 1162 <label>Base Mode</label> 1163 <div class="custom-select-wrapper" id="calcBaseWrapper" style="flex:1"> 1164 <div class="custom-select-trigger"> 1165 <span class="selection-text">Decimal (Base 10)</span> 1166 <span class="arrow">▼</span> 1167 </div> 1168 <div class="custom-options"> 1169 <div class="custom-option" data-value="2">Binary (Base 2)</div> 1170 <div class="custom-option" data-value="8">Octal (Base 8)</div> 1171 <div class="custom-option selected" data-value="10">Decimal (Base 10)</div> 1172 <div class="custom-option" data-value="16">Hex (Base 16)</div> 1173 </div> 1174 <select id="calcBase" style="display:none" onchange="validateAllRows()"> 1175 <option value="2">Binary</option> 1176 <option value="8">Octal</option> 1177 <option value="10" selected>Decimal</option> 1178 <option value="16">Hex</option> 1179 </select> 1180 </div> 1181 </div> 1182 <div class="calc-ctrl-group"> 1183 <label>Operation</label> 1184 <div class="custom-select-wrapper" id="calcOpWrapper" style="flex:1"> 1185 <div class="custom-select-trigger"> 1186 <span class="selection-text">Addition (+)</span> 1187 <span class="arrow">▼</span> 1188 </div> 1189 <div class="custom-options"> 1190 <div class="custom-option selected" data-value="add">Addition (+)</div> 1191 <div class="custom-option" data-value="sub">Subtraction (-)</div> 1192 <div class="custom-option" data-value="mul">Multiplication (×)</div> 1193 <div class="custom-option" data-value="div">Division (÷)</div> 1194 </div> 1195 <select id="calcOp" style="display:none" onchange="runCalculation()"> 1196 <option value="add" selected>Addition</option> 1197 <option value="sub">Subtraction</option> 1198 <option value="mul">Multiplication</option> 1199 <option value="div">Division</option> 1200 </select> 1201 </div> 1202 </div> 1203 </div> 1204 1205 <div id="calcRows" class="calc-rows"> 1206 <!-- Dynamic Rows Injected Here --> 1207 </div> 1208 1209 <!-- ADD ROW BUTTON (Left Aligned) --> 1210 <button class="add-row-btn" onclick="addCalcRow()">+ Add Number</button> 1211 1212 <div class="calc-actions"> 1213 <button class="visualize-btn" id="vizBtn" onclick="visualizeCalculation()"> 1214 <svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor"><path d="M12 4.5C7 4.5 2.73 7.61 1 12c1.73 4.39 6 7.5 11 7.5s9.27-3.11 11-7.5c-1.73-4.39-6-7.5-11-7.5zM12 17c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5zm0-8c-1.66 0-3 1.34-3 3s1.34 3 3 3 3-1.34 3-3-1.34-3-3-3z"/></svg> 1215 Visualize 1216 </button> 1217 1218 <div class="calc-result-area"> 1219 <div class="calc-result-display"> 1220 <div class="res-label">Result</div> 1221 <div class="res-value" id="calcResult">0</div> 1222 </div> 1223 </div> 1224 </div> 1225 1226 <!-- BIT-BY-BIT VISUALIZER PANEL --> 1227 <div class="calc-visualizer" id="visualizerPanel"> 1228 <h4 style="color:var(--md-sys-color-primary); margin-bottom:15px; border-bottom:1px solid #333; padding-bottom:10px;">Calculation Steps</h4> 1229 <div class="viz-grid" id="vizGrid"> 1230 <!-- Grid injected here --> 1231 </div> 1232 <div class="viz-info" id="vizInfo">Click visualize to start...</div> 1233 </div> 1234 1235 </div> 1236 </section> 1237 1238 </main> 1239 1240 <!-- PROJECT LEGACY MODAL --> 1241 <div class="modal-overlay" id="historyModal"> 1242 <div class="modal-content"> 1243 <div class="modal-header"> 1244 <h3>Project Legacy</h3> 1245 <button class="close-modal" onclick="toggleHistory()">×</button> 1246 </div> 1247 <div class="modal-body"> 1248 <div class="history-timeline"> 1249 <div class="timeline-item"> 1250 <div class="version-tag">v1.0 (2024)</div> 1251 <p class="timeline-desc">Initial High School Project. Single file, manual conversion logic, animated background.</p> 1252 </div> 1253 <div class="timeline-item"> 1254 <div class="version-tag">v2.0</div> 1255 <p class="timeline-desc">Modularized Logic & URL State. Moved away from switch cases to a Base-N engine.</p> 1256 </div> 1257 <div class="timeline-item"> 1258 <div class="version-tag">v2.1</div> 1259 <p class="timeline-desc">Material You UI. Glassmorphism, animated cards, and custom dropdowns.</p> 1260 </div> 1261 <div class="timeline-item"> 1262 <div class="version-tag">v2.2</div> 1263 <p class="timeline-desc">Dynamic Calculator. Added list-based arithmetic with strict validation.</p> 1264 </div> 1265 <div class="timeline-item"> 1266 <div class="version-tag">v2.3 - v2.8</div> 1267 <p class="timeline-desc">Visualizer Engine. Bit-by-bit animation, step explanations, and history tape.</p> 1268 </div> 1269 <div class="timeline-item"> 1270 <div class="version-tag">v2.10</div> 1271 <p class="timeline-desc">Professional Polish. Optimized UX layout, legacy tracking, and performance tuning.</p> 1272 </div> 1273 <div class="timeline-item"> 1274 <div class="version-tag current">v2.11 (2026)</div> 1275 <p class="timeline-desc">Ultimate Refinement. Enhanced animations, layout stability, and smart controls.</p> 1276 </div> 1277 </div> 1278 </div> 1279 </div> 1280 </div> 1281 1282 <!-- SIDEBAR CHEATSHEET --> 1283 <aside class="sidebar" id="sidebar"> 1284 <button class="close-sidebar" onclick="toggleSidebar()">×</button> 1285 <h3>Cheat Sheet</h3> 1286 1287 <h4>Powers of 2</h4> 1288 <table class="cheat-table" style="margin-bottom:20px;"> 1289 <tr><td>2⁰</td><td>1</td></tr> 1290 <tr><td>2¹</td><td>2</td></tr> 1291 <tr><td>2²</td><td>4</td></tr> 1292 <tr><td>2³</td><td>8</td></tr> 1293 <tr><td>2⁴</td><td>16</td></tr> 1294 <tr><td>2⁵</td><td>32</td></tr> 1295 <tr><td>2⁶</td><td>64</td></tr> 1296 <tr><td>2⁷</td><td>128</td></tr> 1297 <tr><td>2⁸</td><td>256</td></tr> 1298 <tr><td>2¹⁰</td><td>1024</td></tr> 1299 </table> 1300 1301 <h4>Hex Lookup</h4> 1302 <table class="cheat-table"> 1303 <tr><th>Dec</th><th>Hex</th><th>Bin</th></tr> 1304 <tr><td>0</td><td>0</td><td>0000</td></tr> 1305 <tr><td>1</td><td>1</td><td>0001</td></tr> 1306 <tr><td>10</td><td>A</td><td>1010</td></tr> 1307 <tr><td>11</td><td>B</td><td>1011</td></tr> 1308 <tr><td>12</td><td>C</td><td>1100</td></tr> 1309 <tr><td>13</td><td>D</td><td>1101</td></tr> 1310 <tr><td>14</td><td>E</td><td>1110</td></tr> 1311 <tr><td>15</td><td>F</td><td>1111</td></tr> 1312 </table> 1313 </aside> 1314 1315 <footer> 1316 <div class="footer-content"> 1317 <p>&copy; 2026 <strong>Amit Dutta</strong>. All rights reserved.</p> 1318 <div class="footer-links"> 1319 <a href="https://amit.is-a.dev" target="_blank" class="footer-link">amit.is-a.dev</a> 1320 <span>•</span> 1321 <a href="https://github.com/notamitgamer" target="_blank" class="footer-link">GitHub</a> 1322 </div> 1323 </div> 1324 </footer> 1325 1326 <script> 1327 document.addEventListener('DOMContentLoaded', () => { 1328 setupCustomDropdown('sourceSelectWrapper', 'fromBase'); 1329 setupCustomDropdown('targetSelectWrapper', 'toBase'); 1330 setupCustomDropdown('calcBaseWrapper', 'calcBase'); 1331 setupCustomDropdown('calcOpWrapper', 'calcOp'); 1332 1333 setupEventListeners(); 1334 1335 // Initialize Calculator 1336 addCalcRow(); 1337 addCalcRow(); 1338 1339 renderHistory(); 1340 1341 try { 1342 const params = new URLSearchParams(window.location.search); 1343 if(params.has('val')) { 1344 document.getElementById('inputVal').value = params.get('val'); 1345 if(params.has('from')) { 1346 const val = params.get('from'); 1347 document.getElementById('fromBase').value = val; 1348 updateDropdownUI('sourceSelectWrapper', val); 1349 } 1350 if(params.has('to')) { 1351 const val = params.get('to'); 1352 document.getElementById('toBase').value = val; 1353 updateDropdownUI('targetSelectWrapper', val); 1354 } 1355 convert(); 1356 } 1357 } catch(e) {} 1358 1359 document.addEventListener('click', (e) => { 1360 const sidebar = document.getElementById('sidebar'); 1361 const historyModal = document.getElementById('historyModal'); 1362 const toolsBtns = document.querySelectorAll('.tools-btn'); 1363 let clickedBtn = false; 1364 toolsBtns.forEach(btn => { if(btn.contains(e.target)) clickedBtn = true; }); 1365 1366 if (!sidebar.contains(e.target) && !clickedBtn && sidebar.classList.contains('open')) { 1367 toggleSidebar(); 1368 } 1369 if (e.target === historyModal) { 1370 toggleHistory(); 1371 } 1372 }); 1373 }); 1374 1375 /* --- NAVIGATION --- */ 1376 function switchTab(tabName, index) { 1377 document.querySelectorAll('.app-section').forEach(el => el.classList.remove('active')); 1378 document.getElementById(tabName + '-section').classList.add('active'); 1379 1380 document.querySelectorAll('.nav-tab').forEach(el => el.classList.remove('active')); 1381 const tabs = document.querySelectorAll('.nav-tab'); 1382 tabs[index].classList.add('active'); 1383 1384 const glider = document.getElementById('navGlider'); 1385 glider.style.transform = `translateX(${index * 100}%)`; 1386 } 1387 1388 function toggleSidebar() { 1389 document.getElementById('sidebar').classList.toggle('open'); 1390 } 1391 1392 function toggleHistory() { 1393 const modal = document.getElementById('historyModal'); 1394 modal.classList.toggle('open'); 1395 } 1396 1397 /* --- DROPDOWN LOGIC --- */ 1398 function setupCustomDropdown(wrapperId, nativeSelectId) { 1399 const wrapper = document.getElementById(wrapperId); 1400 const nativeSelect = document.getElementById(nativeSelectId); 1401 const trigger = wrapper.querySelector('.custom-select-trigger'); 1402 const triggerText = trigger.querySelector('.selection-text'); 1403 const options = wrapper.querySelectorAll('.custom-option'); 1404 1405 trigger.addEventListener('click', (e) => { 1406 e.stopPropagation(); 1407 document.querySelectorAll('.custom-select-wrapper').forEach(w => { 1408 if(w !== wrapper) w.classList.remove('open'); 1409 }); 1410 wrapper.classList.toggle('open'); 1411 }); 1412 1413 options.forEach(opt => { 1414 opt.addEventListener('click', (e) => { 1415 e.stopPropagation(); 1416 wrapper.querySelectorAll('.custom-option').forEach(o => o.classList.remove('selected')); 1417 opt.classList.add('selected'); 1418 triggerText.textContent = opt.textContent; 1419 wrapper.classList.remove('open'); 1420 1421 nativeSelect.value = opt.getAttribute('data-value'); 1422 nativeSelect.dispatchEvent(new Event('change')); 1423 }); 1424 }); 1425 1426 document.addEventListener('click', () => { 1427 wrapper.classList.remove('open'); 1428 }); 1429 } 1430 1431 function updateDropdownUI(wrapperId, value) { 1432 const wrapper = document.getElementById(wrapperId); 1433 const triggerText = wrapper.querySelector('.selection-text'); 1434 const options = wrapper.querySelectorAll('.custom-option'); 1435 1436 options.forEach(opt => { 1437 if(opt.getAttribute('data-value') === value) { 1438 opt.classList.add('selected'); 1439 triggerText.textContent = opt.textContent; 1440 } else { 1441 opt.classList.remove('selected'); 1442 } 1443 }); 1444 } 1445 1446 /* --- CONVERTER LOGIC --- */ 1447 function convert() { 1448 const inputVal = document.getElementById('inputVal').value; 1449 const outputEl = document.getElementById('outputVal'); 1450 const errorChip = document.getElementById('errorChip'); 1451 const fromBase = parseInt(document.getElementById('fromBase').value); 1452 const toBase = parseInt(document.getElementById('toBase').value); 1453 1454 document.getElementById('sourceCount').textContent = `${inputVal.length} chars`; 1455 errorChip.classList.remove('visible'); 1456 1457 if(!inputVal) { 1458 outputEl.value = ''; 1459 document.getElementById('stepsContainer').classList.remove('visible'); 1460 updateURL(''); 1461 return; 1462 } 1463 1464 const normalized = inputVal.trim().toUpperCase(); 1465 const validChars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ.".substring(0, fromBase) + "."; 1466 const regex = new RegExp(`^[${validChars}]+$`); 1467 1468 if(!regex.test(normalized)) { 1469 errorChip.classList.add('visible'); 1470 outputEl.value = "Error"; 1471 document.getElementById('stepsContainer').classList.remove('visible'); 1472 return; 1473 } 1474 1475 try { 1476 updateURL(normalized, fromBase, toBase); 1477 1478 const parts = normalized.split('.'); 1479 const decInt = parseInt(parts[0], fromBase); 1480 let decTotal = decInt; 1481 1482 const result = decTotal.toString(toBase).toUpperCase(); 1483 outputEl.value = result; 1484 1485 generateSteps(normalized, fromBase, toBase, decTotal, result); 1486 addToHistory(normalized, result, fromBase, toBase); 1487 1488 } catch(e) { 1489 outputEl.value = "Error"; 1490 } 1491 } 1492 1493 function generateSteps(input, from, to, decimal, result) { 1494 const container = document.getElementById('stepsContainer'); 1495 const output = document.getElementById('stepsOutput'); 1496 container.classList.add('visible'); 1497 1498 let log = `Converting ${input} (Base ${from}) to Base ${to}\n`; 1499 log += `------------------------------------------\n`; 1500 1501 if(from !== 10) { 1502 log += `1. Convert to Decimal:\n `; 1503 const digits = input.split('.')[0].split('').reverse(); 1504 digits.forEach((d, i) => { 1505 log += `${d}×${from}^${i} + `; 1506 }); 1507 log = log.slice(0, -3); // Remove last + 1508 log += ` = ${decimal}\n\n`; 1509 } else { 1510 log += `1. Already Decimal: ${decimal}\n\n`; 1511 } 1512 1513 if(to !== 10) { 1514 log += `2. Convert Decimal ${decimal} to Base ${to}:\n`; 1515 let temp = decimal; 1516 while(temp > 0) { 1517 let rem = temp % to; 1518 let quot = Math.floor(temp / to); 1519 log += ` ${temp} ÷ ${to} = ${quot} [Rem: ${rem.toString(to).toUpperCase()}]\n`; 1520 temp = quot; 1521 } 1522 log += ` Read remainders bottom-up: ${result}`; 1523 } else { 1524 log += `2. Result is Decimal: ${result}`; 1525 } 1526 output.textContent = log; 1527 } 1528 1529 function updateURL(val, from, to) { 1530 try { 1531 const url = new URL(window.location); 1532 if(val) { 1533 url.searchParams.set('val', val); 1534 url.searchParams.set('from', from); 1535 url.searchParams.set('to', to); 1536 window.history.replaceState({}, '', url); 1537 } else { 1538 const cleanUrl = window.location.pathname; 1539 window.history.replaceState({}, '', cleanUrl); 1540 } 1541 } catch(e) {} 1542 } 1543 1544 /* --- HISTORY LOGIC --- */ 1545 function addToHistory(inVal, outVal, from, to) { 1546 const key = "nct_history"; 1547 let history = JSON.parse(localStorage.getItem(key) || "[]"); 1548 if(history.length > 0 && history[0].inVal === inVal && history[0].to === to) return; 1549 history.unshift({ inVal, outVal, from, to }); 1550 if(history.length > 10) history.pop(); 1551 localStorage.setItem(key, JSON.stringify(history)); 1552 renderHistory(); 1553 } 1554 1555 function renderHistory() { 1556 const list = document.getElementById('historyList'); 1557 const history = JSON.parse(localStorage.getItem("nct_history") || "[]"); 1558 if(history.length === 0) { 1559 list.innerHTML = '<div style="padding:10px; color:#555; text-align:center;">No history yet</div>'; 1560 return; 1561 } 1562 list.innerHTML = ''; 1563 history.forEach(item => { 1564 const div = document.createElement('div'); 1565 div.className = 'history-item'; 1566 div.innerHTML = `<div><span class="hist-meta">Base ${item.from} → ${item.to}</span><span class="hist-val">${item.inVal} = ${item.outVal}</span></div>`; 1567 div.onclick = () => { 1568 document.getElementById('inputVal').value = item.inVal; 1569 document.getElementById('fromBase').value = item.from; 1570 document.getElementById('toBase').value = item.to; 1571 updateDropdownUI('sourceSelectWrapper', item.from.toString()); 1572 updateDropdownUI('targetSelectWrapper', item.to.toString()); 1573 convert(); 1574 }; 1575 list.appendChild(div); 1576 }); 1577 } 1578 1579 function clearHistory() { 1580 const list = document.getElementById('historyList'); 1581 const btn = document.getElementById('clearHistBtn'); 1582 1583 if(list.children.length === 0 || list.innerText.includes("No history")) return; 1584 1585 // Trigger Shake Animation on Button 1586 btn.classList.add('shaking'); 1587 1588 // Trigger Slide Out Animation on List 1589 list.classList.add('clearing'); 1590 1591 setTimeout(() => { 1592 localStorage.removeItem("nct_history"); 1593 renderHistory(); 1594 list.classList.remove('clearing'); 1595 btn.classList.remove('shaking'); 1596 }, 400); // Duration matches CSS animation 1597 } 1598 1599 /* --- DYNAMIC CALCULATOR LOGIC --- */ 1600 function addCalcRow() { 1601 const container = document.getElementById('calcRows'); 1602 const div = document.createElement('div'); 1603 div.className = 'calc-row'; 1604 div.innerHTML = ` 1605 <div class="calc-input-wrapper"> 1606 <input type="text" class="calc-input" placeholder="Enter number..." oninput="validateRow(this); runCalculation()"> 1607 </div> 1608 <button class="calc-remove-btn" onclick="removeRow(this)" title="Remove">×</button> 1609 `; 1610 container.appendChild(div); 1611 div.querySelector('input').focus(); 1612 } 1613 1614 function removeRow(btn) { 1615 const row = btn.parentElement; 1616 if(document.querySelectorAll('.calc-row').length > 1) { 1617 row.classList.add('removing'); 1618 setTimeout(() => { 1619 row.remove(); 1620 runCalculation(); 1621 }, 300); 1622 } 1623 } 1624 1625 function validateRow(input) { 1626 const base = parseInt(document.getElementById('calcBase').value); 1627 const val = input.value.trim().toUpperCase(); 1628 if(!val) { 1629 input.classList.remove('invalid'); 1630 return true; 1631 } 1632 const validChars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ.".substring(0, base) + "."; 1633 const regex = new RegExp(`^[${validChars}]+$`); 1634 if(!regex.test(val)) { 1635 input.classList.add('invalid'); 1636 return false; 1637 } else { 1638 input.classList.remove('invalid'); 1639 return true; 1640 } 1641 } 1642 1643 function validateAllRows() { 1644 document.querySelectorAll('.calc-input').forEach(input => validateRow(input)); 1645 runCalculation(); 1646 } 1647 1648 function runCalculation() { 1649 const base = parseInt(document.getElementById('calcBase').value); 1650 const op = document.getElementById('calcOp').value; 1651 const inputs = document.querySelectorAll('.calc-input'); 1652 const vizBtn = document.getElementById('vizBtn'); 1653 let total = null; 1654 let error = false; 1655 1656 inputs.forEach(input => { 1657 if(input.classList.contains('invalid')) { error = true; return; } 1658 const valStr = input.value.trim().toUpperCase(); 1659 if(!valStr) return; 1660 const val = parseInt(valStr, base); 1661 if(isNaN(val)) return; 1662 if(total === null) { total = val; } else { 1663 if(op === 'add') total += val; 1664 if(op === 'sub') total -= val; 1665 if(op === 'mul') total *= val; 1666 if(op === 'div') total = Math.floor(total / val); 1667 } 1668 }); 1669 1670 const resEl = document.getElementById('calcResult'); 1671 if(error) { 1672 resEl.textContent = "Invalid Input"; 1673 resEl.style.color = "var(--md-sys-color-error)"; 1674 vizBtn.style.display = 'none'; 1675 } else if(total === null) { 1676 resEl.textContent = "0"; 1677 resEl.style.color = "var(--md-sys-color-primary)"; 1678 vizBtn.style.display = 'none'; 1679 } else { 1680 resEl.textContent = total.toString(base).toUpperCase(); 1681 resEl.style.color = "var(--md-sys-color-primary)"; 1682 vizBtn.style.display = (op === 'add') ? 'flex' : 'none'; 1683 } 1684 document.getElementById('visualizerPanel').classList.remove('active'); 1685 } 1686 1687 /* --- VISUALIZER LOGIC (Multi-Base) --- */ 1688 function visualizeCalculation() { 1689 const base = parseInt(document.getElementById('calcBase').value); 1690 const op = document.getElementById('calcOp').value; 1691 const panel = document.getElementById('visualizerPanel'); 1692 const grid = document.getElementById('vizGrid'); 1693 const info = document.getElementById('vizInfo'); 1694 1695 if(op !== 'add') return; 1696 1697 const inputs = []; 1698 let isValid = true; 1699 document.querySelectorAll('.calc-input').forEach(inp => { 1700 if(inp.value.trim()) { 1701 const val = parseInt(inp.value.trim(), base); 1702 if(isNaN(val)) isValid = false; 1703 inputs.push(val); 1704 } 1705 }); 1706 1707 if(!isValid || inputs.length < 2) { 1708 alert("Enter at least two numbers."); 1709 return; 1710 } 1711 1712 panel.classList.add('active'); 1713 grid.innerHTML = ''; 1714 1715 const digitArrays = inputs.map(n => n.toString(base).toUpperCase().split('')); 1716 const maxLen = Math.max(...digitArrays.map(arr => arr.length)) + 1; 1717 1718 const paddedArrays = digitArrays.map(arr => { 1719 const padding = new Array(maxLen - arr.length).fill('0'); 1720 return padding.concat(arr); 1721 }); 1722 1723 grid.style.gridTemplateColumns = `repeat(${maxLen}, 1fr)`; 1724 1725 for(let i=0; i<maxLen; i++) { 1726 const cell = document.createElement('div'); 1727 cell.className = 'viz-cell carry'; 1728 cell.id = `carry-${i}`; 1729 grid.appendChild(cell); 1730 } 1731 1732 paddedArrays.forEach((rowDigits, rIdx) => { 1733 for(let i=0; i<maxLen; i++) { 1734 const cell = document.createElement('div'); 1735 cell.className = 'viz-cell'; 1736 if(rIdx === paddedArrays.length -1) cell.classList.add('border-bottom'); 1737 const val = rowDigits[i]; 1738 cell.textContent = val; 1739 if(i === 0 && rIdx > 0) { 1740 cell.classList.add('operator'); 1741 cell.innerHTML = `+ ${val}`; 1742 } 1743 grid.appendChild(cell); 1744 } 1745 }); 1746 1747 for(let i=0; i<maxLen; i++) { 1748 const cell = document.createElement('div'); 1749 cell.className = 'viz-cell result'; 1750 cell.id = `res-${i}`; 1751 cell.textContent = '?'; 1752 grid.appendChild(cell); 1753 } 1754 1755 let carry = 0; 1756 let colIdx = maxLen - 1; 1757 1758 function step() { 1759 if(colIdx < 0) { 1760 info.textContent = "Complete."; 1761 return; 1762 } 1763 document.querySelectorAll('.active-col').forEach(el => el.classList.remove('active-col')); 1764 const resCell = document.getElementById(`res-${colIdx}`); 1765 resCell.classList.add('active-col'); 1766 1767 let sum = carry; 1768 const digitVals = []; 1769 paddedArrays.forEach(row => { 1770 const char = row[colIdx]; 1771 const val = parseInt(char, base); 1772 sum += val; 1773 digitVals.push(val.toString(base).toUpperCase()); 1774 }); 1775 1776 const writeVal = sum % base; 1777 const newCarry = Math.floor(sum / base); 1778 1779 resCell.textContent = writeVal.toString(base).toUpperCase(); 1780 if(newCarry > 0 && colIdx > 0) { 1781 document.getElementById(`carry-${colIdx-1}`).textContent = newCarry.toString(base).toUpperCase(); 1782 } 1783 1784 const eqStr = `${digitVals.join('+')} + ${carry}(c) = ${sum}`; 1785 const writeStr = writeVal.toString(base).toUpperCase(); 1786 const carryStr = newCarry.toString(base).toUpperCase(); 1787 info.textContent = `Col ${maxLen - colIdx}: ${eqStr} (Base ${base}). Write ${writeStr}, Carry ${carryStr}`; 1788 1789 carry = newCarry; 1790 colIdx--; 1791 setTimeout(step, 800); 1792 } 1793 step(); 1794 } 1795 1796 /* --- UTILS & SWAP --- */ 1797 function setupEventListeners() { 1798 document.addEventListener('keydown', (e) => { 1799 if(e.ctrlKey && e.key === 'Enter') copyToClipboard('outputVal', 'copyBtn'); 1800 if(e.altKey && e.key === 's') document.getElementById('swapBtn').click(); 1801 }); 1802 1803 document.getElementById('inputVal').addEventListener('input', convert); 1804 document.getElementById('fromBase').addEventListener('change', convert); 1805 document.getElementById('toBase').addEventListener('change', convert); 1806 1807 document.getElementById('swapBtn').addEventListener('click', () => { 1808 const cardSource = document.getElementById('cardSource'); 1809 const cardTarget = document.getElementById('cardTarget'); 1810 const isMobile = window.innerWidth <= 768; 1811 1812 if(isMobile) { 1813 cardSource.classList.add('anim-move-down'); 1814 cardTarget.classList.add('anim-move-up'); 1815 } else { 1816 cardSource.classList.add('anim-move-right'); 1817 cardTarget.classList.add('anim-move-left'); 1818 } 1819 1820 setTimeout(() => { 1821 const inEl = document.getElementById('inputVal'); 1822 const outEl = document.getElementById('outputVal'); 1823 const from = document.getElementById('fromBase'); 1824 const to = document.getElementById('toBase'); 1825 1826 const tempVal = inEl.value; 1827 inEl.value = outEl.value; 1828 const tempBase = from.value; 1829 from.value = to.value; 1830 to.value = tempBase; 1831 1832 updateDropdownUI('sourceSelectWrapper', from.value); 1833 updateDropdownUI('targetSelectWrapper', to.value); 1834 1835 cardSource.classList.remove('anim-move-down', 'anim-move-right'); 1836 cardTarget.classList.remove('anim-move-up', 'anim-move-left'); 1837 convert(); 1838 }, 400); 1839 }); 1840 } 1841 1842 function speakResult() { 1843 const txt = document.getElementById('outputVal').value; 1844 if(!txt) return; 1845 let spoken = txt; 1846 if(document.getElementById('toBase').value == 2) { 1847 spoken = txt.split('').join(' '); 1848 } 1849 const utt = new SpeechSynthesisUtterance(spoken); 1850 window.speechSynthesis.speak(utt); 1851 } 1852 1853 function copyToClipboard(id, btnId) { 1854 const el = document.getElementById(id); 1855 if(el) { 1856 el.select(); 1857 document.execCommand('copy'); 1858 } 1859 const btn = document.getElementById(btnId); 1860 if(btn) { 1861 const originalHTML = btn.innerHTML; 1862 btn.innerHTML = `<svg width="20" height="20" viewBox="0 0 24 24" fill="var(--md-sys-color-success)"><path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/></svg>`; 1863 btn.classList.add('success'); 1864 setTimeout(() => { 1865 btn.innerHTML = originalHTML; 1866 btn.classList.remove('success'); 1867 }, 2000); 1868 } 1869 } 1870 1871 function copyStepsText() { 1872 const stepsText = document.getElementById('stepsOutput').innerText; 1873 navigator.clipboard.writeText(stepsText).then(() => { 1874 const btn = document.getElementById('copyStepsBtn'); 1875 const originalHTML = btn.innerHTML; 1876 btn.innerHTML = `<svg width="18" height="18" viewBox="0 0 24 24" fill="var(--md-sys-color-success)"><path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/></svg>`; 1877 btn.classList.add('success'); 1878 setTimeout(() => { 1879 btn.innerHTML = originalHTML; 1880 btn.classList.remove('success'); 1881 }, 2000); 1882 }); 1883 } 1884 1885 function clearAll() { 1886 document.getElementById('inputVal').value = ''; 1887 document.getElementById('outputVal').value = ''; 1888 document.getElementById('sourceCount').textContent = '0 chars'; 1889 document.getElementById('errorChip').classList.remove('visible'); 1890 document.getElementById('stepsContainer').classList.remove('visible'); 1891 updateURL(''); 1892 } 1893 </script> 1894 </body> 1895 </html></pre> 1896 </div> 1897 </div> 1898 1899 <div id="download-tab-content" class="tab-content"> 1900 <div class="download-item"> 1901 <div class="download-info"> 1902 <h3>nct-legacy.html</h3> 1903 <p>Standalone production code of the Number Conversion Tool.</p> 1904 </div> 1905 <!-- Force download via download attribute --> 1906 <a href="nct-legacy.html" download="nct-legacy.html" class="download-button">Download File</a> 1907 </div> 1908 <div class="download-item"> 1909 <div class="download-info"> 1910 <h3>GitHub Repository</h3> 1911 <p>View source and contribute on GitHub.</p> 1912 </div> 1913 <a href="https://github.com/notamitgamer/nct-legacy" target="_blank" class="download-button" style="background-color: #333;">GitHub</a> 1914 </div> 1915 </div> 1916 </div> 1917 1918 <script> 1919 function switchTab(evt, tabId) { 1920 // Hide all contents 1921 document.querySelectorAll('.tab-content').forEach(content => { 1922 content.classList.remove('active'); 1923 }); 1924 // Deactivate all buttons 1925 document.querySelectorAll('.tab-button').forEach(button => { 1926 button.classList.remove('active'); 1927 }); 1928 1929 // Show current 1930 document.getElementById(tabId + '-tab-content').classList.add('active'); 1931 evt.currentTarget.classList.add('active'); 1932 } 1933 1934 async function copyToClipboard(btn) { 1935 const code = document.getElementById('sourceCode').innerText; 1936 try { 1937 await navigator.clipboard.writeText(code); 1938 const originalHTML = btn.innerHTML; 1939 btn.innerHTML = '<span>✓ Copied!</span>'; 1940 btn.classList.add('success'); 1941 1942 setTimeout(() => { 1943 btn.innerHTML = originalHTML; 1944 btn.classList.remove('success'); 1945 }, 2000); 1946 } catch (err) { 1947 console.error('Failed to copy code: ', err); 1948 } 1949 } 1950 </script> 1951 1952 </body> 1953 </html>