<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8" />
  <title>Register Student</title>
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <style>
    :root{
      --bg:#F7F8FA; --surface:#ffffff; --text:#0f172a; --muted:#64748b; --primary:#2563eb;
      --warn:#ee6c4d; --ok:#10b981; --border:#e5e7eb; --chip:#f1f5f9; --sidebar:#111827; --sidefg:#e5e7eb;
    }
    *{box-sizing:border-box}
    body{font-family:system-ui, -apple-system, Segoe UI, Roboto, Arial; color:var(--text); background:var(--bg); margin:0;}
    a{color:var(--primary); text-decoration:none}
    a:hover{text-decoration:underline}

    /* ===== Sidebar ===== */
    .shell{display:flex; min-height:100vh;}
    .sidebar{
      width:260px; background:var(--sidebar); color:var(--sidefg); padding:18px 16px; position:sticky; top:0; height:100dvh;
      display:flex; flex-direction:column; gap:16px; border-right:1px solid rgba(255,255,255,.06);
    }
    .brand{display:flex; align-items:center; gap:10px; font-weight:800; letter-spacing:.2px}
    .brand .dot{width:10px; height:10px; border-radius:50%; background:var(--ok)}
    .side-nav{display:flex; flex-direction:column; gap:6px; margin-top:6px}
    .side-link{
      display:flex; align-items:center; gap:10px; padding:10px 12px; border-radius:10px; color:var(--sidefg);
      text-decoration:none; border:1px solid transparent; transition:background .12s, transform .04s;
    }
    .side-link:hover{background:rgba(255,255,255,.06)}
    .side-link.active{background:#1f2937; border-color:#374151}
    .side-spacer{flex:1}
    .side-foot{font-size:12px; opacity:.75}

    .hamburger{display:none; position:fixed; top:12px; left:12px; z-index:50; background:var(--sidebar); color:var(--sidefg);
      border:1px solid #2b3440; border-radius:10px; padding:8px 10px; cursor:pointer}

    /* ===== Content ===== */
    .content{flex:1; display:block}
    .container{max-width:1200px; margin:24px auto; padding:0 16px;}
    .topbar{display:flex; gap:12px; align-items:center; justify-content:space-between; margin-bottom:16px}
    .breadcrumbs{display:flex; gap:10px; align-items:center; flex-wrap:wrap; font-size:14px; color:var(--muted)}
    .breadcrumbs a{color:var(--text)}
    .page-title{font-size:28px; font-weight:800; letter-spacing:.2px}

    .layout{display:grid; grid-template-columns: 1.1fr .9fr; gap:24px}
    @media (max-width: 980px){ .layout{grid-template-columns:1fr} }

    .panel{background:var(--surface); border:1px solid var(--border); border-radius:12px; padding:14px 14px}
    .panel h3{margin:0 0 10px 0; font-size:16px}

    .grid{display:grid; grid-template-columns: repeat(2, minmax(0,1fr)); gap:12px}
    @media (max-width: 640px){ .grid{grid-template-columns:1fr} }

    label{display:block; margin:6px 0 6px 2px; font-size:13px; color:var(--muted)}
    input, select{
      width:100%; padding:10px 12px; border:1px solid var(--border); border-radius:10px;
      background:#fff; outline:none; transition:box-shadow .15s,border-color .15s; font-size:14px;
    }
    input:focus, select:focus{border-color:#c7d2fe; box-shadow:0 0 0 3px rgba(37,99,235,.15)}

    .row{display:flex; gap:16px; align-items:flex-start; flex-wrap:wrap}
    .kv{display:flex; gap:10px; align-items:center; flex-wrap:wrap}

    .button{
      padding:10px 14px; border:none; border-radius:10px; cursor:pointer; font-weight:600; font-size:14px;
      background:var(--primary); color:#fff; box-shadow:0 4px 12px rgba(37,99,235,.15);
      transition:transform .05s ease, filter .15s;
    }
    .button:active{transform:translateY(1px)}
    .button.secondary{background:#475569}
    .button.warn{background:var(--warn)}
    .button:disabled{opacity:.6; cursor:not-allowed; filter:grayscale(.15)}

    .video-wrap{ position: relative; width: 100%; max-width: 560px; aspect-ratio: 4/3; background:#000;
      border-radius:12px; overflow:hidden; margin:6px 0; }
    video{position:absolute; inset:0; width:100%; height:100%; transform:scaleX(1); object-fit:cover}
    .prompt{margin-top:8px; font-weight:700}
    .progress{display:flex; gap:6px; margin-top:8px; flex-wrap:wrap}
    .dot{width:10px; height:10px; border-radius:50%; background:#d1d5db}
    .dot.ok{background:var(--ok)}
    .gallery{display:flex; gap:6px; flex-wrap:wrap; margin-top:8px}
    .gallery img{width:70px; height:70px; object-fit:cover; border-radius:8px; border:1px solid var(--border); background:#000}

    .subgrid{display:grid; grid-template-columns: repeat(2,minmax(0,1fr)); gap:16px}
    @media (max-width: 640px){ .subgrid{grid-template-columns:1fr} }

    .meter{height:8px; background:#eef2f7; border-radius:5px; width:160px; overflow:hidden; border:1px solid var(--border)}
    .meter > i{display:block; height:100%; width:0; background:linear-gradient(90deg,#f59e0b,#10b981)}
    .small{font-size:12px; color:var(--muted)}

    .log{background:#0b1220; color:#e5e7eb; border-radius:12px; border:1px solid #111827; padding:10px; height:280px; overflow:auto; font:12px/1.4 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace}
    .log div{padding:4px 0; border-bottom:1px dashed rgba(255,255,255,.06)}
    .log div:last-child{border-bottom:none}

    #grab{display:none}

    /* Mobile sidebar */
    @media (max-width: 900px){
      .sidebar{position:fixed; left:-280px; z-index:40; transition:left .18s ease}
      .sidebar.open{left:0}
      .hamburger{display:block}
      .content{margin-left:0}
    }
  </style>
</head>
<body>
  <button class="hamburger" id="hamburger">☰ Menu</button>
  <div class="shell">
   <!-- sidebar.html -->
<aside class="sidebar" id="sidebar">
  <div class="brand">
    <span class="dot"></span> <span>FaceAttend</span>
  </div>

  <nav class="side-nav">
   <a class="side-link" href="/admin">Admin Panel</a>
    <a class="side-link" href="/register">Register (Users)</a>
    <a class="side-link" href="/attendance">Take Attendance</a>
     <a class="side-link" href="/logout">Logout</a>
  </nav>

  <div class="side-spacer"></div>
  <div class="side-foot">© <span id="yy"></span> • v1.1</div>
</aside>

<script>
  document.getElementById('yy').textContent = new Date().getFullYear();
</script>


    <!-- Content -->
    <main class="content">
      <div class="container">
        <div class="topbar">
          <div class="breadcrumbs">
            <span class="page-title">Register Student</span>
          </div>
        </div>

        <div class="layout">
          <!-- LEFT: form + camera -->
          <div>
            <div class="panel" aria-labelledby="formTitle">
              <h3 id="formTitle">Student Info</h3>
              <div class="grid" style="margin-top:6px">
                <div>
                  <label for="code">Student Code</label>
                  <input id="code" placeholder="e.g., S-0101" />
                </div>
                <div>
                  <label for="name">Full Name</label>
                  <input id="name" placeholder="Full name" />
                </div>
                <div>
                  <label for="cls">Class</label>
                  <input id="cls" placeholder="e.g., 10-A" />
                </div>
                <div>
                  <label for="res">Resolution</label>
                  <select id="res">
                    <option value="640x480">480p (640×480)</option>
                    <option value="1280x720">720p (1280×720)</option>
                    <option value="480x360" selected>360p (480×360)</option>
                  </select>
                </div>
              </div>

              <div class="row" style="margin-top:12px">
                <button id="btnCreate" class="button">Create / Load Student</button>
                <button id="btnStart" class="button" disabled>Start</button>
                <button id="btnPause" class="button secondary" disabled>Pause</button>
                <button id="btnManual" class="button secondary" disabled>Manual Capture</button>
                <button id="btnStop" class="button warn" disabled>Stop</button>
              </div>
            </div>

            <div class="panel" style="margin-top:14px" aria-labelledby="captureTitle">
              <h3 id="captureTitle">Camera & Capture</h3>
              <div class="video-wrap">
                <video id="video" autoplay playsinline muted></video>
              </div>

              <div class="kv" style="margin:6px 0 2px">
                <label class="kv"><input type="checkbox" id="mirror" checked /> Mirror video</label>
                <label class="kv"><input type="checkbox" id="autoTrain" checked /> Auto-train when finished</label>
                <label class="kv"><input type="checkbox" id="showGallery" checked /> Show preview gallery</label>
              </div>

              <div class="prompt" id="prompt">Prompt: —</div>
              <div class="progress" id="progress"></div>
              <div class="gallery" id="gallery" aria-live="polite"></div>
            </div>

            <div class="panel" style="margin-top:14px" aria-labelledby="capOpts">
              <h3 id="capOpts">Capture Options</h3>

              <div class="subgrid">
                <div>
                  <label for="shots">Shots per expression</label>
                  <input id="shots" type="number" min="3" max="30" value="10" />
                </div>
                <div>
                  <label for="interval">Interval (ms)</label>
                  <input id="interval" type="number" min="200" step="50" value="800" />
                </div>
                <div>
                  <label for="nth">Save every Nth frame</label>
                  <input id="nth" type="number" min="1" value="1" />
                  <div class="small">Use 2 or 3 to reduce files.</div>
                </div>
                <div>
                  <label class="kv" for="qualityGate"><input type="checkbox" id="qualityGate" checked /> Enable quality gate</label>
                  <div class="small">Only save when sharp & well-lit</div>
                </div>
              </div>

              <div class="subgrid" style="margin-top:6px">
                <div>
                  <label for="minSharp">Min Sharpness (Laplacian)</label>
                  <input id="minSharp" type="number" min="20" max="500" value="120" />
                  <div class="kv" style="margin-top:6px">
                    <span class="small">Live:</span>
                    <div class="meter"><i id="sharpBar"></i></div>
                    <span id="sharpVal" class="small">0</span>
                  </div>
                </div>
                <div>
                  <label>Brightness range</label>
                  <div class="kv">
                    <input id="minBright" type="number" min="0" max="255" value="70" />
                    <span>to</span>
                    <input id="maxBright" type="number" min="0" max="255" value="190" />
                  </div>
                  <div class="kv" style="margin-top:6px">
                    <span class="small">Live:</span>
                    <div class="meter"><i id="brightBar"></i></div>
                    <span id="brightVal" class="small">0</span>
                  </div>
                </div>
              </div>
            </div>

            <div class="panel" style="margin-top:14px" aria-labelledby="expTitle">
              <h3 id="expTitle">Expressions to capture</h3>
              <div class="row">
                <label><input type="checkbox" class="exp" value="neutral" checked /> Neutral</label>
                <label><input type="checkbox" class="exp" value="smile" checked /> Smile</label>
                <label><input type="checkbox" class="exp" value="brows_up" checked /> Eyebrows Up</label>
                <label><input type="checkbox" class="exp" value="eyes_closed" /> Eyes Closed</label>
                <label><input type="checkbox" class="exp" value="look_left" /> Look Left</label>
                <label><input type="checkbox" class="exp" value="look_right" /> Look Right</label>
                <label><input type="checkbox" class="exp" value="look_up" /> Look Up</label>
                <label><input type="checkbox" class="exp" value="look_down" /> Look Down</label>
              </div>

              <div class="row" style="margin-top:12px">
                <button id="btnTrain" class="button" disabled>Train Now</button>
              </div>
            </div>
          </div>

          <!-- RIGHT: log -->
          <div>
            <div class="panel" aria-labelledby="logTitle">
              <h3 id="logTitle">Log</h3>
              <div id="log" class="log"></div>
            </div>
          </div>
        </div>
      </div>
    </main>
  </div>

  <!-- hidden canvas -->
  <canvas id="grab" width="480" height="360"></canvas>

  <!-- ===== JS (existing functionality preserved) ===== -->
  <script>
    // Sidebar toggling for mobile
    const hamburger = document.getElementById('hamburger');
    const sidebar = document.getElementById('sidebar');
    hamburger.addEventListener('click', ()=>{ sidebar.classList.toggle('open'); });
    document.getElementById('yy').textContent = new Date().getFullYear();

    // --- Elements
    const video = document.getElementById('video');
    const grab = document.getElementById('grab');
    const gctx = grab.getContext('2d');
    const code = document.getElementById('code');
    const nameI = document.getElementById('name');
    const clsI = document.getElementById('cls');

    const btnCreate = document.getElementById('btnCreate');
    const btnStart = document.getElementById('btnStart');
    const btnPause = document.getElementById('btnPause');
    const btnManual = document.getElementById('btnManual');
    const btnStop = document.getElementById('btnStop');
    const btnTrain = document.getElementById('btnTrain');

    const resSel = document.getElementById('res');
    const mirrorCk = document.getElementById('mirror');
    const autoTrainCk = document.getElementById('autoTrain');
    const showGalleryCk = document.getElementById('showGallery');

    const shotsI = document.getElementById('shots');
    const intervalI = document.getElementById('interval');
    const nthI = document.getElementById('nth');

    const qualityGateCk = document.getElementById('qualityGate');
    const minSharpI = document.getElementById('minSharp');
    const minBrightI = document.getElementById('minBright');
    const maxBrightI = document.getElementById('maxBright');
    const sharpBar = document.getElementById('sharpBar');
    const brightBar = document.getElementById('brightBar');
    const sharpVal = document.getElementById('sharpVal');
    const brightVal = document.getElementById('brightVal');

    const promptEl = document.getElementById('prompt');
    const progress = document.getElementById('progress');
    const gallery = document.getElementById('gallery');
    const logEl = document.getElementById('log');

    const expChecks = Array.from(document.querySelectorAll('.exp'));

    // --- State
    let stream = null, running = false, paused = false, timer = null;
    let stepIndex = 0, shot = 0, saved = 0, saveCounter = 0;
    let steps = [];

    // --- Utils
    function log(t){ const d=document.createElement('div'); d.textContent=new Date().toLocaleTimeString()+' - '+t; logEl.prepend(d); }
    function setPrompt(){ const s = steps[stepIndex] || null; promptEl.textContent = s ? `Prompt: ${s.text}` : 'Prompt: —'; }
    function buildDots(){
      progress.innerHTML='';
      const total = steps.length * Number(shotsI.value);
      for(let i=0;i<total;i++){ const d=document.createElement('div'); d.className='dot'; progress.appendChild(d); }
    }
    function markDot(ix){ const d=progress.children[ix]; if(d) d.classList.add('ok'); }
    function frameDataURL(){
      gctx.save();
      gctx.translate(grab.width, 0);
      gctx.scale(mirrorCk.checked ? -1 : 1, 1);
      gctx.drawImage(video, 0, 0, grab.width, grab.height);
      gctx.restore();
      return grab.toDataURL('image/jpeg', 0.9);
    }

    function laplacianSharpness(imgData){
      const d = imgData.data; let sx=0, sxx=0, n=0;
      for(let i=0;i<d.length;i+=16){
        const r=d[i], g=d[i+1], b=d[i+2];
        const y = 0.299*r + 0.587*g + 0.114*b;
        sx += y; sxx += y*y; n++;
      }
      const mean = sx/n, varY = sxx/n - mean*mean;
      return varY;
    }

    function brightnessMean(imgData){
      const d = imgData.data; let sx=0, n=0;
      for(let i=0;i<d.length;i+=16){ const r=d[i], g=d[i+1], b=d[i+2]; sx += 0.299*r + 0.587*g + 0.114*b; n++; }
      return sx/n;
    }

    function updateMeters(){
      const w=160, h=120;
      const tmp = document.createElement('canvas'); tmp.width=w; tmp.height=h;
      const tctx = tmp.getContext('2d');
      tctx.drawImage(video, 0,0,w,h);
      const img = tctx.getImageData(0,0,w,h);
      const sharp = laplacianSharpness(img);
      const bright = brightnessMean(img);

      const sharpMin=0, sharpMax=400;
      const bMin=0, bMax=255;
      const sharpPct = Math.max(0, Math.min(1, (sharp - sharpMin)/(sharpMax - sharpMin)));
      const brightPct = Math.max(0, Math.min(1, (bright - bMin)/(bMax - bMin)));

      sharpBar.style.width = (sharpPct*160)+'px';
      brightBar.style.width = (brightPct*160)+'px';
      sharpVal.textContent = sharp.toFixed(0);
      brightVal.textContent = bright.toFixed(0);

      return { sharp, bright };
    }

    async function openCam(){
      if(stream) return;
      const [w,h] = resSel.value.split('x').map(Number);
      try{
        stream = await navigator.mediaDevices.getUserMedia({ video:{ width:{ideal:w}, height:{ideal:h} }, audio:false });
        video.srcObject = stream;
        await video.play();
        const wrap = document.querySelector('.video-wrap');
        const rect = wrap.getBoundingClientRect();
        grab.width = Math.round(rect.width);
        grab.height = Math.round(rect.height);
      }catch(e){ log('Webcam error: '+e.message); }
    }
    function closeCam(){ if(stream){ stream.getTracks().forEach(t=>t.stop()); stream=null; } video.srcObject=null; }

    async function saveFrame(exprKey){
      const dataURL = frameDataURL();
      const payload = { student_code: code.value.trim(), image_base64: dataURL, expression: exprKey };
      const r = await fetch('/api/save_image', {
        method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify(payload)
      }).then(r=>r.json());
      if(!r.ok){ log('Save failed: '+(r.error||'unknown')); }
      if(showGalleryCk.checked){
        const img = new Image(); img.src = dataURL; gallery.prepend(img);
        while(gallery.children.length > 12) gallery.removeChild(gallery.lastChild);
      }
    }

    function buildSteps(){
      const selected = expChecks.filter(x=>x.checked).map(x=>x.value);
      const map = {
        neutral: 'Look straight with neutral face',
        smile: 'Smile 🙂',
        brows_up: 'Raise your eyebrows',
        eyes_closed: 'Close your eyes briefly',
        look_left: 'Turn your head slightly left',
        look_right: 'Turn your head slightly right',
        look_up: 'Look up slightly',
        look_down: 'Look down slightly'
      };
      steps = selected.map(k => ({key:k, text: map[k] || k}));
    }

    function startLoop(){
      const INTERVAL_MS = Number(intervalI.value);
      const NTH = Math.max(1, Number(nthI.value));
      const perStep = Math.max(1, Number(shotsI.value));
      const totalDots = steps.length * perStep;
      let dotIndex = 0;
      let frameIndex = 0;

      timer = setInterval(async ()=>{
        if(!running || paused) return;

        const q = updateMeters();
        const sharpOK = q.sharp >= Number(minSharpI.value);
        const brightOK = q.bright >= Number(minBrightI.value) && q.bright <= Number(maxBrightI.value);

        setPrompt();

        frameIndex++;
        if(frameIndex % NTH !== 0) return;

        if(qualityGateCk.checked && !(sharpOK && brightOK)){
          return;
        }

        const exprKey = steps[stepIndex].key;
        await saveFrame(exprKey);
        saved++;

        markDot(dotIndex);
        dotIndex++;

        shot++;
        if(shot >= perStep){
          stepIndex++; shot = 0;
        }
        if(stepIndex >= steps.length){
          stopCapture();
          btnTrain.disabled = false;
          log(`Capture complete. Saved ${saved} images.`);
          if(autoTrainCk.checked){
            await doTrain();
          }
        }
      }, INTERVAL_MS);
    }

    function startCapture(){
      if(running) return;
      buildSteps();
      if(steps.length === 0){ log('Select at least one expression.'); return; }
      const perStep = Math.max(1, Number(shotsI.value));
      progress.innerHTML='';
      for(let i=0;i<steps.length*perStep;i++){ const d=document.createElement('div'); d.className='dot'; progress.appendChild(d); }
      stepIndex=0; shot=0; saved=0;
      setPrompt();
      running = true; paused = false;
      btnStart.disabled = true; btnPause.disabled = false; btnManual.disabled = false; btnStop.disabled = false;
      log('Capture started.');
      startLoop();
    }

    function pauseCapture(){
      paused = !paused;
      btnPause.textContent = paused ? 'Resume' : 'Pause';
      log(paused ? 'Paused.' : 'Resumed.');
    }

    function stopCapture(){
      running = false;
      paused = false;
      if(timer){ clearInterval(timer); timer=null; }
      btnStart.disabled = false; btnPause.disabled = true; btnManual.disabled = true; btnStop.disabled = true;
      setPrompt();
      log('Stopped.');
    }

    async function doTrain(){
      btnTrain.disabled = true;
      log('Training… (with augmentation in train.py)');
      const r = await fetch('/api/train',{method:'POST',headers:{'Content-Type':'application/json'},
        body: JSON.stringify({mode:'full'})}).then(r=>r.json());
      if(r.ok){
        log(`Training OK: ${r.num_students} students, ${r.num_faces} faces. Thr=${r.calibration.lbph_threshold.toFixed(1)}`);
      }else{
        log('Training failed: '+(r.error||'unknown'));
      }
    }

    // --- Event wiring (unchanged behavior)
    btnCreate.onclick = async ()=>{
      const c=code.value.trim(), n=nameI.value.trim(), cl=clsI.value.trim();
      if(!c||!n||!cl){ log('Fill code, name, class first.'); return; }
      const r = await fetch('/api/register_student',{method:'POST',headers:{'Content-Type':'application/json'},
        body:JSON.stringify({student_code:c, full_name:n, class_name:cl})}).then(r=>r.json());
      if(r.ok){ log('Student ready: '+c); btnStart.disabled=false; btnTrain.disabled=false; await openCam(); }
      else { log('Register failed: '+(r.error||'unknown')); }
    };

    btnStart.onclick = startCapture;
    btnPause.onclick = pauseCapture;
    btnStop.onclick = stopCapture;
    btnManual.onclick = async ()=>{
      if(!stream){ log('Open camera first.'); return; }
      const q = updateMeters();
      const sharpOK = q.sharp >= Number(minSharpI.value);
      const brightOK = q.bright >= Number(minBrightI.value) && q.bright <= Number(maxBrightI.value);
      if(qualityGateCk.checked && !(sharpOK && brightOK)){
        log('Manual capture rejected by quality gate.');
        return;
      }
      const s = steps[stepIndex] || {key:'manual', text:'Manual capture'};
      await saveFrame(s.key);
      log('Manual frame saved.');
    };

    btnTrain.onclick = doTrain;

    mirrorCk.addEventListener('change', ()=>{
      video.style.transform = mirrorCk.checked ? 'scaleX(-1)' : 'scaleX(1)';
    });
    mirrorCk.dispatchEvent(new Event('change'));

    resSel.addEventListener('change', async ()=>{
      if(stream){ closeCam(); await openCam(); }
    });

    window.addEventListener('beforeunload', closeCam);
  </script>
</body>
</html>
