| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122 |
- <!DOCTYPE html>
- <html lang="en">
- <head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1">
- <title>BizGaze Support — Share your screen</title>
- <style>
- :root{ --brand:#FFC708; --brand-d:#E0AC00; --blue:#1F3B73; --blue-d:#16294f; --blue-soft:#EAF0FB; --ink:#1f2430; --muted:#6b7280; --bg:#f6f8fb; --card:#ffffff; --line:#e6e9ef; }
- *{box-sizing:border-box;}
- body{font-family:'Segoe UI',system-ui,sans-serif;background:var(--bg);color:var(--ink);margin:0;}
- .stage{display:flex;min-height:100vh;}
- .brandpanel{flex:1;background:linear-gradient(160deg,var(--blue),var(--blue-d));color:#fff;display:flex;flex-direction:column;justify-content:center;align-items:center;text-align:center;padding:2.5rem;}
- .mark{width:88px;height:88px;border-radius:22px;background:var(--brand);display:grid;place-items:center;font-weight:800;font-size:2.6rem;color:var(--blue);margin-bottom:1.2rem;box-shadow:0 12px 30px rgba(0,0,0,.25);}
- .wordmark{font-size:2.2rem;font-weight:800;letter-spacing:.01em;}
- .wordmark span{color:var(--brand);}
- .tagline{color:#cdd7ee;margin-top:.6rem;font-size:1rem;max-width:300px;line-height:1.5;}
- .panelside{flex:1;display:flex;align-items:center;justify-content:center;padding:2rem;}
- .card{background:var(--card);border:1px solid var(--line);border-radius:18px;padding:2.4rem;max-width:440px;width:100%;text-align:center;box-shadow:0 10px 30px rgba(20,30,60,.06);}
- h1{font-size:1.45rem;margin:.2rem 0 .4rem;color:var(--blue);}
- .sub{color:var(--muted);font-size:.97rem;line-height:1.5;margin-bottom:1.6rem;}
- .codewrap{background:#fffdf2;border:2px dashed var(--brand);border-radius:14px;padding:1.2rem;}
- .codelabel{font-size:.78rem;letter-spacing:.08em;text-transform:uppercase;color:var(--muted);margin-bottom:.3rem;}
- .code{font-size:3rem;letter-spacing:.5rem;font-weight:800;color:var(--ink);}
- .status{margin-top:1.3rem;padding:.7rem 1rem;border-radius:10px;background:#f1f5f9;color:#475569;font-size:.92rem;}
- .status.on{background:#ecfdf3;color:#15803d;}
- .consent{margin-top:1.3rem;border:1px solid #c7d6f0;background:var(--blue-soft);border-left:5px solid var(--blue);border-radius:12px;padding:1.3rem;text-align:left;color:var(--blue-d);}
- .consent .who{font-weight:700;color:var(--blue);}
- .btns{margin-top:1rem;display:flex;gap:.6rem;}
- button{flex:1;padding:.8rem 1rem;border:none;border-radius:10px;font-weight:700;font-size:.98rem;cursor:pointer;}
- .grant{background:var(--brand);color:var(--ink);} .grant:hover{background:var(--brand-d);}
- .deny{background:#fff;color:var(--blue);border:1px solid #c7d6f0;} .deny:hover{background:#f3f6fc;}
- .foot{color:var(--muted);font-size:.8rem;margin-top:1.4rem;}
- .indicator{position:fixed;top:0;left:0;right:0;background:#dc2626;color:#fff;text-align:center;padding:.5rem;font-size:.9rem;display:none;font-weight:600;z-index:9;}
- .indicator.show{display:block;}
- @media(max-width:860px){ .stage{flex-direction:column;} .brandpanel{padding:2rem;min-height:auto;} .mark{width:60px;height:60px;border-radius:16px;font-size:1.8rem;margin-bottom:.7rem;} .wordmark{font-size:1.5rem;} .tagline{display:none;} }
- </style>
- </head>
- <body>
- <div class="indicator" id="indicator">● Your screen is being shared — close this tab anytime to stop</div>
- <div class="stage">
- <div class="brandpanel">
- <img src="/logo.png" alt="" style="width:88px;height:88px;border-radius:22px;object-fit:contain;margin-bottom:1.2rem;background:#fff;padding:8px;box-shadow:0 12px 30px rgba(0,0,0,.25)" onerror="this.replaceWith(Object.assign(document.createElement('div'),{className:'mark',textContent:'B'}))">
- <div class="wordmark">BizGaze <span>Support</span></div>
- <div class="tagline">Secure, instant remote support — no downloads, you stay in control.</div>
- </div>
- <div class="panelside">
- <div class="card">
- <h1>Let's get you connected</h1>
- <div class="sub">Share the code below with your BizGaze support agent.</div>
- <div class="codewrap">
- <div class="codelabel">Your session code</div>
- <div style="display:flex;align-items:center;justify-content:center;gap:.7rem">
- <div class="code" id="code">······</div>
- <button id="copyBtn" title="Click to copy" aria-label="Click to copy" style="flex:0 0 auto;width:38px;height:38px;padding:0;background:#fff;border:1px solid var(--brand);color:var(--blue);border-radius:8px;cursor:pointer;display:inline-flex;align-items:center;justify-content:center"><svg id="copyIcon" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg></button>
- </div>
- </div>
- <div id="status" class="status">Preparing your code…</div>
- <div id="consentBox"></div>
- <div class="foot">🔒 You stay in control. The agent can only see your screen after you tap Allow, and you can stop anytime.</div>
- </div>
- </div>
- </div>
- <script>
- const codeEl=document.getElementById('code'), statusEl=document.getElementById('status'),
- consentBox=document.getElementById('consentBox'), indicator=document.getElementById('indicator');
- const setStatus=(t,c='')=>{statusEl.textContent=t;statusEl.className='status '+c;};
- document.getElementById('copyBtn').onclick=async()=>{
- const code=codeEl.textContent.trim();
- if(!/^\d{6}$/.test(code)) return;
- try{ await navigator.clipboard.writeText(code); }
- catch(e){ const ta=document.createElement('textarea');ta.value=code;document.body.appendChild(ta);ta.select();document.execCommand('copy');ta.remove(); }
- const b=document.getElementById('copyBtn'); const old=b.innerHTML;
- b.innerHTML='<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="#16a34a" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6 9 17l-5-5"/></svg>';
- setTimeout(()=>{b.innerHTML=old;},1500);
- };
- let ws,pc,localStream,sessionId;
- ws=new WebSocket((location.protocol==='https:'?'wss://':'ws://')+location.host+'/ws');
- ws.onopen=()=>ws.send(JSON.stringify({type:'share-create'}));
- ws.onmessage=async(e)=>{const m=JSON.parse(e.data);switch(m.type){
- case 'share-code': codeEl.textContent=m.code; setStatus('Waiting for your agent to enter the code…'); break;
- case 'share-request': onAgentConnected(m); break;
- case 'start-stream': sessionId=m.sessionId; await startStreaming(); break;
- case 'answer': if(pc) await pc.setRemoteDescription(new RTCSessionDescription(m.sdp)); break;
- case 'ice-candidate': if(m.candidate&&pc) await pc.addIceCandidate(new RTCIceCandidate(m.candidate)); break;
- case 'session-ended': teardown(); break;
- case 'error': setStatus(m.message,''); break;
- }};
- ws.onclose=()=>setStatus('Connection closed. Refresh the page to start again.');
- function onAgentConnected(m){
- const cw=document.querySelector('.codewrap');
- if(cw) cw.style.display='none';
- setStatus('Your agent has connected. Please respond below.','on');
- showConsent(m);
- }
- function showConsent(m){
- const name=(m.technician&&m.technician.trim())?m.technician:'Your support agent';
- consentBox.innerHTML='<div class="consent">Your support agent <span class="who">'+esc(name)+'</span> would like to view your screen to help you.'+
- '<div class="btns"><button class="grant" id="g">Allow</button><button class="deny" id="d">Not now</button></div></div>';
- const allow=()=>{consentBox.innerHTML='';document.removeEventListener('keydown',onKey);ws.send(JSON.stringify({type:'consent',sessionId:m.sessionId,granted:true}));};
- const onKey=(e)=>{if(e.key==='Enter'){e.preventDefault();allow();}};
- document.addEventListener('keydown',onKey);
- document.getElementById('g').onclick=allow;
- document.getElementById('d').onclick=()=>{consentBox.innerHTML='';document.removeEventListener('keydown',onKey);ws.send(JSON.stringify({type:'consent',sessionId:m.sessionId,granted:false}));setStatus('Connection declined. Refresh this page if you need a new code.');};
- }
- async function startStreaming(){
- setStatus('In the popup: click the screen preview so it is selected, then press Share.','on');
- try{ localStream=await navigator.mediaDevices.getDisplayMedia({video:{displaySurface:'monitor',frameRate:{ideal:30}},audio:false,monitorTypeSurfaces:'include'}); }
- catch(err){ try{ws.send(JSON.stringify({type:'end-session',sessionId,reason:'share-cancelled'}));}catch(e){} setStatus('Screen share was cancelled. Refresh the page to try again.'); return; }
- indicator.classList.add('show'); setStatus('You are now sharing your screen with your agent.','on');
- pc=new RTCPeerConnection({iceServers:[{urls:'stun:stun.l.google.com:19302'}]});
- localStream.getTracks().forEach(t=>pc.addTrack(t,localStream));
- pc.ondatachannel=(ev)=>{ev.channel.onmessage=()=>{};};
- pc.onicecandidate=(ev)=>{if(ev.candidate)ws.send(JSON.stringify({type:'ice-candidate',sessionId,candidate:ev.candidate}));};
- const offer=await pc.createOffer(); await pc.setLocalDescription(offer);
- ws.send(JSON.stringify({type:'offer',sessionId,sdp:pc.localDescription}));
- localStream.getVideoTracks()[0].onended=()=>{ws.send(JSON.stringify({type:'end-session',sessionId,reason:'customer-ended'}));teardown();};
- }
- function teardown(){indicator.classList.remove('show');if(localStream){localStream.getTracks().forEach(t=>t.stop());localStream=null;}if(pc){pc.close();pc=null;}consentBox.innerHTML='';setStatus('Session ended. Refresh this page to get a new code.');}
- function esc(s){return String(s).replace(/[&<>"]/g,c=>({'&':'&','<':'<','>':'>','"':'"'}[c]));}
- </script>
- </body>
- </html>
|