Spaces:
Sleeping
Sleeping
| <html lang="id"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>ConvNeXt CIFAR10 - Research Deployment</title> | |
| <style> | |
| @import url('https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@300;400;500;600;700;800&display=swap'); | |
| * { | |
| margin: 0; | |
| padding: 0; | |
| box-sizing: border-box; | |
| font-family: 'Plus Jakarta Sans', sans-serif; | |
| } | |
| html { | |
| scroll-behavior: smooth; | |
| } | |
| body { | |
| background-color: #f4f7fe; | |
| color: #1e293b; | |
| display: flex; | |
| min-height: 100vh; | |
| overflow-x: hidden; | |
| } | |
| .sidebar { | |
| width: 280px; | |
| background: #ffffff; | |
| height: 100vh; | |
| position: fixed; | |
| left: 0; | |
| top: 0; | |
| box-shadow: 4px 0 24px rgba(0,0,0,0.04); | |
| display: flex; | |
| flex-direction: column; | |
| z-index: 1000; | |
| transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1); | |
| } | |
| .sidebar-header { | |
| padding: 40px 30px; | |
| text-align: center; | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| } | |
| .sidebar-logo { | |
| font-size: 24px; | |
| font-weight: 800; | |
| background: linear-gradient(135deg, #4f46e5 0%, #7c3aed 100%); | |
| -webkit-background-clip: text; | |
| -webkit-text-fill-color: transparent; | |
| letter-spacing: -0.5px; | |
| } | |
| .nav-menu { | |
| list-style: none; | |
| padding: 0 20px; | |
| display: flex; | |
| flex-direction: column; | |
| gap: 10px; | |
| } | |
| .nav-item { | |
| display: block; | |
| } | |
| .nav-link { | |
| display: flex; | |
| align-items: center; | |
| gap: 16px; | |
| padding: 16px 20px; | |
| text-decoration: none; | |
| color: #64748b; | |
| font-weight: 600; | |
| font-size: 15px; | |
| border-radius: 16px; | |
| transition: all 0.3s ease; | |
| } | |
| .nav-link:hover, .nav-link.active { | |
| background: #f1f5f9; | |
| color: #4f46e5; | |
| } | |
| .nav-link svg { | |
| width: 20px; | |
| height: 20px; | |
| stroke: currentColor; | |
| stroke-width: 2.5; | |
| fill: none; | |
| } | |
| .main-wrapper { | |
| flex: 1; | |
| margin-left: 280px; | |
| display: flex; | |
| flex-direction: column; | |
| transition: margin-left 0.3s ease; | |
| } | |
| .hamburger-btn { | |
| display: none; | |
| position: fixed; | |
| top: 20px; | |
| left: 20px; | |
| z-index: 2000; | |
| background: #ffffff; | |
| border: none; | |
| width: 45px; | |
| height: 45px; | |
| border-radius: 12px; | |
| box-shadow: 0 4px 12px rgba(0,0,0,0.08); | |
| cursor: pointer; | |
| align-items: center; | |
| justify-content: center; | |
| } | |
| .hamburger-btn svg { | |
| width: 24px; | |
| height: 24px; | |
| stroke: #1e293b; | |
| stroke-width: 2.5; | |
| } | |
| .hero-banner { | |
| background: linear-gradient(120deg, #4f46e5 0%, #8b5cf6 100%); | |
| padding: 80px 60px; | |
| color: white; | |
| position: relative; | |
| overflow: hidden; | |
| border-bottom-left-radius: 40px; | |
| border-bottom-right-radius: 40px; | |
| margin: 20px; | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| justify-content: center; | |
| text-align: center; | |
| } | |
| .hero-title { | |
| font-size: 42px; | |
| font-weight: 800; | |
| margin-bottom: 12px; | |
| letter-spacing: -1px; | |
| position: relative; | |
| z-index: 2; | |
| } | |
| .hero-subtitle { | |
| font-size: 18px; | |
| color: #e0e7ff; | |
| font-weight: 500; | |
| position: relative; | |
| z-index: 2; | |
| } | |
| .container { | |
| max-width: 1200px; | |
| margin: 0 auto; | |
| padding: 40px; | |
| width: 100%; | |
| } | |
| .dashboard-grid { | |
| display: grid; | |
| grid-template-columns: 1fr; | |
| gap: 40px; | |
| } | |
| .card-panel { | |
| background: #ffffff; | |
| border-radius: 30px; | |
| padding: 45px; | |
| box-shadow: 0 10px 40px rgba(112, 144, 176, 0.08); | |
| border: 1px solid rgba(226, 232, 240, 0.8); | |
| position: relative; | |
| overflow: hidden; | |
| } | |
| .support-heading { | |
| font-size: 16px; | |
| font-weight: 700; | |
| color: #718096; | |
| text-transform: uppercase; | |
| letter-spacing: 2px; | |
| text-align: center; | |
| margin-bottom: 40px; | |
| } | |
| .support-logos-container { | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| gap: 50px; | |
| } | |
| .support-item { | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| gap: 20px; | |
| flex: 1; | |
| } | |
| .support-img { | |
| height: 75px; | |
| object-fit: contain; | |
| } | |
| .support-divider { | |
| width: 1px; | |
| height: 100px; | |
| background-color: #cbd5e0; | |
| } | |
| .support-text { | |
| font-size: 15px; | |
| font-weight: 600; | |
| color: #718096; | |
| text-align: center; | |
| } | |
| .section-header { | |
| display: flex; | |
| align-items: center; | |
| gap: 15px; | |
| margin-bottom: 35px; | |
| } | |
| .section-icon { | |
| width: 48px; | |
| height: 48px; | |
| background: #eef2ff; | |
| border-radius: 14px; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| color: #4f46e5; | |
| } | |
| .section-icon svg { | |
| width: 24px; | |
| height: 24px; | |
| stroke: currentColor; | |
| stroke-width: 2.5; | |
| fill: none; | |
| } | |
| .section-titles h2 { | |
| font-size: 24px; | |
| font-weight: 800; | |
| color: #1e293b; | |
| } | |
| .section-titles p { | |
| color: #64748b; | |
| font-size: 14px; | |
| margin-top: 4px; | |
| font-weight: 500; | |
| } | |
| .pills-grid { | |
| display: flex; | |
| flex-wrap: wrap; | |
| gap: 15px; | |
| } | |
| .pill { | |
| background: #f8fafc; | |
| border: 2px solid transparent; | |
| padding: 12px 24px; | |
| border-radius: 16px; | |
| font-weight: 700; | |
| font-size: 15px; | |
| color: #475569; | |
| cursor: pointer; | |
| display: flex; | |
| align-items: center; | |
| gap: 10px; | |
| transition: all 0.3s; | |
| } | |
| .pill:hover { | |
| border-color: #4f46e5; | |
| background: #eef2ff; | |
| color: #4f46e5; | |
| transform: translateY(-3px); | |
| box-shadow: 0 10px 20px rgba(79, 70, 229, 0.1); | |
| } | |
| .upload-zone { | |
| border: 3px dashed #cbd5e0; | |
| border-radius: 24px; | |
| background: #f8fafc; | |
| padding: 70px 20px; | |
| text-align: center; | |
| cursor: pointer; | |
| transition: all 0.3s ease; | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| justify-content: center; | |
| } | |
| .upload-zone:hover { | |
| border-color: #4f46e5; | |
| background: #f5f3ff; | |
| } | |
| #upload-prompt { | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| justify-content: center; | |
| width: 100%; | |
| } | |
| .upload-icon { | |
| width: 70px; | |
| height: 70px; | |
| background: #ffffff; | |
| border-radius: 20px; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| margin-bottom: 20px; | |
| box-shadow: 0 10px 25px rgba(0,0,0,0.06); | |
| color: #4f46e5; | |
| transition: transform 0.3s; | |
| } | |
| .upload-zone:hover .upload-icon { | |
| transform: scale(1.1); | |
| } | |
| .upload-text { | |
| font-size: 20px; | |
| font-weight: 800; | |
| color: #1e293b; | |
| } | |
| .upload-subtext { | |
| font-size: 14px; | |
| color: #64748b; | |
| margin-top: 8px; | |
| font-weight: 500; | |
| } | |
| #preview-wrapper { | |
| display: none; | |
| flex-direction: column; | |
| align-items: center; | |
| width: 100%; | |
| } | |
| #preview-img { | |
| max-height: 300px; | |
| border-radius: 16px; | |
| box-shadow: 0 15px 30px rgba(0,0,0,0.1); | |
| margin-bottom: 25px; | |
| object-fit: cover; | |
| } | |
| .btn-outline { | |
| background: #ffffff; | |
| border: 2px solid #e2e8f0; | |
| padding: 12px 30px; | |
| border-radius: 16px; | |
| font-weight: 700; | |
| font-size: 15px; | |
| color: #475569; | |
| cursor: pointer; | |
| transition: all 0.3s; | |
| } | |
| .btn-outline:hover { | |
| border-color: #4f46e5; | |
| color: #4f46e5; | |
| background: #f5f3ff; | |
| } | |
| .results-container { | |
| display: none; | |
| margin-top: 40px; | |
| } | |
| .cards-grid { | |
| display: grid; | |
| grid-template-columns: 1fr 1fr; | |
| gap: 30px; | |
| } | |
| .result-card { | |
| background: #ffffff; | |
| border: 1px solid #e2e8f0; | |
| border-radius: 24px; | |
| padding: 30px; | |
| box-shadow: 0 4px 20px rgba(0,0,0,0.03); | |
| position: relative; | |
| } | |
| .result-card::before { | |
| content: ''; | |
| position: absolute; | |
| top: 0; | |
| left: 0; | |
| width: 100%; | |
| height: 6px; | |
| background: linear-gradient(90deg, #4f46e5, #8b5cf6); | |
| border-top-left-radius: 24px; | |
| border-top-right-radius: 24px; | |
| } | |
| .rc-label { | |
| font-size: 13px; | |
| font-weight: 800; | |
| color: #64748b; | |
| text-transform: uppercase; | |
| letter-spacing: 1.5px; | |
| margin-bottom: 12px; | |
| } | |
| .rc-value { | |
| font-size: 32px; | |
| font-weight: 800; | |
| color: #1e293b; | |
| margin-bottom: 8px; | |
| } | |
| .rc-conf { | |
| font-size: 14px; | |
| color: #059669; | |
| font-weight: 700; | |
| background: #d1fae5; | |
| padding: 6px 14px; | |
| border-radius: 10px; | |
| display: inline-flex; | |
| align-items: center; | |
| gap: 6px; | |
| } | |
| .rc-bars { | |
| margin-top: 30px; | |
| display: flex; | |
| flex-direction: column; | |
| gap: 12px; | |
| } | |
| .bar-item { | |
| display: flex; | |
| align-items: center; | |
| font-size: 13px; | |
| font-weight: 700; | |
| color: #475569; | |
| } | |
| .bar-label { width: 90px; } | |
| .bar-bg { flex: 1; height: 10px; background: #f1f5f9; border-radius: 5px; margin: 0 15px; overflow: hidden; } | |
| .bar-fill { height: 100%; background: #4f46e5; border-radius: 5px; transition: width 0.8s cubic-bezier(0.4, 0, 0.2, 1); } | |
| .bar-val { width: 45px; text-align: right; } | |
| .interpretation-alert { | |
| margin-top: 30px; | |
| padding: 25px; | |
| border-radius: 20px; | |
| font-size: 15px; | |
| line-height: 1.6; | |
| display: flex; | |
| align-items: flex-start; | |
| gap: 20px; | |
| box-shadow: 0 10px 25px rgba(0,0,0,0.03); | |
| } | |
| .alert-icon { font-size: 28px; line-height: 1; } | |
| .alert-content strong { display: block; margin-bottom: 6px; font-size: 16px; font-weight: 800; } | |
| .loader { | |
| display: none; | |
| text-align: center; | |
| padding: 60px; | |
| } | |
| .spinner { | |
| width: 50px; | |
| height: 50px; | |
| border: 5px solid #eef2ff; | |
| border-top: 5px solid #4f46e5; | |
| border-radius: 50%; | |
| animation: spin 1s cubic-bezier(0.4, 0, 0.2, 1) infinite; | |
| margin: 0 auto 20px; | |
| } | |
| @keyframes spin { 100% { transform: rotate(360deg); } } | |
| .modal { | |
| display: none; | |
| position: fixed; | |
| top: 0; left: 0; width: 100%; height: 100%; | |
| background: rgba(15, 23, 42, 0.4); | |
| backdrop-filter: blur(8px); | |
| z-index: 9999; | |
| align-items: center; | |
| justify-content: center; | |
| opacity: 0; | |
| transition: opacity 0.3s; | |
| } | |
| .modal.show { opacity: 1; } | |
| .modal-content { | |
| background: white; | |
| padding: 40px; | |
| border-radius: 30px; | |
| max-width: 420px; | |
| width: 90%; | |
| text-align: center; | |
| position: relative; | |
| transform: translateY(20px); | |
| transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1); | |
| box-shadow: 0 25px 50px rgba(0,0,0,0.1); | |
| } | |
| .modal.show .modal-content { transform: translateY(0); } | |
| .modal-close { | |
| position: absolute; | |
| top: 20px; right: 25px; | |
| font-size: 28px; | |
| color: #94a3b8; | |
| cursor: pointer; | |
| transition: color 0.3s; | |
| background: none; | |
| border: none; | |
| } | |
| .modal-close:hover { color: #ef4444; } | |
| .modal-emoji { font-size: 60px; margin-bottom: 20px; } | |
| .modal-title { font-size: 24px; font-weight: 800; color: #1e293b; margin-bottom: 12px; } | |
| .modal-desc { font-size: 15px; color: #475569; line-height: 1.7; font-weight: 500;} | |
| @media (max-width: 1024px) { | |
| .sidebar { transform: translateX(-100%); } | |
| .sidebar.active { transform: translateX(0); } | |
| .main-wrapper { margin-left: 0; } | |
| .hamburger-btn { display: flex; } | |
| .hero-banner { padding: 80px 30px 60px; margin: 0; border-radius: 0; border-bottom-left-radius: 30px; border-bottom-right-radius: 30px;} | |
| .container { padding: 30px 20px; } | |
| } | |
| @media (max-width: 768px) { | |
| .cards-grid { grid-template-columns: 1fr; } | |
| .hero-title { font-size: 32px; } | |
| .support-logos-container { flex-direction: column; gap: 30px; } | |
| .support-divider { width: 80%; height: 1px; } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <button class="hamburger-btn" id="menu-toggle"> | |
| <svg viewBox="0 0 24 24"> | |
| <line x1="3" y1="12" x2="21" y2="12"></line> | |
| <line x1="3" y1="6" x2="21" y2="6"></line> | |
| <line x1="3" y1="18" x2="21" y2="18"></line> | |
| </svg> | |
| </button> | |
| <aside class="sidebar" id="sidebar"> | |
| <div class="sidebar-header"> | |
| <div class="sidebar-logo">ConvNeXt</div> | |
| </div> | |
| <ul class="nav-menu"> | |
| <li class="nav-item"> | |
| <a href="#beranda" class="nav-link active" onclick="closeSidebar()"> | |
| <svg viewBox="0 0 24 24"><path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"></path><polyline points="9 22 9 12 15 12 15 22"></polyline></svg> | |
| Beranda | |
| </a> | |
| </li> | |
| <li class="nav-item"> | |
| <a href="#dukungan" class="nav-link" onclick="closeSidebar()"> | |
| <svg viewBox="0 0 24 24"><path d="M20.59 13.41l-7.17 7.17a2 2 0 0 1-2.83 0L2 12V2h10l8.59 8.59a2 2 0 0 1 0 2.82z"></path><line x1="7" y1="7" x2="7.01" y2="7"></line></svg> | |
| Dukungan | |
| </a> | |
| </li> | |
| <li class="nav-item"> | |
| <a href="#dataset" class="nav-link" onclick="closeSidebar()"> | |
| <svg viewBox="0 0 24 24"><rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect><line x1="3" y1="9" x2="21" y2="9"></line><line x1="9" y1="21" x2="9" y2="9"></line></svg> | |
| Data CIFAR-10 | |
| </a> | |
| </li> | |
| <li class="nav-item"> | |
| <a href="#deteksi" class="nav-link" onclick="closeSidebar()"> | |
| <svg viewBox="0 0 24 24"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path><polyline points="17 8 12 3 7 8"></polyline><line x1="12" y1="3" x2="12" y2="15"></line></svg> | |
| Deteksi Objek | |
| </a> | |
| </li> | |
| </ul> | |
| </aside> | |
| <div class="main-wrapper"> | |
| <header class="hero-banner" id="beranda"> | |
| <h1 class="hero-title">Deployment ConvNeXt pada CIFAR10</h1> | |
| <div class="hero-subtitle">Bryan (NPM)</div> | |
| </header> | |
| <main class="container"> | |
| <div class="dashboard-grid"> | |
| <div class="card-panel" id="dukungan"> | |
| <h3 class="support-heading">PENELITIAN INI DIDUKUNG OLEH</h3> | |
| <div class="support-logos-container"> | |
| <div class="support-item"> | |
| <img src="/static/DSC UI.png" class="support-img" alt="DSC"> | |
| <span class="support-text">Data Science Center, FMIPA UI</span> | |
| </div> | |
| <div class="support-divider"></div> | |
| <div class="support-item"> | |
| <img src="/static/BrainAI.png" class="support-img" alt="BrainAI"> | |
| <span class="support-text">BrainAI Lab, Departemen Matematika FMIPA UI</span> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="card-panel" id="dataset"> | |
| <div class="section-header"> | |
| <div class="section-icon"> | |
| <svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="16" x2="12" y2="12"></line><line x1="12" y1="8" x2="12.01" y2="8"></line></svg> | |
| </div> | |
| <div class="section-titles"> | |
| <h2>Dataset CIFAR-10</h2> | |
| <p>Jelajahi 10 kelas objek visual yang menjadi acuan pengenalan citra.</p> | |
| </div> | |
| </div> | |
| <div class="pills-grid"> | |
| <div class="pill" onclick="showClass('Pesawat', '✈️', 'Mencakup pesawat komersial, jet tempur, dan pesawat baling-baling.')">✈️ Pesawat</div> | |
| <div class="pill" onclick="showClass('Mobil', '🚗', 'Kendaraan roda empat untuk penumpang seperti sedan dan SUV.')">🚗 Mobil</div> | |
| <div class="pill" onclick="showClass('Burung', '🐦', 'Berbagai spesies burung liar maupun peliharaan.')">🐦 Burung</div> | |
| <div class="pill" onclick="showClass('Kucing', '🐱', 'Mamalia famili Felidae, mencakup berbagai ras domestik.')">🐱 Kucing</div> | |
| <div class="pill" onclick="showClass('Rusa', '🦌', 'Hewan mamalia pemamah biak dari famili Cervidae.')">🦌 Rusa</div> | |
| <div class="pill" onclick="showClass('Anjing', '🐶', 'Mamalia karnivora yang telah didomestikasi.')">🐶 Anjing</div> | |
| <div class="pill" onclick="showClass('Katak', '🐸', 'Amfibi tak berekor yang pandai melompat.')">🐸 Katak</div> | |
| <div class="pill" onclick="showClass('Kuda', '🐴', 'Mamalia berkuku satu untuk berkuda atau pekerja beban.')">🐴 Kuda</div> | |
| <div class="pill" onclick="showClass('Kapal', '🚢', 'Kendaraan air ukuran besar dan kecil.')">🚢 Kapal</div> | |
| <div class="pill" onclick="showClass('Truk', '🚚', 'Kendaraan bermotor besar pengangkut barang.')">🚚 Truk</div> | |
| </div> | |
| </div> | |
| <div class="card-panel" id="deteksi"> | |
| <div class="section-header"> | |
| <div class="section-icon"> | |
| <svg viewBox="0 0 24 24"><rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect><circle cx="8.5" cy="8.5" r="1.5"></circle><polyline points="21 15 16 10 5 21"></polyline></svg> | |
| </div> | |
| <div class="section-titles"> | |
| <h2>Deteksi Objek ConvNeXt</h2> | |
| <p>Unggah citra untuk dianalisis oleh arsitektur ConvNeXt-MLP dan ConvNeXt-KAN.</p> | |
| </div> | |
| </div> | |
| <div class="upload-zone" id="drop-zone"> | |
| <div id="upload-prompt"> | |
| <div class="upload-icon"> | |
| <svg width="30" height="30" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path><polyline points="17 8 12 3 7 8"></polyline><line x1="12" y1="3" x2="12" y2="15"></line></svg> | |
| </div> | |
| <div class="upload-text">Drag or Drop your image here</div> | |
| <div class="upload-subtext">Format didukung: JPG, PNG (Max 5MB)</div> | |
| </div> | |
| <div id="preview-wrapper"> | |
| <img id="preview-img" src="#" alt="Preview"> | |
| <button class="btn-outline" id="btn-change">Ganti Gambar</button> | |
| </div> | |
| <input type="file" id="file-input" accept="image/jpeg, image/png" hidden> | |
| </div> | |
| <div class="loader" id="loader"> | |
| <div class="spinner"></div> | |
| <div style="color: #64748b; font-size: 15px; font-weight: 700;">Please wait while your image is being analyzed</div> | |
| </div> | |
| <div class="results-container" id="results"> | |
| <div class="cards-grid"> | |
| <div class="result-card"> | |
| <div class="rc-label">Model 1: ConvNeXt-MLP</div> | |
| <div class="rc-value" id="mlp-name">-</div> | |
| <div class="rc-conf"> | |
| <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"></polyline></svg> | |
| <span id="mlp-conf">-</span> | |
| </div> | |
| <div class="rc-bars" id="mlp-probs"></div> | |
| </div> | |
| <div class="result-card"> | |
| <div class="rc-label">Model 2: ConvNeXt-KAN</div> | |
| <div class="rc-value" id="kan-name">-</div> | |
| <div class="rc-conf"> | |
| <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"></polyline></svg> | |
| <span id="kan-conf">-</span> | |
| </div> | |
| <div class="rc-bars" id="kan-probs"></div> | |
| </div> | |
| </div> | |
| <div class="interpretation-alert" id="inter-box"> | |
| <div class="alert-icon" id="inter-emoji"></div> | |
| <div class="alert-content"> | |
| <strong id="inter-title"></strong> | |
| <span id="inter-text"></span> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </main> | |
| </div> | |
| <div class="modal" id="infoModal"> | |
| <div class="modal-content"> | |
| <button class="modal-close" onclick="closeModal('infoModal')">×</button> | |
| <div class="modal-emoji" id="m-emoji"></div> | |
| <div class="modal-title" id="m-title"></div> | |
| <div class="modal-desc" id="m-desc"></div> | |
| </div> | |
| </div> | |
| <script> | |
| const menuToggle = document.getElementById('menu-toggle'); | |
| const sidebar = document.getElementById('sidebar'); | |
| menuToggle.addEventListener('click', () => { | |
| sidebar.classList.toggle('active'); | |
| }); | |
| function closeSidebar() { | |
| if (window.innerWidth <= 1024) { | |
| sidebar.classList.remove('active'); | |
| } | |
| } | |
| const navLinks = document.querySelectorAll('.nav-link'); | |
| navLinks.forEach(link => { | |
| link.addEventListener('click', function() { | |
| navLinks.forEach(l => l.classList.remove('active')); | |
| this.classList.add('active'); | |
| }); | |
| }); | |
| const dropZone = document.getElementById('drop-zone'); | |
| const fileInput = document.getElementById('file-input'); | |
| const uploadPrompt = document.getElementById('upload-prompt'); | |
| const previewWrapper = document.getElementById('preview-wrapper'); | |
| const previewImg = document.getElementById('preview-img'); | |
| const btnChange = document.getElementById('btn-change'); | |
| const loader = document.getElementById('loader'); | |
| const results = document.getElementById('results'); | |
| dropZone.addEventListener('click', (e) => { | |
| if (e.target !== btnChange) fileInput.click(); | |
| }); | |
| btnChange.addEventListener('click', (e) => { | |
| e.stopPropagation(); | |
| fileInput.click(); | |
| }); | |
| fileInput.addEventListener('change', (e) => { | |
| if (e.target.files[0]) handleFile(e.target.files[0]); | |
| }); | |
| dropZone.addEventListener('dragover', (e) => { | |
| e.preventDefault(); | |
| dropZone.style.borderColor = '#4f46e5'; | |
| dropZone.style.backgroundColor = '#f5f3ff'; | |
| }); | |
| dropZone.addEventListener('dragleave', () => { | |
| dropZone.style.borderColor = '#cbd5e0'; | |
| dropZone.style.backgroundColor = '#f8fafc'; | |
| }); | |
| dropZone.addEventListener('drop', (e) => { | |
| e.preventDefault(); | |
| dropZone.style.borderColor = '#cbd5e0'; | |
| dropZone.style.backgroundColor = '#f8fafc'; | |
| if (e.dataTransfer.files.length) { | |
| fileInput.files = e.dataTransfer.files; | |
| handleFile(e.dataTransfer.files[0]); | |
| } | |
| }); | |
| function handleFile(file) { | |
| if (file.type === 'image/jpeg' || file.type === 'image/png') { | |
| const reader = new FileReader(); | |
| reader.onload = (e) => { | |
| previewImg.src = e.target.result; | |
| uploadPrompt.style.display = 'none'; | |
| previewWrapper.style.display = 'flex'; | |
| dropZone.style.borderStyle = 'solid'; | |
| dropZone.style.padding = '30px 20px'; | |
| processUpload(file); | |
| }; | |
| reader.readAsDataURL(file); | |
| } | |
| } | |
| function translateClass(enClass) { | |
| const mapId = { | |
| 'airplane': 'Pesawat', 'automobile': 'Mobil', 'bird': 'Burung', 'cat': 'Kucing', | |
| 'deer': 'Rusa', 'dog': 'Anjing', 'frog': 'Katak', 'horse': 'Kuda', | |
| 'ship': 'Kapal', 'truck': 'Truk' | |
| }; | |
| return mapId[enClass] || enClass; | |
| } | |
| function renderProbs(containerId, probs) { | |
| const container = document.getElementById(containerId); | |
| container.innerHTML = ''; | |
| probs.slice(0, 5).forEach(p => { | |
| let color = '#4f46e5'; | |
| if(p.confidence > 70) color = '#10b981'; | |
| else if(p.confidence < 15) color = '#94a3b8'; | |
| container.innerHTML += ` | |
| <div class="bar-item"> | |
| <div class="bar-label">${translateClass(p.class)}</div> | |
| <div class="bar-bg"><div class="bar-fill" style="width: ${p.confidence}%; background: ${color}"></div></div> | |
| <div class="bar-val">${p.confidence}%</div> | |
| </div> | |
| `; | |
| }); | |
| } | |
| function processUpload(file) { | |
| results.style.display = 'none'; | |
| loader.style.display = 'block'; | |
| const formData = new FormData(); | |
| formData.append('file', file); | |
| fetch('/api/predict', { method: 'POST', body: formData }) | |
| .then(res => res.json()) | |
| .then(data => { | |
| loader.style.display = 'none'; | |
| if (data.status === 'error') { | |
| alert("Kesalahan server."); | |
| return; | |
| } | |
| results.style.display = 'block'; | |
| const c1 = translateClass(data.model_1.class); | |
| const c2 = translateClass(data.model_2.class); | |
| document.getElementById('mlp-name').innerText = c1; | |
| document.getElementById('mlp-conf').innerText = data.model_1.confidence + '%'; | |
| document.getElementById('kan-name').innerText = c2; | |
| document.getElementById('kan-conf').innerText = data.model_2.confidence + '%'; | |
| renderProbs('mlp-probs', data.model_1.all_probs); | |
| renderProbs('kan-probs', data.model_2.all_probs); | |
| const interBox = document.getElementById('inter-box'); | |
| const emoji = document.getElementById('inter-emoji'); | |
| const title = document.getElementById('inter-title'); | |
| const text = document.getElementById('inter-text'); | |
| if (data.is_outlier) { | |
| interBox.style.background = '#fffbeb'; | |
| interBox.style.border = '1px solid #fde68a'; | |
| interBox.style.color = '#b45309'; | |
| title.innerText = 'Unidentified Object'; | |
| text.innerText = 'Confidence score for this image is low across both models, indicating it may not belong to any known class in the CIFAR-10 dataset.'; | |
| } else if(c1 === c2) { | |
| interBox.style.background = '#f0fdf4'; | |
| interBox.style.border = '1px solid #bbf7d0'; | |
| interBox.style.color = '#15803d'; | |
| title.innerText = 'Consistent Prediction'; | |
| text.innerText = `Both architectures confidently classify the image as ${c1}.`; | |
| } else { | |
| interBox.style.background = '#fef2f2'; | |
| interBox.style.border = '1px solid #fecaca'; | |
| interBox.style.color = '#b91c1c'; | |
| title.innerText = 'Inconsistent Prediction'; | |
| text.innerText = `Model ConvNeXt-MLP sees the object as ${c1}, while ConvNeXt-KAN is inclined towards ${c2}.`; | |
| } | |
| setTimeout(() => { | |
| results.scrollIntoView({ behavior: 'smooth', block: 'start' }); | |
| }, 100); | |
| }) | |
| .catch(err => { | |
| loader.style.display = 'none'; | |
| alert("Gagal koneksi ke server."); | |
| }); | |
| } | |
| function showClass(title, emoji, desc) { | |
| document.getElementById('m-title').innerText = title; | |
| document.getElementById('m-emoji').innerText = emoji; | |
| document.getElementById('m-desc').innerText = desc; | |
| const modal = document.getElementById('infoModal'); | |
| modal.style.display = 'flex'; | |
| setTimeout(() => modal.classList.add('show'), 10); | |
| } | |
| function closeModal(id) { | |
| const modal = document.getElementById(id); | |
| modal.classList.remove('show'); | |
| setTimeout(() => modal.style.display = 'none', 300); | |
| } | |
| window.onclick = function(event) { | |
| if (event.target.classList.contains('modal')) closeModal(event.target.id); | |
| } | |
| </script> | |
| </body> | |
| </html> |