Mobile-emulator / camera.html
Akira
Initial commit without large assets
4e5205d
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Phoenix Camera Pro — v2.3</title>
<style>
:root{--bg:#070507;--gold:#f6b24a;--muted:rgba(255,255,255,0.68);--glass:rgba(6,6,6,0.45)}
*{box-sizing:border-box;margin:0;padding:0}
html,body{height:100%;background:var(--bg);color:#fff;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial}
.camera-view{width:100vw;height:100vh;position:relative;overflow:hidden;background:linear-gradient(180deg,#000 0%, #070507 100%);touch-action:none}
#cameraVideo{width:100%;height:100%;object-fit:cover;display:block;transition:transform .18s ease,filter .2s}
#aiOverlay{position:absolute;inset:0;width:100%;height:100%;pointer-events:none;opacity:0;transition:opacity .38s ease}
.top-bar{position:absolute;top:14px;left:12px;right:12px;display:flex;align-items:center;justify-content:space-between;z-index:120;backdrop-filter:blur(12px);background:var(--glass);padding:6px;border-radius:12px}
.top-left,.top-right{display:flex;gap:10px;align-items:center}
.icon-btn{min-width:44px;height:44px;border-radius:10px;display:flex;align-items:center;justify-content:center;background:transparent;border:1px solid rgba(255,255,255,0.04);cursor:pointer;color:var(--muted);transition:all .14s;position:relative}
.icon-btn.active{color:var(--gold);box-shadow:0 6px 20px rgba(246,178,74,0.06)}
.icon-label{position:absolute;left:50%;transform:translateX(-50%);bottom:-34px;background:rgba(0,0,0,0.6);padding:6px 8px;border-radius:10px;font-size:12px;opacity:0;pointer-events:none;color:#fff;white-space:nowrap;transition:opacity .18s,transform .18s}
.icon-label.show{opacity:1;transform:translateX(-50%) translateY(-4px)}
.mode-strip{position:absolute;left:0;right:0;bottom:170px;display:flex;justify-content:center;z-index:80}
.mode-list{display:flex;gap:18px;overflow:auto;padding:6px 12px;background:transparent;border-radius:999px}
.mode-item{padding:8px 10px;font-size:13px;color:rgba(255,255,255,0.78);cursor:pointer;text-transform:uppercase;letter-spacing:.6px}
.mode-item.active{color:#fff;border-bottom:2px solid var(--gold);font-weight:700}
.bottom-bar{position:absolute;left:0;right:0;bottom:70px;display:flex;justify-content:space-between;align-items:center;padding:8px 18px;z-index:120;backdrop-filter:blur(10px);background:var(--glass);border-radius:14px;margin:0 12px}
.thumb{width:56px;height:56px;border-radius:12px;overflow:hidden;border:2px solid rgba(255,255,255,0.06);background:#111;background-size:cover;background-position:center;cursor:pointer}
.shutter-wrap{display:flex;align-items:center;gap:18px}
.shutter-btn{width:84px;height:84px;border-radius:50%;background:linear-gradient(180deg,#fff,#f1e9d6);display:flex;align-items:center;justify-content:center;border:6px solid rgba(246,178,74,0.12);box-shadow:0 10px 30px rgba(0,0,0,0.6);cursor:pointer;position:relative}
.shutter-inner{width:46px;height:46px;border-radius:50%;background:#fff;border:6px solid rgba(0,0,0,0.06);box-shadow:inset 0 2px 6px rgba(0,0,0,0.12);transition:background .14s, box-shadow .14s}
.shutter-inner.recording{background:#ff3b30;box-shadow:0 0 0 6px rgba(255,59,48,0.08) inset}
.switch-btn{width:56px;height:56px;border-radius:12px;background:transparent;display:flex;align-items:center;justify-content:center;border:1px solid rgba(255,255,255,0.04);cursor:pointer}
.center-focus{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);width:96px;height:96px;border-radius:50%;display:flex;align-items:center;justify-content:center;pointer-events:none;z-index:95}
.focus-ring{position:absolute;width:64px;height:64px;border-radius:50%;border:2px solid rgba(246,178,74,0.9);opacity:0;transform:scale(.6);transition:all .34s}
.focus-ring.active{opacity:1;transform:scale(1);animation:focusPulse .9s ease-out}
@keyframes focusPulse{0%{transform:scale(.8);opacity:.9}50%{transform:scale(1.05)}100%{transform:scale(1);opacity:.9}}
.zoom-indicator{position:absolute;right:22px;top:50%;transform:translateY(-50%);font-size:15px;color:var(--gold);z-index:140;opacity:0;transition:opacity .25s}
.zoom-indicator.active{opacity:1}
.flash-effect{position:absolute;inset:0;background:#fff;opacity:0;pointer-events:none;z-index:150}
.flash-effect.on{animation:flashAnim .5s ease}
@keyframes flashAnim{0%{opacity:0}10%{opacity:1}60%{opacity:1}100%{opacity:0}}
.settings-page{position:absolute;top:0;right:0;width:100%;height:100%;background:linear-gradient(180deg,rgba(5,3,2,0.98),rgba(9,6,3,0.98));transform:translateX(100%);transition:transform .45s cubic-bezier(.2,.9,.2,1);z-index:200;display:flex;flex-direction:column}
.settings-page.open{transform:translateX(0)}
.settings-header{display:flex;align-items:center;padding:18px;border-bottom:1px solid rgba(255,255,255,0.04)}
.settings-title{flex:1;text-align:center;font-size:18px;color:var(--gold);font-weight:700}
.settings-list{padding:12px 8px;overflow:auto}
.option{display:flex;align-items:center;justify-content:space-between;padding:14px;border-bottom:1px solid rgba(255,255,255,0.03)}
.option label{font-size:15px;color:#fff}
.select, .toggle{background:transparent;color:#fff;border:1px solid rgba(255,255,255,0.06);padding:8px;border-radius:8px}
.info-overlay{position:absolute;left:12px;top:12px;padding:8px 12px;background:rgba(0,0,0,0.45);border-radius:12px;font-size:13px;z-index:130}
.grid-overlay{position:absolute;inset:0;pointer-events:none;z-index:85;display:none}
.grid-overlay.on{display:block}
.grid-line{position:absolute;background:rgba(255,255,255,0.12)}
.grid-line.v{width:1px;height:100%}
.grid-line.h{height:1px;width:100%}
.recording-indicator{position:absolute;left:50%;top:14px;transform:translateX(-50%);background:rgba(0,0,0,0.5);padding:8px 12px;border-radius:18px;display:flex;align-items:center;gap:8px;font-weight:600;color:#fff;z-index:210;opacity:0;transition:opacity .2s}
.recording-indicator.on{opacity:1}
.record-dot{width:12px;height:12px;border-radius:50%;background:#ff3b30;box-shadow:0 0 8px rgba(255,59,48,0.8)}
.timer-text{font-family:monospace}
.toast-center{position:fixed;left:50%;top:48%;transform:translate(-50%,-50%) scale(.98);background:rgba(0,0,0,0.75);color:#fff;padding:10px 16px;border-radius:18px;font-size:15px;z-index:900;opacity:0;pointer-events:none;transition:opacity .28s ease,transform .28s ease}
.toast-center.show{opacity:1;transform:translate(-50%,-50%) scale(1)}
.toast-center.hide{opacity:0;transform:translate(-50%,-50%) scale(.98)}
.lunid-modal-overlay{position:fixed;inset:0;background:rgba(0,0,0,0.9);z-index:10000;display:none;align-items:center;justify-content:center;overflow-y:auto}
.lunid-modal-overlay.show{display:flex}
.lunid-modal-close{position:absolute;top:16px;right:16px;background:none;border:none;color:#fff;font-size:28px;cursor:pointer}
</style>
<script src="/static/lunid-auth.js"></script>
</head>
<body>
<div id="lunidModalOverlay" class="lunid-modal-overlay">
<button class="lunid-modal-close" onclick="closeLunidModal()">&times;</button>
<div id="lunidAuthContainer" style="width:100%;max-width:400px"></div>
</div>
<div class="camera-view" id="cameraRoot">
<video id="cameraVideo" autoplay playsinline></video>
<canvas id="cameraCanvas" style="display:none"></canvas>
<canvas id="aiOverlay"></canvas>
<div class="flash-effect" id="flashEffect"></div>
<div class="top-bar">
<div class="top-left">
<button class="icon-btn" onclick="if(window.parent !== window) { window.parent.postMessage({action:'closeApp'}, '*'); } else { window.location.href='index.html'; }" title="Back to Home">
<svg viewBox="0 0 24 24" style="width:20px;height:20px;fill:currentColor"><path d="M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z"/></svg>
<div class="icon-label">Home</div>
</button>
<button class="icon-btn" id="flashBtn" title="Flash">
<svg viewBox="0 0 24 24" style="width:20px;height:20px;fill:currentColor"><path d="M7 2v11h3v9l7-12h-4l4-8z"/></svg>
<div class="icon-label">Flash</div>
</button>
<button class="icon-btn" id="hdrBtn" title="HDR">
<svg viewBox="0 0 24 24" style="width:20px;height:20px;fill:currentColor"><path d="M12 6l-2 4-4 .5 3 2.8L8 18l4-2 4 2-1-4.7L20 10.5 16 10z"/></svg>
<div class="icon-label">HDR</div>
</button>
<button class="icon-btn" id="aiBtn" title="AI Enhance">AI<div class="icon-label">AI Enhance</div></button>
</div>
<div class="top-right">
<div id="signInBtnContainer" style="display:flex;align-items:center"></div>
<button class="icon-btn" id="filterBtn" title="Filter">
<svg viewBox="0 0 24 24" style="width:20px;height:20px;fill:currentColor"><path d="M10 4H4v6h2V6h4V4zm10 0h-6v2h4v4h2V4zM4 14v6h6v-2H6v-4H4zm14 6v-4h-2v4h-4v2h6v-2z"/></svg>
<div class="icon-label">Filter</div>
</button>
<button class="icon-btn" id="menuBtn" title="Settings">
<svg viewBox="0 0 24 24" style="width:20px;height:20px;fill:currentColor"><path d="M12 8a4 4 0 1 0 0 8 4 4 0 0 0 0-8zm8.94 1.06l-1.43-.82a7.98 7.98 0 0 0-.9-1.56l.77-1.5L18.3 4l-1.5.77a7.98 7.98 0 0 0-1.56-.9L14 2.06 12 2l-1.99.06L9.17 3.4a7.98 7.98 0 0 0-1.56.9L6.11 3.54 4.8 4.8l.77 1.5c-.35.5-.62 1.06-.9 1.56L3.39 9.59 4.82 11l1.5-.77c.5.35 1.06.62 1.56.9L9.17 13l.84 1.99L12 16l1.99-.06.84-1.99c.5-.28 1.06-.55 1.56-.9l1.5.77 1.43-1.43-.77-1.5c.35-.5.62-1.06.9-1.56l1.43-.82z"/></svg>
<div class="icon-label">Settings</div>
</button>
</div>
</div>
<div class="info-overlay" id="infoOverlay">PHOTO</div>
<div class="center-focus"><div class="focus-ring" id="focusRing"></div></div>
<div class="zoom-indicator" id="zoomIndicator">1.0x</div>
<div class="mode-strip"><div class="mode-list" id="modeList">
<div class="mode-item">Short Video</div>
<div class="mode-item">Video</div>
<div class="mode-item active">Photo</div>
<div class="mode-item">Portrait</div>
<div class="mode-item">Night</div>
<div class="mode-item">Pro</div>
</div></div>
<div class="grid-overlay" id="gridOverlay">
<div class="grid-line v" style="left:33.33%"></div>
<div class="grid-line v" style="left:66.66%"></div>
<div class="grid-line h" style="top:33.33%"></div>
<div class="grid-line h" style="top:66.66%"></div>
</div>
<div class="bottom-bar">
<div class="thumb" id="thumbPreview" onclick="localStorage.setItem('openGallery', 'true'); if(window.parent !== window) { window.parent.postMessage({action:'closeApp'}, '*'); } else { window.location.href='index.html'; }"></div>
<div class="shutter-wrap">
<button class="shutter-btn" id="shutterBtn" title="Capture"><div class="shutter-inner" id="shutterInner"></div></button>
</div>
<button class="switch-btn" id="switchBtn" title="Switch Camera">
<svg viewBox="0 0 24 24" style="width:20px;height:20px;fill:currentColor"><path d="M12 6v3l4-4-4-4v3c-4.97 0-9 4.03-9 9 0 1.66.43 3.22 1.18 4.57L5.1 20.6C3.95 18.88 3.2 16.55 3.2 14c0-5.52 4.48-10 10-10zm6.8 1.4L20.9 3.4C22.05 5.12 22.8 7.45 22.8 10c0 5.52-4.48 10-10 10v-3l-4 4 4 4v-3c4.97 0 9-4.03 9-9 0-1.66-.43-3.22-1.18-4.57z"/></svg>
</button>
</div>
<div class="settings-page" id="settingsPage">
<div class="settings-header">
<button class="icon-btn" id="settingsBack"><svg viewBox="0 0 24 24" style="width:20px;height:20px;fill:currentColor"><path d="M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z"/></svg></button>
<div class="settings-title">Camera Settings</div>
<div style="width:42px"></div>
</div>
<div class="settings-list">
<div class="option"><label>Photo Resolution</label><select id="photoRes" class="select"><option value="720">720p</option><option value="1080" selected>1080p</option><option value="2160">4K</option></select></div>
<div class="option"><label>Video Quality</label><select id="videoQuality" class="select"><option value="480">480p</option><option value="720">720p</option><option value="1080" selected>1080p</option></select></div>
<div class="option"><label>Flash Mode</label><select id="flashMode" class="select"><option value="auto">Auto</option><option value="on">On</option><option value="off">Off</option></select></div>
<div class="option"><label>Grid Lines</label><input type="checkbox" id="gridToggle" class="toggle" /></div>
<div class="option"><label>Default Filter</label><select id="defaultFilter" class="select"><option value="none">Normal</option><option value="warm">Warm</option><option value="vintage">Vintage</option><option value="bw">Black & White</option></select></div>
<div class="option"><label>Shutter Sound</label><input type="checkbox" id="shutterToggle" class="toggle" checked /></div>
<div class="option"><label>AI Enhancement</label><input type="checkbox" id="aiToggleSetting" class="toggle" /></div>
<div class="option"><label>HDR Mode</label><input type="checkbox" id="hdrToggleSetting" class="toggle" /></div>
</div>
</div>
<div class="recording-indicator" id="recordingIndicator"><div class="record-dot"></div><div class="timer-text" id="recordTimer">00:00</div></div>
</div>
<script>
let cameraStream=null,currentFacing='environment',mediaRecorder=null,recordedChunks=[],isRecording=false;
let flashMode='auto',shutterSound=true,gridOn=false,defaultFilter='none';
let zoomLevel=1,lastTouchDistance=null,zoomTimeout=null;
let recordStartTime=0,recordTimerInterval=null;
let aiEnabled=false,hdrEnabled=false;
const video=document.getElementById('cameraVideo');
const canvas=document.getElementById('cameraCanvas');
const flashEl=document.getElementById('flashEffect');
const focusRing=document.getElementById('focusRing');
const infoOverlay=document.getElementById('infoOverlay');
const thumb=document.getElementById('thumbPreview');
const zoomIndicator=document.getElementById('zoomIndicator');
const recordingIndicator=document.getElementById('recordingIndicator');
const recordTimerEl=document.getElementById('recordTimer');
function formatTime(ms){const s=Math.floor(ms/1000);const mm=Math.floor(s/60).toString().padStart(2,'0');const ss=(s%60).toString().padStart(2,'0');return `${mm}:${ss}`;}
async function startCamera(){
try{
if(cameraStream){cameraStream.getTracks().forEach(t=>t.stop());cameraStream=null}
const photoRes=parseInt(document.getElementById('photoRes').value,10)||1080;
const constraints={audio:false,video:{facingMode:currentFacing,width:{ideal:photoRes},height:{ideal:Math.floor(photoRes*16/9)}}};
cameraStream=await navigator.mediaDevices.getUserMedia(constraints);
video.srcObject=cameraStream;await video.play();updateInfo();applyFilter();updateThumb();
}catch(e){console.error(e);showToast('Camera access denied');}
}
function updateInfo(){const w=video.videoWidth||0;const h=video.videoHeight||0;infoOverlay.textContent=`${w}×${h} • ${getActiveMode().toUpperCase()}`;}
function getActiveMode(){const active=document.querySelector('.mode-item.active');return active?active.textContent.trim().toLowerCase():'photo';}
function applyFilter(){
defaultFilter=document.getElementById('defaultFilter').value;
let filter='none';
switch(defaultFilter){
case 'warm':filter='sepia(.12) saturate(1.1) contrast(1.03)';break;
case 'vintage':filter='sepia(.22) contrast(.95) saturate(.9)';break;
case 'bw':filter='grayscale(1) contrast(1.05)';break;
}
if(aiEnabled)filter=(filter==='none'?'':'filter+')+' brightness(1.15) contrast(1.1) saturate(1.05)';
if(hdrEnabled)filter=(filter==='none'?'':'filter+'')+' contrast(1.15) brightness(1.05)';
video.style.filter=filter;
}
document.getElementById('defaultFilter').addEventListener('change',applyFilter);
document.getElementById('gridToggle').addEventListener('change',e=>{gridOn=e.target.checked;document.getElementById('gridOverlay').classList.toggle('on',gridOn);});
document.getElementById('shutterToggle').addEventListener('change',e=>{shutterSound=e.target.checked;});
document.getElementById('flashMode').addEventListener('change',e=>{flashMode=e.target.value;});
document.getElementById('menuBtn').addEventListener('click',()=>document.getElementById('settingsPage').classList.add('open'));
document.getElementById('settingsBack').addEventListener('click',()=>document.getElementById('settingsPage').classList.remove('open'));
document.getElementById('shutterBtn').addEventListener('click',captureFlow);
document.getElementById('switchBtn').addEventListener('click',async()=>{currentFacing=currentFacing==='user'?'environment':'user';await startCamera();showToast(`Switched to ${currentFacing==='user'?'front':'rear'}`);});
document.getElementById('modeList').addEventListener('click',e=>{const m=e.target.closest('.mode-item');if(!m)return;document.querySelectorAll('.mode-item').forEach(x=>x.classList.remove('active'));m.classList.add('active');updateInfo();});
const cameraRoot=document.getElementById('cameraRoot');
function getTouchDistance(touches){const dx=touches[0].clientX-touches[1].clientX;const dy=touches[0].clientY-touches[1].clientY;return Math.sqrt(dx*dx+dy*dy);}
function handleTouchStart(e){if(e.touches&&e.touches.length===2)lastTouchDistance=getTouchDistance(e.touches);}
function handleTouchMove(e){if(e.touches&&e.touches.length===2){e.preventDefault();const currentDistance=getTouchDistance(e.touches);if(lastTouchDistance){const delta=currentDistance/lastTouchDistance;zoomLevel=Math.min(5,Math.max(1,zoomLevel*delta));applyZoom();}lastTouchDistance=currentDistance;}}
function handleTouchEnd(e){if(!e.touches||e.touches.length<2)lastTouchDistance=null;}
function applyZoom(){video.style.transform=`scale(${zoomLevel})`;zoomIndicator.textContent=`${zoomLevel.toFixed(1)}x`;zoomIndicator.classList.add('active');clearTimeout(zoomTimeout);zoomTimeout=setTimeout(()=>zoomIndicator.classList.remove('active'),1000);}
cameraRoot.addEventListener('touchstart',handleTouchStart,{passive:false});
cameraRoot.addEventListener('touchmove',handleTouchMove,{passive:false});
cameraRoot.addEventListener('touchend',handleTouchEnd);
cameraRoot.addEventListener('click',e=>{if(e.target.closest('.icon-btn')||e.target.closest('.shutter-btn')||e.target.closest('.mode-list')||e.target.closest('.settings-page'))return;const rect=cameraRoot.getBoundingClientRect();const x=e.clientX-rect.left;const y=e.clientY-rect.top;showFocus(x,y);});
function showFocus(x,y){const ring=focusRing;ring.classList.remove('active');ring.style.left=`${x-32}px`;ring.style.top=`${y-32}px`;void ring.offsetWidth;ring.classList.add('active');setTimeout(()=>ring.classList.remove('active'),900);}
async function measureBrightness(){try{const w=Math.min(160,video.videoWidth||320);const h=Math.min(90,video.videoHeight||180);if(!w||!h)return 255;canvas.width=w;canvas.height=h;const ctx=canvas.getContext('2d');ctx.drawImage(video,0,0,w,h);const data=ctx.getImageData(0,0,w,h).data;let sum=0;for(let i=0;i<data.length;i+=4)sum+=(0.299*data[i]+0.587*data[i+1]+0.114*data[i+2]);return sum/(w*h);}catch(e){return 255;}}
async function captureFlow(){
const mode=getActiveMode();
if(mode==='video'||mode==='short video'){toggleRecording();return;}
let simulateFlash=false;
if(flashMode==='on')simulateFlash=true;
else if(flashMode==='auto'){const b=await measureBrightness();if(b<60)simulateFlash=true;}
if(simulateFlash)triggerFlash();
const dataUrl=await capturePhoto();
if(!dataUrl)return;
let photos=JSON.parse(localStorage.getItem('photos')||'[]');
photos.push({data:dataUrl,date:new Date().toISOString(),mode:getActiveMode(),filter:defaultFilter,ai:aiEnabled,hdr:hdrEnabled});
localStorage.setItem('photos',JSON.stringify(photos));
showToast('✓ Photo saved');
updateThumb();
}
function triggerFlash(){flashEl.classList.remove('on');void flashEl.offsetWidth;flashEl.classList.add('on');setTimeout(()=>flashEl.classList.remove('on'),600);}
async function capturePhoto(){
try{
if(!cameraStream){showToast('Camera not ready');return null;}
if(shutterSound&&document.getElementById('shutterToggle').checked){try{const s=new Audio('https://actions.google.com/sounds/v1/camera/camera_shutter_click.ogg');s.play().catch(()=>{});}catch(e){}}
const w=video.videoWidth;const h=video.videoHeight;if(!w||!h)return null;
canvas.width=w;canvas.height=h;const ctx=canvas.getContext('2d');
const filter=getComputedStyle(video).getPropertyValue('filter');
ctx.filter=filter==='none'?'none':filter;
ctx.drawImage(video,0,0,w,h);
ctx.fillStyle='rgba(0,0,0,0.4)';ctx.fillRect(8,h-46,320,36);
ctx.fillStyle='#fff';ctx.font='18px sans-serif';
let modeText=getActiveMode().toUpperCase();
if(aiEnabled)modeText+=' AI';
if(hdrEnabled)modeText+=' HDR';
ctx.fillText(`${new Date().toLocaleString()} • ${modeText}`,14,h-20);
return canvas.toDataURL('image/jpeg',0.95);
}catch(e){console.error(e);showToast('Capture failed');return null;}
}
function updateThumb(){
try{
const photos=JSON.parse(localStorage.getItem('photos')||'[]');
if(photos.length>0&&thumb){
const last=photos[photos.length-1];
thumb.style.backgroundImage=`url(${last.data})`;
}else if(thumb){
thumb.style.backgroundImage='none';
thumb.style.background='linear-gradient(135deg,#111,#0b0603)';
}
}catch(e){
// Silent fail - no error logging
}
}
function toggleRecording(){
if(isRecording){if(mediaRecorder&&mediaRecorder.state==='recording')mediaRecorder.stop();stopRecordingUI();return;}
if(!cameraStream){showToast('Camera not ready');return;}
try{mediaRecorder=new MediaRecorder(cameraStream,{mimeType:'video/webm;codecs=vp8'});}catch(e){try{mediaRecorder=new MediaRecorder(cameraStream);}catch(err){showToast('Recording not supported');return;}}
recordedChunks=[];
mediaRecorder.ondataavailable=e=>{if(e.data&&e.data.size)recordedChunks.push(e.data);};
mediaRecorder.onstop=async()=>{
const blob=new Blob(recordedChunks,{type:'video/webm'});
const reader=new FileReader();
reader.onload=function(){
const dataUrl=reader.result;
let photos=JSON.parse(localStorage.getItem('photos')||'[]');
photos.push({data:dataUrl,date:new Date().toISOString(),mode:'video',filter:'none',isVideo:true});
localStorage.setItem('photos',JSON.stringify(photos));
showToast('✓ Video saved');
updateThumb();
};
reader.readAsDataURL(blob);
};
mediaRecorder.start();isRecording=true;startRecordingUI();showToast('Recording...');
}
function startRecordingUI(){recordingIndicator.classList.add('on');recordStartTime=Date.now();recordTimerEl.textContent='00:00';recordTimerInterval=setInterval(()=>{recordTimerEl.textContent=formatTime(Date.now()-recordStartTime);},500);document.getElementById('shutterInner').classList.add('recording');}
function stopRecordingUI(){recordingIndicator.classList.remove('on');if(recordTimerInterval)clearInterval(recordTimerInterval);recordTimerInterval=null;isRecording=false;document.getElementById('shutterInner').classList.remove('recording');}
function showToast(msg,t=1600){
const existing=document.querySelector('.toast-center');
if(existing){existing.remove();clearTimeout(existing._removeTimer);}
const el=document.createElement('div');
el.className='toast-center';
el.textContent=msg;
document.body.appendChild(el);
void el.offsetWidth;
el.classList.add('show');
el._removeTimer=setTimeout(()=>{el.classList.remove('show');el.classList.add('hide');setTimeout(()=>el.remove(),300);},t);
}
const flashBtn=document.getElementById('flashBtn');
flashBtn.addEventListener('click',()=>{const fm=document.getElementById('flashMode');fm.value=fm.value==='on'?'off':'on';flashMode=fm.value;flashBtn.classList.toggle('active',flashMode==='on');showToast('Flash: '+flashMode);});
const filterBtn=document.getElementById('filterBtn');
filterBtn.addEventListener('click',()=>{const sel=document.getElementById('defaultFilter');const next={none:'warm',warm:'vintage',vintage:'bw',bw:'none'}[sel.value];sel.value=next;applyFilter();showToast('Filter: '+next);});
const aiBtn=document.getElementById('aiBtn');
aiBtn.addEventListener('click',()=>{aiEnabled=!aiEnabled;aiBtn.classList.toggle('active',aiEnabled);document.getElementById('aiToggleSetting').checked=aiEnabled;applyFilter();showToast(aiEnabled?'🤖 AI Enhance On':'AI Enhance Off');});
const hdrBtn=document.getElementById('hdrBtn');
hdrBtn.addEventListener('click',()=>{hdrEnabled=!hdrEnabled;hdrBtn.classList.toggle('active',hdrEnabled);document.getElementById('hdrToggleSetting').checked=hdrEnabled;applyFilter();showToast(hdrEnabled?'✨ HDR On':'HDR Off');});
document.getElementById('aiToggleSetting').addEventListener('change',e=>{aiEnabled=e.target.checked;aiBtn.classList.toggle('active',aiEnabled);applyFilter();showToast(aiEnabled?'🤖 AI Enhance On':'AI Enhance Off');});
document.getElementById('hdrToggleSetting').addEventListener('change',e=>{hdrEnabled=e.target.checked;hdrBtn.classList.toggle('active',hdrEnabled);applyFilter();showToast(hdrEnabled?'✨ HDR On':'HDR Off');});
['photoRes','videoQuality'].forEach(id=>document.getElementById(id).addEventListener('change',()=>{startCamera();}));
[...document.querySelectorAll('.icon-btn')].forEach(b=>{b.addEventListener('mouseenter',()=>{const label=b.querySelector('.icon-label');if(label){label.classList.add('show');setTimeout(()=>label.classList.remove('show'),900);}});b.addEventListener('touchstart',()=>{const label=b.querySelector('.icon-label');if(label){label.classList.add('show');setTimeout(()=>label.classList.remove('show'),900);}},{passive:true});});
video.addEventListener('loadedmetadata',updateInfo);
window.addEventListener('load',startCamera);
window.addEventListener('beforeunload',()=>{if(cameraStream)cameraStream.getTracks().forEach(t=>t.stop());if(recordTimerInterval)clearInterval(recordTimerInterval);});
function initSignInButton() {
const style = document.createElement('style');
style.textContent = LunIDAuth.getStyles();
document.head.appendChild(style);
if (window.lunidAuth) {
lunidAuth.createSignInButton('signInBtnContainer', {
onClick: (isAuth, user) => showLunidModal()
});
}
}
function showLunidModal() {
document.getElementById('lunidModalOverlay').classList.add('show');
if (window.lunidAuth) {
lunidAuth.createAccountPickerUI('lunidAuthContainer', {
appName: 'Camera',
onContinue: (user) => closeLunidModal(),
onCancel: closeLunidModal
});
}
}
function closeLunidModal() {
document.getElementById('lunidModalOverlay').classList.remove('show');
}
initSignInButton();
</script>
</body>
</html>