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