Edit Code · SiloTalk Softphone
Back to CMS
Open Page
Source Code Viewer
39714 bytes loaded
<?php if (($_GET['test_mode'] ?? '') !== '1') { $target = '/silocloud/apps/silotalk/phone.php'; if (!empty($_SERVER['QUERY_STRING'])) { parse_str((string)$_SERVER['QUERY_STRING'], $query); unset($query['test_mode']); if ($query) $target .= '?' . http_build_query($query); } header('Location: ' . $target, true, 302); exit; } // SiloTalk identity — reads from PHP session if available, falls back to URL param or default (Lael Alexander / S3601248) if (session_status() === PHP_SESSION_NONE) { @session_start(); } $siloUserUid = $_SESSION['user_uid'] ?? $_GET['user_uid'] ?? 'usr_c25bc0878bee6db0'; $siloNumber = $_SESSION['silo_number'] ?? $_GET['silo_number'] ?? 'S3601248'; $siloName = $_SESSION['display_name'] ?? $_GET['display_name'] ?? 'Lael Alexander'; ?> <?php $pageTitle = "SiloTalk Voice"; $logo = "/assets/images/SiloTalk.png"; $calls = [ ["num"=>"(405) 879-6012", "meta"=>"May 21, 2:38 PM", "type"=>"missed", "avatar"=>"A"], ["num"=>"(405) 475-7156", "meta"=>"May 21, 12:58 PM", "type"=>"inbound", "avatar"=>"B"], ["num"=>"(405) 631-2743", "meta"=>"May 21, 12:32 PM", "type"=>"outbound", "avatar"=>"C"], ["num"=>"(405) 941-7528", "meta"=>"May 21, 11:53 AM", "type"=>"inbound", "avatar"=>"D"], ["num"=>"(405) 717-2410", "meta"=>"May 21, 11:12 AM", "type"=>"outbound", "avatar"=>"E"], ["num"=>"(405) 798-7235", "meta"=>"Suspected spam · May 21", "type"=>"spam", "avatar"=>"!"], ["num"=>"(405) 469-0614", "meta"=>"May 21, 9:24 AM", "type"=>"inbound", "avatar"=>"F"], ["num"=>"(405) 663-0512", "meta"=>"Suspected spam · May 20", "type"=>"spam", "avatar"=>"!"], ["num"=>"(405) 743-7056", "meta"=>"May 20, 4:53 PM", "type"=>"outbound", "avatar"=>"G"] ]; $keypad = [ ["1",""], ["2","ABC"], ["3","DEF"], ["4","GHI"], ["5","JKL"], ["6","MNO"], ["7","PQRS"], ["8","TUV"], ["9","WXYZ"], ["*",""], ["0","+"], ["#",""] ]; ?> <!doctype html> <html lang="en"> <head><link rel="icon" type="image/png" href="/silocloud/assets/images/cloud.png"> <meta charset="utf-8"> <title><?= htmlspecialchars($pageTitle) ?></title> <meta name="viewport" content="width=device-width, initial-scale=1"> <style> :root{ --bg:#f4f6f9; --panel:#ffffff; --line:#dfe5ee; --line2:#cfd7e3; --ink:#101828; --muted:#5f6b7a; --blue:#1f5eff; --green:#128447; --red:#b42318; --orange:#f97316; --dark:#111827; --r:8px; } *{box-sizing:border-box} html,body{height:100%;overflow:hidden} body{margin:0;background:var(--bg);font-family:Arial,Helvetica,sans-serif;color:var(--ink)} .app{ height:100vh; display:grid; grid-template-rows:68px 56px minmax(0,1fr); overflow:hidden; } .topbar{ height:68px; background:#fff; border-bottom:1px solid var(--line); display:grid; grid-template-columns:240px minmax(320px,520px) 1fr 260px; gap:18px; align-items:center; padding:0 22px; } .brand{display:flex;align-items:center;gap:12px} .brand img{width:40px;height:40px;object-fit:contain} .brand b{font-size:24px;letter-spacing:-.03em} .search input{ width:100%; height:40px; border:1px solid var(--line); border-radius:8px; padding:0 14px; font-size:14px; background:#f8fafc; } .status-select{ justify-self:start; height:40px; border:1px solid var(--line2); border-radius:8px; background:#fff; padding:0 14px; font-weight:800; color:#344054; } .user{ justify-self:end; display:flex; align-items:center; gap:12px; font-size:13px; } .user .avatar{ width:38px; height:38px; border-radius:50%; background:#111827; color:#fff; display:flex; align-items:center; justify-content:center; font-weight:900; } .notice{ height:56px; background:#eaf2ff; border-bottom:1px solid #c8dcff; display:flex; align-items:center; justify-content:space-between; padding:0 28px; color:#0f2f68; font-size:14px; } .notice b{margin-right:14px} .notice a{color:#1f5eff;text-decoration:none;font-weight:800;margin-left:18px} .notice button{border:0;background:transparent;font-size:24px;color:#1f5eff;cursor:pointer} .workspace{ min-height:0; display:grid; grid-template-columns:72px 340px minmax(0,1fr) 392px; overflow:hidden; } .rail{ background:#fff; border-right:1px solid var(--line); display:flex; flex-direction:column; align-items:center; padding-top:18px; gap:16px; } .rail-btn{ width:48px; height:48px; border:0; border-radius:8px; background:transparent; position:relative; display:flex; align-items:center; justify-content:center; } .rail-btn.active{background:#eaf2ff} .rail-btn img{width:23px;height:23px;object-fit:contain} .badge{ position:absolute; right:3px; top:2px; min-width:20px; height:20px; border-radius:999px; background:#1f5eff; color:#fff; display:flex; align-items:center; justify-content:center; font-size:11px; font-weight:900; } .call-list{ background:#fff; border-right:1px solid var(--line); overflow:hidden; display:grid; grid-template-rows:58px minmax(0,1fr); } .list-head{ height:58px; display:flex; align-items:center; justify-content:space-between; padding:0 16px; border-bottom:1px solid var(--line); } .list-head h2{font-size:18px;margin:0} .calls{overflow:auto} .call-row{ min-height:70px; display:grid; grid-template-columns:46px minmax(0,1fr); gap:12px; align-items:center; padding:10px 14px; border-bottom:1px solid #eef1f5; } .call-row:hover{background:#f8fafc} .call-avatar{ width:38px; height:38px; border-radius:50%; display:flex; align-items:center; justify-content:center; color:#fff; font-weight:900; background:#2563eb; } .call-avatar.spam{background:#b42318} .call-avatar.missed{background:#7c3aed} .call-avatar.inbound{background:#0f766e} .call-avatar.outbound{background:#0369a1} .call-row b{display:block;font-size:15px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis} .call-row small{color:#475467;font-size:13px} .call-row small.spam{color:#b42318;font-weight:800} .center{ min-width:0; background:#fff; display:grid; grid-template-rows:minmax(0,1fr) 94px; overflow:hidden; } .welcome{ display:flex; align-items:center; justify-content:center; text-align:center; padding:28px; position:relative; } .welcome-inner{ max-width:720px; } .welcome img{ width:92px; height:92px; object-fit:contain; margin-bottom:24px; } .welcome h1{ margin:0 0 12px; font-size:38px; letter-spacing:-.04em; } .welcome p{ margin:0 auto; color:#475467; line-height:1.55; font-size:16px; max-width:520px; } .stat-grid{ margin-top:30px; display:grid; grid-template-columns:repeat(3,1fr); gap:12px; } .stat{ border:1px solid var(--line); background:#f8fafc; border-radius:8px; padding:16px; } .stat small{color:#667085} .stat b{display:block;font-size:25px;margin-top:6px} .center-actions{ height:94px; border-top:1px solid var(--line); display:flex; align-items:center; justify-content:center; gap:38px; background:#fff; } .action-btn{ border:0; background:transparent; display:flex; flex-direction:column; align-items:center; gap:6px; font-size:12px; color:#344054; font-weight:700; } .action-btn img{width:25px;height:25px;object-fit:contain} .dialer{ background:#fff; border-left:1px solid var(--line); display:grid; grid-template-rows:66px 70px 84px minmax(0,1fr) 54px; overflow:hidden; } .call-as{ padding:14px 18px; border-bottom:1px solid var(--line); } .call-as small{display:block;color:#667085;margin-bottom:4px} .call-as b{font-size:16px} .input-line{ display:grid; grid-template-columns:1fr 74px; gap:12px; align-items:center; padding:0 18px; border-bottom:1px solid var(--line); } .input-line input{ border:0; outline:none; font-size:15px; } .dial-small{ height:42px; border:0; border-radius:999px; background:#e5e7eb; color:#98a2b3; } .suggestions{ border-bottom:1px solid var(--line); padding:10px 18px; } .suggestions small{ display:block; text-transform:uppercase; letter-spacing:.08em; color:#667085; font-size:11px; font-weight:900; margin-bottom:8px; } .suggestion{ display:grid; grid-template-columns:42px 1fr; gap:10px; align-items:center; } .suggestion .photo{ width:38px; height:38px; border-radius:50%; background:#eaf2ff; display:flex; align-items:center; justify-content:center; font-weight:900; color:#1f5eff; } .suggestion b{display:block;font-size:14px} .suggestion span{color:#475467;font-size:13px} .keypad{ display:grid; grid-template-columns:repeat(3,1fr); align-content:center; justify-items:center; gap:8px 0; padding:18px 28px; } .key{ width:82px; height:62px; border:0; background:#fff; font-size:34px; color:#20242b; cursor:pointer; } .key small{ display:block; font-size:11px; letter-spacing:.18em; color:#5f6b7a; } .hide-keypad{ height:54px; border-top:1px solid var(--line); display:flex; align-items:center; justify-content:center; gap:8px; color:#475467; font-size:14px; } @media(max-width:1200px){ .workspace{grid-template-columns:72px 300px 1fr} .dialer{display:none} .topbar{grid-template-columns:220px 1fr 190px} .status-select{display:none} } /* MULTI-SCREEN TRANSITION MOCKUP */ .screen-pane{ display:none; height:100%; min-height:0; overflow:hidden; animation:screenFade .22s ease-out; } .screen-pane.active{ display:grid; } @keyframes screenFade{ from{opacity:.2;transform:translateX(10px)} to{opacity:1;transform:translateX(0)} } .rail-btn{ transition:.18s ease; } .rail-btn:hover{ background:#f1f5f9; } .screen-chat{ grid-template-columns:340px minmax(0,1fr); background:#fff; } .chat-list-panel{ border-right:1px solid var(--line); display:grid; grid-template-rows:58px 54px minmax(0,1fr); overflow:hidden; } .chat-panel-head{ height:58px; border-bottom:1px solid var(--line); display:flex; align-items:center; justify-content:space-between; padding:0 16px; } .chat-panel-head h2{ margin:0; font-size:20px; } .chat-search{ padding:8px 14px; border-bottom:1px solid var(--line); } .chat-search input{ width:100%; height:38px; border:1px solid var(--line); border-radius:8px; padding:0 12px; } .chat-thread{ min-height:68px; display:grid; grid-template-columns:42px minmax(0,1fr) auto; gap:10px; align-items:center; padding:9px 14px; border-bottom:1px solid #eef1f5; } .chat-thread.active{ background:#eaf2ff; } .chat-thread b{ display:block; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; } .chat-thread small{ color:#475467; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; } .thread-avatar{ width:38px; height:38px; border-radius:50%; background:#eaf2ff; color:#1f5eff; display:flex; align-items:center; justify-content:center; font-weight:900; } .chat-room{ display:grid; grid-template-rows:62px minmax(0,1fr) 76px; overflow:hidden; } .chat-room-head{ border-bottom:1px solid var(--line); display:flex; align-items:center; justify-content:space-between; padding:0 18px; } .chat-room-title{ display:flex; gap:10px; align-items:center; } .chat-actions{ display:flex; gap:16px; } .chat-actions img{ width:22px; height:22px; object-fit:contain; } .chat-body{ padding:22px; background:#fbfcff; overflow:hidden; } .message{ max-width:560px; margin-bottom:14px; display:flex; } .message.right{ margin-left:auto; justify-content:flex-end; } .bubble{ padding:12px 14px; border-radius:10px; background:#fff; border:1px solid var(--line); line-height:1.45; } .message.right .bubble{ background:#1f5eff; color:#fff; border-color:#1f5eff; } .chat-composer{ border-top:1px solid var(--line); display:grid; grid-template-columns:44px minmax(0,1fr) 92px; gap:10px; align-items:center; padding:0 16px; } .chat-composer input{ height:42px; border:1px solid var(--line); border-radius:8px; padding:0 12px; } .chat-composer button{ height:42px; border:0; border-radius:8px; background:#1f5eff; color:white; font-weight:900; } .screen-simple{ background:#fff; place-items:center; text-align:center; } .simple-card{ width:min(620px,90%); border:1px solid var(--line); border-radius:8px; background:#fff; padding:34px; box-shadow:0 10px 28px rgba(15,23,42,.06); } .simple-card img{ width:74px; height:74px; object-fit:contain; margin-bottom:18px; } .simple-card h2{ margin:0 0 10px; font-size:30px; } .simple-card p{ margin:0; color:#667085; line-height:1.55; } /* KEYPAD COLLAPSE */ .workspace.keypad-hidden{ grid-template-columns:72px 340px minmax(0,1fr) 0px !important; } .workspace.keypad-hidden .dialer{ width:0 !important; min-width:0 !important; overflow:hidden !important; border-left:0 !important; } .workspace{ transition:grid-template-columns .25s ease; } .dialer{ transition:all .25s ease; } .hide-keypad{ cursor:pointer; user-select:none; font-weight:700; } /* SMART SPACE MODES */ .workspace.focus-mode{ grid-template-columns:72px 86px minmax(0,1fr) 0px !important; } .workspace.focus-mode .call-list{ overflow:hidden; } .workspace.focus-mode .list-head h2, .workspace.focus-mode .list-head b, .workspace.focus-mode .call-row b, .workspace.focus-mode .call-row small{ display:none !important; } .workspace.focus-mode .call-row{ grid-template-columns:46px !important; justify-content:center !important; padding:10px 0 !important; } .workspace.focus-mode .call-list{ grid-template-rows:58px minmax(0,1fr) !important; } .workspace.focus-mode .calls{ overflow:auto; } .workspace.focus-mode .dialer{ width:0 !important; min-width:0 !important; overflow:hidden !important; border-left:0 !important; } .workspace.dialpad-open{ grid-template-columns:72px 340px minmax(0,1fr) 392px !important; } .workspace.contact-mode{ grid-template-columns:72px 340px minmax(0,1fr) 392px !important; } .restore-dialpad{ position:absolute; right:0; top:50%; transform:translateY(-50%); width:34px; height:120px; border:1px solid var(--line); border-right:0; background:#fff; color:#1f5eff; font-weight:900; writing-mode:vertical-rl; text-orientation:mixed; display:none; align-items:center; justify-content:center; cursor:pointer; z-index:20; } .workspace.keypad-hidden .restore-dialpad, .workspace.focus-mode .restore-dialpad{ display:flex; } .center{ position:relative; } /* FOOTER LOCK: transitions affect the content area only */ .center{ display:grid !important; grid-template-rows:minmax(0,1fr) 94px !important; height:100% !important; min-height:0 !important; overflow:hidden !important; } .center-actions{ grid-row:2 / 3 !important; height:94px !important; min-height:94px !important; max-height:94px !important; border-top:1px solid var(--line) !important; background:#fff !important; align-self:end !important; z-index:30 !important; } #screen-calls.active{ display:contents !important; } #screen-calls .welcome{ grid-row:1 / 2 !important; min-height:0 !important; } #screen-calls .center-actions{ grid-row:2 / 3 !important; } .screen-pane.active:not(#screen-calls){ grid-row:1 / 2 !important; min-height:0 !important; height:100% !important; } .screen-chat, .screen-simple{ grid-row:1 / 2 !important; height:100% !important; min-height:0 !important; } </style> <script> (function(){ const q = new URLSearchParams(window.location.search); if (q.get("test_mode") === "1") { window.SILOTALK_TEST_MODE = true; window.SILOTALK_USER_UID = q.get("user_uid") || window.SILOTALK_USER_UID || ""; window.SILOTALK_SILO_NUMBER = q.get("silo_number") || window.SILOTALK_SILO_NUMBER || ""; window.SILOTALK_DISPLAY_NAME = q.get("display_name") || window.SILOTALK_DISPLAY_NAME || "SiloTalk Test Customer"; document.addEventListener("DOMContentLoaded", function(){ const banner = document.createElement("div"); banner.style.position = "fixed"; banner.style.top = "10px"; banner.style.left = "50%"; banner.style.transform = "translateX(-50%)"; banner.style.zIndex = "99999"; banner.style.background = "#101827"; banner.style.color = "#fff"; banner.style.border = "1px solid rgba(255,255,255,.2)"; banner.style.borderRadius = "8px"; banner.style.padding = "8px 12px"; banner.style.font = "12px Arial"; banner.innerHTML = "<b>Customer Test Phone</b> · " + window.SILOTALK_DISPLAY_NAME + " · " + window.SILOTALK_SILO_NUMBER; document.body.appendChild(banner); }); } })(); </script> <style> #st-incoming{display:none;position:fixed;top:0;left:0;right:0;z-index:9999;background:#1f5eff;color:#fff;padding:13px 24px;flex-direction:row;align-items:center;justify-content:space-between;font-size:14px;font-weight:700;box-shadow:0 2px 14px rgba(0,0,0,.35)} #st-call-overlay{display:none;position:fixed;bottom:110px;right:24px;z-index:9998;background:#111827;color:#fff;border-radius:14px;padding:20px 24px;min-width:240px;box-shadow:0 8px 32px rgba(0,0,0,.4);border:2px solid #374151;font-family:inherit} .st-co-label{font-size:11px;color:#9ca3af;margin-bottom:4px;text-transform:uppercase;letter-spacing:.06em} .st-co-who{font-size:20px;font-weight:900;margin-bottom:4px} .st-co-timer{font-size:13px;color:#6b7280;margin-bottom:14px} .st-co-btns{display:flex;gap:10px} .st-btn{border:0;border-radius:8px;padding:8px 16px;cursor:pointer;font-size:13px;font-weight:700;transition:background .15s} .st-btn.mute{background:#374151;color:#fff}.st-btn.mute.active{background:#f97316} .st-btn.hangup{background:#ef4444;color:#fff} .st-btn.answer{background:#22c55e;color:#fff} .st-btn.decline{background:#ef4444;color:#fff} #st-incoming .st-ic-btns{display:flex;gap:10px} </style> </head> <body> <div class="app"> <header class="topbar"> <div class="brand"> <img src="<?= htmlspecialchars($logo) ?>"> <div> <b>SiloTalk Voice</b><br> <small>Communications Console</small> </div> </div> <div class="search"> <input placeholder="Search SiloTalk"> </div> <button class="status-select">Receiving calls ▾</button> <div class="user"> <span>⚙</span> <span>▦</span> <div class="avatar">LA</div> </div> </header> <div class="notice"> <div> <b>Try one-click dialing</b> Register SiloTalk so browser phone links can launch calls through your SiloCloud workspace. <a href="#">Register now</a> <a href="#">Learn more</a> </div> <button>×</button> </div> <main class="workspace"> <aside class="rail"> <button class="rail-btn active" title="Calls" data-screen="calls"> <img src="/silocloud/assets/images/20260501060110_b09a2e84_Silotalk_Black.png"> <span class="badge">85</span> </button> <button class="rail-btn" title="Messages" data-screen="chat"> <img src="/silocloud/assets/icons/comments-solid.svg"> <span class="badge">2</span> </button> <button class="rail-btn" title="Voicemail" data-screen="voicemail"> <img src="/silocloud/assets/icons/ellipsis-h-solid.svg"> <span class="badge">9</span> </button> <button class="rail-btn" title="Contacts" data-screen="contacts"><img src="/silocloud/assets/icons/address-book-solid-full.svg"></button> <button class="rail-btn" title="Archive" data-screen="settings"><img src="/silocloud/assets/icons/tools-solid.svg"></button> </aside> <section class="call-list"> <div class="list-head"> <h2>Calls</h2> <b style="color:#1f5eff">New</b> </div> <div class="calls"> <?php foreach($calls as $c): ?> <div class="call-row"> <div class="call-avatar <?= htmlspecialchars($c["type"]) ?>"> <?= htmlspecialchars($c["avatar"]) ?> </div> <div> <b><?= htmlspecialchars($c["num"]) ?></b> <small class="<?= $c["type"] === "spam" ? "spam" : "" ?>"> <?= $c["type"] === "missed" ? "↙ " : "↘ " ?><?= htmlspecialchars($c["meta"]) ?> </small> </div> </div> <?php endforeach; ?> </div> </section> <section class="center"> <button class="restore-dialpad" type="button">Dialpad</button> <div class="screen-pane active" id="screen-calls"> <div class="welcome"> <div class="welcome-inner"> <img src="<?= htmlspecialchars($logo) ?>"> <h1>Hi Lael.</h1> <p>You have 2 unread messages, 85 missed calls, and 9 new voicemails in your SiloTalk communications workspace.</p> <div class="stat-grid"> <div class="stat"><small>Unread Messages</small><b>2</b></div> <div class="stat"><small>Missed Calls</small><b>85</b></div> <div class="stat"><small>Voicemails</small><b>9</b></div> </div> </div> </div> <footer class="center-actions"> <button class="action-btn"><img src="/silocloud/assets/images/20260501060127_69137e93_microphone_black.png"><span>Mute</span></button> <button class="action-btn"><img src="/silocloud/assets/icons/video-solid.svg"><span>Video</span></button> <button class="action-btn" data-screen="chat"><img src="/silocloud/assets/icons/comments-solid.svg"><span>Messages</span></button> <button class="action-btn" data-screen="contacts"><img src="/silocloud/assets/icons/users-solid.svg"><span>Contacts</span></button> <button class="action-btn" data-screen="settings"><img src="/silocloud/assets/icons/tools-solid.svg"><span>Settings</span></button> </footer> </div> <div class="screen-pane screen-chat" id="screen-chat"> <aside class="chat-list-panel"> <div class="chat-panel-head"> <h2>Chats</h2> <b style="color:#1f5eff">New</b> </div> <div class="chat-search"> <input placeholder="Search or start a new chat"> </div> <div> <div class="chat-thread active"> <div class="thread-avatar">SD</div> <div><b>Silo Dev Team</b><small>Runtime appliance and app registry structure.</small></div> <small>Today</small> </div> <div class="chat-thread"> <div class="thread-avatar">SC</div> <div><b>Silo Cloud</b><small>Group members have changed.</small></div> <small>11/24</small> </div> <div class="chat-thread"> <div class="thread-avatar">CM</div> <div><b>Silo Communications</b><small>RTC relay and messaging stack ready.</small></div> <small>Yesterday</small> </div> <div class="chat-thread"> <div class="thread-avatar">AI</div> <div><b>SiloAI Console</b><small>Assistant context is ready.</small></div> <small>Thu</small> </div> </div> </aside> <section class="chat-room"> <div class="chat-room-head"> <div class="chat-room-title"> <div class="thread-avatar">SD</div> <div><b>Silo Dev Team</b><br><small style="color:#667085">12 members · secure SiloTalk workspace</small></div> </div> <div class="chat-actions"> <img src="/silocloud/assets/images/20260501060110_b09a2e84_Silotalk_Black.png" title="Call"> <img src="/silocloud/assets/icons/video-solid.svg" title="Video"> <img src="/silocloud/assets/icons/ellipsis-h-solid.svg" title="More"> </div> </div> <div class="chat-body"> <div class="message"> <div class="bubble"><b>Silo Dev Team</b><br>Let's finalize how SiloTalk, SiloChat, SiloConnect, and SiloAI transition inside the same communications family.</div> </div> <div class="message right"> <div class="bubble"><b>You</b><br>Keep the look consistent with the voice console. The user should feel like they moved screens, not apps.</div> </div> <div class="message"> <div class="bubble"><b>SiloAI Console</b><br>Transition state confirmed. Chat is now rendered as an internal screen within the SiloTalk shell.</div> </div> </div> <footer class="chat-composer"> <button>+</button> <input placeholder="Message Silo Dev Team"> <button>Send</button> </footer> </section> </div> <div class="screen-pane screen-simple" id="screen-voicemail"> <div class="simple-card"> <img src="/silocloud/assets/images/SiloTalk.png"> <h2>Voicemail</h2> <p>New voicemail queue, transcriptions, playback, routing, and message escalation will appear here.</p> </div> </div> <div class="screen-pane screen-simple" id="screen-contacts"> <div class="simple-card"> <img src="/silocloud/assets/icons/users-solid.svg"> <h2>Contacts</h2> <p>Directory contacts, SiloID users, teams, departments, and call/chat/video escalation controls will appear here.</p> </div> </div> <div class="screen-pane screen-simple" id="screen-settings"> <div class="simple-card"> <img src="/silocloud/assets/icons/tools-solid.svg"> <h2>Settings</h2> <p>Device selection, call routing, SiloID binding, notification rules, and endpoint runtime configuration will appear here.</p> </div> </div> </section> <aside class="dialer"> <div class="call-as"> <small>Call as</small> <b>(405) 265-6031</b> </div> <div class="input-line"> <input placeholder="Enter a name or number"> <button class="dial-small">☎</button> </div> <div class="suggestions"> <small>Suggestions</small> <div class="suggestion"> <div class="photo">LA</div> <div> <b>Lakeida Alexander</b> <span>(832) 886-7421 · Mobile</span> </div> </div> </div> <div class="keypad"> <?php foreach($keypad as $k): ?> <button class="key"> <?= htmlspecialchars($k[0]) ?> <small><?= htmlspecialchars($k[1]) ?></small> </button> <?php endforeach; ?> </div> <div class="hide-keypad">⌄ Hide keypad</div> </aside> </main> </div> <script> document.querySelectorAll('[data-screen]').forEach(btn=>{ btn.addEventListener('click', ()=>{ const target = btn.getAttribute('data-screen'); document.querySelectorAll('.rail-btn').forEach(b=>b.classList.remove('active')); document.querySelectorAll('.rail-btn[data-screen="'+target+'"]').forEach(b=>b.classList.add('active')); document.querySelectorAll('.screen-pane').forEach(p=>p.classList.remove('active')); const pane = document.getElementById('screen-' + target); if(pane) pane.classList.add('active'); const titleMap = { calls: 'Calls', chat: 'Chats', voicemail: 'Voicemail', contacts: 'Contacts', settings: 'Settings' }; const listTitle = document.querySelector('.list-head h2'); if(listTitle && titleMap[target]) listTitle.textContent = titleMap[target]; }); }); </script> <script> (function(){ const workspace = document.querySelector('.workspace'); const toggle = document.querySelector('.hide-keypad'); if(!workspace || !toggle) return; let hidden = false; toggle.addEventListener('click', function(){ hidden = !hidden; if(hidden){ workspace.classList.add('keypad-hidden'); toggle.innerHTML = '⌃ Show keypad'; }else{ workspace.classList.remove('keypad-hidden'); toggle.innerHTML = '⌄ Hide keypad'; } }); })(); </script> <script> // SMART SCREEN SPACE CONTROLLER (function(){ const workspace = document.querySelector('.workspace'); const restoreDialpad = document.querySelector('.restore-dialpad'); function setMode(mode){ if(!workspace) return; workspace.classList.remove('keypad-hidden','focus-mode','dialpad-open','contact-mode'); if(mode === 'calls'){ workspace.classList.add('dialpad-open'); } if(mode === 'contacts'){ workspace.classList.add('contact-mode'); workspace.classList.add('dialpad-open'); } if(mode === 'chat' || mode === 'video' || mode === 'voicemail'){ workspace.classList.add('focus-mode'); } if(mode === 'settings'){ workspace.classList.add('keypad-hidden'); } } document.querySelectorAll('[data-screen]').forEach(btn=>{ btn.addEventListener('click', ()=>{ const target = btn.getAttribute('data-screen'); if(target === 'contacts'){ setMode('contacts'); } else if(target === 'chat'){ setMode('chat'); } else if(target === 'voicemail'){ setMode('voicemail'); } else if(target === 'calls'){ setMode('calls'); } else { setMode(target); } }); }); if(restoreDialpad){ restoreDialpad.addEventListener('click', ()=>{ workspace.classList.remove('keypad-hidden','focus-mode'); workspace.classList.add('dialpad-open'); }); } // Make footer Contacts restore contacts + dialpad document.querySelectorAll('.action-btn[data-screen="contacts"]').forEach(btn=>{ btn.addEventListener('click', ()=>setMode('contacts')); }); })(); </script> <!-- SiloRTC Core Engine — must load before UI adapters --> <script src="/silocloud/apps/silotalk/assets/js/silotalk-ws-client.js?v=2.1"></script> <script src="/silocloud/apps/silotalk/assets/js/silortc-core/silortc-policy.js?v=2.1"></script> <script src="/silocloud/apps/silotalk/assets/js/silortc-core/silortc-events.js?v=2.1"></script> <script src="/silocloud/apps/silotalk/assets/js/silortc-core/silortc-devices.js?v=2.1"></script> <script src="/silocloud/apps/silotalk/assets/js/silortc-core/silortc-api.js?v=2.1"></script> <script src="/silocloud/apps/silotalk/assets/js/silortc-core/silortc-session.js?v=2.1"></script> <script src="/silocloud/apps/silotalk/assets/js/silortc-core/silortc-core.js?v=2.1"></script> <script src="/silocloud/apps/silotalk/assets/js/silotalk-rack-adapter.js?v=1.0.1" defer></script> <script src="/silocloud/apps/silotalk/assets/js/silotalk-softphone-ui.js?v=1.0.0" defer></script> <script src="/silocloud/apps/silotalk/assets/js/silotalk-call-controls.js?v=1.0.0" defer></script> <script src="/silocloud/apps/silotalk/assets/js/silotalk-dialpad-fix.js?v=1.0.0" defer></script> <script src="/silocloud/apps/silotalk/assets/js/silotalk-device-diagnostics.js?v=1.0.0" defer></script> <script src="/silocloud/apps/silotalk/assets/js/silotalk-customer-call-controls.js?v=1.0.0" defer></script> <script src="/silocloud/apps/silotalk/assets/js/silotalk-customer-test-relay-v2.js?v=2.0.0" defer></script> <script src="/silocloud/apps/silotalk/assets/js/silotalk-customer-webrtc-video.js?v=1.0.0" defer></script> <!-- Incoming call banner --> <div id="st-incoming"> <span id="st-ic-text">Incoming call...</span> <div class="st-ic-btns"> <button class="st-btn answer" id="st-btn-answer">Answer</button> <button class="st-btn decline" id="st-btn-decline">Decline</button> </div> </div> <!-- Active call overlay --> <div id="st-call-overlay"> <div class="st-co-label">Active Call</div> <div class="st-co-who" id="st-co-who">—</div> <div class="st-co-timer" id="st-co-timer">00:00</div> <div class="st-co-btns"> <button class="st-btn mute" id="st-btn-mute">Mute</button> <button class="st-btn hangup" id="st-btn-hangup">Hang Up</button> </div> <audio id="st-remote-audio" autoplay playsinline style="display:none"></audio> </div> <!-- SiloRTC Softphone Identity (PHP-injected) --> <script> window.SILOTALK_USER_UID = '<?= htmlspecialchars($siloUserUid, ENT_QUOTES) ?>'; window.SILOTALK_SILO_NUMBER = '<?= htmlspecialchars($siloNumber, ENT_QUOTES) ?>'; window.SILOTALK_DISPLAY_NAME = '<?= htmlspecialchars($siloName, ENT_QUOTES) ?>'; </script> <!-- SiloRTC Softphone Wiring --> <script> (function(){ var USER_UID = window.SILOTALK_USER_UID || ''; var SILO_NUMBER = window.SILOTALK_SILO_NUMBER || ''; var DISPLAY_NAME = window.SILOTALK_DISPLAY_NAME || 'SiloTalk User'; var rtc = null, muted = false, pendingOffer = null, callTimer = null; function showIncoming(from){ var t = document.getElementById('st-ic-text'); if(t) t.textContent = 'Incoming call from ' + (from || 'Unknown'); var b = document.getElementById('st-incoming'); if(b) b.style.display = 'flex'; } function hideIncoming(){ var b = document.getElementById('st-incoming'); if(b) b.style.display = 'none'; } function showActive(who){ var el = document.getElementById('st-co-who'); if(el) el.textContent = who || 'Active Call'; var ov = document.getElementById('st-call-overlay'); if(ov) ov.style.display = 'block'; var secs = 0; clearInterval(callTimer); callTimer = setInterval(function(){ secs++; var m = String(Math.floor(secs/60)).padStart(2,'0'); var s = String(secs % 60).padStart(2,'0'); var te = document.getElementById('st-co-timer'); if(te) te.textContent = m + ':' + s; }, 1000); } function hideActive(){ var ov = document.getElementById('st-call-overlay'); if(ov) ov.style.display = 'none'; clearInterval(callTimer); var te = document.getElementById('st-co-timer'); if(te) te.textContent = '00:00'; var btn = document.getElementById('st-btn-hangup'); var dialBtn = document.querySelector('.dial-small'); if(dialBtn){ dialBtn.disabled = false; dialBtn.textContent = String.fromCharCode(9990); } } function wireAudio(){ var el = document.getElementById('st-remote-audio'); if(el && rtc && rtc.remoteStream){ el.srcObject = rtc.remoteStream; } } async function init(){ try{ var events = new SiloRTCEvents(); rtc = SiloRTCCore.create({events}); events.on('call.incoming', function(ev){ showIncoming(ev.from_silo_number || ev.from_user_uid); }); events.on('call.started', function(ev){ showActive(rtc.target && (rtc.target.silo_number || rtc.target.user_uid) || 'Connected'); setTimeout(wireAudio, 600); }); events.on('call.answered', function(ev){ hideIncoming(); showActive(rtc.target && (rtc.target.silo_number || rtc.target.user_uid) || 'Caller'); setTimeout(wireAudio, 600); }); events.on('call.ended', function(){ hideActive(); hideIncoming(); pendingOffer = null; }); events.on('call.remote_ended', function(){ hideActive(); hideIncoming(); pendingOffer = null; }); events.on('pc.connection_state', function(ev){ if(ev.state === 'connected'){ wireAudio(); var ov = document.getElementById('st-call-overlay'); if(ov) ov.style.borderColor = '#22c55e'; } if(ev.state === 'failed' ){ var ov = document.getElementById('st-call-overlay'); if(ov) ov.style.borderColor = '#ef4444'; } }); await rtc.bootstrapCustomer({ user_uid: USER_UID, silo_number: SILO_NUMBER, display_name: DISPLAY_NAME, state: 'OK', endpoint_type: 'browser', label: 'SiloTalk Browser' }); rtc.connectSignaling(); // Intercept automatic offer processing — hold until user clicks Answer if(rtc.ws && rtc.ws.handlers){ rtc.ws.handlers['offer'] = [function(ev){ pendingOffer = ev; showIncoming(ev.from_silo_number || ev.from_user_uid || 'Unknown'); }]; // Fix ws.on('*') wildcard signature bug in silortc-session.js (read-only on server) // SiloTalkWSClient calls '*' handlers as fn(data) but session.js expects fn(type,data) if(rtc.ws.emit){ var _origEmit = rtc.ws.emit.bind(rtc.ws); rtc.ws.emit = function(type, data){ _origEmit(type, data); // Patch: also re-emit on rtc.events so ws.* diagnostics work if(rtc.events) rtc.events.emit('ws.' + (type || 'unknown'), data); }; } } // Update "Call as" label var callAsLabel = document.querySelector('.call-as b'); if(callAsLabel) callAsLabel.textContent = SILO_NUMBER + ' \u00b7 Ready'; } catch(e){ console.error('[SiloRTC] init failed:', e); var callAsLabel = document.querySelector('.call-as b'); if(callAsLabel) callAsLabel.textContent = (SILO_NUMBER || 'SiloTalk') + ' \u00b7 Offline'; } } // Dial button document.addEventListener('DOMContentLoaded', function(){ var dialBtn = document.querySelector('.dial-small'); var dialInput = document.querySelector('.input-line input'); if(dialBtn) dialBtn.addEventListener('click', async function(){ var num = dialInput ? dialInput.value.trim() : ''; if(!num){ alert('Enter a Silo number to dial (e.g. S3601244)'); return; } if(!rtc) { alert('SiloTalk is not connected. Refresh the page.'); return; } try{ dialBtn.disabled = true; dialBtn.textContent = '...'; await rtc.startCall(num, null, null, 'voice'); } catch(err){ alert('Call failed: ' + err.message); dialBtn.disabled = false; dialBtn.textContent = String.fromCharCode(9990); } }); // Keypad keys append digits document.querySelectorAll('.key').forEach(function(key){ key.addEventListener('click', function(){ var digit = (key.childNodes[0] ? key.childNodes[0].textContent : '').trim(); if(digit && dialInput) dialInput.value += digit; }); }); // Notice bar close var noticeClose = document.querySelector('.notice button'); if(noticeClose) noticeClose.addEventListener('click', function(){ var n = document.querySelector('.notice'); if(n) n.style.display = 'none'; }); // Init after load (short delay to ensure all scripts parsed) setTimeout(function(){ if(typeof SiloRTCCore !== 'undefined'){ init(); } else{ console.error('[SiloRTC] SiloRTCCore not found — check script load order'); } }, 200); }); // Answer / Decline / Hangup / Mute via delegation document.addEventListener('click', async function(e){ var id = e.target.id; if(id === 'st-btn-answer'){ if(!rtc || !pendingOffer){ alert('No pending call.'); return; } try{ await rtc.receiveOffer(pendingOffer, null, null); pendingOffer = null; hideIncoming(); showActive(rtc.target && (rtc.target.silo_number || rtc.target.user_uid) || 'Caller'); } catch(err){ console.error('Answer failed:', err); } } if(id === 'st-btn-decline'){ pendingOffer = null; hideIncoming(); if(rtc) await rtc.hangup().catch(function(){}); } if(id === 'st-btn-hangup'){ if(rtc) await rtc.hangup().catch(function(){}); hideActive(); hideIncoming(); } if(id === 'st-btn-mute'){ if(rtc && rtc.devices && rtc.devices.localStream){ muted = !muted; rtc.devices.localStream.getAudioTracks().forEach(function(t){ t.enabled = !muted; }); var mb = document.getElementById('st-btn-mute'); if(mb){ mb.textContent = muted ? 'Unmute' : 'Mute'; mb.classList.toggle('active', muted); } } } }); })(); </script> </body> </html> PHP