// Chart Growth Hub โ floating chat dock (v2, two-pane).
//
// One component for both portals. Staff get the full room list (team room,
// boardroom, broadcasts, signal view, and a support thread per student); a
// student gets their own coach thread plus whichever channels their tier lets
// them read.
//
// v2 changes: two-pane layout (list stays on the left, the room fills the
// right), mono message type at 16px for legibility, brighter bubbles, a
// "new messages" divider, reactions, staff-only read receipts, pinned
// messages, per-room mute (per device), a drag-resizable panel, and simple
// link cards.
//
// Exposes window.ChatDock({client, email, role, name, mode, tier, isVIP,
// openThread, onThreadOpened}).
// mode 'staff' โ full room list, needs role
// mode 'student' โ own coach thread + eligible channels
(function(){
const { useState, useEffect, useRef, useMemo, useCallback } = React;
const C = window.ChatCommon || {};
const fmtTime = C.fmtTime || (d => new Date(d).toLocaleTimeString(undefined,{hour:'numeric',minute:'2-digit'}));
const dayKey = C.dayKey || (d => String(d).slice(0,10));
const dayLabel = C.dayLabel || (d => new Date(d).toLocaleDateString(undefined,{month:'short',day:'numeric'}));
const EMOJI = {
'Smileys': ['๐','๐','๐','๐','๐','๐','๐','๐ฅฐ','๐','๐','๐คฉ','๐ค','๐ค','๐','๐','๐','๐ด','๐ฎ','๐ข','๐ญ','๐ค','๐ก','๐ฅณ','๐','๐ค','๐'],
'Gestures':['๐','๐','๐','โ๏ธ','๐ค','๐','๐','๐ช','๐','โ','๐ซก','๐ค','โ๏ธ','๐','๐','๐'],
'Trading': ['๐','๐','๐น','๐ฐ','๐ต','๐ธ','๐ฆ','โ๏ธ','๐ฏ','๐','โฐ','โก','๐ฅ','๐','๐','๐ง ','๐','๐','โ
','โ','โ ๏ธ','๐'],
'Hearts': ['โค๏ธ','๐งก','๐','๐','๐','๐','๐ค','๐ค','๐ฏ','โญ','๐','โจ','๐','๐','๐','๐'],
};
// Reactions people actually reach for โ the full picker is one tap further in.
const QUICK_REACTIONS = ['๐','๐ฅ','โ
','๐','๐','โค๏ธ'];
/* โโ per-device room mute โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Kept in localStorage rather than a table: muting is a personal, per-device
preference, and this way it needs no migration to work today. */
const MUTE_KEY = 'cg_chat_muted';
function mutedSet(){ try{ return new Set(JSON.parse(localStorage.getItem(MUTE_KEY)||'[]')); }catch(e){ return new Set(); } }
function isMuted(key){ return mutedSet().has(key); }
function toggleMuted(key){ const s = mutedSet(); if(s.has(key)) s.delete(key); else s.add(key);
try{ localStorage.setItem(MUTE_KEY, JSON.stringify([...s])); }catch(e){} return s.has(key); }
// chat-notify.js reads this so a muted room stays silent everywhere.
window.cgChatMutedThreads = () => [...mutedSet()];
const SIZE_KEY = 'cg_chat_dock_size';
function savedSize(){ try{ return JSON.parse(localStorage.getItem(SIZE_KEY)||'null'); }catch(e){ return null; } }
/* โโ styles โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
(function css(){
if(document.getElementById('cg-chatdock-css')) document.getElementById('cg-chatdock-css').remove();
const s = document.createElement('style');
s.id = 'cg-chatdock-css';
s.textContent = `
.cd-launch{position:fixed;left:24px;bottom:24px;z-index:250;width:68px;height:68px;border-radius:50%;
border:1px solid var(--border-gold);cursor:pointer;display:grid;place-items:center;
background:radial-gradient(circle at 34% 24%,rgba(255,255,255,.55),rgba(255,255,255,0) 46%),
linear-gradient(155deg,var(--gold-2),var(--gold) 62%,var(--gold-2));
box-shadow:0 14px 30px rgba(0,0,0,.5),0 0 0 6px rgba(212,175,55,.10),
inset 0 2px 1px rgba(255,255,255,.6),inset 0 -8px 14px rgba(90,60,0,.35);
transition:transform .18s cubic-bezier(.2,.8,.2,1),box-shadow .18s}
.cd-launch::after{content:'';position:absolute;inset:5px;border-radius:50%;pointer-events:none;
background:linear-gradient(180deg,rgba(255,255,255,.42),rgba(255,255,255,0) 52%)}
.cd-launch:hover{transform:translateY(-3px) scale(1.05);box-shadow:0 20px 42px rgba(0,0,0,.55),0 0 0 9px rgba(212,175,55,.14),inset 0 2px 1px rgba(255,255,255,.65),inset 0 -8px 14px rgba(90,60,0,.35)}
.cd-launch:active{transform:translateY(1px) scale(.99);box-shadow:0 6px 14px rgba(0,0,0,.5),inset 0 3px 8px rgba(90,60,0,.5)}
.cd-launch svg{width:30px;height:30px;fill:none;stroke:#1a1303;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;
filter:drop-shadow(0 1px 0 rgba(255,255,255,.45));position:relative;z-index:1}
.cd-launch .cd-badge{position:absolute;top:-3px;right:-3px;font-size:.7rem;padding:4px 7px;min-width:21px}
.cd-launch.has{animation:cdPulse 2.4s ease-out infinite}
@keyframes cdPulse{
0%{box-shadow:0 10px 30px rgba(0,0,0,.45),0 0 0 0 rgba(212,175,55,.42)}
70%{box-shadow:0 10px 30px rgba(0,0,0,.45),0 0 0 16px rgba(212,175,55,0)}
100%{box-shadow:0 10px 30px rgba(0,0,0,.45),0 0 0 0 rgba(212,175,55,0)}}
@media(prefers-reduced-motion:reduce){ .cd-launch.has{animation:none} }
.cd-badge{font-family:var(--font-mono);font-size:.68rem;font-weight:800;line-height:1;
background:radial-gradient(circle at 34% 26%,rgba(255,255,255,.5),rgba(255,255,255,0) 55%),var(--red);
color:#fff;padding:4px 7px;border-radius:999px;min-width:19px;text-align:center;
box-shadow:0 3px 7px rgba(0,0,0,.5),inset 0 1px 0 rgba(255,255,255,.45),0 0 0 2px var(--navy-950)}
/* shell โ two panes: rooms on the left, the conversation on the right */
.cd-panel{position:fixed;left:22px;bottom:22px;z-index:251;display:flex;border-radius:18px;overflow:hidden;
border:1px solid var(--border-gold);background:var(--navy-950);
box-shadow:0 26px 70px rgba(0,0,0,.62);animation:cdIn .22s cubic-bezier(.2,.8,.2,1) both}
@keyframes cdIn{from{opacity:0;transform:translateY(14px) scale(.985)}to{opacity:1;transform:none}}
.cd-side{width:266px;flex-shrink:0;display:flex;flex-direction:column;border-right:1px solid var(--border);
background:var(--navy-900)}
.cd-main{flex:1;min-width:0;display:flex;flex-direction:column}
.cd-resize{position:absolute;top:0;right:0;width:22px;height:22px;cursor:nesw-resize;z-index:6}
.cd-resize::after{content:'';position:absolute;top:7px;right:7px;width:9px;height:9px;
border-top:2px solid var(--border-gold);border-right:2px solid var(--border-gold);border-radius:2px 3px 0 0}
.cd-head{display:flex;align-items:center;gap:10px;padding:13px 14px;flex-shrink:0;min-height:60px;
border-bottom:1px solid var(--border);
background:linear-gradient(135deg,rgba(212,175,55,.16),transparent 70%),var(--navy-900)}
/* Chat heads: lit glass orbs, not flat circles. A radial key light top-left,
a rim light, an inner floor shadow, and a specular sheen layer. */
.cd-avatar,.cd-av{position:relative;border-radius:50%;flex-shrink:0;display:grid;place-items:center;
font-family:var(--font-mono);font-weight:800;color:var(--gold-2);
border:1px solid var(--border-gold);isolation:isolate;
background:radial-gradient(circle at 32% 24%,rgba(255,255,255,.34),rgba(255,255,255,0) 46%),
linear-gradient(158deg,var(--navy-700),var(--navy-950) 78%);
box-shadow:0 5px 12px rgba(0,0,0,.5),inset 0 1px 0 rgba(255,255,255,.30),
inset 0 -7px 13px rgba(0,0,0,.45)}
.cd-avatar::after,.cd-av::after{content:'';position:absolute;inset:2px;border-radius:50%;pointer-events:none;
background:linear-gradient(180deg,rgba(255,255,255,.22),rgba(255,255,255,0) 48%)}
.cd-avatar{width:38px;height:38px;font-size:.78rem;overflow:hidden}
.cd-avatar img{width:100%;height:100%;object-fit:contain;padding:6px;border-radius:50%;
filter:drop-shadow(0 2px 3px rgba(0,0,0,.5))}
.cd-av{width:31px;height:31px;font-size:.66rem}
.cd-av.hide{visibility:hidden}
.cd-avatar svg,.cd-av svg{position:relative;z-index:1;fill:none;stroke:var(--gold-2);stroke-width:1.9;
stroke-linecap:round;stroke-linejoin:round;filter:drop-shadow(0 1px 2px rgba(0,0,0,.6))}
.cd-avatar svg{width:20px;height:20px} .cd-av svg{width:17px;height:17px}
/* one hue per room type, so a channel is recognisable at a glance */
.cd-av.h-gold,.cd-avatar.h-gold{background:radial-gradient(circle at 32% 24%,rgba(255,255,255,.5),rgba(255,255,255,0) 46%),linear-gradient(158deg,var(--gold-2),var(--gold) 80%);color:#1a1303}
.cd-av.h-gold svg,.cd-avatar.h-gold svg{stroke:#1a1303}
.cd-av.h-em,.cd-avatar.h-em{background:radial-gradient(circle at 32% 24%,rgba(255,255,255,.42),rgba(255,255,255,0) 46%),linear-gradient(158deg,#3ddba0,#0b6647 82%);color:#04150f}
.cd-av.h-em svg{stroke:#062018}
.cd-av.h-sky,.cd-avatar.h-sky{background:radial-gradient(circle at 32% 24%,rgba(255,255,255,.42),rgba(255,255,255,0) 46%),linear-gradient(158deg,#57c7f7,#0a5687 82%);color:#04141f}
.cd-av.h-sky svg{stroke:#04202f}
.cd-av.h-ind,.cd-avatar.h-ind{background:radial-gradient(circle at 32% 24%,rgba(255,255,255,.42),rgba(255,255,255,0) 46%),linear-gradient(158deg,#a9b6ff,#3b34b8 82%);color:#0d0f2b}
.cd-av.h-ind svg{stroke:#0d0f2b}
.cd-av.h-slate,.cd-avatar.h-slate{background:radial-gradient(circle at 32% 24%,rgba(255,255,255,.36),rgba(255,255,255,0) 46%),linear-gradient(158deg,#cbd5e1,#55637a 82%);color:#10151f}
.cd-av.h-slate svg{stroke:#141b26}
.cd-head h4{margin:0;font-size:.98rem;font-weight:700;line-height:1.2;color:var(--ink);
white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.cd-sub{font-size:.75rem;color:var(--ink-dim);margin-top:2px;font-family:var(--font-mono);
white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
/* Glass 3D control buttons โ a lit top edge, a soft floor shadow, and a real
press. Flat squares read as unfinished at this size. */
.cd-icobtn{border:1px solid var(--border);padding:7px;border-radius:11px;cursor:pointer;color:var(--ink-dim);
display:grid;place-items:center;flex-shrink:0;position:relative;
background:linear-gradient(165deg,rgba(255,255,255,.12),rgba(255,255,255,.02));
-webkit-backdrop-filter:blur(9px);backdrop-filter:blur(9px);
box-shadow:0 3px 8px rgba(0,0,0,.36),inset 0 1px 0 rgba(255,255,255,.20);
transition:transform .13s,box-shadow .13s,color .13s,border-color .13s}
.cd-icobtn:hover{color:var(--gold-2);border-color:var(--border-gold);transform:translateY(-1px);
background:linear-gradient(165deg,rgba(255,255,255,.18),rgba(212,175,55,.07));
box-shadow:0 6px 14px rgba(0,0,0,.42),inset 0 1px 0 rgba(255,255,255,.28)}
.cd-icobtn:active{transform:translateY(1px);
box-shadow:inset 0 3px 8px rgba(0,0,0,.5),inset 0 1px 0 rgba(255,255,255,.06)}
.cd-icobtn.on{color:var(--gold-2)}
.cd-icobtn.off{opacity:.55}
.cd-icobtn svg{width:17px;height:17px;fill:none;stroke:currentColor;stroke-width:2;
stroke-linecap:round;stroke-linejoin:round;filter:drop-shadow(0 1px 1px rgba(0,0,0,.45))}
.cd-threads{flex:1;overflow-y:auto;padding:6px}
.cd-thread{display:flex;gap:10px;align-items:center;width:100%;padding:11px 11px;border-radius:11px;
border:0;background:none;cursor:pointer;text-align:left;color:var(--ink);font-family:inherit}
.cd-thread:hover{background:var(--surface)}
.cd-thread.on{background:rgba(212,175,55,.13);box-shadow:inset 3px 0 0 var(--gold-2)}
.cd-thread .t{flex:1;min-width:0}
.cd-thread .t b{display:block;font-size:.9rem;font-weight:700;color:var(--ink);
white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.cd-thread .t span{display:block;font-size:.74rem;color:var(--ink-dim);white-space:nowrap;
overflow:hidden;text-overflow:ellipsis;margin-top:1px}
.cd-thread .cd-av{width:36px;height:36px}
.cd-grouphead{font-size:.66rem;letter-spacing:.11em;text-transform:uppercase;color:var(--ink-dim);
padding:13px 12px 6px;font-family:var(--font-mono);font-weight:700}
.cd-body{flex:1;overflow-y:auto;padding:16px 16px 8px;display:flex;flex-direction:column;gap:2px;
background:radial-gradient(1100px 420px at 20% -10%,rgba(212,175,55,.05),transparent 60%)}
.cd-day{align-self:center;margin:14px 0 10px;font-size:.7rem;font-family:var(--font-mono);
letter-spacing:.07em;text-transform:uppercase;color:var(--ink-dim);font-weight:700;
background:var(--surface);border:1px solid var(--border);border-radius:999px;padding:4px 13px}
.cd-newline{display:flex;align-items:center;gap:10px;margin:14px 2px 8px;
font-family:var(--font-mono);font-size:.68rem;font-weight:800;letter-spacing:.1em;
text-transform:uppercase;color:var(--gold-2)}
.cd-newline::before,.cd-newline::after{content:'';flex:1;height:1px;background:var(--border-gold)}
.cd-row{display:flex;gap:9px;align-items:flex-end;margin-top:3px;position:relative}
.cd-row.me{flex-direction:row-reverse}
.cd-row.first{margin-top:13px}
.cd-row.first .cd-bub{border-bottom-left-radius:5px}
.cd-row.me.first .cd-bub{border-bottom-left-radius:16px;border-bottom-right-radius:5px}
/* Bubbles: brighter fills, real borders and a lit top edge so they sit above
the ground rather than melting into it. */
.cd-bub{max-width:78%;padding:10px 14px 8px;border-radius:16px;position:relative;
background:linear-gradient(168deg,var(--navy-850),var(--navy-900));border:1px solid var(--border);
min-width:80px;box-shadow:0 4px 12px rgba(0,0,0,.34),inset 0 1px 0 rgba(255,255,255,.07)}
.cd-row.me .cd-bub{background:linear-gradient(155deg,rgba(212,175,55,.34),rgba(212,175,55,.16));
border-color:var(--border-gold);box-shadow:0 5px 14px rgba(0,0,0,.38),inset 0 1px 0 rgba(255,255,255,.22)}
.cd-name{font-size:.76rem;font-weight:800;color:var(--gold-2);margin-bottom:3px;
font-family:var(--font-mono);letter-spacing:.02em}
/* mono at 16px โ the readability fix */
.cd-text{font-family:var(--font-mono);font-size:16px;line-height:1.62;color:var(--ink);
white-space:pre-wrap;word-break:break-word;font-weight:500}
.cd-text.gone{color:var(--ink-dim);font-style:italic;font-weight:400}
.cd-meta{display:flex;align-items:center;gap:6px;justify-content:flex-end;margin-top:4px;
font-family:var(--font-mono);font-size:.7rem;color:var(--ink-dim);line-height:1}
.cd-meta svg{width:14px;height:14px;fill:none;stroke:currentColor;stroke-width:2.2;
stroke-linecap:round;stroke-linejoin:round}
.cd-meta .seen{color:var(--gold-2)}
.cd-quote{border-left:3px solid var(--gold-2);padding:5px 9px;margin-bottom:7px;border-radius:5px;
background:rgba(212,175,55,.09);cursor:pointer}
.cd-quote b{display:block;font-size:.72rem;color:var(--gold-2);font-family:var(--font-mono)}
.cd-quote span{display:block;font-size:.8rem;color:var(--ink-dim);white-space:nowrap;
overflow:hidden;text-overflow:ellipsis;max-width:230px}
.cd-link{display:flex;align-items:center;gap:9px;margin-top:8px;padding:9px 11px;border-radius:11px;
border:1px solid var(--border-gold);background:rgba(212,175,55,.07);text-decoration:none;
color:var(--ink);max-width:100%}
.cd-link:hover{background:rgba(212,175,55,.13)}
.cd-link svg{width:17px;height:17px;flex-shrink:0;fill:none;stroke:var(--gold-2);stroke-width:2;
stroke-linecap:round;stroke-linejoin:round}
.cd-link .l{min-width:0}
.cd-link b{display:block;font-size:.8rem;font-family:var(--font-mono);color:var(--gold-2)}
.cd-link span{display:block;font-size:.74rem;color:var(--ink-dim);white-space:nowrap;
overflow:hidden;text-overflow:ellipsis;max-width:240px}
.cd-rx{display:flex;flex-wrap:wrap;gap:5px;margin-top:7px}
.cd-rx button{display:inline-flex;align-items:center;gap:4px;padding:3px 9px;border-radius:999px;
border:1px solid var(--border);cursor:pointer;font-size:.86rem;line-height:1.3;
color:var(--ink-dim);font-family:var(--font-mono);font-weight:700;
background:linear-gradient(165deg,rgba(255,255,255,.12),rgba(255,255,255,.02));
-webkit-backdrop-filter:blur(7px);backdrop-filter:blur(7px);
box-shadow:0 2px 5px rgba(0,0,0,.3),inset 0 1px 0 rgba(255,255,255,.2)}
.cd-rx button:active{transform:translateY(1px)}
.cd-rx button.mine{border-color:var(--border-gold);background:var(--gold-soft);color:var(--gold-2)}
.cd-rxpick{position:absolute;top:-42px;display:flex;gap:2px;padding:4px;border-radius:12px;
background:var(--navy-900);border:1px solid var(--border-gold);box-shadow:0 8px 22px rgba(0,0,0,.5);z-index:6}
.cd-row.me .cd-rxpick{right:8px} .cd-row:not(.me) .cd-rxpick{left:8px}
.cd-rxpick button{background:none;border:0;font-size:1.1rem;padding:4px 6px;border-radius:8px;cursor:pointer}
.cd-rxpick button:hover{background:var(--surface)}
.cd-acts{position:absolute;top:-13px;display:flex;gap:1px;padding:3px;border-radius:10px;
background:var(--navy-900);border:1px solid var(--border);box-shadow:0 6px 18px rgba(0,0,0,.45);
opacity:0;pointer-events:none;transition:opacity .12s;z-index:3}
.cd-row:hover .cd-acts,.cd-acts.open{opacity:1;pointer-events:auto}
.cd-row.me .cd-acts{left:8px}
.cd-row:not(.me) .cd-acts{right:8px}
.cd-acts button{background:none;border:0;padding:6px;border-radius:7px;cursor:pointer;
color:var(--ink-dim);display:grid;place-items:center}
.cd-acts button:hover{background:var(--surface);color:var(--gold-2)}
.cd-acts button.danger:hover{color:var(--red);background:rgba(248,113,113,.1)}
.cd-acts svg{width:15px;height:15px;fill:none;stroke:currentColor;stroke-width:2;
stroke-linecap:round;stroke-linejoin:round}
.cd-pins{flex-shrink:0;border-bottom:1px solid var(--border);background:rgba(212,175,55,.08);
padding:8px 13px;display:flex;flex-direction:column;gap:5px;max-height:104px;overflow-y:auto}
.cd-pin{display:flex;align-items:center;gap:8px;font-size:.82rem;color:var(--ink);cursor:pointer}
.cd-pin svg{width:14px;height:14px;flex-shrink:0;fill:none;stroke:var(--gold-2);stroke-width:2;
stroke-linecap:round;stroke-linejoin:round}
.cd-pin span{flex:1;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
font-family:var(--font-mono)}
.cd-pin button{background:none;border:0;color:var(--ink-dim);cursor:pointer;font-size:.72rem;
font-family:var(--font-mono);flex-shrink:0}
.cd-pin button:hover{color:var(--red)}
.cd-foot{flex-shrink:0;border-top:1px solid var(--border);background:var(--navy-900)}
.cd-reply{display:flex;align-items:center;gap:8px;padding:9px 13px;border-bottom:1px solid var(--border);
background:rgba(212,175,55,.07)}
.cd-reply .r{flex:1;min-width:0;border-left:3px solid var(--gold-2);padding-left:9px}
.cd-reply b{display:block;font-size:.72rem;color:var(--gold-2);font-family:var(--font-mono)}
.cd-reply span{display:block;font-size:.82rem;color:var(--ink-dim);white-space:nowrap;
overflow:hidden;text-overflow:ellipsis}
.cd-compose{display:flex;align-items:flex-end;gap:8px;padding:10px 11px}
.cd-compose textarea{flex:1;resize:none;max-height:132px;min-height:44px;padding:11px 13px;
border-radius:13px;border:1px solid var(--border);background:var(--navy-950);color:var(--ink);
font-family:var(--font-mono);font-size:15px;line-height:1.5}
.cd-compose textarea::placeholder{color:var(--ink-faint)}
.cd-compose textarea:focus{outline:none;border-color:var(--gold);box-shadow:0 0 0 3px var(--gold-soft)}
.cd-send{width:42px;height:42px;flex-shrink:0;border-radius:50%;border:1px solid var(--border-gold);
cursor:pointer;display:grid;place-items:center;position:relative;
background:radial-gradient(circle at 34% 24%,rgba(255,255,255,.55),rgba(255,255,255,0) 48%),
linear-gradient(155deg,var(--gold-2),var(--gold) 70%);
box-shadow:0 6px 14px rgba(0,0,0,.45),inset 0 1px 0 rgba(255,255,255,.55),
inset 0 -6px 11px rgba(90,60,0,.32);
transition:filter .15s,opacity .15s,transform .12s,box-shadow .12s}
.cd-send:disabled{opacity:.4;cursor:default}
.cd-send:not(:disabled):hover{filter:brightness(1.07);transform:translateY(-1px)}
.cd-send:not(:disabled):active{transform:translateY(1px);
box-shadow:0 2px 6px rgba(0,0,0,.45),inset 0 3px 8px rgba(90,60,0,.5)}
.cd-send svg{width:18px;height:18px;fill:none;stroke:#1a1303;stroke-width:2.2;
stroke-linecap:round;stroke-linejoin:round;filter:drop-shadow(0 1px 0 rgba(255,255,255,.4))}
.cd-readonly{padding:13px 15px;font-size:.82rem;color:var(--ink-dim);text-align:center;
font-family:var(--font-mono)}
.cd-emoji{position:absolute;bottom:60px;left:10px;right:10px;max-height:230px;overflow-y:auto;
background:var(--navy-900);border:1px solid var(--border-gold);border-radius:13px;padding:8px;
box-shadow:0 -8px 28px rgba(0,0,0,.45);z-index:5}
.cd-emoji h6{margin:6px 4px 4px;font-size:.66rem;font-family:var(--font-mono);letter-spacing:.09em;
text-transform:uppercase;color:var(--ink-dim);font-weight:700}
.cd-emoji .g{display:grid;grid-template-columns:repeat(8,1fr);gap:2px}
.cd-emoji button{background:none;border:0;font-size:1.2rem;line-height:1;padding:6px;border-radius:7px;cursor:pointer}
.cd-emoji button:hover{background:var(--surface)}
.cd-ask{position:absolute;inset:0;z-index:8;display:grid;place-items:center;padding:18px;
background:rgba(4,7,14,.74);backdrop-filter:blur(3px)}
.cd-askcard{background:var(--navy-900);border:1px solid var(--border-gold);border-radius:14px;
padding:17px 19px;max-width:320px;box-shadow:0 18px 44px rgba(0,0,0,.55)}
.cd-askcard b{display:block;font-size:.96rem;margin-bottom:7px;color:var(--ink)}
.cd-askcard p{font-size:.83rem;line-height:1.55;color:var(--ink-dim);margin:0 0 15px}
.cd-askbtns{display:flex;gap:8px;justify-content:flex-end}
.cd-askbtns button{padding:8px 15px;border-radius:9px;font-size:.83rem;font-weight:700;
cursor:pointer;font-family:inherit;border:1px solid var(--border);background:var(--surface);
color:var(--ink-dim)}
.cd-askno:hover{border-color:var(--border-gold);color:var(--gold-2)}
.cd-askyes{border-color:var(--border-gold) !important;color:var(--gold-2) !important;
background:var(--gold-soft) !important}
.cd-askyes.danger{border-color:rgba(248,113,113,.45) !important;color:var(--red) !important;
background:rgba(248,113,113,.12) !important}
.cd-sig{border:1px solid var(--border);border-left-width:3px;border-radius:10px;padding:10px 12px;
background:var(--surface);margin-bottom:9px}
.cd-sig-top{display:flex;align-items:center;gap:8px;flex-wrap:wrap;font-size:.86rem}
.cd-sig-top strong{font-family:var(--font-mono);letter-spacing:.03em;color:var(--ink)}
.cd-sig-top span{font-family:var(--font-mono);font-size:.78rem;font-weight:800}
.cd-sig-top em{font-style:normal;font-size:.74rem;color:var(--ink-dim);font-family:var(--font-mono)}
.cd-sig-when{margin-left:auto;color:var(--ink-dim);font-weight:400 !important}
.cd-sig-lv{font-family:var(--font-mono);font-size:.8rem;color:var(--ink-dim);margin-top:6px}
.cd-sig-msg{font-size:.87rem;color:var(--ink);margin-top:7px;white-space:pre-wrap;line-height:1.5;
font-family:var(--font-mono)}
.cd-sig-by{font-family:var(--font-mono);font-size:.7rem;color:var(--ink-dim);margin-top:7px}
.cd-typing{padding:3px 15px 7px;font-size:.76rem;color:var(--gold-2);font-family:var(--font-mono)}
.cd-empty{flex:1;display:grid;place-items:center;padding:34px 22px;text-align:center;
color:var(--ink-dim);font-size:.88rem}
.cd-err{padding:8px 14px;font-size:.78rem;color:var(--red);font-family:var(--font-mono)}
/* one pane at a time on narrow screens, full screen on phones */
/* light theme: glass has to invert or it disappears on a white ground */
html[data-cg-theme="light"] .cd-panel{background:var(--navy-900)}
html[data-cg-theme="light"] .cd-side{background:var(--navy-850)}
html[data-cg-theme="light"] .cd-icobtn{background:linear-gradient(165deg,rgba(255,255,255,.92),rgba(20,30,60,.05));
box-shadow:0 3px 8px rgba(20,30,60,.14),inset 0 1px 0 rgba(255,255,255,.95)}
html[data-cg-theme="light"] .cd-icobtn svg{filter:none}
html[data-cg-theme="light"] .cd-bub{background:linear-gradient(168deg,var(--navy-900),var(--navy-850));
box-shadow:0 3px 10px rgba(20,30,60,.12),inset 0 1px 0 rgba(255,255,255,.9)}
html[data-cg-theme="light"] .cd-avatar,html[data-cg-theme="light"] .cd-av{
box-shadow:0 4px 10px rgba(20,30,60,.22),inset 0 1px 0 rgba(255,255,255,.55),inset 0 -6px 12px rgba(20,30,60,.16)}
html[data-cg-theme="light"] .cd-rx button{background:linear-gradient(165deg,rgba(255,255,255,.92),rgba(20,30,60,.05))}
@media(max-width:900px){ .cd-side{width:100%;border-right:0} .cd-resize{display:none}
.cd-panel{width:min(440px,calc(100vw - 32px));height:min(620px,calc(100vh - 44px))} }
@media(max-width:640px){
.cd-panel{left:0 !important;right:0;bottom:0;top:0;width:100% !important;height:100% !important;
border-radius:0;border:0}
.cd-launch{left:16px;bottom:16px}
.cd-bub{max-width:86%}
}`;
document.head.appendChild(s);
})();
/* โโ icons โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
const I = {
chat: ,
close: ,
back: ,
reply: ,
edit: ,
trash: ,
copy: ,
smile: ,
react: ,
pin: ,
send: ,
link: ,
check: ,
checks:,
bellOn: ,
bellOff: ,
};
// One mark per room type โ a channel is not the same kind of thing as a person.
const ROOM_ICON = {
staff: ,
boardroom: ,
all: ,
trial: ,
vip: ,
signal: ,
};
function roomIcon(key){
if(key==='staff') return ROOM_ICON.staff;
if(key==='boardroom') return ROOM_ICON.boardroom;
if(key==='broadcast:all') return ROOM_ICON.all;
if(key==='broadcast:trial') return ROOM_ICON.trial;
if(key==='broadcast:vip') return ROOM_ICON.vip;
if(key==='signalview') return ROOM_ICON.signal;
return null; // a person โ initials read better
}
function hueClass(key){
if(key==='staff') return 'h-gold';
if(key==='boardroom') return 'h-em';
if(key==='signalview') return 'h-sky';
if(key==='broadcast:all') return 'h-ind';
if(key==='broadcast:trial') return 'h-slate';
if(key==='broadcast:vip') return 'h-gold';
return ''; // a person keeps the neutral navy orb
}
const initials = s => (String(s||'?').trim().split(/[\s@._-]+/).filter(Boolean).slice(0,2)
.map(w=>w[0]).join('') || '?').toUpperCase();
let DOCK_SEQ = 0;
/* โโ thread naming โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
function threadTitle(key){
if(key==='staff') return 'Team room';
if(key==='boardroom') return 'Boardroom';
if(key==='broadcast:all') return 'All members';
if(key==='broadcast:trial') return 'Free trial tier';
if(key==='broadcast:vip') return 'VIP members';
if(key==='signalview') return 'Signal View';
if(key && key.indexOf('support:')===0) return key.slice(8);
return key || '';
}
function threadSub(key){
if(key==='staff') return 'Coaches and admins';
if(key==='boardroom') return 'Admin-only';
if(key && key.indexOf('broadcast:')===0) return 'Broadcast channel';
if(key==='signalview') return 'Live signal feed ยท read-only';
if(key && key.indexOf('support:')===0) return 'Direct support';
return '';
}
// Students read broadcast channels, they do not post in them.
function readOnlyFor(key, staffMode){
if(key==='signalview') return true;
if(!staffMode && String(key).indexOf('broadcast:')===0) return true;
return false;
}
const URL_RE = /(https?:\/\/[^\s]+)/i;
function firstUrl(text){ const m = URL_RE.exec(String(text||'')); return m ? m[1].replace(/[.,)]+$/,'') : null; }
function hostOf(u){ try{ return new URL(u).hostname.replace(/^www\./,''); }catch(e){ return u; } }
/* The dock's own mark โ the brand logo as a lit orb, with the chat glyph as a
fallback if the image is missing. */
function BrandOrb(){
const [bad,setBad] = useState(false);
return
{bad ? I.chat :
setBad(true)}/>}
;
}
/* โโ one message โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
function Bubble({m, mine, first, canPurge, canPin, quoted, reactions, meEmail, seen, showReceipts,
onReply, onEdit, onDelete, onPurge, onPin, onJumpTo, onReact}){
const [picker,setPicker] = useState(false);
const gone = !!m.deleted;
const url = gone ? null : firstUrl(m.body);
const counts = {};
(reactions||[]).forEach(r=>{ counts[r.emoji] = (counts[r.emoji]||0)+1; });
const mineRx = (reactions||[]).filter(r=>String(r.reactor_email).toLowerCase()===meEmail).map(r=>r.emoji);
return
{ask.note}