1class FloatingChatSystem{constructor(){this.isExpanded=!1,this.messages=[],this.socket=null,this.unreadCount=0,this.isMainChatroom="/chatroom"===window.location.pathname,this.init()}init(){this.isMainChatroom||(this.addFloatingChatHTML(),window.user&&window.user.id&&this.setupSocket(),this.startStatsUpdater())}addFloatingChatHTML(){if(document.getElementById("floatingChatSystem"))return;const t=window.__("chatroom.floating.chat"),e=window.__("chatroom.floating.virtual_chat"),n=window.__("chatroom.floating.open_full_chatroom"),a=window.__("chatroom.floating.connect_to_see_messages"),o=window.__("chatroom.floating.join_full_chat_exp"),s=window.__("chatroom.floating.type_message"),i=window.__("chatroom.floating.login_to_join"),r=window.__("chatroom.floating.login"),l=window.__("chatroom.floating.register");if("function"==typeof window.generateTemplateFloatingChatHTML)try{const d={chatText:t,virtualChatText:e,openFullChatroomText:n,connectToSeeMessagesText:a,joinFullChatExpText:o,typeMessageText:s,loginToJoinText:i,loginText:r,registerText:l,userAuthenticated:window.user&&window.user.id},c=window.generateTemplateFloatingChatHTML(d);if(c)return document.body.insertAdjacentHTML("beforeend",c),this.setupUserInterface(),this.setupMessageInput(),void this.setupDraggable()}catch(t){}const d=/Android/.test(navigator.userAgent)&&(/wv|WebView/.test(navigator.userAgent)||"undefined"!=typeof AndroidKeyboard||navigator.userAgent.includes("SehatWalafiyat")),c=`\n \x3c!-- Floating Chat System --\x3e\n <div id="floatingChatSystem" class="fixed ${d?"bottom-20":"bottom-6"} right-6 ${d?"z-[9999]":"z-40"}" style="cursor: move; will-change: transform;">\n \x3c!-- Chat Button (collapsed state) --\x3e\n <div id="floatingChatButton" class="relative" style="display: block !important; visibility: visible !important; opacity: 1 !important;">\n <button onclick="window.floatingChat.toggleChat()"\n class="flex items-center space-x-2 bg-purple-600 dark:bg-purple-500 hover:bg-purple-700 dark:hover:bg-purple-600\n text-white px-4 py-3 rounded-full shadow-lg hover:shadow-xl dark:shadow-2xl\n transition-all duration-200 transform hover:scale-105\n sm:px-4 sm:py-3 sm:space-x-2\n max-sm:px-3 max-sm:py-2 max-sm:space-x-1"\n style="display: flex !important; visibility: visible !important; opacity: 1 !important; pointer-events: auto !important;">\n <svg class="w-5 h-5 max-sm:w-4 max-sm:h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">\n <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z"/>\n </svg>\n <span class="font-medium text-base max-sm:text-sm max-sm:hidden">${t}</span>\n <span id="floatingPlayerCount" class="bg-white/20 dark:bg-black/20 text-xs px-2 py-1 rounded-full max-sm:px-1.5 max-sm:py-0.5">0</span>\n <span id="chatNotificationBadge" class="absolute -top-2 -right-2 bg-red-500 dark:bg-red-400 \n text-white text-xs w-5 h-5 rounded-full flex items-center justify-center \n hidden animate-pulse max-sm:w-4 max-sm:h-4 max-sm:-top-1 max-sm:-right-1">!</span>\n </button>\n </div>\n\n \x3c!-- Chat Window --\x3e\n <div id="floatingChatWindow" class="hidden absolute\n bottom-0 right-0\n w-80 h-96 \n max-w-[calc(100vw-2rem)] \n bg-white dark:bg-gray-800 rounded-2xl shadow-2xl dark:shadow-3xl border border-gray-200 dark:border-gray-600 flex flex-col\n transform-gpu\n max-sm:w-72 max-sm:h-72 max-sm:bottom-12">\n \n \x3c!-- Chat Header --\x3e\n <div class="flex items-center justify-between p-4 border-b border-gray-200 dark:border-gray-600 \n bg-purple-50 dark:bg-purple-900/30 rounded-t-2xl max-sm:p-3">\n <div class="flex items-center space-x-2">\n <svg class="w-5 h-5 text-purple-600 dar
1k:text-purple-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">\n <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z"/>\n </svg>\n <h3 class="font-semibold text-gray-900 dark:text-white text-base max-sm:text-sm">${e}</h3>\n <a href="/chatroom" onclick="window.floatingChat.openFullChatroom(event)" title="${n}"\n style="text-decoration:none">\n <span id="windowPlayerCount" class="bg-purple-600 dark:bg-purple-500 text-white text-xs px-2 py-1 rounded-full max-sm:px-1.5 max-sm:py-0.5">0</span>\n </a>\n </div>\n <div class="flex items-center space-x-2 max-sm:space-x-1">\n <a href="/chatroom" onclick="window.floatingChat.openFullChatroom(event)" class="text-purple-600 dark:text-purple-400 hover:text-purple-700 dark:hover:text-purple-300 p-1\n hover:bg-purple-100 dark:hover:bg-purple-800/50 rounded transition" title="${n}">\n <svg class="w-5 h-5 max-sm:w-4 max-sm:h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">\n <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3.75 3.75v4.5m0-4.5h4.5m-4.5 0L9 9M3.75 20.25v-4.5m0 4.5h4.5m-4.5 0L9 15M2
10.25 3.75h-4.5m4.5 0v4.5m0-4.5L15 9m5.25 11.25h-4.5m4.5 0v-4.5m0 4.5L15 15"/>\n </svg>\n </a>\n <button onclick="window.floatingChat.toggleChat()" \n class="text-gray-400 dark:text-gray-300 hover:text-gray-600 dark:hover:text-gray-100 p-1">\n <svg class="w-5 h-5 max-sm:w-4 max-sm:h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">\n <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>\n </svg>\n </button>\n </div>\n </div>\n\n \x3c!-- Chat Messages --\x3e\n <div id="floatingChatMessages" class="flex-1 overflow-y-auto p-4 space-y-2 max-sm:p-3">\n <div class="text-center text-gray-500 dark:text-gray-400 text-sm py-4">\n <svg class="w-8 h-8 max-sm:w-6 max-sm:h-6 mb-2 mx-auto text-gray-400 dark:text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">\n <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z"/>\n </svg>\n <p class="text-sm max-sm:text-xs">${a}</p>\n <p class="text-xs mt-1">${o}</p>\n </div>\n </div>\n\n \x3c!-- Chat Input --\x3e\n <div class="p-4 border-t border-gray-200 dark:border-gray-600 max-sm:p-3">\n <div id="floatingChatInputContainer">\n \x3c!-- Will be populated based on user authentication --\x3e\n </div>\n </div>\n </div>\n </div>\n `;document.body.insertAdjacentHTML("beforeend",c),this.setupUserInterface(),this.setupMessageInput(),this.setupDraggable()}setupDraggable(){const t=document.getElementById("floatingChatSystem");if(!t)return;let e,n,a,o,s=!1,i=!1;const r=localStorage.getItem("floatingChatPosition");if(r)try{const e=JSON.parse(r);t.style.right="auto",t.style.bottom="auto",t.style.left=e.left+"px",t.style.top=e.top+"px"}catch(t){}const l=r=>{const l=r.target;if(l.closest("button, a, input, textarea"))return;if(this.isExpanded){const t=l.closest('.rounded-t-2xl, [class*="border-b"]');if(!(t&&t.closest("#floatingChatWindow")))return}else if(l.closest("#floatingChatWindow"))return;s=!0,i=!1,e=r.clientX||r.touches?.[0]?.clientX,n=r.clientY||r.touches?.[0]?.clientY;const d=t.getBoundingClientRect();a=d.left,o=d.top,t.style.transition="none"},d=r=>{if(!s)return;const l=r.clientX||r.touches?.[0]?.clientX,d=r.clientY||r.touches?.[0]?.clientY,c=l-e,h=d-n;if((Math.abs(c)>10||Math.abs(h)>10)&&(i=!0,r.preventDefault()),!i)return;let p=a+c,m=o+h;const u=t.getBoundingClientRect(),g=window.innerWidth-u.width,x=window.innerHeight-u.height;p=Math.max(0,Math.min(p,g)),m=Math.max(0,Math.min(m,x)),t.style.right="auto",t.style.bottom="auto",t.style.left=p+"px",t.style.top=m+"px"},c=()=>{if(s&&(s=!1,t.style.transition="",i)){const e=t.getBoundingClientRect();localStorage.setItem("floatingChatPosition",JSON.stringify({left:e.left,top:e.top}))}};t.addEventListener("click",t=>{i&&(t.stopPropagation(),t.preventDefault(),i=!1)},!0),t.addEventListener("mousedown",l),document.addEventListener("mousemove",d),document.addEventListener("mouseup",c),t.addEventListener("touchstart",l,{passive:!0}),document.addEventListener("touchmove",d,{passive:!1}),document.addEventListener("touchend",c);const h=document.getElementById("floatingChatWindow");if(h){const t=h.querySelector('.rounded-t-2xl, [class*="border-b"]');t&&(t.style.cursor="move")}}setupUserInterface(){const t=document.getElementById("floatingChatInputContainer");if(t)if(window.user&&window.user.id){const e=window.__("chatroom.floating.type_message");t.innerHTML=`\n <div id="floatingChatInput">\n <div class="flex space-x-2">\n <input type="text" id="floatingMessageInput" placeholder="${e}" \n class="flex-1 px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg \n bg-white dark:bg-gray-700 text-gray-900 dark:text-white placeholder-gray-500 dark:placeholder-gray-400\n focus:outline-none focus:ring-2 focus:ring-purple-500 dark:focus:ring-purple-400 text-sm\n max-sm:px-2 max-sm:py-1.5">\n <button onclick="window.floatingChat.sendMessage()" \n class="bg-purple-600 dar
1k:bg-purple-500 text-white px-4 py-2 rounded-lg \n hover:bg-purple-700 dark:hover:bg-purple-600 transition max-sm:px-3 max-sm:py-1.5">\n <svg class="w-4 h-4 max-sm:w-3.5 max-sm:h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">\n <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 12L3.269 3.126A59.768 59.768 0 0121.485 12 59.77 59.77 0 013.27 20.876L5.999 12zm0 0h7.5"/>\n </svg>\n </button>\n </div>\n </div>\n `}else{const e=window.__("chatroom.floating.login_to_join"),n=window.__("chatroom.floating.login"),a=window.__("chatroom.floating.register");t.innerHTML=`\n <div class="text-center">\n <p class="text-sm text-gray-600 dark:text-gray-300 mb-3 max-sm:text-xs max-sm:mb-2">${e}</p>\n <div class="flex space-x-2">\n <a href="/login" class="flex-1 bg-purple-600 dark:bg-purple-500 text-white py-2 px-3 \n rounded-lg text-sm font-medium text-center hover:bg-purple-700 dark:hover:bg-purple-600 transition\n max-sm:py-1.5 max-sm:px-2 max-sm:text-xs">\n ${n}\n </a>\n <a href="/register" class="flex-1 border border-purple-600 dark:border-purple-400 text-purple-600 dark:text-purple-400 \n py-2 px-3 rounded-lg text-sm font-medium text-center \n hover:bg-purple-50 dark:hover:bg-purple-800/50 transition max-sm:py-1.5 max-sm:px-2 max-sm:text-xs">\n ${a}\n </a>\n </div>\n </div>\n `}}setupSocket(){"undefined"!=typeof io&&window.user&&(this.socket=io({auth:{userId:window.user.id,token:sessionStorage.getItem("socket_token")||"floating_"+Date.now()}}),this.socket.on("connect",()=>{this.loadChatHistory()}),this.socket.on("chatMessage",t=>{this.handleChatMessage(t)}),this.socket.on("disconnect",()=>{}))}setupMessageInput(){const t=document.getElementById("floatingMessageInput");t&&t.addEventListener("keydown",t=>{"Enter"!==t.key||t.shiftKey||(t.preventDefault(),this.sendMessage())})}toggleChat(){this.isExpanded=!this.isExpanded;const t=document.getElementById("floatingChatButton"),e=document.getElementById("floatingChatWindow");this.isExpanded?(e?.classList.remove("hidden"),t?.classList.add("hidden"),t?.style.setProperty("display","none","important"),this.unreadCount=0,this.updateNotificationBadge(),this.mulaiPollingStats()):(e?.classList.add("hidden"),t?.classList.remove("hidden"),t?.style.setProperty("display","block","important"),this.hentikanPollingStats())}async openFullChatroom(t){if(/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)||window.matchMedia&&window.matchMedia("(max-width: 1024px)").matches&&"ontouchstart"in window){t.preventDefault();document.documentElement;window.location.href="/chatroom"}}async sendMessage(){const t=document.getElementById("floatingMessageInput"),e=t?.value.trim();if(e&&window.user)try{const n=new FormData;n.append("message",e),$.ajax({url:"/chatroom/floating-message",method:"POST",data:n,processData:!1,contentType:!1}).done(function(e){t.value=""}).fail(function(t,e,n){})}catch(t){}}handleChatMessage(t){this.messages.push(t),this.updateChatDisplay(),this.isExpanded||(this.unreadCount++,this.updateNotificationBadge())}loadChatHistory(){$.ajax({url:"/chatroom/history",method:"GET",dataType:"json"}).done(t=>{200===t.status&&t.data.messages&&(this.messages=t.data.messages,this.updateChatDisplay())}).fail((t,e,n)=>{})}updateChatDisplay(){const t=document.getElementById("floatingChatMessages");if(!t)return;const e=this.messages.slice(-50);if(0===e.length){const e=window.__("chatroom.floating.connect_to_see_messages"),n=window.__("chatroom.floating.join_full_chat_exp");return void(t.innerHTML=`\n <div class="text-center text-gray-500 dark:text-gray-400 text-sm py-4">\n <svg class="w-8 h-8 mb-2 mx-auto text-gray-400 dark:text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">\n <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.25
15-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z"/>\n </svg>\n <p>${e}</p>\n <p class="text-xs mt-1">${n}</p>\n </div>\n `)}let n="";if("function"==typeof window.generateTemplateFloatingChatMessageHTML)try{n=window.generateTemplateFloatingChatMessageHTML(e,this.escapeHtml.bind(this))}catch(t){}t.innerHTML=n||e.map(t=>{var e=parseInt(t.level,10)||1,n=t.avatarUrl||"/assets/images/default-avatar.jpg";return`\n <div style="display:flex;gap:9px;padding:5px 8px;border-radius:10px">\n <img src="${this.escapeHtml(n)}" alt="" style="width:30px;height:30px;border-radius:9999px;object-fit:cover;flex:none;margin-top:1px" onerror="this.onerror=null;this.src='/assets/images/default-avatar.jpg'">\n <div style="min-width:0;flex:1">\n <div style="display:flex;align-items:center;gap:6px;min-width:0">\n <span class="text-gray-900 dark:text-white" style="font-weight:600;font-size:13px;letter-spacing:-.01em;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0">${this.escapeHtml(t.name)}</span>\n <span style="flex:none;background:#6366f1;color:#fff;font-size:9px;font-weight:700;line-height:1.5;padding:0 5px;border-radius:9999px">R${e}</span>\n </div>\n <div class="text-gray-700 dark:text-gray-300" style="font-size:13px;line-height:1.45;margin-top:1px;word-break:break-word;overflow-wrap:anywhere">${this.escapeHtml(t.message)}</div>\n </div>\n </div>`}).join(""),t.scrollTop=t.scrollHeight}updateNotificationBadge(){const t=document.getElementById("chatNotificationBadge");t&&(this.unreadCount>0?(t.textContent=this.unreadCount>9?"9+":this.unreadCount.toString(),t.classList.remove("hidden")):t.classList.add("hidden"))}updateStats(){$.ajax({url:"/chatroom/stats",method:"GET",dataType:"json"}).done(t=>{200===t.status&&this.updateCounters(t.data.onlineCount)}).fail((t,e,n)=>{})}updateCounters(t){["#floatingPlayerCount","#windowPlayerCount","#headerPlayerCount","#sidebarPlayerCount"].forEach(e=>{const n=document.querySelector(e);if(!n)return;const a=parseInt(t,10)||0;a>0?(n.textContent=a,n.style.background="linear-gradient(90deg,#059669,#047857)"):(n.textContent=window.__?window.__("chatroom.floating.be_first"):"Masuk",n.style.background="")})}startStatsUpdater(){window.__okaStatsSekali||(window.__okaStatsSekali=!0,this.updateStats())}mulaiPollingStats(){this._statsTimer||(this.updateStats(),this._statsTimer=setInterval(()=>{"hidden"!==document.visibilityState&&this.updateStats()},3e4))}hentikanPollingStats(){this._statsTimer&&(clearInterval(this._statsTimer),this._statsTimer=null)}escapeHtml(t){const e=document.createElement("div");return e.textContent=t,e.innerHTML}}
1document.addEventListener("DOMContentLoaded",()=>{setTimeout(()=>{window.floatingChat||(window.floatingChat=new FloatingChatSystem)},100)});
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.