| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8" /> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0" /> |
| <title>DermSight PRO β AI Skin Lesion Classification</title> |
| <link rel="preconnect" href="https://fonts.googleapis.com" /> |
| <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin /> |
| <link href="https://fonts.googleapis.com/css2?family=Bricolage+Grotesque:opsz,wght@12..96,300;12..96,400;12..96,500;12..96,600;12..96,700;12..96,800&family=Geist:wght@300;400;500;600&family=Geist+Mono:wght@400;500&display=swap" rel="stylesheet" /> |
|
|
| <style> |
| |
| :root { |
| --bg: #f7f8fc; |
| --bg-2: #eef0f7; |
| --surface: #ffffff; |
| --surface-2: #f3f4f8; |
| --border: #e2e5ef; |
| --border-hi: #c8cddf; |
| |
| --navy: #0f2240; |
| --navy-2: #1e3a5f; |
| --jade: #0d9488; |
| --jade-dim: rgba(13,148,136,0.08); |
| --jade-border: rgba(13,148,136,0.25); |
| --rose: #e11d48; |
| --rose-dim: rgba(225,29,72,0.07); |
| --rose-border: rgba(225,29,72,0.2); |
| --amber: #d97706; |
| --amber-dim: rgba(217,119,6,0.08); |
| --amber-border:rgba(217,119,6,0.25); |
| --green: #059669; |
| --green-dim: rgba(5,150,105,0.08); |
| --green-border:rgba(5,150,105,0.22); |
| |
| --text: #0f2240; |
| --text-2: #4b5a72; |
| --text-3: #8c97ab; |
| |
| --font-display:'Bricolage Grotesque', system-ui, sans-serif; |
| --font-body: 'Geist', system-ui, sans-serif; |
| --font-mono: 'Geist Mono', monospace; |
| |
| --radius: 8px; |
| --radius-lg: 14px; |
| --radius-xl: 20px; |
| --shadow-sm: 0 1px 3px rgba(15,34,64,0.06), 0 1px 2px rgba(15,34,64,0.04); |
| --shadow-md: 0 4px 16px rgba(15,34,64,0.08), 0 1px 4px rgba(15,34,64,0.05); |
| --shadow-lg: 0 12px 40px rgba(15,34,64,0.10), 0 2px 8px rgba(15,34,64,0.06); |
| --transition: 180ms cubic-bezier(0.4,0,0.2,1); |
| } |
| |
| |
| *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; } |
| html { scroll-behavior: smooth; } |
| body { |
| font-family: var(--font-body); |
| background: var(--bg); |
| color: var(--text); |
| min-height: 100vh; |
| overflow-x: hidden; |
| } |
| |
| |
| body::before { |
| content: ''; |
| position: fixed; inset: 0; |
| background-image: radial-gradient(circle, rgba(15,34,64,0.06) 1px, transparent 1px); |
| background-size: 28px 28px; |
| pointer-events: none; |
| z-index: 0; |
| } |
| |
| |
| body::after { |
| content: ''; |
| position: fixed; |
| top: -120px; right: -120px; |
| width: 480px; height: 480px; |
| background: radial-gradient(circle, rgba(13,148,136,0.07) 0%, transparent 70%); |
| pointer-events: none; |
| z-index: 0; |
| } |
| |
| |
| .nav { |
| position: sticky; top: 0; z-index: 100; |
| background: rgba(247,248,252,0.92); |
| backdrop-filter: blur(18px); |
| border-bottom: 1px solid var(--border); |
| padding: 0 28px; |
| height: 62px; |
| display: flex; |
| align-items: center; |
| justify-content: space-between; |
| } |
| .nav-brand { display: flex; align-items: center; gap: 13px; } |
| .brand-mark { |
| width: 38px; height: 38px; |
| background: var(--navy); |
| border-radius: 10px; |
| display: flex; align-items: center; justify-content: center; |
| font-family: var(--font-display); |
| font-size: 19px; |
| font-weight: 800; |
| color: #ffffff; |
| box-shadow: 0 0 0 3px rgba(13,148,136,0.2); |
| transition: box-shadow var(--transition); |
| } |
| .brand-mark:hover { box-shadow: 0 0 0 5px rgba(13,148,136,0.3); } |
| .brand-text-wrap {} |
| .brand-name { |
| font-family: var(--font-display); |
| font-size: 17px; |
| font-weight: 700; |
| color: var(--navy); |
| letter-spacing: -0.02em; |
| line-height: 1.2; |
| } |
| .brand-name span { color: var(--jade); } |
| .brand-sub { |
| font-family: var(--font-mono); |
| font-size: 9px; |
| letter-spacing: 0.14em; |
| text-transform: uppercase; |
| color: var(--text-3); |
| } |
| .nav-links { display: flex; align-items: center; gap: 10px; } |
| .nav-link { |
| font-family: var(--font-mono); |
| font-size: 10px; |
| letter-spacing: 0.08em; |
| text-transform: uppercase; |
| color: var(--text-2); |
| text-decoration: none; |
| padding: 7px 13px; |
| border: 1px solid var(--border); |
| border-radius: var(--radius); |
| transition: all var(--transition); |
| background: var(--surface); |
| } |
| .nav-link:hover { color: var(--jade); border-color: var(--jade-border); background: var(--jade-dim); } |
| .nav-link.primary { |
| background: var(--navy); |
| border-color: var(--navy); |
| color: #fff; |
| } |
| .nav-link.primary:hover { background: var(--navy-2); border-color: var(--navy-2); color: #fff; } |
| |
| |
| .page { |
| position: relative; z-index: 1; |
| max-width: 1200px; |
| margin: 0 auto; |
| padding: 40px 24px 80px; |
| } |
| |
| |
| .hero { |
| text-align: center; |
| margin-bottom: 44px; |
| animation: fade-up 0.5s ease both; |
| } |
| .hero-eyebrow { |
| display: inline-flex; |
| align-items: center; |
| gap: 8px; |
| background: var(--jade-dim); |
| border: 1px solid var(--jade-border); |
| border-radius: 99px; |
| padding: 5px 14px; |
| font-family: var(--font-mono); |
| font-size: 10px; |
| letter-spacing: 0.14em; |
| text-transform: uppercase; |
| color: var(--jade); |
| margin-bottom: 18px; |
| } |
| .hero-eyebrow-dot { |
| width: 6px; height: 6px; |
| border-radius: 50%; |
| background: var(--jade); |
| animation: pulse-dot 2.5s ease infinite; |
| } |
| @keyframes pulse-dot { |
| 0%,100% { box-shadow: 0 0 0 0 rgba(13,148,136,0.5); } |
| 50% { box-shadow: 0 0 0 5px rgba(13,148,136,0); } |
| } |
| .hero-title { |
| font-family: var(--font-display); |
| font-size: clamp(30px, 5vw, 52px); |
| font-weight: 800; |
| color: var(--navy); |
| line-height: 1.1; |
| letter-spacing: -0.03em; |
| margin-bottom: 14px; |
| } |
| .hero-title em { font-style: italic; color: var(--jade); } |
| .hero-sub { |
| font-size: 15px; |
| color: var(--text-2); |
| max-width: 520px; |
| margin: 0 auto; |
| line-height: 1.7; |
| font-weight: 400; |
| } |
| |
| |
| .main-grid { |
| display: grid; |
| grid-template-columns: 360px 1fr; |
| gap: 22px; |
| align-items: start; |
| } |
| |
| |
| .card { |
| background: var(--surface); |
| border: 1px solid var(--border); |
| border-radius: var(--radius-xl); |
| box-shadow: var(--shadow-md); |
| overflow: hidden; |
| } |
| .card-header { |
| padding: 15px 22px; |
| border-bottom: 1px solid var(--border); |
| display: flex; |
| align-items: center; |
| justify-content: space-between; |
| background: var(--surface-2); |
| } |
| .card-title { |
| font-family: var(--font-mono); |
| font-size: 10px; |
| letter-spacing: 0.14em; |
| text-transform: uppercase; |
| color: var(--text-2); |
| display: flex; |
| align-items: center; |
| gap: 7px; |
| } |
| .card-tag { |
| font-family: var(--font-mono); |
| font-size: 9px; |
| color: var(--text-3); |
| letter-spacing: 0.08em; |
| } |
| .card-body { padding: 22px; } |
| |
| |
| .upload-zone { |
| border: 2px dashed var(--border-hi); |
| border-radius: var(--radius-lg); |
| cursor: pointer; |
| transition: all var(--transition); |
| overflow: hidden; |
| position: relative; |
| } |
| .upload-zone:hover { border-color: var(--jade); background: var(--jade-dim); } |
| .upload-zone input[type="file"] { display: none; } |
| |
| .upload-placeholder { |
| padding: 36px 20px; |
| text-align: center; |
| } |
| .upload-icon { |
| width: 60px; height: 60px; |
| background: var(--bg-2); |
| border: 1px solid var(--border); |
| border-radius: 14px; |
| display: flex; align-items: center; justify-content: center; |
| margin: 0 auto 12px; |
| font-size: 24px; |
| transition: transform var(--transition); |
| } |
| .upload-zone:hover .upload-icon { transform: scale(1.1); } |
| .upload-hint { |
| font-family: var(--font-mono); |
| font-size: 10px; |
| letter-spacing: 0.1em; |
| text-transform: uppercase; |
| color: var(--text-3); |
| line-height: 1.8; |
| } |
| .upload-hint b { color: var(--jade); font-weight: 600; } |
| |
| .scan-container { display: none; position: relative; } |
| .scanline { |
| position: absolute; left: 0; right: 0; |
| height: 2px; |
| background: var(--jade); |
| box-shadow: 0 0 10px rgba(13,148,136,0.6); |
| top: 0; |
| animation: scan 2.4s ease-in-out infinite; |
| display: none; |
| z-index: 10; |
| } |
| @keyframes scan { |
| 0% { top: 0%; opacity: 1; } |
| 90% { top: 100%; opacity: 1; } |
| 100% { top: 100%; opacity: 0; } |
| } |
| #preview { |
| width: 100%; |
| height: 250px; |
| object-fit: cover; |
| border-radius: var(--radius); |
| display: block; |
| } |
| |
| |
| .analyze-btn { |
| width: 100%; |
| margin-top: 16px; |
| padding: 14px 20px; |
| background: var(--navy); |
| color: #ffffff; |
| border: none; |
| border-radius: var(--radius-lg); |
| font-family: var(--font-display); |
| font-size: 14px; |
| font-weight: 700; |
| letter-spacing: 0.03em; |
| cursor: pointer; |
| transition: all var(--transition); |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| gap: 9px; |
| box-shadow: var(--shadow-sm); |
| } |
| .analyze-btn:hover { |
| background: var(--navy-2); |
| transform: translateY(-1px); |
| box-shadow: 0 6px 20px rgba(15,34,64,0.25); |
| } |
| .analyze-btn:active { transform: translateY(0); } |
| .analyze-btn:disabled { opacity: 0.45; cursor: not-allowed; transform: none; box-shadow: none; } |
| |
| .spinner { |
| width: 16px; height: 16px; |
| border: 2px solid rgba(255,255,255,0.25); |
| border-top-color: #fff; |
| border-radius: 50%; |
| animation: spin 0.8s linear infinite; |
| flex-shrink: 0; |
| } |
| @keyframes spin { to { transform: rotate(360deg); } } |
| |
| .wakeup-hint { |
| display: none; |
| font-family: var(--font-mono); |
| font-size: 10px; |
| letter-spacing: 0.1em; |
| text-transform: uppercase; |
| color: var(--jade); |
| text-align: center; |
| margin-top: 10px; |
| animation: blink 1.4s ease infinite; |
| } |
| @keyframes blink { 0%,100% { opacity: 1; } 50% { opacity: 0.35; } } |
| |
| .error-toast { |
| display: none; |
| background: var(--rose-dim); |
| border: 1px solid var(--rose-border); |
| border-radius: var(--radius); |
| padding: 12px 15px; |
| font-family: var(--font-mono); |
| font-size: 11px; |
| color: var(--rose); |
| margin-top: 12px; |
| line-height: 1.5; |
| } |
| |
| .safety-note { |
| margin-top: 14px; |
| background: var(--amber-dim); |
| border: 1px solid var(--amber-border); |
| border-radius: var(--radius); |
| padding: 13px 15px; |
| display: flex; |
| gap: 10px; |
| align-items: flex-start; |
| } |
| .safety-icon { font-size: 14px; flex-shrink: 0; margin-top: 1px; } |
| .safety-text { font-size: 12px; line-height: 1.65; color: var(--text-2); } |
| .safety-text strong { color: var(--amber); } |
| |
| |
| .result-idle { |
| min-height: 500px; |
| display: flex; |
| flex-direction: column; |
| align-items: center; |
| justify-content: center; |
| gap: 14px; |
| text-align: center; |
| } |
| .idle-icon { |
| width: 68px; height: 68px; |
| background: var(--bg-2); |
| border: 1px solid var(--border); |
| border-radius: 18px; |
| display: flex; align-items: center; justify-content: center; |
| font-size: 28px; |
| opacity: 0.55; |
| } |
| .idle-text { |
| font-family: var(--font-mono); |
| font-size: 11px; |
| letter-spacing: 0.14em; |
| text-transform: uppercase; |
| color: var(--text-3); |
| } |
| |
| .result-content { display: none; } |
| |
| |
| |
| .verdict-zone { |
| display: flex; |
| align-items: flex-start; |
| gap: 28px; |
| padding: 22px; |
| background: var(--surface-2); |
| border-radius: var(--radius-lg); |
| border: 1px solid var(--border); |
| margin-bottom: 20px; |
| flex-wrap: wrap; |
| } |
| |
| |
| .dial-block { |
| display: flex; |
| flex-direction: column; |
| align-items: center; |
| gap: 0; |
| flex-shrink: 0; |
| } |
| .dial-wrap { |
| width: 160px; |
| height: 90px; |
| position: relative; |
| } |
| .dial-svg { width: 160px; height: 90px; overflow: visible; } |
| .dial-track { |
| fill: none; |
| stroke: var(--bg-2); |
| stroke-width: 13; |
| stroke-linecap: round; |
| } |
| .dial-fill { |
| fill: none; |
| stroke: var(--jade); |
| stroke-width: 13; |
| stroke-linecap: round; |
| stroke-dasharray: 213.6; |
| stroke-dashoffset: 213.6; |
| transition: stroke-dashoffset 1.4s cubic-bezier(0.34,1.1,0.64,1), stroke 0.4s; |
| } |
| .dial-center { |
| position: absolute; inset: 0; |
| display: flex; flex-direction: column; |
| align-items: center; justify-content: flex-end; |
| padding-bottom: 4px; |
| } |
| .dial-pct { |
| font-family: var(--font-display); |
| font-size: 30px; |
| font-weight: 800; |
| color: var(--navy); |
| line-height: 1; |
| letter-spacing: -0.02em; |
| transition: color 0.4s; |
| } |
| |
| .dial-caption { |
| font-family: var(--font-mono); |
| font-size: 9px; |
| letter-spacing: 0.14em; |
| text-transform: uppercase; |
| color: var(--text-3); |
| margin-top: 6px; |
| text-align: center; |
| } |
| |
| |
| .verdict-info { flex: 1; min-width: 180px; } |
| .verdict-badge { |
| display: inline-flex; |
| align-items: center; |
| gap: 6px; |
| padding: 4px 12px; |
| border-radius: 99px; |
| font-family: var(--font-mono); |
| font-size: 9px; |
| letter-spacing: 0.12em; |
| text-transform: uppercase; |
| font-weight: 600; |
| margin-bottom: 10px; |
| border: 1px solid; |
| } |
| .verdict-badge.urgent { background: var(--rose-dim); color: var(--rose); border-color: var(--rose-border); } |
| .verdict-badge.caution { background: var(--amber-dim); color: var(--amber); border-color: var(--amber-border); } |
| .verdict-badge.normal { background: var(--green-dim); color: var(--green); border-color: var(--green-border); } |
| |
| .verdict-name { |
| font-family: var(--font-display); |
| font-size: clamp(26px, 4vw, 38px); |
| font-weight: 800; |
| color: var(--navy); |
| letter-spacing: -0.03em; |
| line-height: 1.05; |
| margin-bottom: 6px; |
| } |
| .verdict-code { |
| font-family: var(--font-mono); |
| font-size: 11px; |
| color: var(--jade); |
| letter-spacing: 0.08em; |
| text-transform: uppercase; |
| } |
| |
| |
| .desc-box { |
| background: var(--surface-2); |
| border: 1px solid var(--border); |
| border-left: 3px solid var(--jade); |
| border-radius: var(--radius); |
| padding: 14px 16px; |
| margin-bottom: 24px; |
| } |
| .desc-label { |
| font-family: var(--font-mono); |
| font-size: 9px; |
| letter-spacing: 0.14em; |
| text-transform: uppercase; |
| color: var(--jade); |
| margin-bottom: 7px; |
| } |
| .desc-text { |
| font-size: 13px; |
| color: var(--text-2); |
| line-height: 1.75; |
| } |
| |
| |
| .result-lower { |
| display: grid; |
| grid-template-columns: 1fr 1fr; |
| gap: 20px; |
| } |
| .bars-label { |
| font-family: var(--font-mono); |
| font-size: 9px; |
| letter-spacing: 0.14em; |
| text-transform: uppercase; |
| color: var(--text-3); |
| margin-bottom: 14px; |
| } |
| .prob-bar-item { margin-bottom: 11px; } |
| .prob-bar-meta { |
| display: flex; |
| justify-content: space-between; |
| font-family: var(--font-mono); |
| font-size: 10px; |
| color: var(--text-2); |
| margin-bottom: 5px; |
| } |
| .prob-bar-track { |
| height: 5px; |
| background: var(--bg-2); |
| border-radius: 99px; |
| overflow: hidden; |
| } |
| .prob-bar-fill { |
| height: 100%; |
| border-radius: 99px; |
| background: var(--border-hi); |
| width: 0%; |
| transition: width 1.2s cubic-bezier(0.34,1.1,0.64,1); |
| } |
| .prob-bar-fill.winner { |
| background: var(--jade); |
| } |
| .prob-bar-fill.danger-bar { |
| background: var(--rose); |
| } |
| .prob-bar-fill.caution-bar { |
| background: var(--amber); |
| } |
| |
| |
| .report-side { display: flex; flex-direction: column; gap: 10px; } |
| .stat-row { |
| background: var(--surface-2); |
| border: 1px solid var(--border); |
| border-radius: var(--radius); |
| padding: 11px 14px; |
| } |
| .stat-key { |
| font-family: var(--font-mono); |
| font-size: 9px; |
| letter-spacing: 0.12em; |
| text-transform: uppercase; |
| color: var(--text-3); |
| margin-bottom: 4px; |
| } |
| .stat-val { |
| font-family: var(--font-mono); |
| font-size: 14px; |
| font-weight: 600; |
| color: var(--navy); |
| transition: color 0.3s; |
| } |
| |
| .download-btn { |
| width: 100%; |
| padding: 11px 16px; |
| background: transparent; |
| border: 1px solid var(--border-hi); |
| border-radius: var(--radius); |
| font-family: var(--font-mono); |
| font-size: 10px; |
| letter-spacing: 0.1em; |
| text-transform: uppercase; |
| color: var(--text-2); |
| cursor: pointer; |
| transition: all var(--transition); |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| gap: 7px; |
| margin-top: auto; |
| } |
| .download-btn:hover { |
| background: var(--navy); |
| border-color: var(--navy); |
| color: #fff; |
| } |
| |
| |
| .ref-strip { |
| margin-top: 28px; |
| animation: fade-up 0.5s 0.25s ease both; |
| } |
| .ref-strip-title { |
| font-family: var(--font-mono); |
| font-size: 9px; |
| letter-spacing: 0.2em; |
| text-transform: uppercase; |
| color: var(--text-3); |
| text-align: center; |
| margin-bottom: 13px; |
| } |
| .ref-grid { |
| display: grid; |
| grid-template-columns: repeat(7, 1fr); |
| gap: 8px; |
| } |
| .ref-chip { |
| background: var(--surface); |
| border: 1px solid var(--border); |
| border-radius: var(--radius); |
| padding: 10px 6px; |
| text-align: center; |
| transition: all var(--transition); |
| cursor: default; |
| box-shadow: var(--shadow-sm); |
| } |
| .ref-chip:hover { box-shadow: var(--shadow-md); transform: translateY(-2px); } |
| .ref-code { |
| font-family: var(--font-mono); |
| font-size: 12px; |
| font-weight: 600; |
| color: var(--jade); |
| margin-bottom: 4px; |
| } |
| .ref-name { font-size: 9px; color: var(--text-3); line-height: 1.4; } |
| .ref-chip.danger .ref-code { color: var(--rose); } |
| .ref-chip.danger { border-color: var(--rose-border); background: var(--rose-dim); } |
| .ref-chip.caution .ref-code { color: var(--amber); } |
| .ref-chip.caution { border-color: var(--amber-border); background: var(--amber-dim); } |
| |
| |
| .footer { |
| position: relative; z-index: 1; |
| border-top: 1px solid var(--border); |
| padding: 28px 24px 44px; |
| text-align: center; |
| margin-top: 52px; |
| } |
| .footer-name { |
| font-family: var(--font-display); |
| font-size: 14px; |
| font-weight: 600; |
| color: var(--text-2); |
| margin-bottom: 16px; |
| } |
| .footer-links { |
| display: flex; |
| justify-content: center; |
| gap: 20px; |
| flex-wrap: wrap; |
| margin-bottom: 18px; |
| } |
| .footer-link { |
| font-family: var(--font-mono); |
| font-size: 10px; |
| letter-spacing: 0.1em; |
| text-transform: uppercase; |
| color: var(--text-3); |
| text-decoration: none; |
| display: flex; |
| align-items: center; |
| gap: 6px; |
| transition: color var(--transition); |
| } |
| .footer-link:hover { color: var(--jade); } |
| .footer-disclaimer { |
| font-size: 11px; |
| color: var(--text-3); |
| max-width: 560px; |
| margin: 0 auto; |
| line-height: 1.75; |
| } |
| |
| |
| @keyframes fade-up { |
| from { opacity: 0; transform: translateY(16px); } |
| to { opacity: 1; transform: translateY(0); } |
| } |
| .hero { animation: fade-up 0.45s ease both; } |
| .main-grid { animation: fade-up 0.45s 0.12s ease both; } |
| |
| ::-webkit-scrollbar { width: 5px; } |
| ::-webkit-scrollbar-track { background: transparent; } |
| ::-webkit-scrollbar-thumb { background: var(--border-hi); border-radius: 3px; } |
| |
| |
| @media (max-width: 980px) { |
| .main-grid { grid-template-columns: 1fr; } |
| .result-lower { grid-template-columns: 1fr; } |
| .ref-grid { grid-template-columns: repeat(4, 1fr); } |
| } |
| @media (max-width: 640px) { |
| .nav { padding: 0 16px; } |
| .page { padding: 24px 14px 56px; } |
| .verdict-zone { flex-direction: column; gap: 20px; } |
| .nav-links .nav-link:not(.primary) { display: none; } |
| .ref-grid { grid-template-columns: repeat(4, 1fr); } |
| } |
| @media (max-width: 400px) { |
| .ref-grid { grid-template-columns: repeat(3, 1fr); } |
| .result-lower { grid-template-columns: 1fr; } |
| } |
| </style> |
| </head> |
| <body> |
|
|
| |
| <nav class="nav"> |
| <div class="nav-brand"> |
| <div class="brand-mark">D</div> |
| <div class="brand-text-wrap"> |
| <div class="brand-name">DermSight <span>PRO</span></div> |
| <div class="brand-sub">ResNet-50 Β· HAM10000 Β· 7 Classes</div> |
| </div> |
| </div> |
| <div class="nav-links"> |
| <a href="https://github.com/nilotpaldhar2004/DermSight-AI-Deep-Learning-for-Skin-Lesion-Classification" target="_blank" class="nav-link">Source</a> |
| <a href="https://www.linkedin.com/in/nilotpal-dhar-24b304294" target="_blank" class="nav-link primary">LinkedIn</a> |
| </div> |
| </nav> |
|
|
| |
| <div class="page"> |
|
|
| |
| <div class="hero"> |
| <div> |
| <span class="hero-eyebrow"> |
| <span class="hero-eyebrow-dot"></span> |
| Deep Residual Learning Β· Dermatology |
| </span> |
| </div> |
| <h1 class="hero-title">Skin Lesion <em>Classification</em></h1> |
| <p class="hero-sub">Upload a dermoscopic image β the ResNet-50 model returns a probability distribution across all 7 ISIC diagnostic categories in seconds.</p> |
| </div> |
|
|
| |
| <div class="main-grid"> |
|
|
| |
| <div> |
| <div class="card"> |
| <div class="card-header"> |
| <div class="card-title">π¬ Image Input</div> |
| <div class="card-tag">DERMOSCOPY Β· RGB</div> |
| </div> |
| <div class="card-body"> |
|
|
| <div class="upload-zone" onclick="document.getElementById('imageInput').click()"> |
| <input type="file" id="imageInput" accept="image/*" onchange="previewImage(event)" /> |
| <div class="upload-placeholder" id="uploadPlaceholder"> |
| <div class="upload-icon">π©Ί</div> |
| <div class="upload-hint"> |
| <b>Tap to select</b> dermoscopic image<br> |
| JPG Β· PNG Β· TIFF supported |
| </div> |
| </div> |
| <div class="scan-container" id="scanContainer"> |
| <div class="scanline" id="scanline"></div> |
| <img id="preview" alt="Preview" /> |
| </div> |
| </div> |
|
|
| <button class="analyze-btn" id="analyzeBtn" onclick="runAnalysis()"> |
| <span id="btnText">Run Classification</span> |
| <div class="spinner" id="spinner" style="display:none"></div> |
| </button> |
|
|
| <div class="wakeup-hint" id="wakeupHint"> |
| Cloud engine initializing β est. 30s on first load |
| </div> |
|
|
| <div class="error-toast" id="errorToast"></div> |
|
|
| <div class="safety-note"> |
| <div class="safety-icon">β οΈ</div> |
| <div class="safety-text"> |
| <strong>Research Use Only.</strong> This tool uses deep learning to analyze dermoscopic images. It is <strong>not a medical diagnostic device</strong> and cannot replace a qualified dermatologist. Always consult a physician. |
| </div> |
| </div> |
|
|
| </div> |
| </div> |
| </div> |
|
|
| |
| <div> |
| <div class="card"> |
| <div class="card-header"> |
| <div class="card-title">𧬠Classification Output</div> |
| <div class="card-tag" id="resultTag">AWAITING INPUT</div> |
| </div> |
| <div class="card-body"> |
|
|
| |
| <div class="result-idle" id="resultIdle"> |
| <div class="idle-icon">π</div> |
| <div class="idle-text">System idling β awaiting image input</div> |
| </div> |
|
|
| |
| <div class="result-content" id="resultContent"> |
|
|
| |
| |
| |
| |
| |
| <div class="verdict-zone"> |
|
|
| |
| <div class="dial-block"> |
| <div class="dial-wrap"> |
| |
| |
| |
| |
| |
| <svg class="dial-svg" viewBox="0 0 240 96"> |
| <path class="dial-track" d="M 18 82 A 68 68 0 0 1 222 82" /> |
| <path class="dial-fill" id="dialFill" d="M 18 82 A 68 68 0 0 1 222 82" /> |
| </svg> |
| <div class="dial-center"> |
| <div class="dial-pct" id="dialPct">β</div> |
| </div> |
| </div> |
| |
| <div class="dial-caption">Confidence</div> |
| </div> |
|
|
| |
| <div class="verdict-info"> |
| <div class="verdict-badge normal" id="verdictBadge"> |
| <span>β</span> |
| <span id="badgeText">Benign</span> |
| </div> |
| <div class="verdict-name" id="verdictName">β</div> |
| <div class="verdict-code" id="verdictCode">β</div> |
| </div> |
|
|
| </div> |
| |
|
|
| |
| <div class="desc-box"> |
| <div class="desc-label">// Clinical Description</div> |
| <div class="desc-text" id="descText">β</div> |
| </div> |
| |
|
|
| |
| <div class="result-lower"> |
|
|
| <div> |
| <div class="bars-label">Probability Distribution Β· All Classes</div> |
| <div id="probBars"></div> |
| </div> |
|
|
| <div class="report-side"> |
| <div class="stat-row"> |
| <div class="stat-key">Primary Prediction</div> |
| <div class="stat-val" id="statPred">β</div> |
| </div> |
| <div class="stat-row"> |
| <div class="stat-key">Confidence Score</div> |
| <div class="stat-val" id="statConf">β</div> |
| </div> |
| <div class="stat-row"> |
| <div class="stat-key">Risk Category</div> |
| <div class="stat-val" id="statRisk">β</div> |
| </div> |
| <div class="stat-row"> |
| <div class="stat-key">Model Architecture</div> |
| <div class="stat-val" style="color:var(--jade)">ResNet-50</div> |
| </div> |
| <button class="download-btn" onclick="downloadReport()"> |
| β Export Analysis Report (.txt) |
| </button> |
| </div> |
|
|
| </div> |
|
|
| </div> |
| </div> |
| </div> |
| </div> |
|
|
| </div> |
|
|
| |
| <div class="ref-strip"> |
| <div class="ref-strip-title">ISIC Diagnostic Categories Β· HAM10000 Dataset</div> |
| <div class="ref-grid"> |
| <div class="ref-chip danger"> |
| <div class="ref-code">MEL</div> |
| <div class="ref-name">Melanoma</div> |
| </div> |
| <div class="ref-chip danger"> |
| <div class="ref-code">BCC</div> |
| <div class="ref-name">Basal Cell Carcinoma</div> |
| </div> |
| <div class="ref-chip caution"> |
| <div class="ref-code">AKIEC</div> |
| <div class="ref-name">Actinic Keratoses</div> |
| </div> |
| <div class="ref-chip"> |
| <div class="ref-code">BKL</div> |
| <div class="ref-name">Benign Keratosis</div> |
| </div> |
| <div class="ref-chip"> |
| <div class="ref-code">NV</div> |
| <div class="ref-name">Melanocytic Nevi</div> |
| </div> |
| <div class="ref-chip"> |
| <div class="ref-code">DF</div> |
| <div class="ref-name">Dermatofibroma</div> |
| </div> |
| <div class="ref-chip"> |
| <div class="ref-code">VASC</div> |
| <div class="ref-name">Vascular Lesions</div> |
| </div> |
| </div> |
| </div> |
|
|
| </div> |
|
|
| |
| <footer class="footer"> |
| <div class="footer-name">Developed by Nilotpal Dhar Β· 2026</div> |
| <div class="footer-links"> |
| <a class="footer-link" href="https://github.com/nilotpaldhar2004" target="_blank"> |
| <svg width="13" height="13" fill="currentColor" viewBox="0 0 24 24"><path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"/></svg> |
| GitHub |
| </a> |
| <a class="footer-link" href="https://www.linkedin.com/in/nilotpal-dhar-24b304294" target="_blank"> |
| <svg width="13" height="13" fill="currentColor" viewBox="0 0 24 24"><path d="M19 0h-14c-2.761 0-5 2.239-5 5v14c0 2.761 2.239 5 5 5h14c2.762 0 5-2.239 5-5v-14c0-2.761-2.238-5-5-5zm-11 19h-3v-11h3v11zm-1.5-12.268c-.966 0-1.75-.79-1.75-1.764s.784-1.764 1.75-1.764 1.75.79 1.75 1.764-.783 1.764-1.75 1.764zm13.5 12.268h-3v-5.604c0-3.368-4-3.113-4 0v5.604h-3v-11h3v1.765c1.396-2.586 7-2.777 7 2.476v6.759z"/></svg> |
| LinkedIn |
| </a> |
| <a class="footer-link" href="https://github.com/nilotpaldhar2004/DermSight-AI-Deep-Learning-for-Skin-Lesion-Classification" target="_blank"> |
| <svg width="13" height="13" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"/></svg> |
| Repository |
| </a> |
| </div> |
| <div class="footer-disclaimer"> |
| βοΈ For research and educational use only. Not a substitute for professional medical advice, diagnosis, or treatment. All predictions must be verified by a qualified dermatologist. |
| </div> |
| </footer> |
|
|
| <script> |
| |
| const RENDER_URL = ''; |
| |
| |
| |
| const DIAL_LEN = 213.6; |
| |
| let currentAnalysis = null; |
| |
| |
| const descriptions = { |
| nv: 'Melanocytic Nevi: A benign proliferation of melanocytes. Common in adults; regular monitoring is recommended to detect changes in size or morphology.', |
| mel: 'Melanoma: A malignant tumor arising from melanocytic cells with high metastatic potential. Immediate surgical consultation and biopsy are required.', |
| bkl: 'Benign Keratosis: Includes seborrheic keratoses and lichen planus-like keratoses. Generally non-cancerous lesions of the outer skin layers.', |
| bcc: 'Basal Cell Carcinoma: The most common form of skin cancer. Slow-growing and locally invasive, but rarely metastatic. Surgical excision is standard care.', |
| akiec: 'Actinic Keratoses / Intraepithelial Carcinoma: Pre-cancerous lesions induced by sun damage. Can progress to squamous cell carcinoma if untreated.', |
| vasc: 'Vascular Lesions: Includes angiomas, angiokeratomas, and pyogenic granulomas. Typically benign blood-vessel proliferations.', |
| df: 'Dermatofibroma: A benign fibrous nodule usually arising on the lower limbs, often following minor skin trauma. Low recurrence after excision.', |
| }; |
| |
| |
| const riskMap = { |
| mel: { level: 'Malignant', cls: 'urgent', risk: 'HIGH', barCls: 'danger-bar' }, |
| bcc: { level: 'Malignant', cls: 'urgent', risk: 'HIGH', barCls: 'danger-bar' }, |
| akiec: { level: 'Pre-cancerous', cls: 'caution', risk: 'MEDIUM', barCls: 'caution-bar' }, |
| bkl: { level: 'Benign', cls: 'normal', risk: 'LOW', barCls: '' }, |
| nv: { level: 'Benign', cls: 'normal', risk: 'LOW', barCls: '' }, |
| df: { level: 'Benign', cls: 'normal', risk: 'LOW', barCls: '' }, |
| vasc: { level: 'Benign', cls: 'normal', risk: 'LOW', barCls: '' }, |
| }; |
| |
| |
| function previewImage(event) { |
| if (!event.target.files[0]) return; |
| document.getElementById('preview').src = URL.createObjectURL(event.target.files[0]); |
| document.getElementById('uploadPlaceholder').style.display = 'none'; |
| document.getElementById('scanContainer').style.display = 'block'; |
| document.getElementById('errorToast').style.display = 'none'; |
| } |
| |
| |
| function setBusy(busy) { |
| const btn = document.getElementById('analyzeBtn'); |
| const spinner = document.getElementById('spinner'); |
| const hint = document.getElementById('wakeupHint'); |
| const scanline = document.getElementById('scanline'); |
| btn.disabled = busy; |
| spinner.style.display = busy ? 'block' : 'none'; |
| hint.style.display = busy ? 'block' : 'none'; |
| scanline.style.display = busy ? 'block' : 'none'; |
| document.getElementById('btnText').textContent = busy ? 'Analyzingβ¦' : 'Run Classification'; |
| } |
| |
| |
| function setDial(pct, riskCls) { |
| const fill = document.getElementById('dialFill'); |
| const pctEl = document.getElementById('dialPct'); |
| const offset = DIAL_LEN - (pct / 100) * DIAL_LEN; |
| const color = |
| riskCls === 'urgent' ? 'var(--rose)' : |
| riskCls === 'caution' ? 'var(--amber)' : |
| 'var(--jade)'; |
| fill.style.strokeDashoffset = offset; |
| fill.style.stroke = color; |
| pctEl.textContent = pct.toFixed(1) + '%'; |
| pctEl.style.color = color; |
| } |
| |
| |
| function showResult(data) { |
| const pred = data.prediction.toLowerCase(); |
| const conf = parseFloat(data.confidence); |
| const risk = riskMap[pred] || { level: 'Unknown', cls: 'normal', risk: 'LOW', barCls: '' }; |
| const desc = descriptions[pred] || 'Classification complete.'; |
| |
| |
| |
| const malignantProb = (data.all_probabilities['mel'] || 0) + (data.all_probabilities['bcc'] || 0); |
| let displayRiskLevel = risk.risk; |
| let displayRiskCls = risk.cls; |
| let displayBadgeText = risk.level; |
| |
| if (malignantProb > 20 && pred === 'nv') { |
| displayRiskLevel = 'ELEVATED'; |
| displayRiskCls = 'caution'; |
| displayBadgeText = 'Review Required'; |
| } |
| |
| |
| setDial(conf, displayRiskCls); |
| |
| const badge = document.getElementById('verdictBadge'); |
| badge.className = `verdict-badge ${displayRiskCls}`; |
| document.getElementById('badgeText').textContent = displayBadgeText; |
| |
| document.getElementById('verdictName').textContent = data.prediction.toUpperCase(); |
| document.getElementById('verdictCode').textContent = `/ ${pred.toUpperCase()} Β· ${displayRiskLevel} RISK`; |
| document.getElementById('descText').textContent = desc; |
| |
| document.getElementById('statPred').textContent = data.prediction.toUpperCase(); |
| document.getElementById('statConf').textContent = conf.toFixed(1) + '%'; |
| |
| const riskEl = document.getElementById('statRisk'); |
| riskEl.textContent = displayRiskLevel; |
| riskEl.style.color = displayRiskCls === 'urgent' ? 'var(--rose)' : (displayRiskCls === 'caution' ? 'var(--amber)' : 'var(--green)'); |
| |
| document.getElementById('resultTag').textContent = displayRiskCls === 'urgent' ? 'β PRIORITY' : (displayRiskCls === 'caution' ? 'β REVIEW' : 'β ROUTINE'); |
| |
| |
| const container = document.getElementById('probBars'); |
| container.innerHTML = ''; |
| const sorted = Object.entries(data.all_probabilities).sort(([,a],[,b]) => b - a); |
| |
| sorted.forEach(([cls, val]) => { |
| const pctVal = parseFloat(val).toFixed(1); |
| const isWin = cls.toLowerCase() === pred; |
| const winRisk = riskMap[cls.toLowerCase()] || {}; |
| const barExtraCls = isWin ? ` winner` : (winRisk.barCls ? ` ${winRisk.barCls}` : ''); |
| |
| const item = document.createElement('div'); |
| item.className = 'prob-bar-item'; |
| item.innerHTML = ` |
| <div class="prob-bar-meta"> |
| <span style="font-weight:${isWin ? '600' : '400'};color:${isWin ? 'var(--navy)' : 'var(--text-2)'}">${cls.toUpperCase()}</span> |
| <span>${pctVal}%</span> |
| </div> |
| <div class="prob-bar-track"> |
| <div class="prob-bar-fill${barExtraCls}" style="width:0%" data-width="${pctVal}%"></div> |
| </div>`; |
| container.appendChild(item); |
| }); |
| |
| document.getElementById('resultIdle').style.display = 'none'; |
| document.getElementById('resultContent').style.display = 'block'; |
| |
| requestAnimationFrame(() => { |
| requestAnimationFrame(() => { |
| container.querySelectorAll('.prob-bar-fill').forEach(el => { el.style.width = el.dataset.width; }); |
| }); |
| }); |
| } |
| |
| |
| function showError(msg) { |
| const toast = document.getElementById('errorToast'); |
| toast.style.display = 'block'; |
| toast.textContent = 'β ' + msg; |
| } |
| |
| |
| async function runAnalysis() { |
| const input = document.getElementById('imageInput'); |
| if (!input.files[0]) { |
| showError('Please select a dermoscopic image first.'); |
| return; |
| } |
| |
| setBusy(true); |
| document.getElementById('errorToast').style.display = 'none'; |
| |
| const formData = new FormData(); |
| formData.append('file', input.files[0]); |
| |
| try { |
| const res = await fetch(`${RENDER_URL}/predict`, { |
| method: 'POST', |
| body: formData, |
| }); |
| |
| if (!res.ok) throw new Error(`Server error: HTTP ${res.status}`); |
| |
| const data = await res.json(); |
| currentAnalysis = data; |
| showResult(data); |
| |
| } catch (err) { |
| showError( |
| err.message.includes('Failed to fetch') |
| ? 'Cannot reach the inference server. The engine may be cold-starting β please wait 30 seconds and try again.' |
| : err.message |
| ); |
| } finally { |
| setBusy(false); |
| } |
| } |
| |
| |
| function downloadReport() { |
| if (!currentAnalysis) return; |
| const pred = currentAnalysis.prediction.toLowerCase(); |
| const risk = riskMap[pred] || { level: 'Unknown', risk: 'UNKNOWN' }; |
| const desc = descriptions[pred] || 'N/A'; |
| |
| const lines = [ |
| 'DERMSIGHT PRO β ANALYSIS REPORT', |
| `Generated : ${new Date().toLocaleString()}`, |
| 'β'.repeat(52), |
| '', |
| `Primary Prediction : ${currentAnalysis.prediction.toUpperCase()}`, |
| `Confidence Score : ${parseFloat(currentAnalysis.confidence).toFixed(2)}%`, |
| `Risk Category : ${risk.risk}`, |
| `Classification Type : ${risk.level}`, |
| '', |
| 'Probability Distribution (sorted by confidence):', |
| ...Object.entries(currentAnalysis.all_probabilities) |
| .sort(([,a],[,b]) => parseFloat(b) - parseFloat(a)) |
| .map(([k,v]) => ` ${k.toUpperCase().padEnd(8)} : ${parseFloat(v).toFixed(2)}%`), |
| '', |
| 'Clinical Description:', |
| ` ${desc}`, |
| '', |
| 'β'.repeat(52), |
| 'Model : ResNet-50 (fine-tuned on HAM10000)', |
| 'Dataset : HAM10000 β 10,015 dermoscopic images', |
| 'Categories : 7 ISIC diagnostic classes', |
| '', |
| 'β DISCLAIMER: Research and educational use only.', |
| ' Not a medical diagnosis. Consult a qualified', |
| ' dermatologist for clinical assessment.', |
| '', |
| 'Developed by Nilotpal Dhar', |
| 'https://github.com/nilotpaldhar2004', |
| ].join('\n'); |
| |
| const blob = new Blob([lines], { type: 'text/plain' }); |
| const url = URL.createObjectURL(blob); |
| const a = document.createElement('a'); |
| a.href = url; |
| a.download = `dermsight_report_${Date.now()}.txt`; |
| a.click(); |
| URL.revokeObjectURL(url); |
| } |
| </script> |
| </body> |
| </html> |
|
|