/** * Selector helpers. Prefer semantic names and text content over Tailwind * utility classes — utility classes change every deploy. */ export const SITE = { overlay: '.bb-room-overlay', inGameRoot: '.bb-room-overlay', homeRoot: '.bb-scanlines', loginChip: '.bb-login-chip', footerNav: '.bb-footer-nav-main-fade', chatInputCandidates: [ 'textarea.bb-chat-input', '[data-bb-chat-input]', 'textarea[placeholder*="chat" i]', 'textarea[placeholder*="message" i]', 'textarea[aria-label*="message" i]', 'textarea[aria-label*="chat" i]', ], chatScrollbackCandidates: [ '.bb-chat-messages', '[data-bb-chat-messages]', '[role="log"]', '.bb-messages', ], sendButtonCandidates: [ 'button[data-bb-chat-send]', 'button[aria-label*="send" i]', 'button[type="submit"]', ], menuItems: '.bb-menu-item', inGameTimer: '.bb-timer, [data-bb-timer]', inGamePlayers: '[data-bb-players], .bb-players', inGameRankPanel: '.bb-rank-panel, [data-bb-rank]', }; /** * First matching element from a candidate selector list, OR a fallback * function-based search. */ export function pick( candidates: string[], root: ParentNode = document, ): T | null { for (const sel of candidates) { const el = root.querySelector(sel); if (el) return el as T; } return null; } /** * Text-based finder: returns the deepest element whose own text exactly * matches one of the given strings (case-insensitive, trimmed). */ export function findByText( root: ParentNode, needles: string[], options: { tag?: string } = {}, ): Element | null { const tag = options.tag?.toLowerCase() ?? ''; const norm = needles.map((s) => s.trim().toLowerCase()); const walker = document.createTreeWalker(root as Node, NodeFilter.SHOW_ELEMENT); let found: Element | null = null; let node: Node | null = walker.currentNode as Node; while (node) { const el = node as Element; if (el.children.length === 0 && (!tag || el.tagName.toLowerCase() === tag)) { const txt = (el.textContent ?? '').trim().toLowerCase(); if (txt.length > 0 && norm.includes(txt)) { found = el; break; } } node = walker.nextNode(); } return found; } export function isHome(): boolean { return location.pathname === '/' || location.pathname === ''; } export function isLogin(): boolean { return location.pathname.startsWith('/login'); } export function isInGame(): boolean { return !!document.querySelector(SITE.overlay); } export function isLobbyList(): boolean { // Heuristic: home rendered in browse-mode shows lobby list return isHome() && !isInGame() && !!document.querySelector('.bb-lobby-card, [data-bb-lobby]'); }