fix(brand/ux): real brand C on header, yellow read-tick, sign-in spinner, notif DP
- #5: use the ACTUAL brand C (extracted transparent white C + gold dot from the master app-icon) on the blue headers instead of a hand-drawn mark; login card keeps the app icon. - #3: read-receipt double tick on my own (blue) bubble is now brand yellow (was blue-on-blue, invisible). - #2: Sign-in button shows a spinner + 'Signing in…' on submit. - #1: web notification uses the sender/group DP URL directly as the icon (drawing an external DP to canvas tainted it → silently fell back to initials). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -76,7 +76,7 @@
|
|||||||
<script>if(new URLSearchParams(location.search).get('embed')==='1')document.documentElement.classList.add('embed');</script>
|
<script>if(new URLSearchParams(location.search).get('embed')==='1')document.documentElement.classList.add('embed');</script>
|
||||||
<a href="/home" id="homeLink"><span data-ic="arrowLeft" data-sz="16"></span> Home</a>
|
<a href="/home" id="homeLink"><span data-ic="arrowLeft" data-sz="16"></span> Home</a>
|
||||||
<div class="topbar" id="topbar">
|
<div class="topbar" id="topbar">
|
||||||
<div class="brandrow"><img src="/mark-light.svg" alt="" style="height:46px;width:auto;max-width:190px;border-radius:8px;object-fit:contain;image-rendering:-webkit-optimize-contrast" onerror="this.replaceWith(Object.assign(document.createElement('div'),{className:'logo',textContent:'B'}))"><div class="brand">Biz <span>Connect</span></div></div>
|
<div class="brandrow"><img src="/mark-light.png" alt="" style="height:46px;width:auto;max-width:190px;border-radius:8px;object-fit:contain;image-rendering:-webkit-optimize-contrast" onerror="this.replaceWith(Object.assign(document.createElement('div'),{className:'logo',textContent:'B'}))"><div class="brand">Biz <span>Connect</span></div></div>
|
||||||
<div class="agentchip" id="agentChip"></div>
|
<div class="agentchip" id="agentChip"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="topbar2" id="bar"><span id="barStatus"></span><button id="endBtn">End session</button></div>
|
<div class="topbar2" id="bar"><span id="barStatus"></span><button id="endBtn">End session</button></div>
|
||||||
|
|||||||
@@ -92,7 +92,7 @@
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<header>
|
<header>
|
||||||
<div class="brandrow"><img src="/mark-light.svg" alt="" style="height:46px;width:auto;max-width:190px;border-radius:8px;object-fit:contain;image-rendering:-webkit-optimize-contrast" onerror="this.replaceWith(Object.assign(document.createElement('div'),{className:'logo',textContent:'B'}))"><div class="brand">Biz <span class="y">Connect</span> <span class="tag">· Dashboard</span></div></div>
|
<div class="brandrow"><img src="/mark-light.png" alt="" style="height:46px;width:auto;max-width:190px;border-radius:8px;object-fit:contain;image-rendering:-webkit-optimize-contrast" onerror="this.replaceWith(Object.assign(document.createElement('div'),{className:'logo',textContent:'B'}))"><div class="brand">Biz <span class="y">Connect</span> <span class="tag">· Dashboard</span></div></div>
|
||||||
<div class="row" id="hdrRight"></div>
|
<div class="row" id="hdrRight"></div>
|
||||||
</header>
|
</header>
|
||||||
<main id="app"></main>
|
<main id="app"></main>
|
||||||
|
|||||||
+18
-14
@@ -301,8 +301,9 @@
|
|||||||
.sys-msg{align-self:center;font-size:.76rem;color:var(--muted);background:rgba(0,0,0,.04);padding:.25rem .7rem;border-radius:99px;margin:.2rem 0;max-width:90%;text-align:center;}
|
.sys-msg{align-self:center;font-size:.76rem;color:var(--muted);background:rgba(0,0,0,.04);padding:.25rem .7rem;border-radius:99px;margin:.2rem 0;max-width:90%;text-align:center;}
|
||||||
.bubble .t{display:flex;align-items:center;justify-content:flex-end;gap:.25rem;font-size:.64rem;opacity:.65;margin-top:.15rem;}
|
.bubble .t{display:flex;align-items:center;justify-content:flex-end;gap:.25rem;font-size:.64rem;opacity:.65;margin-top:.15rem;}
|
||||||
.bubble .rcpt{display:inline-flex;}
|
.bubble .rcpt{display:inline-flex;}
|
||||||
.bubble.mine .rcpt{opacity:.8;}
|
.bubble.mine .rcpt{opacity:.85;}
|
||||||
.bubble.mine .rcpt.seen{color:#8fd3ff;opacity:1;}
|
/* Read receipt on my own (blue) bubble: brand yellow so the double tick actually stands out. */
|
||||||
|
.bubble.mine .rcpt.seen{color:var(--brand);opacity:1;}
|
||||||
.att-img{cursor:zoom-in;}
|
.att-img{cursor:zoom-in;}
|
||||||
.fmt-bar{display:flex;align-items:center;gap:.05rem;padding:.3rem .4rem .1rem;flex-wrap:wrap;border-bottom:1px dashed var(--line);}
|
.fmt-bar{display:flex;align-items:center;gap:.05rem;padding:.3rem .4rem .1rem;flex-wrap:wrap;border-bottom:1px dashed var(--line);}
|
||||||
.fmt-bar button{border:none;background:transparent;color:var(--muted);cursor:pointer;width:30px;height:30px;border-radius:7px;display:grid;place-items:center;}
|
.fmt-bar button{border:none;background:transparent;color:var(--muted);cursor:pointer;width:30px;height:30px;border-radius:7px;display:grid;place-items:center;}
|
||||||
@@ -662,6 +663,9 @@
|
|||||||
.authcard .sub{color:var(--muted);font-size:.9rem;text-align:center;margin-bottom:1.2rem;}
|
.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). */
|
/* 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{background:var(--brand);color:var(--blue-d);}
|
||||||
|
.gobtn:disabled{opacity:.9;cursor:default;}
|
||||||
|
.btn-spin{display:inline-block;width:15px;height:15px;border:2px solid rgba(22,41,79,.3);border-top-color:var(--blue-d);border-radius:50%;animation:btnspin .6s linear infinite;vertical-align:-3px;margin-right:7px;}
|
||||||
|
@keyframes btnspin{to{transform:rotate(360deg);}}
|
||||||
.authcard .gobtn:hover{filter:brightness(.95);}
|
.authcard .gobtn:hover{filter:brightness(.95);}
|
||||||
.authtabs{display:flex;gap:.5rem;margin-bottom:1.1rem;}
|
.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{flex:1;background:#eef1f6;color:var(--muted);font-weight:600;border:none;border-radius:9px;padding:.5rem;cursor:pointer;font-size:.9rem;}
|
||||||
@@ -760,7 +764,7 @@
|
|||||||
<body>
|
<body>
|
||||||
<script src="/icons.js?v=4"></script>
|
<script src="/icons.js?v=4"></script>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/@twemoji/api@15.1.0/dist/twemoji.min.js" crossorigin="anonymous"></script>
|
<script src="https://cdn.jsdelivr.net/npm/@twemoji/api@15.1.0/dist/twemoji.min.js" crossorigin="anonymous"></script>
|
||||||
<script>window.__BUILD='2026-07-02-batch35';console.log('%cBiz Connect','color:#1F3B73;font-weight:bold','build '+window.__BUILD);
|
<script>window.__BUILD='2026-07-02-batch36';console.log('%cBiz Connect','color:#1F3B73;font-weight:bold','build '+window.__BUILD);
|
||||||
// Render modern (Twemoji) emojis in place of the OS's flat ones. No-op if the CDN didn't load
|
// Render modern (Twemoji) emojis in place of the OS's flat ones. No-op if the CDN didn't load
|
||||||
// (emojis stay as plain Unicode). (#5)
|
// (emojis stay as plain Unicode). (#5)
|
||||||
function twemojify(el){ try{ if(el && window.twemoji) window.twemoji.parse(el, { folder:'svg', ext:'.svg' }); }catch(_){} }</script>
|
function twemojify(el){ try{ if(el && window.twemoji) window.twemoji.parse(el, { folder:'svg', ext:'.svg' }); }catch(_){} }</script>
|
||||||
@@ -769,7 +773,7 @@ function twemojify(el){ try{ if(el && window.twemoji) window.twemoji.parse(el, {
|
|||||||
<header>
|
<header>
|
||||||
<div class="brandrow">
|
<div class="brandrow">
|
||||||
<button class="navtoggle" id="navToggle" title="Toggle menu" aria-label="Toggle menu"><svg viewBox="0 0 24 24" width="22" height="22" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><line x1="3" y1="6" x2="21" y2="6"/><line x1="3" y1="12" x2="21" y2="12"/><line x1="3" y1="18" x2="21" y2="18"/></svg></button>
|
<button class="navtoggle" id="navToggle" title="Toggle menu" aria-label="Toggle menu"><svg viewBox="0 0 24 24" width="22" height="22" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><line x1="3" y1="6" x2="21" y2="6"/><line x1="3" y1="12" x2="21" y2="12"/><line x1="3" y1="18" x2="21" y2="18"/></svg></button>
|
||||||
<img src="/mark-light.svg" alt="" style="height:46px;width:auto;max-width:190px;border-radius:8px;object-fit:contain;image-rendering:-webkit-optimize-contrast" onerror="this.replaceWith(Object.assign(document.createElement('div'),{className:'logo',textContent:'B'}))">
|
<img src="/mark-light.png" alt="" style="height:46px;width:auto;max-width:190px;border-radius:8px;object-fit:contain;image-rendering:-webkit-optimize-contrast" onerror="this.replaceWith(Object.assign(document.createElement('div'),{className:'logo',textContent:'B'}))">
|
||||||
<div class="brand">Biz <span class="y">Connect</span></div>
|
<div class="brand">Biz <span class="y">Connect</span></div>
|
||||||
</div>
|
</div>
|
||||||
<div id="hdrRight"></div>
|
<div id="hdrRight"></div>
|
||||||
@@ -2015,16 +2019,14 @@ function notify(title, body, kind, id){
|
|||||||
}).catch(()=>{});
|
}).catch(()=>{});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Web / PWA: standard Notification. Icon = the sender's/group's DP (falling back to their
|
// Web / PWA: standard Notification. Use the sender's/group's DP URL DIRECTLY as the icon —
|
||||||
// initials, then the brand app icon) — never the old wordmark. badge = brand mark.
|
// the browser fetches notification icons cross-origin fine, whereas drawing an external DP to a
|
||||||
|
// canvas taints it and silently fell back to initials (the bug). Brand icon only if no DP.
|
||||||
if(!('Notification' in window) || Notification.permission!=='granted') return;
|
if(!('Notification' in window) || Notification.permission!=='granted') return;
|
||||||
notifAvatarDataUrl(kind, id, String(title||'').replace(/^[^\p{L}\p{N}]+/u,'')).then(avatar=>{
|
const _row=rowFor(kind,id)||{};
|
||||||
const n=new Notification(title, { body, icon: avatar || '/icon-192.png', badge:'/icon-192.png' });
|
const n=new Notification(title, { body, icon: _row.avatar || '/icon-192.png', badge:'/icon-192.png' });
|
||||||
n.onclick=()=>{ n.close(); openFromNotif(kind, id); };
|
n.onclick=()=>{ n.close(); openFromNotif(kind, id); };
|
||||||
setTimeout(()=>{ try{ n.close(); }catch(_){} }, 8000);
|
setTimeout(()=>{ try{ n.close(); }catch(_){} }, 8000);
|
||||||
}).catch(()=>{
|
|
||||||
try{ const n=new Notification(title, { body, icon:'/icon-192.png' }); n.onclick=()=>{ n.close(); openFromNotif(kind, id); }; }catch(_){}
|
|
||||||
});
|
|
||||||
}catch(_){}
|
}catch(_){}
|
||||||
}
|
}
|
||||||
let _audioCtx=null;
|
let _audioCtx=null;
|
||||||
@@ -2845,7 +2847,7 @@ async function renderLogin(){
|
|||||||
const aw=document.getElementById('authwrap'); aw.style.display='flex';
|
const aw=document.getElementById('authwrap'); aw.style.display='flex';
|
||||||
let regOpen=false; try{ regOpen=(await (await fetch('/api/setup-state')).json()).registrationOpen; }catch(_){}
|
let regOpen=false; try{ regOpen=(await (await fetch('/api/setup-state')).json()).registrationOpen; }catch(_){}
|
||||||
aw.innerHTML=`<div class="authcard">
|
aw.innerHTML=`<div class="authcard">
|
||||||
<div class="auth-brand"><img src="/mark-light.svg" alt="Biz Connect"><div class="wm">Biz <b>Connect</b></div></div>
|
<div class="auth-brand"><img src="/icon-192.png" alt="Biz Connect"><div class="wm">Biz <b>Connect</b></div></div>
|
||||||
<h1>Welcome back</h1>
|
<h1>Welcome back</h1>
|
||||||
<div class="sub">Sign in to access chats, screen share and connect.</div>
|
<div class="sub">Sign in to access chats, screen share and connect.</div>
|
||||||
${regOpen?`<div class="authtabs">
|
${regOpen?`<div class="authtabs">
|
||||||
@@ -2880,10 +2882,12 @@ async function renderLogin(){
|
|||||||
}
|
}
|
||||||
async function doLogin(){
|
async function doLogin(){
|
||||||
clearErr('li_err');
|
clearErr('li_err');
|
||||||
|
const btn=document.getElementById('li_btn'); const orig=btn?btn.innerHTML:'';
|
||||||
|
if(btn){ btn.disabled=true; btn.innerHTML='<span class="btn-spin"></span>Signing in…'; } // show progress on Enter/click
|
||||||
try{
|
try{
|
||||||
await postJSON('/api/login',{email:document.getElementById('li_email').value,password:document.getElementById('li_pw').value,remember:document.getElementById('li_remember').checked});
|
await postJSON('/api/login',{email:document.getElementById('li_email').value,password:document.getElementById('li_pw').value,remember:document.getElementById('li_remember').checked});
|
||||||
location.reload();
|
location.reload(); // the branded splash takes over from here
|
||||||
}catch(e){ showErr('li_err', /invalid credentials/i.test(e.message)?'Incorrect email or password. Please try again.':e.message); }
|
}catch(e){ if(btn){ btn.disabled=false; btn.innerHTML=orig; } showErr('li_err', /invalid credentials/i.test(e.message)?'Incorrect email or password. Please try again.':e.message); }
|
||||||
}
|
}
|
||||||
async function doRegister(){
|
async function doRegister(){
|
||||||
clearErr('rg_err');
|
clearErr('rg_err');
|
||||||
|
|||||||
@@ -55,7 +55,7 @@
|
|||||||
<body>
|
<body>
|
||||||
<header>
|
<header>
|
||||||
<div class="brandrow">
|
<div class="brandrow">
|
||||||
<img src="/mark-light.svg" alt="" style="height:40px;width:auto;max-width:170px;border-radius:8px;object-fit:contain" onerror="this.style.display='none'">
|
<img src="/mark-light.png" alt="" style="height:40px;width:auto;max-width:170px;border-radius:8px;object-fit:contain" onerror="this.style.display='none'">
|
||||||
<div class="brand">Biz <span>Connect</span></div>
|
<div class="brand">Biz <span>Connect</span></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="hdr-right">
|
<div class="hdr-right">
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
@@ -1,5 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" role="img" aria-label="Biz Connect">
|
|
||||||
<!-- White C + gold dot on a transparent background — for placing directly on the blue header -->
|
|
||||||
<path d="M40 10 A22 22 0 1 0 40 54" fill="none" stroke="#ffffff" stroke-width="9" stroke-linecap="round"/>
|
|
||||||
<circle cx="40" cy="32" r="6.5" fill="#FFC708"/>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 366 B |
Reference in New Issue
Block a user