diff --git a/extension/sidepanel.html b/extension/sidepanel.html
index 3160d80..120b3d1 100644
--- a/extension/sidepanel.html
+++ b/extension/sidepanel.html
@@ -25,6 +25,40 @@
#input-row { display: flex; gap: 6px; }
#text { flex: 1; resize: none; height: 44px; }
#send { cursor: pointer; background: #6b4fd6; border-color: #6b4fd6; padding: 0 14px; }
+ #emoji-btn { cursor: pointer; padding: 0 9px; font-size: 15px; line-height: 1; }
+
+ #emoji-pad {
+ background: #1b1826; border: 1px solid #3a3548; border-radius: 6px;
+ padding: 6px; margin-bottom: 6px;
+ }
+ /* An ID selector outranks the UA's [hidden]{display:none}, so `hidden` alone
+ never closes this pad โ it has to be spelled out here. */
+ #emoji-pad[hidden] { display: none; }
+
+ #emoji-cats {
+ display: flex; gap: 2px; margin-bottom: 5px;
+ border-bottom: 1px solid #2c2836; padding-bottom: 5px;
+ }
+ #emoji-cats button {
+ flex: 1; border: 0; background: transparent; cursor: pointer;
+ font-size: 14px; line-height: 1; padding: 4px 0; border-radius: 4px;
+ }
+ #emoji-cats button:hover { background: #2f2a40; }
+ #emoji-cats button.on { background: #3a3155; box-shadow: inset 0 -2px 0 #a78bfa; }
+
+ #emoji-lbl {
+ font-size: 10px; opacity: .45; margin-bottom: 4px;
+ text-transform: lowercase; letter-spacing: .04em;
+ }
+ #emoji-grid {
+ display: grid; grid-template-columns: repeat(8, 1fr); gap: 2px;
+ max-height: 168px; overflow-y: auto;
+ }
+ #emoji-grid button {
+ border: 0; background: transparent; padding: 4px 0;
+ font-size: 17px; line-height: 1.1; cursor: pointer; border-radius: 4px;
+ }
+ #emoji-grid button:hover { background: #2f2a40; }
#status { font-size: 11px; opacity: .6; margin-bottom: 6px; }
#ctx {
font-size: 11px; color: #9d8fd0; margin-bottom: 6px;
@@ -93,7 +127,13 @@
โ
connectingโฆ
+
+
diff --git a/extension/sidepanel.js b/extension/sidepanel.js
index 58dbf9a..c7ebfd2 100644
--- a/extension/sidepanel.js
+++ b/extension/sidepanel.js
@@ -211,6 +211,88 @@ async function sendText() {
}
}
+// โโ Emoji pad โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+// Category table lifted verbatim from celest-chat's 07-emoji.js โ same house,
+// same taste, and the "house" row only means something in this room. Offline by
+// construction: no library, no fetch, the panel's CSP would block a set anyway.
+const EMOJI = [
+ ["๐","house", "๐ ๐ ๐ ๐ซ โจ ๐ฏ๏ธ ๐ ๐ป ๐ค ๐ฉต ๐ธ ๐ฅ โก ๐ฏ ๐ง ๐ช ๐ ๏ธ ๐งป ๐ฌ ๐ช ๐ โพ๏ธ ๐ ๐ซ ๐ฆ ๐บ ๐ง ๐ณ ๐ผ ๐ฐ"],
+ ["๐","faces", "๐ ๐ ๐ ๐ ๐ ๐
๐คฃ ๐ ๐ ๐ ๐ ๐ ๐ ๐ฅฐ ๐ ๐คฉ ๐ ๐ ๐ ๐ ๐ฅฒ ๐ ๐ ๐ ๐คช ๐ ๐ค ๐คญ ๐คซ ๐ค ๐คจ ๐ ๐ ๐ถ ๐ ๐ ๐ ๐ฌ ๐ฎโ๐จ ๐คฅ ๐ ๐ ๐ช ๐คค ๐ด ๐ฅฑ ๐ตโ๐ซ ๐ค ๐ฅด ๐คข ๐คฎ ๐คง ๐ฅต ๐ฅถ ๐ ๐ค ๐ง ๐ ๐ ๐ ๐ฎ ๐ฏ ๐ฒ ๐ณ ๐ฅบ ๐ฆ ๐ง ๐จ ๐ฐ ๐ฅ ๐ข ๐ญ ๐ฑ ๐ ๐ฃ ๐ ๐ ๐ฉ ๐ซ ๐ค ๐ก ๐ ๐คฌ ๐ ๐ ๐คก"],
+ ["๐ซถ","hands", "๐ ๐ค ๐๏ธ โ ๐ ๐ ๐ค ๐ค โ๏ธ ๐ค ๐ซฐ ๐ค ๐ค ๐ค ๐ ๐ ๐ ๐ ๐ โ๏ธ ๐ ๐ โ ๐ ๐ค ๐ค ๐ ๐ ๐ซถ ๐ ๐คฒ ๐ค ๐ โ๏ธ ๐
๐คณ ๐ช ๐ฆพ ๐ฆต ๐ฆถ ๐ ๐๏ธ ๐ง ๐ซ ๐ซ ๐ฆท ๐
๐"],
+ ["๐","animals","๐ถ ๐ฑ ๐ญ ๐น ๐ฐ ๐ฆ ๐ป ๐ผ ๐จ ๐ฏ ๐ฆ ๐ฎ ๐ท ๐ธ ๐ต ๐ ๐ ๐ ๐ ๐ง ๐ฆ ๐ค ๐ฆ ๐ฆ ๐ฆ ๐บ ๐ ๐ด ๐ฆ ๐ ๐ชฒ ๐ฆ ๐ ๐ ๐ ๐ท๏ธ ๐ฆ ๐ข ๐ ๐ฆ ๐ ๐ฆ ๐ฆ ๐ฆ ๐ก ๐ ๐ ๐ฌ ๐ณ ๐ ๐ฆ ๐ ๐
๐ฆ ๐ฆ ๐ ๐ฆ ๐ซ ๐ฆ ๐ฆ ๐ ๐ ๐๏ธ"],
+ ["๐","food", "๐ ๐ ๐ ๐ ๐ ๐ ๐ ๐ ๐ ๐ซ ๐ ๐ ๐ ๐ฅญ ๐ ๐ฅฅ ๐ฅ ๐
๐ฅ ๐ฅฆ ๐ฅ ๐ฝ ๐ถ๏ธ ๐ฅ ๐ ๐ฅ ๐ ๐ฅ ๐ง ๐ฅ ๐ณ ๐ฅ ๐ง ๐ฅ ๐ ๐ ๐ ๐ญ ๐ฅช ๐ฎ ๐ฏ ๐ฅ ๐ ๐ฒ ๐ ๐ฃ ๐ฑ ๐ฅ ๐ค ๐ ๐ ๐ฅ ๐ฅ ๐ฆ ๐ฐ ๐ ๐ซ ๐ฌ ๐ญ ๐ฎ โ ๐ต ๐ง ๐ฅค ๐บ ๐ป ๐ฅ ๐ท ๐ฅ"],
+ ["๐","things", "โจ๏ธ ๐ฅ๏ธ ๐ฑ๏ธ ๐ป ๐ฑ ๐น๏ธ ๐พ ๐ฟ ๐ ๐ ๐ ๐ชซ ๐ท ๐ฅ ๐บ ๐ป ๐๏ธ ๐๏ธ ๐๏ธ โฑ๏ธ โฐ ๐ฐ๏ธ ๐ ๐ ๐ ๐ ๐ ๐๏ธ ๐จ ๐ช โ๏ธ ๐ ๏ธ ๐ก๏ธ โ๏ธ ๐ง ๐ช ๐ฉ โ๏ธ ๐งฐ ๐งฒ ๐ฃ ๐งจ ๐ชค ๐งช ๐งฌ ๐ฌ ๐ญ ๐ก ๐ ๐ ๐ ๐ธ โ๏ธ ๐ ๐ ๐๏ธ ๐ฒ โต ๐ฐ๏ธ"],
+ ["๐","nature", "๐ ๐ ๐ ๐ ๐ ๐ ๐ ๐ ๐ ๐ ๐ ๐ ๐ ๐ ๐ โ๏ธ ๐ ๐ โญ ๐ ๐ซ โจ โ๏ธ ๐ช ๐ โ๏ธ โ
๐ค๏ธ ๐ฅ๏ธ ๐ฆ๏ธ ๐ง๏ธ โ๏ธ ๐ฉ๏ธ ๐จ๏ธ โ๏ธ โ๏ธ ๐ฌ๏ธ ๐ช๏ธ ๐ซ๏ธ ๐ ๐ง ๐ฅ ๐ ๐ธ ๐บ ๐ป ๐ผ ๐ท ๐น ๐ฅ ๐ฟ ๐ ๐ ๐ ๐พ ๐ต ๐ฒ ๐ณ ๐ด"],
+ ["๐ฃ","symbols","โค๏ธ ๐งก ๐ ๐ ๐ ๐ ๐ค ๐ค ๐ค ๐ โฃ๏ธ ๐ ๐ ๐ ๐ ๐ ๐ ๐ โ
โ โญ ๐ซ โ โ ๏ธ ๐ด ๐ ๐ก ๐ข ๐ต ๐ฃ โซ โช ๐บ ๐ป ๐ถ ๐ท โฌ โฌ โถ๏ธ โธ๏ธ โน๏ธ ๐ ๐ ๐ โ โ โ๏ธ โ โพ๏ธ โ โ ๐ฏ ๐ ๐ โณ โ ๐ฏ ๐ ๐ฅ ๐ ๐"],
+];
+const RECENT_KEY = "ccsidepanel-emoji-recent", RECENT_MAX = 24;
+
+const emojiPad = document.getElementById("emoji-pad");
+const emojiBtn = document.getElementById("emoji-btn");
+const emojiCats = document.getElementById("emoji-cats");
+const emojiGrid = document.getElementById("emoji-grid");
+const emojiLbl = document.getElementById("emoji-lbl");
+
+function recents() {
+ try { return JSON.parse(localStorage.getItem(RECENT_KEY)) || []; } catch (e) { return []; }
+}
+function remember(ch) {
+ const list = [ch, ...recents().filter(x => x !== ch)].slice(0, RECENT_MAX);
+ try { localStorage.setItem(RECENT_KEY, JSON.stringify(list)); } catch (e) {}
+}
+
+function insertEmoji(ch) {
+ // Insert at the caret, not at the end โ appending mangles a half-typed line.
+ const start = textEl.selectionStart ?? textEl.value.length;
+ const end = textEl.selectionEnd ?? textEl.value.length;
+ textEl.value = textEl.value.slice(0, start) + ch + textEl.value.slice(end);
+ const caret = start + ch.length;
+ textEl.focus();
+ textEl.setSelectionRange(caret, caret);
+}
+
+function paintEmoji(list, label) {
+ emojiGrid.innerHTML = "";
+ emojiLbl.textContent = list.length ? label : `${label} ยท nothing yet`;
+ for (const ch of list) {
+ const b = document.createElement("button");
+ b.type = "button";
+ b.textContent = ch;
+ b.addEventListener("click", () => { insertEmoji(ch); remember(ch); }); // pad stays open
+ emojiGrid.appendChild(b);
+ }
+}
+
+function showCat(i) {
+ [...emojiCats.children].forEach((b, k) => b.classList.toggle("on", k === i));
+ if (i === 0) return paintEmoji(recents(), "recent");
+ const [, name, set] = EMOJI[i - 1];
+ paintEmoji(set.split(" ").filter(Boolean), name);
+}
+
+// tab 0 is recents, then one per category
+[["๐", "recent"], ...EMOJI.map(c => [c[0], c[1]])].forEach(([icon, name], i) => {
+ const b = document.createElement("button");
+ b.type = "button";
+ b.textContent = icon;
+ b.title = name;
+ b.addEventListener("click", () => showCat(i));
+ emojiCats.appendChild(b);
+});
+
+function toggleEmoji(show) {
+ emojiPad.hidden = show === undefined ? !emojiPad.hidden : !show;
+ if (!emojiPad.hidden) { showCat(recents().length ? 0 : 1); textEl.focus(); }
+}
+
+emojiBtn.addEventListener("click", (e) => { e.stopPropagation(); toggleEmoji(); });
+document.addEventListener("click", (e) => {
+ if (!emojiPad.hidden && !emojiPad.contains(e.target)) toggleEmoji(false);
+});
+document.addEventListener("keydown", (e) => {
+ if (e.key === "Escape" && !emojiPad.hidden) toggleEmoji(false);
+});
+
sendBtn.addEventListener("click", sendText);
textEl.addEventListener("keydown", (e) => {
if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); sendText(); }