| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"/> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"/> |
| <title>SCENEIQ — Intel Scene Classifier | Enhanced Readability</title> |
| <link rel="preconnect" href="https://fonts.googleapis.com"> |
| <link href="https://fonts.googleapis.com/css2?family=Share+Tech+Mono&family=Rajdhani:wght@400;500;600;700&family=Exo+2:wght@300;400;600&display=swap" rel="stylesheet"> |
| <style> |
| |
| |
| :root { |
| --g0:#000000; --g1:#071007; --g2:#0f1a0f; --g3:#152215; |
| --g4:#1f331f; |
| --green:#00ff41; --green2:#00dd33; --green3:#00aa22; |
| --green-dim:#00661a; --green-glow:rgba(0,255,65,0.2); |
| --border:rgba(0,255,65,0.22); --border2:rgba(0,255,65,0.45); |
| --muted:rgba(0,255,65,0.55); |
| --ff-mono:'Share Tech Mono',monospace; |
| --ff-head:'Rajdhani',sans-serif; |
| --ff-body:'Exo 2',sans-serif; |
| --r:8px; |
| } |
| |
| *, *::before, *::after { box-sizing:border-box; margin:0; padding:0; } |
| |
| html, body { |
| height: 100vh; |
| overflow: hidden; |
| font-size: 18px; |
| } |
| |
| body { |
| background: var(--g0); |
| color: var(--green); |
| font-family: var(--ff-body); |
| display: flex; |
| flex-direction: column; |
| line-height: 1.45; |
| } |
| |
| |
| #plexus-bg { |
| position: fixed; inset: 0; z-index: 0; pointer-events: none; |
| } |
| body::before { |
| content: ''; position: fixed; inset: 0; z-index: 0; pointer-events: none; |
| background-image: |
| linear-gradient(rgba(0,255,65,0.045) 1px, transparent 1px), |
| linear-gradient(90deg, rgba(0,255,65,0.045) 1px, transparent 1px); |
| background-size: 40px 40px; |
| } |
| body::after { |
| content: ''; position: fixed; inset: 0; z-index: 0; pointer-events: none; |
| background: repeating-linear-gradient( |
| 0deg, transparent, transparent 2px, |
| rgba(0,0,0,0.1) 2px, rgba(0,0,0,0.1) 5px |
| ); |
| } |
| |
| |
| .wrapper { |
| width: 100%; |
| max-width: 1400px; |
| margin: 0 auto; |
| padding: 0 1.5rem; |
| height: 100vh; |
| display: flex; |
| flex-direction: column; |
| gap: 0.25rem; |
| position: relative; |
| z-index: 2; |
| } |
| |
| |
| header { |
| flex-shrink: 0; |
| border-bottom: 2px solid var(--border); |
| padding: 1.2rem 0 0.8rem 0; |
| display: flex; align-items: center; justify-content: space-between; |
| animation: fadeIn .5s ease both; |
| } |
| .logo { display:flex; align-items:center; gap:1rem; } |
| .logo-icon { |
| width:50px; height:50px; border:1.8px solid var(--green); |
| display:grid; place-items:center; font-size:1.4rem; |
| box-shadow: 0 0 18px var(--green-glow), inset 0 0 12px var(--green-glow); |
| animation: pulse-box 3s ease-in-out infinite; |
| background: rgba(0,255,65,0.02); |
| } |
| @keyframes pulse-box { |
| 0%,100%{box-shadow:0 0 14px var(--green-glow),inset 0 0 8px var(--green-glow);} |
| 50%{box-shadow:0 0 28px rgba(0,255,65,.4),inset 0 0 18px rgba(0,255,65,.2);} |
| } |
| .logo-text { |
| font-family:var(--ff-head); font-size:2.5rem; font-weight:700; |
| letter-spacing:.12em; text-shadow:0 0 22px rgba(0,255,65,.7); |
| } |
| .logo-text span { color:var(--green3); } |
| |
| .header-right { display:flex; align-items:center; gap:1.6rem; } |
| .status-dot { |
| display:flex; align-items:center; gap:.9rem; |
| font-family:var(--ff-mono); font-size:0.85rem; color:var(--muted); |
| letter-spacing: 0.5px; |
| } |
| .dot { |
| width:9px; height:9px; border-radius:50%; |
| background:var(--green); box-shadow:0 0 12px var(--green); |
| animation:blink 2s step-end infinite; |
| } |
| @keyframes blink{0%,100%{opacity:1}50%{opacity:.2}} |
| .version { font-family:var(--ff-mono); font-size:.85rem; color:var(--green-dim); letter-spacing:.12em; } |
| |
| |
| .classes-bar { |
| flex-shrink: 0; |
| display: flex; align-items: center; gap: 1rem; |
| padding: 0.7rem 0; |
| border-bottom: 1px solid var(--border); |
| animation: fadeIn .6s ease .1s both; |
| } |
| .cs-label { |
| font-family:var(--ff-mono); font-size:0.85rem; |
| color:var(--green-dim); letter-spacing:.2em; |
| white-space:nowrap; font-weight:500; |
| } |
| .cs-pills { display:flex; gap:0.6rem; flex-wrap:wrap; } |
| .cs-pill { |
| font-family:var(--ff-mono); font-size:0.85rem; |
| padding:0.3rem 0.9rem; |
| border:1px solid var(--border); border-radius:20px; |
| color:var(--green-dim); transition:all .2s; cursor:default; |
| background: rgba(0,20,0,0.4); |
| backdrop-filter: blur(1px); |
| } |
| .cs-pill:hover { border-color:var(--green2); color:var(--green); background:rgba(0,255,65,0.08); } |
| |
| |
| .main-grid { |
| flex: 1; |
| min-height: 0; |
| display: grid; |
| grid-template-columns: 1fr 1fr; |
| gap: 1.5rem; |
| padding: 1rem 0 0.75rem 0; |
| animation: fadeIn .7s ease .15s both; |
| } |
| |
| |
| .panel { |
| background: rgba(10, 21, 10, 0.92); |
| backdrop-filter: blur(2px); |
| border: 1px solid var(--border); |
| border-radius: var(--r); |
| padding: 1.5rem 1.6rem; |
| position: relative; |
| overflow: hidden; |
| display: flex; |
| flex-direction: column; |
| box-shadow: 0 8px 20px rgba(0,0,0,0.6), 0 0 0 1px rgba(0,255,65,0.05) inset; |
| transition: box-shadow 0.2s; |
| } |
| .panel:hover { |
| box-shadow: 0 10px 28px rgba(0,0,0,0.7), 0 0 0 1px rgba(0,255,65,0.15) inset; |
| } |
| .panel::before { |
| content:''; position:absolute; top:0; left:0; right:0; height:3px; |
| background:linear-gradient(90deg,transparent,var(--green2),transparent); |
| } |
| .panel-title { |
| flex-shrink: 0; |
| font-family:var(--ff-mono); font-size:0.9rem; |
| letter-spacing:.25em; color:var(--green3); |
| text-transform:uppercase; margin-bottom:1rem; |
| display:flex; align-items:center; gap:.8rem; |
| font-weight: 500; |
| } |
| .panel-title::after { content:''; flex:1; height:1.5px; background:var(--border); } |
| |
| |
| .panel-scroll { |
| flex: 1; |
| min-height: 0; |
| overflow-y: auto; |
| overflow-x: hidden; |
| scrollbar-width: thin; |
| scrollbar-color: var(--green3) var(--g2); |
| padding-right: 3px; |
| } |
| .panel-scroll::-webkit-scrollbar { width: 5px; } |
| .panel-scroll::-webkit-scrollbar-track { background: var(--g2); border-radius: 4px; } |
| .panel-scroll::-webkit-scrollbar-thumb { background: var(--green-dim); border-radius: 4px; } |
| |
| |
| .model-grid { |
| display:grid; grid-template-columns:1fr 1fr; gap:1rem; |
| margin-bottom:1.4rem; |
| } |
| .model-card { |
| border:1.5px solid var(--border); border-radius:12px; |
| padding:0.9rem 0.8rem; cursor:pointer; transition:all .2s; |
| background:var(--g1); |
| display:flex; flex-direction:column; gap:0.4rem; |
| } |
| .model-card:hover { border-color:var(--green2); background:rgba(0,255,65,0.08); transform:translateY(-2px); } |
| .model-card.active { |
| border-color:var(--green); background:rgba(0,255,65,.1); |
| box-shadow:0 0 20px rgba(0,255,65,.18),inset 0 0 12px rgba(0,255,65,.06); |
| } |
| .model-card.active .mc-name { color:var(--green); text-shadow:0 0 5px var(--green-glow);} |
| .mc-icon { font-size:1.6rem; } |
| .mc-name { font-family:var(--ff-head); font-weight:700; font-size:1.3rem; color:var(--green2); transition:color .2s; } |
| .mc-sub { font-family:var(--ff-mono); font-size:0.75rem; color:var(--muted); letter-spacing: -0.2px;} |
| |
| |
| .input-tabs { |
| display:flex; margin-bottom:1rem; |
| border:1px solid var(--border); border-radius:12px; overflow:hidden; |
| } |
| .tab-btn { |
| flex:1; padding:0.7rem 0.5rem; background:var(--g1); |
| border:none; color:var(--muted); font-family:var(--ff-mono); |
| font-size:0.85rem; letter-spacing:.1em; cursor:pointer; |
| transition:all .2s; text-transform:uppercase; font-weight:500; |
| } |
| .tab-btn:hover { background:var(--g3); color:var(--green2); } |
| .tab-btn.active { |
| background:rgba(0,255,65,.12); color:var(--green); |
| box-shadow:inset 0 -3px 0 var(--green); |
| } |
| |
| |
| .drop-zone { |
| border:2px dashed var(--border2); border-radius:16px; |
| height: 210px; |
| display:flex; flex-direction:column; |
| align-items:center; justify-content:center; |
| gap:.7rem; cursor:pointer; |
| transition:all .25s; background:var(--g1); |
| position:relative; overflow:hidden; |
| } |
| .drop-zone:hover,.drop-zone.drag { |
| border-color:var(--green); background:rgba(0,255,65,.08); |
| box-shadow:0 0 28px rgba(0,255,65,.12); |
| } |
| .drop-zone.has-img .dz-ph { display:none; } |
| #preview-img { |
| position:absolute; inset:0; width:100%; height:100%; |
| object-fit:cover; display:none; |
| border-radius:14px; opacity:.9; |
| } |
| .drop-zone.has-img #preview-img { display:block; } |
| .dz-corner { position:absolute; width:14px; height:14px; border-color:var(--green2); border-style:solid; } |
| .dz-corner.tl{top:10px;left:10px;border-width:2px 0 0 2px;} |
| .dz-corner.tr{top:10px;right:10px;border-width:2px 2px 0 0;} |
| .dz-corner.bl{bottom:10px;left:10px;border-width:0 0 2px 2px;} |
| .dz-corner.br{bottom:10px;right:10px;border-width:0 2px 2px 0;} |
| .dz-ph { display:flex; flex-direction:column; align-items:center; gap:.5rem; pointer-events:none; } |
| .dz-icon { |
| width:48px; height:48px; border:1.5px solid var(--border2); |
| border-radius:50%; display:grid; place-items:center; |
| font-size:1.5rem; color:var(--green2); |
| } |
| .dz-ph p { font-family:var(--ff-mono); font-size:0.95rem; color:var(--muted); font-weight:500; } |
| .dz-ph em { font-family:var(--ff-mono); font-size:0.75rem; color:var(--green-dim); font-style:normal; } |
| #file-input { display:none; } |
| |
| |
| .url-input-wrap { display:none; flex-direction:column; gap:.8rem; } |
| .url-input-wrap.show { display:flex; } |
| .url-field { |
| display:flex; align-items:center; |
| border:1.5px solid var(--border2); border-radius:12px; |
| background:var(--g1); overflow:hidden; |
| } |
| .url-prefix { font-family:var(--ff-mono); font-size:0.85rem; color:var(--green2); padding:0 0.8rem; white-space:nowrap; border-right:1.5px solid var(--border); } |
| .url-input { |
| flex:1; background:transparent; border:none; outline:none; |
| color:var(--green); font-family:var(--ff-mono); font-size:0.9rem; |
| padding:0.8rem 1rem; caret-color:var(--green); |
| } |
| .url-input::placeholder { color:var(--green-dim); font-size:0.8rem; } |
| .url-load-btn { |
| padding:0.7rem 1rem; background:rgba(0,255,65,.12); border:none; |
| border-left:1.5px solid var(--border); |
| color:var(--green2); font-family:var(--ff-mono); font-size:0.85rem; |
| cursor:pointer; transition:background .2s; white-space:nowrap; |
| font-weight:600; |
| } |
| .url-load-btn:hover { background:rgba(0,255,65,.25); } |
| .url-preview { |
| width:100%; height:130px; object-fit:cover; |
| border-radius:12px; border:1px solid var(--border); display:none; |
| } |
| .url-preview.show { display:block; } |
| |
| |
| .classify-btn { |
| flex-shrink: 0; |
| width:100%; margin-top:1rem; |
| padding:0.9rem 0.5rem; |
| background:linear-gradient(135deg,var(--green3),var(--green-dim)); |
| border:1.5px solid var(--green3); border-radius:60px; |
| color:var(--green); font-family:var(--ff-head); |
| font-size:1.3rem; font-weight:700; letter-spacing:.12em; |
| cursor:pointer; position:relative; overflow:hidden; |
| transition:all .25s; text-transform:uppercase; |
| box-shadow:0 2px 8px rgba(0,0,0,0.3); |
| } |
| .classify-btn::before { |
| content:''; position:absolute; top:-2px; left:-100%; |
| width:60%; height:calc(100% + 4px); |
| background:linear-gradient(90deg,transparent,rgba(0,255,65,.25),transparent); |
| transform:skewX(-15deg); |
| } |
| .classify-btn.loading::before { animation:sweep 1.2s linear infinite; } |
| @keyframes sweep { to { left:150%; } } |
| .classify-btn:not(:disabled):hover { |
| background:linear-gradient(135deg,var(--green2),var(--green3)); |
| box-shadow:0 0 30px rgba(0,255,65,.45); transform:translateY(-2px); |
| border-color: var(--green); |
| } |
| .classify-btn:disabled { opacity:.4; cursor:not-allowed; transform: none; } |
| |
| |
| .result-panel-inner { |
| flex: 1; |
| min-height: 0; |
| overflow-y: auto; |
| overflow-x: hidden; |
| scrollbar-width: thin; |
| padding-right: 4px; |
| } |
| .result-panel-inner::-webkit-scrollbar { width: 5px; } |
| |
| .result-waiting { |
| display:flex; flex-direction:column; |
| align-items:flex-start; justify-content:center; |
| flex:1; gap:.8rem; padding:0.6rem 0; |
| } |
| .result-waiting .rw-title { font-family:var(--ff-head); font-size:1.5rem; font-weight:600; color:var(--green2); letter-spacing:-0.2px; } |
| .result-waiting .rw-sub { font-family:var(--ff-mono); font-size:1rem; color:var(--green-dim); line-height:1.9; } |
| .terminal-cursor { |
| display:inline-block; width:9px; height:18px; |
| background:var(--green); margin-left:4px; |
| animation:blink-c .8s step-end infinite; vertical-align:middle; |
| } |
| @keyframes blink-c{0%,100%{opacity:1}50%{opacity:0}} |
| |
| .result-content { display:none; flex-direction:column; gap:1.4rem; } |
| .result-content.show { display:flex; animation:scanIn .45s ease; } |
| @keyframes scanIn { |
| from{opacity:0;clip-path:inset(0 0 100% 0)} |
| to{opacity:1;clip-path:inset(0 0 0% 0)} |
| } |
| |
| .rc-header { display:flex; flex-direction:column; gap:.5rem; } |
| .rc-label { font-family:var(--ff-mono); font-size:0.7rem; letter-spacing:.25em; color:var(--green3); font-weight:500; } |
| .rc-class { |
| font-family:var(--ff-head); font-size:4rem; font-weight:800; |
| letter-spacing:-0.02em; line-height:1.1; color:var(--green); |
| text-shadow:0 0 28px rgba(0,255,65,.7); |
| word-break: break-word; |
| } |
| .rc-conf { font-family:var(--ff-mono); font-size:1rem; color:var(--muted); margin-top:.2rem; } |
| .rc-conf strong { color:var(--green2); font-size:1.2rem; } |
| |
| .conf-track { |
| height:8px; background:var(--g4); border-radius:40px; overflow:hidden; margin-top:.6rem; |
| } |
| .conf-fill { |
| height:100%; |
| background:linear-gradient(90deg,var(--green3),var(--green)); |
| border-radius:40px; width:0%; |
| transition:width 1s cubic-bezier(.2,.9,.3,1.1); |
| box-shadow:0 0 12px var(--green); |
| } |
| |
| .rc-divider { height:1.5px; background:var(--border); flex-shrink:0; margin:.2rem 0; } |
| |
| .prob-list { display:flex; flex-direction:column; gap:1rem; margin-top:0.3rem; } |
| .prob-row { display:flex; flex-direction:column; gap:.3rem; } |
| .prob-meta { display:flex; justify-content:space-between; align-items:baseline; } |
| .prob-name { |
| font-family:var(--ff-body); font-size:1rem; font-weight:500; |
| color:var(--muted); display:flex; align-items:center; gap:.5rem; text-transform:capitalize; |
| } |
| .prob-row.top .prob-name { color:var(--green); font-weight:700; text-shadow:0 0 3px rgba(0,255,65,0.3);} |
| .prob-pct { font-family:var(--ff-mono); font-size:0.85rem; color:var(--green-dim); font-weight:500; } |
| .prob-row.top .prob-pct { color:var(--green2); font-weight:600; } |
| .prob-track { height:7px; background:var(--g4); border-radius:40px; overflow:hidden; } |
| .prob-fill { |
| height:100%; background:var(--green-dim); |
| border-radius:40px; width:0%; |
| transition:width .8s cubic-bezier(.3,.8,.4,1); |
| } |
| .prob-row.top .prob-fill { |
| background:linear-gradient(90deg,var(--green3),var(--green)); |
| box-shadow:0 0 10px rgba(0,255,65,.5); |
| } |
| |
| .result-error { |
| display:none; padding:1rem; |
| border:1px solid rgba(255,70,70,.5); border-radius:12px; |
| background:rgba(255,30,30,.08); |
| font-family:var(--ff-mono); font-size:0.85rem; color:#ff7a7a; line-height:1.7; |
| } |
| .result-error.show { display:block; animation:fadeIn .3s ease; } |
| |
| |
| .footer-bar { |
| flex-shrink: 0; |
| border-top: 1.5px solid var(--border); |
| padding: 0.5rem 0; |
| display: flex; justify-content: space-between; align-items: center; |
| animation: fadeIn .8s ease .25s both; |
| } |
| .footer-bar p { font-family:var(--ff-mono); font-size:0.8rem; color:var(--green-dim); letter-spacing:0.3px; } |
| |
| @keyframes fadeIn{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:none}} |
| </style> |
| </head> |
| <body> |
|
|
| <canvas id="plexus-bg"></canvas> |
|
|
| <div class="wrapper"> |
|
|
| |
| <header> |
| <div class="logo"> |
| <div class="logo-icon">⬡</div> |
| <div class="logo-text">SCENE<span>IQ</span></div> |
| </div> |
| <div class="header-right"> |
| <div class="status-dot"><div class="dot"></div> SYSTEM ONLINE</div> |
| <div class="version">v4.0 · PARFAIT</div> |
| </div> |
| </header> |
|
|
| |
| <div class="classes-bar"> |
| <span class="cs-label">CLASSES //</span> |
| <div class="cs-pills"> |
| <span class="cs-pill">🏙 buildings</span> |
| <span class="cs-pill">🌲 forest</span> |
| <span class="cs-pill">🧊 glacier</span> |
| <span class="cs-pill">⛰ mountain</span> |
| <span class="cs-pill">🌊 sea</span> |
| <span class="cs-pill">🛣 street</span> |
| </div> |
| </div> |
|
|
| |
| <div class="main-grid"> |
|
|
| |
| <div class="panel"> |
| <div class="panel-title">01 // MODEL_SELECT</div> |
| <div class="panel-scroll"> |
| <div class="model-grid"> |
| <div class="model-card active" data-fw="pytorch" onclick="selectModel(this)"> |
| <span class="mc-icon">⚡</span> |
| <span class="mc-name">PyTorch</span> |
| <span class="mc-sub">CNN_Torch · .pth</span> |
| </div> |
| <div class="model-card" data-fw="tensorflow" onclick="selectModel(this)"> |
| <span class="mc-icon">🧠</span> |
| <span class="mc-name">TensorFlow</span> |
| <span class="mc-sub">CNN_TF · .keras</span> |
| </div> |
| </div> |
|
|
| <div class="panel-title">02 // INPUT_IMAGE</div> |
|
|
| <div class="input-tabs"> |
| <button class="tab-btn active" onclick="switchTab('file', this)">↑ FILE_UPLOAD</button> |
| <button class="tab-btn" onclick="switchTab('url', this)">⬡ IMAGE_URL</button> |
| </div> |
|
|
| |
| <div id="tab-file"> |
| <div class="drop-zone" id="drop-zone" onclick="document.getElementById('file-input').click()"> |
| <div class="dz-corner tl"></div><div class="dz-corner tr"></div> |
| <div class="dz-corner bl"></div><div class="dz-corner br"></div> |
| <img id="preview-img" src="" alt="preview"/> |
| <div class="dz-ph"> |
| <div class="dz-icon">↑</div> |
| <p>Drop image here or click</p> |
| <em>JPG · PNG · WEBP · GIF</em> |
| </div> |
| </div> |
| <input type="file" id="file-input" accept="image/*"/> |
| </div> |
|
|
| |
| <div id="tab-url" class="url-input-wrap"> |
| <div class="url-field"> |
| <span class="url-prefix">URL://</span> |
| <input type="text" class="url-input" id="url-input" |
| placeholder="https://example.com/image.jpg"/> |
| <button class="url-load-btn" onclick="loadFromUrl()">LOAD</button> |
| </div> |
| <img id="url-preview" class="url-preview" src="" alt="URL preview"/> |
| </div> |
|
|
| <div class="panel-title" style="margin-top:1rem;">03 // ANALYZE</div> |
| </div> |
|
|
| <button class="classify-btn" id="classify-btn" disabled onclick="classify()"> |
| RUN CLASSIFICATION → |
| </button> |
| </div> |
|
|
| |
| <div class="panel"> |
| <div class="panel-title">04 // SCAN_RESULTS</div> |
|
|
| <div class="result-panel-inner"> |
|
|
| <div class="result-waiting" id="result-waiting"> |
| <div class="rw-title">AWAITING INPUT</div> |
| <div class="rw-sub"> |
| > select_model()<br> |
| > load_image() <span class="terminal-cursor"></span><br> |
| > predict() |
| </div> |
| </div> |
|
|
| <div class="result-content" id="result-content"> |
| <div class="rc-header"> |
| <div class="rc-label">// PREDICTED_CLASS</div> |
| <div class="rc-class" id="rc-class">—</div> |
| <div class="rc-conf">confidence : <strong id="rc-conf">—</strong></div> |
| <div class="conf-track"><div class="conf-fill" id="conf-fill"></div></div> |
| </div> |
| <div class="rc-divider"></div> |
| <div> |
| <div class="panel-title" style="margin-bottom:.8rem;">// CLASS_SCORES</div> |
| <div class="prob-list" id="prob-list"></div> |
| </div> |
| </div> |
|
|
| <div class="result-error" id="result-error"></div> |
|
|
| </div> |
| </div> |
|
|
| </div> |
|
|
| |
| <div class="footer-bar"> |
| <p>SCENEIQ · Intel Image Classification · CNN PyTorch & TensorFlow</p> |
| <p>by PARFAIT · seed=42 · reproducible</p> |
| </div> |
|
|
| </div> |
|
|
| <script> |
| const EMOJIS = {buildings:"🏙",forest:"🌲",glacier:"🧊",mountain:"⛰",sea:"🌊",street:"🛣"}; |
| const CLASSES = ["buildings","forest","glacier","mountain","sea","street"]; |
| |
| let selectedFw = "pytorch"; |
| let selectedFile = null; |
| let urlImageReady = false; |
| let currentTab = "file"; |
| |
| function selectModel(card) { |
| document.querySelectorAll(".model-card").forEach(c => c.classList.remove("active")); |
| card.classList.add("active"); |
| selectedFw = card.dataset.fw; |
| } |
| |
| function switchTab(tab, btn) { |
| currentTab = tab; |
| document.querySelectorAll(".tab-btn").forEach(b => b.classList.remove("active")); |
| btn.classList.add("active"); |
| document.getElementById("tab-file").style.display = tab === "file" ? "block" : "none"; |
| document.getElementById("tab-url").classList.toggle("show", tab === "url"); |
| updateBtn(); |
| } |
| |
| const dropZone = document.getElementById("drop-zone"); |
| const fileInput = document.getElementById("file-input"); |
| |
| fileInput.addEventListener("change", () => { if (fileInput.files[0]) loadFile(fileInput.files[0]); }); |
| dropZone.addEventListener("dragover", e => { e.preventDefault(); dropZone.classList.add("drag"); }); |
| dropZone.addEventListener("dragleave", () => dropZone.classList.remove("drag")); |
| dropZone.addEventListener("drop", e => { |
| e.preventDefault(); dropZone.classList.remove("drag"); |
| const f = e.dataTransfer.files[0]; |
| if (f && f.type.startsWith("image/")) loadFile(f); |
| }); |
| |
| function loadFile(file) { |
| selectedFile = file; |
| const reader = new FileReader(); |
| reader.onload = e => { |
| document.getElementById("preview-img").src = e.target.result; |
| dropZone.classList.add("has-img"); |
| }; |
| reader.readAsDataURL(file); |
| resetResult(); updateBtn(); |
| } |
| |
| function loadFromUrl() { |
| const url = document.getElementById("url-input").value.trim(); |
| if (!url) return; |
| const preview = document.getElementById("url-preview"); |
| preview.onload = () => { preview.classList.add("show"); urlImageReady = true; resetResult(); updateBtn(); }; |
| preview.onerror = () => { preview.classList.remove("show"); urlImageReady = false; updateBtn(); }; |
| preview.src = url; |
| } |
| |
| document.getElementById("url-input").addEventListener("keydown", e => { if (e.key === "Enter") loadFromUrl(); }); |
| |
| function updateBtn() { |
| const ready = (currentTab === "file" && selectedFile) || (currentTab === "url" && urlImageReady); |
| document.getElementById("classify-btn").disabled = !ready; |
| } |
| |
| async function classify() { |
| const btn = document.getElementById("classify-btn"); |
| btn.disabled = true; btn.textContent = "SCANNING…"; btn.classList.add("loading"); |
| |
| const form = new FormData(); |
| form.append("model", selectedFw); |
| if (currentTab === "file" && selectedFile) form.append("image", selectedFile); |
| else form.append("image_url", document.getElementById("url-input").value.trim()); |
| |
| try { |
| const res = await fetch("/predict", { method: "POST", body: form }); |
| const data = await res.json(); |
| if (data.error) throw new Error(data.error); |
| showResult(data); |
| } catch(err) { |
| showError(err.message); |
| } finally { |
| btn.textContent = "RUN CLASSIFICATION →"; btn.classList.remove("loading"); btn.disabled = false; |
| } |
| } |
| |
| function showResult(data) { |
| document.getElementById("result-waiting").style.display = "none"; |
| document.getElementById("result-error").classList.remove("show"); |
| |
| const pct = Math.round(data.confidence * 100); |
| document.getElementById("rc-class").textContent = |
| (EMOJIS[data.class]||"") + " " + data.class.charAt(0).toUpperCase() + data.class.slice(1); |
| document.getElementById("rc-conf").textContent = pct + "%"; |
| |
| const content = document.getElementById("result-content"); |
| content.classList.remove("show"); void content.offsetWidth; content.classList.add("show"); |
| setTimeout(() => { document.getElementById("conf-fill").style.width = pct + "%"; }, 60); |
| |
| const list = document.getElementById("prob-list"); |
| list.innerHTML = ""; |
| const sorted = [...CLASSES].sort((a,b) => data.probabilities[b]-data.probabilities[a]); |
| sorted.forEach((cls, i) => { |
| const p = Math.round(data.probabilities[cls] * 100); |
| const top = cls === data.class; |
| const row = document.createElement("div"); |
| row.className = "prob-row" + (top ? " top" : ""); |
| row.innerHTML = ` |
| <div class="prob-meta"> |
| <span class="prob-name">${EMOJIS[cls]||""} ${cls}</span> |
| <span class="prob-pct" id="pct-${cls}">0%</span> |
| </div> |
| <div class="prob-track"><div class="prob-fill" id="bar-${cls}"></div></div>`; |
| list.appendChild(row); |
| setTimeout(() => { |
| document.getElementById("bar-"+cls).style.width = p+"%"; |
| document.getElementById("pct-"+cls).textContent = p+"%"; |
| }, 100 + i*50); |
| }); |
| } |
| |
| function showError(msg) { |
| document.getElementById("result-waiting").style.display = "none"; |
| document.getElementById("result-content").classList.remove("show"); |
| const err = document.getElementById("result-error"); |
| err.textContent = "ERROR > " + msg; err.classList.add("show"); |
| } |
| |
| function resetResult() { |
| document.getElementById("result-waiting").style.display = "flex"; |
| document.getElementById("result-content").classList.remove("show"); |
| document.getElementById("result-error").classList.remove("show"); |
| document.getElementById("conf-fill").style.width = "0%"; |
| } |
| |
| document.getElementById("tab-file").style.display = "block"; |
| </script> |
|
|
| |
| <script> |
| (function(){ |
| const canvas = document.getElementById('plexus-bg'); |
| const ctx = canvas.getContext('2d'); |
| const NODE_COLOR='rgba(0,255,65,', LINE_COLOR='rgba(0,255,65,'; |
| const MAX_DIST=160, MOUSE_DIST=220, ATTRACT_FORCE=0.012, NODE_COUNT=110, SPEED=0.4; |
| let W, H, nodes=[], mouse={x:-9999,y:-9999}; |
| |
| window.addEventListener('mousemove', e=>{mouse.x=e.clientX;mouse.y=e.clientY;}); |
| window.addEventListener('mouseleave',()=>{mouse.x=-9999;mouse.y=-9999;}); |
| |
| function resize(){W=canvas.width=window.innerWidth;H=canvas.height=window.innerHeight;} |
| function Node(){this.x=Math.random()*W;this.y=Math.random()*H;this.vx=(Math.random()-.5)*SPEED;this.vy=(Math.random()-.5)*SPEED;this.r=Math.random()*2+1.5;} |
| function init(){nodes=[];for(let i=0;i<NODE_COUNT;i++)nodes.push(new Node());} |
| |
| function draw(){ |
| ctx.clearRect(0,0,W,H); |
| for(const n of nodes){ |
| const dxM=mouse.x-n.x,dyM=mouse.y-n.y,dM=Math.sqrt(dxM*dxM+dyM*dyM); |
| if(dM<MOUSE_DIST&&dM>0){const f=(1-dM/MOUSE_DIST)*ATTRACT_FORCE;n.vx+=dxM/dM*f;n.vy+=dyM/dM*f;} |
| const spd=Math.sqrt(n.vx*n.vx+n.vy*n.vy),maxSpd=SPEED*3; |
| if(spd>maxSpd){n.vx=(n.vx/spd)*maxSpd;n.vy=(n.vy/spd)*maxSpd;} |
| if(dM>=MOUSE_DIST){n.vx*=.996;n.vy*=.996;const s=Math.sqrt(n.vx*n.vx+n.vy*n.vy);if(s<SPEED*.3){n.vx+=(Math.random()-.5)*.06;n.vy+=(Math.random()-.5)*.06;}} |
| n.x+=n.vx;n.y+=n.vy; |
| if(n.x<0||n.x>W)n.vx*=-1;if(n.y<0||n.y>H)n.vy*=-1; |
| } |
| for(let i=0;i<nodes.length;i++)for(let j=i+1;j<nodes.length;j++){ |
| const a=nodes[i],b=nodes[j],dx=a.x-b.x,dy=a.y-b.y,d=Math.sqrt(dx*dx+dy*dy); |
| if(d<MAX_DIST){const al=(1-d/MAX_DIST)*.28;ctx.beginPath();ctx.moveTo(a.x,a.y);ctx.lineTo(b.x,b.y);ctx.strokeStyle=LINE_COLOR+al+')';ctx.lineWidth=.9;ctx.stroke();} |
| } |
| for(const n of nodes){ |
| const dxM=mouse.x-n.x,dyM=mouse.y-n.y,dM=Math.sqrt(dxM*dxM+dyM*dyM); |
| if(dM<MOUSE_DIST){const al=(1-dM/MOUSE_DIST)*.35;ctx.beginPath();ctx.moveTo(mouse.x,mouse.y);ctx.lineTo(n.x,n.y);ctx.strokeStyle=LINE_COLOR+al+')';ctx.lineWidth=.7;ctx.stroke();} |
| } |
| if(mouse.x>0){ |
| const g=ctx.createRadialGradient(mouse.x,mouse.y,0,mouse.x,mouse.y,22); |
| g.addColorStop(0,'rgba(0,255,65,0.45)');g.addColorStop(1,'rgba(0,255,65,0)'); |
| ctx.beginPath();ctx.arc(mouse.x,mouse.y,22,0,Math.PI*2);ctx.fillStyle=g;ctx.fill(); |
| ctx.beginPath();ctx.arc(mouse.x,mouse.y,3,0,Math.PI*2);ctx.fillStyle='rgba(0,255,65,0.95)';ctx.fill(); |
| } |
| for(const n of nodes){ |
| const g=ctx.createRadialGradient(n.x,n.y,0,n.x,n.y,n.r*6); |
| g.addColorStop(0,NODE_COLOR+'0.35)');g.addColorStop(1,NODE_COLOR+'0)'); |
| ctx.beginPath();ctx.arc(n.x,n.y,n.r*6,0,Math.PI*2);ctx.fillStyle=g;ctx.fill(); |
| ctx.beginPath();ctx.arc(n.x,n.y,n.r,0,Math.PI*2);ctx.fillStyle=NODE_COLOR+'0.9)';ctx.fill(); |
| } |
| requestAnimationFrame(draw); |
| } |
| resize();init();draw(); |
| window.addEventListener('resize',()=>{resize();init();}); |
| })(); |
| </script> |
| </body> |
| </html> |
| ``` |