Açıklama Yok
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta name="viewport" content="width=device-width, initial-scale=1">
  6. <title>BizGaze Connect</title>
  7. <style>
  8. :root{ --brand:#FFC708; --brand-d:#E0AC00; --blue:#1F3B73; --blue-d:#16294f; --blue-soft:#EAF0FB; --ink:#1f2430; --muted:#6b7280; --bg:#f6f8fb; --card:#fff; --line:#e6e9ef; --green:#16a34a; --red:#b91c1c; }
  9. *{box-sizing:border-box;}
  10. html,body{height:100%;}
  11. body{font-family:'Segoe UI',system-ui,sans-serif;background:var(--bg);color:var(--ink);margin:0;display:flex;flex-direction:column;height:100vh;overflow:hidden;}
  12. /* ---- Top bar ---- */
  13. header{background:var(--blue);padding:.7rem 1.4rem;display:flex;justify-content:space-between;align-items:center;flex:0 0 auto;}
  14. .brandrow{display:flex;align-items:center;gap:.6rem;}
  15. .logo{width:30px;height:30px;border-radius:8px;background:var(--brand);display:grid;place-items:center;font-weight:800;color:var(--blue);}
  16. .brand{font-weight:700;color:#fff;font-size:1.05rem;} .brand span.y{color:var(--brand);font-weight:700;}
  17. /* ---- Profile dropdown (from console.html) ---- */
  18. .profile{position:relative}
  19. .profile .pbtn{display:flex;align-items:center;gap:.5rem;background:rgba(255,255,255,.14);color:#fff;border:1px solid #46598c;border-radius:10px;padding:.4rem .85rem .4rem .5rem;font-weight:600;font-size:.88rem;cursor:pointer}
  20. .profile .pbtn:hover{background:rgba(255,255,255,.24)}
  21. .profile .pbtn .pav{width:28px;height:28px;border-radius:50%;background:var(--brand);color:var(--blue);display:grid;place-items:center;font-weight:800;font-size:.78rem}
  22. .profile .pmenu{position:absolute;right:0;top:calc(100% + 6px);background:#fff;border:1px solid #e6e9ef;border-radius:10px;box-shadow:0 10px 28px rgba(0,0,0,.18);min-width:210px;overflow:hidden;z-index:5000;display:none}
  23. .profile .pmenu.open{display:block}
  24. .profile .pmenu .phead{padding:.7rem .9rem;border-bottom:1px solid #eef1f6}
  25. .profile .pmenu .phead .n{font-weight:700;font-size:.9rem}
  26. .profile .pmenu .phead .e{color:var(--muted);font-size:.78rem}
  27. .profile .pmenu a{display:block;padding:.6rem .9rem;color:#1f2430;text-decoration:none;font-size:.9rem;cursor:pointer}
  28. .profile .pmenu a:hover{background:#f1f5f9}
  29. .profile .pmenu a.danger{color:#b91c1c;border-top:1px solid #eef1f6}
  30. /* ---- Shell ---- */
  31. .shell{flex:1 1 auto;display:flex;min-height:0;}
  32. /* ---- Icon rail ---- */
  33. .rail{width:74px;flex:0 0 74px;background:var(--card);border-right:1px solid var(--line);display:flex;flex-direction:column;align-items:center;padding:.8rem 0;gap:.4rem;}
  34. .railbtn{position:relative;width:50px;height:50px;border:none;background:transparent;border-radius:14px;color:var(--muted);cursor:pointer;display:grid;place-items:center;transition:background .12s,color .12s;}
  35. .railbtn:hover{background:var(--blue-soft);color:var(--blue);}
  36. .railbtn.active{background:var(--blue);color:#fff;}
  37. .railbtn .rdot{position:absolute;top:8px;right:8px;min-width:16px;height:16px;border-radius:99px;background:var(--brand);color:var(--blue);font-size:.62rem;font-weight:800;display:grid;place-items:center;padding:0 .2rem;border:2px solid var(--card);}
  38. .railbtn.active .rdot{border-color:var(--blue);}
  39. .railbtn .livedot{position:absolute;top:8px;right:8px;width:11px;height:11px;border-radius:50%;background:var(--green);border:2px solid var(--card);display:none;}
  40. .railbtn.active .livedot{border-color:var(--blue);}
  41. .railbtn.live .livedot{display:block;animation:livePulse 1.4s infinite;}
  42. @keyframes livePulse{0%,100%{opacity:1}50%{opacity:.3}}
  43. .railbtn .rlabel{font-size:.6rem;margin-top:0;}
  44. /* tooltip */
  45. .railbtn::after{content:attr(data-tip);position:absolute;left:calc(100% + 12px);top:50%;transform:translateY(-50%);background:var(--blue-d);color:#fff;padding:.35rem .6rem;border-radius:8px;font-size:.78rem;font-weight:600;white-space:nowrap;opacity:0;pointer-events:none;transition:opacity .14s;z-index:200;box-shadow:0 6px 16px rgba(0,0,0,.25);}
  46. .railbtn::before{content:"";position:absolute;left:calc(100% + 6px);top:50%;transform:translateY(-50%);border:6px solid transparent;border-right-color:var(--blue-d);opacity:0;pointer-events:none;transition:opacity .14s;z-index:200;}
  47. .railbtn:hover::after,.railbtn:hover::before{opacity:1;}
  48. .rail-spacer{flex:1 1 auto;}
  49. .caption{font-size:.58rem;color:var(--muted);text-align:center;line-height:1.2;}
  50. /* ---- Chat list column ---- */
  51. .chatcol{width:312px;flex:0 0 312px;background:var(--card);border-right:1px solid var(--line);display:flex;flex-direction:column;min-height:0;}
  52. .chatcol.hidden{display:none;}
  53. .side-head{padding:1rem 1rem .75rem;border-bottom:1px solid var(--line);}
  54. .side-title{display:flex;align-items:center;justify-content:space-between;margin-bottom:.7rem;}
  55. .side-title h2{font-size:.95rem;margin:0;color:var(--blue);}
  56. .newchat{width:30px;height:30px;border-radius:9px;border:none;background:var(--blue-soft);color:var(--blue);font-size:1.2rem;line-height:1;cursor:pointer;font-weight:700;display:grid;place-items:center;padding:0;}
  57. .newchat:hover{background:#dbe6fb;}
  58. .search{position:relative;}
  59. .search svg{position:absolute;left:.65rem;top:50%;transform:translateY(-50%);color:var(--muted);}
  60. .search input{width:100%;padding:.55rem .7rem .55rem 2.1rem;border-radius:10px;border:2px solid var(--line);background:#fbfcfe;color:var(--ink);font-size:.9rem;}
  61. .search input:focus{outline:none;border-color:var(--brand);}
  62. .chatlist{overflow-y:auto;flex:1 1 auto;padding:.4rem;}
  63. .chat-row{display:flex;gap:.7rem;align-items:center;padding:.6rem .65rem;border-radius:12px;cursor:pointer;position:relative;}
  64. .chat-row:hover{background:#f3f6fb;}
  65. .chat-row.active{background:var(--blue-soft);}
  66. .chat-row.active::before{content:"";position:absolute;left:0;top:.7rem;bottom:.7rem;width:3px;border-radius:3px;background:var(--blue);}
  67. .avatar{width:42px;height:42px;flex:0 0 42px;border-radius:50%;display:grid;place-items:center;color:#fff;font-weight:700;font-size:.92rem;position:relative;}
  68. .avatar .dot{position:absolute;right:-1px;bottom:-1px;width:11px;height:11px;border-radius:50%;border:2px solid #fff;background:#cbd2dd;}
  69. .avatar .dot.on{background:var(--green);}
  70. .chat-main{flex:1 1 auto;min-width:0;}
  71. .chat-top{display:flex;justify-content:space-between;align-items:baseline;gap:.5rem;}
  72. .chat-name{font-weight:600;font-size:.92rem;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
  73. .chat-time{color:var(--muted);font-size:.72rem;flex:0 0 auto;}
  74. .chat-bottom{display:flex;justify-content:space-between;align-items:center;gap:.5rem;margin-top:.15rem;}
  75. .chat-prev{color:var(--muted);font-size:.82rem;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;flex:1 1 auto;}
  76. .chat-row.unread .chat-prev{color:var(--ink);font-weight:500;}
  77. .chat-row.unread .chat-name{font-weight:700;}
  78. .badge{flex:0 0 auto;background:var(--blue);color:#fff;font-size:.7rem;font-weight:700;min-width:19px;height:19px;border-radius:99px;padding:0 .35rem;display:grid;place-items:center;}
  79. .no-results{padding:2rem 1rem;text-align:center;color:var(--muted);font-size:.85rem;}
  80. .demo-note{padding:.5rem 1rem;border-top:1px solid var(--line);color:var(--muted);font-size:.72rem;text-align:center;background:#fbfcfe;}
  81. /* ---- Main content ---- */
  82. .content{flex:1 1 auto;position:relative;min-width:0;min-height:0;background:var(--bg);}
  83. .panel{position:absolute;inset:0;display:none;}
  84. .panel.active{display:flex;}
  85. .panel.center{align-items:center;justify-content:center;padding:2rem;overflow-y:auto;}
  86. .panel iframe{width:100%;height:100%;border:0;display:block;background:var(--bg);}
  87. /* welcome + feature cards */
  88. .welcome{text-align:center;max-width:560px;}
  89. .welcome .wave{font-size:3rem;line-height:1;margin-bottom:.4rem;}
  90. .welcome h1{font-size:1.8rem;color:var(--blue);margin:.2rem 0 .5rem;}
  91. .welcome p{color:var(--muted);font-size:1rem;line-height:1.6;margin:0 auto 1.8rem;max-width:440px;}
  92. .wcards{display:flex;gap:1rem;flex-wrap:wrap;justify-content:center;}
  93. .wcard{flex:1;min-width:150px;max-width:180px;background:var(--card);border:1px solid var(--line);border-radius:14px;padding:1.2rem 1rem;cursor:pointer;transition:transform .12s,box-shadow .12s,border-color .12s;text-align:center;}
  94. .wcard:hover{transform:translateY(-3px);box-shadow:0 12px 28px rgba(20,30,60,.1);border-color:var(--brand);}
  95. .wcard .wi{width:46px;height:46px;border-radius:12px;display:grid;place-items:center;margin:0 auto .6rem;background:var(--blue-soft);color:var(--blue);}
  96. .wcard h3{margin:0 0 .2rem;font-size:.95rem;color:var(--blue);}
  97. .wcard p{margin:0;font-size:.78rem;color:var(--muted);line-height:1.4;}
  98. .card{background:var(--card);border:1px solid var(--line);border-radius:16px;padding:2.2rem;box-shadow:0 6px 18px rgba(20,30,60,.05);text-align:center;max-width:520px;}
  99. .feat-icon{width:72px;height:72px;border-radius:20px;display:grid;place-items:center;margin:0 auto 1.2rem;}
  100. .feat-icon.yellow{background:#fff6d8;color:var(--brand-d);}
  101. .card h1{font-size:1.45rem;margin:0 0 .5rem;color:var(--blue);}
  102. .card p{color:var(--muted);font-size:.95rem;line-height:1.55;margin:0 auto 1.6rem;max-width:400px;}
  103. .pill-soon{display:inline-block;background:#fff6d8;color:var(--brand-d);font-size:.74rem;font-weight:700;padding:.25rem .7rem;border-radius:99px;letter-spacing:.03em;margin-bottom:1.2rem;}
  104. .btn{display:inline-flex;align-items:center;gap:.5rem;text-decoration:none;padding:.8rem 1.6rem;background:var(--brand);color:var(--ink);border:none;border-radius:11px;font-weight:700;font-size:.95rem;cursor:pointer;}
  105. .btn:hover{background:var(--brand-d);}
  106. .hint{margin-top:1.4rem;font-size:.8rem;color:var(--muted);}
  107. /* conversation placeholder (selected chat, no backend yet) */
  108. .convo{flex-direction:column;display:flex;width:100%;height:100%;}
  109. .convo-head{display:flex;align-items:center;gap:.7rem;padding:.9rem 1.2rem;border-bottom:1px solid var(--line);background:var(--card);}
  110. .convo-back{border:none;background:var(--blue-soft);color:var(--blue);width:34px;height:34px;border-radius:9px;font-size:1.1rem;cursor:pointer;display:grid;place-items:center;flex:0 0 auto;}
  111. .convo-back:hover{background:#dbe6fb;}
  112. .convo-head .nm{font-weight:700;color:var(--ink);}
  113. .convo-head .st{font-size:.78rem;color:var(--muted);}
  114. .convo-body{flex:1;display:grid;place-items:center;text-align:center;color:var(--muted);padding:2rem;}
  115. .convo-body .big{font-size:2.4rem;margin-bottom:.4rem;}
  116. /* ---- Login (shown on /home when logged out) ---- */
  117. .authwrap{flex:1 1 auto;display:none;align-items:center;justify-content:center;padding:1.5rem;min-height:0;}
  118. .authcard{background:var(--card);border:1px solid var(--line);border-radius:16px;padding:2rem;max-width:400px;width:100%;box-shadow:0 10px 30px rgba(20,30,60,.08);}
  119. .authcard h1{font-size:1.3rem;color:var(--blue);margin:0 0 .3rem;text-align:center;}
  120. .authcard .sub{color:var(--muted);font-size:.9rem;text-align:center;margin-bottom:1.2rem;}
  121. .authtabs{display:flex;gap:.5rem;margin-bottom:1.1rem;}
  122. .authtabs button{flex:1;background:#eef1f6;color:var(--muted);font-weight:600;border:none;border-radius:9px;padding:.5rem;cursor:pointer;font-size:.9rem;}
  123. .authtabs button.active{background:var(--blue);color:#fff;}
  124. .authcard .lbl{display:block;font-size:.74rem;color:var(--muted);text-transform:uppercase;letter-spacing:.06em;margin:.7rem 0 .15rem;}
  125. .authcard input{width:100%;padding:.6rem .7rem;border-radius:10px;border:2px solid var(--line);background:#fbfcfe;color:var(--ink);font-size:.92rem;}
  126. .authcard input:focus{outline:none;border-color:var(--brand);}
  127. .authcard .gobtn{width:100%;margin-top:1rem;padding:.7rem;background:var(--brand);color:var(--ink);border:none;border-radius:10px;font-weight:700;cursor:pointer;font-size:.95rem;}
  128. .authcard .gobtn:hover{background:var(--brand-d);}
  129. .authcard .pwwrap{position:relative;} .authcard .pwwrap input{padding-right:2.6rem;}
  130. .authcard .eye{position:absolute;right:.35rem;top:50%;transform:translateY(-50%);background:none;border:none;padding:.3rem;width:auto;color:var(--muted);display:inline-flex;align-items:center;cursor:pointer;}
  131. .authcard .eye:hover{color:var(--blue);}
  132. .formerr{color:#b91c1c;font-weight:600;font-size:.88rem;margin:.7rem 0 0;min-height:1em;}
  133. .formerr.show{display:flex;align-items:center;gap:.5rem;background:#fee2e2;border:1px solid #fca5a5;border-radius:9px;padding:.6rem .75rem;animation:errShake .35s;}
  134. .formerr.show::before{content:"⚠";font-size:1rem;}
  135. @keyframes errShake{0%,100%{transform:translateX(0)}20%,60%{transform:translateX(-5px)}40%,80%{transform:translateX(5px)}}
  136. .hidden{display:none;}
  137. /* ---- Loading / toast ---- */
  138. .loading{position:fixed;inset:0;display:grid;place-items:center;background:var(--bg);z-index:9000;color:var(--muted);font-size:.9rem;}
  139. .toast{position:fixed;left:50%;bottom:1.6rem;transform:translateX(-50%) translateY(1rem);background:var(--blue);color:#fff;padding:.7rem 1.2rem;border-radius:10px;font-size:.88rem;box-shadow:0 10px 28px rgba(0,0,0,.22);opacity:0;pointer-events:none;transition:opacity .2s,transform .2s;z-index:9500;}
  140. .toast.show{opacity:1;transform:translateX(-50%) translateY(0);}
  141. @media (max-width:760px){
  142. .chatcol{width:260px;flex:0 0 260px;}
  143. }
  144. </style>
  145. </head>
  146. <body>
  147. <div class="loading" id="loading">Loading…</div>
  148. <header>
  149. <div class="brandrow">
  150. <img src="/logo.png" alt="" style="height:46px;width:auto;max-width:190px;border-radius:8px;object-fit:contain;background:#fff;padding:5px 12px;image-rendering:-webkit-optimize-contrast" onerror="this.replaceWith(Object.assign(document.createElement('div'),{className:'logo',textContent:'B'}))">
  151. <div class="brand">BizGaze <span class="y">Connect</span></div>
  152. </div>
  153. <div id="hdrRight"></div>
  154. </header>
  155. <div class="shell">
  156. <!-- ---------- Icon rail ---------- -->
  157. <nav class="rail" id="rail">
  158. <button class="railbtn active" data-tab="chat" data-tip="Chat" aria-label="Chat">
  159. <svg viewBox="0 0 24 24" width="22" height="22" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>
  160. <span class="rdot" id="railUnread" style="display:none">0</span>
  161. </button>
  162. <button class="railbtn" data-tab="share" data-tip="Share Screen" aria-label="Share Screen">
  163. <svg viewBox="0 0 24 24" width="22" height="22" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="3" width="20" height="14" rx="2"/><line x1="8" y1="21" x2="16" y2="21"/><line x1="12" y1="17" x2="12" y2="21"/></svg>
  164. <span class="livedot"></span>
  165. </button>
  166. <button class="railbtn" data-tab="connect" data-tip="Connect Screen" aria-label="Connect Screen">
  167. <svg viewBox="0 0 24 24" width="22" height="22" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12.55a11 11 0 0 1 14.08 0"/><path d="M1.42 9a16 16 0 0 1 21.16 0"/><path d="M8.53 16.11a6 6 0 0 1 6.95 0"/><line x1="12" y1="20" x2="12.01" y2="20"/></svg>
  168. <span class="livedot"></span>
  169. </button>
  170. <button class="railbtn" data-tab="meeting" data-tip="Meeting" aria-label="Meeting">
  171. <svg viewBox="0 0 24 24" width="22" height="22" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="23 7 16 12 23 17 23 7"/><rect x="1" y="5" width="15" height="14" rx="2" ry="2"/></svg>
  172. </button>
  173. <div class="rail-spacer"></div>
  174. </nav>
  175. <!-- ---------- Chat list (Chat tab only) ---------- -->
  176. <aside class="chatcol" id="chatcol">
  177. <div class="side-head">
  178. <div class="side-title">
  179. <h2>Chats</h2>
  180. <button class="newchat" id="newChat" title="New chat" aria-label="New chat">+</button>
  181. </div>
  182. <div class="search">
  183. <svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
  184. <input id="chatSearch" placeholder="Search chats" autocomplete="off">
  185. </div>
  186. </div>
  187. <div class="chatlist" id="chatlist"></div>
  188. <div class="demo-note">💬 Chat is coming soon — showing sample conversations</div>
  189. </aside>
  190. <!-- ---------- Main content ---------- -->
  191. <main class="content">
  192. <!-- Chat panel: welcome (no selection) OR conversation placeholder -->
  193. <div class="panel center active" data-panel="chat" id="chatPanel"></div>
  194. <!-- Share -->
  195. <div class="panel" data-panel="share" id="sharePanel"></div>
  196. <!-- Connect -->
  197. <div class="panel" data-panel="connect" id="connectPanel"></div>
  198. <!-- Meeting -->
  199. <div class="panel center" data-panel="meeting">
  200. <div class="card">
  201. <div class="feat-icon yellow">
  202. <svg viewBox="0 0 24 24" width="34" height="34" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="23 7 16 12 23 17 23 7"/><rect x="1" y="5" width="15" height="14" rx="2" ry="2"/></svg>
  203. </div>
  204. <span class="pill-soon">COMING SOON</span>
  205. <h1>Meetings are on the way</h1>
  206. <p>Soon you'll be able to host multi-party video meetings with your BizGaze team and customers — right here, no install needed.</p>
  207. <button class="btn" id="notifyBtn">🔔 Notify me when it's ready</button>
  208. <div class="hint">In the meantime, use <b>Share Screen</b> or <b>Connect Screen</b> from the left.</div>
  209. </div>
  210. </div>
  211. </main>
  212. </div>
  213. <div class="authwrap" id="authwrap"></div>
  214. <div class="toast" id="toast"></div>
  215. <script>
  216. // ---------- Helpers ----------
  217. function pEsc(s){return String(s==null?'':s).replace(/[&<>"]/g,c=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;'}[c]));}
  218. function initials(name){const p=String(name||'?').trim().split(/\s+/);return ((p[0]||'?')[0]+(p[1]?p[1][0]:'')).toUpperCase();}
  219. function firstName(name){return String(name||'').trim().split(/\s+/)[0]||'there';}
  220. const AV_COLORS=['#1F3B73','#2563eb','#0e7490','#7c3aed','#be185d','#b45309','#15803d','#9d174d'];
  221. function avColor(name){let h=0;for(const c of String(name))h=(h*31+c.charCodeAt(0))>>>0;return AV_COLORS[h%AV_COLORS.length];}
  222. let toastTimer=null;
  223. function toast(msg){const t=document.getElementById('toast');t.textContent=msg;t.classList.add('show');clearTimeout(toastTimer);toastTimer=setTimeout(()=>t.classList.remove('show'),2600);}
  224. // ---------- Profile dropdown (mirrors profileHTML()/wireProfile() from console.html) ----------
  225. function profileHTML(u){
  226. const display=u.name||u.email;
  227. return '<div class="profile"><button class="pbtn" id="pbtn">'
  228. + '<span class="pav">'+pEsc(initials(display))+'</span>'
  229. + pEsc(display)+' <span style="font-size:.65rem">&#9662;</span></button>'
  230. + '<div class="pmenu" id="pmenu">'
  231. + '<div class="phead"><div class="n">'+pEsc(display)+'</div><div class="e">'+pEsc(u.email)+(u.role?' · '+pEsc(u.role):'')+'</div></div>'
  232. + '<a href="/dashboard">Dashboard</a>'
  233. + '<a class="danger" id="plogout">Logout</a>'
  234. + '</div></div>';
  235. }
  236. function wireProfile(){
  237. const btn=document.getElementById('pbtn'),menu=document.getElementById('pmenu');
  238. if(!btn)return;
  239. btn.onclick=(e)=>{e.stopPropagation();menu.classList.toggle('open');};
  240. document.addEventListener('click',()=>menu.classList.remove('open'));
  241. const lo=document.getElementById('plogout');
  242. if(lo)lo.onclick=async()=>{try{await fetch('/api/logout',{method:'POST'});}catch(_){}location.href='/';};
  243. }
  244. // ---------- Mock chat data (placeholder — no chat backend yet, see CLAUDE.md) ----------
  245. const CHATS=[
  246. {name:'Anwi Systems', msg:"Perfect, the screen share worked great. Thanks!", time:'9:42 AM', unread:0, online:true},
  247. {name:'Priya Sharma', msg:"Can you connect to my screen at 3pm?", time:'9:15 AM', unread:2, online:true},
  248. {name:'GAPL Group', msg:"You: I've shared the 6-digit code with you", time:'Yesterday', unread:0, online:false},
  249. {name:'Battery Doctors', msg:"The invoice module is throwing an error again", time:'Yesterday', unread:5, online:true},
  250. {name:'Ramesh Marketing', msg:"You: Let me know once you're at your desk", time:'Mon', unread:0, online:false},
  251. {name:'STC Support', msg:"Typing…", time:'Mon', unread:1, online:true},
  252. {name:'Samruddhi Traders',msg:"Thanks for the help earlier 👍", time:'Sun', unread:0, online:false},
  253. {name:'DMS 3.0 Team', msg:"You: Closing the ticket, all resolved", time:'Fri', unread:0, online:false},
  254. ];
  255. let selectedChat=null; // index into CHATS, or null = welcome
  256. const listEl=document.getElementById('chatlist');
  257. function chatRowHTML(c,i){
  258. const cls=['chat-row'];
  259. if(selectedChat===i)cls.push('active');
  260. if(c.unread>0)cls.push('unread');
  261. return '<div class="'+cls.join(' ')+'" data-i="'+i+'">'
  262. + '<div class="avatar" style="background:'+avColor(c.name)+'">'+pEsc(initials(c.name))
  263. + '<span class="dot'+(c.online?' on':'')+'"></span></div>'
  264. + '<div class="chat-main">'
  265. + '<div class="chat-top"><span class="chat-name">'+pEsc(c.name)+'</span><span class="chat-time">'+pEsc(c.time)+'</span></div>'
  266. + '<div class="chat-bottom"><span class="chat-prev">'+pEsc(c.msg)+'</span>'
  267. + (c.unread>0?'<span class="badge">'+c.unread+'</span>':'')+'</div>'
  268. + '</div></div>';
  269. }
  270. function renderChats(filter){
  271. const q=(filter||'').trim().toLowerCase();
  272. const rows=CHATS.map((c,i)=>({c,i})).filter(({c})=>!q||c.name.toLowerCase().includes(q)||c.msg.toLowerCase().includes(q));
  273. listEl.innerHTML = rows.length
  274. ? rows.map(({c,i})=>chatRowHTML(c,i)).join('')
  275. : '<div class="no-results">No chats match “'+pEsc(filter)+'”.</div>';
  276. listEl.querySelectorAll('.chat-row').forEach(row=>{
  277. row.onclick=()=>{
  278. selectedChat=+row.dataset.i;
  279. CHATS[selectedChat].unread=0;
  280. renderChats(document.getElementById('chatSearch').value);
  281. renderChatPanel();
  282. updateRailUnread();
  283. };
  284. });
  285. }
  286. function updateRailUnread(){
  287. const total=CHATS.reduce((a,c)=>a+(c.unread||0),0);
  288. const d=document.getElementById('railUnread');
  289. if(total>0){ d.textContent=total>99?'99+':total; d.style.display='grid'; }
  290. else d.style.display='none';
  291. }
  292. // ---------- Chat main panel: welcome OR conversation placeholder ----------
  293. let ME={};
  294. function welcomeHTML(){
  295. return '<div class="welcome">'
  296. + '<div class="wave">👋</div>'
  297. + '<h1>Hi, '+pEsc(firstName(ME.name||ME.email))+'! Welcome to BizGaze Connect</h1>'
  298. + '<p>Pick a conversation on the left to start chatting, or jump straight into a session from the sidebar.</p>'
  299. + '<div class="wcards">'
  300. + '<div class="wcard" data-go="share"><div class="wi"><svg viewBox="0 0 24 24" width="22" height="22" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="3" width="20" height="14" rx="2"/><line x1="8" y1="21" x2="16" y2="21"/><line x1="12" y1="17" x2="12" y2="21"/></svg></div><h3>Share Screen</h3><p>Show your screen with a 6-digit code</p></div>'
  301. + '<div class="wcard" data-go="connect"><div class="wi"><svg viewBox="0 0 24 24" width="22" height="22" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12.55a11 11 0 0 1 14.08 0"/><path d="M1.42 9a16 16 0 0 1 21.16 0"/><path d="M8.53 16.11a6 6 0 0 1 6.95 0"/><line x1="12" y1="20" x2="12.01" y2="20"/></svg></div><h3>Connect Screen</h3><p>Enter a customer\'s code to help</p></div>'
  302. + '<div class="wcard" data-go="meeting"><div class="wi"><svg viewBox="0 0 24 24" width="22" height="22" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="23 7 16 12 23 17 23 7"/><rect x="1" y="5" width="15" height="14" rx="2" ry="2"/></svg></div><h3>Meeting</h3><p>Multi-party video — coming soon</p></div>'
  303. + '</div></div>';
  304. }
  305. function convoHTML(c){
  306. return '<div class="convo">'
  307. + '<div class="convo-head">'
  308. + '<button class="convo-back" id="convoBack" title="Back to home (Esc)" aria-label="Back to home">&#8592;</button>'
  309. + '<div class="avatar" style="width:38px;height:38px;flex:0 0 38px;background:'+avColor(c.name)+'">'+pEsc(initials(c.name))+'<span class="dot'+(c.online?' on':'')+'"></span></div>'
  310. + '<div><div class="nm">'+pEsc(c.name)+'</div><div class="st">'+(c.online?'Online':'Offline')+'</div></div>'
  311. + '</div>'
  312. + '<div class="convo-body"><div><div class="big">💬</div><div style="font-weight:600;color:var(--ink);margin-bottom:.3rem">Messaging is coming soon</div><div>Persistent 1:1 chat with '+pEsc(c.name)+' will live here.<br>For now, start a screen session from the left.</div></div></div>'
  313. + '</div>';
  314. }
  315. function renderChatPanel(){
  316. const el=document.getElementById('chatPanel');
  317. if(selectedChat==null){ el.classList.add('center'); el.innerHTML=welcomeHTML(); wireWelcome(); }
  318. else { el.classList.remove('center'); el.innerHTML=convoHTML(CHATS[selectedChat]); const b=document.getElementById('convoBack'); if(b) b.onclick=showWelcome; }
  319. }
  320. function wireWelcome(){
  321. document.querySelectorAll('#chatPanel .wcard').forEach(card=>{
  322. card.onclick=()=>switchTab(card.dataset.go);
  323. });
  324. }
  325. // ---------- Tabs (icon rail) ----------
  326. // Chat and Meeting are in-shell panels; Share and Connect load in the center panel via
  327. // a single, same-origin, lazily-loaded iframe (cheap isolation, no page navigation).
  328. const railBtns=document.querySelectorAll('.railbtn');
  329. const panels=document.querySelectorAll('.panel');
  330. const chatcol=document.getElementById('chatcol');
  331. let loaded={share:false,connect:false};
  332. function currentTab(){ const b=document.querySelector('.railbtn.active'); return b?b.dataset.tab:'chat'; }
  333. function switchTab(tab){
  334. railBtns.forEach(b=>b.classList.toggle('active',b.dataset.tab===tab));
  335. panels.forEach(p=>p.classList.toggle('active',p.dataset.panel===tab));
  336. chatcol.classList.toggle('hidden', tab!=='chat');
  337. // Lazy-load the embedded flows on first open; keep them mounted afterwards so a
  338. // live session survives tab switches.
  339. if(tab==='share' && !loaded.share){ document.getElementById('sharePanel').innerHTML='<iframe src="/share?embed=1" allow="camera; microphone; display-capture; clipboard-read; clipboard-write"></iframe>'; loaded.share=true; }
  340. if(tab==='connect' && !loaded.connect){ document.getElementById('connectPanel').innerHTML='<iframe src="/connect?embed=1" allow="camera; microphone; display-capture; clipboard-read; clipboard-write"></iframe>'; loaded.connect=true; }
  341. }
  342. function showWelcome(){ selectedChat=null; renderChats(document.getElementById('chatSearch').value); renderChatPanel(); updateRailUnread(); }
  343. railBtns.forEach(btn=>{ btn.onclick=()=>{
  344. const tab=btn.dataset.tab;
  345. // Re-clicking Chat (while already on it) returns to the welcome screen.
  346. if(tab==='chat' && currentTab()==='chat' && selectedChat!=null){ showWelcome(); }
  347. switchTab(tab);
  348. }; });
  349. // Esc clears the open conversation and brings back the welcome screen.
  350. document.addEventListener('keydown',(e)=>{ if(e.key==='Escape' && currentTab()==='chat' && selectedChat!=null){ showWelcome(); } });
  351. // Embedded Share/Connect flows report session start/stop so the rail can show a "live"
  352. // dot — that's how you know a session is still running after switching to Chat.
  353. window.addEventListener('message',(e)=>{
  354. if(e.origin!==location.origin) return;
  355. const d=e.data;
  356. if(!d||d.type!=='bzc-session'||(d.flow!=='share'&&d.flow!=='connect')) return;
  357. const btn=document.querySelector('.railbtn[data-tab="'+d.flow+'"]');
  358. if(!btn) return;
  359. btn.classList.toggle('live', !!d.active);
  360. if(d.active && currentTab()!==d.flow){
  361. toast((d.flow==='share'?'Screen share':'Connection')+' is live — tap the highlighted icon to return');
  362. }
  363. });
  364. // Sidebar + misc wiring
  365. document.getElementById('chatSearch').addEventListener('input',e=>renderChats(e.target.value));
  366. document.getElementById('newChat').onclick=()=>toast('New chat is coming soon');
  367. document.getElementById('notifyBtn').onclick=()=>toast("Thanks! We'll let you know when Meetings launches.");
  368. // ---------- Login (shown here on /home when logged out) ----------
  369. const EYE_OFF='<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24"/><line x1="1" y1="1" x2="23" y2="23"/></svg>';
  370. const EYE_ON='<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg>';
  371. function pwField(id,ph){return '<div class="pwwrap"><input id="'+id+'" type="password" placeholder="'+ph+'"><button type="button" class="eye" data-for="'+id+'" aria-label="Show password"></button></div>';}
  372. function wireEyes(){document.querySelectorAll('.eye').forEach(b=>{if(b._w)return;b._w=1;b.innerHTML=EYE_OFF;b.onclick=()=>{const inp=document.getElementById(b.getAttribute('data-for'));if(!inp)return;const show=inp.type==='password';inp.type=show?'text':'password';b.innerHTML=show?EYE_ON:EYE_OFF;};});}
  373. function onEnter(ids,fn){ids.forEach(id=>{const el=document.getElementById(id);if(el)el.addEventListener('keydown',e=>{if(e.key==='Enter'){e.preventDefault();fn();}});});}
  374. function showErr(id,msg){const el=document.getElementById(id);el.textContent=msg;el.classList.add('show');}
  375. function clearErr(id){const el=document.getElementById(id);el.textContent='';el.classList.remove('show');}
  376. async function postJSON(path,body){const r=await fetch(path,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(body)});const d=await r.json().catch(()=>({}));if(!r.ok)throw new Error(d.error||'request failed');return d;}
  377. async function renderLogin(){
  378. document.querySelector('.shell').style.display='none';
  379. const aw=document.getElementById('authwrap'); aw.style.display='flex';
  380. let regOpen=false; try{ regOpen=(await (await fetch('/api/setup-state')).json()).registrationOpen; }catch(_){}
  381. aw.innerHTML=`<div class="authcard">
  382. <h1>Welcome to BizGaze Connect</h1>
  383. <div class="sub">Sign in to access chats, screen share and connect.</div>
  384. ${regOpen?`<div class="authtabs">
  385. <button id="tabLogin" class="active">Sign in</button>
  386. <button id="tabReg">Register team</button>
  387. </div>`:''}
  388. <div id="loginForm">
  389. <span class="lbl">Email</span><input id="li_email" type="email" placeholder="you@bizgaze.com">
  390. <span class="lbl">Password</span>${pwField('li_pw','password')}
  391. <label style="display:flex;align-items:center;gap:.5rem;margin:.7rem 0;color:var(--ink);font-size:.9rem;cursor:pointer"><input type="checkbox" id="li_remember" style="width:18px;height:18px;accent-color:var(--blue);margin:0"> Remember me on this device</label>
  392. <button class="gobtn" id="li_btn">Sign in</button>
  393. <p id="li_err" class="formerr"></p>
  394. </div>
  395. ${regOpen?`<div id="regForm" class="hidden">
  396. <span class="lbl">Team name</span><input id="rg_team" placeholder="e.g. BizGaze Support">
  397. <span class="lbl">Email</span><input id="rg_email" type="email" placeholder="you@bizgaze.com">
  398. <span class="lbl">Password</span>${pwField('rg_pw','min 8 characters')}
  399. <button class="gobtn" id="rg_btn">Create team</button>
  400. <p id="rg_err" class="formerr"></p>
  401. </div>`:''}
  402. </div>`;
  403. document.getElementById('li_btn').onclick=doLogin;
  404. wireEyes();
  405. onEnter(['li_email','li_pw'],doLogin);
  406. if(regOpen){
  407. const lf=document.getElementById('loginForm'), rf=document.getElementById('regForm');
  408. const tl=document.getElementById('tabLogin'), tr=document.getElementById('tabReg');
  409. tl.onclick=()=>{lf.classList.remove('hidden');rf.classList.add('hidden');tl.classList.add('active');tr.classList.remove('active');};
  410. tr.onclick=()=>{rf.classList.remove('hidden');lf.classList.add('hidden');tr.classList.add('active');tl.classList.remove('active');};
  411. document.getElementById('rg_btn').onclick=doRegister;
  412. onEnter(['rg_team','rg_email','rg_pw'],doRegister);
  413. }
  414. }
  415. async function doLogin(){
  416. clearErr('li_err');
  417. try{
  418. await postJSON('/api/login',{email:document.getElementById('li_email').value,password:document.getElementById('li_pw').value,remember:document.getElementById('li_remember').checked});
  419. location.reload();
  420. }catch(e){ showErr('li_err', /invalid credentials/i.test(e.message)?'Incorrect email or password. Please try again.':e.message); }
  421. }
  422. async function doRegister(){
  423. clearErr('rg_err');
  424. try{
  425. await postJSON('/api/register',{email:document.getElementById('rg_email').value,password:document.getElementById('rg_pw').value,teamName:document.getElementById('rg_team').value});
  426. await postJSON('/api/login',{email:document.getElementById('rg_email').value,password:document.getElementById('rg_pw').value});
  427. location.reload();
  428. }catch(e){ showErr('rg_err', e.message); }
  429. }
  430. // ---------- Boot: show the app if signed in, otherwise the login ----------
  431. (async function(){
  432. let me=null;
  433. try{ const r=await fetch('/api/me'); if(r.ok) me=await r.json(); }catch(_){}
  434. if(!me){ await renderLogin(); document.getElementById('loading').style.display='none'; return; }
  435. ME=me;
  436. document.getElementById('hdrRight').innerHTML=profileHTML(me);
  437. wireProfile();
  438. renderChats('');
  439. renderChatPanel();
  440. updateRailUnread();
  441. document.getElementById('loading').style.display='none';
  442. })();
  443. </script>
  444. </body>
  445. </html>