fix(mobile): stop bubble/composer balloon + transition pop (shared root cause, workflow-verified)
Parallel root-cause analysis: during body.chat-opening the pane is momentarily ~0 width; word-break: break-word / overflow-wrap:anywhere let bubbles collapse to 1-GLYPH min-content, so pre-wrap text wraps into hundreds of lines (5684px 'balloon'); same inflates the empty/draft textarea (autoGrow->140=4 lines); and translateX(100%) of a ~0-width pane = no travel = the 'jump'. The tall box hid in anonymous line boxes (renderMsgBody emits bare text nodes), which is why the probe only saw the 84px timestamp span. Fixes (all @media max-width:760px / chat-opening scoped, desktop untouched): 1. .bubble: word-break:normal + overflow-wrap:break-word (min-content = longest word, never collapses) 2. mobile bubble group: overflow-wrap:anywhere -> break-word 3. .msg-link: word-break:break-word -> overflow-wrap:break-word (URL-only msgs can't reopen the hole) 4. body.chat-opening .content: width:100vw -> definite width, no collapse frame, real slide travel Build marker -> 2026-07-18-batch117. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+12
-6
@@ -310,7 +310,10 @@
|
||||
.convo-body .big{font-size:2.4rem;margin-bottom:.4rem;}
|
||||
/* message thread */
|
||||
.convo-msgs{flex:1;overflow-y:auto;padding:1rem 1.2rem;display:flex;flex-direction:column;gap:.35rem;background:var(--bg);}
|
||||
.bubble{max-width:72%;padding:.5rem .75rem;border-radius:14px;font-size:.9rem;line-height:1.4;white-space:pre-wrap;word-break:break-word;box-shadow:0 1px 2px rgba(20,30,60,.06);}
|
||||
/* word-break:normal + overflow-wrap:break-word: still emergency-breaks an over-long word/URL to fit,
|
||||
but keeps min-content = longest WORD (not 1 glyph) so a bubble can never collapse to ~0 width and
|
||||
wrap text into hundreds of lines during a transient 0-width layout frame (the "stretch" balloon). */
|
||||
.bubble{max-width:72%;padding:.5rem .75rem;border-radius:14px;font-size:.9rem;line-height:1.4;white-space:pre-wrap;word-break:normal;overflow-wrap:break-word;box-shadow:0 1px 2px rgba(20,30,60,.06);}
|
||||
.bubble.them{align-self:flex-start;background:#fff;border:1px solid var(--line);color:var(--ink);border-bottom-left-radius:4px;}
|
||||
.bubble.mine{align-self:flex-end;background:var(--blue);color:#fff;border-bottom-right-radius:4px;}
|
||||
.bubble .t{display:block;font-size:.64rem;opacity:.65;margin-top:.15rem;text-align:right;}
|
||||
@@ -529,7 +532,7 @@
|
||||
.upd-dot{position:absolute;top:-3px;right:-3px;width:15px;height:15px;border-radius:50%;background:var(--brand);color:var(--blue-d);font-size:.66rem;font-weight:800;font-style:italic;display:grid;place-items:center;border:2px solid var(--blue);pointer-events:none;}
|
||||
#setUpd.has-update{background:var(--brand);color:var(--blue-d);}
|
||||
.set-ver-i{display:inline-grid;place-items:center;width:15px;height:15px;border-radius:50%;background:var(--brand);color:var(--blue-d);font-size:.66rem;font-weight:800;font-style:italic;margin-left:.35rem;vertical-align:middle;}
|
||||
.bubble .msg-link{color:inherit;text-decoration:underline;text-underline-offset:2px;word-break:break-word;}
|
||||
.bubble .msg-link{color:inherit;text-decoration:underline;text-underline-offset:2px;overflow-wrap:break-word;}
|
||||
.bubble .msg-tel{color:inherit;text-decoration:underline;text-underline-offset:2px;white-space:nowrap;cursor:pointer;} /* #9 */
|
||||
.bubble.them .msg-link{color:var(--blue);} .bubble.mine .msg-link{color:#dbe9ff;}
|
||||
.bubble .fwd-label{display:flex;align-items:center;gap:.2rem;font-size:.72rem;font-style:italic;opacity:.7;margin-bottom:.2rem;}
|
||||
@@ -1041,7 +1044,7 @@
|
||||
/* Stop the page from widening WITHOUT clipping overlays (profile/bell menus, reaction popups): only
|
||||
cap the chat-list scroller and let long words/URLs wrap instead of pushing width. */
|
||||
.chatlist{overflow-x:hidden;}
|
||||
.chat-main,.msg,.msg-body,.bubble,.chat-sub,.chat-preview{min-width:0;max-width:100%;overflow-wrap:anywhere;}
|
||||
.chat-main,.msg,.msg-body,.bubble,.chat-sub,.chat-preview{min-width:0;max-width:100%;overflow-wrap:break-word;}
|
||||
/* #1/#5: the top safe-area inset (notch / Dynamic Island) is applied PER SURFACE — the chat-list
|
||||
column and each content panel — rather than on the whole shell, so nothing hides under the island
|
||||
and there's no double-padding. Bottom inset is handled by the fixed nav + panel bottom below. */
|
||||
@@ -1093,7 +1096,10 @@
|
||||
@keyframes bzListParallaxOut{from{transform:translateX(0);filter:brightness(1);}to{transform:translateX(-22%);filter:brightness(.97);}}
|
||||
@keyframes bzListParallaxIn{from{transform:translateX(-22%);filter:brightness(.97);}to{transform:translateX(0);filter:brightness(1);}}
|
||||
/* OPEN: content slides in over the list (list shown beneath, parallax out) */
|
||||
body.chat-opening .content{display:block!important;z-index:2;box-shadow:-12px 0 30px rgba(15,23,42,.18);will-change:transform;animation:bzContentIn .3s cubic-bezier(.32,.72,0,1);}
|
||||
/* width:100vw makes the sliding pane a DEFINITE width regardless of any ancestor's transient 0-width
|
||||
during the animation frame — so bubbles never collapse, the composer measures at the right width,
|
||||
and translateX(100%) travels a real screen width (a true slide, not a no-travel "pop"). */
|
||||
body.chat-opening .content{display:block!important;width:100vw;max-width:100vw;z-index:2;box-shadow:-12px 0 30px rgba(15,23,42,.18);will-change:transform;animation:bzContentIn .3s cubic-bezier(.32,.72,0,1);}
|
||||
body.chat-opening .chatcol{display:flex!important;position:absolute;inset:0;z-index:0;animation:bzListParallaxOut .3s cubic-bezier(.32,.72,0,1);}
|
||||
/* CLOSE / drag-back: reveal the list underneath (parallax in) while the conversation slides off right
|
||||
(.content moved by an inline transform in showWelcome()/the edge-drag gesture). */
|
||||
@@ -1126,7 +1132,7 @@
|
||||
</head>
|
||||
<body>
|
||||
<script src="/icons.js?v=6"></script>
|
||||
<script>window.__BUILD='2026-07-18-batch116';console.log('%cBiz Connect','color:#1F3B73;font-weight:bold','build '+window.__BUILD);
|
||||
<script>window.__BUILD='2026-07-18-batch117';console.log('%cBiz Connect','color:#1F3B73;font-weight:bold','build '+window.__BUILD);
|
||||
// Emoji are rendered with the OS's own (colour) emoji font — instant, zero network.
|
||||
//
|
||||
// We used to run Twemoji over every emoji, which swapped each one for an <img> pulled INDIVIDUALLY from
|
||||
@@ -1264,7 +1270,7 @@ function pEsc(s){return String(s==null?'':s).replace(/[&<>"]/g,c=>({'&':'&',
|
||||
function initials(name){const p=String(name||'?').trim().split(/\s+/);return ((p[0]||'?')[0]+(p[1]?p[1][0]:'')).toUpperCase();}
|
||||
function fmtDateTime(ts){ if(!ts) return ''; const d=new Date(ts); return d.toLocaleDateString([],{year:'numeric',month:'short',day:'numeric'})+' · '+d.toLocaleTimeString([],{hour:'2-digit',minute:'2-digit'}); }
|
||||
function fmtClock(ts){ if(!ts) return ''; return new Date(ts).toLocaleTimeString([],{hour:'2-digit',minute:'2-digit'}); } // bubble time = clock only; date is the center separator
|
||||
function autoGrow(el){ if(!el) return; el.style.height='auto'; const max=140; el.style.height=Math.min(el.scrollHeight,max)+'px'; el.style.overflowY=el.scrollHeight>max?'auto':'hidden'; }
|
||||
function autoGrow(el){ if(!el) return; if(!el.value){ el.style.height=''; el.style.overflowY='hidden'; return; } /* empty → 1 line via CSS (never inflate from a mid-layout scrollHeight) */ el.style.height='auto'; const max=140; el.style.height=Math.min(el.scrollHeight,max)+'px'; el.style.overflowY=el.scrollHeight>max?'auto':'hidden'; }
|
||||
// Mild, light tint for a quoted reply, color-coded by who is being quoted. [bg, bar]
|
||||
const REPLY_TINTS=[['#eef4ff','#3b6fd4'],['#eafaf2','#1f9d57'],['#fef4e9','#d98324'],['#f6eefe','#8b46c9'],['#fdeef3','#d6457f'],['#e9fafa','#179a9a'],['#fef7e6','#c9a227']];
|
||||
function replyTint(key){ let h=0; const s=String(key||''); for(let i=0;i<s.length;i++) h=(h*31+s.charCodeAt(i))>>>0; return REPLY_TINTS[h%REPLY_TINTS.length]; }
|
||||
|
||||
Reference in New Issue
Block a user