diff --git a/brand-assets/ADD-BRAND-ASSETS.md b/brand-assets/ADD-BRAND-ASSETS.md new file mode 100644 index 0000000..37b4fa3 --- /dev/null +++ b/brand-assets/ADD-BRAND-ASSETS.md @@ -0,0 +1,122 @@ +# TASK FOR CLAUDE (VS Code): Apply the Biz Connect brand — icons, splash, loaders, toasts + +Read this whole file, then execute it. Work CONSERVATIVELY — only ADD / restyle, do NOT +refactor unrelated code. The app is live in production. Find the right spots in the CURRENT +code yourself (don't assume old names). Summarise all changes and STOP for my review before +committing anything. + +Repo root: C:\BizGaze_Support\remote-access-app +Master brand assets are already placed in: brand-assets\ + - app-icon-1024.png (1024 full-bleed app icon master) + - splash-2732-dark.png (2732 splash, brand blue — primary) + - splash-2732-light.png (2732 splash, white — optional light theme) + - loader-ring.svg (default spinner, light backgrounds) + - loader-orbit.svg (branded spinner, light backgrounds) + - loader-orbit-dark.svg (branded spinner, dark/overlay backgrounds) +Brand colours: blue #1F3B73, blue-dark #16294F, yellow #FFC708, gold #E0AC00. + +Note: brand-assets/ is only the SOURCE. Put the GENERATED / used copies where each target +needs them — web assets under server/public/ (served over HTTP); native icons/splash into the +platform build folders (Electron / Android / iOS) as applicable. + +## 1. App icons (from brand-assets/app-icon-1024.png) +Generate and place: +- Windows .ico (multi-size 16/32/48/256) — fixes the tiny/blurry taskbar icon. +- Favicon: server/public/favicon.ico + a 32px PNG; add to each page : + + +- PWA icons 192, 512, and a maskable 512; reference them in the web manifest, and set the + manifest "background_color" and "theme_color" to "#1F3B73". +- Native mobile icons (Android mipmap set + adaptive, iOS AppIcon set) into their folders. + +## 2. Splash (from brand-assets/splash-2732-dark.png) +- Web/PWA loading + Electron splash: brand-blue background #1F3B73 with the logo centred + (use the 2732 image, or reuse the app's splash.html if present). +- Native launch screens: use the 2732 image (Android 12 SplashScreen background #1F3B73 + the + app icon; iOS LaunchScreen background #1F3B73 + centred logo). Keep native launch STATIC. +- Use splash-2732-light.png only if a light theme is supported. + +## 3. Loaders (from brand-assets/loader-*.svg) +Copy the three SVGs into server/public/ (e.g. server/public/loaders/). Wire them: +- Everyday "Loading…" and in-app spinners -> loader-ring.svg +- The hero "Connecting…" moment (session connect) -> loader-orbit.svg (light) / loader-orbit-dark.svg (on dark) +Show one centered in the middle of the screen/panel while loading; hide when done. Always pair +show with hide on BOTH the success and error paths so it can never get stuck. + +## 4. Branded notification toasts +Create server/public/bizconnect-toast.css with EXACTLY: + +```css +/* Biz Connect — branded notification toast. BZToast.success('…') / .error / .message / .info */ +.bzt-wrap{position:fixed;top:16px;right:16px;z-index:2147483600;display:flex;flex-direction:column;gap:10px;max-width:min(380px,92vw)} +@supports(top:env(safe-area-inset-top)){.bzt-wrap{top:calc(16px + env(safe-area-inset-top));right:calc(16px + env(safe-area-inset-right))}} +.bzt{display:flex;align-items:flex-start;gap:12px;background:#fff;color:#1f2430;border-radius:14px;padding:12px 14px; + box-shadow:0 12px 30px rgba(16,26,53,.20);border-left:5px solid #1F3B73; + transform:translateX(120%);opacity:0;transition:transform .3s cubic-bezier(.2,.7,.2,1),opacity .3s} +.bzt.bzt-in{transform:translateX(0);opacity:1} +.bzt.success{border-left-color:#16a34a}.bzt.error{border-left-color:#b91c1c} +.bzt-badge{flex:none;width:34px;height:34px;border-radius:50%;display:grid;place-items:center;background:#1F3B73} +.bzt.success .bzt-badge{background:#16a34a}.bzt.error .bzt-badge{background:#b91c1c} +.bzt-badge svg{width:20px;height:20px} +.bzt-body{flex:1;min-width:0;padding-top:1px} +.bzt-title{font:700 13.5px/1.3 'Segoe UI',system-ui,sans-serif;color:#1F3B73;margin:0 0 1px} +.bzt.success .bzt-title{color:#15803d}.bzt.error .bzt-title{color:#b91c1c} +.bzt-msg{font:500 13px/1.4 'Segoe UI',system-ui,sans-serif;color:#3a4152;overflow-wrap:anywhere} +.bzt-x{flex:none;background:none;border:none;color:#9aa3b2;cursor:pointer;font-size:18px;line-height:1;padding:2px 4px} +.bzt-x:hover{color:#1f2430} +@media(prefers-reduced-motion:reduce){.bzt{transition:opacity .2s}} +``` + +Create server/public/bizconnect-toast.js with EXACTLY: + +```js +/* Biz Connect toast API. Requires bizconnect-toast.css. + BZToast.success('Saved'); BZToast.error('Connection lost'); BZToast.message('Hi', {title:'Ravi'}); */ +window.BZToast=(function(){ + var wrap=null; + var ICON={ + message:'', + info:'', + success:'', + error:'' + }; + function esc(s){return String(s==null?'':s).replace(/[&<>"]/g,function(c){return{'&':'&','<':'<','>':'>','"':'"'}[c];});} + function ensure(){ if(wrap) return wrap; wrap=document.createElement('div'); wrap.className='bzt-wrap'; document.body.appendChild(wrap); return wrap; } + function show(message,opts){ + opts=opts||{}; var type=opts.type||'message'; var w=ensure(); + var t=document.createElement('div'); t.className='bzt '+type; + t.innerHTML='
'+(ICON[type]||ICON.message)+'
' + +(opts.title?'
'+esc(opts.title)+'
':'') + +'
'+esc(message)+'
'; + w.appendChild(t); requestAnimationFrame(function(){ t.classList.add('bzt-in'); }); + var dur=(opts.duration==null?4000:opts.duration), timer; + function close(){ t.classList.remove('bzt-in'); setTimeout(function(){ if(t.parentNode) t.parentNode.removeChild(t); },320); clearTimeout(timer); } + t.querySelector('.bzt-x').onclick=close; if(dur>0) timer=setTimeout(close,dur); return close; + } + return { show:show, + message:function(m,o){o=o||{};o.type='message';return show(m,o);}, + success:function(m,o){o=o||{};o.type='success';return show(m,o);}, + error:function(m,o){o=o||{};o.type='error';return show(m,o);}, + info:function(m,o){o=o||{};o.type='info';return show(m,o);} }; +})(); +``` + +Then, on every page that shows notifications (connect, share, home, dashboard, console), +include both files near the top BEFORE the page's own inline +(Load the JS via src only — never inline.) + +Now REPLACE the existing toast / notification pop-ups with the branded API (keep the same +triggers and text, just route them through BZToast so they look on-brand): +- Incoming chat message -> BZToast.message(text, {title: senderName}) +- A success (e.g. recording/transcript saved, agent added) -> BZToast.success('…') +- An error (connection lost, upload failed, invalid code) -> BZToast.error('…') +- Neutral info -> BZToast.info('…') +Remove the old inline toast markup/styles it replaces. Toasts appear top-right and auto-dismiss. + +## 5. Verify (do not commit until I review) +- node --check any JS you extract/touch; confirm every page still loads. +- Confirm: taskbar/favicon icon is crisp, splash shows on launch, a loader appears centered + during connect/report, and notifications now use the branded toast. +- List exactly which files changed, then STOP for my review. diff --git a/desktop/PACKAGING.md b/desktop/PACKAGING.md index 97fd9ed..7926051 100644 --- a/desktop/PACKAGING.md +++ b/desktop/PACKAGING.md @@ -22,8 +22,9 @@ Output in `desktop/dist/`: - `latest.yml` — the update manifest electron-updater reads - `*.blockmap` — enables delta downloads -The release build points at production (`SERVER_URL` defaults to `https://remote.bizgaze.com` -in `main.js`). +A PACKAGED build points at production (`main.js` defaults `SERVER_URL` to +`https://remote.bizgaze.com` when `app.isPackaged`); running from source in dev defaults to +`http://localhost:8090`. `SERVER_URL` overrides either. ## Publish a release (self-hosted feed) 1. Bump `version` in `desktop/package.json` (semver — electron-updater compares this). diff --git a/desktop/README.md b/desktop/README.md index 5ee3f2d..f2326dc 100644 --- a/desktop/README.md +++ b/desktop/README.md @@ -3,11 +3,15 @@ Electron shell that loads the live Connect web UI and adds native screen capture. See the overall plan in [../CLIENTS.md](../CLIENTS.md). -## Run (dev) +## Run (dev = local testing) ```bash npm install -SERVER_URL=http://localhost:8090 npm start # default is https://remote.bizgaze.com +npm start # dev auto-targets http://localhost:8090 (your local server) +SERVER_URL=https://remote.bizgaze.com npm start # …or point dev at production to compare ``` +`npm start` IS the local desktop test — no separate "local" installer needed. In dev (unpackaged) +the shell defaults to the local server; a PACKAGED installer defaults to production. `SERVER_URL` +overrides either. (Bash/Git-Bash syntax above; in PowerShell: `$env:SERVER_URL='…'; npm start`.) ## Build installers ```bash diff --git a/desktop/main.js b/desktop/main.js index fe04ad9..30154cc 100644 --- a/desktop/main.js +++ b/desktop/main.js @@ -97,9 +97,26 @@ ipcMain.on('set-unread', (_e, { count, dataUrl } = {}) => { } catch (_) {} }); -const SERVER_URL = (process.env.SERVER_URL || 'https://remote.bizgaze.com').replace(/\/+$/, ''); +// Server origin: a PACKAGED build (the installer) points at production; running from source in dev +// (`npm start`, unpackaged) defaults to the local server so you can test the shell against localhost +// with no flags or separate "local" build. SERVER_URL always overrides (e.g. point dev at prod). +const SERVER_URL = (process.env.SERVER_URL || (app.isPackaged ? 'https://remote.bizgaze.com' : 'http://localhost:8090')).replace(/\/+$/, ''); let win; +let splash; + +// A tiny brand-blue splash (splash.html) shown while the web UI loads, so launch feels instant +// and on-brand instead of a blank window. Closed as soon as the main window is ready to show. +function createSplash() { + splash = new BrowserWindow({ + width: 440, height: 440, frame: false, resizable: false, center: true, + backgroundColor: '#1F3B73', skipTaskbar: true, alwaysOnTop: true, show: true, + webPreferences: { contextIsolation: true, nodeIntegration: false }, + }); + splash.loadFile(path.join(__dirname, 'splash.html')); + splash.on('closed', () => { splash = null; }); +} +function closeSplash() { if (splash) { try { splash.close(); } catch (_) {} splash = null; } } function createWindow() { win = new BrowserWindow({ @@ -108,7 +125,8 @@ function createWindow() { minWidth: 880, minHeight: 600, title: 'Biz Connect', - backgroundColor: '#0f1830', + backgroundColor: '#1F3B73', + show: false, // reveal only once the page is ready — the splash covers the gap webPreferences: { preload: path.join(__dirname, 'preload.js'), contextIsolation: true, @@ -118,6 +136,12 @@ function createWindow() { }, }); + // Reveal the main window when its first paint is ready, and retire the splash. A fallback + // timer guarantees we never get stuck on the splash if the load stalls. + const reveal = () => { closeSplash(); if (win && !win.isVisible()) { win.show(); win.focus(); } }; + win.once('ready-to-show', reveal); + setTimeout(reveal, 12000); + // Open the landing page (same entry as the website): the "before login" screen with the // no-login "Share my screen" option + sign-in. It redirects logged-in users straight to /home. win.loadURL(SERVER_URL + '/'); @@ -158,6 +182,7 @@ function configureSession() { app.whenReady().then(() => { configureSession(); + createSplash(); createWindow(); Menu.setApplicationMenu(null); // hide the default menu bar; the web UI is the chrome app.on('activate', () => { if (BrowserWindow.getAllWindows().length === 0) createWindow(); }); diff --git a/desktop/package.json b/desktop/package.json index 334b9c8..d38af5d 100644 --- a/desktop/package.json +++ b/desktop/package.json @@ -1,6 +1,6 @@ { "name": "biz-connect-desktop", - "version": "0.1.2", + "version": "0.1.3", "description": "Biz Connect technician desktop client — loads the Connect web UI with native screen capture", "author": { "name": "BizGaze", @@ -12,8 +12,7 @@ "dist": "electron-builder" }, "dependencies": { - "electron-updater": "^6.3.9", - "node-notifier": "^10.0.1" + "electron-updater": "^6.3.9" }, "devDependencies": { "electron": "^31.0.0", @@ -26,9 +25,6 @@ "buildResources": "build", "output": "dist" }, - "asarUnpack": [ - "**/node_modules/node-notifier/**" - ], "publish": [ { "provider": "generic", diff --git a/desktop/splash.html b/desktop/splash.html new file mode 100644 index 0000000..a395857 --- /dev/null +++ b/desktop/splash.html @@ -0,0 +1,26 @@ + + + + + + + + + + + +
Biz Connect
+
Starting…
+ + diff --git a/server/chat.js b/server/chat.js index 9009057..a6dfa81 100644 --- a/server/chat.js +++ b/server/chat.js @@ -1,7 +1,8 @@ // Chat presence + real-time delivery. A logged-in user opens a WebSocket and sends // `chat-hello`; signaling.js registers the socket here. Messages are persisted over HTTP // (routes.js) and pushed live to the recipient's sockets via pushToUser(). -const { chatClients } = require('./presence'); +const { chatClients, meetingRooms } = require('./presence'); +let _repos = null; const repos = () => (_repos || (_repos = require('./repos'))); // lazy: avoid a require cycle function register(userId, ws) { if (!chatClients.has(userId)) chatClients.set(userId, new Set()); @@ -28,4 +29,27 @@ function pushToUser(userId, obj) { for (const ws of s) { if (ws.readyState === 1) { try { ws.send(data); } catch (_) {} } } } -module.exports = { register, unregister, isOnline, pushToUser }; +// --- Live presence ------------------------------------------------------------------------- +// A user's effective status (online / in-a-call / away / …) changes with no HTTP round-trip: +// they connect or disconnect a socket, or join/leave a call. Without pushing that change, OTHER +// users only see it after a full page reload — impossible in the desktop/mobile apps. So whenever +// it changes we broadcast the user's fresh status to everyone else's sockets, and the client +// updates that contact's dot/subtitle in place. +function isInCall(userId) { + for (const [, peers] of meetingRooms) { for (const [, p] of peers) { if (p.ws && p.ws._meetingUserId === userId) return true; } } + return false; +} +function effectiveStatus(userId) { + if (isInCall(userId)) return 'incall'; // derived (overrides the stored status) + try { const u = repos().users.byId(userId); return (u && u.status) || 'active'; } catch (_) { return 'active'; } +} +function broadcastPresence(userId) { + if (!userId) return; + const payload = JSON.stringify({ type: 'presence', userId, online: isOnline(userId), status: effectiveStatus(userId) }); + for (const [uid, set] of chatClients) { + if (uid === userId) continue; // no need to tell someone about their own status + for (const ws of set) { if (ws.readyState === 1) { try { ws.send(payload); } catch (_) {} } } + } +} + +module.exports = { register, unregister, isOnline, pushToUser, broadcastPresence }; diff --git a/server/public/apple-touch-icon.png b/server/public/apple-touch-icon-180.png similarity index 100% rename from server/public/apple-touch-icon.png rename to server/public/apple-touch-icon-180.png diff --git a/server/public/bizconnect-toast.css b/server/public/bizconnect-toast.css new file mode 100644 index 0000000..6d7c6b9 --- /dev/null +++ b/server/public/bizconnect-toast.css @@ -0,0 +1,18 @@ +/* Biz Connect — branded notification toast. BZToast.success('…') / .error / .message / .info */ +.bzt-wrap{position:fixed;top:16px;right:16px;z-index:2147483600;display:flex;flex-direction:column;gap:10px;max-width:min(380px,92vw)} +@supports(top:env(safe-area-inset-top)){.bzt-wrap{top:calc(16px + env(safe-area-inset-top));right:calc(16px + env(safe-area-inset-right))}} +.bzt{display:flex;align-items:flex-start;gap:12px;background:#fff;color:#1f2430;border-radius:14px;padding:12px 14px; + box-shadow:0 12px 30px rgba(16,26,53,.20);border-left:5px solid #1F3B73; + transform:translateX(120%);opacity:0;transition:transform .3s cubic-bezier(.2,.7,.2,1),opacity .3s} +.bzt.bzt-in{transform:translateX(0);opacity:1} +.bzt.success{border-left-color:#16a34a}.bzt.error{border-left-color:#b91c1c} +.bzt-badge{flex:none;width:34px;height:34px;border-radius:50%;display:grid;place-items:center;background:#1F3B73} +.bzt.success .bzt-badge{background:#16a34a}.bzt.error .bzt-badge{background:#b91c1c} +.bzt-badge svg{width:20px;height:20px} +.bzt-body{flex:1;min-width:0;padding-top:1px} +.bzt-title{font:700 13.5px/1.3 'Segoe UI',system-ui,sans-serif;color:#1F3B73;margin:0 0 1px} +.bzt.success .bzt-title{color:#15803d}.bzt.error .bzt-title{color:#b91c1c} +.bzt-msg{font:500 13px/1.4 'Segoe UI',system-ui,sans-serif;color:#3a4152;overflow-wrap:anywhere} +.bzt-x{flex:none;background:none;border:none;color:#9aa3b2;cursor:pointer;font-size:18px;line-height:1;padding:2px 4px} +.bzt-x:hover{color:#1f2430} +@media(prefers-reduced-motion:reduce){.bzt{transition:opacity .2s}} diff --git a/server/public/bizconnect-toast.js b/server/public/bizconnect-toast.js new file mode 100644 index 0000000..01be92e --- /dev/null +++ b/server/public/bizconnect-toast.js @@ -0,0 +1,29 @@ +/* Biz Connect toast API. Requires bizconnect-toast.css. + BZToast.success('Saved'); BZToast.error('Connection lost'); BZToast.message('Hi', {title:'Ravi'}); */ +window.BZToast=(function(){ + var wrap=null; + var ICON={ + message:'', + info:'', + success:'', + error:'' + }; + function esc(s){return String(s==null?'':s).replace(/[&<>"]/g,function(c){return{'&':'&','<':'<','>':'>','"':'"'}[c];});} + function ensure(){ if(wrap) return wrap; wrap=document.createElement('div'); wrap.className='bzt-wrap'; document.body.appendChild(wrap); return wrap; } + function show(message,opts){ + opts=opts||{}; var type=opts.type||'message'; var w=ensure(); + var t=document.createElement('div'); t.className='bzt '+type; + t.innerHTML='
'+(ICON[type]||ICON.message)+'
' + +(opts.title?'
'+esc(opts.title)+'
':'') + +'
'+esc(message)+'
'; + w.appendChild(t); requestAnimationFrame(function(){ t.classList.add('bzt-in'); }); + var dur=(opts.duration==null?4000:opts.duration), timer; + function close(){ t.classList.remove('bzt-in'); setTimeout(function(){ if(t.parentNode) t.parentNode.removeChild(t); },320); clearTimeout(timer); } + t.querySelector('.bzt-x').onclick=close; if(dur>0) timer=setTimeout(close,dur); return close; + } + return { show:show, + message:function(m,o){o=o||{};o.type='message';return show(m,o);}, + success:function(m,o){o=o||{};o.type='success';return show(m,o);}, + error:function(m,o){o=o||{};o.type='error';return show(m,o);}, + info:function(m,o){o=o||{};o.type='info';return show(m,o);} }; +})(); diff --git a/server/public/connect.html b/server/public/connect.html index 2dede34..c95ab12 100644 --- a/server/public/connect.html +++ b/server/public/connect.html @@ -4,6 +4,12 @@ Biz Connect — Agent Console + + + + + + - - -
-
- -
BizGaze Connect · Home
-
-
-
- -
- - - - -
-
- - - -
- -
- -
-
-
- -
- COMING SOON -

Meetings are on the way

-

Soon you'll be able to host multi-party video meetings with your BizGaze team and customers — right here, no install needed. We're putting on the finishing touches.

- -
In the meantime, use Share Screen or Connect Screen to start a session.
-
-
- - -
-
-
- -
-

Share your screen

-

Let a teammate or customer see your screen instantly. You'll get a 6-digit code to share — they enter it to connect. No download, works right in the browser.

- Start sharing → -
Desktop browsers only — phones can't share their screen yet.
-
-
- - -
-
-
- -
-

Connect to a screen

-

Helping someone out? Enter the 6-digit code they give you to view their screen and provide live support — with two-way voice and chat built in.

- Open connect page → -
The other person taps Allow before you can see anything.
-
-
-
-
-
- - - - diff --git a/server/public/home.html b/server/public/home.html index 265d92c..42a9fe4 100644 --- a/server/public/home.html +++ b/server/public/home.html @@ -7,9 +7,11 @@ + - - + + + @@ -645,10 +647,18 @@ .modal-actions .gobtn{flex:1;border:none;border-radius:10px;padding:.6rem;font-weight:700;cursor:pointer;} /* ---- Login (shown on /home when logged out) ---- */ - .authwrap{flex:1 1 auto;display:none;align-items:center;justify-content:center;padding:1.5rem;min-height:0;} - .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);} - .authcard h1{font-size:1.3rem;color:var(--blue);margin:0 0 .3rem;text-align:center;} + .authwrap{flex:1 1 auto;display:none;align-items:center;justify-content:center;padding:1.5rem;min-height:0; + background:radial-gradient(1200px 600px at 50% -10%, #24437f 0%, #1F3B73 42%, #16294F 100%);} + .authcard{background:var(--card);border:1px solid var(--line);border-radius:18px;padding:2rem 2rem 1.7rem;max-width:400px;width:100%;box-shadow:0 24px 60px rgba(9,17,38,.38);} + .authcard .auth-brand{display:flex;flex-direction:column;align-items:center;gap:.55rem;margin-bottom:1rem;} + .authcard .auth-brand img{width:66px;height:66px;border-radius:17px;box-shadow:0 10px 24px rgba(31,59,115,.30);} + .authcard .auth-brand .wm{font-size:1.15rem;font-weight:800;color:var(--blue);letter-spacing:.2px;} + .authcard .auth-brand .wm b{color:var(--brand-d);font-weight:800;} + .authcard h1{font-size:1.12rem;color:var(--blue);margin:0 0 .3rem;text-align:center;} .authcard .sub{color:var(--muted);font-size:.9rem;text-align:center;margin-bottom:1.2rem;} + /* Blue card + gold CTA so the sign-in screen carries BOTH brand colours (never mono-blue). */ + .authcard .gobtn{background:var(--brand);color:var(--blue-d);} + .authcard .gobtn:hover{filter:brightness(.95);} .authtabs{display:flex;gap:.5rem;margin-bottom:1.1rem;} .authtabs button{flex:1;background:#eef1f6;color:var(--muted);font-weight:600;border:none;border-radius:9px;padding:.5rem;cursor:pointer;font-size:.9rem;} .authtabs button.active{background:var(--blue);color:#fff;} @@ -667,11 +677,14 @@ .hidden{display:none;} /* ---- Loading / toast ---- */ - .loading{position:fixed;inset:0;display:grid;place-items:center;background:var(--bg);z-index:9000;color:var(--muted);font-size:.9rem;} - .loading .ld-inner{display:flex;flex-direction:column;align-items:center;gap:.9rem;} - .loading .ld-inner img{width:64px;height:64px;} - .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;} - .toast.show{opacity:1;transform:translateX(-50%) translateY(0);} + .loading{position:fixed;inset:0;display:grid;place-items:center;background:#1F3B73;z-index:9000;color:rgba(255,255,255,.82);font-size:.9rem;} + .loading .ld-inner{display:flex;flex-direction:column;align-items:center;gap:1rem;} + .loading .ld-inner img{width:72px;height:72px;} + /* Chat thread loading (light pane) and call-connecting (dark stage) — branded, centered. */ + .thread-loading{height:100%;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:.7rem;color:var(--muted);font-size:.86rem;} + .call-connecting{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:1rem;background:#16294F;color:rgba(255,255,255,.82);font-size:.95rem;} + .call-connecting .cc-txt{letter-spacing:.2px;} + /* in-app toasts now render via /bizconnect-toast.css (BZToast) */ /* Hamburger menu button (header) */ .navtoggle{background:transparent;border:none;color:#fff;cursor:pointer;display:grid;place-items:center;width:38px;height:38px;border-radius:9px;} @@ -740,11 +753,11 @@ - -
Loading…
+
Loading…
@@ -815,7 +828,6 @@ function twemojify(el){ try{ if(el && window.twemoji) window.twemoji.parse(el, {
-