speedrohmoohyun / templates /capture.html
unknown
Deploy update with encrypted code: 2025-05-12_17-33-22
cf59a50
<!DOCTYPE html><html lang="ko"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>ํ™”๋ฉด ์บก์ฒ˜ ๋ฐ ๊ฒ€์ƒ‰</title><style>*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}html{scroll-behavior:smooth}body{font-family:'Malgun Gothic','๋ง‘์€ ๊ณ ๋”•',Dotum,'๋‹์›€',sans-serif;background-color:#f8f9fa;color:#212529;line-height:1.6;padding:20px;font-size:15px}.container{max-width:1400px;margin:0 auto}.main-layout{display:flex;gap:25px;align-items:flex-start;flex-wrap:wrap}.left-panel{flex:1 1 40%;min-width:300px;background:white;padding:25px;border-radius:8px;box-shadow:0 1px 3px rgba(0,0,0,.1)}.right-panel{flex:1 1 55%;min-width:400px;background:white;padding:25px;border-radius:8px;box-shadow:0 1px 3px rgba(0,0,0,.1)}h2{color:#343a40;margin-bottom:20px;font-size:1.4em;border-bottom:1px solid #dee2e6;padding-bottom:10px}h3{color:#495057;margin-bottom:15px;font-size:1.2em}h4{color:#6c757d;margin:10px 0 5px;font-size:1em}.capture-controls{display:flex;gap:10px;flex-wrap:wrap;margin-bottom:15px}button{background-color:#0d6efd;color:white;border:none;padding:9px 18px;border-radius:6px;cursor:pointer;font-size:1rem;font-weight:500;transition:background-color .2s ease;display:inline-flex;align-items:center;justify-content:center}button:hover{background-color:#0b5ed7}button:disabled{background-color:#adb5bd;cursor:not-allowed;opacity:.7}.stop-button{background-color:#dc3545}.stop-button:hover:not(:disabled){background-color:#bb2d3b}.canvas-container{position:relative;margin:20px 0;border:1px solid #dee2e6;line-height:0}#captureCanvas{display:block;width:100%;height:auto;cursor:crosshair;background-image:url(/static/2352354634.png);background-size:contain;background-repeat:no-repeat;background-position:center center;background-color:#e9ecef;transition:background-image .1s ease-in-out}#captureCanvas.sharing{background-image:none}.capture-frame{position:absolute;border:2px dashed #0d6efd;cursor:move;background-color:rgba(13,110,253,.1);display:none}.capture-handle{position:absolute;width:10px;height:10px;background-color:#0d6efd;border:1px solid white;border-radius:50%}.handle-tl{top:-5px;left:-5px;cursor:nwse-resize}.handle-tr{top:-5px;right:-5px;cursor:nesw-resize}.handle-bl{bottom:-5px;left:-5px;cursor:nesw-resize}.handle-br{bottom:-5px;right:-5px;cursor:nwse-resize}#statusMessage{font-weight:600;color:#495057;margin:10px 0;text-align:center;min-height:2.4em}#statusMessage small{display:block;color:#6c757d;font-weight:normal;font-size:.85rem}#statusMessage.sharing{color:#198754}#statusMessage.paused{color:#fd7e14}#statusMessage.error{color:#dc3545}.preview-container{text-align:center;margin-top:20px}#capturePreview{max-width:100%;max-height:150px;border:1px solid #dee2e6;border-radius:6px;object-fit:contain;background-color:#e9ecef}#capturePreview[src^="data:image/svg"]{border:1px dashed #adb5bd;padding:10px}.loading{display:none;text-align:center;margin:40px 0}.spinner{border:4px solid #e9ecef;border-top:4px solid #0d6efd;border-radius:50%;width:40px;height:40px;animation:spin 1.5s linear infinite;margin:0 auto 10px}@keyframes spin{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}.loading p{font-size:1rem;color:#495057}.error-message{color:#dc3545;background-color:#f8d7da;border:1px solid #f5c2c7;padding:12px 15px;border-radius:6px;margin:20px 0;display:none}.results-section{display:none}.image-comparison{margin-top:30px;border-top:1px solid #eee;padding-top:20px;display:flex;justify-content:space-around;gap:15px;flex-wrap:wrap}.image-container{text-align:center;flex:1;min-width:180px}.image-container img{max-width:100%;max-height:200px;border-radius:6px;border:1px solid #dee2e6;object-fit:contain}.results-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(140px,1fr));gap:18px;margin-top:15px}.result-card{background:#f8f9fa;border:1px solid #e9ecef;border-radius:6px;padding:12px 8px;text-align:center;transition:transform .2s,box-shadow .2s;cursor:pointer;display:flex;flex-direction:column;justify-content:space-between}.result-card:hover{transform:translateY(-4px);box-shadow:0 4px 8px rgba(0,0,0,.1)}.result-image{height:90px;max-width:100%;object-fit:contain;margin-bottom:8px;align-self:center}.result-info{margin-top:auto}.result-name{font-size:.85rem;font-weight:600;color:#343a40;word-break:break-word;margin-bottom:4px}.result-category{display:inline-block;padding:2px 8px;border-radius:10px;font-size:.75rem;font-weight:500;margin-bottom:4px}.category-npc{background-color:#cfe2ff;color:#0a58ca}.category-mob{background-color:#f8d7da;color:#b02a37}.result-similarity{font-size:.8rem;color:#6c757d}#toast-message{position:fixed;top:30px;left:50%;transform:translateX(-50%);background-color:rgba(33,37,41,.9);color:white;padding:10px 20px;border-radius:6px;font-size:.95rem;z-index:1050;opacity:0;transition:opacity .3s ease-out;box-shadow:0 2px 5px rgba(0,0,0,.2);pointer-events:none}.auto-copy-control label{display:flex;align-items:center;cursor:pointer;font-weight:normal;font-size:.95rem}.auto-copy-control input[type=checkbox]{margin-right:8px;width:16px;height:16px;cursor:pointer}@media (max-width:992px){.main-layout{flex-direction:column}.left-panel,.right-panel{flex:1 1 100%}}@media (max-width:576px){body{padding:10px}.left-panel,.right-panel{padding:15px}h2{font-size:1.2em}h3{font-size:1.1em}button{font-size:.9rem;padding:8px 15px}.results-grid{grid-template-columns:repeat(auto-fill,minmax(100px,1fr))}.result-image{height:60px}.result-name{font-size:.8rem}}</style></head><body><div class="container"><div class="main-layout"><div class="left-panel"><section class="capture-setup"><h2>ํ™”๋ฉด ์บก์ฒ˜ ์„ค์ •</h2><div class="capture-controls"><button id="startScreenShare" type="button">ํ™”๋ฉด ๊ณต์œ  ์‹œ์ž‘</button><button id="stopScreenShare" type="button" disabled class="stop-button">ํ™”๋ฉด ๊ณต์œ  ์ค‘์ง€</button></div><div class="canvas-container"><canvas id="captureCanvas" width="640" height="360" title="๊ณต์œ ๋œ ํ™”๋ฉด์ด ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค. ์บก์ฒ˜ํ•  ์˜์—ญ์„ ๋“œ๋ž˜๊ทธํ•˜์—ฌ ์„ ํƒํ•˜์„ธ์š”."></canvas><div id="captureFrame" class="capture-frame" title="์ด ์˜์—ญ์„ ๋“œ๋ž˜๊ทธํ•˜์—ฌ ์ด๋™ํ•˜๊ฑฐ๋‚˜ ๋ชจ์„œ๋ฆฌ๋ฅผ ์กฐ์ ˆํ•˜์„ธ์š”."><div class="capture-handle handle-tl"></div><div class="capture-handle handle-tr"></div><div class="capture-handle handle-bl"></div><div class="capture-handle handle-br"></div></div></div><div class="capture-controls"><button id="singleCapture" type="button" disabled>์„ ํƒ ์˜์—ญ ์บก์ฒ˜ ๋ฐ ๊ฒ€์ƒ‰</button></div><div class="capture-controls auto-copy-control" style="margin-top:15px;border-top:1px solid #eee;padding-top:15px"><label for="autoCopyToggle"><input type="checkbox" id="autoCopyToggle" disabled>์‹ค์‹œ๊ฐ„ ์ž๋™ ๋ณต์‚ฌ ํ™œ์„ฑํ™” (์ตœ์ƒ์œ„ ๊ฒฐ๊ณผ)</label></div><p id="statusMessage">'ํ™”๋ฉด ๊ณต์œ  ์‹œ์ž‘' ๋ฒ„ํŠผ ํด๋ฆญ.<small>https://arca.live/b/mapleland</small></p><div class="preview-container" style="margin-top:25px"><h3>์บก์ฒ˜๋œ ์˜์—ญ ๋ฏธ๋ฆฌ๋ณด๊ธฐ</h3><img id="capturePreview" src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='100' height='60' viewBox='0 0 100 60'%3E%3Crect width='100' height='60' fill='%23e9ecef'/%3E%3Ctext x='50' y='35' font-family='sans-serif' font-size='10' fill='%23adb5bd' text-anchor='middle'%3EPreview%3C/text%3E%3C/svg%3E" alt="์บก์ฒ˜ ๋ฏธ๋ฆฌ๋ณด๊ธฐ"></div></section></div><div class="right-panel"><div id="loading" class="loading"><div class="spinner"></div><p>์ด๋ฏธ์ง€๋ฅผ ๋ถ„์„ ์ค‘์ž…๋‹ˆ๋‹ค...</p></div><div id="errorMessage" class="error-message"></div><section id="resultsSection" class="results-section"><h2>๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ</h2><h3>๊ฐ€์žฅ ์œ ์‚ฌํ•œ ์บ๋ฆญํ„ฐ ๋ชฉ๋ก</h3><div id="resultsGrid" class="results-grid"></div><div id="imageComparison" class="image-comparison" style="display:none"><div class="image-container"><h4>์บก์ฒ˜๋œ ์ด๋ฏธ์ง€</h4><img id="originalImage" src="#" alt="์บก์ฒ˜๋œ ์ด๋ฏธ์ง€"></div><div class="image-container" id="preprocessedContainer"><h4>์ „์ฒ˜๋ฆฌ๋œ ์ด๋ฏธ์ง€</h4><img id="preprocessedImage" src="#" alt="์ „์ฒ˜๋ฆฌ๋œ ์ด๋ฏธ์ง€"></div></div></section></div></div></div><script>document.addEventListener('DOMContentLoaded',function(){const startShareBtn=document.getElementById('startScreenShare'),stopShareBtn=document.getElementById('stopScreenShare'),captureBtn=document.getElementById('singleCapture');const canvas=document.getElementById('captureCanvas'),frame=document.getElementById('captureFrame'),previewImg=document.getElementById('capturePreview');const statusMsg=document.getElementById('statusMessage'),loadingIndicator=document.getElementById('loading'),errorMsgDiv=document.getElementById('errorMessage');const resultsSection=document.getElementById('resultsSection'),resultsGrid=document.getElementById('resultsGrid');const originalImageElem=document.getElementById('originalImage'),preprocessedImageElem=document.getElementById('preprocessedImage');const preprocessedContainer=document.getElementById('preprocessedContainer'),imageComparisonSection=document.getElementById('imageComparison');const autoCopyToggle=document.getElementById('autoCopyToggle');const ctx=canvas.getContext('2d');let stream=null,video=document.createElement('video');video.autoplay=true;video.muted=true;let animationFrameId=null;let frameRect={x:50,y:50,width:200,height:150};let isDraggingFrame=false,isResizingFrame=false,resizeHandleType='';let dragStartPos={x:0,y:0},frameStartRect={...frameRect};let isAutoCopyActive=false,isAutoCopyPaused=false,autoCopyIntervalId=null;let lastCopiedName=null;let lastTopResultName=null;const AUTO_COPY_INTERVAL=1000,MIN_SIMILARITY_THRESHOLD=.3;let textToCopyToClipboardLater=null;const FRAME_RECT_STORAGE_KEY='captureFrameRect';function loadFrameRect(){try{const s=localStorage.getItem(FRAME_RECT_STORAGE_KEY);if(s){const r=JSON.parse(s);if(r&&typeof r.x==='number'&&typeof r.y==='number'&&typeof r.width==='number'&&r.width>10&&typeof r.height==='number'&&r.height>10){frameRect=r;console.log('Loaded frame:',frameRect)}else{console.warn('Invalid frame data.')}}}catch(e){console.error('Load frame error:',e)}frameStartRect={...frameRect}}function saveFrameRect(){try{localStorage.setItem(FRAME_RECT_STORAGE_KEY,JSON.stringify(frameRect))}catch(e){console.error('Save frame error:',e)}}let isFrameUpdateScheduled=false;let lastCanvasRect=null;const showLoading=(isAutoMode=false)=>{if(!isAutoMode)loadingIndicator.style.display='block';errorMsgDiv.style.display='none';if(!isAutoMode)resultsSection.style.display='none';captureBtn.disabled=true;stopShareBtn.disabled=true;autoCopyToggle.disabled=true};const hideLoading=(isAutoMode=false)=>{loadingIndicator.style.display='none';captureBtn.disabled=stream===null;stopShareBtn.disabled=stream===null;autoCopyToggle.disabled=stream===null};const showError=(message)=>{updateStatus(`์˜ค๋ฅ˜: ${message}`,false,true);errorMsgDiv.style.display='none';resultsSection.style.display='none';hideLoading();stopAutoCopy(false,true);if(autoCopyToggle)autoCopyToggle.checked=false;textToCopyToClipboardLater=null};const hideError=()=>{errorMsgDiv.style.display='none';if(stream)updateStatus("ํ™”๋ฉด ๊ณต์œ  ์ค‘...",true,false,isAutoCopyPaused);else updateStatus("'์‹œ์ž‘' ๋ฒ„ํŠผ ํด๋ฆญ...")};const updateStatus=(message,isSharing=false,isError=false,isPaused=false)=>{statusMsg.innerHTML=message;statusMsg.classList.toggle('sharing',isSharing&&!isPaused&&!isError);statusMsg.classList.toggle('paused',isPaused);statusMsg.classList.toggle('error',isError)};startShareBtn.addEventListener('click',startScreenShare);stopShareBtn.addEventListener('click',()=>stopScreenShare(false));captureBtn.addEventListener('click',()=>captureAndAnalyze(false));autoCopyToggle.addEventListener('change',handleAutoCopyToggle);frame.addEventListener('mousedown',onFrameMouseDown);document.addEventListener('mousemove',onFrameMouseMove);document.addEventListener('mouseup',onFrameMouseUp);window.addEventListener('resize',handleResize);document.addEventListener('visibilitychange',handleVisibilityChange);async function startScreenShare(){if(stream)stopScreenShare(false);hideError();if(!navigator.mediaDevices?.getDisplayMedia){showError('ํ™”๋ฉด ๊ณต์œ  ๋ฏธ์ง€์› ๋˜๋Š” ์•ˆ์ „ํ•˜์ง€ ์•Š์€ ํ™˜๊ฒฝ');return}try{updateStatus("ํ™”๋ฉด ๊ณต์œ  ๊ถŒํ•œ ์š”์ฒญ ์ค‘...");stream=await navigator.mediaDevices.getDisplayMedia({video:{cursor:"always",frameRate:15},audio:false});video.srcObject=stream;await new Promise((resolve,reject)=>{let playing=false;const timeoutId=setTimeout(()=>{if(!playing)reject(new Error("๋น„๋””์˜ค ์žฌ์ƒ ์‹œ์ž‘ ์‹œ๊ฐ„ ์ดˆ๊ณผ"))},5000);video.onplaying=()=>{if(playing)return;playing=true;clearTimeout(timeoutId);console.log("[Debug] Video is playing.");canvas.width=video.videoWidth;canvas.height=video.videoHeight;adjustCanvasDisplaySize();updateCaptureFrameStyleVisuals();frame.style.display='block';startShareBtn.disabled=true;stopShareBtn.disabled=false;captureBtn.disabled=false;autoCopyToggle.disabled=false;canvas.classList.add('sharing');updateStatus("ํ™”๋ฉด ๊ณต์œ  ์ค‘. ์บก์ฒ˜ ์˜์—ญ ์กฐ์ ˆ.",true);drawVideoFrame();resolve()};video.onerror=(e)=>{clearTimeout(timeoutId);console.error("Video element error:",e);reject(new Error("๋น„๋””์˜ค ์žฌ์ƒ ์˜ค๋ฅ˜"))};video.onloadedmetadata=()=>{console.log("[Debug] Video metadata loaded.");video.play().catch(playError=>console.error("Error calling video.play():",playError))};video.load()});stream.getVideoTracks()[0].onended=()=>{updateStatus("ํ™”๋ฉด ๊ณต์œ  ์ค‘์ง€๋จ.",false,true);stopScreenShare(true)}}catch(err){console.error("Screen Share Error:",err);const msg=err.name==='NotAllowedError'?'๊ถŒํ•œ ๊ฑฐ๋ถ€๋จ.':`์‹œ์ž‘ ์‹คํŒจ: ${err.message||err}`;showError(msg);stopScreenShare(false)}}function stopScreenShare(triggeredByEvent){stopAutoCopy(false,true);textToCopyToClipboardLater=null;if(animationFrameId){cancelAnimationFrame(animationFrameId);animationFrameId=null}if(stream){stream.getTracks().forEach(track=>track.stop());stream=null}video.srcObject=null;video.pause();canvas.classList.remove('sharing');console.log("StopShare: Removed 'sharing' class.");requestAnimationFrame(()=>{if(ctx){if(canvas.width>0&&canvas.height>0){console.log("StopShare: Clearing canvas via requestAnimationFrame.");ctx.clearRect(0,0,canvas.width,canvas.height)}else{console.warn("StopShare: Canvas dimensions zero in rAF, skipping clearRect.")}}});startShareBtn.disabled=false;stopShareBtn.disabled=true;captureBtn.disabled=true;autoCopyToggle.disabled=true;if(autoCopyToggle)autoCopyToggle.checked=false;if(!triggeredByEvent){updateStatus("'์‹œ์ž‘' ๋ฒ„ํŠผ ํด๋ฆญ.<small>https://arca.live/b/mapleland</small>")}frame.style.display='none';hideLoading()}function handleAutoCopyToggle(event){isAutoCopyActive=event.target.checked;isAutoCopyPaused=false;lastTopResultName=null;textToCopyToClipboardLater=null;if(isAutoCopyActive)startAutoCopy();else stopAutoCopy(false,true)}function startAutoCopy(){if(!stream){showError("ํ™”๋ฉด ๊ณต์œ  ๋ฏธ์‹œ์ž‘");if(autoCopyToggle)autoCopyToggle.checked=false;isAutoCopyActive=false;return}if(autoCopyIntervalId!==null){updateStatus("์ž๋™ ๋ณต์‚ฌ ํ™œ์„ฑํ™”๋จ. ๊ฐ์‹œ ์ค‘...",true,false,isAutoCopyPaused);return}console.log("[AutoCopy] Starting loop...");updateStatus("์ž๋™ ๋ณต์‚ฌ ํ™œ์„ฑํ™”๋จ. ๊ฐ์‹œ ์ค‘...",true);lastCopiedName=null;lastTopResultName=null;isAutoCopyPaused=false;textToCopyToClipboardLater=null;const runAutoCapture=()=>{if(isAutoCopyActive&&stream){captureAndAnalyze(true)}else if(!stream||!isAutoCopyActive){stopAutoCopy(false,true)}};runAutoCapture();autoCopyIntervalId=setInterval(runAutoCapture,AUTO_COPY_INTERVAL)}function stopAutoCopy(keepPausedState=false,clearOnly=false){if(autoCopyIntervalId!==null){console.log("[AutoCopy] Stopping loop.");clearInterval(autoCopyIntervalId);autoCopyIntervalId=null}textToCopyToClipboardLater=null;if(!keepPausedState){isAutoCopyPaused=false}if(!clearOnly){if(!isAutoCopyPaused&&stream)updateStatus("ํ™”๋ฉด ๊ณต์œ  ์ค‘. ์บก์ฒ˜ ์˜์—ญ ์กฐ์ ˆ.",true);else if(!isAutoCopyPaused&&!stream)updateStatus("'์‹œ์ž‘' ๋ฒ„ํŠผ ํด๋ฆญ...")}}function drawVideoFrame(){if(!stream||video.paused||video.ended||!ctx)return;try{if(canvas.width>0&&canvas.height>0)ctx.drawImage(video,0,0,canvas.width,canvas.height)}catch(e){console.error("Draw frame error:",e)}animationFrameId=requestAnimationFrame(drawVideoFrame)}function handleResize(){adjustCanvasDisplaySize();lastCanvasRect=null;updateCaptureFrameStyleVisuals()}function adjustCanvasDisplaySize(){if(!canvas.parentElement)return;const cw=canvas.parentElement.clientWidth;if(canvas.width>0&&cw>0){const ar=canvas.height/canvas.width;canvas.style.width=`${cw}px`;canvas.style.height=`${Math.round(cw*ar)}px`;lastCanvasRect=null}}function onFrameMouseDown(e){e.preventDefault();e.stopPropagation();const t=e.target;isDraggingFrame=t===frame;isResizingFrame=t.classList.contains('capture-handle');if(!isDraggingFrame&&!isResizingFrame)return;if(isResizingFrame)resizeHandleType=t.className.split(' ').find(c=>c.startsWith('handle-'))?.replace('handle-','');dragStartPos={x:e.clientX,y:e.clientY};frameStartRect={...frameRect};lastCanvasRect=canvas.getBoundingClientRect()}function onFrameMouseMove(e){if(!isDraggingFrame&&!isResizingFrame)return;e.preventDefault();e.stopPropagation();const dx=e.clientX-dragStartPos.x,dy=e.clientY-dragStartPos.y;const r=lastCanvasRect||canvas.getBoundingClientRect();if(!r||r.width<=0||canvas.width<=0)return;const cxScale=canvas.width/r.width;const cyScale=canvas.height/r.height;const canvasDx=dx*cxScale,canvasDy=dy*cyScale;let nx=frameStartRect.x,ny=frameStartRect.y,nw=frameStartRect.width,nh=frameStartRect.height;const minSize=20;if(isDraggingFrame){nx=frameStartRect.x+canvasDx;ny=frameStartRect.y+canvasDy}else if(resizeHandleType){switch(resizeHandleType){case'tl':nx=frameStartRect.x+canvasDx;ny=frameStartRect.y+canvasDy;nw=frameStartRect.width-canvasDx;nh=frameStartRect.height-canvasDy;break;case'tr':ny=frameStartRect.y+canvasDy;nw=frameStartRect.width+canvasDx;nh=frameStartRect.height-canvasDy;break;case'bl':nx=frameStartRect.x+canvasDx;nw=frameStartRect.width-canvasDx;nh=frameStartRect.height+canvasDy;break;case'br':nw=frameStartRect.width+canvasDx;nh=frameStartRect.height+canvasDy;break}if(nw<minSize){if(resizeHandleType==='tl'||resizeHandleType==='bl')nx=frameStartRect.x+frameStartRect.width-minSize;nw=minSize}if(nh<minSize){if(resizeHandleType==='tl'||resizeHandleType==='tr')ny=frameStartRect.y+frameStartRect.height-minSize;nh=minSize}}frameRect.x=Math.max(0,Math.min(nx,canvas.width-minSize));frameRect.y=Math.max(0,Math.min(ny,canvas.height-minSize));frameRect.width=Math.max(minSize,Math.min(nw,canvas.width-frameRect.x));frameRect.height=Math.max(minSize,Math.min(nh,canvas.height-frameRect.y));if(!isFrameUpdateScheduled){isFrameUpdateScheduled=true;requestAnimationFrame(()=>{updateCaptureFrameStyleVisuals();isFrameUpdateScheduled=false})}}function onFrameMouseUp(e){if(isDraggingFrame||isResizingFrame){e.preventDefault();e.stopPropagation();saveFrameRect();isDraggingFrame=false;isResizingFrame=false;resizeHandleType='';lastCanvasRect=null}}function updateCaptureFrameStyleVisuals(){const r=canvas.getBoundingClientRect();if(!r||r.width<=0||!canvas||canvas.width<=0)return;const sx=r.width/canvas.width,sy=r.height/canvas.height;frame.style.left=`${Math.round(frameRect.x*sx)}px`;frame.style.top=`${Math.round(frameRect.y*sy)}px`;frame.style.width=`${Math.round(frameRect.width*sx)}px`;frame.style.height=`${Math.round(frameRect.height*sy)}px`;if(frame.style.display==='none'&&stream)frame.style.display='block'}async function captureAndAnalyze(isAuto=false){if(!isAuto)console.log("[Debug] Manual capture started.");if(!stream||!ctx){if(!isAuto)showError(!stream?"ํ™”๋ฉด ๊ณต์œ  X":"Context X");stopAutoCopy(false,true);return}const showSpinner=!isAuto||(isAuto&&!isAutoCopyPaused);if(showSpinner)showLoading(isAuto);if(!isAuto)hideError();if(!isAuto)await new Promise(resolve=>setTimeout(resolve,50));try{const captureX=Math.max(0,Math.round(frameRect.x));const captureY=Math.max(0,Math.round(frameRect.y));const captureWidth=Math.max(1,Math.round(frameRect.width));const captureHeight=Math.max(1,Math.round(frameRect.height));if(captureX+captureWidth>canvas.width+1||captureY+captureHeight>canvas.height+1||captureWidth<=0||captureHeight<=0){console.error("์บก์ฒ˜ ์˜์—ญ ์˜ค๋ฅ˜:",{captureX,captureY,captureWidth,captureHeight,canvasW:canvas.width,canvasH:canvas.height});throw new Error(`์บก์ฒ˜ ์˜์—ญ ์˜ค๋ฅ˜ (X:${captureX}, Y:${captureY}, W:${captureWidth}, H:${captureHeight})`)}const tempCanvas=document.createElement('canvas');tempCanvas.width=captureWidth;tempCanvas.height=captureHeight;const tempCtx=tempCanvas.getContext('2d');if(!tempCtx)throw new Error("Context ์ƒ์„ฑ ์‹คํŒจ");try{tempCtx.drawImage(canvas,captureX,captureY,captureWidth,captureHeight,0,0,captureWidth,captureHeight)}catch(drawError){console.error("DrawImage error:",drawError,{captureX,captureY,captureWidth,captureHeight});throw new Error(`์บก์ฒ˜ ๊ทธ๋ฆฌ๊ธฐ ์˜ค๋ฅ˜: ${drawError.message}`)}if(!isAuto)previewImg.src=tempCanvas.toDataURL('image/png');const blob=await new Promise((res,rej)=>tempCanvas.toBlob(b=>b?res(b):rej(new Error("Blob ๋ณ€ํ™˜ ์‹คํŒจ")),'image/png',.95));const formData=new FormData();formData.append('file',blob,'capture.png');formData.append('top_n',20);console.log(`[Debug] Sending ${isAuto?'auto':'manual'} capture analysis request... (Paused: ${isAutoCopyPaused})`);const response=await fetch('/upload',{method:'POST',body:formData});if(!isAuto)console.log(`[Debug] Server status: ${response.status}`);if(!response.ok){let eMsg=`Server error: ${response.status}`;try{const d=await response.json();eMsg=d.error||eMsg}catch{}throw new Error(eMsg)}const data=await response.json();if(!data.success||data.error)throw new Error(data.error||'Unknown error');console.log(`[Debug] Received analysis response (Auto: ${isAuto}, Paused: ${isAutoCopyPaused})`);if(showSpinner)hideLoading(isAuto);displayResults(data,isAuto)}catch(error){console.error('[Debug] Capture/Analyze Error:',error);if(showSpinner)hideLoading(isAuto);if(!isAuto){showError(error.message||'์บก์ฒ˜/๋ถ„์„ ์˜ค๋ฅ˜')}else{updateStatus(`์ž๋™ ๋ณต์‚ฌ ์˜ค๋ฅ˜: ${error.message}`,true,true);textToCopyToClipboardLater=null}}}function displayResults(data,isAuto=false){resultsSection.style.display='block';if(!isAuto)hideError();if(data.query_image){const type=guessImageType(data.query_image);originalImageElem.src=`data:image/${type};base64,${data.query_image}`;originalImageElem.alt="์บก์ฒ˜๋œ ์ด๋ฏธ์ง€"}else if(!isAuto){originalImageElem.src=previewImg.src;originalImageElem.alt="์บก์ฒ˜ ์ด๋ฏธ์ง€(๋ฏธ๋ฆฌ๋ณด๊ธฐ)"}else{originalImageElem.src='#';originalImageElem.alt="์บก์ฒ˜ ์—†์Œ"}if(data.preprocessed_image){preprocessedImageElem.src=`data:image/png;base64,${data.preprocessed_image}`;preprocessedImageElem.alt="์ „์ฒ˜๋ฆฌ ์ด๋ฏธ์ง€";preprocessedContainer.style.display='block';imageComparisonSection.style.display='flex'}else{imageComparisonSection.style.display='none';preprocessedContainer.style.display='none'}let currentTopResults=[];resultsGrid.innerHTML='';if(data.results?.length>0){currentTopResults=data.results.slice(0,3).map(r=>{let name=r.name;if(name?.toLowerCase().includes('๋ฌผ์Œํ‘œ'))name='????';return name}).filter(Boolean);resultsGrid.innerHTML='';data.results.forEach(result=>resultsGrid.appendChild(createResultCard(result)));if(isAutoCopyActive){const topResult=data.results[0];let topName=topResult.name;if(topName?.toLowerCase().includes('๋ฌผ์Œํ‘œ'))topName='????';if(isAutoCopyPaused){const currentTopSet=new Set(currentTopResults);const lastTopSet=new Set(lastTopResultName||[]);let changed=currentTopSet.size!==lastTopSet.size||![...currentTopSet].every(name=>lastTopSet.has(name));if(changed){console.log(`[AutoCopy] Resuming. Top3 changed.`);isAutoCopyPaused=false;if(topResult.similarity>=MIN_SIMILARITY_THRESHOLD&&topName&&topName!==lastCopiedName){copyToClipboard(topName,true)}else{updateStatus(`์ž๋™ ๋ณต์‚ฌ ์žฌ๊ฐœ: ๋Œ€๊ธฐ ์ค‘...`,true);textToCopyToClipboardLater=null}}else{updateStatus(`์ž๋™ ๋ณต์‚ฌ ์ผ์‹œ ์ค‘์ง€๋จ (๊ฒฐ๊ณผ ๋™์ผ)`,true,false,true)}}else{if(topResult.similarity>=MIN_SIMILARITY_THRESHOLD){if(topName&&topName!==lastCopiedName){copyToClipboard(topName,true)}else if(topName){updateStatus(`์ž๋™ ๋ณต์‚ฌ: ${topName} (๋ณ€๊ฒฝ ์—†์Œ)`,true);textToCopyToClipboardLater=null}else{updateStatus(`์ž๋™ ๋ณต์‚ฌ: ๋Œ€๊ธฐ ์ค‘ (์ด๋ฆ„?)`,true);textToCopyToClipboardLater=null}}else{updateStatus(`์ž๋™ ๋ณต์‚ฌ: ๋Œ€๊ธฐ ์ค‘ (์œ ์‚ฌ๋„โ†“)`,true);textToCopyToClipboardLater=null}}}}else{resultsGrid.innerHTML='<p>์œ ์‚ฌ ์ด๋ฏธ์ง€ ์—†์Œ.</p>';currentTopResults=[];if(isAutoCopyActive&&isAutoCopyPaused&&(lastTopResultName||[]).length>0){console.log(`[AutoCopy] Resuming (no results now).`);isAutoCopyPaused=false;updateStatus(`์ž๋™ ๋ณต์‚ฌ ์žฌ๊ฐœ: ๋Œ€๊ธฐ ์ค‘...`,true)}else if(isAutoCopyActive&&!isAutoCopyPaused){updateStatus(`์ž๋™ ๋ณต์‚ฌ: ์œ ์‚ฌ ๊ฒฐ๊ณผ ์—†์Œ`,true)}textToCopyToClipboardLater=null}lastTopResultName=currentTopResults;if(!isAuto){setTimeout(()=>{resultsSection.scrollIntoView({behavior:'smooth',block:'start'})},100)}}function createResultCard(result){const card=document.createElement('div');card.className='result-card';card.title=`${result.name} (ํด๋ฆญ ์‹œ ์ด๋ฆ„ ๋ณต์‚ฌ ๋ฐ ์ตœ์ƒ๋‹จ ์ด๋™)`;const img=document.createElement('img');const imgType=guessImageType(result.image_data);img.src=`data:image/${imgType};base64,${result.image_data}`;img.alt=result.name;img.className='result-image';img.loading='lazy';const infoDiv=document.createElement('div');infoDiv.className='result-info';const nameElem=document.createElement('div');nameElem.className='result-name';nameElem.textContent=result.name;const categoryElem=document.createElement('div');categoryElem.className=`result-category ${result.category==='npc'?'category-npc':'category-mob'}`;categoryElem.textContent=result.category==='npc'?'NPC':'๋ชฌ์Šคํ„ฐ';const similarityElem=document.createElement('div');similarityElem.className='result-similarity';similarityElem.textContent=`์œ ์‚ฌ๋„: ${(result.similarity*100).toFixed(1)}%`;infoDiv.append(nameElem,categoryElem,similarityElem);card.append(img,infoDiv);card.addEventListener('click',()=>{let textToCopy=result.name;if(result.name?.toLowerCase().includes('๋ฌผ์Œํ‘œ'))textToCopy='????';copyToClipboard(textToCopy,false);if(isAutoCopyActive&&!isAutoCopyPaused){console.log("[AutoCopy] Paused by card click.");isAutoCopyPaused=true;updateStatus(`์ž๋™ ๋ณต์‚ฌ ์ผ์‹œ ์ค‘์ง€๋จ (๊ฒฐ๊ณผ ํด๋ฆญ)`,true,false,true);textToCopyToClipboardLater=null}window.scrollTo({top:0,behavior:'smooth'})});return card}
function copyToClipboard(text, isAutoAttempt = false) {
if (!isAutoAttempt || (isAutoAttempt && isAutoCopyActive)) {
navigator.clipboard.writeText(text).then(() => {
if (isAutoAttempt && isAutoCopyActive) {
console.log(`[AutoCopy] Success Copy: ${text}`);
showToast(`์ž๋™ ๋ณต์‚ฌ: '${text}' ์™„๋ฃŒ`);
updateStatus(`์ž๋™ ๋ณต์‚ฌ: ${text} ๋ณต์‚ฌ๋จ`, true);
lastCopiedName = text;
textToCopyToClipboardLater = null;
} else if (!isAutoAttempt) {
showToast(`'${text}' ๋ณต์‚ฌ ์™„๋ฃŒ`);
textToCopyToClipboardLater = null;
}
}).catch(err => {
if (isAutoAttempt && isAutoCopyActive && (err.name === 'NotAllowedError' || err.message.toLowerCase().includes('document is not focused'))) {
console.warn(`[AutoCopy] Clipboard write rejected (likely inactive tab): ${err.name} - ${err.message}. Storing '${text}' for later.`);
textToCopyToClipboardLater = text;
showToast('ํƒญ ๋น„ํ™œ์„ฑ ์ƒํƒœ. ํ™œ์„ฑํ™” ์‹œ ์ž๋™ ๋ณต์‚ฌ๋ฉ๋‹ˆ๋‹ค.');
updateStatus(`์ž๋™ ๋ณต์‚ฌ ๋Œ€๊ธฐ: ${text} (ํƒญ ๋น„ํ™œ์„ฑ)`, true, false, isAutoCopyPaused);
} else {
console.error('Clipboard write failed:', err);
showToast('ํด๋ฆฝ๋ณด๋“œ ๋ณต์‚ฌ ์‹คํŒจ');
if (isAutoAttempt && isAutoCopyActive) {
updateStatus(`์ž๋™ ๋ณต์‚ฌ ์‹คํŒจ (์˜ค๋ฅ˜)`, true, true);
}
textToCopyToClipboardLater = null;
}
});
} else {
console.log("[AutoCopy] Attempted auto-copy while feature is inactive, ignoring.");
}
}
function handleVisibilityChange(){if(document.visibilityState==='visible'&&isAutoCopyActive&&textToCopyToClipboardLater){const textToCopyNow=textToCopyToClipboardLater;console.log(`[AutoCopy] Tab visible. Attempting delayed copy: '${textToCopyNow}'`);textToCopyToClipboardLater=null;navigator.clipboard.writeText(textToCopyNow).then(()=>{console.log(`[AutoCopy] Copied (delayed): ${textToCopyNow}`);showToast(`์ž๋™ ๋ณต์‚ฌ: '${textToCopyNow}' ์™„๋ฃŒ (์ง€์—ฐ๋จ)`);updateStatus(`์ž๋™ ๋ณต์‚ฌ: ${textToCopyNow} ๋ณต์‚ฌ๋จ`,true);lastCopiedName=textToCopyNow}).catch(err=>{console.error('[AutoCopy] Delayed clipboard write failed: ',err);showToast('์ž๋™ ๋ณต์‚ฌ ์‹คํŒจ (์˜ค๋ฅ˜ ๋ฐœ์ƒ)');updateStatus(`์ž๋™ ๋ณต์‚ฌ ์‹คํŒจ (์˜ค๋ฅ˜)`,true,true)})}else if(document.visibilityState==='hidden'&&textToCopyToClipboardLater){console.log("[AutoCopy] Tab hidden with pending copy.");updateStatus(`์ž๋™ ๋ณต์‚ฌ ๋Œ€๊ธฐ: ${textToCopyToClipboardLater} (ํƒญ ๋น„ํ™œ์„ฑ)`,true,false,isAutoCopyPaused)}}function showToast(message){const t=document.getElementById('toast-message');if(t)t.remove();const n=document.createElement('div');n.id='toast-message';n.textContent=message;document.body.appendChild(n);requestAnimationFrame(()=>n.style.opacity='1');setTimeout(()=>{n.style.opacity='0';n.addEventListener('transitionend',()=>n.remove())},2500)}function guessImageType(b64){if(!b64)return'png';if(b64.startsWith('/9j/'))return'jpeg';if(b64.startsWith('iVBOR'))return'png';if(b64.startsWith('R0lGOD'))return'gif';return'png'}loadFrameRect();adjustCanvasDisplaySize();updateCaptureFrameStyleVisuals()});</script></body></html>