app / templates /index.html
itsLu's picture
Update templates/index.html
ba12cc1 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>NeuraScan</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<style>
:root{
--bg:#0b1220;
--card:#0f1b33;
--card2:#0c172e;
--text:#e8eefc;
--muted:#a9b7d0;
--border:rgba(255,255,255,.12);
--primary:#4f7cff;
--ring:rgba(79,124,255,.35);
}
:root[data-theme="light"]{
--bg:#f6f7fb;
--card:#ffffff;
--card2:#ffffff;
--text:#0c1222;
--muted:#44506a;
--border:rgba(0,0,0,.12);
--primary:#355dff;
--ring:rgba(53,93,255,.25);
}
*{box-sizing:border-box}
body{
margin:0;
font-family:system-ui,-apple-system,Segoe UI,Roboto,sans-serif;
background:var(--bg);
color:var(--text);
}
header{
display:flex;
justify-content:space-between;
align-items:center;
padding:20px 28px;
}
.brand{display:flex;gap:12px;align-items:center}
.brand img{width:40px;height:40px}
.chip{
padding:8px 12px;
border-radius:999px;
background:var(--card);
border:1px solid var(--border);
cursor:pointer;
}
nav{
padding:0 28px 18px;
display:flex;
gap:10px;
}
.tab{
padding:8px 16px;
border-radius:999px;
border:1px solid var(--border);
background:rgba(255,255,255,.06);
cursor:pointer;
}
.tab.active{
background:rgba(79,124,255,.18);
box-shadow:0 0 0 4px var(--ring);
}
.section{display:none}
.section.show{display:block}
main{
display:grid;
grid-template-columns:1.2fr 1fr;
gap:24px;
padding:0 28px 28px;
}
@media(max-width:900px){main{grid-template-columns:1fr}}
.card{
background:linear-gradient(180deg,var(--card),var(--card2));
border-radius:18px;
border:1px solid var(--border);
padding:22px;
}
h2{margin:0 0 12px;font-size:20px}
.controls{
display:flex;
gap:12px;
flex-wrap:wrap;
align-items:center;
}
.select-wrap{position:relative}
.select-wrap::after{
content:"▾";
position:absolute;
right:14px;
top:50%;
transform:translateY(-50%);
pointer-events:none;
color:var(--muted);
}
select{
appearance:none;
padding:10px 40px 10px 14px;
border-radius:12px;
border:1px solid var(--border);
background:rgba(255,255,255,.06);
color:var(--text);
font-weight:600;
min-width:260px;
}
.file-btn{
padding:10px 14px;
border-radius:12px;
border:1px dashed var(--border);
cursor:pointer;
background:rgba(255,255,255,.04);
}
button.primary{
padding:10px 18px;
border-radius:12px;
border:none;
background:linear-gradient(180deg,var(--primary),#2f62ff);
color:white;
font-weight:700;
cursor:pointer;
}
button.secondary{
padding:10px 14px;
border-radius:12px;
border:1px solid var(--border);
background:rgba(255,255,255,.04);
cursor:pointer;
}
button:disabled{opacity:.6}
.upload-box{
margin-top:14px;
padding:18px;
border:2px dashed var(--border);
border-radius:14px;
text-align:center;
color:var(--muted);
}
.preview{margin-top:14px;background:black;border-radius:14px;overflow:hidden}
.preview.hidden{display:none}
.preview img{width:100%;max-height:360px;object-fit:contain}
.result{margin-top:16px}
.result-title{font-size:22px;font-weight:800}
.result-msg{color:var(--muted);margin-top:6px}
.examples .ex{
margin-top:12px;
border:1px solid var(--border);
border-radius:14px;
overflow:hidden;
}
.examples .lbl{padding:10px;font-weight:800;border-bottom:1px solid var(--border)}
.examples .desc{padding:0 10px 10px;color:var(--muted)}
.examples img{width:100%;max-height:280px;object-fit:contain;background:black}
.team{
display:grid;
grid-template-columns:repeat(auto-fit,minmax(240px,1fr));
gap:14px;
}
.member{
display:flex;
gap:12px;
align-items:center;
padding:10px;
border:1px solid var(--border);
border-radius:14px;
}
.member img{width:48px;height:48px;border-radius:50%;cursor:pointer}
footer{
padding:14px 28px;
border-top:1px solid var(--border);
font-size:13px;
color:var(--muted);
}
/* modal */
.modal-backdrop{
position:fixed;
inset:0;
background:rgba(0,0,0,.6);
display:none;
align-items:center;
justify-content:center;
}
.modal-backdrop.show{display:flex}
.modal{
background:var(--card);
padding:18px;
border-radius:16px;
border:1px solid var(--border);
width:420px;
}
</style>
</head>
<body>
<header>
<div class="brand">
<img src="{{ url_for('static', filename='assets/brain/brain.svg') }}">
<div>
<strong>NeuraScan</strong><br>
<small style="color:var(--muted)">AI-assisted MRI screening (demo)</small>
</div>
</div>
<button id="themeToggle" class="chip">☀️</button>
</header>
<nav>
<button class="tab active" onclick="showSection('scan',this)">Scan</button>
<button class="tab" onclick="showSection('info',this)">Info</button>
</nav>
<section id="scan" class="section show">
<main>
<div class="card">
<h2>New scan</h2>
<div class="controls">
<span class="select-wrap"><select id="modelSelect"></select></span>
<label class="file-btn">
Choose image
<input type="file" id="fileInput" accept="image/*" hidden>
</label>
<button class="primary" id="runBtn" onclick="runScan()">Run scan</button>
<button class="secondary" id="newBtn" style="display:none" onclick="newScan()">New scan</button>
</div>
<div class="upload-box" id="dropZone">
Drag & drop or paste (Ctrl+V) an MRI image
</div>
<div class="preview hidden" id="previewBox">
<img id="previewImg">
</div>
<div class="result">
<div id="resultTitle" class="result-title"></div>
<div id="resultMsg" class="result-msg">Upload an image to begin.</div>
<button id="likelyBtn" class="secondary" style="display:none;margin-top:10px" onclick="showLikely()">Show most likely result</button>
</div>
</div>
<div class="card">
<h2>Examples</h2>
<div class="examples">
<div class="ex">
<div class="lbl">Supported example</div>
<div class="desc">Brain-only MRI slice (no skull visible).</div>
<img src="{{ url_for('static', filename='assets/brain/supportedexample.jpg') }}">
</div>
<div class="ex">
<div class="lbl">Unsupported example</div>
<div class="desc">Skull visible — please upload a brain-only slice.</div>
<img src="{{ url_for('static', filename='assets/brain/unsupportedexample.jpg') }}">
</div>
</div>
</div>
</main>
</section>
<section id="info" class="section">
<main>
<div class="card">
<h2>About NeuraScan</h2>
<p>Educational demo for Alzheimer’s stage classification. Low confidence → <b>Uncertain</b>.</p>
<h2>Under the supervision of:</h2>
<p>
Prof. Muhammad Sayed Hammad<br>
Eng. Heidi Ahmed
</p>
<h2>Team</h2>
<div class="team">
<div class="member"><img src="{{ url_for('static', filename='assets/images/team/fatma.jpg') }}" onclick="window.open('https://www.linkedin.com/in/fatma-al-zahraa-emad-326b64234/')"><span>Fatma Al-Zahraa Emad</span></div>
<div class="member"><img src="{{ url_for('static', filename='assets/images/team/gehad.jpg') }}" onclick="window.open('https://www.linkedin.com/in/gehad-mohamed-2a4946252/')"><span>Gehad Mohamed</span></div>
<div class="member"><img src="{{ url_for('static', filename='assets/images/team/heba.jpg') }}" onclick="window.open('https://www.linkedin.com/in/hebatullah-elgazoly-308ab2243/')"><span>Hebatullah El Gazoly</span></div>
<div class="member"><img src="{{ url_for('static', filename='assets/images/team/asem.jpg') }}" onclick="window.open('https://www.linkedin.com/in/mohamedasem318/')"><span>Mohamed Assem</span></div>
<div class="member"><img src="{{ url_for('static', filename='assets/images/team/sameh.jpg') }}" onclick="window.open('https://www.linkedin.com/in/muhamedsameh/')"><span>Mohamed Sameh</span></div>
</div>
</div>
</main>
</section>
<footer>
Contact: <a href="mailto:mohamedasem318@gmail.com">Mohamed Assem</a>
<a href="mailto:mohamed.sameh8103@gmail.com">Mohamed Sameh</a><br>
Educational demo — not medical advice
</footer>
<div class="modal-backdrop" id="modalBackdrop" onclick="closeModalIfBackdrop(event)">
<div class="modal">
<h3>Most likely result</h3>
<p id="modalLabel"></p>
<p id="modalProb"></p>
<button class="secondary" onclick="closeModal()">Close</button>
</div>
</div>
<script>
function showSection(id,btn){
document.querySelectorAll('.section').forEach(s=>s.classList.remove('show'));
document.getElementById(id).classList.add('show');
document.querySelectorAll('.tab').forEach(t=>t.classList.remove('active'));
btn.classList.add('active');
}
function setTheme(t){
document.documentElement.dataset.theme=t;
localStorage.setItem("theme",t);
themeToggle.textContent=t==="dark"?"☀️":"🌙";
}
themeToggle.onclick=()=>setTheme(document.documentElement.dataset.theme==="dark"?"light":"dark");
setTheme(localStorage.getItem("theme")||"dark");
let currentFile=null,lastMostLikely=null;
const previewBox=document.getElementById("previewBox");
const previewImg=document.getElementById("previewImg");
function setFile(f){
currentFile=f;
previewImg.src=URL.createObjectURL(f);
previewBox.classList.remove("hidden");
resultTitle.textContent="—";
resultMsg.textContent="Ready to run scan.";
likelyBtn.style.display="none";
newBtn.style.display="none";
lastMostLikely=null;
}
fileInput.onchange=e=>e.target.files[0]&&setFile(e.target.files[0]);
dropZone.ondragover=e=>{e.preventDefault()};
dropZone.ondrop=e=>{
e.preventDefault();
e.dataTransfer.files[0]&&setFile(e.dataTransfer.files[0]);
};
window.addEventListener("paste",e=>{
for(const i of e.clipboardData.items){
if(i.type.startsWith("image/")){setFile(i.getAsFile());break;}
}
});
async function loadModels(){
const r=await fetch("/api/models");
const d=await r.json();
modelSelect.innerHTML="";
d.models.forEach(m=>{
const o=document.createElement("option");
o.value=m.id;o.textContent=m.name;
modelSelect.appendChild(o);
});
modelSelect.value=d.default_model_id;
}
async function runScan(){
if(!currentFile){resultMsg.textContent="Upload an image first.";return;}
runBtn.disabled=true;
modelSelect.disabled=true;
resultTitle.textContent="Running…";
resultMsg.textContent="Analyzing image…";
likelyBtn.style.display="none";
lastMostLikely=null;
try{
const fd=new FormData();
fd.append("file",currentFile);
fd.append("model_id",modelSelect.value);
const r=await fetch("/api/classify",{method:"POST",body:fd});
const d=await r.json();
if(d.prediction.label==="Uncertain"){
resultTitle.textContent="Uncertain";
resultMsg.textContent="Consult a professional.";
const probs=[...(d.probabilities||[])].sort((a,b)=>b.prob-a.prob);
if(probs[0]){
lastMostLikely=probs[0];
likelyBtn.style.display="inline-block";
}
}else{
resultTitle.textContent=d.prediction.label;
resultMsg.textContent=`Confidence: ${(d.prediction.confidence*100).toFixed(1)}%`;
likelyBtn.style.display="none";
lastMostLikely=null;
}
newBtn.style.display="inline-block";
}catch(e){
resultTitle.textContent="Sorry";
resultMsg.textContent="Something went wrong.";
}finally{
runBtn.disabled=false;
modelSelect.disabled=false;
}
}
function newScan(){
currentFile=null;
fileInput.value="";
previewBox.classList.add("hidden");
previewImg.src="";
resultTitle.textContent="—";
resultMsg.textContent="Upload an image to begin.";
likelyBtn.style.display="none";
newBtn.style.display="none";
lastMostLikely=null;
}
function showLikely(){
if(!lastMostLikely)return;
modalLabel.textContent=lastMostLikely.label;
modalProb.textContent=`Probability: ${(lastMostLikely.prob*100).toFixed(1)}%`;
modalBackdrop.classList.add("show");
}
function closeModal(){modalBackdrop.classList.remove("show")}
function closeModalIfBackdrop(e){if(e.target.id==="modalBackdrop")closeModal()}
loadModels();
</script>
</body>
</html>