@gengage/assistant-fe 0.3.12 → 0.3.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assistant-fe.css +1 -1
- package/dist/chat/components/ChatDrawer.d.ts +6 -0
- package/dist/chat/components/ChatDrawer.d.ts.map +1 -1
- package/dist/chat/index.d.ts +1 -0
- package/dist/chat/index.d.ts.map +1 -1
- package/dist/chat/stream-error-display.d.ts +7 -0
- package/dist/chat/stream-error-display.d.ts.map +1 -0
- package/dist/chat-CET9ibDo.cjs +15 -0
- package/dist/chat-CET9ibDo.cjs.map +1 -0
- package/dist/{chat-DotV4wWS.js → chat-D7c9emMm.js} +507 -392
- package/dist/chat-D7c9emMm.js.map +1 -0
- package/dist/chat.cjs +1 -1
- package/dist/chat.iife.js +8 -6
- package/dist/chat.iife.js.map +1 -1
- package/dist/chat.js +1 -1
- package/dist/common/protocol-adapter.d.ts.map +1 -1
- package/dist/{common-DMw7ug2l.cjs → common-C0QxGDX5.cjs} +2 -2
- package/dist/{common-DMw7ug2l.cjs.map → common-C0QxGDX5.cjs.map} +1 -1
- package/dist/{common-BXGv2eQ-.js → common-Dtx5Bgu_.js} +3 -3
- package/dist/{common-BXGv2eQ-.js.map → common-Dtx5Bgu_.js.map} +1 -1
- package/dist/common.cjs +1 -1
- package/dist/common.js +4 -4
- package/dist/index.cjs +1 -1
- package/dist/index.js +6 -6
- package/dist/{native-webview-CpzrpNOT.cjs → native-webview-BlmrmhV_.cjs} +2 -2
- package/dist/{native-webview-CpzrpNOT.cjs.map → native-webview-BlmrmhV_.cjs.map} +1 -1
- package/dist/{native-webview-Cusg9-Do.js → native-webview-CRFBySOa.js} +5 -5
- package/dist/{native-webview-Cusg9-Do.js.map → native-webview-CRFBySOa.js.map} +1 -1
- package/dist/native.cjs +1 -1
- package/dist/native.iife.js +16 -14
- package/dist/native.iife.js.map +1 -1
- package/dist/native.js +1 -1
- package/dist/qna/components/TextInput.d.ts.map +1 -1
- package/dist/qna-DvgpFom7.cjs +2 -0
- package/dist/qna-DvgpFom7.cjs.map +1 -0
- package/dist/{qna-ybyRS6Ld.js → qna-c7t5suwI.js} +74 -53
- package/dist/qna-c7t5suwI.js.map +1 -0
- package/dist/qna.cjs +1 -1
- package/dist/qna.css +1 -1
- package/dist/qna.iife.js +12 -12
- package/dist/qna.iife.js.map +1 -1
- package/dist/qna.js +1 -1
- package/dist/{schemas-C6SfDfYA.cjs → schemas-C-giAkv4.cjs} +2 -2
- package/dist/schemas-C-giAkv4.cjs.map +1 -0
- package/dist/{schemas-ErdIv_5c.js → schemas-DUid6HBM.js} +7 -7
- package/dist/schemas-DUid6HBM.js.map +1 -0
- package/dist/{simrel-CQWwR6lP.js → simrel-1J9Ph5k5.js} +2 -2
- package/dist/{simrel-CQWwR6lP.js.map → simrel-1J9Ph5k5.js.map} +1 -1
- package/dist/{simrel-cBtT9MB3.cjs → simrel-CT26hFlJ.cjs} +2 -2
- package/dist/{simrel-cBtT9MB3.cjs.map → simrel-CT26hFlJ.cjs.map} +1 -1
- package/dist/simrel.cjs +1 -1
- package/dist/simrel.iife.js +1 -1
- package/dist/simrel.iife.js.map +1 -1
- package/dist/simrel.js +1 -1
- package/package.json +1 -1
- package/dist/chat-DlpBnJVz.cjs +0 -13
- package/dist/chat-DlpBnJVz.cjs.map +0 -1
- package/dist/chat-DotV4wWS.js.map +0 -1
- package/dist/qna-C-ZYmCnM.cjs +0 -2
- package/dist/qna-C-ZYmCnM.cjs.map +0 -1
- package/dist/qna-ybyRS6Ld.js.map +0 -1
- package/dist/schemas-C6SfDfYA.cjs.map +0 -1
- package/dist/schemas-ErdIv_5c.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chat-CET9ibDo.cjs","names":[],"sources":["../src/common/uuidv7.ts","../src/common/communication-bridge.ts","../src/common/action-router.ts","../src/chat/attachment-utils.ts","../src/chat/api.ts","../src/chat/utils/chat-presentation-debug.ts","../src/chat/utils/get-chat-scroll-element.ts","../src/chat/locales/tr.ts","../src/chat/locales/en.ts","../src/chat/locales/index.ts","../src/common/voice-input.ts","../src/chat/components/KvkkBanner.ts","../src/chat/components/PanelTopBar.ts","../src/chat/components/ThumbnailsColumn.ts","../src/chat/components/ChatDrawer.ts","../src/chat/components/Launcher.ts","../src/common/tts-player.ts","../src/chat/components/ComparisonTable.ts","../src/chat/utils/ui.ts","../src/chat/components/ReviewHighlights.ts","../src/chat/components/AITopPicks.ts","../src/chat/components/GroundingReviewCard.ts","../src/chat/components/AIGroupingCards.ts","../src/chat/components/AISuggestedSearchCards.ts","../src/chat/components/FloatingComparisonButton.ts","../src/chat/components/ProsAndCons.ts","../src/chat/components/CategoriesContainer.ts","../src/chat/components/HandoffNotice.ts","../src/chat/components/ProductSummaryCard.ts","../src/chat/components/renderUISpec.ts","../src/chat/components/typewriter.ts","../src/chat/components/productMentionLinker.ts","../src/chat/components/actionClassifier.ts","../src/chat/components/ChoicePrompter.ts","../src/common/indexed-db.ts","../src/chat/extendedModeManager.ts","../src/chat/panel-manager.ts","../src/chat/session-persistence.ts","../src/chat/chat-presentation-state.ts","../src/chat/stream-error-display.ts","../src/chat/kvkk.ts","../src/chat/components/chat.css?inline","../src/chat/catalog.ts","../src/chat/index.ts"],"sourcesContent":["/**\n * Zero-dependency UUIDv7 generator.\n *\n * 48-bit Unix-ms timestamp + random bits, version nibble 0x7, variant 0b10.\n * Lexicographically sortable — critical for thread ordering (threadA < threadB).\n */\n\nexport function uuidv7(): string {\n const now = Date.now();\n const bytes = new Uint8Array(16);\n crypto.getRandomValues(bytes);\n\n // 48-bit timestamp (big-endian) in bytes 0–5\n bytes[0] = (now / 2 ** 40) & 0xff;\n bytes[1] = (now / 2 ** 32) & 0xff;\n bytes[2] = (now / 2 ** 24) & 0xff;\n bytes[3] = (now / 2 ** 16) & 0xff;\n bytes[4] = (now / 2 ** 8) & 0xff;\n bytes[5] = now & 0xff;\n\n // Version 7: set high nibble of byte 6\n bytes[6] = (bytes[6]! & 0x0f) | 0x70;\n\n // Variant 10xx: set high bits of byte 8\n bytes[8] = (bytes[8]! & 0x3f) | 0x80;\n\n const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join('');\n return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;\n}\n","/**\n * Two-way communication bridge between the host page and embedded widgets.\n *\n * Host -> Widget: The host sends `window.postMessage({ gengage, type, payload })`.\n * The bridge validates the origin, checks the namespace, and routes to handlers.\n *\n * Widget -> Host: The bridge dispatches a `CustomEvent('gengage:bridge:message')`\n * on window so the host can listen without tight coupling.\n *\n * Built-in message types:\n * - 'addToCart' -- host confirms cart addition\n * - 'productFavorite' -- widget → host: product card heart toggled (payload: sku, product, favorited, sessionId)\n * - 'navigate' -- host navigates to URL\n * - 'openChat' -- programmatic open\n * - 'closeChat' -- programmatic close\n * - 'getContext' -- host requests current context\n */\n\nexport interface BridgeMessage {\n type: string;\n payload?: unknown;\n}\n\nexport interface CommunicationBridgeOptions {\n /** Widget namespace for message identification (e.g. 'chat', 'qna'). */\n namespace: string;\n /** Allowed origins for postMessage security (default: ['*']). */\n allowedOrigins?: string[];\n /** Callback when a message is received from the host. */\n onMessage?: (msg: BridgeMessage) => void;\n}\n\ntype BridgeHandler = (payload: unknown) => void;\n\nexport class CommunicationBridge {\n private readonly _namespace: string;\n private readonly _allowedOrigins: readonly string[];\n private readonly _onMessage: ((msg: BridgeMessage) => void) | undefined;\n private readonly _handlers = new Map<string, Set<BridgeHandler>>();\n private readonly _messageListener: (event: MessageEvent) => void;\n private _destroyed = false;\n\n constructor(options: CommunicationBridgeOptions) {\n this._namespace = options.namespace;\n // Default to same-origin for postMessage security. Customers who need\n // cross-origin iframe communication can pass allowedOrigins: ['*'] or a\n // specific list of origins via ChatWidgetConfig.allowedOrigins.\n this._allowedOrigins = options.allowedOrigins ?? [location.origin];\n this._onMessage = options.onMessage;\n\n if (this._allowedOrigins.includes('*') && _isDevMode()) {\n console.warn('[gengage] postMessage bridge using wildcard origin. Set allowedOrigins for production security.');\n }\n\n this._messageListener = (event: MessageEvent) => this._handlePostMessage(event);\n window.addEventListener('message', this._messageListener);\n }\n\n /** Send a message to the host page via CustomEvent on window. */\n send(type: string, payload?: unknown): void {\n if (this._destroyed) return;\n\n const detail: { namespace: string; type: string; payload?: unknown } = {\n namespace: this._namespace,\n type,\n };\n if (payload !== undefined) {\n detail.payload = payload;\n }\n\n window.dispatchEvent(\n new CustomEvent('gengage:bridge:message', {\n detail,\n bubbles: false,\n }),\n );\n }\n\n /**\n * Register a handler for a specific message type.\n * Returns an unsubscribe function.\n */\n on(type: string, handler: BridgeHandler): () => void {\n if (!this._handlers.has(type)) {\n this._handlers.set(type, new Set());\n }\n // The Map.get is guaranteed non-null after the set above\n const handlers = this._handlers.get(type)!;\n handlers.add(handler);\n\n return () => {\n handlers.delete(handler);\n if (handlers.size === 0) {\n this._handlers.delete(type);\n }\n };\n }\n\n /** Clean up all event listeners and handlers. */\n destroy(): void {\n if (this._destroyed) return;\n this._destroyed = true;\n window.removeEventListener('message', this._messageListener);\n this._handlers.clear();\n }\n\n // ---------------------------------------------------------------------------\n // Private\n // ---------------------------------------------------------------------------\n\n private _handlePostMessage(event: MessageEvent): void {\n if (this._destroyed) return;\n\n // Validate origin\n if (!this._isOriginAllowed(event.origin)) return;\n\n // Validate message shape: must be an object with { gengage, type }\n const data: unknown = event.data;\n if (!isValidBridgeData(data)) return;\n\n // Only process messages targeting this namespace\n if (data.gengage !== this._namespace) return;\n\n const msg: BridgeMessage = { type: data.type };\n if (data.payload !== undefined) {\n msg.payload = data.payload;\n }\n\n // Invoke the general onMessage callback\n this._onMessage?.(msg);\n\n // Route to type-specific handlers\n const handlers = this._handlers.get(msg.type);\n if (handlers) {\n for (const handler of handlers) {\n handler(msg.payload);\n }\n }\n }\n\n private _isOriginAllowed(origin: string): boolean {\n if (this._allowedOrigins.includes('*')) return true;\n return this._allowedOrigins.includes(origin);\n }\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/** Shape guard for incoming postMessage data. */\nfunction isValidBridgeData(data: unknown): data is { gengage: string; type: string; payload?: unknown } {\n if (typeof data !== 'object' || data === null) return false;\n const obj = data as Record<string, unknown>;\n return typeof obj['gengage'] === 'string' && typeof obj['type'] === 'string';\n}\n\n/**\n * Returns true when running in a development build.\n * Uses process.env.NODE_ENV (injected by Vite/bundlers for all output formats)\n * instead of import.meta.env which is ESM-only and triggers CJS build warnings.\n */\nfunction _isDevMode(): boolean {\n try {\n return typeof process !== 'undefined' && process.env?.['NODE_ENV'] !== 'production';\n } catch {\n return false;\n }\n}\n","import type { UnknownActionPolicy } from './config-schema.js';\nimport type { ActionPayload, AddToCartParams, StreamEventAction } from './types.js';\nimport { isSafeUrl } from './safe-html.js';\nimport { debugLog } from './debug.js';\n\nexport interface HostActionHandlers {\n openChat?: (payload?: ActionPayload | unknown) => void;\n navigate?: (params: { url: string; newTab?: boolean }) => void;\n saveSession?: (params: { sessionId: string; sku: string }) => void;\n addToCart?: (params: AddToCartParams) => void;\n scriptCall?: (params: { name: string; payload?: Record<string, unknown> }) => void;\n unknown?: (action: StreamEventAction['action']) => void;\n}\n\nexport interface ActionRouterOptions {\n allowScriptCall?: boolean;\n unknownActionPolicy?: UnknownActionPolicy;\n logger?: Pick<Console, 'warn' | 'error' | 'debug'>;\n}\n\nconst defaultLogger: Pick<Console, 'warn' | 'error' | 'debug'> = console;\n\nexport function routeStreamAction(\n event: StreamEventAction,\n handlers: HostActionHandlers,\n options: ActionRouterOptions = {},\n): void {\n const action = event.action;\n const logger = options.logger ?? defaultLogger;\n debugLog('action', `routing action: ${action.kind}`, action);\n\n switch (action.kind) {\n case 'open_chat': {\n handlers.openChat?.(action.payload);\n return;\n }\n case 'navigate': {\n if (typeof action.url !== 'string') {\n handleUnknownAction(action, handlers, options, logger);\n return;\n }\n if (!isSafeUrl(action.url)) {\n logger.warn('[gengage] Blocked navigation to unsafe URL:', action.url);\n return;\n }\n const newTab = typeof action.newTab === 'boolean' ? action.newTab : undefined;\n if (handlers.navigate) {\n handlers.navigate({ url: action.url, ...(newTab !== undefined && { newTab }) });\n return;\n }\n defaultNavigate(action.url, newTab);\n return;\n }\n case 'save_session': {\n if (typeof action.sessionId !== 'string' || typeof action.sku !== 'string') {\n handleUnknownAction(action, handlers, options, logger);\n return;\n }\n handlers.saveSession?.({ sessionId: action.sessionId, sku: action.sku });\n return;\n }\n case 'add_to_cart': {\n if (\n typeof action.sku !== 'string' ||\n typeof action.quantity !== 'number' ||\n typeof action.cartCode !== 'string'\n ) {\n handleUnknownAction(action, handlers, options, logger);\n return;\n }\n handlers.addToCart?.({\n sku: action.sku,\n quantity: action.quantity,\n cartCode: action.cartCode,\n });\n return;\n }\n case 'script_call': {\n if (options.allowScriptCall === false) {\n handleUnknownAction(action, handlers, options, logger);\n return;\n }\n if (typeof action.name !== 'string') {\n handleUnknownAction(action, handlers, options, logger);\n return;\n }\n const payload = isRecord(action.payload) ? action.payload : undefined;\n handlers.scriptCall?.({ name: action.name, ...(payload !== undefined && { payload }) });\n return;\n }\n default: {\n handleUnknownAction(action, handlers, options, logger);\n }\n }\n}\n\nfunction handleUnknownAction(\n action: StreamEventAction['action'],\n handlers: HostActionHandlers,\n options: ActionRouterOptions,\n logger: Pick<Console, 'warn' | 'error' | 'debug'>,\n): void {\n const policy = options.unknownActionPolicy ?? 'log-and-ignore';\n if (policy === 'delegate') {\n handlers.unknown?.(action);\n if (!handlers.unknown) {\n logger.warn('[gengage] Unknown action received without delegate handler', action);\n }\n return;\n }\n\n if (policy === 'throw') {\n throw new Error(`[gengage] Unknown action kind: ${(action as { kind?: unknown }).kind}`);\n }\n\n logger.warn('[gengage] Unknown action ignored', action);\n}\n\nfunction defaultNavigate(url: string, newTab?: boolean): void {\n if (typeof window === 'undefined') return;\n if (!isSafeUrl(url)) {\n console.warn('[gengage] Blocked navigation to unsafe URL:', url);\n return;\n }\n if (newTab) {\n window.open(url, '_blank', 'noopener,noreferrer');\n return;\n }\n window.location.href = url;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n","export const ALLOWED_MIME_TYPES = ['image/jpeg', 'image/png', 'image/webp'] as const;\nexport const MAX_FILE_SIZE = 5 * 1024 * 1024; // 5 MB\n\nexport type ValidationResult = { ok: true } | { ok: false; reason: 'invalid_type' | 'too_large' };\n\nexport function validateImageFile(file: File): ValidationResult {\n if (!(ALLOWED_MIME_TYPES as readonly string[]).includes(file.type)) {\n return { ok: false, reason: 'invalid_type' };\n }\n if (file.size > MAX_FILE_SIZE) {\n return { ok: false, reason: 'too_large' };\n }\n return { ok: true };\n}\n","import { consumeStream } from '../common/streaming.js';\nimport { adaptBackendEvent } from '../common/protocol-adapter.js';\nimport { buildChatEndpointUrl } from '../common/api-paths.js';\nimport type { StreamEvent, UISpec, PageContext } from '../common/types.js';\nimport type { ChatTransportConfig } from '../common/api-paths.js';\n\nexport interface BackendRequestMeta {\n outputLanguage: string;\n parentUrl: string;\n windowWidth: string;\n windowHeight: string;\n selfUrl: string;\n id: string;\n userId: string;\n appId: string;\n threads: unknown[];\n createdAt: string;\n kvkkApproved: boolean;\n voiceEnabled: boolean;\n threadId: string;\n isControlGroup: boolean;\n isMobile: boolean;\n viewId?: string;\n}\n\nexport interface ProcessActionRequest {\n account_id: string;\n session_id: string;\n correlation_id: string;\n user_id?: string;\n view_id?: string;\n\n /**\n * @deprecated Use top-level `type` and `payload` instead.\n * Kept for one release cycle of backward compatibility.\n */\n action?: {\n title: string;\n type: string;\n payload?: unknown;\n };\n\n /** Backend action type identifier (preferred over `action.type`). */\n type?: string;\n /** Arbitrary action data passed to the backend (preferred over `action.payload`). */\n payload?: unknown;\n\n sku?: string;\n page_type?: string;\n locale?: string;\n meta?: BackendRequestMeta;\n context?: {\n messages?: Array<{ role: string; content: string }>;\n [key: string]: unknown;\n };\n}\n\nexport interface ActionEnrichmentContext {\n pageContext?: PageContext | undefined;\n backendContext?: import('../common/types.js').BackendContext | null | undefined;\n isMobile?: boolean | undefined;\n}\n\n/**\n * Enriches action payloads with fields the backend expects.\n * Only adds fields that are not already present in the payload.\n */\nexport function enrichActionPayload(\n action: { title: string; type: string; payload?: unknown },\n ctx: ActionEnrichmentContext,\n): { title: string; type: string; payload?: unknown } {\n const type = action.type;\n const existing =\n action.payload != null && typeof action.payload === 'object' && !Array.isArray(action.payload)\n ? (action.payload as Record<string, unknown>)\n : {};\n\n // Helper: only set if not already present\n const merge = (additions: Record<string, unknown>): Record<string, unknown> => {\n const result = { ...existing };\n for (const [key, value] of Object.entries(additions)) {\n if (!(key in result)) {\n result[key] = value;\n }\n }\n return result;\n };\n\n switch (type) {\n case 'inputText': {\n const additions: Record<string, unknown> = {\n is_launcher: 0,\n };\n if (ctx.pageContext?.extra) additions['page_details'] = ctx.pageContext.extra;\n // is_suggested_text may already be set by adapter — don't overwrite\n if (!('is_suggested_text' in existing)) additions['is_suggested_text'] = 0;\n return { ...action, payload: merge(additions) };\n }\n\n case 'findSimilar': {\n const additions: Record<string, unknown> = {\n is_launcher: 0,\n };\n if (action.title) {\n additions['text'] = action.title;\n additions['input'] = action.title;\n }\n return { ...action, payload: merge(additions) };\n }\n\n case 'getComparisonTable': {\n // sku_list should already be set; no-op if present\n return action;\n }\n\n case 'addToCart': {\n const additions: Record<string, unknown> = {};\n if (!('error_message' in existing)) additions['error_message'] = '';\n return { ...action, payload: merge(additions) };\n }\n\n case 'reviewSummary': {\n const additions: Record<string, unknown> = {};\n if (ctx.pageContext?.sku && !('sku' in existing)) {\n additions['sku'] = ctx.pageContext.sku;\n }\n if (Object.keys(additions).length === 0) return action;\n return { ...action, payload: merge(additions) };\n }\n\n default:\n return action;\n }\n}\n\nexport interface StreamCallbacks {\n onTextChunk: (\n content: string,\n isFinal: boolean,\n extra?: {\n productMentions?: Array<{ sku: string; short_name: string }> | undefined;\n skuToProductItem?: Record<string, Record<string, unknown>> | undefined;\n conversationMode?: string | undefined;\n },\n ) => void;\n onUISpec: (spec: UISpec, widget: string, panelHint?: 'panel', clearPanel?: boolean) => void;\n onAction: (event: StreamEvent) => void;\n onMetadata: (event: StreamEvent) => void;\n onError: (err: Error) => void;\n onDone: () => void;\n}\n\n/**\n * Action type mapping.\n * The backend's ActionType enum uses `inputText` for user messages,\n * while the frontend uses `user_message`. Map at the boundary.\n */\nconst ACTION_TYPE_MAP: Record<string, string> = {\n user_message: 'inputText',\n};\n\n/**\n * Builds the request body for `/chat/process_action`.\n *\n * Backend expects `type` and `payload` at the top level —\n * matching chat_api.py's current schema.\n */\nfunction buildRequestBody(request: ProcessActionRequest): string {\n const { action, type: flatType, payload: flatPayload, ...rest } = request;\n // Prefer top-level type/payload; fall back to deprecated action wrapper.\n const rawType = flatType ?? action?.type ?? 'inputText';\n const rawPayload = flatPayload ?? action?.payload;\n const mappedType = ACTION_TYPE_MAP[rawType] ?? rawType;\n const body: Record<string, unknown> = {\n ...rest,\n type: mappedType,\n };\n if (rawPayload !== undefined) {\n // Backend expects payload as an object. Wrap string payloads for compatibility.\n body.payload = typeof rawPayload === 'string' ? { text: rawPayload } : rawPayload;\n }\n return JSON.stringify(body);\n}\n\nexport function sendChatMessage(\n request: ProcessActionRequest,\n callbacks: StreamCallbacks,\n transport: ChatTransportConfig,\n): AbortController {\n const url = buildChatEndpointUrl('process_action', transport);\n const controller = new AbortController();\n\n const run = async (): Promise<void> => {\n try {\n const requestBody = buildRequestBody(request);\n\n // Use FormData only when an attachment is present; otherwise send JSON.\n const useFormData = transport.attachment !== undefined;\n\n let fetchInit: RequestInit;\n if (useFormData) {\n const formData = new FormData();\n formData.append('request', requestBody);\n if (transport.attachment !== undefined) {\n formData.append('attachment', transport.attachment);\n }\n fetchInit = {\n method: 'POST',\n body: formData,\n signal: controller.signal,\n };\n } else {\n fetchInit = {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: requestBody,\n signal: controller.signal,\n };\n }\n\n const response = await fetch(url, fetchInit);\n\n if (!response.ok) {\n let detail = response.statusText;\n try {\n const body = await response.json();\n const msg =\n (body as Record<string, unknown>).detail ??\n (body as Record<string, unknown>).message ??\n (body as Record<string, unknown>).error;\n if (typeof msg === 'string') detail = msg;\n } catch {\n /* body not JSON — keep statusText */\n }\n callbacks.onError(new Error(`HTTP ${response.status}: ${detail}`));\n return;\n }\n\n // Guard against double-fire: onDone must fire exactly once\n let doneFired = false;\n const fireDone = () => {\n if (doneFired) return;\n doneFired = true;\n callbacks.onDone();\n };\n\n await consumeStream(response, {\n onEvent: (event: StreamEvent) => {\n const normalized = adaptBackendEvent(event as unknown as Record<string, unknown>);\n\n if (!normalized) return;\n\n switch (normalized.type) {\n case 'text_chunk':\n callbacks.onTextChunk(normalized.content, normalized.final === true, {\n productMentions: normalized.productMentions,\n skuToProductItem: normalized.skuToProductItem,\n conversationMode: normalized.conversationMode,\n });\n break;\n case 'ui_spec':\n callbacks.onUISpec(\n normalized.spec,\n normalized.widget,\n normalized.panelHint,\n normalized.clearPanel === true,\n );\n break;\n case 'action':\n callbacks.onAction(normalized);\n break;\n case 'metadata':\n callbacks.onMetadata(normalized);\n break;\n case 'error':\n callbacks.onError(new Error(normalized.message));\n break;\n case 'done':\n fireDone();\n break;\n }\n },\n onError: callbacks.onError,\n signal: controller.signal,\n });\n\n // Fallback: if stream completed without a chatStreamEnd event (backend\n // bug or truncated response), ensure onDone fires so the widget doesn't\n // get stuck with a spinning typing indicator.\n fireDone();\n } catch (err) {\n if (err instanceof DOMException && err.name === 'AbortError') return;\n callbacks.onError(err instanceof Error ? err : new Error(String(err)));\n }\n };\n\n void run();\n return controller;\n}\n","/**\n * Opt-in debug logging for chat presentation (query ?chat_debug=1 or localStorage gengage_chat_debug=1).\n */\n\nconst DEBUG_QUERY_KEYS = ['chat_debug', 'presentation_debug'];\nconst DEBUG_STORAGE_KEYS = ['gengage_chat_debug', 'gengage_presentation_debug'];\n\ntype Entry = { seq: number; time: string; scope: string; message: string; payload?: unknown };\n\ndeclare global {\n interface Window {\n __gengageChatPresentationDebugLog?: Entry[];\n __gengageChatPresentationDebugSeq?: number;\n }\n}\n\nexport function isChatPresentationDebugEnabled(): boolean {\n if (typeof window === 'undefined') return false;\n const params = new URLSearchParams(window.location.search);\n if (DEBUG_QUERY_KEYS.some((k) => params.get(k) === '1')) return true;\n try {\n return DEBUG_STORAGE_KEYS.some((k) => window.localStorage.getItem(k) === '1');\n } catch {\n return false;\n }\n}\n\nexport function logChatPresentation(scope: string, message: string, payload?: unknown): void {\n if (!isChatPresentationDebugEnabled()) return;\n const seq = (window.__gengageChatPresentationDebugSeq ?? 0) + 1;\n window.__gengageChatPresentationDebugSeq = seq;\n const entry: Entry = { seq, time: new Date().toISOString(), scope, message, payload };\n if (!window.__gengageChatPresentationDebugLog) window.__gengageChatPresentationDebugLog = [];\n window.__gengageChatPresentationDebugLog.push(entry);\n if (window.__gengageChatPresentationDebugLog.length > 400) window.__gengageChatPresentationDebugLog.shift();\n const prefix = `[gengage-chat-debug #${seq}][${scope}] ${message}`;\n // eslint-disable-next-line no-console\n if (payload === undefined) console.debug(prefix);\n // eslint-disable-next-line no-console\n else console.debug(prefix, payload);\n}\n","/**\n * Resolves the chat transcript scroll container (messages pane).\n * Prefers a widget-registered element (Shadow DOM–safe); falls back to #gengage-chat-scroll in light DOM.\n */\n\nimport { logChatPresentation } from './chat-presentation-debug.js';\n\n/** DOM id on the transcript scroller — stable for host tooling */\nexport const CHAT_SCROLL_ELEMENT_ID = 'gengage-chat-scroll';\n\nlet registered: HTMLElement | null = null;\n\n/** Called when the chat drawer mounts / updates its messages scroller */\nexport function registerChatScrollElement(el: HTMLElement | null): void {\n registered = el;\n}\n\nexport function getChatScrollElement(): HTMLElement | null {\n if (registered && registered.isConnected) {\n return registered;\n }\n registered = null;\n\n const light = document.getElementById(CHAT_SCROLL_ELEMENT_ID);\n if (light) {\n return light;\n }\n\n logChatPresentation('chat-scroll', 'failed to resolve scroll container (not registered)', {\n id: CHAT_SCROLL_ELEMENT_ID,\n });\n return null;\n}\n\nexport function invalidateChatScrollCache(): void {\n registered = null;\n}\n","import type { ChatI18n } from '../types.js';\n\nexport const CHAT_I18N_TR: ChatI18n = {\n headerTitle: 'Ürün Uzmanı',\n inputPlaceholder: 'Ürün ara, soru sor',\n sendButton: 'Gönder',\n closeButton: 'Kapat',\n openButton: 'Sohbeti aç',\n newChatButton: 'Yeni sohbet',\n poweredBy: 'Gengage ile',\n errorMessage: 'Bir hata oluştu. Lütfen tekrar deneyin.',\n retryButton: 'Tekrar Dene',\n loadingMessage: 'Düşünüyorum...',\n loadingSequenceGeneric: [\n 'Sizin için en önemli noktaları anlıyorum',\n 'Ürünleri, yorumları ve özellikleri inceliyorum',\n 'En güçlü seçenekleri hazırlıyorum',\n ],\n loadingSequencePanel: [\n 'En ilgili detayları inceliyorum',\n 'En güçlü sinyalleri kontrol ediyorum',\n 'Bu görünümü sizin için hazırlıyorum',\n ],\n loadingSequenceComparison: [\n 'Ürün farklarını inceliyorum',\n 'En güçlü farkları ve ödünleşimleri kontrol ediyorum',\n 'Karşılaştırmayı sizin için hazırlıyorum',\n ],\n productCtaLabel: 'Satın Al',\n viewOnSiteLabel: 'Sitede Gör',\n attachImageButton: 'Resim ekle',\n attachMenuSelectPhoto: 'Fotoğraf Seç',\n attachMenuPaste: 'Yapıştır',\n clipboardNoImageMessage: 'Panoda görsel bulunamadı. Önce bir görsel kopyalayın veya dosya seçin.',\n removeAttachmentButton: 'Resmi kaldır',\n invalidFileType: 'Sadece JPEG, PNG ve WebP dosyaları destekleniyor.',\n fileTooLarge: \"Dosya boyutu 5 MB'dan küçük olmalıdır.\",\n aiTopPicksTitle: 'Sizin İçin En İyiler',\n roleWinner: 'En Beğendiğim',\n roleBestValue: 'En Uygun Fiyatlı',\n roleBestAlternative: 'En İyi Alternatif',\n viewDetails: 'Detayları Gör',\n groundingReviewCta: 'Yorumları Oku',\n groundingReviewSubtitle: '{count} yorum mevcut',\n variantsLabel: 'Varyantlar',\n sortRelated: 'Önerilen',\n sortPriceAsc: 'Fiyat düşükten yükseğe',\n sortPriceDesc: 'Fiyat yüksekten düşüğe',\n sortToolbarAriaLabel: 'Ürünleri sırala',\n compareSelected: 'Karşılaştır',\n compareMinHint: 'En az 2 ürün seçin',\n compareMaxHint: 'En fazla 5 ürün seçebilirsiniz',\n comparisonSelectLabel: 'Karşılaştırmak için seç',\n comparisonSelectedLabel: 'Seçildi',\n comparisonSelectCardHint: 'Karşılaştırmaya eklemek veya çıkarmak için kartın herhangi bir yerine dokunun.',\n comparisonPreparingLabel: 'Karşılaştırma tablosu hazırlanıyor…',\n panelTitleProductDetails: 'Ürün Detayı',\n panelTitleSimilarProducts: 'Benzer Ürünler',\n panelTitleComparisonResults: 'Karşılaştırma Sonuçları',\n panelTitleCategories: 'Kategoriler',\n panelTitleSearchResults: 'Arama Sonuçları',\n inStockLabel: 'Stokta',\n outOfStockLabel: 'Tükendi',\n findSimilarLabel: 'Benzerlerini Bul',\n galleryPrevAriaLabel: 'Önceki görsel',\n galleryNextAriaLabel: 'Sonraki görsel',\n choicePrompterHeading: 'Kararsız mı kaldın?',\n choicePrompterSuggestion: 'Ürünleri seçip karşılaştırabilirsin',\n choicePrompterCta: 'Seç ve Karşılaştır',\n viewMoreLabel: 'Daha Fazla Göster',\n similarProductsLabel: 'Benzer Ürünler',\n addToCartButton: 'Sepete Ekle',\n addedToCartToast: 'Sepete eklendi',\n shareButton: 'Paylaş',\n productInfoTab: 'Ürün Bilgileri',\n specificationsTab: 'Teknik Özellikler',\n recommendedChoiceLabel: 'Önerilen Seçim',\n highlightsLabel: 'Öne Çıkan Özellikler',\n keyDifferencesLabel: 'Temel Farklar',\n specialCasesLabel: 'Özel Durumlar İçin',\n emptyReviewsMessage: 'Yorum özeti bulunamadı.',\n closeAriaLabel: 'Kapat',\n startChatLabel: 'Sohbete Başla',\n voiceButton: 'Sesli giriş',\n voiceListening: 'Dinleniyor...',\n voiceNotSupported: 'Sesli giriş bu tarayıcıda desteklenmiyor.',\n voicePermissionDenied: 'Mikrofon erişimi reddedildi.',\n voiceError: 'Sesli giriş hatası.',\n handoffHeading: 'Destek temsilcisine aktarılıyor',\n productNotFoundMessage: 'Bu ürün bilgisi şu an kullanılamıyor. Başka bir konuda yardımcı olabilirim.',\n stopGenerating: 'Yanıtı Durdur',\n offlineMessage: 'Çevrimdışısınız — bağlantı kurulduğunda mesajlar gönderilecek.',\n cartAriaLabel: 'Sepetim',\n favoritesAriaLabel: 'Favorilerim',\n showPanelAriaLabel: 'Sonuçları Göster',\n addToFavoritesLabel: 'Favorilere ekle',\n customerReviewsTitle: 'Müşteri Yorumları',\n togglePanelAriaLabel: 'Paneli aç/kapat',\n chatMessagesAriaLabel: 'Sohbet mesajları',\n suggestionsAriaLabel: 'Öneriler',\n moreSuggestionsAriaLabel: 'Daha fazla öneri',\n rollbackAriaLabel: 'Bu mesaja geri dön',\n backAriaLabel: 'Geri',\n forwardAriaLabel: 'İleri',\n closePanelAriaLabel: 'Paneli kapat',\n dismissAriaLabel: 'Kapat',\n cartAddErrorMessage: 'Üzgünüm sepete ekleyemedim, bir sorunla karşılaştım.',\n favoriteToggleErrorMessage: 'Favoriler güncellenemedi. Lütfen tekrar deneyin.',\n reviewFilterPositive: 'Olumlu',\n reviewFilterNegative: 'Olumsuz',\n decreaseLabel: 'Azalt',\n increaseLabel: 'Artır',\n reviewCustomersMentionSingular: '1 müşteri bahsediyor',\n reviewCustomersMentionPlural: 'müşteri bahsediyor',\n reviewSubjectsHeading: 'İncelemek için seçin',\n tryAgainButton: 'Tekrar dene',\n askSomethingElseButton: 'Başka bir şey sor',\n accountInactiveMessage: 'Bu hesap şu an aktif değil. Lütfen daha sonra tekrar deneyin.',\n favoritesPageTitle: 'Favorilerim',\n emptyFavoritesMessage: 'Henüz favori ürün yok. Bir ürünü kalp ikonuna tıklayarak favorilere ekleyebilirsin.',\n showFormerMessagesButton: '↑ Önceki mesajları göster',\n aiAnalysisAnalyzingLabel: 'Ürünler analiz ediliyor...',\n aiBrowseCategoriesTitle: 'Kategorilere Göz At',\n};\n","import type { ChatI18n } from '../types.js';\n\nexport const CHAT_I18N_EN: ChatI18n = {\n headerTitle: 'Product Expert',\n inputPlaceholder: 'Search products, ask questions',\n sendButton: 'Send',\n closeButton: 'Close',\n openButton: 'Open chat',\n newChatButton: 'New chat',\n poweredBy: 'Powered by Gengage',\n errorMessage: 'Something went wrong. Please try again.',\n retryButton: 'Retry',\n loadingMessage: 'Thinking...',\n loadingSequenceGeneric: [\n 'Understanding what matters most',\n 'Reviewing products, reviews, and specs',\n 'Preparing the best options to review',\n ],\n loadingSequencePanel: [\n 'Reviewing the most relevant details',\n 'Checking the strongest signals',\n 'Preparing this view for you',\n ],\n loadingSequenceComparison: [\n 'Reviewing the product differences',\n 'Checking the strongest tradeoffs',\n 'Preparing your comparison',\n ],\n productCtaLabel: 'Buy',\n viewOnSiteLabel: 'View on Site',\n attachImageButton: 'Attach image',\n attachMenuSelectPhoto: 'Choose photo',\n attachMenuPaste: 'Paste',\n clipboardNoImageMessage: 'No image found in clipboard. Copy an image first or choose a file.',\n removeAttachmentButton: 'Remove image',\n invalidFileType: 'Only JPEG, PNG and WebP files are supported.',\n fileTooLarge: 'File must be smaller than 5 MB.',\n aiTopPicksTitle: 'Top Picks for You',\n roleWinner: 'Top Pick',\n roleBestValue: 'Best Value',\n roleBestAlternative: 'Best Alternative',\n viewDetails: 'View Details',\n groundingReviewCta: 'Read Reviews',\n groundingReviewSubtitle: '{count} reviews available',\n variantsLabel: 'Variants',\n sortRelated: 'Recommended',\n sortPriceAsc: 'Price low to high',\n sortPriceDesc: 'Price high to low',\n sortToolbarAriaLabel: 'Sort products',\n compareSelected: 'Compare',\n compareMinHint: 'Select at least 2 products',\n compareMaxHint: 'You can select up to 5 products',\n comparisonSelectLabel: 'Select to compare',\n comparisonSelectedLabel: 'Selected',\n comparisonSelectCardHint: 'Tap anywhere on the card to add or remove it from comparison.',\n comparisonPreparingLabel: 'Preparing your comparison…',\n panelTitleProductDetails: 'Product Details',\n panelTitleSimilarProducts: 'Similar Products',\n panelTitleComparisonResults: 'Comparison Results',\n panelTitleCategories: 'Categories',\n panelTitleSearchResults: 'Search Results',\n inStockLabel: 'In Stock',\n outOfStockLabel: 'Out of Stock',\n findSimilarLabel: 'Find Similar',\n galleryPrevAriaLabel: 'Previous image',\n galleryNextAriaLabel: 'Next image',\n choicePrompterHeading: \"Can't decide?\",\n choicePrompterSuggestion: 'Select products to compare them',\n choicePrompterCta: 'Select & Compare',\n viewMoreLabel: 'Show More',\n similarProductsLabel: 'Similar Products',\n addToCartButton: 'Add to Cart',\n addedToCartToast: 'Added to cart',\n shareButton: 'Share',\n productInfoTab: 'Product Info',\n specificationsTab: 'Specifications',\n recommendedChoiceLabel: 'Recommended Choice',\n highlightsLabel: 'Key Highlights',\n keyDifferencesLabel: 'Key Differences',\n specialCasesLabel: 'For Special Cases',\n emptyReviewsMessage: 'No review summary found.',\n closeAriaLabel: 'Close',\n startChatLabel: 'Start Chat',\n voiceButton: 'Voice input',\n voiceListening: 'Listening...',\n voiceNotSupported: 'Voice input is not supported in this browser.',\n voicePermissionDenied: 'Microphone access denied.',\n voiceError: 'Voice input error.',\n handoffHeading: 'Transferring to a support agent',\n productNotFoundMessage: 'Product information is currently unavailable. I can help with something else.',\n stopGenerating: 'Stop generating',\n offlineMessage: \"You're offline — messages will send when you reconnect.\",\n cartAriaLabel: 'My cart',\n favoritesAriaLabel: 'My favorites',\n showPanelAriaLabel: 'Show Results',\n addToFavoritesLabel: 'Add to favorites',\n customerReviewsTitle: 'Customer Reviews',\n togglePanelAriaLabel: 'Toggle panel',\n chatMessagesAriaLabel: 'Chat messages',\n suggestionsAriaLabel: 'Suggestions',\n moreSuggestionsAriaLabel: 'More suggestions',\n rollbackAriaLabel: 'Rollback to this message',\n backAriaLabel: 'Back',\n forwardAriaLabel: 'Forward',\n closePanelAriaLabel: 'Close panel',\n dismissAriaLabel: 'Dismiss',\n cartAddErrorMessage: \"Sorry, I couldn't add that to your cart. Something went wrong.\",\n favoriteToggleErrorMessage: \"We couldn't update favorites. Please try again.\",\n reviewFilterPositive: 'Positive',\n reviewFilterNegative: 'Negative',\n decreaseLabel: 'Decrease',\n increaseLabel: 'Increase',\n reviewCustomersMentionSingular: '1 customer mentions',\n reviewCustomersMentionPlural: 'customers mention',\n reviewSubjectsHeading: 'Select to learn more',\n tryAgainButton: 'Try again',\n askSomethingElseButton: 'Ask something else',\n accountInactiveMessage: 'This account is currently inactive. Please try again later.',\n favoritesPageTitle: 'My Favorites',\n emptyFavoritesMessage: 'No favorites yet. Heart a product to save it here.',\n showFormerMessagesButton: '↑ Show earlier messages',\n aiAnalysisAnalyzingLabel: 'Analyzing products...',\n aiBrowseCategoriesTitle: 'Browse categories',\n};\n","import type { ChatI18n } from '../types.js';\nimport { CHAT_I18N_TR } from './tr.js';\nimport { CHAT_I18N_EN } from './en.js';\n\nfunction normalizeLocale(locale?: string): string {\n if (!locale) return 'tr';\n return locale.toLowerCase().split('-')[0] ?? 'tr';\n}\n\nexport function resolveChatLocale(locale?: string): ChatI18n {\n switch (normalizeLocale(locale)) {\n case 'en':\n return CHAT_I18N_EN;\n default:\n return CHAT_I18N_TR;\n }\n}\n\nexport { CHAT_I18N_TR, CHAT_I18N_EN };\n","/**\n * Browser-native Web Speech API voice input.\n *\n * Uses the SpeechRecognition API for real-time speech-to-text.\n * The frontend sends transcribed text directly — no audio blobs\n * are sent to the backend. This replaces server-side Groq Whisper.\n *\n * Supports:\n * - Real-time transcription with interim results\n * - Turkish (`tr-TR`) and English (`en-US`) language support\n * - Auto-submit on silence (configurable timeout)\n * - Microphone permission handling with descriptive errors\n *\n * Browser support:\n * - Chrome 33+, Edge 79+, Safari 14.1+ (via webkitSpeechRecognition)\n * - Firefox: NOT SUPPORTED (no SpeechRecognition API)\n */\n\n// ---------------------------------------------------------------------------\n// Web Speech API types (not in lib.dom.d.ts for all TS targets)\n// ---------------------------------------------------------------------------\n\ninterface SpeechRecognitionEvent extends Event {\n readonly resultIndex: number;\n readonly results: SpeechRecognitionResultList;\n}\n\ninterface SpeechRecognitionResultList {\n readonly length: number;\n item(index: number): SpeechRecognitionResult;\n [index: number]: SpeechRecognitionResult;\n}\n\ninterface SpeechRecognitionResult {\n readonly isFinal: boolean;\n readonly length: number;\n item(index: number): SpeechRecognitionAlternative;\n [index: number]: SpeechRecognitionAlternative;\n}\n\ninterface SpeechRecognitionAlternative {\n readonly transcript: string;\n readonly confidence: number;\n}\n\ninterface SpeechRecognitionErrorEvent extends Event {\n readonly error: string;\n readonly message: string;\n}\n\ninterface SpeechRecognitionConstructor {\n new (): SpeechRecognitionInstance;\n}\n\ninterface SpeechRecognitionInstance extends EventTarget {\n continuous: boolean;\n interimResults: boolean;\n lang: string;\n maxAlternatives: number;\n onresult: ((event: SpeechRecognitionEvent) => void) | null;\n onerror: ((event: SpeechRecognitionErrorEvent) => void) | null;\n onend: (() => void) | null;\n onstart: (() => void) | null;\n onspeechend: (() => void) | null;\n onaudiostart: (() => void) | null;\n start(): void;\n stop(): void;\n abort(): void;\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\nexport type VoiceInputState = 'idle' | 'listening' | 'error';\n\nexport type VoiceInputErrorCode =\n | 'not-supported'\n | 'not-allowed'\n | 'no-microphone'\n | 'no-speech'\n | 'network'\n | 'aborted'\n | 'unknown';\n\nexport interface VoiceInputCallbacks {\n /** Called with interim transcript while the user speaks. */\n onInterim?: (text: string) => void;\n /** Called with final transcript when a phrase is recognized. */\n onFinal?: (text: string) => void;\n /** Called when auto-submit fires (silence timeout reached with final text). */\n onAutoSubmit?: (text: string) => void;\n /** Called when voice input state changes. */\n onStateChange?: (state: VoiceInputState) => void;\n /** Called on recognition error. */\n onError?: (code: VoiceInputErrorCode, message: string) => void;\n}\n\nexport interface VoiceInputOptions {\n /** BCP 47 language tag. Default: 'tr-TR'. */\n lang?: string;\n /** Silence duration in ms before auto-submit. Default: 1500. */\n silenceTimeoutMs?: number;\n /** Whether to auto-submit on silence. Default: true. */\n autoSubmit?: boolean;\n}\n\n/**\n * Check whether the browser supports the Web Speech API.\n */\nexport function isVoiceInputSupported(): boolean {\n return getSpeechRecognitionConstructor() !== null;\n}\n\nfunction getSpeechRecognitionConstructor(): SpeechRecognitionConstructor | null {\n const w = globalThis as unknown as Record<string, unknown>;\n return (w.SpeechRecognition ?? w.webkitSpeechRecognition ?? null) as SpeechRecognitionConstructor | null;\n}\n\nexport class VoiceInput {\n private recognition: SpeechRecognitionInstance | null = null;\n private _state: VoiceInputState = 'idle';\n private silenceTimer: ReturnType<typeof setTimeout> | null = null;\n private accumulatedTranscript = '';\n private readonly callbacks: VoiceInputCallbacks;\n private readonly lang: string;\n private readonly silenceTimeoutMs: number;\n private readonly autoSubmit: boolean;\n private intentionalStop = false;\n private _lastRestartAt = 0;\n\n constructor(callbacks: VoiceInputCallbacks, options?: VoiceInputOptions) {\n this.callbacks = callbacks;\n this.lang = options?.lang ?? 'tr-TR';\n this.silenceTimeoutMs = options?.silenceTimeoutMs ?? 1500;\n this.autoSubmit = options?.autoSubmit ?? true;\n }\n\n get state(): VoiceInputState {\n return this._state;\n }\n\n /**\n * Start listening. Requests microphone permission on first call.\n */\n start(): void {\n if (this._state === 'listening') return;\n\n const Ctor = getSpeechRecognitionConstructor();\n if (!Ctor) {\n this.setState('error');\n this.callbacks.onError?.('not-supported', 'Web Speech API is not supported in this browser.');\n return;\n }\n\n // Require secure context (HTTPS)\n if (typeof globalThis.isSecureContext !== 'undefined' && !globalThis.isSecureContext) {\n this.setState('error');\n this.callbacks.onError?.('not-allowed', 'Voice input requires HTTPS.');\n return;\n }\n\n this.accumulatedTranscript = '';\n this.intentionalStop = false;\n\n const recognition = new Ctor();\n recognition.continuous = true;\n recognition.interimResults = true;\n recognition.lang = this.lang;\n recognition.maxAlternatives = 1;\n\n recognition.onstart = () => {\n this.setState('listening');\n };\n\n recognition.onresult = (event: SpeechRecognitionEvent) => {\n this.clearSilenceTimer();\n\n let interim = '';\n let latestFinal = '';\n\n for (let i = event.resultIndex; i < event.results.length; i++) {\n const result = event.results[i];\n if (!result) continue;\n const alt = result[0];\n if (!alt) continue;\n if (result.isFinal) {\n latestFinal += alt.transcript;\n } else {\n interim += alt.transcript;\n }\n }\n\n if (latestFinal) {\n this.accumulatedTranscript += latestFinal;\n this.callbacks.onFinal?.(this.accumulatedTranscript);\n }\n\n if (interim) {\n this.callbacks.onInterim?.(this.accumulatedTranscript + interim);\n }\n\n // Start silence timer for auto-submit\n if (this.autoSubmit && this.accumulatedTranscript) {\n this.startSilenceTimer();\n }\n };\n\n recognition.onerror = (event: SpeechRecognitionErrorEvent) => {\n const code = mapErrorCode(event.error);\n // 'no-speech' and 'aborted' during intentional stop are not real errors\n if (this.intentionalStop && (event.error === 'no-speech' || event.error === 'aborted')) {\n return;\n }\n this.setState('error');\n this.callbacks.onError?.(code, event.message || event.error);\n };\n\n recognition.onend = () => {\n this.clearSilenceTimer();\n // Auto-restart if still in listening state (browser may stop recognition arbitrarily)\n if (this._state === 'listening' && !this.intentionalStop) {\n const now = Date.now();\n // Prevent rapid restart loop on Chrome — if onend fires within 500ms\n // of the last restart, the browser is refusing to stay active.\n if (now - this._lastRestartAt < 500) {\n this.setState('idle');\n return;\n }\n this._lastRestartAt = now;\n try {\n recognition.start();\n } catch {\n this.setState('idle');\n }\n return;\n }\n this.setState('idle');\n };\n\n this.recognition = recognition;\n\n try {\n recognition.start();\n } catch {\n this.setState('error');\n this.callbacks.onError?.('unknown', 'Failed to start speech recognition.');\n }\n }\n\n /**\n * Stop listening. Returns the accumulated transcript.\n */\n stop(): string {\n this.intentionalStop = true;\n this.clearSilenceTimer();\n if (this.recognition) {\n try {\n this.recognition.stop();\n } catch {\n // Already stopped\n }\n this.recognition = null;\n }\n this.setState('idle');\n return this.accumulatedTranscript;\n }\n\n /**\n * Abort listening. Discards any accumulated transcript.\n */\n abort(): void {\n this.intentionalStop = true;\n this.clearSilenceTimer();\n this.accumulatedTranscript = '';\n if (this.recognition) {\n try {\n this.recognition.abort();\n } catch {\n // Already aborted\n }\n this.recognition = null;\n }\n this.setState('idle');\n }\n\n /** Destroy the instance and release resources. */\n destroy(): void {\n this.abort();\n }\n\n private setState(state: VoiceInputState): void {\n if (this._state !== state) {\n this._state = state;\n this.callbacks.onStateChange?.(state);\n }\n }\n\n private startSilenceTimer(): void {\n this.clearSilenceTimer();\n this.silenceTimer = setTimeout(() => {\n const text = this.stop();\n if (text.trim()) {\n this.callbacks.onAutoSubmit?.(text.trim());\n }\n }, this.silenceTimeoutMs);\n }\n\n private clearSilenceTimer(): void {\n if (this.silenceTimer !== null) {\n clearTimeout(this.silenceTimer);\n this.silenceTimer = null;\n }\n }\n}\n\nfunction mapErrorCode(error: string): VoiceInputErrorCode {\n switch (error) {\n case 'not-allowed':\n return 'not-allowed';\n case 'no-speech':\n return 'no-speech';\n case 'audio-capture':\n return 'no-microphone';\n case 'network':\n return 'network';\n case 'aborted':\n return 'aborted';\n default:\n return 'unknown';\n }\n}\n","import { sanitizeHtml } from '../../common/safe-html.js';\n\nexport interface KvkkBannerOptions {\n htmlContent: string;\n onDismiss: () => void;\n closeAriaLabel?: string;\n}\n\nexport function createKvkkBanner(options: KvkkBannerOptions): HTMLElement {\n const banner = document.createElement('div');\n banner.className = 'gengage-chat-kvkk-banner gds-evidence-card gds-evidence-card-warning';\n banner.dataset['gengagePart'] = 'kvkk-banner';\n banner.setAttribute('role', 'alert');\n\n const content = document.createElement('div');\n content.className = 'gengage-chat-kvkk-content';\n content.dataset['gengagePart'] = 'kvkk-content';\n content.innerHTML = sanitizeHtml(options.htmlContent);\n banner.appendChild(content);\n\n const dismiss = document.createElement('button');\n dismiss.className = 'gengage-chat-kvkk-dismiss';\n dismiss.dataset['gengagePart'] = 'kvkk-dismiss';\n dismiss.type = 'button';\n dismiss.setAttribute('aria-label', options.closeAriaLabel ?? 'Close privacy notice');\n dismiss.textContent = '\\u00D7';\n dismiss.addEventListener('click', options.onDismiss);\n banner.appendChild(dismiss);\n\n return banner;\n}\n","/**\n * PanelTopBar — navigation bar at the top of the panel pane.\n *\n * Shows back/forward arrow buttons and a title derived from the current\n * panel content type. On mobile a close (✕) button is also rendered so\n * users can dismiss all panel layers with a single tap and return to the\n * conversation, without having to navigate back through every history entry.\n */\n\nexport interface PanelTopBarOptions {\n onBack: () => void;\n onForward: () => void;\n /** Called when the mobile close (✕) button is tapped. Should clear all panel history. */\n onClose?: () => void;\n backAriaLabel?: string;\n forwardAriaLabel?: string;\n /** Aria label for the close button (mobile only). */\n closePanelAriaLabel?: string;\n}\n\nexport class PanelTopBar {\n private _el: HTMLElement;\n private _backBtn: HTMLButtonElement;\n private _forwardBtn: HTMLButtonElement;\n private _titleEl: HTMLElement;\n private _actionsEl: HTMLElement;\n private _closeBtn: HTMLButtonElement;\n\n constructor(options: PanelTopBarOptions) {\n this._el = document.createElement('div');\n this._el.className = 'gengage-chat-panel-topbar gds-toolbar';\n this._el.dataset['gengagePart'] = 'panel-topbar';\n\n this._backBtn = document.createElement('button');\n this._backBtn.className = 'gengage-chat-panel-topbar-back gds-btn gds-btn-ghost gds-icon-btn';\n this._backBtn.dataset['gengagePart'] = 'panel-topbar-back';\n this._backBtn.type = 'button';\n this._backBtn.disabled = true;\n this._backBtn.setAttribute('aria-label', options.backAriaLabel ?? 'Back');\n this._backBtn.title = options.backAriaLabel ?? 'Back';\n this._backBtn.innerHTML =\n '<svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"m15 18-6-6 6-6\"/><path d=\"M21 12H9\"/></svg>';\n this._backBtn.addEventListener('click', () => options.onBack());\n\n const titleWrap = document.createElement('div');\n titleWrap.className = 'gengage-chat-panel-topbar-title-wrap';\n\n this._titleEl = document.createElement('span');\n this._titleEl.className = 'gengage-chat-panel-topbar-title';\n this._titleEl.dataset['gengagePart'] = 'panel-topbar-title';\n titleWrap.appendChild(this._titleEl);\n\n this._forwardBtn = document.createElement('button');\n this._forwardBtn.className = 'gengage-chat-panel-topbar-forward gds-btn gds-btn-ghost gds-icon-btn';\n this._forwardBtn.dataset['gengagePart'] = 'panel-topbar-forward';\n this._forwardBtn.type = 'button';\n this._forwardBtn.disabled = true;\n this._forwardBtn.setAttribute('aria-label', options.forwardAriaLabel ?? 'Forward');\n this._forwardBtn.title = options.forwardAriaLabel ?? 'Forward';\n this._forwardBtn.innerHTML =\n '<svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"m9 18 6-6-6-6\"/><path d=\"M3 12h12\"/></svg>';\n this._forwardBtn.addEventListener('click', () => options.onForward());\n\n this._actionsEl = document.createElement('div');\n this._actionsEl.className = 'gengage-chat-panel-topbar-actions';\n this._actionsEl.dataset['gengagePart'] = 'panel-topbar-actions';\n\n // Close button — only visible on mobile via CSS.\n // Dismisses all panel layers and returns to the base conversation.\n this._closeBtn = document.createElement('button');\n this._closeBtn.className = 'gengage-chat-panel-topbar-close gds-btn gds-btn-ghost gds-icon-btn';\n this._closeBtn.dataset['gengagePart'] = 'panel-topbar-close';\n this._closeBtn.type = 'button';\n this._closeBtn.setAttribute('aria-label', options.closePanelAriaLabel ?? 'Close panel');\n this._closeBtn.title = options.closePanelAriaLabel ?? 'Close panel';\n this._closeBtn.innerHTML =\n '<svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\"><line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"/><line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"/></svg>';\n this._closeBtn.addEventListener('click', () => options.onClose?.());\n\n this._el.appendChild(this._backBtn);\n this._el.appendChild(titleWrap);\n this._el.appendChild(this._actionsEl);\n this._el.appendChild(this._forwardBtn);\n this._el.appendChild(this._closeBtn);\n }\n\n update(canBack: boolean, canForward: boolean, title: string): void {\n this._backBtn.disabled = !canBack;\n this._forwardBtn.disabled = !canForward;\n this._backBtn.hidden = !canBack;\n this._forwardBtn.hidden = !canForward;\n this._titleEl.textContent = title;\n this._titleEl.title = title;\n }\n\n getElement(): HTMLElement {\n return this._el;\n }\n\n setTitle(title: string): void {\n this._titleEl.textContent = title;\n }\n\n setActions(actionsEl: HTMLElement | null): void {\n this._actionsEl.replaceChildren();\n if (!actionsEl) return;\n this._actionsEl.appendChild(actionsEl);\n }\n\n getTitle(): string {\n return this._titleEl.textContent ?? '';\n }\n}\n","/**\n * Vertical strip of product thumbnails along the right edge of the panel (results) pane.\n *\n * Clicking a thumbnail dispatches a rollback to the thread where that product was shown.\n *\n * **Display:** The column is hidden via CSS (`chat.css`); wiring (`setThumbnails`) stays for a future toggle.\n */\n\nimport { isSafeImageUrl } from '../../common/safe-html.js';\n\nexport interface ThumbnailEntry {\n sku: string;\n imageUrl: string;\n threadId: string;\n}\n\nexport interface ThumbnailsColumnOptions {\n onThumbnailClick: (threadId: string) => void;\n}\n\nexport class ThumbnailsColumn {\n private readonly _el: HTMLElement;\n private readonly _onThumbnailClick: (threadId: string) => void;\n\n constructor(options: ThumbnailsColumnOptions) {\n this._onThumbnailClick = options.onThumbnailClick;\n\n this._el = document.createElement('div');\n this._el.className = 'gengage-chat-thumbnails-column';\n this._el.style.display = 'none';\n }\n\n getElement(): HTMLElement {\n return this._el;\n }\n\n setEntries(entries: ThumbnailEntry[]): void {\n // Deduplicate by SKU (keep first occurrence)\n const seen = new Set<string>();\n const unique: ThumbnailEntry[] = [];\n for (const entry of entries) {\n if (!seen.has(entry.sku)) {\n seen.add(entry.sku);\n unique.push(entry);\n }\n }\n\n this._el.innerHTML = '';\n\n for (const entry of unique) {\n const thumb = document.createElement('button');\n thumb.type = 'button';\n thumb.className = 'gengage-chat-thumbnail-btn';\n thumb.title = entry.sku;\n\n if (isSafeImageUrl(entry.imageUrl)) {\n const img = document.createElement('img');\n img.className = 'gengage-chat-thumbnail-img';\n img.src = entry.imageUrl;\n img.alt = entry.sku;\n img.width = 40;\n img.height = 40;\n thumb.appendChild(img);\n }\n\n thumb.addEventListener('click', () => {\n this._onThumbnailClick(entry.threadId);\n });\n\n this._el.appendChild(thumb);\n }\n }\n\n show(): void {\n this._el.style.display = '';\n }\n\n hide(): void {\n this._el.style.display = 'none';\n }\n}\n","import type { ChatI18n, ChatMessage } from '../types.js';\nimport { registerChatScrollElement, CHAT_SCROLL_ELEMENT_ID } from '../utils/get-chat-scroll-element.js';\nimport { sanitizeHtml, isSafeImageUrl } from '../../common/safe-html.js';\nimport { dispatch } from '../../common/events.js';\nimport { CHAT_I18N_TR } from '../locales/index.js';\nimport { VoiceInput, isVoiceInputSupported } from '../../common/voice-input.js';\nimport { createKvkkBanner } from './KvkkBanner.js';\nimport { PanelTopBar } from './PanelTopBar.js';\nimport { ThumbnailsColumn } from './ThumbnailsColumn.js';\nimport type { ThumbnailEntry } from './ThumbnailsColumn.js';\n\n/** Generic fallback icon (right-arrow) used when a pill specifies an icon name not in the map. */\nconst DEFAULT_ACTION_ICON =\n '<svg viewBox=\"0 0 16 16\" class=\"gengage-chat-icon\"><path d=\"M3 8h10M9 4l4 4-4 4\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/></svg>';\n\n/** SVG icon map for suggested action chips/pills. Keys match backend icon names. */\nconst SUGGESTED_ACTION_ICONS: Record<string, string> = {\n search:\n '<svg viewBox=\"0 0 16 16\" class=\"gengage-chat-icon\"><circle cx=\"6.5\" cy=\"6.5\" r=\"5\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"/><line x1=\"10\" y1=\"10\" x2=\"15\" y2=\"15\" stroke=\"currentColor\" stroke-width=\"1.5\"/></svg>',\n review:\n '<svg viewBox=\"0 0 16 16\" class=\"gengage-chat-icon\"><polygon points=\"8,1 10,6 15,6 11,9 12.5,14 8,11 3.5,14 5,9 1,6 6,6\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"/></svg>',\n info: '<svg viewBox=\"0 0 16 16\" class=\"gengage-chat-icon\"><circle cx=\"8\" cy=\"8\" r=\"7\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"/><line x1=\"8\" y1=\"7\" x2=\"8\" y2=\"12\" stroke=\"currentColor\" stroke-width=\"1.5\"/><circle cx=\"8\" cy=\"4.5\" r=\"0.8\" fill=\"currentColor\"/></svg>',\n similar:\n '<svg viewBox=\"0 0 16 16\" class=\"gengage-chat-icon\"><rect x=\"1\" y=\"3\" width=\"6\" height=\"6\" rx=\"1\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"/><rect x=\"9\" y=\"3\" width=\"6\" height=\"6\" rx=\"1\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"/></svg>',\n};\n\nexport { SUGGESTED_ACTION_ICONS, DEFAULT_ACTION_ICON };\n\nexport interface ChatDrawerOptions {\n i18n: ChatI18n;\n onSend: (text: string, attachment?: File) => void;\n /** Callback fired when the cart icon button in the header is clicked. */\n onCartClick?: (() => void) | undefined;\n onClose: () => void;\n onAttachment?: (file: File) => void;\n onPanelToggle?: () => void;\n /**\n * Fired when side panel visibility/content changes so the parent can refresh host scroll lock\n * and backdrop classes. Not related to the Google Chrome browser — “host shell” means the\n * surrounding page/document integration.\n */\n onHostShellSync?: () => void;\n onRollback?: (messageId: string) => void;\n headerTitle?: string | undefined;\n headerAvatarUrl?: string | undefined;\n /** Launcher image URL — used as avatar fallback when headerAvatarUrl is not set. */\n launcherImageUrl?: string | undefined;\n headerBadge?: string | undefined;\n /** URL for the cart icon link in the header (e.g. \"/sepetim\"). */\n /** @deprecated Use onCartClick instead. If set, the cart button will navigate to this URL. */\n headerCartUrl?: string | undefined;\n /** When true, render the header favorites (heart) button. */\n showHeaderFavorites?: boolean | undefined;\n onFavoritesClick?: (() => void) | undefined;\n /** Callback fired when the panel back button is clicked. */\n onPanelBack?: (() => void) | undefined;\n /** Callback fired when the panel forward button is clicked. */\n onPanelForward?: (() => void) | undefined;\n /** Callback fired when the mobile panel close (✕) button is tapped.\n * Should clear panel history and comparison state in the caller. */\n onPanelClose?: (() => void) | undefined;\n /**\n * Fired when the user drags the mobile handle and releases.\n * 'half' | 'full' → switch to that snap position.\n * 'close' → close the drawer.\n */\n onMobileSnap?: ((state: 'half' | 'full' | 'close') => void) | undefined;\n /** Returns the current mobile sheet state so the drag handler knows which snap to target. */\n getMobileState?: (() => 'half' | 'full') | undefined;\n /** Returns true when the chat is displayed as a mobile bottom-sheet. Used to keep the side-panel back button always enabled. */\n getMobileViewport?: (() => boolean) | undefined;\n /** Callback fired when a product thumbnail is clicked (for thread rollback). */\n onThumbnailClick?: ((threadId: string) => void) | undefined;\n /** Callback fired when a link in bot HTML is clicked. */\n onLinkClick?: ((url: string) => void) | undefined;\n /** Enable voice input (Web Speech API STT). Default: false. */\n voiceEnabled?: boolean | undefined;\n /** BCP 47 language for speech recognition. Default: 'tr-TR'. */\n voiceLang?: string | undefined;\n /** Callback fired when the \"New Chat\" button is clicked. */\n onNewChat?: (() => void) | undefined;\n /**\n * Transcript presentation hooks (focus thread, pin-to-bottom heuristics).\n * Optional — when omitted, legacy scroll behaviour is unchanged.\n */\n presentation?: {\n onPinnedToBottomChange?: (pinned: boolean) => void;\n onUserInteractingChange?: (interacting: boolean) => void;\n /** User scrolled up while a thread focus is active — parent may show \"former messages\" */\n onFormerMessagesHint?: () => void;\n /** When true, stream-driven soft scroll-to-bottom is suppressed */\n shouldBlockSoftAutoScroll?: () => boolean;\n /** User tapped \"show former messages\" */\n onReleasePresentationFocus?: () => void;\n };\n}\n\nconst DEFAULT_I18N: ChatI18n = CHAT_I18N_TR;\nconst LOADING_STEP_INTERVAL_MS = 1400;\n\ninterface LoadingSequenceBinding {\n labelEl: HTMLElement;\n steps: string[];\n index: number;\n intervalId: ReturnType<typeof setInterval> | null;\n}\n\nconst CLIPBOARD_DATA_URL_IN_HTML = /data:image\\/(png|jpeg|jpg|webp);base64,[A-Za-z0-9+/=]+/gi;\n\n/** Turn clipboard items into an image File (PNG/JPEG/WebP). */\nasync function fileFromClipboardItems(items: ClipboardItem[]): Promise<File | null> {\n for (const clipItem of items) {\n for (const type of clipItem.types) {\n if (!type.startsWith('image/')) continue;\n try {\n const blob = await clipItem.getType(type);\n if (!blob || blob.size === 0) continue;\n const mime = type || blob.type || 'image/png';\n const ext = mime === 'image/png' ? 'png' : mime === 'image/webp' ? 'webp' : 'jpg';\n const name = `paste-${Date.now()}.${ext}`;\n return new File([blob], name, { type: mime });\n } catch {\n continue;\n }\n }\n }\n\n // Windows / Office / browser copy sometimes exposes images only inside text/html (data URLs).\n for (const clipItem of items) {\n if (!clipItem.types.includes('text/html')) continue;\n try {\n const blob = await clipItem.getType('text/html');\n const html = await blob.text();\n const matches = html.match(CLIPBOARD_DATA_URL_IN_HTML);\n const dataUrl = matches?.[0];\n if (!dataUrl || dataUrl.length > 5_000_000) continue;\n const res = await fetch(dataUrl);\n const out = await res.blob();\n if (!out || out.size === 0) continue;\n const mime = out.type || 'image/png';\n if (!['image/jpeg', 'image/png', 'image/webp'].includes(mime)) continue;\n const ext = mime === 'image/png' ? 'png' : mime === 'image/webp' ? 'webp' : 'jpg';\n return new File([out], `paste-${Date.now()}.${ext}`, { type: mime });\n } catch {\n continue;\n }\n }\n\n return null;\n}\n\n/**\n * Read image from system clipboard.\n * @param readPromise - Pass `navigator.clipboard.read()` from the **synchronous** click handler.\n * Chromium on Windows often requires starting `read()` in the same turn as the user gesture;\n * awaiting other work first can clear activation so `read()` yields nothing / rejects.\n */\nasync function readClipboardImageAsFile(readPromise?: Promise<ClipboardItem[]>): Promise<File | null> {\n try {\n // readPromise is always supplied by the click handler (started synchronously for Chromium\n // user-activation). The fallback only fires in non-Chromium environments where activation\n // is not required, so it will never be reached on the browsers that need the fix.\n const p = readPromise ?? (typeof navigator.clipboard?.read === 'function' ? navigator.clipboard.read() : null);\n if (!p) return null;\n const items = await p;\n return fileFromClipboardItems(items);\n } catch {\n /* unsupported or permission denied */\n }\n return null;\n}\n\nexport class ChatDrawer {\n private root: HTMLElement;\n private messagesEl: HTMLElement;\n private inputEl: HTMLTextAreaElement;\n private sendBtn: HTMLButtonElement;\n private _sendStopHandler: (() => void) | null = null;\n private i18n: ChatI18n;\n private onSend: (text: string, attachment?: File) => void;\n private _panelEl: HTMLElement;\n private _panelVisible = false;\n private _panelCollapsed = false;\n private _dividerPreviewEnabled = false;\n private _dividerEl: HTMLElement;\n private _dividerPreviewEl: HTMLElement;\n private _onPanelToggle: (() => void) | undefined = undefined;\n private _onHostShellSync: (() => void) | undefined = undefined;\n private _pendingAttachment: File | null = null;\n private _fileInput: HTMLInputElement;\n private _previewStrip: HTMLElement;\n private _previewName: HTMLElement;\n private _onAttachment: ((file: File) => void) | undefined = undefined;\n private _onRollback: ((messageId: string) => void) | undefined = undefined;\n private _onLinkClick: ((url: string) => void) | undefined = undefined;\n private _pillsEl: HTMLElement;\n private _kvkkSlot: HTMLElement;\n private _panelTopBar: PanelTopBar;\n private _userScrolledUp = false;\n private _scrollLockedUntil = 0;\n private _inputChipsEl: HTMLElement;\n private _thumbnailsColumn: ThumbnailsColumn;\n private _panelFloatingEl: HTMLElement;\n /** Mobile: overlay host for comparison dock (above panel scroll; avoids transformed panel containing block). */\n private _comparisonDockSlotEl: HTMLElement;\n /** Slot between panel top bar and main scroll content (desktop AI picks / analyzing strip). */\n private _panelAiZoneEl!: HTMLElement;\n private _favBadgeEl: HTMLElement | null = null;\n private _thinkingSteps: string[] = [];\n private _firstBotMessageIds: Set<string> = new Set();\n private _voiceInput: VoiceInput | null = null;\n private _micBtn: HTMLButtonElement | null = null;\n private _voiceEnabled = false;\n private _voiceLang = 'tr-TR';\n private _ignoreNextDividerClick = false;\n /** Cancels in-flight panel list scroll-to-top tween when a new one starts. */\n private _panelListScrollAnimToken = 0;\n private readonly _cleanups: Array<() => void> = [];\n private _focusTrapHandler: ((e: KeyboardEvent) => void) | null = null;\n private _previouslyFocusedElement: HTMLElement | null = null;\n private _conversationEl: HTMLElement | null = null;\n private readonly _options: ChatDrawerOptions;\n private _reopenPanelBtn: HTMLButtonElement | null = null;\n private _presentationFocusThreadId: string | null = null;\n private _formerMessagesBtn: HTMLButtonElement | null = null;\n private _programmaticScrollUntil = 0;\n private _userInteractionUntil = 0;\n private _touchStartY: number | null = null;\n private _presentationPinned = true;\n private _presentationUserInteracting = false;\n private _resizeRafId: number | null = null;\n private _cartBtn: HTMLButtonElement | null = null;\n private _attachWrapEl: HTMLElement | null = null;\n private _attachMenuEl: HTMLElement | null = null;\n private _attachBtn: HTMLButtonElement | null = null;\n private _attachMenuCleanup: (() => void) | null = null;\n private _attachMenuClickTimerId: number | null = null;\n private _typingLoadingBinding: LoadingSequenceBinding | null = null;\n private _panelLoadingBinding: LoadingSequenceBinding | null = null;\n private _panelAiZoneLoadingBinding: LoadingSequenceBinding | null = null;\n\n constructor(container: HTMLElement, options: ChatDrawerOptions) {\n this._options = options;\n this.i18n = { ...DEFAULT_I18N, ...options.i18n };\n this.onSend = options.onSend;\n if (options.onPanelToggle !== undefined) {\n this._onPanelToggle = options.onPanelToggle;\n }\n if (options.onHostShellSync !== undefined) {\n this._onHostShellSync = options.onHostShellSync;\n }\n if (options.onAttachment !== undefined) {\n this._onAttachment = options.onAttachment;\n }\n if (options.onRollback !== undefined) {\n this._onRollback = options.onRollback;\n }\n if (options.onLinkClick !== undefined) {\n this._onLinkClick = options.onLinkClick;\n }\n if (options.voiceEnabled) {\n this._voiceEnabled = true;\n }\n if (options.voiceLang !== undefined) {\n this._voiceLang = options.voiceLang;\n }\n\n this.root = document.createElement('div');\n this.root.className = 'gengage-chat-drawer gds-panel';\n this.root.dataset['gengagePart'] = 'chat-drawer';\n this.root.setAttribute('role', 'dialog');\n this.root.setAttribute('aria-label', this.i18n.headerTitle ?? 'Chat');\n this.root.setAttribute('aria-modal', 'true');\n\n const descId = 'gengage-chat-dialog-desc';\n const descEl = document.createElement('span');\n descEl.id = descId;\n descEl.className = 'gengage-sr-only';\n descEl.textContent = this.i18n.headerTitle ?? 'AI shopping assistant';\n this.root.appendChild(descEl);\n this.root.setAttribute('aria-describedby', descId);\n\n // Mobile drag handle — visual pill indicator (events attached to the full header below)\n let _handleEl: HTMLDivElement | null = null;\n {\n const handleEl = document.createElement('div');\n handleEl.className = 'gengage-chat-drawer-handle';\n handleEl.dataset['gengagePart'] = 'chat-drawer-handle';\n handleEl.setAttribute('aria-hidden', 'true');\n handleEl.style.pointerEvents = 'none'; // visual only; header receives the touch events\n _handleEl = handleEl;\n }\n\n // Header — branded dark bar\n const header = document.createElement('div');\n header.className = 'gengage-chat-header gds-shell-header';\n header.dataset['gengagePart'] = 'chat-header';\n\n const headerLeft = document.createElement('div');\n headerLeft.className = 'gengage-chat-header-left';\n headerLeft.dataset['gengagePart'] = 'chat-header-left';\n\n const avatarUrl = options.headerAvatarUrl ?? options.launcherImageUrl;\n const useLogoAvatar =\n typeof options.headerAvatarUrl === 'string' &&\n options.headerAvatarUrl.length > 0 &&\n options.headerAvatarUrl !== options.launcherImageUrl;\n if (avatarUrl) {\n const avatar = document.createElement('img');\n avatar.className = 'gengage-chat-header-avatar';\n if (useLogoAvatar) avatar.classList.add('gengage-chat-header-avatar--logo');\n avatar.dataset['gengagePart'] = 'chat-header-avatar';\n avatar.src = avatarUrl;\n avatar.alt = options.headerTitle ?? 'Assistant';\n headerLeft.appendChild(avatar);\n }\n\n const headerInfo = document.createElement('div');\n headerInfo.className = 'gengage-chat-header-info';\n headerInfo.dataset['gengagePart'] = 'chat-header-info';\n\n const titleRow = document.createElement('div');\n titleRow.className = 'gengage-chat-header-title-row';\n titleRow.dataset['gengagePart'] = 'chat-header-title-row';\n const title = document.createElement('span');\n title.className = 'gengage-chat-header-title';\n title.dataset['gengagePart'] = 'chat-header-title';\n title.textContent = options.headerTitle ?? this.i18n.headerTitle ?? 'Product Expert';\n titleRow.appendChild(title);\n\n if (options.headerBadge) {\n const badge = document.createElement('span');\n badge.className = 'gengage-chat-header-badge gds-badge gds-badge-brand';\n badge.dataset['gengagePart'] = 'chat-header-badge';\n badge.textContent = options.headerBadge;\n titleRow.appendChild(badge);\n }\n headerInfo.appendChild(titleRow);\n\n const powered = document.createElement('a');\n powered.className = 'gengage-chat-header-powered';\n powered.dataset['gengagePart'] = 'chat-header-powered-by';\n powered.href = 'https://gengage.ai/';\n powered.target = '_blank';\n powered.rel = 'noopener noreferrer';\n powered.innerHTML =\n `<svg viewBox=\"0 0 15 15\" fill=\"none\" aria-hidden=\"true\">` +\n `<path d=\"M15 5.88941C12.2201 5.88941 9.72762 7.14107 8.05571 9.11059H0C2.77991 9.11059 5.27238 7.85893 6.94429 5.88941H15Z\" fill=\"currentColor\"/>` +\n `<path d=\"M9.10964 0C9.10964 2.24394 8.29524 4.30038 6.94429 5.88941C5.27238 7.85962 2.77922 9.11059 0 9.11059V5.88941C3.24802 5.88941 5.89036 3.2465 5.89036 0H9.10964Z\" fill=\"currentColor\" fill-opacity=\"0.68\"/>` +\n `<path d=\"M15 5.88941V9.11059C11.752 9.11059 9.10964 11.7535 9.10964 15H5.89036C5.89036 12.7561 6.70476 10.6996 8.05571 9.11059C9.72762 7.14038 12.2208 5.88941 15 5.88941Z\" fill=\"currentColor\" fill-opacity=\"0.68\"/>` +\n `</svg>${this.i18n.poweredBy}`;\n headerInfo.appendChild(powered);\n\n headerLeft.appendChild(headerInfo);\n header.appendChild(headerLeft);\n\n const headerRight = document.createElement('div');\n headerRight.className = 'gengage-chat-header-right';\n headerRight.dataset['gengagePart'] = 'chat-header-actions';\n\n // Reopen-panel button — shown on mobile when the side panel is hidden but has content\n {\n const reopenBtn = document.createElement('button');\n reopenBtn.type = 'button';\n reopenBtn.className =\n 'gengage-chat-header-btn gengage-chat-header-btn--reopen-panel gds-btn gds-btn-ghost gds-icon-btn';\n reopenBtn.dataset['gengagePart'] = 'chat-header-reopen-panel';\n reopenBtn.setAttribute('aria-label', this.i18n.showPanelAriaLabel);\n reopenBtn.innerHTML = `<svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect x=\"3\" y=\"3\" width=\"18\" height=\"18\" rx=\"2\"/><line x1=\"9\" y1=\"3\" x2=\"9\" y2=\"21\"/></svg>`;\n reopenBtn.addEventListener('click', () => this._showMobilePanelFromBtn());\n headerRight.appendChild(reopenBtn);\n this._reopenPanelBtn = reopenBtn;\n }\n\n // Cart button — always a <button> so the onCartClick callback is always invoked\n // (handles session persistence before navigation when headerCartUrl is set).\n {\n const cartBtn = document.createElement('button');\n cartBtn.type = 'button';\n cartBtn.className = 'gengage-chat-header-btn gds-btn gds-btn-ghost gds-icon-btn';\n cartBtn.dataset['gengagePart'] = 'chat-header-cart';\n cartBtn.setAttribute('aria-label', this.i18n.cartAriaLabel);\n cartBtn.innerHTML = `<svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"9\" cy=\"21\" r=\"1\"/><circle cx=\"20\" cy=\"21\" r=\"1\"/><path d=\"M1 1h4l2.68 13.39a2 2 0 0 0 2 1.61h9.72a2 2 0 0 0 2-1.61L23 6H6\"/></svg>`;\n cartBtn.addEventListener('click', () => options.onCartClick?.());\n headerRight.appendChild(cartBtn);\n this._cartBtn = cartBtn;\n }\n\n // New Chat button (optional — reset conversation)\n if (options.onNewChat) {\n const newChatBtn = document.createElement('button');\n newChatBtn.className = 'gengage-chat-header-btn gengage-chat-new-chat gds-btn gds-btn-ghost gds-icon-btn';\n newChatBtn.dataset['gengagePart'] = 'chat-header-new-chat';\n newChatBtn.type = 'button';\n newChatBtn.setAttribute('aria-label', this.i18n.newChatButton);\n newChatBtn.title = this.i18n.newChatButton;\n newChatBtn.innerHTML = `<svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M12 20h9\"/><path d=\"M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z\"/></svg>`;\n newChatBtn.addEventListener('click', () => options.onNewChat?.());\n headerRight.appendChild(newChatBtn);\n }\n\n const closeBtn = document.createElement('button');\n closeBtn.className = 'gengage-chat-close gds-btn gds-btn-ghost gds-icon-btn';\n closeBtn.dataset['gengagePart'] = 'chat-header-close';\n closeBtn.type = 'button';\n closeBtn.setAttribute('aria-label', this.i18n.closeButton);\n closeBtn.innerHTML = `<svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\"><line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"/><line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"/></svg>`;\n closeBtn.addEventListener('click', options.onClose);\n\n if (options.showHeaderFavorites) {\n const favBtn = document.createElement('button');\n favBtn.className = 'gengage-chat-header-btn gengage-chat-header-btn--fav gds-btn gds-btn-ghost gds-icon-btn';\n favBtn.dataset['gengagePart'] = 'chat-header-favorites';\n favBtn.type = 'button';\n favBtn.setAttribute('aria-label', this.i18n.favoritesAriaLabel);\n favBtn.innerHTML = `<svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z\"/></svg>`;\n\n const badge = document.createElement('span');\n badge.className = 'gengage-chat-header-fav-badge';\n badge.dataset['gengagePart'] = 'chat-header-favorites-badge';\n badge.setAttribute('aria-hidden', 'true');\n badge.style.display = 'none';\n favBtn.appendChild(badge);\n this._favBadgeEl = badge;\n\n favBtn.addEventListener('click', () => {\n options.onFavoritesClick?.();\n });\n headerRight.appendChild(favBtn);\n }\n\n headerRight.appendChild(closeBtn);\n // Insert handle at the very top of header (before headerLeft / headerRight)\n if (_handleEl) header.insertBefore(_handleEl, header.firstChild);\n header.appendChild(headerRight);\n\n // Attach drag-to-dismiss events to the full header so any header tap-drag works.\n // Interactive children (buttons, links) are excluded so they keep normal tap behaviour.\n {\n const SNAP_THRESHOLD = 72;\n let dragStartY = 0;\n let dragDelta = 0;\n let dragging = false;\n\n const onDragStart = (e: TouchEvent) => {\n if (!(this._options.getMobileViewport?.() ?? window.innerWidth <= 768)) return;\n // Don't start drag if the touch landed on an interactive element\n const target = e.target as HTMLElement;\n if (target.closest('button, a, input, [role=\"button\"]')) return;\n const t = e.changedTouches?.[0];\n if (!t) return;\n dragStartY = t.clientY;\n dragDelta = 0;\n dragging = true;\n this.root.style.transition = 'none';\n };\n\n const onDragMove = (e: TouchEvent) => {\n if (!dragging) return;\n const t = e.changedTouches?.[0];\n if (!t) return;\n dragDelta = t.clientY - dragStartY;\n const currentState = options.getMobileState?.() ?? 'full';\n const clampedDelta = currentState === 'full' ? Math.max(0, dragDelta) : dragDelta;\n e.preventDefault();\n this.root.style.transform = `translateY(${clampedDelta}px)`;\n };\n\n const onDragEnd = () => {\n if (!dragging) return;\n dragging = false;\n const currentState = options.getMobileState?.() ?? 'full';\n\n let nextState: 'half' | 'full' | 'close';\n if (dragDelta > SNAP_THRESHOLD) {\n nextState = currentState === 'full' ? 'half' : 'close';\n } else if (dragDelta < -SNAP_THRESHOLD && currentState === 'half') {\n nextState = 'full';\n } else {\n nextState = currentState;\n }\n\n this.root.style.transition = '';\n if (nextState === 'close') {\n this.root.style.transform = 'translateY(100%)';\n setTimeout(() => {\n this.root.style.transform = '';\n options.onMobileSnap?.('close');\n }, 280);\n } else {\n this.root.style.transform = '';\n options.onMobileSnap?.(nextState);\n }\n dragDelta = 0;\n };\n\n const onDragCancel = () => {\n if (!dragging) return;\n dragging = false;\n dragDelta = 0;\n this.root.style.transition = '';\n this.root.style.transform = '';\n };\n\n header.addEventListener('touchstart', onDragStart, { passive: true });\n header.addEventListener('touchmove', onDragMove, { passive: false });\n header.addEventListener('touchend', onDragEnd, { passive: true });\n header.addEventListener('touchcancel', onDragCancel, { passive: true });\n this._cleanups.push(() => {\n header.removeEventListener('touchstart', onDragStart);\n header.removeEventListener('touchmove', onDragMove);\n header.removeEventListener('touchend', onDragEnd);\n header.removeEventListener('touchcancel', onDragCancel);\n });\n }\n\n // Body: flex container for panel + conversation\n const body = document.createElement('div');\n body.className = 'gengage-chat-body';\n body.dataset['gengagePart'] = 'chat-body';\n\n // Panel (hidden by default)\n this._panelEl = document.createElement('div');\n this._panelEl.className = 'gengage-chat-panel gds-panel';\n this._panelEl.dataset['gengagePart'] = 'chat-panel';\n\n // Panel top bar (navigation)\n this._panelTopBar = new PanelTopBar({\n onBack: () => options.onPanelBack?.(),\n onForward: () => options.onPanelForward?.(),\n onClose: () => {\n if (options.getMobileViewport?.() ?? false) {\n this.hideMobilePanel();\n } else {\n this.clearPanel();\n }\n options.onPanelClose?.();\n },\n backAriaLabel: this.i18n.backAriaLabel,\n forwardAriaLabel: this.i18n.forwardAriaLabel,\n closePanelAriaLabel: this.i18n.closePanelAriaLabel,\n });\n this._panelEl.appendChild(this._panelTopBar.getElement());\n\n // Panel scroll affordance — bottom fade gradient when content is scrollable\n const onPanelScroll = () => this._updateScrollAffordance();\n this._panelEl.addEventListener('scroll', onPanelScroll, { passive: true });\n this._cleanups.push(() => this._panelEl.removeEventListener('scroll', onPanelScroll));\n\n body.appendChild(this._panelEl);\n\n // Divider between panel and conversation\n this._dividerEl = document.createElement('div');\n this._dividerEl.className = 'gengage-chat-panel-divider gengage-chat-panel-divider--hidden';\n this._dividerEl.dataset['gengagePart'] = 'chat-panel-divider';\n this._dividerEl.setAttribute('role', 'separator');\n this._dividerEl.setAttribute('aria-label', this.i18n.togglePanelAriaLabel);\n this._dividerEl.setAttribute('title', this.i18n.togglePanelAriaLabel);\n this._dividerPreviewEl = document.createElement('div');\n this._dividerPreviewEl.className = 'gengage-chat-panel-divider-preview';\n this._dividerPreviewEl.dataset['gengagePart'] = 'chat-panel-divider-preview';\n this._dividerPreviewEl.setAttribute('aria-hidden', 'true');\n this._dividerEl.appendChild(this._dividerPreviewEl);\n const chevron = document.createElement('button');\n chevron.className = 'gengage-chat-panel-divider-toggle gds-btn gds-btn-ghost';\n chevron.dataset['gengagePart'] = 'chat-panel-divider-toggle';\n chevron.type = 'button';\n chevron.setAttribute('aria-label', this.i18n.togglePanelAriaLabel);\n chevron.setAttribute('title', this.i18n.togglePanelAriaLabel);\n chevron.textContent = '\\u00BB'; // » (collapse right)\n chevron.addEventListener('click', () => {\n if (this._ignoreNextDividerClick) {\n this._ignoreNextDividerClick = false;\n return;\n }\n this.togglePanel();\n this._onPanelToggle?.();\n });\n let touchStartX: number | null = null;\n let touchStartY: number | null = null;\n const swipeThreshold = 24;\n const onDividerTouchStart = (event: TouchEvent) => {\n if (!(this._options.getMobileViewport?.() ?? window.innerWidth <= 768)) return;\n const touch = event.changedTouches?.[0];\n if (!touch) return;\n touchStartX = touch.clientX;\n touchStartY = touch.clientY;\n };\n const onDividerTouchEnd = (event: TouchEvent) => {\n if (!(this._options.getMobileViewport?.() ?? window.innerWidth <= 768)) return;\n if (touchStartX === null || touchStartY === null) return;\n const touch = event.changedTouches?.[0];\n if (!touch) return;\n\n const deltaX = touch.clientX - touchStartX;\n const deltaY = touch.clientY - touchStartY;\n touchStartX = null;\n touchStartY = null;\n\n // Vertical swipe only. Swipe up collapses panel, swipe down expands.\n if (Math.abs(deltaY) < swipeThreshold || Math.abs(deltaY) < Math.abs(deltaX)) return;\n const nextCollapsed = deltaY < 0;\n if (nextCollapsed === this._panelCollapsed) return;\n\n this._ignoreNextDividerClick = true;\n this.setPanelCollapsed(nextCollapsed);\n this._onPanelToggle?.();\n };\n this._dividerEl.addEventListener('touchstart', onDividerTouchStart, { passive: true });\n this._dividerEl.addEventListener('touchend', onDividerTouchEnd, { passive: true });\n this._cleanups.push(() => {\n this._dividerEl.removeEventListener('touchstart', onDividerTouchStart);\n this._dividerEl.removeEventListener('touchend', onDividerTouchEnd);\n });\n this._dividerEl.appendChild(chevron);\n body.appendChild(this._dividerEl);\n\n // Conversation wrapper — header lives inside so it only spans chat width\n const conversation = document.createElement('div');\n conversation.className = 'gengage-chat-conversation';\n conversation.dataset['gengagePart'] = 'chat-conversation';\n this._conversationEl = conversation;\n conversation.appendChild(header);\n\n // Offline status bar (hidden by default, shown when navigator.onLine === false)\n const offlineBar = document.createElement('div');\n offlineBar.className = 'gengage-chat-offline-bar gds-evidence-card gds-evidence-card-warning';\n offlineBar.dataset['gengagePart'] = 'chat-offline-bar';\n offlineBar.setAttribute('role', 'status');\n offlineBar.setAttribute('aria-live', 'polite');\n offlineBar.textContent = this.i18n.offlineMessage;\n if (typeof navigator !== 'undefined' && !navigator.onLine) {\n offlineBar.classList.add('gengage-chat-offline-bar--visible');\n }\n conversation.appendChild(offlineBar);\n\n const onOffline = () => offlineBar.classList.add('gengage-chat-offline-bar--visible');\n const onOnline = () => offlineBar.classList.remove('gengage-chat-offline-bar--visible');\n window.addEventListener('offline', onOffline);\n window.addEventListener('online', onOnline);\n this._cleanups.push(() => {\n window.removeEventListener('offline', onOffline);\n window.removeEventListener('online', onOnline);\n });\n\n // KVKK banner slot (inserted above messages)\n this._kvkkSlot = document.createElement('div');\n this._kvkkSlot.className = 'gengage-chat-kvkk-slot';\n this._kvkkSlot.dataset['gengagePart'] = 'chat-kvkk-slot';\n conversation.appendChild(this._kvkkSlot);\n\n // Messages area (stable id for host tooling / getChatScrollElement registration)\n this.messagesEl = document.createElement('div');\n this.messagesEl.id = CHAT_SCROLL_ELEMENT_ID;\n this.messagesEl.className = 'gengage-chat-messages';\n this.messagesEl.dataset['gengagePart'] = 'chat-messages';\n this.messagesEl.setAttribute('role', 'log');\n this.messagesEl.setAttribute('aria-live', 'polite');\n this.messagesEl.setAttribute('aria-atomic', 'false');\n this.messagesEl.setAttribute('aria-label', this.i18n.chatMessagesAriaLabel);\n registerChatScrollElement(this.messagesEl);\n\n const formerBtn = document.createElement('button');\n formerBtn.type = 'button';\n formerBtn.className = 'gengage-chat-former-messages-btn gds-chip';\n formerBtn.dataset['gengagePart'] = 'chat-former-messages-button';\n formerBtn.textContent = this.i18n.showFormerMessagesButton;\n formerBtn.setAttribute('aria-label', this.i18n.showFormerMessagesButton);\n formerBtn.style.display = 'none';\n formerBtn.addEventListener('click', () => {\n this._options.presentation?.onReleasePresentationFocus?.();\n });\n this.messagesEl.appendChild(formerBtn);\n this._formerMessagesBtn = formerBtn;\n\n const markExplicitUserInteraction = () => {\n this._userInteractionUntil = Date.now() + 2000;\n };\n\n // Track user scroll position + presentation pin / interaction (aligned with legacy UX)\n let scrollRafPending = false;\n const pres = () => this._options.presentation;\n const onMessagesScroll = () => {\n if (scrollRafPending) return;\n scrollRafPending = true;\n requestAnimationFrame(() => {\n scrollRafPending = false;\n const { scrollTop, scrollHeight, clientHeight } = this.messagesEl;\n const distanceFromBottom = scrollHeight - scrollTop - clientHeight;\n this._userScrolledUp = distanceFromBottom > 10;\n\n const pinnedEnterThreshold = 32;\n const pinnedExitThreshold = 96;\n const previouslyPinned = this._presentationPinned;\n const pinned = previouslyPinned\n ? distanceFromBottom < pinnedExitThreshold\n : distanceFromBottom < pinnedEnterThreshold;\n\n const now = Date.now();\n const isProgrammaticScroll = now < this._programmaticScrollUntil;\n const explicitUserInteracting = !pinned && now < this._userInteractionUntil;\n const nextUserInteracting = isProgrammaticScroll ? false : explicitUserInteracting;\n\n if (pinned !== this._presentationPinned) {\n this._presentationPinned = pinned;\n pres()?.onPinnedToBottomChange?.(pinned);\n }\n if (nextUserInteracting !== this._presentationUserInteracting) {\n this._presentationUserInteracting = nextUserInteracting;\n pres()?.onUserInteractingChange?.(nextUserInteracting);\n }\n });\n };\n this.messagesEl.addEventListener('scroll', onMessagesScroll, { passive: true });\n this._cleanups.push(() => {\n this.messagesEl.removeEventListener('scroll', onMessagesScroll);\n });\n\n const onWheel = (e: WheelEvent) => {\n markExplicitUserInteraction();\n if (e.deltaY < -6 && this._presentationFocusThreadId) {\n this._options.presentation?.onFormerMessagesHint?.();\n }\n };\n this.messagesEl.addEventListener('wheel', onWheel, { passive: true });\n this._cleanups.push(() => this.messagesEl.removeEventListener('wheel', onWheel));\n\n const onTouchStart = (e: TouchEvent) => {\n markExplicitUserInteraction();\n this._touchStartY = e.touches[0]?.clientY ?? null;\n };\n const onTouchMove = (e: TouchEvent) => {\n markExplicitUserInteraction();\n const y = e.touches[0]?.clientY;\n const start = this._touchStartY;\n if (typeof y === 'number' && typeof start === 'number' && y - start > 10 && this._presentationFocusThreadId) {\n this._options.presentation?.onFormerMessagesHint?.();\n }\n };\n this.messagesEl.addEventListener('touchstart', onTouchStart, { passive: true });\n this.messagesEl.addEventListener('touchmove', onTouchMove, { passive: true });\n this._cleanups.push(() => {\n this.messagesEl.removeEventListener('touchstart', onTouchStart);\n this.messagesEl.removeEventListener('touchmove', onTouchMove);\n });\n\n conversation.appendChild(this.messagesEl);\n\n // Thumbnails column (right edge of panel — quick-scroll shortcuts for search results)\n this._thumbnailsColumn = new ThumbnailsColumn({\n onThumbnailClick: (threadId) => options.onThumbnailClick?.(threadId),\n });\n this._panelEl.appendChild(this._thumbnailsColumn.getElement());\n\n // Floating overlay: sticky zero-height anchor so absolutely-positioned overlays\n // (e.g. ChoicePrompter) stay fixed to the panel's visible area regardless of scroll.\n this._panelFloatingEl = document.createElement('div');\n this._panelFloatingEl.className = 'gengage-chat-panel-float';\n this._panelFloatingEl.dataset['gengagePart'] = 'chat-panel-floating-layer';\n this._panelEl.appendChild(this._panelFloatingEl);\n\n this._resetPanelAiZoneElement();\n\n // Suggestion pills row (between messages and input)\n this._pillsEl = document.createElement('div');\n this._pillsEl.className = 'gengage-chat-pills';\n this._pillsEl.dataset['gengagePart'] = 'chat-suggestion-pills';\n this._pillsEl.setAttribute('role', 'toolbar');\n this._pillsEl.setAttribute('aria-label', this.i18n.suggestionsAriaLabel);\n this._pillsEl.style.display = 'none';\n\n const pillsScroll = document.createElement('div');\n pillsScroll.className = 'gengage-chat-pills-scroll';\n pillsScroll.dataset['gengagePart'] = 'chat-suggestion-pills-scroll';\n this._pillsEl.appendChild(pillsScroll);\n\n const pillsArrow = document.createElement('button');\n pillsArrow.className = 'gengage-chat-pills-arrow gds-btn gds-btn-ghost';\n pillsArrow.dataset['gengagePart'] = 'chat-suggestion-pills-more';\n pillsArrow.type = 'button';\n pillsArrow.setAttribute('aria-label', this.i18n.moreSuggestionsAriaLabel);\n pillsArrow.textContent = '\\u203A'; // › single right-pointing angle\n pillsArrow.addEventListener('click', () => {\n pillsScroll.scrollBy({ left: 150, behavior: 'smooth' });\n });\n this._pillsEl.appendChild(pillsArrow);\n\n // Hide arrow when fully scrolled\n let pillsRafPending = false;\n const onPillsScroll = () => {\n if (pillsRafPending) return;\n pillsRafPending = true;\n requestAnimationFrame(() => {\n pillsRafPending = false;\n const atEnd = pillsScroll.scrollLeft + pillsScroll.clientWidth >= pillsScroll.scrollWidth - 4;\n pillsArrow.style.display = atEnd ? 'none' : '';\n });\n };\n pillsScroll.addEventListener('scroll', onPillsScroll, { passive: true });\n this._cleanups.push(() => {\n pillsScroll.removeEventListener('scroll', onPillsScroll);\n });\n\n conversation.appendChild(this._pillsEl);\n\n // Input-area chips (compact chips above input for search/info/review/similar)\n this._inputChipsEl = document.createElement('div');\n this._inputChipsEl.className = 'gengage-chat-input-chips';\n this._inputChipsEl.dataset['gengagePart'] = 'chat-input-chips';\n this._inputChipsEl.style.display = 'none';\n conversation.appendChild(this._inputChipsEl);\n\n // Input area\n const inputArea = document.createElement('div');\n inputArea.className = 'gengage-chat-input-area';\n inputArea.dataset['gengagePart'] = 'chat-input-area';\n\n this.inputEl = document.createElement('textarea');\n this.inputEl.className = 'gengage-chat-input';\n this.inputEl.dataset['gengagePart'] = 'chat-input';\n this.inputEl.rows = 1;\n this.inputEl.placeholder = this.i18n.inputPlaceholder;\n\n // Auto-expand on desktop as user types (capped at 120px)\n this.inputEl.addEventListener('input', () => {\n // Cancel any pending resize rAF to avoid queuing multiple reflows\n if (this._resizeRafId !== null) {\n cancelAnimationFrame(this._resizeRafId);\n }\n this._resizeRafId = requestAnimationFrame(() => {\n this._resizeRafId = null;\n this.inputEl.style.height = 'auto';\n this.inputEl.style.height = `${Math.min(this.inputEl.scrollHeight, 120)}px`;\n });\n this._updateSendEnabled();\n });\n\n // Enter submits; Shift+Enter inserts newline on desktop only\n this.inputEl.addEventListener('keydown', (e) => {\n if (e.key === 'Enter') {\n const isMobile = this._options.getMobileViewport?.() ?? window.innerWidth <= 768;\n if (isMobile || !e.shiftKey) {\n e.preventDefault();\n this._submit();\n }\n // else: Shift+Enter on desktop → natural newline (no preventDefault)\n }\n });\n\n this.inputEl.addEventListener('paste', (e) => {\n const cd = e.clipboardData;\n if (!cd) return;\n let file: File | null = null;\n const f0 = cd.files?.[0];\n if (f0 && f0.type.startsWith('image/')) {\n file = f0;\n } else if (cd.items?.length) {\n for (let i = 0; i < cd.items.length; i++) {\n const item = cd.items[i];\n if (item?.kind === 'file' && item.type.startsWith('image/')) {\n const f = item.getAsFile();\n if (f) {\n file = f;\n break;\n }\n }\n }\n }\n if (file) {\n e.preventDefault();\n this._routeAttachmentFile(file);\n }\n });\n\n // Hidden file input\n this._fileInput = document.createElement('input');\n this._fileInput.type = 'file';\n this._fileInput.accept = 'image/jpeg,image/png,image/webp';\n this._fileInput.style.display = 'none';\n this._fileInput.addEventListener('change', () => {\n const file = this._fileInput.files?.[0];\n if (file) {\n this._routeAttachmentFile(file);\n }\n this._fileInput.value = '';\n });\n\n // Attach: camera button + popup (fotoğraf seç / panodan yapıştır)\n const attachWrap = document.createElement('div');\n attachWrap.className = 'gengage-chat-attach-wrap';\n attachWrap.dataset['gengagePart'] = 'chat-attach-wrap';\n this._attachWrapEl = attachWrap;\n\n const attachBtn = document.createElement('button');\n this._attachBtn = attachBtn;\n attachBtn.className = 'gengage-chat-attach-btn gds-btn gds-btn-ghost';\n attachBtn.dataset['gengagePart'] = 'chat-attach-button';\n attachBtn.type = 'button';\n attachBtn.setAttribute('aria-label', this.i18n.attachImageButton);\n attachBtn.setAttribute('aria-haspopup', 'menu');\n attachBtn.setAttribute('aria-expanded', 'false');\n attachBtn.innerHTML = `<svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z\"/><circle cx=\"12\" cy=\"13\" r=\"4\"/></svg>`;\n attachBtn.addEventListener('click', (ev) => {\n ev.stopPropagation();\n this._toggleAttachMenu();\n });\n\n const attachMenu = document.createElement('div');\n this._attachMenuEl = attachMenu;\n attachMenu.className = 'gengage-chat-attach-menu gds-menu';\n attachMenu.dataset['gengagePart'] = 'chat-attach-menu';\n attachMenu.setAttribute('role', 'menu');\n attachMenu.setAttribute('hidden', '');\n\n const selectPhotoBtn = document.createElement('button');\n selectPhotoBtn.type = 'button';\n selectPhotoBtn.className = 'gengage-chat-attach-menu-item gds-btn gds-btn-ghost';\n selectPhotoBtn.dataset['gengagePart'] = 'chat-attach-menu-select-photo';\n selectPhotoBtn.setAttribute('role', 'menuitem');\n selectPhotoBtn.innerHTML =\n '<span class=\"gengage-chat-attach-menu-icon\" aria-hidden=\"true\">' +\n '<svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z\"/><circle cx=\"12\" cy=\"13\" r=\"4\"/></svg></span>' +\n `<span class=\"gengage-chat-attach-menu-label\">${this.i18n.attachMenuSelectPhoto}</span>`;\n selectPhotoBtn.addEventListener('click', (ev) => {\n ev.stopPropagation();\n this._closeAttachMenu();\n this._fileInput.click();\n });\n\n const sep = document.createElement('div');\n sep.className = 'gengage-chat-attach-menu-sep';\n sep.dataset['gengagePart'] = 'chat-attach-menu-separator';\n sep.setAttribute('aria-hidden', 'true');\n\n const pasteBtn = document.createElement('button');\n pasteBtn.type = 'button';\n pasteBtn.className = 'gengage-chat-attach-menu-item gds-btn gds-btn-ghost';\n pasteBtn.dataset['gengagePart'] = 'chat-attach-menu-paste';\n pasteBtn.setAttribute('role', 'menuitem');\n pasteBtn.innerHTML =\n '<span class=\"gengage-chat-attach-menu-icon\" aria-hidden=\"true\">' +\n '<svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">' +\n '<path d=\"M15 2H9a1 1 0 0 0-1 1v1a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V3a1 1 0 0 0-1-1Z\"/>' +\n '<path d=\"M8 4H6a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-2\"/>' +\n '<path d=\"M16 4h2a2 2 0 0 1 2 2v4\"/>' +\n '<path d=\"M21 14H11\"/>' +\n '<path d=\"m15 10-4 4 4 4\"/>' +\n '</svg></span>' +\n `<span class=\"gengage-chat-attach-menu-label\">${this.i18n.attachMenuPaste}</span>`;\n pasteBtn.addEventListener('click', (ev) => {\n ev.stopPropagation();\n // Start Clipboard API read synchronously in this handler (Chromium user-activation rule).\n const clipRead = typeof navigator.clipboard?.read === 'function' ? navigator.clipboard.read() : undefined;\n void this._pasteImageFromClipboardMenu(clipRead);\n });\n\n attachMenu.appendChild(selectPhotoBtn);\n attachMenu.appendChild(sep);\n attachMenu.appendChild(pasteBtn);\n attachWrap.appendChild(attachBtn);\n attachWrap.appendChild(attachMenu);\n\n // Attachment preview strip (hidden by default)\n this._previewStrip = document.createElement('div');\n this._previewStrip.className =\n 'gengage-chat-attachment-preview gengage-chat-attachment-preview--hidden gds-card-soft';\n this._previewStrip.dataset['gengagePart'] = 'chat-attachment-preview';\n const previewThumb = document.createElement('img');\n previewThumb.className = 'gengage-chat-attachment-preview-thumb';\n previewThumb.dataset['gengagePart'] = 'chat-attachment-preview-thumb';\n previewThumb.alt = '';\n this._previewName = document.createElement('span');\n this._previewName.className = 'gengage-chat-attachment-name';\n this._previewName.dataset['gengagePart'] = 'chat-attachment-preview-name';\n const removeBtn = document.createElement('button');\n removeBtn.className = 'gengage-chat-attachment-remove gds-btn gds-btn-ghost';\n removeBtn.dataset['gengagePart'] = 'chat-attachment-preview-remove';\n removeBtn.type = 'button';\n removeBtn.setAttribute('aria-label', this.i18n.removeAttachmentButton);\n removeBtn.textContent = '\\u00D7'; // multiplication sign (x)\n removeBtn.addEventListener('click', () => this.clearAttachment());\n this._previewStrip.appendChild(previewThumb);\n this._previewStrip.appendChild(this._previewName);\n this._previewStrip.appendChild(removeBtn);\n\n this.sendBtn = document.createElement('button');\n this.sendBtn.className = 'gengage-chat-send gds-btn gds-btn-primary';\n this.sendBtn.dataset['gengagePart'] = 'chat-send';\n this.sendBtn.type = 'button';\n this.sendBtn.disabled = true;\n this.sendBtn.setAttribute('aria-label', this.i18n.sendButton);\n this.sendBtn.dataset['tooltip'] = this.i18n.sendButton;\n this._renderSendButtonIcon('send');\n this.sendBtn.addEventListener('click', () => {\n if (this._sendStopHandler) {\n const onStop = this._sendStopHandler;\n this.hideStopButton();\n onStop();\n return;\n }\n this._submit();\n });\n\n // Drag-and-drop on input area\n inputArea.addEventListener('dragover', (e) => {\n e.preventDefault();\n inputArea.classList.add('gengage-chat-input-area--dragover');\n });\n inputArea.addEventListener('dragleave', () => {\n inputArea.classList.remove('gengage-chat-input-area--dragover');\n });\n inputArea.addEventListener('drop', (e) => {\n e.preventDefault();\n inputArea.classList.remove('gengage-chat-input-area--dragover');\n const file = e.dataTransfer?.files[0];\n if (file) {\n this._routeAttachmentFile(file);\n }\n });\n\n // Build pill container: [camera] [input] [mic?] [send]\n const pill = document.createElement('div');\n pill.className = 'gengage-chat-input-pill gds-input-shell';\n pill.dataset['gengagePart'] = 'chat-input-shell';\n pill.appendChild(attachWrap);\n pill.appendChild(this.inputEl);\n\n // Voice input mic button (Web Speech API STT)\n if (this._voiceEnabled && isVoiceInputSupported()) {\n this._micBtn = document.createElement('button');\n this._micBtn.className = 'gengage-chat-mic-btn gds-btn gds-btn-ghost';\n this._micBtn.dataset['gengagePart'] = 'chat-mic-button';\n this._micBtn.type = 'button';\n this._micBtn.setAttribute('aria-label', this.i18n.voiceButton);\n this._micBtn.innerHTML =\n '<svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">' +\n '<path d=\"M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z\"/>' +\n '<path d=\"M19 10v2a7 7 0 0 1-14 0v-2\"/>' +\n '<line x1=\"12\" y1=\"19\" x2=\"12\" y2=\"23\"/>' +\n '<line x1=\"8\" y1=\"23\" x2=\"16\" y2=\"23\"/>' +\n '</svg>';\n this._micBtn.addEventListener('click', () => this._toggleVoice());\n pill.appendChild(this._micBtn);\n\n this._voiceInput = new VoiceInput(\n {\n onInterim: (text) => {\n if (this._resizeRafId !== null) {\n cancelAnimationFrame(this._resizeRafId);\n this._resizeRafId = null;\n }\n this.inputEl.value = text;\n this.inputEl.style.height = 'auto';\n this.inputEl.style.height = `${Math.min(this.inputEl.scrollHeight, 120)}px`;\n },\n onFinal: (text) => {\n this.inputEl.value = text;\n },\n onAutoSubmit: (text) => {\n this.inputEl.value = text;\n this._micBtn?.classList.remove('gengage-chat-mic-btn--active');\n this._submit();\n },\n onStateChange: (state) => {\n if (state === 'listening') {\n this._micBtn?.classList.add('gengage-chat-mic-btn--active');\n } else {\n this._micBtn?.classList.remove('gengage-chat-mic-btn--active');\n }\n },\n onError: (_code, _message) => {\n this._micBtn?.classList.remove('gengage-chat-mic-btn--active');\n },\n },\n { lang: this._voiceLang },\n );\n }\n\n pill.appendChild(this.sendBtn);\n\n inputArea.appendChild(this._previewStrip);\n inputArea.appendChild(this._fileInput);\n inputArea.appendChild(pill);\n conversation.appendChild(inputArea);\n\n body.appendChild(conversation);\n\n this._comparisonDockSlotEl = document.createElement('div');\n this._comparisonDockSlotEl.className = 'gengage-chat-comparison-dock-slot';\n this._comparisonDockSlotEl.dataset['gengagePart'] = 'comparison-dock-slot';\n body.appendChild(this._comparisonDockSlotEl);\n\n this.root.appendChild(body);\n\n // Horizontal swipe to toggle panel on mobile (GAP-101)\n this._setupHorizontalSwipe(conversation);\n this._setupHorizontalSwipe(this._panelEl);\n\n // Footer\n const footer = document.createElement('div');\n footer.className = 'gengage-chat-footer';\n footer.dataset['gengagePart'] = 'chat-footer';\n footer.textContent = this.i18n.poweredBy;\n this.root.appendChild(footer);\n\n // Escape key to close drawer\n const escapeHandler = (e: KeyboardEvent) => {\n if (e.key === 'Escape') {\n options.onClose();\n }\n };\n this.root.addEventListener('keydown', escapeHandler);\n this._cleanups.push(() => this.root.removeEventListener('keydown', escapeHandler));\n\n container.appendChild(this.root);\n }\n\n addMessage(message: ChatMessage): void {\n const bubble = document.createElement('div');\n const bubbleRoleClass = message.role === 'assistant' ? 'gds-message-assistant' : 'gds-message-user';\n bubble.className = `gengage-chat-bubble gds-message ${bubbleRoleClass} gengage-chat-bubble--${message.role}`;\n bubble.dataset['gengagePart'] = message.role === 'assistant' ? 'chat-message-assistant' : 'chat-message-user';\n bubble.setAttribute('role', 'listitem');\n bubble.dataset['messageId'] = message.id;\n if (message.threadId) {\n bubble.dataset['threadId'] = message.threadId;\n }\n\n if (this._firstBotMessageIds.has(message.id)) {\n bubble.classList.add('gengage-chat-bubble--first');\n }\n\n if (message.attachment) {\n const thumbEl = document.createElement('img');\n thumbEl.className = 'gengage-chat-attachment-thumb';\n const blobUrl = URL.createObjectURL(message.attachment);\n thumbEl.src = blobUrl;\n thumbEl.alt = message.attachment.name;\n // Revoke blob URL once image loads (or errors) to free memory\n thumbEl.addEventListener('load', () => URL.revokeObjectURL(blobUrl), { once: true });\n thumbEl.addEventListener('error', () => URL.revokeObjectURL(blobUrl), { once: true });\n bubble.insertBefore(thumbEl, bubble.firstChild);\n }\n\n if (message.content) {\n const text = document.createElement('div');\n text.className = 'gengage-chat-bubble-text';\n text.dataset['gengagePart'] = 'chat-message-text';\n if (message.role === 'assistant') {\n text.innerHTML = sanitizeHtml(message.content);\n // Intercept all links in bot HTML\n if (this._onLinkClick) {\n const links = text.querySelectorAll('a[href]');\n for (const link of links) {\n link.addEventListener('click', (e) => {\n e.preventDefault();\n const href = link.getAttribute('href');\n if (href) {\n this._onLinkClick?.(href);\n }\n });\n }\n }\n } else {\n text.textContent = message.content; // User messages: always safe textContent\n }\n bubble.appendChild(text);\n }\n\n // Add rollback button to user message bubbles\n if (message.role === 'user' && this._onRollback) {\n const rollbackBtn = document.createElement('button');\n rollbackBtn.className = 'gengage-chat-rollback-btn gds-btn gds-btn-ghost';\n rollbackBtn.dataset['gengagePart'] = 'chat-message-rollback';\n rollbackBtn.type = 'button';\n rollbackBtn.setAttribute('aria-label', this.i18n.rollbackAriaLabel);\n rollbackBtn.title = this.i18n.rollbackAriaLabel;\n rollbackBtn.innerHTML = `<svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><polyline points=\"1 4 1 10 7 10\"/><path d=\"M3.51 15a9 9 0 1 0 2.13-9.36L1 10\"/></svg>`;\n rollbackBtn.addEventListener('click', (e) => {\n e.stopPropagation();\n this._onRollback?.(message.id);\n });\n bubble.appendChild(rollbackBtn);\n }\n\n this.messagesEl.appendChild(bubble);\n if (this._presentationFocusThreadId) {\n this._applyPresentationCollapsed();\n }\n this._scrollToBottom(message.role === 'user');\n }\n\n /** Remove one transcript bubble (e.g. superseded empty assistant placeholder). */\n removeMessageBubble(messageId: string): void {\n this._firstBotMessageIds.delete(messageId);\n this.messagesEl.querySelector(`[data-message-id=\"${CSS.escape(messageId)}\"]`)?.remove();\n if (this._presentationFocusThreadId) {\n this._applyPresentationCollapsed();\n }\n }\n\n showTypingIndicator(searchText?: string): void {\n this.removeTypingIndicator();\n const initialSteps =\n this._thinkingSteps.length > 0\n ? this._thinkingSteps.slice(-3)\n : searchText\n ? [searchText]\n : this.i18n.loadingSequenceGeneric;\n const { root, binding } = this._createLoadingSequence(\n 'chat',\n initialSteps,\n 'chat-typing-indicator',\n 'gengage-chat-typing',\n );\n root.dataset['typing'] = 'true';\n this._typingLoadingBinding = binding;\n\n this.messagesEl.appendChild(root);\n this._scrollToBottom(true);\n }\n\n /** Accumulate a new thinking step (shown as a checklist in the typing indicator). */\n addThinkingStep(text: string): void {\n const normalized = text.trim();\n if (!normalized) return;\n if (this._thinkingSteps[this._thinkingSteps.length - 1] === normalized) return;\n this._thinkingSteps.push(normalized);\n this._thinkingSteps = this._thinkingSteps.slice(-3);\n if (this._typingLoadingBinding) {\n this._applyLoadingSteps(this._typingLoadingBinding, this._thinkingSteps, true);\n }\n if (this._panelLoadingBinding) {\n this._applyLoadingSteps(this._panelLoadingBinding, this._thinkingSteps, true);\n }\n if (this._panelAiZoneLoadingBinding) {\n this._applyLoadingSteps(this._panelAiZoneLoadingBinding, this._thinkingSteps, true);\n }\n }\n\n setThinkingSteps(steps: string[]): void {\n const normalized = steps\n .map((step) => step.trim())\n .filter(Boolean)\n .slice(-3);\n if (normalized.length === 0) return;\n this._thinkingSteps = normalized;\n if (this._typingLoadingBinding) {\n this._applyLoadingSteps(this._typingLoadingBinding, this._thinkingSteps, true);\n }\n if (this._panelLoadingBinding) {\n this._applyLoadingSteps(this._panelLoadingBinding, this._thinkingSteps, true);\n }\n if (this._panelAiZoneLoadingBinding) {\n this._applyLoadingSteps(this._panelAiZoneLoadingBinding, this._thinkingSteps, true);\n }\n }\n\n removeTypingIndicator(): void {\n this._destroyLoadingBinding(this._typingLoadingBinding);\n this._typingLoadingBinding = null;\n const existing = this.messagesEl.querySelector('.gengage-chat-typing');\n existing?.remove();\n this._thinkingSteps = [];\n this.hideStopButton();\n }\n\n /** Show a \"Stop generating\" button below the typing indicator. */\n showStopButton(onStop: () => void): void {\n this._sendStopHandler = onStop;\n this.sendBtn.disabled = false;\n this.sendBtn.classList.add('gengage-chat-send--stop', 'gds-btn-secondary');\n this.sendBtn.classList.remove('gds-btn-primary');\n this.sendBtn.setAttribute('aria-label', this.i18n.stopGenerating);\n this.sendBtn.dataset['tooltip'] = this.i18n.stopGenerating;\n this._renderSendButtonIcon('stop');\n }\n\n /** Remove the stop-generating button if present. */\n hideStopButton(): void {\n this._sendStopHandler = null;\n this.sendBtn.classList.remove('gengage-chat-send--stop', 'gds-btn-secondary');\n this.sendBtn.classList.add('gds-btn-primary');\n this.sendBtn.setAttribute('aria-label', this.i18n.sendButton);\n this.sendBtn.dataset['tooltip'] = this.i18n.sendButton;\n this._renderSendButtonIcon('send');\n this._updateSendEnabled();\n }\n\n showError(message?: string, onRetry?: () => void): void {\n const errEl = document.createElement('div');\n errEl.className = 'gengage-chat-error';\n errEl.setAttribute('role', 'alert');\n const textEl = document.createElement('span');\n textEl.textContent = message ?? this.i18n.errorMessage;\n errEl.appendChild(textEl);\n\n if (onRetry) {\n const retryBtn = document.createElement('button');\n retryBtn.className = 'gengage-chat-error-retry';\n retryBtn.textContent = this.i18n.retryButton ?? 'Retry';\n retryBtn.addEventListener('click', () => {\n errEl.remove();\n onRetry();\n });\n errEl.appendChild(retryBtn);\n }\n\n this.messagesEl.appendChild(errEl);\n this._scrollToBottom(true);\n }\n\n /** Show error with recovery action pills (\"Try again\" + \"Ask something else\"). */\n showErrorWithRecovery(message: string, actions: { onRetry: () => void; onNewQuestion: () => void }): void {\n this.showError(message);\n this.setRecoveryPills(actions);\n }\n\n /** Recovery pills only — error copy is shown as a normal assistant message. */\n showRecoveryPillsOnly(actions: { onRetry: () => void; onNewQuestion: () => void }): void {\n this.setRecoveryPills(actions);\n }\n\n private setRecoveryPills(actions: { onRetry: () => void; onNewQuestion: () => void }): void {\n this.setPills([\n { label: this.i18n.tryAgainButton, onAction: actions.onRetry },\n { label: this.i18n.askSomethingElseButton, onAction: actions.onNewQuestion },\n ]);\n }\n\n clearMessages(): void {\n const former = this._formerMessagesBtn;\n for (const child of [...this.messagesEl.children]) {\n if (child !== former) child.remove();\n }\n }\n\n /** Replace suggestion pills. Pass empty array to hide. */\n setPills(\n pills: Array<{ label: string; onAction: () => void; icon?: string; image?: string; description?: string }>,\n ): void {\n const scroll = this._pillsEl.querySelector('.gengage-chat-pills-scroll');\n if (!scroll) return;\n while (scroll.firstChild) scroll.removeChild(scroll.firstChild);\n\n if (pills.length === 0) {\n this._pillsEl.style.display = 'none';\n return;\n }\n\n this._pillsEl.style.display = '';\n for (const pill of pills) {\n const btn = document.createElement('button');\n btn.className = pill.image\n ? 'gengage-chat-pill gds-chip gds-chip-active gengage-chat-pill--rich'\n : 'gengage-chat-pill gds-chip gds-chip-active';\n btn.type = 'button';\n\n if (pill.icon) {\n const svgHtml = SUGGESTED_ACTION_ICONS[pill.icon] ?? DEFAULT_ACTION_ICON;\n const iconSpan = document.createElement('span');\n iconSpan.className = 'gengage-chat-pill-icon';\n iconSpan.innerHTML = svgHtml;\n btn.appendChild(iconSpan);\n }\n\n if (pill.image && isSafeImageUrl(pill.image)) {\n const img = document.createElement('img');\n img.className = 'gengage-chat-pill-img';\n img.src = pill.image;\n img.alt = '';\n btn.appendChild(img);\n }\n\n const textWrap = document.createElement('span');\n textWrap.className = 'gengage-chat-pill-text';\n textWrap.textContent = pill.label;\n btn.appendChild(textWrap);\n\n if (pill.description) {\n const desc = document.createElement('span');\n desc.className = 'gengage-chat-pill-desc';\n const descId = `pill-desc-${Math.random().toString(36).slice(2, 9)}`;\n desc.id = descId;\n desc.textContent = pill.description;\n btn.appendChild(desc);\n btn.setAttribute('aria-describedby', descId);\n }\n\n btn.addEventListener('click', () => pill.onAction());\n scroll.appendChild(btn);\n }\n\n // Show/hide arrow based on overflow\n const arrow = this._pillsEl.querySelector('.gengage-chat-pills-arrow') as HTMLElement | null;\n if (arrow) {\n requestAnimationFrame(() => {\n arrow.style.display = scroll.scrollWidth > scroll.clientWidth ? '' : 'none';\n });\n }\n }\n\n focusInput(): void {\n this.inputEl.focus();\n }\n\n showKvkkBanner(html: string, onDismiss: () => void): void {\n this._kvkkSlot.innerHTML = '';\n const banner = createKvkkBanner({ htmlContent: html, onDismiss, closeAriaLabel: this.i18n.closeAriaLabel });\n this._kvkkSlot.appendChild(banner);\n }\n\n hideKvkkBanner(): void {\n this._kvkkSlot.innerHTML = '';\n }\n\n getElement(): HTMLElement {\n return this.root;\n }\n\n /** Stage a file attachment for sending. Shows preview. */\n stageAttachment(file: File): void {\n this._pendingAttachment = file;\n this._previewName.textContent = file.name;\n const thumb = this._previewStrip.querySelector('.gengage-chat-attachment-preview-thumb') as HTMLImageElement;\n if (thumb) {\n // Revoke previous blob URL to prevent memory leak\n if (thumb.src && thumb.src.startsWith('blob:')) {\n URL.revokeObjectURL(thumb.src);\n }\n thumb.src = URL.createObjectURL(file);\n }\n this._previewStrip.classList.remove('gengage-chat-attachment-preview--hidden');\n this._updateSendEnabled();\n }\n\n /** Remove the staged attachment and hide preview. */\n clearAttachment(): void {\n const thumb = this._previewStrip.querySelector('.gengage-chat-attachment-preview-thumb') as HTMLImageElement;\n if (thumb?.src) {\n URL.revokeObjectURL(thumb.src);\n thumb.src = '';\n }\n this._pendingAttachment = null;\n this._previewStrip.classList.add('gengage-chat-attachment-preview--hidden');\n this._updateSendEnabled();\n }\n\n private _routeAttachmentFile(file: File): void {\n if (this._onAttachment) {\n try {\n this._onAttachment(file);\n } catch (err) {\n console.error('[gengage:chat] Attachment callback error:', err);\n }\n } else {\n this.stageAttachment(file);\n }\n }\n\n private _closeAttachMenu(): void {\n if (!this._attachMenuEl) return;\n this._attachMenuEl.setAttribute('hidden', '');\n this._attachBtn?.setAttribute('aria-expanded', 'false');\n if (this._attachMenuClickTimerId !== null) {\n clearTimeout(this._attachMenuClickTimerId);\n this._attachMenuClickTimerId = null;\n }\n if (this._attachMenuCleanup) {\n this._attachMenuCleanup();\n this._attachMenuCleanup = null;\n }\n }\n\n private _openAttachMenu(): void {\n if (!this._attachMenuEl) return;\n this._attachMenuEl.removeAttribute('hidden');\n this._attachBtn?.setAttribute('aria-expanded', 'true');\n const onDocCapture = (e: MouseEvent): void => {\n if (this._attachWrapEl?.contains(e.target as Node)) return;\n this._closeAttachMenu();\n };\n const onEsc = (e: KeyboardEvent): void => {\n if (e.key === 'Escape') {\n e.stopPropagation();\n this._closeAttachMenu();\n }\n };\n this._attachMenuClickTimerId = window.setTimeout(() => {\n this._attachMenuClickTimerId = null;\n document.addEventListener('click', onDocCapture, true);\n }, 0);\n document.addEventListener('keydown', onEsc, true);\n this._attachMenuCleanup = () => {\n document.removeEventListener('click', onDocCapture, true);\n document.removeEventListener('keydown', onEsc, true);\n };\n }\n\n private _toggleAttachMenu(): void {\n if (!this._attachMenuEl) return;\n if (this._attachMenuEl.hasAttribute('hidden')) {\n this._openAttachMenu();\n } else {\n this._closeAttachMenu();\n }\n }\n\n private async _pasteImageFromClipboardMenu(clipRead?: Promise<ClipboardItem[]>): Promise<void> {\n const file = await readClipboardImageAsFile(clipRead);\n if (file) {\n this._routeAttachmentFile(file);\n this._closeAttachMenu();\n return;\n }\n dispatch('gengage:global:error', {\n message: this.i18n.clipboardNoImageMessage,\n source: 'chat' as const,\n });\n this._closeAttachMenu();\n }\n\n /** Get the currently staged attachment file, or null. */\n getPendingAttachment(): File | null {\n return this._pendingAttachment;\n }\n\n /**\n * Desktop: area above the main panel body for “analyzing” + AITopPicks / AIGroupingCards\n * so they are not duplicated in the chat column.\n */\n setPanelAiZoneState(\n state: 'hidden' | 'analyzing' | 'results',\n options?: { resultEl?: HTMLElement; analyzingLabel?: string },\n ): void {\n if (!this._panelAiZoneEl.isConnected) return;\n this._destroyLoadingBinding(this._panelAiZoneLoadingBinding);\n this._panelAiZoneLoadingBinding = null;\n if (state === 'hidden') {\n this._panelAiZoneEl.innerHTML = '';\n this._panelAiZoneEl.setAttribute('hidden', '');\n return;\n }\n this._panelAiZoneEl.removeAttribute('hidden');\n if (state === 'analyzing') {\n this._panelAiZoneEl.innerHTML = '';\n const fallbackSequence = [\n options?.analyzingLabel ?? this.i18n.aiAnalysisAnalyzingLabel,\n ...this.i18n.loadingSequencePanel,\n ];\n const { root, binding } = this._createLoadingSequence(\n 'panel',\n this._thinkingSteps.length > 0 ? this._thinkingSteps.slice(-3) : fallbackSequence,\n 'panel-ai-zone-loading',\n 'gengage-chat-panel-ai-zone-inner',\n );\n this._panelAiZoneLoadingBinding = binding;\n this._panelAiZoneEl.appendChild(root);\n } else if (state === 'results' && options?.resultEl) {\n this._panelAiZoneEl.innerHTML = '';\n this._panelAiZoneEl.appendChild(options.resultEl);\n }\n }\n\n private _resetPanelAiZoneElement(): void {\n this._panelAiZoneEl = document.createElement('div');\n this._panelAiZoneEl.className = 'gengage-chat-panel-ai-zone';\n this._panelAiZoneEl.setAttribute('hidden', '');\n }\n\n private _emitHostShellSync(): void {\n this._onHostShellSync?.();\n }\n\n private _syncPanelTopBarFromContent(contentEl: HTMLElement): void {\n const gridHead = contentEl.querySelector<HTMLElement>('.gengage-chat-product-grid-head');\n if (gridHead) {\n const titleEl = gridHead.querySelector<HTMLElement>('.gengage-chat-product-grid-head-title');\n const actionsEl = gridHead.querySelector<HTMLElement>('.gengage-chat-product-grid-head-actions');\n if (titleEl?.textContent?.trim()) {\n const derivedTitle = titleEl.textContent.trim();\n contentEl.dataset['gengagePanelDerivedTitle'] = derivedTitle;\n this._panelTopBar.setTitle(derivedTitle);\n }\n if (actionsEl) {\n actionsEl.classList.add('gengage-chat-panel-topbar-toolbar-host');\n this._panelTopBar.setActions(actionsEl);\n } else {\n this._panelTopBar.setActions(null);\n }\n gridHead.remove();\n return;\n }\n this._syncPanelTopBarTitleFromContent(contentEl);\n }\n\n private _syncPanelTopBarTitleFromContent(contentEl: HTMLElement): void {\n const derivedTitle = contentEl.dataset['gengagePanelDerivedTitle'];\n if (derivedTitle?.trim()) {\n this._panelTopBar.setTitle(derivedTitle.trim());\n return;\n }\n const titleCandidate = contentEl.querySelector<HTMLElement>(\n '.gengage-chat-product-details-title, .gengage-chat-product-details-similars-heading, .gengage-chat-ai-top-picks-title',\n );\n const titleText = titleCandidate?.textContent?.trim();\n if (titleText) {\n this._panelTopBar.setTitle(titleText);\n }\n }\n\n /** Replace panel content and show the panel. */\n setPanelContent(el: HTMLElement): void {\n this._destroyLoadingBinding(this._panelLoadingBinding);\n this._panelLoadingBinding = null;\n this._destroyLoadingBinding(this._panelAiZoneLoadingBinding);\n this._panelAiZoneLoadingBinding = null;\n const wasVisible = this._panelVisible;\n // Only apply opacity crossfade when swapping content in an already-visible panel.\n // Applying it on first-show would hide the slide-in animation (opacity:0 masks the transform).\n if (wasVisible) {\n this._panelEl.classList.add('gengage-chat-panel--transitioning');\n }\n this._panelEl.innerHTML = '';\n this._resetPanelAiZoneElement();\n this._panelEl.appendChild(this._panelTopBar.getElement());\n this._panelEl.appendChild(this._panelAiZoneEl);\n this._panelTopBar.setActions(null);\n this._panelEl.appendChild(el);\n this._panelEl.appendChild(this._thumbnailsColumn.getElement());\n this._panelEl.appendChild(this._panelFloatingEl);\n this._syncPanelTopBarFromContent(el);\n this._dividerEl.classList.remove('gengage-chat-panel-divider--hidden');\n if (!this._panelVisible) {\n this._panelVisible = true;\n this._panelEl.classList.add('gengage-chat-panel--visible');\n this.root.classList.add('gengage-chat-drawer--with-panel');\n }\n if (this._panelCollapsed) {\n this._panelEl.classList.add('gengage-chat-panel--collapsed');\n }\n this._syncDividerPreview();\n requestAnimationFrame(() => {\n this._panelEl.classList.remove('gengage-chat-panel--transitioning');\n this._updateScrollAffordance();\n this._smoothScrollPanelListToTop();\n });\n // New content always reopens the panel — hide the reopen button\n if (this._reopenPanelBtn) this._reopenPanelBtn.style.display = 'none';\n this._emitHostShellSync();\n }\n\n /** Append content to the panel without replacing existing content. */\n appendPanelContent(el: HTMLElement): void {\n const thumb = this._thumbnailsColumn.getElement();\n const ref = thumb.parentElement === this._panelEl ? thumb : this._panelFloatingEl;\n this._panelEl.insertBefore(el, ref);\n this._syncPanelTopBarFromContent(this.getPanelContentElement() ?? el);\n this._dividerEl.classList.remove('gengage-chat-panel-divider--hidden');\n if (!this._panelVisible) {\n this._panelVisible = true;\n this._panelEl.classList.add('gengage-chat-panel--visible');\n this.root.classList.add('gengage-chat-drawer--with-panel');\n }\n this._syncDividerPreview();\n this._emitHostShellSync();\n }\n\n /** Return the panel element's content child (after topbar), or null. */\n getPanelContentElement(): HTMLElement | null {\n const children = this._panelEl.children;\n for (let i = 0; i < children.length; i++) {\n const child = children[i] as HTMLElement;\n if (\n child.classList.contains('gengage-chat-panel-topbar') ||\n child.classList.contains('gengage-chat-panel-ai-zone') ||\n child.classList.contains('gengage-chat-thumbnails-column') ||\n child.classList.contains('gengage-chat-panel-float')\n ) {\n continue;\n }\n return child;\n }\n return null;\n }\n\n /** Whether the panel is currently visible (may be empty). */\n isPanelVisible(): boolean {\n return this._panelVisible;\n }\n\n /** Whether the panel is currently visible and has rendered content (beyond topbar + thumbnails column). */\n hasPanelContent(): boolean {\n return this._panelVisible && this.getPanelContentElement() !== null;\n }\n\n /** Whether panel currently shows loading skeleton blocks. */\n isPanelLoading(): boolean {\n return this._panelEl.querySelector('.gengage-chat-panel-skeleton') !== null;\n }\n\n /** Show loading skeleton in the panel. Variant depends on contentType hint. */\n showPanelLoading(contentType?: string): void {\n this._destroyLoadingBinding(this._panelLoadingBinding);\n this._panelLoadingBinding = null;\n this._dividerEl.classList.remove('gengage-chat-panel-divider--hidden');\n this._panelEl.innerHTML = '';\n this._resetPanelAiZoneElement();\n this._panelEl.appendChild(this._panelTopBar.getElement());\n this._panelEl.appendChild(this._panelAiZoneEl);\n const skeleton = document.createElement('div');\n skeleton.className = 'gengage-chat-panel-skeleton';\n const panelSequence =\n contentType === 'comparisonTable' ? this.i18n.loadingSequenceComparison : this.i18n.loadingSequencePanel;\n const { root: panelStatus, binding: panelBinding } = this._createLoadingSequence(\n 'panel',\n this._thinkingSteps.length > 0 ? this._thinkingSteps.slice(-3) : panelSequence,\n 'panel-loading-status',\n 'gengage-chat-panel-loading-status',\n );\n this._panelLoadingBinding = panelBinding;\n\n switch (contentType) {\n case 'productDetails': {\n skeleton.appendChild(panelStatus);\n // Tall image placeholder + text lines\n const imgBlock = document.createElement('div');\n imgBlock.className = 'gengage-chat-panel-skeleton-block gengage-chat-panel-skeleton-block--image';\n skeleton.appendChild(imgBlock);\n for (let i = 0; i < 3; i++) {\n const line = document.createElement('div');\n line.className = 'gengage-chat-panel-skeleton-block gengage-chat-panel-skeleton-block--text';\n skeleton.appendChild(line);\n }\n break;\n }\n case 'productList':\n case 'groupList': {\n skeleton.appendChild(panelStatus);\n // 2x3 grid of small card placeholders\n const grid = document.createElement('div');\n grid.className = 'gengage-chat-panel-skeleton-grid';\n for (let i = 0; i < 6; i++) {\n const card = document.createElement('div');\n card.className = 'gengage-chat-panel-skeleton-block gengage-chat-panel-skeleton-block--card';\n grid.appendChild(card);\n }\n skeleton.appendChild(grid);\n break;\n }\n case 'comparisonTable': {\n skeleton.classList.add('gengage-chat-panel-skeleton--comparison');\n skeleton.appendChild(panelStatus);\n const root = document.createElement('div');\n root.className = 'gengage-chat-comparison gengage-chat-comparison--skeleton';\n root.setAttribute('aria-busy', 'true');\n\n // Önerilen seçim kartı — gerçek .gengage-chat-comparison-recommended ile aynı kutu\n const rec = document.createElement('div');\n rec.className = 'gengage-chat-comparison-recommended';\n const recLabel = document.createElement('div');\n recLabel.className = 'gengage-chat-comparison-skeleton-shimmer gengage-chat-comparison-skeleton-rec-label';\n rec.appendChild(recLabel);\n const recBody = document.createElement('div');\n recBody.className = 'gengage-chat-comparison-recommended-body';\n const recImg = document.createElement('div');\n recImg.className = 'gengage-chat-comparison-skeleton-shimmer gengage-chat-comparison-skeleton-rec-img';\n recImg.setAttribute('aria-hidden', 'true');\n const recInfo = document.createElement('div');\n recInfo.className = 'gengage-chat-comparison-recommended-info';\n for (let i = 0; i < 2; i++) {\n const t = document.createElement('div');\n t.className = 'gengage-chat-comparison-skeleton-shimmer gengage-chat-comparison-skeleton-rec-title';\n if (i === 1) t.classList.add('gengage-chat-comparison-skeleton-rec-title--short');\n recInfo.appendChild(t);\n }\n const recPrice = document.createElement('div');\n recPrice.className = 'gengage-chat-comparison-skeleton-shimmer gengage-chat-comparison-skeleton-rec-price';\n recInfo.appendChild(recPrice);\n recBody.appendChild(recImg);\n recBody.appendChild(recInfo);\n rec.appendChild(recBody);\n const hl = document.createElement('div');\n hl.className = 'gengage-chat-comparison-highlights';\n const hlLab = document.createElement('div');\n hlLab.className = 'gengage-chat-comparison-skeleton-shimmer gengage-chat-comparison-skeleton-hl-label';\n hl.appendChild(hlLab);\n const hlUl = document.createElement('ul');\n hlUl.className = 'gengage-chat-comparison-skeleton-hl-list';\n for (let i = 0; i < 3; i++) {\n const li = document.createElement('li');\n const line = document.createElement('div');\n line.className = 'gengage-chat-comparison-skeleton-shimmer gengage-chat-comparison-skeleton-hl-line';\n if (i === 1) line.classList.add('gengage-chat-comparison-skeleton-hl-line--medium');\n if (i === 2) line.classList.add('gengage-chat-comparison-skeleton-hl-line--short');\n li.appendChild(line);\n hlUl.appendChild(li);\n }\n hl.appendChild(hlUl);\n rec.appendChild(hl);\n root.appendChild(rec);\n\n // Temel farklar — .gengage-chat-comparison-key-differences\n const kd = document.createElement('div');\n kd.className = 'gengage-chat-comparison-key-differences';\n const kdH = document.createElement('div');\n kdH.className = 'gengage-chat-comparison-skeleton-shimmer gengage-chat-comparison-skeleton-kd-heading';\n kd.appendChild(kdH);\n const kdContent = document.createElement('div');\n kdContent.className = 'gengage-chat-comparison-key-differences-content';\n for (let i = 0; i < 4; i++) {\n const line = document.createElement('div');\n line.className = 'gengage-chat-comparison-skeleton-shimmer gengage-chat-comparison-skeleton-kd-line';\n kdContent.appendChild(line);\n }\n kd.appendChild(kdContent);\n root.appendChild(kd);\n\n // Özel durumlar çubuğu — gerçek special ile aynı dolgu/kenar\n const special = document.createElement('div');\n special.className = 'gengage-chat-comparison-special gengage-chat-comparison-special--skeleton';\n const specialInner = document.createElement('div');\n specialInner.className =\n 'gengage-chat-comparison-skeleton-shimmer gengage-chat-comparison-skeleton-special-line';\n special.appendChild(specialInner);\n root.appendChild(special);\n\n // Tablo özeti — thead (görsel + isim + fiyat) + birkaç attribute satırı\n const tableWrap = document.createElement('div');\n tableWrap.className = 'gengage-chat-comparison-skeleton-table-wrap';\n const thead = document.createElement('div');\n thead.className = 'gengage-chat-comparison-skeleton-table-head';\n const corner = document.createElement('div');\n corner.className = 'gengage-chat-comparison-skeleton-table-corner';\n thead.appendChild(corner);\n for (let c = 0; c < 3; c++) {\n const col = document.createElement('div');\n col.className = 'gengage-chat-comparison-skeleton-table-col';\n const thImg = document.createElement('div');\n thImg.className = 'gengage-chat-comparison-skeleton-shimmer gengage-chat-comparison-skeleton-table-th-img';\n const thName = document.createElement('div');\n thName.className = 'gengage-chat-comparison-skeleton-shimmer gengage-chat-comparison-skeleton-table-th-name';\n const thPrice = document.createElement('div');\n thPrice.className =\n 'gengage-chat-comparison-skeleton-shimmer gengage-chat-comparison-skeleton-table-th-price';\n col.appendChild(thImg);\n col.appendChild(thName);\n col.appendChild(thPrice);\n thead.appendChild(col);\n }\n tableWrap.appendChild(thead);\n for (let r = 0; r < 3; r++) {\n const row = document.createElement('div');\n row.className = 'gengage-chat-comparison-skeleton-table-row';\n const labelCell = document.createElement('div');\n labelCell.className = 'gengage-chat-comparison-skeleton-shimmer gengage-chat-comparison-skeleton-table-label';\n row.appendChild(labelCell);\n for (let c = 0; c < 3; c++) {\n const cell = document.createElement('div');\n cell.className = 'gengage-chat-comparison-skeleton-shimmer gengage-chat-comparison-skeleton-table-cell';\n row.appendChild(cell);\n }\n tableWrap.appendChild(row);\n }\n root.appendChild(tableWrap);\n\n skeleton.appendChild(root);\n break;\n }\n default: {\n skeleton.appendChild(panelStatus);\n // Generic: 3 blocks (existing behavior)\n for (let i = 0; i < 3; i++) {\n const block = document.createElement('div');\n block.className = 'gengage-chat-panel-skeleton-block';\n skeleton.appendChild(block);\n }\n break;\n }\n }\n\n this._panelEl.appendChild(skeleton);\n this._panelEl.appendChild(this._thumbnailsColumn.getElement());\n this._panelEl.appendChild(this._panelFloatingEl);\n if (!this._panelVisible) {\n this._panelVisible = true;\n this._panelEl.classList.add('gengage-chat-panel--visible');\n this.root.classList.add('gengage-chat-drawer--with-panel');\n }\n this._syncDividerPreview();\n this._emitHostShellSync();\n }\n\n /** Update the panel top bar navigation state. */\n updatePanelTopBar(canBack: boolean, canForward: boolean, title: string): void {\n // On mobile the back button always closes the side-panel overlay, so keep it active\n const isMobile = this._options.getMobileViewport?.() ?? false;\n this._panelTopBar.update(isMobile ? true : canBack, canForward, title);\n const contentEl = this.getPanelContentElement();\n if (contentEl) {\n this._syncPanelTopBarTitleFromContent(contentEl);\n }\n }\n\n getPanelTopBarTitle(): string {\n return this._panelTopBar.getTitle();\n }\n\n /** Update the favorites badge count. Pass 0 to hide the badge. */\n updateFavoritesBadge(count: number): void {\n if (!this._favBadgeEl) return;\n if (count > 0) {\n this._favBadgeEl.textContent = count > 99 ? '99+' : String(count);\n this._favBadgeEl.style.display = '';\n } else {\n this._favBadgeEl.style.display = 'none';\n }\n }\n\n /**\n * Hide the panel and clear its content. Always hides — even in force-expanded mode.\n * Callers: _hideDrawer (stale panel cleanup), stream onDone (loading skeleton cleanup),\n * thread navigation (no snapshot to restore). All require full hide.\n * Keeps `_panelCollapsed` untouched so user collapse preference survives future panel renders.\n */\n clearPanel(): void {\n this._destroyLoadingBinding(this._panelLoadingBinding);\n this._panelLoadingBinding = null;\n this._destroyLoadingBinding(this._panelAiZoneLoadingBinding);\n this._panelAiZoneLoadingBinding = null;\n this._panelEl.innerHTML = '';\n this._resetPanelAiZoneElement();\n this._panelEl.appendChild(this._panelTopBar.getElement());\n this._panelEl.appendChild(this._panelAiZoneEl);\n this._panelEl.appendChild(this._thumbnailsColumn.getElement());\n this._panelEl.appendChild(this._panelFloatingEl);\n this._panelTopBar.setActions(null);\n this._panelVisible = false;\n this._panelEl.classList.remove('gengage-chat-panel--visible', 'gengage-chat-panel--collapsed');\n this.root.classList.remove('gengage-chat-drawer--with-panel');\n this._dividerEl.classList.add('gengage-chat-panel-divider--hidden');\n this._dividerPreviewEnabled = false;\n this._syncDividerPreview();\n if (this._reopenPanelBtn) this._reopenPanelBtn.style.display = 'none';\n this.setComparisonDockContent(null);\n this._emitHostShellSync();\n }\n\n /**\n * Mobile-only slot (see CSS): pins the comparison dock above panel scroll.\n * Pass null to clear.\n */\n setComparisonDockContent(el: HTMLElement | null): void {\n this._comparisonDockSlotEl.replaceChildren();\n if (el) this._comparisonDockSlotEl.appendChild(el);\n }\n\n /**\n * On mobile: hide the side panel overlay without clearing its content.\n * Shows the reopen button in the header so the user can slide the panel back in.\n */\n hideMobilePanel(): void {\n if (!this._panelVisible) return;\n this._panelVisible = false;\n this._panelEl.classList.remove('gengage-chat-panel--visible');\n if (this._reopenPanelBtn) this._reopenPanelBtn.style.display = 'flex';\n this._emitHostShellSync();\n }\n\n private _showMobilePanelFromBtn(): void {\n if (this._panelVisible) return;\n this._panelVisible = true;\n this._panelEl.classList.add('gengage-chat-panel--visible');\n if (this._reopenPanelBtn) this._reopenPanelBtn.style.display = 'none';\n this._emitHostShellSync();\n }\n\n /** Expand panel without locking — user can still toggle via divider. */\n expandPanel(): void {\n this._panelCollapsed = false;\n this._panelEl.classList.remove('gengage-chat-panel--collapsed');\n if (!this._panelVisible) {\n this._panelVisible = true;\n this._panelEl.classList.add('gengage-chat-panel--visible');\n this.root.classList.add('gengage-chat-drawer--with-panel');\n }\n this._syncDividerPreview();\n this._emitHostShellSync();\n }\n\n /**\n * Ensure the panel starts expanded (panelMode: 'expanded').\n * Users can still collapse/expand via the divider chevron.\n */\n setForceExpanded(): void {\n this._panelCollapsed = false;\n this._panelEl.classList.remove('gengage-chat-panel--collapsed');\n // Show panel immediately even if empty\n if (!this._panelVisible) {\n this._panelVisible = true;\n this._panelEl.classList.add('gengage-chat-panel--visible');\n this.root.classList.add('gengage-chat-drawer--with-panel');\n }\n this._dividerEl.classList.remove('gengage-chat-panel-divider--hidden');\n this._syncDividerPreview();\n this._emitHostShellSync();\n }\n\n /**\n * After new list/grid content is mounted, scroll the left panel toward the top smoothly.\n * InnerHTML resets scrollTop to 0, so we nudge down first; a rAF tween (ease-out quint) replaces\n * native smooth scroll for a softer deceleration.\n */\n private _smoothScrollPanelListToTop(): void {\n const panel = this._panelEl;\n const reduceMotion =\n typeof window !== 'undefined' && (window.matchMedia?.('(prefers-reduced-motion: reduce)')?.matches ?? false);\n\n if (reduceMotion) {\n panel.scrollTop = 0;\n return;\n }\n\n this._panelListScrollAnimToken += 1;\n const token = this._panelListScrollAnimToken;\n\n requestAnimationFrame(() => {\n if (token !== this._panelListScrollAnimToken) return;\n const maxScroll = Math.max(0, panel.scrollHeight - panel.clientHeight);\n if (maxScroll <= 0) return;\n\n const startTop = Math.min(160, Math.max(48, maxScroll * 0.28));\n panel.scrollTop = startTop;\n\n const durationMs = Math.min(720, Math.max(380, 320 + Math.sqrt(startTop) * 28));\n const t0 = performance.now();\n\n const easeOutQuint = (t: number) => 1 - (1 - t) ** 5;\n\n const step = (now: number) => {\n if (token !== this._panelListScrollAnimToken) return;\n const elapsed = now - t0;\n const linear = Math.min(1, elapsed / durationMs);\n const eased = easeOutQuint(linear);\n panel.scrollTop = startTop * (1 - eased);\n if (linear < 1) {\n requestAnimationFrame(step);\n } else {\n panel.scrollTop = 0;\n }\n };\n requestAnimationFrame(step);\n });\n }\n\n /** Update scroll affordance (bottom fade gradient) on the panel. */\n private _updateScrollAffordance(): void {\n const panel = this._panelEl;\n const atBottom = panel.scrollTop + panel.clientHeight >= panel.scrollHeight - 10;\n panel.classList.toggle('gengage-chat-panel--has-scroll', !atBottom && panel.scrollHeight > panel.clientHeight);\n panel.classList.toggle('gengage-chat-panel--scrolled', panel.scrollTop > 88);\n }\n\n /** Horizontal swipe on conversation/panel areas to toggle the panel (mobile only). */\n private _setupHorizontalSwipe(el: HTMLElement): void {\n let startX = 0;\n let startY = 0;\n\n const onTouchStart = (e: TouchEvent) => {\n if (!(this._options.getMobileViewport?.() ?? window.innerWidth <= 768)) return;\n const t = e.touches[0];\n if (!t) return;\n startX = t.clientX;\n startY = t.clientY;\n };\n\n const onTouchEnd = (e: TouchEvent) => {\n if (!(this._options.getMobileViewport?.() ?? window.innerWidth <= 768)) return;\n const t = e.changedTouches[0];\n if (!t) return;\n const dx = t.clientX - startX;\n const dy = t.clientY - startY;\n // Only trigger if horizontal movement > 50px and dominant direction\n if (Math.abs(dx) > 50 && Math.abs(dx) > Math.abs(dy) * 2) {\n this.togglePanel();\n this._onPanelToggle?.();\n }\n };\n\n el.addEventListener('touchstart', onTouchStart, { passive: true });\n el.addEventListener('touchend', onTouchEnd, { passive: true });\n this._cleanups.push(() => {\n el.removeEventListener('touchstart', onTouchStart);\n el.removeEventListener('touchend', onTouchEnd);\n });\n }\n\n /** Toggle panel between collapsed and expanded. */\n togglePanel(): void {\n this.setPanelCollapsed(!this._panelCollapsed);\n }\n\n /** Whether the panel is currently collapsed by the user. */\n isPanelCollapsed(): boolean {\n return this._panelCollapsed;\n }\n\n /** Programmatically set panel collapsed state. */\n setPanelCollapsed(collapsed: boolean): void {\n this._panelCollapsed = collapsed;\n if (collapsed) {\n this._panelEl.classList.add('gengage-chat-panel--collapsed');\n } else {\n this._panelEl.classList.remove('gengage-chat-panel--collapsed');\n }\n const chevronBtn = this._dividerEl.querySelector('.gengage-chat-panel-divider-toggle');\n if (chevronBtn) {\n chevronBtn.textContent = collapsed ? '\\u00AB' : '\\u00BB'; // « (expand left) or » (collapse right)\n }\n this._syncDividerPreview();\n }\n\n setDividerPreviewEnabled(enabled: boolean): void {\n this._dividerPreviewEnabled = enabled;\n this._syncDividerPreview();\n }\n\n /** Save panel collapsed state to sessionStorage. */\n persistPanelState(accountId: string): void {\n try {\n const key = `gengage:panel:${accountId}`;\n if (this._panelCollapsed) {\n sessionStorage.setItem(key, 'collapsed');\n } else {\n sessionStorage.removeItem(key);\n }\n } catch {\n // sessionStorage may be unavailable in restricted environments\n }\n }\n\n /** Restore panel collapsed state from sessionStorage. Returns true when restored as collapsed. */\n restorePanelState(accountId: string): boolean {\n try {\n const key = `gengage:panel:${accountId}`;\n if (sessionStorage.getItem(key) === 'collapsed') {\n this._panelCollapsed = true;\n return true;\n }\n } catch {\n // sessionStorage may be unavailable in restricted environments\n }\n return false;\n }\n\n private _createLoadingSequence(\n variant: 'chat' | 'panel',\n steps: string[],\n part: string,\n className: string,\n ): { root: HTMLElement; binding: LoadingSequenceBinding } {\n const root = document.createElement('div');\n root.className = `${className} gds-progress-loader ${variant === 'chat' ? 'gds-progress-loader-chat' : 'gds-progress-loader-panel'}`;\n root.dataset['gengagePart'] = part;\n root.setAttribute('role', 'status');\n root.setAttribute('aria-live', 'polite');\n\n const label = document.createElement('span');\n label.className =\n variant === 'chat'\n ? 'gengage-chat-typing-text gds-progress-label'\n : 'gengage-chat-panel-loading-label gds-progress-label';\n root.appendChild(label);\n\n const dots = document.createElement('span');\n dots.className = variant === 'chat' ? 'gengage-chat-typing-dots gds-progress-dots' : 'gds-progress-dots';\n dots.setAttribute('aria-hidden', 'true');\n for (let i = 0; i < 3; i++) {\n const dot = document.createElement('span');\n dot.className = 'gds-progress-dot';\n dots.appendChild(dot);\n }\n root.appendChild(dots);\n\n const binding: LoadingSequenceBinding = {\n labelEl: label,\n steps: [],\n index: 0,\n intervalId: null,\n };\n this._applyLoadingSteps(binding, steps);\n return { root, binding };\n }\n\n private _applyLoadingSteps(binding: LoadingSequenceBinding, steps: string[], forceLatest = false): void {\n const normalized = steps\n .map((s) => s.trim())\n .filter(Boolean)\n .slice(-3);\n const fallback = [this.i18n.loadingMessage];\n binding.steps = normalized.length > 0 ? normalized : fallback;\n this._clearLoadingBindingInterval(binding);\n binding.index = forceLatest ? binding.steps.length - 1 : 0;\n binding.labelEl.textContent = binding.steps[binding.index]!;\n\n if (!forceLatest && binding.steps.length > 1) {\n binding.intervalId = setInterval(() => {\n if (binding.index >= binding.steps.length - 1) {\n this._clearLoadingBindingInterval(binding);\n return;\n }\n binding.index += 1;\n binding.labelEl.textContent = binding.steps[binding.index]!;\n if (binding.index >= binding.steps.length - 1) {\n this._clearLoadingBindingInterval(binding);\n }\n }, LOADING_STEP_INTERVAL_MS);\n }\n }\n\n private _clearLoadingBindingInterval(binding: LoadingSequenceBinding | null): void {\n if (binding?.intervalId) {\n clearInterval(binding.intervalId);\n binding.intervalId = null;\n }\n }\n\n private _destroyLoadingBinding(binding: LoadingSequenceBinding | null): void {\n this._clearLoadingBindingInterval(binding);\n }\n\n private _updateSendEnabled(): void {\n if (this._sendStopHandler) {\n this.sendBtn.disabled = false;\n return;\n }\n const hasContent = this.inputEl.value.trim().length > 0 || this._pendingAttachment !== null;\n this.sendBtn.disabled = !hasContent;\n }\n\n private _renderSendButtonIcon(mode: 'send' | 'stop'): void {\n this.sendBtn.innerHTML =\n mode === 'stop'\n ? '<span class=\"gengage-chat-send-stop-icon\" aria-hidden=\"true\"></span>'\n : `<svg viewBox=\"0 0 24 24\" fill=\"currentColor\" aria-hidden=\"true\"><path d=\"M2.01 21L23 12 2.01 3 2 10l15 2-15 2z\"/></svg>`;\n }\n\n private _submit(): void {\n const text = this.inputEl.value.trim();\n const attachment = this._pendingAttachment;\n if (!text && !attachment) return;\n // Match send-button behaviour: while \"Stop generating\" is active, Enter must run\n // the same abort + cleanup as Stop before starting a new request. Otherwise the\n // prior stream's placeholder and bridge state drift from _sendAction's abort.\n if (this._sendStopHandler) {\n const onStop = this._sendStopHandler;\n this.hideStopButton();\n onStop();\n }\n this.onSend(text, attachment ?? undefined);\n this.inputEl.value = '';\n this.inputEl.style.height = 'auto'; // Reset textarea height after submit\n this.clearAttachment();\n this._updateSendEnabled();\n }\n\n private _toggleVoice(): void {\n if (!this._voiceInput) return;\n if (this._voiceInput.state === 'listening') {\n const text = this._voiceInput.stop();\n if (text.trim()) {\n this.inputEl.value = text;\n this._submit();\n }\n } else {\n this.inputEl.value = '';\n this._voiceInput.start();\n }\n }\n\n /** Lock auto-scroll for 500ms after session history restore to prevent visual jump. */\n lockScrollForRestore(): void {\n this._scrollLockedUntil = Date.now() + 500;\n }\n\n /** Scroll to bottom only if user hasn't scrolled up. Force=true always scrolls. */\n private _scrollToBottom(force = false): void {\n if (!force && this._userScrolledUp) return;\n if (!force && this._options.presentation?.shouldBlockSoftAutoScroll?.()) return;\n if (!force && Date.now() < this._scrollLockedUntil) return;\n requestAnimationFrame(() => {\n this.messagesEl.scrollTop = this.messagesEl.scrollHeight;\n this._userScrolledUp = false;\n });\n }\n\n /** Public method for typewriter ticks — scrolls only if user is near bottom. */\n scrollToBottomIfNeeded(): void {\n this._scrollToBottom(false);\n }\n\n /** Update a bot message's text content in the DOM (e.g. for fallback messages). */\n updateBotMessage(messageId: string, html: string): void {\n const bubble = this.messagesEl.querySelector(`[data-message-id=\"${CSS.escape(messageId)}\"]`);\n if (!bubble) return;\n let textEl = bubble.querySelector('.gengage-chat-bubble-text');\n if (!textEl) {\n textEl = document.createElement('div');\n textEl.className = 'gengage-chat-bubble-text';\n bubble.appendChild(textEl);\n }\n textEl.innerHTML = sanitizeHtml(html);\n this._scrollToBottom(false);\n }\n\n /** Mark a message as the first bot message in its thread (for special styling). */\n markFirstBotMessage(messageId: string): void {\n this._firstBotMessageIds.add(messageId);\n const bubble = this.messagesEl.querySelector(`[data-message-id=\"${CSS.escape(messageId)}\"]`);\n if (bubble) {\n bubble.classList.add('gengage-chat-bubble--first');\n }\n }\n\n /** Scroll to the first message of the last thread (for restore targeting). */\n scrollToLastThread(): void {\n const allBubbles = this.messagesEl.querySelectorAll('[data-thread-id]');\n if (allBubbles.length === 0) {\n this._scrollToBottom(true);\n return;\n }\n const lastThreadId = allBubbles[allBubbles.length - 1]!.getAttribute('data-thread-id');\n if (!lastThreadId) {\n this._scrollToBottom(true);\n return;\n }\n this._programmaticScrollUntil = Date.now() + 700;\n const target = this.messagesEl.querySelector(`[data-thread-id=\"${CSS.escape(lastThreadId)}\"]`);\n if (target) {\n requestAnimationFrame(() => {\n target.scrollIntoView({ block: 'start', behavior: 'auto' });\n this._userScrolledUp = false;\n });\n } else {\n this._scrollToBottom(true);\n }\n }\n\n /**\n * Smooth scroll transcript so the given thread’s first bubble is near the top.\n * Used by centralized presentation scroll requests.\n */\n scrollThreadIntoView(threadId: string, behavior: ScrollBehavior = 'smooth'): boolean {\n const matches = this.messagesEl.querySelectorAll(`[data-thread-id=\"${CSS.escape(threadId)}\"]`);\n let target: HTMLElement | null = null;\n for (let i = 0; i < matches.length; i++) {\n const el = matches[i];\n if (!(el instanceof HTMLElement)) continue;\n if (el.classList.contains('gengage-chat-bubble--presentation-collapsed')) continue;\n target = el;\n break;\n }\n if (!target && matches.length > 0 && matches[0] instanceof HTMLElement) {\n target = matches[0];\n }\n if (!target) return false;\n const topInset = 20;\n const nextTop = Math.max(target.offsetTop - topInset, 0);\n this._programmaticScrollUntil = Date.now() + 700;\n this._scrollMessagesTo(nextTop, behavior);\n return true;\n }\n\n /** Programmatic scroll to bottom (e.g. host bridge) — bypasses “user scrolled up” until next frame. */\n scrollToBottomPresentation(behavior: ScrollBehavior = 'smooth'): void {\n this._programmaticScrollUntil = Date.now() + 700;\n requestAnimationFrame(() => {\n this._scrollMessagesTo(this.messagesEl.scrollHeight, behavior);\n this._userScrolledUp = false;\n });\n }\n\n private _scrollMessagesTo(top: number, behavior: ScrollBehavior): void {\n if (typeof this.messagesEl.scrollTo === 'function') {\n this.messagesEl.scrollTo({ top, behavior });\n return;\n }\n this.messagesEl.scrollTop = top;\n }\n\n /** Collapse transcript to a single thread (null = show full history). */\n setPresentationFocus(threadId: string | null): void {\n this._presentationFocusThreadId = threadId;\n this._applyPresentationCollapsed();\n }\n\n setFormerMessagesButtonVisible(visible: boolean): void {\n if (this._formerMessagesBtn) {\n this._formerMessagesBtn.style.display = visible ? '' : 'none';\n }\n }\n\n private _applyPresentationCollapsed(): void {\n const focus = this._presentationFocusThreadId;\n this.messagesEl.querySelectorAll<HTMLElement>('[data-thread-id]').forEach((el) => {\n const tid = el.dataset['threadId'];\n if (!tid) return;\n if (focus && tid !== focus) {\n el.classList.add('gengage-chat-bubble--presentation-collapsed');\n } else {\n el.classList.remove('gengage-chat-bubble--presentation-collapsed');\n }\n });\n }\n\n /** Call after inline chat DOM (e.g. ProductSummaryCard) is appended — reapplies thread collapse. */\n refreshPresentationCollapsed(): void {\n if (this._presentationFocusThreadId) {\n this._applyPresentationCollapsed();\n }\n }\n\n /** Set compact input-area chips (search/info/review shortcuts above input). */\n setInputAreaChips(chips: Array<{ label: string; onAction: () => void; icon?: string }>): void {\n this._inputChipsEl.innerHTML = '';\n if (chips.length === 0) {\n this._inputChipsEl.style.display = 'none';\n return;\n }\n this._inputChipsEl.style.display = '';\n for (const chip of chips) {\n const btn = document.createElement('button');\n btn.className = 'gengage-chat-input-chip gds-chip';\n btn.type = 'button';\n\n // Icon (SVG from icon map, falls back to generic arrow for unknown names)\n if (chip.icon) {\n const svgHtml = SUGGESTED_ACTION_ICONS[chip.icon] ?? DEFAULT_ACTION_ICON;\n const iconSpan = document.createElement('span');\n iconSpan.className = 'gengage-chat-input-chip-icon';\n iconSpan.innerHTML = svgHtml;\n btn.appendChild(iconSpan);\n }\n\n const label = document.createElement('span');\n label.textContent = chip.label;\n btn.appendChild(label);\n\n btn.addEventListener('click', () => chip.onAction());\n this._inputChipsEl.appendChild(btn);\n }\n }\n\n /** Clear input-area chips. */\n clearInputAreaChips(): void {\n this._inputChipsEl.innerHTML = '';\n this._inputChipsEl.style.display = 'none';\n }\n\n setThumbnails(entries: ThumbnailEntry[]): void {\n this._thumbnailsColumn.setEntries(entries);\n if (entries.length > 0) {\n this._thumbnailsColumn.show();\n } else {\n this._thumbnailsColumn.hide();\n }\n this._renderDividerPreview(entries);\n this._syncDividerPreview();\n }\n\n hideThumbnails(): void {\n this._thumbnailsColumn.hide();\n this._renderDividerPreview([]);\n this._syncDividerPreview();\n }\n\n private _renderDividerPreview(entries: ThumbnailEntry[]): void {\n this._dividerPreviewEl.innerHTML = '';\n\n const seen = new Set<string>();\n const previewEntries: ThumbnailEntry[] = [];\n for (let i = entries.length - 1; i >= 0; i--) {\n const entry = entries[i];\n if (!entry || seen.has(entry.sku) || !isSafeImageUrl(entry.imageUrl)) continue;\n seen.add(entry.sku);\n previewEntries.push(entry);\n if (previewEntries.length >= 3) break;\n }\n previewEntries.reverse();\n\n for (const entry of previewEntries) {\n const thumb = document.createElement('span');\n thumb.className = 'gengage-chat-panel-divider-preview-thumb';\n const img = document.createElement('img');\n img.className = 'gengage-chat-panel-divider-preview-img';\n img.src = entry.imageUrl;\n img.alt = '';\n img.width = 48;\n img.height = 48;\n thumb.appendChild(img);\n this._dividerPreviewEl.appendChild(thumb);\n }\n }\n\n private _syncDividerPreview(): void {\n const hasPreview = this._dividerPreviewEl.childElementCount > 0;\n const isActive =\n this._dividerPreviewEnabled &&\n hasPreview &&\n this._panelCollapsed &&\n !this._dividerEl.classList.contains('gengage-chat-panel-divider--hidden');\n this._dividerEl.classList.toggle('gengage-chat-panel-divider--preview-active', isActive);\n }\n\n /** Activate focus trap — Tab/Shift+Tab cycles within the drawer. */\n trapFocus(): void {\n this._previouslyFocusedElement = document.activeElement as HTMLElement | null;\n this.releaseFocus();\n\n const handler = (e: KeyboardEvent) => {\n if (e.key !== 'Tab') return;\n const focusable = this.root.querySelectorAll<HTMLElement>(\n 'a[href], button:not([disabled]), textarea:not([disabled]), input:not([disabled]), select:not([disabled]), [tabindex]:not([tabindex=\"-1\"])',\n );\n if (focusable.length === 0) return;\n const first = focusable[0]!;\n const last = focusable[focusable.length - 1]!;\n\n // Use getRootNode() to resolve activeElement inside Shadow DOM\n const rootNode = this.root.getRootNode();\n const active = rootNode instanceof ShadowRoot ? rootNode.activeElement : document.activeElement;\n\n if (e.shiftKey) {\n if (active === first || !this.root.contains(active)) {\n e.preventDefault();\n last.focus();\n }\n } else {\n if (active === last || !this.root.contains(active)) {\n e.preventDefault();\n first.focus();\n }\n }\n };\n\n this._focusTrapHandler = handler;\n this.root.addEventListener('keydown', handler);\n }\n\n /** Release the focus trap and restore previously focused element. */\n releaseFocus(): void {\n if (this._focusTrapHandler) {\n this.root.removeEventListener('keydown', this._focusTrapHandler);\n this._focusTrapHandler = null;\n }\n if (this._previouslyFocusedElement) {\n try {\n this._previouslyFocusedElement.focus();\n } catch {\n // Element may no longer be in the DOM\n }\n this._previouslyFocusedElement = null;\n }\n }\n\n /** Briefly animate the cart icon button to signal a successful add-to-cart. */\n flashCartBadge(): void {\n if (!this._cartBtn) return;\n // Restart animation by removing then re-adding the class after a reflow\n this._cartBtn.classList.remove('gengage-chat-header-btn--cart-flash');\n void this._cartBtn.offsetWidth;\n this._cartBtn.classList.add('gengage-chat-header-btn--cart-flash');\n this._cartBtn.addEventListener(\n 'animationend',\n () => {\n this._cartBtn?.classList.remove('gengage-chat-header-btn--cart-flash');\n },\n { once: true },\n );\n }\n\n /** Show a temporary success toast inside the shadow root. */\n showCartToast(message: string): void {\n const existing = this.root.querySelector('.gengage-chat-cart-toast');\n existing?.remove();\n const toast = document.createElement('div');\n toast.className = 'gengage-chat-cart-toast';\n toast.setAttribute('role', 'status');\n toast.setAttribute('aria-live', 'polite');\n toast.textContent = message;\n this.root.appendChild(toast);\n // Force reflow then add visible class for animation\n void toast.offsetWidth;\n toast.classList.add('gengage-chat-cart-toast--visible');\n setTimeout(() => {\n toast.classList.remove('gengage-chat-cart-toast--visible');\n setTimeout(() => toast.remove(), 300);\n }, 2500);\n }\n\n /** Clean up event listeners and child resources (VoiceInput, timers). */\n destroy(): void {\n registerChatScrollElement(null);\n this.releaseFocus();\n if (this._resizeRafId !== null) {\n cancelAnimationFrame(this._resizeRafId);\n this._resizeRafId = null;\n }\n this._destroyLoadingBinding(this._typingLoadingBinding);\n this._typingLoadingBinding = null;\n this._destroyLoadingBinding(this._panelLoadingBinding);\n this._panelLoadingBinding = null;\n this._destroyLoadingBinding(this._panelAiZoneLoadingBinding);\n this._panelAiZoneLoadingBinding = null;\n this._closeAttachMenu();\n for (const cleanup of this._cleanups) cleanup();\n this._cleanups.length = 0;\n this._voiceInput?.destroy();\n this._voiceInput = null;\n }\n}\n","export interface LauncherOptions {\n onClick: () => void;\n svgMarkup?: string;\n /** Full-size image URL — renders launcher as an image button (no circular bg). */\n imageUrl?: string;\n ariaLabel?: string;\n hideMobile?: boolean;\n mobileBreakpoint?: number;\n tooltip?: string;\n}\n\n/**\n * Result of createLauncher — the container wraps the button and exposes\n * content-area slots where the QNA widget or host page can inject engagement\n * actions (buying-hesitation questions, \"Find Similar\" buttons, etc.).\n */\nexport interface LauncherElements {\n /** Outer container — append this to the DOM. */\n container: HTMLElement;\n /** The clickable FAB button. */\n button: HTMLButtonElement;\n /** Slot above the button (primary QNA actions). */\n contentArea: HTMLElement;\n /** Slot below the button (secondary content). */\n contentAreaBottom: HTMLElement;\n}\n\nconst DEFAULT_SVG = `<svg width=\"28\" height=\"28\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <rect x=\"3\" y=\"7\" width=\"18\" height=\"13\" rx=\"3\" fill=\"currentColor\" opacity=\"0.15\"/>\n <rect x=\"3\" y=\"7\" width=\"18\" height=\"13\" rx=\"3\" stroke=\"currentColor\" stroke-width=\"1.5\"/>\n <circle cx=\"9\" cy=\"13\" r=\"1.5\" fill=\"currentColor\"/>\n <circle cx=\"15\" cy=\"13\" r=\"1.5\" fill=\"currentColor\"/>\n <path d=\"M9.5 17C10.3 17.6 11.1 18 12 18C12.9 18 13.7 17.6 14.5 17\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\"/>\n <path d=\"M12 7V4\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\"/>\n <circle cx=\"12\" cy=\"3\" r=\"1\" fill=\"currentColor\"/>\n <path d=\"M1 12V14\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\"/>\n <path d=\"M23 12V14\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\"/>\n</svg>`;\n\nexport function createLauncher(options: LauncherOptions): LauncherElements {\n // Container holds content areas + button\n const container = document.createElement('div');\n container.className = 'gengage-chat-launcher-container';\n container.dataset['gengagePart'] = 'chat-launcher-container';\n\n // Content area above button (QNA actions, buying-hesitation questions)\n const contentArea = document.createElement('div');\n contentArea.className = 'gengage-chat-launcher-content-area';\n contentArea.dataset['gengagePart'] = 'chat-launcher-content-primary';\n container.appendChild(contentArea);\n\n // The FAB button\n const button = document.createElement('button');\n button.type = 'button';\n button.setAttribute('aria-label', options.ariaLabel ?? 'Open chat');\n\n if (options.imageUrl) {\n button.className = 'gengage-chat-launcher gengage-chat-launcher--image-mode';\n const img = document.createElement('img');\n img.src = options.imageUrl;\n img.alt = '';\n img.draggable = false;\n button.appendChild(img);\n } else {\n button.className = 'gengage-chat-launcher';\n button.innerHTML = options.svgMarkup ?? DEFAULT_SVG;\n }\n button.dataset['gengagePart'] = 'chat-launcher-button';\n\n if (options.tooltip !== undefined) {\n const tooltipEl = document.createElement('span');\n tooltipEl.className = 'gengage-chat-launcher-tooltip';\n tooltipEl.dataset['gengagePart'] = 'chat-launcher-tooltip';\n tooltipEl.textContent = options.tooltip;\n button.appendChild(tooltipEl);\n }\n\n if (options.hideMobile) {\n container.dataset['hideMobile'] = '1';\n }\n if (options.mobileBreakpoint !== undefined) {\n container.dataset['mobileBreakpoint'] = String(options.mobileBreakpoint);\n }\n\n button.addEventListener('click', options.onClick);\n container.appendChild(button);\n\n // Content area below button (secondary content)\n const contentAreaBottom = document.createElement('div');\n contentAreaBottom.className = 'gengage-chat-launcher-content-area-bottom';\n contentAreaBottom.dataset['gengagePart'] = 'chat-launcher-content-secondary';\n container.appendChild(contentAreaBottom);\n\n return { container, button, contentArea, contentAreaBottom };\n}\n","const ALLOWED_AUDIO_TYPES = new Set([\n 'audio/ogg',\n 'audio/mpeg',\n 'audio/mp3',\n 'audio/wav',\n 'audio/webm',\n 'audio/aac',\n 'audio/mp4',\n]);\n\n/** Active audio elements tracked for bulk cleanup. */\nconst activeAudioElements = new Set<HTMLAudioElement>();\n\n/** Release an audio element: pause, revoke, and remove from tracking set. */\nfunction releaseAudio(audio: HTMLAudioElement): void {\n audio.pause();\n audio.removeAttribute('src');\n audio.load(); // Releases the media resource\n activeAudioElements.delete(audio);\n}\n\n/** Returned by `playTtsAudio` on successful playback initiation. */\nexport interface AudioHandle {\n /** Stop playback immediately. Safe to call multiple times. */\n stop: () => void;\n}\n\n/**\n * Plays a base64-encoded audio clip.\n * Returns an `AudioHandle` that can stop playback, or `null` if playback\n * could not be initiated (blocked by browser, unsupported environment, etc.).\n */\nexport function playTtsAudio(base64: string, contentType = 'audio/ogg'): AudioHandle | null {\n // Strip parameters like '; codecs=opus' before checking allowlist\n const baseType = contentType.split(';')[0]!.trim();\n if (!ALLOWED_AUDIO_TYPES.has(baseType)) return null;\n try {\n const audio = new Audio(`data:${contentType};base64,${base64}`);\n activeAudioElements.add(audio);\n audio.addEventListener('ended', () => releaseAudio(audio), { once: true });\n audio.play().catch(() => {\n // Autoplay blocked by browser — release immediately\n releaseAudio(audio);\n });\n return {\n stop: () => releaseAudio(audio),\n };\n } catch {\n // Unsupported environment\n return null;\n }\n}\n\n/** Stop and release all active TTS audio elements. */\nexport function destroyAllTtsAudio(): void {\n for (const audio of activeAudioElements) {\n releaseAudio(audio);\n }\n}\n","/**\n * ComparisonTable — renders a product comparison table with recommended pick,\n * attribute rows, highlights, and optional special cases.\n *\n * XSS safety: All text is set via textContent. Image URLs are validated\n * for safe protocols before being assigned to img.src.\n */\n\nimport { sanitizeHtml, isSafeImageUrl } from '../../common/safe-html.js';\nimport { formatPrice } from '../../common/price-formatter.js';\nimport type { PriceFormatConfig } from '../../common/price-formatter.js';\n\n/**\n * Fallback display names for common e-commerce product attributes.\n * Used when the backend sends raw field names (e.g., \"screen_size\")\n * and no locale-specific criteriaLabels map is provided via i18n.\n */\nconst CRITERIA_DISPLAY_NAMES: Record<string, string> = {\n screen_size: 'Screen Size',\n weight: 'Weight',\n battery_capacity: 'Battery Capacity',\n battery_life: 'Battery Life',\n storage: 'Storage',\n memory: 'Memory',\n ram: 'RAM',\n processor: 'Processor',\n camera: 'Camera',\n resolution: 'Resolution',\n display_type: 'Display Type',\n refresh_rate: 'Refresh Rate',\n color: 'Color',\n material: 'Material',\n dimensions: 'Dimensions',\n warranty: 'Warranty',\n connectivity: 'Connectivity',\n water_resistance: 'Water Resistance',\n operating_system: 'Operating System',\n brand: 'Brand',\n model: 'Model',\n price: 'Price',\n energy_class: 'Energy Class',\n noise_level: 'Noise Level',\n capacity: 'Capacity',\n power: 'Power',\n voltage: 'Voltage',\n width: 'Width',\n height: 'Height',\n depth: 'Depth',\n};\n\n/**\n * Map a raw criteria field name to a human-readable label.\n * Checks locale-specific `criteriaLabels` first (from i18n), then the\n * built-in fallback map, then applies a formatting heuristic.\n */\nexport function formatCriteriaName(rawName: string, criteriaLabels?: Record<string, string>): string {\n return (\n criteriaLabels?.[rawName] ??\n CRITERIA_DISPLAY_NAMES[rawName] ??\n rawName.replace(/_/g, ' ').replace(/^\\w/, (c) => c.toUpperCase())\n );\n}\n\nexport interface ComparisonProduct {\n sku: string;\n name: string;\n price: string;\n imageUrl?: string | undefined;\n rating?: number | undefined;\n reviewCount?: number | undefined;\n}\n\nexport interface ComparisonAttribute {\n label: string;\n values: string[];\n}\n\nexport interface ComparisonTableI18n {\n comparisonHeading?: string;\n recommendedChoiceLabel?: string;\n highlightsLabel?: string;\n keyDifferencesLabel?: string;\n specialCasesLabel?: string;\n viewMoreLabel?: string;\n addToCartButton?: string;\n /** Locale-specific attribute display names (e.g., { screen_size: 'Screen Size' }). */\n criteriaLabels?: Record<string, string>;\n}\n\nexport interface ComparisonTableOptions {\n recommended: ComparisonProduct;\n products: ComparisonProduct[];\n attributes: ComparisonAttribute[];\n highlights: string[];\n specialCases?: string[] | undefined;\n onProductClick: (params: { sku: string; name: string }) => void;\n onAddToCart?: ((sku: string) => void) | undefined;\n recommendedText?: string | undefined;\n winnerHits?: Record<string, { positive?: string[]; negative?: string[] }> | undefined;\n productActions?: Record<string, { title: string; type: string; payload?: unknown }> | undefined;\n keyDifferencesHtml?: string | undefined;\n i18n?: ComparisonTableI18n | undefined;\n pricing?: PriceFormatConfig | undefined;\n}\n\nfunction hasRenderablePrice(raw: string | undefined): raw is string {\n if (typeof raw !== 'string') return false;\n const num = Number(raw);\n return Number.isFinite(num) && num > 0;\n}\n\nfunction hasRenderableRating(raw: number | string | undefined): raw is number | string {\n const num = typeof raw === 'number' ? raw : typeof raw === 'string' ? Number(raw) : NaN;\n return Number.isFinite(num) && num > 0;\n}\n\nfunction createRatingBadge(raw: number | string): HTMLElement {\n const value = typeof raw === 'number' ? raw : Number(raw);\n const badge = document.createElement('div');\n badge.className = 'gengage-chat-comparison-recommended-rating';\n badge.innerHTML =\n '<span class=\"gengage-chat-comparison-recommended-rating-icon\" aria-hidden=\"true\">' +\n '<svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M12 3.6l2.58 5.23 5.77.84-4.17 4.07.98 5.75L12 16.78l-5.16 2.71.99-5.75L3.66 9.67l5.76-.84L12 3.6z\"/></svg>' +\n '</span>';\n const label = document.createElement('span');\n label.className = 'gengage-chat-comparison-recommended-rating-value';\n label.textContent = value.toFixed(1);\n badge.appendChild(label);\n return badge;\n}\n\nexport function renderComparisonTable(options: ComparisonTableOptions): HTMLElement {\n const { recommended, products, attributes, highlights, specialCases, onProductClick, i18n } = options;\n\n const container = document.createElement('div');\n container.className = 'gengage-chat-comparison';\n container.dataset['gengagePart'] = 'comparison-dialog';\n container.setAttribute('role', 'dialog');\n container.setAttribute('aria-label', i18n?.comparisonHeading ?? 'Comparison Results');\n\n // Recommended card\n if (recommended) {\n const recCard = document.createElement('article');\n recCard.className = 'gengage-chat-comparison-recommended gds-card';\n recCard.dataset['gengagePart'] = 'comparison-recommended-card';\n\n const recLabel = document.createElement('div');\n recLabel.className = 'gengage-chat-comparison-recommended-label';\n recLabel.textContent = i18n?.recommendedChoiceLabel ?? 'Recommended Choice';\n recCard.appendChild(recLabel);\n\n const recBody = document.createElement('div');\n recBody.className = 'gengage-chat-comparison-recommended-body';\n recBody.classList.add('gds-clickable');\n recBody.tabIndex = 0;\n recBody.setAttribute('role', 'button');\n recBody.setAttribute('aria-label', recommended.name);\n\n const media = document.createElement('div');\n media.className = 'gengage-chat-comparison-recommended-media';\n if (recommended.imageUrl && isSafeImageUrl(recommended.imageUrl)) {\n const img = document.createElement('img');\n img.src = recommended.imageUrl;\n img.alt = recommended.name;\n img.loading = 'lazy';\n img.addEventListener(\n 'error',\n () => {\n img.style.display = 'none';\n },\n { once: true },\n );\n media.appendChild(img);\n } else {\n const placeholder = document.createElement('div');\n placeholder.className = 'gengage-chat-comparison-recommended-placeholder';\n placeholder.setAttribute('aria-hidden', 'true');\n media.appendChild(placeholder);\n }\n recBody.appendChild(media);\n\n const info = document.createElement('div');\n info.className = 'gengage-chat-comparison-recommended-info';\n const title = document.createElement('div');\n title.className = 'gengage-chat-comparison-recommended-title';\n title.textContent = recommended.name;\n info.appendChild(title);\n const meta = document.createElement('div');\n meta.className = 'gengage-chat-comparison-recommended-meta';\n if (hasRenderableRating(recommended.rating)) {\n meta.appendChild(createRatingBadge(recommended.rating!));\n }\n if (hasRenderablePrice(recommended.price)) {\n const price = document.createElement('div');\n price.className = 'gengage-chat-comparison-recommended-price';\n price.textContent = formatPrice(recommended.price, options.pricing);\n meta.appendChild(price);\n }\n if (meta.childElementCount > 0) info.appendChild(meta);\n if (options.recommendedText) {\n const recExplanation = document.createElement('p');\n recExplanation.className = 'gengage-chat-comparison-recommended-text';\n recExplanation.innerHTML = sanitizeHtml(options.recommendedText);\n info.appendChild(recExplanation);\n }\n recBody.appendChild(info);\n\n const openRecommended = (): void => {\n onProductClick({ sku: recommended.sku, name: recommended.name });\n };\n recBody.addEventListener('click', openRecommended);\n recBody.addEventListener('keydown', (event) => {\n if (event.key !== 'Enter' && event.key !== ' ') return;\n event.preventDefault();\n openRecommended();\n });\n\n recCard.appendChild(recBody);\n\n // Supporting bullets\n if (highlights.length > 0) {\n const hlSection = document.createElement('div');\n hlSection.className = 'gengage-chat-comparison-highlights';\n hlSection.dataset['gengagePart'] = 'comparison-highlights';\n const hlLabel = document.createElement('div');\n hlLabel.className = 'gengage-chat-comparison-highlights-label';\n hlLabel.textContent = i18n?.highlightsLabel ?? 'Key Highlights';\n hlSection.appendChild(hlLabel);\n const ul = document.createElement('ul');\n for (const hl of highlights) {\n const li = document.createElement('li');\n li.textContent = hl;\n ul.appendChild(li);\n }\n hlSection.appendChild(ul);\n recCard.appendChild(hlSection);\n }\n\n container.appendChild(recCard);\n }\n\n // Key Differences section\n if (options.keyDifferencesHtml) {\n const kdSection = document.createElement('details');\n kdSection.className = 'gengage-chat-comparison-key-differences';\n kdSection.dataset['gengagePart'] = 'comparison-key-differences';\n const kdSummary = document.createElement('summary');\n kdSummary.className = 'gengage-chat-comparison-key-differences-summary';\n const kdLabel = document.createElement('span');\n kdLabel.className = 'gengage-chat-comparison-key-differences-summary-label';\n kdLabel.textContent = i18n?.keyDifferencesLabel ?? 'Key Differences';\n const kdMeta = document.createElement('span');\n kdMeta.className = 'gengage-chat-comparison-key-differences-summary-meta';\n kdMeta.textContent = i18n?.viewMoreLabel ?? 'Show More';\n kdSummary.appendChild(kdLabel);\n kdSummary.appendChild(kdMeta);\n kdSection.appendChild(kdSummary);\n const kdContent = document.createElement('div');\n kdContent.className = 'gengage-chat-comparison-key-differences-content';\n kdContent.innerHTML = sanitizeHtml(formatKeyDifferences(options.keyDifferencesHtml));\n kdSection.appendChild(kdContent);\n container.appendChild(kdSection);\n }\n\n // Special cases (expandable)\n if (specialCases && specialCases.length > 0) {\n const special = document.createElement('details');\n special.className = 'gengage-chat-comparison-special gds-evidence-card gds-evidence-card-warning';\n special.dataset['gengagePart'] = 'comparison-special-cases';\n const summary = document.createElement('summary');\n summary.textContent = i18n?.specialCasesLabel ?? 'For Special Cases';\n special.appendChild(summary);\n const list = document.createElement('ul');\n for (const sc of specialCases) {\n appendSpecialCaseListItems(list, sc);\n }\n if (list.childElementCount > 0) {\n special.appendChild(list);\n }\n container.appendChild(special);\n }\n\n // Comparison table\n if (products.length > 0 && attributes.length > 0) {\n const table = document.createElement('table');\n table.className = 'gengage-chat-comparison-table gds-comparison-table';\n\n // Header row: empty cell + product columns\n const thead = document.createElement('thead');\n const headerRow = document.createElement('tr');\n const emptyTh = document.createElement('th');\n headerRow.appendChild(emptyTh);\n for (const product of products) {\n const th = document.createElement('th');\n if (product.sku === recommended?.sku) {\n th.className = 'gengage-chat-comparison-selected gds-comparison-table-winner-cell';\n }\n const headerCell = document.createElement('div');\n headerCell.className =\n 'gengage-chat-comparison-table-header-cell gengage-chat-comparison-table-header-cell--clickable gds-clickable';\n headerCell.tabIndex = 0;\n headerCell.setAttribute('role', 'button');\n headerCell.setAttribute('aria-label', product.name);\n headerCell.title = product.name;\n const openProduct = (): void => {\n onProductClick({ sku: product.sku, name: product.name });\n };\n headerCell.addEventListener('click', openProduct);\n headerCell.addEventListener('keydown', (event) => {\n if (event.key !== 'Enter' && event.key !== ' ') return;\n event.preventDefault();\n openProduct();\n });\n if (product.imageUrl && isSafeImageUrl(product.imageUrl)) {\n const img = document.createElement('img');\n img.src = product.imageUrl;\n img.alt = product.name;\n img.loading = 'lazy';\n img.addEventListener(\n 'error',\n () => {\n img.style.display = 'none';\n },\n { once: true },\n );\n headerCell.appendChild(img);\n } else {\n const placeholder = document.createElement('div');\n placeholder.className = 'gengage-chat-comparison-table-header-img-placeholder';\n placeholder.setAttribute('aria-hidden', 'true');\n headerCell.appendChild(placeholder);\n }\n const name = document.createElement('div');\n name.className = 'gengage-chat-comparison-table-product-name';\n name.textContent = product.name;\n headerCell.appendChild(name);\n if (hasRenderablePrice(product.price)) {\n const prc = document.createElement('div');\n prc.className = 'gengage-chat-comparison-table-price';\n prc.textContent = formatPrice(product.price, options.pricing);\n headerCell.appendChild(prc);\n }\n th.appendChild(headerCell);\n headerRow.appendChild(th);\n }\n thead.appendChild(headerRow);\n table.appendChild(thead);\n\n // Attribute rows\n const tbody = document.createElement('tbody');\n for (const attr of attributes) {\n const row = document.createElement('tr');\n const labelTd = document.createElement('td');\n labelTd.className = 'gengage-chat-comparison-label';\n labelTd.textContent = formatCriteriaName(attr.label, i18n?.criteriaLabels);\n row.appendChild(labelTd);\n for (let i = 0; i < attr.values.length; i++) {\n const td = document.createElement('td');\n if (products[i]?.sku === recommended?.sku) {\n td.className = 'gengage-chat-comparison-selected gds-comparison-table-winner-cell';\n }\n td.textContent = attr.values[i] ?? '';\n row.appendChild(td);\n }\n tbody.appendChild(row);\n }\n table.appendChild(tbody);\n // Wrap in a scrollable container so the table scrolls independently\n const tableWrapper = document.createElement('div');\n tableWrapper.className = 'gengage-chat-comparison-table-wrapper';\n tableWrapper.dataset['gengagePart'] = 'comparison-table-wrapper';\n tableWrapper.appendChild(table);\n container.appendChild(tableWrapper);\n }\n\n // Focus trap: keep Tab cycling within the comparison dialog\n container.addEventListener('keydown', (e) => {\n if (e.key !== 'Tab') return;\n const focusables = container.querySelectorAll<HTMLElement>(\n 'button, [href], input, [tabindex]:not([tabindex=\"-1\"])',\n );\n if (focusables.length === 0) return;\n const first = focusables[0]!;\n const last = focusables[focusables.length - 1]!;\n if (e.shiftKey && document.activeElement === first) {\n e.preventDefault();\n last.focus();\n } else if (!e.shiftKey && document.activeElement === last) {\n e.preventDefault();\n first.focus();\n }\n });\n\n return container;\n}\n\nfunction formatKeyDifferences(text: string): string {\n // If the backend already sent HTML with list elements, pass through as-is\n if (/<[uo]l[\\s>]/i.test(text) || /<li[\\s>]/i.test(text)) return text;\n const lines = text.split('\\n').filter((l) => l.trim());\n if (lines.length <= 1) return text;\n return '<ul>' + lines.map((l) => `<li>${l.trim()}</li>`).join('') + '</ul>';\n}\n\nfunction appendSpecialCaseListItems(list: HTMLUListElement, raw: string): void {\n const sanitized = sanitizeHtml(raw);\n if (!sanitized) return;\n\n const template = document.createElement('template');\n template.innerHTML = sanitized;\n const nestedItems = Array.from(template.content.querySelectorAll('li'));\n if (nestedItems.length > 0) {\n for (const nestedItem of nestedItems) {\n const li = document.createElement('li');\n li.innerHTML = sanitizeHtml(nestedItem.innerHTML);\n list.appendChild(li);\n }\n return;\n }\n\n const li = document.createElement('li');\n if (looksLikeHtml(raw)) {\n li.innerHTML = sanitized;\n } else {\n li.textContent = raw;\n }\n list.appendChild(li);\n}\n\nfunction looksLikeHtml(text: string): boolean {\n return /<\\/?[a-z][\\s\\S]*>/i.test(text);\n}\n","/**\n * UI Utilities for the Gengage Chat components.\n */\n\n/**\n * Creates a Lucide-style SVG icon from paths.\n * @param paths SVG path strings (the 'd' attribute).\n * @param size Icon width/height in pixels.\n * @returns An SVGElement.\n */\nexport function createLucideIcon(paths: string[], size = 18): SVGElement {\n const ns = 'http://www.w3.org/2000/svg';\n const svg = document.createElementNS(ns, 'svg');\n svg.setAttribute('width', String(size));\n svg.setAttribute('height', String(size));\n svg.setAttribute('viewBox', '0 0 24 24');\n svg.setAttribute('fill', 'none');\n svg.setAttribute('stroke', 'currentColor');\n svg.setAttribute('stroke-width', '2');\n svg.setAttribute('stroke-linecap', 'round');\n svg.setAttribute('stroke-linejoin', 'round');\n svg.setAttribute('aria-hidden', 'true');\n\n for (const d of paths) {\n const path = document.createElementNS(ns, 'path');\n path.setAttribute('d', d);\n svg.appendChild(path);\n }\n\n return svg;\n}\n","/**\n * ReviewHighlights — subject-first review intelligence panel.\n *\n * Subjects are neutral topics (e.g., \"Build quality\").\n * Sentiment is represented by per-subject positive/negative mention counts.\n * Clicking a subject shows short snippets, not full review bodies.\n */\n\nimport type { UIElement } from '../../common/types.js';\nimport { createLucideIcon } from '../utils/ui.js';\n\ntype ReviewTone = 'positive' | 'negative' | 'neutral';\n\ninterface ReviewItem {\n review_class?: string;\n review_text?: string;\n review_rating?: string | number;\n review_tag?: string;\n}\n\ninterface ReviewSnippet {\n text: string;\n tone: ReviewTone;\n rating?: string | number;\n}\n\ninterface SubjectAggregate {\n key: string;\n label: string;\n mentions: number;\n positive: number;\n negative: number;\n neutral: number;\n snippets: ReviewSnippet[];\n}\n\nconst DEFAULT_SUBJECT = 'General';\nconst MAX_SNIPPET_CHARS = 220;\nconst MAX_SNIPPETS_PER_SUBJECT = 6;\n\nfunction normalizeTags(tag: unknown): string[] {\n if (typeof tag !== 'string') return [DEFAULT_SUBJECT];\n const split = tag\n .split(/[,;|/]+/)\n .map((part) => part.trim().replace(/\\s+/g, ' '))\n .filter((part) => part.length > 0);\n if (split.length === 0) return [DEFAULT_SUBJECT];\n return Array.from(new Set(split.map((part) => part.toLocaleLowerCase()))).map((lowered) => {\n const original = split.find((part) => part.toLocaleLowerCase() === lowered);\n return original ?? DEFAULT_SUBJECT;\n });\n}\n\nfunction normalizeTone(tone: unknown): ReviewTone {\n if (tone === 'positive' || tone === 'negative' || tone === 'neutral') return tone;\n return 'neutral';\n}\n\nfunction normalizeSnippet(text: unknown): string {\n if (typeof text !== 'string') return '';\n const normalized = text.trim().replace(/\\s+/g, ' ');\n if (normalized.length <= MAX_SNIPPET_CHARS) return normalized;\n return `${normalized.slice(0, MAX_SNIPPET_CHARS - 3).trimEnd()}...`;\n}\n\nfunction subjectTone(subject: SubjectAggregate): ReviewTone {\n if (subject.positive > subject.negative) return 'positive';\n if (subject.negative > subject.positive) return 'negative';\n return 'neutral';\n}\n\nfunction subjectToneIcon(tone: ReviewTone): SVGElement {\n if (tone === 'positive') {\n return createLucideIcon(['M7 17 17 7', 'M7 7h10v10'], 14);\n }\n if (tone === 'negative') {\n return createLucideIcon(['M7 7 17 17', 'M17 7v10H7'], 14);\n }\n return createLucideIcon(['M5 12h14'], 14);\n}\n\nfunction mentionText(\n count: number,\n singularLabel: string = '1 customer mentions',\n pluralLabel: string = 'customers mention',\n): string {\n return count === 1 ? singularLabel : `${count} ${pluralLabel}`;\n}\n\nexport function renderReviewHighlights(\n element: UIElement,\n options?: {\n emptyReviewsMessage?: string | undefined;\n reviewFilterPositive?: string | undefined;\n reviewFilterNegative?: string | undefined;\n reviewCustomersMentionSingular?: string | undefined;\n reviewCustomersMentionPlural?: string | undefined;\n reviewSubjectsHeading?: string | undefined;\n },\n): HTMLElement {\n const container = document.createElement('div');\n container.className = 'gengage-chat-review-highlights';\n\n const reviews = element.props?.['reviews'];\n if (!Array.isArray(reviews) || reviews.length === 0) {\n const empty = document.createElement('div');\n empty.className = 'gengage-chat-review-empty';\n empty.textContent = options?.emptyReviewsMessage ?? 'No review summary found.';\n container.appendChild(empty);\n return container;\n }\n\n const rawItems: ReviewItem[] = reviews.filter(\n (r): r is Record<string, unknown> => r !== null && typeof r === 'object',\n ) as ReviewItem[];\n\n const subjectsMap = new Map<string, SubjectAggregate>();\n for (const item of rawItems) {\n const tone = normalizeTone(item.review_class);\n const snippet = normalizeSnippet(item.review_text);\n const labels = normalizeTags(item.review_tag);\n\n for (const label of labels) {\n const key = label.toLocaleLowerCase();\n let subject = subjectsMap.get(key);\n if (!subject) {\n subject = {\n key,\n label,\n mentions: 0,\n positive: 0,\n negative: 0,\n neutral: 0,\n snippets: [],\n };\n subjectsMap.set(key, subject);\n }\n\n subject.mentions += 1;\n if (tone === 'positive') subject.positive += 1;\n else if (tone === 'negative') subject.negative += 1;\n else subject.neutral += 1;\n\n if (snippet.length > 0 && subject.snippets.length < MAX_SNIPPETS_PER_SUBJECT) {\n const alreadyExists = subject.snippets.some((entry) => entry.text === snippet);\n if (!alreadyExists) {\n const snippetEntry: ReviewSnippet = {\n text: snippet,\n tone,\n };\n if (item.review_rating !== undefined && String(item.review_rating).length > 0) {\n snippetEntry.rating = item.review_rating;\n }\n subject.snippets.push(snippetEntry);\n }\n }\n }\n }\n\n const subjects = Array.from(subjectsMap.values()).sort((a, b) => {\n if (b.mentions !== a.mentions) return b.mentions - a.mentions;\n return a.label.localeCompare(b.label);\n });\n\n if (subjects.length === 0) {\n const empty = document.createElement('div');\n empty.className = 'gengage-chat-review-empty';\n empty.textContent = options?.emptyReviewsMessage ?? 'No review summary found.';\n container.appendChild(empty);\n return container;\n }\n\n const positiveLabel = (options?.reviewFilterPositive ?? 'positive').toLowerCase();\n const negativeLabel = (options?.reviewFilterNegative ?? 'negative').toLowerCase();\n\n const subjectHeading = document.createElement('div');\n subjectHeading.className = 'gengage-chat-review-subjects-heading';\n subjectHeading.textContent = options?.reviewSubjectsHeading ?? 'Select to learn more';\n container.appendChild(subjectHeading);\n\n let activeKey = subjects[0]?.key ?? null;\n\n const subjectRow = document.createElement('div');\n subjectRow.className = 'gengage-chat-review-subjects gds-toolbar gds-toolbar-compact';\n\n const detailCard = document.createElement('section');\n detailCard.className = 'gengage-chat-review-detail gds-evidence-card';\n\n const renderDetail = (): void => {\n while (detailCard.firstChild) detailCard.removeChild(detailCard.firstChild);\n if (!activeKey) return;\n const active = subjects.find((subject) => subject.key === activeKey);\n if (!active) return;\n\n const metaRow = document.createElement('div');\n metaRow.className = 'gengage-chat-review-detail-meta';\n\n const mentions = document.createElement('span');\n mentions.className = 'gengage-chat-review-detail-mentions';\n mentions.textContent = `${mentionText(\n active.mentions,\n options?.reviewCustomersMentionSingular,\n options?.reviewCustomersMentionPlural,\n )} \"${active.label}\"`;\n metaRow.appendChild(mentions);\n\n if (active.positive > 0) {\n const pos = document.createElement('span');\n pos.className = 'gengage-chat-review-detail-positive';\n pos.textContent = `${active.positive} ${positiveLabel}`;\n metaRow.appendChild(pos);\n }\n\n if (active.negative > 0) {\n const neg = document.createElement('span');\n neg.className = 'gengage-chat-review-detail-negative';\n neg.textContent = `${active.negative} ${negativeLabel}`;\n metaRow.appendChild(neg);\n }\n\n detailCard.appendChild(metaRow);\n\n const snippets = document.createElement('div');\n snippets.className = 'gengage-chat-review-snippets';\n\n for (const snippet of active.snippets) {\n const line = document.createElement('article');\n line.className = 'gengage-chat-review-snippet';\n line.dataset['tone'] = snippet.tone;\n\n const text = document.createElement('div');\n text.className = 'gengage-chat-review-snippet-text';\n text.textContent = `\"${snippet.text}\"`;\n line.appendChild(text);\n\n if (snippet.rating !== undefined && String(snippet.rating).length > 0) {\n const rating = document.createElement('div');\n rating.className = 'gengage-chat-review-snippet-rating';\n rating.textContent = `\\u2605 ${String(snippet.rating)}`;\n line.appendChild(rating);\n }\n\n snippets.appendChild(line);\n }\n\n detailCard.appendChild(snippets);\n };\n\n const refreshSubjectSelection = (): void => {\n for (const node of subjectRow.querySelectorAll<HTMLElement>('.gengage-chat-review-subject')) {\n const isActive = node.dataset['subjectKey'] === activeKey;\n node.classList.toggle('gengage-chat-review-subject--active', isActive);\n node.classList.toggle('gds-chip-active', isActive);\n node.setAttribute('aria-pressed', String(isActive));\n }\n };\n\n for (const subject of subjects) {\n const tone = subjectTone(subject);\n const chip = document.createElement('button');\n chip.type = 'button';\n chip.className = 'gengage-chat-review-subject gds-chip';\n chip.dataset['subjectKey'] = subject.key;\n chip.dataset['tone'] = tone;\n\n const icon = document.createElement('span');\n icon.className = 'gengage-chat-review-subject-icon';\n icon.appendChild(subjectToneIcon(tone));\n chip.appendChild(icon);\n\n const label = document.createElement('span');\n label.className = 'gengage-chat-review-subject-label';\n label.textContent = subject.label;\n chip.appendChild(label);\n\n const count = document.createElement('span');\n count.className = 'gengage-chat-review-subject-count';\n count.textContent = `(${subject.mentions})`;\n chip.appendChild(count);\n\n chip.addEventListener('click', () => {\n activeKey = subject.key;\n refreshSubjectSelection();\n renderDetail();\n });\n\n subjectRow.appendChild(chip);\n }\n\n container.appendChild(subjectRow);\n refreshSubjectSelection();\n renderDetail();\n container.appendChild(detailCard);\n return container;\n}\n","/**\n * AI Top Picks renderer.\n *\n * DOM/layout parity with robot-engine-lean MainPane/AIAnalysisZone TopPicksResults:\n * article: role badge, image, title, rating, price, labels, hover CTA.\n */\n\nimport type { UIElement, ActionPayload } from '../../common/types.js';\nimport type { ChatUISpecRenderContext } from '../types.js';\nimport { formatPrice } from '../../common/price-formatter.js';\nimport { isSafeImageUrl, safeSetAttribute } from '../../common/safe-html.js';\nimport { addImageErrorHandler } from '../../common/product-utils.js';\n\ninterface SentimentLabel {\n label: string;\n sentiment?: 'positive' | 'negative' | 'neutral';\n}\n\ninterface AITopPickItem {\n product: Record<string, unknown>;\n role?: string;\n reason?: string;\n labels?: SentimentLabel[];\n expertQualityScore?: number;\n reviewHighlight?: string;\n action?: ActionPayload;\n}\n\nfunction resolveActionSku(item: AITopPickItem): string | null {\n const productSku = item.product['sku'];\n if (typeof productSku === 'string' && productSku.length > 0) return productSku;\n const payload = item.action?.payload;\n if (payload && typeof payload === 'object' && 'sku' in payload && typeof payload.sku === 'string') {\n return payload.sku;\n }\n return null;\n}\n\nconst ROLE_LABELS: Record<string, string> = {\n winner: 'roleWinner',\n best_value: 'roleBestValue',\n best_alternative: 'roleBestAlternative',\n};\n\nfunction getRoleLabel(role: string | undefined, i18n: ChatUISpecRenderContext['i18n']): string | null {\n if (!role || !i18n) return null;\n const key = ROLE_LABELS[role];\n if (!key) return role;\n return (i18n as Record<string, string>)[key] ?? role;\n}\n\nfunction renderRatingRow(product: Record<string, unknown>): HTMLElement | null {\n const raw = product['rating'];\n const num = typeof raw === 'number' ? raw : typeof raw === 'string' ? parseFloat(raw) : NaN;\n if (Number.isNaN(num) || num <= 0) return null;\n const row = document.createElement('div');\n row.className = 'gengage-chat-ai-toppick-rating';\n row.dataset['gengagePart'] = 'ai-top-pick-rating';\n const icon = document.createElement('span');\n icon.className = 'gengage-chat-ai-toppick-rating-icon';\n icon.setAttribute('aria-hidden', 'true');\n icon.innerHTML =\n '<svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M12 3.6l2.58 5.23 5.77.84-4.17 4.07.98 5.75L12 16.78l-5.16 2.71.99-5.75L3.66 9.67l5.76-.84L12 3.6z\"/></svg>';\n const value = document.createElement('span');\n value.className = 'gengage-chat-ai-toppick-rating-value';\n value.textContent = num.toFixed(1);\n row.appendChild(icon);\n row.appendChild(value);\n return row;\n}\n\n/** Image-focused media with the same lightweight hover actions as listing cards. */\nfunction appendTopPickMedia(\n item: AITopPickItem,\n alt: string,\n target: HTMLElement,\n ctx: ChatUISpecRenderContext,\n options?: { skipOverlayActions?: boolean },\n): void {\n const media = document.createElement('div');\n media.className = 'gengage-chat-ai-toppick-media';\n media.dataset['gengagePart'] = 'ai-top-pick-media';\n\n const product = item.product;\n const imageUrl = product['imageUrl'] as string | undefined;\n if (imageUrl && isSafeImageUrl(imageUrl)) {\n const img = document.createElement('img');\n img.className = 'gengage-chat-ai-toppick-img';\n img.dataset['gengagePart'] = 'ai-top-pick-image';\n safeSetAttribute(img, 'src', imageUrl);\n img.loading = 'lazy';\n img.alt = alt;\n addImageErrorHandler(img);\n media.appendChild(img);\n }\n\n const sku = resolveActionSku(item);\n if (sku && !options?.skipOverlayActions) {\n const imgActions = document.createElement('div');\n imgActions.className = 'gengage-chat-product-card-img-actions';\n\n if (ctx.onFavoriteToggle) {\n const heart = document.createElement('button');\n heart.className = 'gengage-chat-favorite-btn';\n heart.type = 'button';\n heart.dataset.gengageFavoriteSku = sku;\n heart.setAttribute('aria-label', ctx.i18n?.addToFavoritesLabel ?? 'Add to favorites');\n const isFav = ctx.favoritedSkus?.has(sku) ?? false;\n if (isFav) heart.classList.add('gengage-chat-favorite-btn--active');\n const svgFill = isFav ? 'currentColor' : 'none';\n heart.innerHTML = `<svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"${svgFill}\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z\"/></svg>`;\n heart.addEventListener('click', (e) => {\n e.stopPropagation();\n heart.classList.toggle('gengage-chat-favorite-btn--active');\n heart\n .querySelector('svg')\n ?.setAttribute(\n 'fill',\n heart.classList.contains('gengage-chat-favorite-btn--active') ? 'currentColor' : 'none',\n );\n ctx.onFavoriteToggle?.(sku, product);\n });\n imgActions.appendChild(heart);\n }\n\n const findSimilarLabel = ctx.i18n?.findSimilarLabel ?? 'Find Similar';\n const pill = document.createElement('button');\n pill.className = 'gengage-chat-find-similar-pill';\n pill.type = 'button';\n pill.setAttribute('aria-label', findSimilarLabel);\n pill.dataset['tooltip'] = findSimilarLabel;\n pill.innerHTML =\n `<span class=\"gengage-chat-find-similar-pill-icon\" aria-hidden=\"true\">` +\n `<svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.25\" stroke-linecap=\"round\" stroke-linejoin=\"round\">` +\n `<circle cx=\"10.5\" cy=\"10.5\" r=\"6.5\"/>` +\n `<path d=\"M16 16l5.5 5.5\"/>` +\n `</svg></span>`;\n const pillText = document.createElement('span');\n pillText.className = 'gengage-chat-find-similar-pill-text';\n pillText.textContent = findSimilarLabel;\n pill.appendChild(pillText);\n pill.addEventListener('click', (e) => {\n e.stopPropagation();\n ctx.onAction({\n title: findSimilarLabel,\n type: 'findSimilar',\n payload: { sku, ...(imageUrl ? { image_url: imageUrl } : {}) },\n });\n });\n imgActions.appendChild(pill);\n\n media.appendChild(imgActions);\n }\n\n target.appendChild(media);\n}\n\nfunction appendPriceRow(product: Record<string, unknown>, body: HTMLElement, ctx: ChatUISpecRenderContext): void {\n const price = product['price'] as string | undefined;\n const originalPrice = product['originalPrice'] as string | undefined;\n if (!price) return;\n const priceRow = document.createElement('div');\n priceRow.className = 'gengage-chat-ai-toppick-price';\n priceRow.dataset['gengagePart'] = 'ai-top-pick-price';\n if (originalPrice && originalPrice !== price) {\n const orig = document.createElement('span');\n orig.className = 'gengage-chat-ai-toppick-original-price';\n orig.textContent = formatPrice(originalPrice, ctx.pricing);\n priceRow.appendChild(orig);\n priceRow.appendChild(document.createTextNode(' '));\n }\n const current = document.createElement('span');\n current.textContent = formatPrice(price, ctx.pricing);\n priceRow.appendChild(current);\n body.appendChild(priceRow);\n}\n\nfunction appendWinnerEvidence(item: AITopPickItem, body: HTMLElement): void {\n const reason = typeof item.reason === 'string' ? item.reason.trim() : '';\n const reviewHighlight = typeof item.reviewHighlight === 'string' ? item.reviewHighlight.trim() : '';\n if (!reason && !reviewHighlight) return;\n\n if (reason) {\n const reasonEl = document.createElement('p');\n reasonEl.className = 'gengage-chat-ai-toppick-reason';\n reasonEl.dataset['gengagePart'] = 'ai-top-pick-reason';\n reasonEl.textContent = reason;\n body.appendChild(reasonEl);\n }\n\n if (reviewHighlight) {\n const reviewEl = document.createElement('div');\n reviewEl.className = 'gengage-chat-ai-toppick-review';\n reviewEl.dataset['gengagePart'] = 'ai-top-pick-review';\n reviewEl.textContent = reviewHighlight;\n body.appendChild(reviewEl);\n }\n}\n\n/**\n * Single card layout matching lean TopPicksResults (all picks use the same structure).\n * `--winner` / `--compact` class names kept for tests (highlight vs secondary border).\n */\nfunction renderPickCard(item: AITopPickItem, ctx: ChatUISpecRenderContext, isWinner: boolean): HTMLElement {\n const card = document.createElement('div');\n card.className = isWinner\n ? 'gengage-chat-ai-toppick-card gengage-chat-ai-toppick-card--winner gds-card'\n : 'gengage-chat-ai-toppick-card gengage-chat-ai-toppick-card--compact gds-card';\n card.dataset['gengagePart'] = isWinner ? 'ai-top-pick-card-winner' : 'ai-top-pick-card';\n const product = item.product;\n const sku = resolveActionSku(item);\n const url = (product['url'] as string) ?? '';\n const cartCode = product['cartCode'] as string | undefined;\n const inStock = product['inStock'];\n const hasCart = !!(sku && cartCode && inStock !== false);\n const action = item.action;\n if (sku && ctx.onProductClick) {\n card.classList.add('gds-clickable');\n card.addEventListener('click', (e) => {\n if ((e.target as HTMLElement).closest('.gengage-chat-ai-toppick-cta')) return;\n if ((e.target as HTMLElement).closest('.gengage-chat-favorite-btn')) return;\n if ((e.target as HTMLElement).closest('.gengage-chat-find-similar-pill')) return;\n ctx.onProductClick?.({ sku, url });\n });\n } else if (action) {\n card.classList.add('gds-clickable');\n card.addEventListener('click', (e) => {\n if ((e.target as HTMLElement).closest('.gengage-chat-ai-toppick-cta')) return;\n if ((e.target as HTMLElement).closest('.gengage-chat-favorite-btn')) return;\n if ((e.target as HTMLElement).closest('.gengage-chat-find-similar-pill')) return;\n ctx.onAction(action);\n });\n }\n const alt = (product['name'] as string) || 'Product image';\n const compactMobile = !isWinner && ctx.isMobile === true;\n\n /* Lean: span.absolute.-top-2.5.left-3 — no wrapper */\n const roleLabel = isWinner\n ? (getRoleLabel(item.role, ctx.i18n) ?? ctx.i18n?.roleWinner ?? 'TOP MATCH')\n : getRoleLabel(item.role, ctx.i18n);\n if (roleLabel && !compactMobile) {\n const badge = document.createElement('span');\n badge.className = 'gengage-chat-ai-toppick-badge gds-badge';\n badge.dataset['gengagePart'] = 'ai-top-pick-role-badge';\n badge.textContent = roleLabel;\n card.appendChild(badge);\n }\n\n const topRow = document.createElement('div');\n topRow.className = 'gengage-chat-ai-toppick-top-row';\n topRow.dataset['gengagePart'] = 'ai-top-pick-top-row';\n appendTopPickMedia(item, alt, topRow, ctx, { skipOverlayActions: compactMobile });\n\n const body = document.createElement('div');\n body.className = 'gengage-chat-ai-toppick-body';\n body.dataset['gengagePart'] = 'ai-top-pick-body';\n\n if (roleLabel && compactMobile) {\n const roleLine = document.createElement('div');\n roleLine.className = 'gengage-chat-ai-toppick-role-line';\n roleLine.dataset['gengagePart'] = 'ai-top-pick-role-line';\n roleLine.textContent = roleLabel;\n body.appendChild(roleLine);\n }\n\n const name = product['name'] as string | undefined;\n if (name) {\n const nameEl = document.createElement('div');\n nameEl.className = 'gengage-chat-ai-toppick-name';\n nameEl.dataset['gengagePart'] = 'ai-top-pick-name';\n nameEl.textContent = name;\n body.appendChild(nameEl);\n }\n\n const ratingRow = renderRatingRow(product);\n if (ratingRow) body.appendChild(ratingRow);\n\n /* Lean order: price before labels */\n appendPriceRow(product, body, ctx);\n\n if (item.labels && item.labels.length > 0) {\n body.appendChild(renderSentimentChips(item.labels));\n }\n\n topRow.appendChild(body);\n card.appendChild(topRow);\n\n const detail = document.createElement('div');\n detail.className = 'gengage-chat-ai-toppick-detail';\n detail.dataset['gengagePart'] = 'ai-top-pick-detail';\n if (isWinner) {\n appendWinnerEvidence(item, detail);\n } else {\n const snippet = typeof item.reason === 'string' ? item.reason.trim() : '';\n if (snippet) {\n const snippetEl = document.createElement('p');\n snippetEl.className = 'gengage-chat-ai-toppick-snippet';\n snippetEl.dataset['gengagePart'] = 'ai-top-pick-snippet';\n snippetEl.textContent = snippet;\n detail.appendChild(snippetEl);\n }\n }\n if (detail.childNodes.length > 0) {\n card.appendChild(detail);\n }\n\n const wantSpinner = !!(sku && ctx.topPicksLoadingSku === sku);\n const showCta = (hasCart || action) && !compactMobile;\n\n if (wantSpinner || hasCart || action) {\n const spinner = document.createElement('div');\n spinner.className = 'gengage-chat-ai-toppick-spinner';\n spinner.dataset['gengagePart'] = 'ai-top-pick-spinner';\n spinner.style.display = wantSpinner ? '' : 'none';\n card.appendChild(spinner);\n\n if (showCta) {\n const cta = document.createElement('button');\n cta.className = 'gengage-chat-ai-toppick-cta gds-btn gds-btn-primary';\n cta.dataset['gengagePart'] = 'ai-top-pick-cta';\n cta.type = 'button';\n cta.textContent = hasCart\n ? (ctx.i18n?.addToCartButton ?? 'Add to Cart')\n : (ctx.i18n?.viewDetails ?? 'View Details');\n cta.addEventListener('click', (e) => {\n e.stopPropagation();\n if (hasCart) {\n ctx.onAction({\n title: ctx.i18n?.addToCartButton ?? 'Add to Cart',\n type: 'addToCart',\n payload: { sku: sku!, cartCode: cartCode!, quantity: 1 },\n });\n return;\n }\n if (!action) return;\n if (action.type === 'findSimilar' && sku && ctx.onProductClick) {\n ctx.onProductClick({ sku, url });\n return;\n }\n ctx.onAction(action);\n });\n card.appendChild(cta);\n }\n }\n\n return card;\n}\n\nexport function renderAITopPicks(element: UIElement, ctx: ChatUISpecRenderContext): HTMLElement {\n const container = document.createElement('div');\n container.className = 'gengage-chat-ai-top-picks';\n container.dataset['gengagePart'] = 'ai-top-picks';\n\n const suggestions = (element.props?.['suggestions'] ?? []) as AITopPickItem[];\n if (suggestions.length === 0) return container;\n\n const title = document.createElement('h3');\n title.className = 'gengage-chat-ai-top-picks-title';\n title.dataset['gengagePart'] = 'ai-top-picks-title';\n title.textContent = ctx.i18n?.aiTopPicksTitle ?? 'Top Picks';\n container.appendChild(title);\n\n const scrollRow = document.createElement('div');\n scrollRow.className = 'gengage-chat-ai-top-picks-scroll';\n scrollRow.dataset['gengagePart'] = 'ai-top-picks-scroll';\n\n const first = suggestions[0]!;\n /* First suggestion always uses winner/highlight card chrome (parity: i === 0 in old loop). */\n scrollRow.appendChild(renderPickCard(first, ctx, true));\n\n if (suggestions.length > 1) {\n const rest = document.createElement('div');\n rest.className = 'gengage-chat-ai-top-picks-rest';\n rest.dataset['gengagePart'] = 'ai-top-picks-rest';\n for (let i = 1; i < suggestions.length; i++) {\n const suggestion = suggestions[i]!;\n const isWinner = suggestion.role === 'winner';\n rest.appendChild(renderPickCard(suggestion, ctx, isWinner));\n }\n scrollRow.appendChild(rest);\n }\n\n container.appendChild(scrollRow);\n return container;\n}\n\nfunction renderSentimentChips(labels: SentimentLabel[]): HTMLElement {\n const container = document.createElement('div');\n container.className = 'gengage-chat-ai-toppick-labels';\n container.dataset['gengagePart'] = 'ai-top-pick-labels';\n for (const label of labels) {\n const chip = document.createElement('span');\n chip.className = 'gengage-chat-ai-toppick-label gds-chip';\n chip.dataset['gengagePart'] = 'ai-top-pick-label';\n chip.dataset['sentiment'] = label.sentiment ?? 'neutral';\n chip.textContent = label.label;\n container.appendChild(chip);\n }\n return container;\n}\n","/**\n * Grounding Review Card renderer.\n *\n * Renders a clickable card for review grounding data with title,\n * review count, and a CTA arrow. The entire card is clickable.\n *\n * XSS safety: All text is set via textContent. No innerHTML.\n */\n\nimport type { UIElement, ActionPayload } from '../../common/types.js';\nimport type { ChatUISpecRenderContext } from '../types.js';\n\nimport { createLucideIcon } from '../utils/ui.js';\n\nexport function renderGroundingReviewCard(element: UIElement, ctx: ChatUISpecRenderContext): HTMLElement {\n const container = document.createElement('div');\n container.className = 'gengage-chat-grounding-review gds-evidence-card';\n container.dataset['gengagePart'] = 'grounding-review-card';\n\n const props = element.props ?? {};\n const title = props['title'] as string | undefined;\n const reviewCount = props['reviewCount'] as string | undefined;\n const action = props['action'] as ActionPayload | undefined;\n const ctaLabel = ctx.i18n?.groundingReviewCta ?? 'Read Reviews';\n\n // Icon\n const icon = document.createElement('span');\n icon.className = 'gengage-chat-grounding-review-icon';\n icon.appendChild(\n createLucideIcon(['M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z', 'M8 9h8', 'M8 13h6']),\n );\n container.appendChild(icon);\n\n const body = document.createElement('div');\n body.className = 'gengage-chat-grounding-review-body';\n body.dataset['gengagePart'] = 'grounding-review-body';\n\n // Title\n const titleEl = document.createElement('div');\n titleEl.className = 'gengage-chat-grounding-review-title';\n titleEl.dataset['gengagePart'] = 'grounding-review-title';\n titleEl.textContent = title ?? ctx.i18n?.customerReviewsTitle ?? 'Customer Reviews';\n body.appendChild(titleEl);\n\n // Subtitle (review count)\n if (reviewCount) {\n const subtitle = document.createElement('div');\n subtitle.className = 'gengage-chat-grounding-review-subtitle';\n subtitle.dataset['gengagePart'] = 'grounding-review-subtitle';\n const template = ctx.i18n?.groundingReviewSubtitle ?? '{count} yorum mevcut';\n subtitle.textContent = template.replace('{count}', reviewCount);\n body.appendChild(subtitle);\n }\n\n container.appendChild(body);\n\n // CTA arrow\n const cta = document.createElement('span');\n cta.className = 'gengage-chat-grounding-review-cta';\n cta.dataset['gengagePart'] = 'grounding-review-cta';\n const ctaLabelEl = document.createElement('span');\n ctaLabelEl.textContent = ctaLabel;\n cta.appendChild(ctaLabelEl);\n const ctaIcon = document.createElement('span');\n ctaIcon.className = 'gengage-chat-grounding-review-cta-icon';\n ctaIcon.appendChild(createLucideIcon(['M5 12h14', 'M15 8l4 4-4 4'], 14));\n cta.appendChild(ctaIcon);\n container.appendChild(cta);\n\n // Make entire card clickable\n if (action) {\n container.classList.add('gds-clickable');\n container.setAttribute('role', 'button');\n container.setAttribute('tabindex', '0');\n container.addEventListener('click', () => ctx.onAction(action));\n container.addEventListener('keydown', (e: KeyboardEvent) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n ctx.onAction(action);\n }\n });\n }\n\n return container;\n}\n","/**\n * AI Grouping Cards renderer.\n *\n * Desktop: compact card with image, name, and labels.\n * Mobile: simple button list with arrow prefix (images hidden via CSS).\n *\n * Note: The media-rail layout is similar to `AISuggestedSearchCards` but with different tokens;\n * a shared primitive may be extracted later if the visual system stabilizes.\n *\n * XSS safety: All text is set via textContent. Image URLs are validated\n * for safe protocols. No innerHTML.\n */\n\nimport type { UIElement, ActionPayload } from '../../common/types.js';\nimport type { ChatUISpecRenderContext } from '../types.js';\nimport { isSafeImageUrl } from '../../common/safe-html.js';\n\ninterface GroupingEntry {\n name: string;\n image?: string;\n description?: string;\n labels?: string[];\n action: ActionPayload;\n}\n\nfunction normalizeGroupingAction(entry: GroupingEntry): ActionPayload {\n if (entry.action.type !== 'findSimilar') return entry.action;\n const payload =\n entry.action.payload && typeof entry.action.payload === 'object'\n ? (entry.action.payload as Record<string, unknown>)\n : null;\n const text =\n (typeof payload?.['input'] === 'string' && payload['input'].trim()) ||\n (typeof payload?.['text'] === 'string' && payload['text'].trim()) ||\n entry.name.trim();\n if (!text) return entry.action;\n\n const normalizedPayload: Record<string, unknown> = {\n text,\n is_suggested_text: 1,\n };\n if (typeof payload?.['sku'] === 'string' && payload['sku'].trim()) {\n normalizedPayload['sku'] = payload['sku'];\n }\n if (Array.isArray(payload?.['group_skus'])) {\n const groupSkus = payload['group_skus'].filter((sku): sku is string => typeof sku === 'string' && sku.length > 0);\n if (groupSkus.length > 0) normalizedPayload['group_skus'] = groupSkus;\n }\n return {\n title: entry.action.title,\n type: 'inputText',\n payload: normalizedPayload,\n };\n}\n\nexport function renderAIGroupingCards(element: UIElement, ctx: ChatUISpecRenderContext): HTMLElement {\n const container = document.createElement('div');\n container.className = 'gengage-chat-grouping-cards';\n container.dataset['gengagePart'] = 'ai-grouping-cards';\n\n const entries = (element.props?.['entries'] ?? []) as GroupingEntry[];\n if (entries.length === 0) return container;\n\n const customTitle = element.props?.['sectionTitle'];\n const sectionTitle =\n typeof customTitle === 'string' && customTitle.trim().length > 0\n ? customTitle.trim()\n : ctx.i18n?.aiBrowseCategoriesTitle;\n if (sectionTitle) {\n const heading = document.createElement('h3');\n heading.className = 'gengage-chat-grouping-section-title';\n heading.textContent = sectionTitle;\n container.appendChild(heading);\n }\n\n const scrollRow = document.createElement('div');\n scrollRow.className = 'gengage-chat-grouping-cards-scroll';\n scrollRow.dataset['gengagePart'] = 'ai-grouping-cards-scroll';\n\n for (const entry of entries) {\n const card = document.createElement('div');\n card.className = 'gengage-chat-grouping-card gds-card';\n card.dataset['gengagePart'] = 'ai-grouping-card';\n card.classList.add('gds-clickable');\n card.addEventListener('click', () => ctx.onAction(normalizeGroupingAction(entry)));\n\n // Image — intrinsic size; CSS sets panel vs chat dimensions\n if (entry.image && isSafeImageUrl(entry.image)) {\n const img = document.createElement('img');\n img.className = 'gengage-chat-grouping-card-img';\n img.dataset['gengagePart'] = 'ai-grouping-card-image';\n img.src = entry.image;\n img.alt = entry.name;\n img.width = 64;\n img.height = 64;\n card.appendChild(img);\n }\n\n const body = document.createElement('div');\n body.className = 'gengage-chat-grouping-card-body';\n body.dataset['gengagePart'] = 'ai-grouping-card-body';\n\n const nameEl = document.createElement('span');\n nameEl.className = 'gengage-chat-grouping-card-name';\n nameEl.dataset['gengagePart'] = 'ai-grouping-card-name';\n nameEl.textContent = entry.name;\n body.appendChild(nameEl);\n\n if (entry.labels && entry.labels.length > 0) {\n const labelsEl = document.createElement('div');\n labelsEl.className = 'gengage-chat-grouping-card-labels';\n labelsEl.dataset['gengagePart'] = 'ai-grouping-card-labels';\n for (const label of entry.labels.slice(0, 2)) {\n const chip = document.createElement('span');\n chip.className = 'gengage-chat-grouping-card-label gds-chip';\n chip.textContent = label;\n labelsEl.appendChild(chip);\n }\n body.appendChild(labelsEl);\n }\n\n card.appendChild(body);\n\n // Mobile prefix arrow (shown via CSS only on mobile)\n const arrow = document.createElement('span');\n arrow.className = 'gengage-chat-grouping-card-arrow';\n arrow.textContent = '\\u21B3';\n card.insertBefore(arrow, card.firstChild);\n\n scrollRow.appendChild(card);\n }\n\n container.appendChild(scrollRow);\n\n return container;\n}\n","/**\n * AI Suggested Search Cards renderer.\n *\n * Cards with a larger image and a single clear title line.\n * Secondary / tertiary supporting lines are intentionally omitted in chat so\n * the module reads as a compact, visual category chooser.\n *\n * XSS safety: All text is set via textContent. Image URLs are validated\n * for safe protocols. No innerHTML.\n */\n\nimport type { UIElement, ActionPayload } from '../../common/types.js';\nimport type { ChatUISpecRenderContext } from '../types.js';\nimport { isSafeImageUrl } from '../../common/safe-html.js';\n\ninterface SuggestedSearchEntry {\n shortName: string;\n image?: string;\n action: ActionPayload;\n}\n\nexport function renderAISuggestedSearchCards(element: UIElement, ctx: ChatUISpecRenderContext): HTMLElement {\n const container = document.createElement('div');\n container.className = 'gengage-chat-suggested-search-cards';\n container.dataset['gengagePart'] = 'ai-suggested-search-cards';\n\n const entries = (element.props?.['entries'] ?? []) as SuggestedSearchEntry[];\n if (entries.length === 0) return container;\n\n for (const entry of entries) {\n const card = document.createElement('div');\n card.className = 'gengage-chat-suggested-search-card gds-card';\n card.dataset['gengagePart'] = 'ai-suggested-search-card';\n card.classList.add('gds-clickable');\n card.addEventListener('click', () => ctx.onAction(entry.action));\n\n // Image\n if (entry.image && isSafeImageUrl(entry.image)) {\n const img = document.createElement('img');\n img.className = 'gengage-chat-suggested-search-card-img';\n img.dataset['gengagePart'] = 'ai-suggested-search-card-image';\n img.src = entry.image;\n img.alt = entry.shortName;\n img.width = 64;\n img.height = 64;\n card.appendChild(img);\n }\n\n const body = document.createElement('div');\n body.className = 'gengage-chat-suggested-search-card-body';\n body.dataset['gengagePart'] = 'ai-suggested-search-card-body';\n\n const nameEl = document.createElement('div');\n nameEl.className = 'gengage-chat-suggested-search-card-name';\n nameEl.dataset['gengagePart'] = 'ai-suggested-search-card-name';\n nameEl.textContent = entry.shortName;\n body.appendChild(nameEl);\n\n card.appendChild(body);\n container.appendChild(card);\n }\n\n return container;\n}\n","/**\n * Slim bottom-docked comparison bar for the main panel.\n *\n * Keeps compare mode visible without the oversized floating prompt.\n */\n\nimport type { ChatUISpecRenderContext } from '../types.js';\n\nexport function renderFloatingComparisonButton(selectedSkus: string[], ctx: ChatUISpecRenderContext): HTMLElement {\n const canCompare = selectedSkus.length >= 2;\n const label = ctx.i18n?.compareSelected ?? 'Compare';\n const warning = ctx.comparisonSelectionWarning;\n\n const dock = document.createElement('div');\n dock.className = 'gengage-chat-comparison-floating-btn';\n dock.dataset['gengagePart'] = 'comparison-dock';\n\n const summary = document.createElement('div');\n summary.className = 'gengage-chat-comparison-floating-summary';\n\n const count = document.createElement('span');\n count.className = 'gengage-chat-comparison-floating-count';\n count.textContent = String(selectedSkus.length);\n summary.appendChild(count);\n\n const text = document.createElement('div');\n text.className = 'gengage-chat-comparison-floating-copy';\n\n const title = document.createElement('div');\n title.className = 'gengage-chat-comparison-floating-title';\n title.textContent = canCompare\n ? `${label} (${selectedSkus.length})`\n : (ctx.i18n?.compareMinHint ?? 'Select at least 2 products');\n text.appendChild(title);\n\n if (warning) {\n const warningEl = document.createElement('div');\n warningEl.className = 'gengage-chat-comparison-floating-warning';\n warningEl.setAttribute('role', 'status');\n warningEl.setAttribute('aria-live', 'polite');\n warningEl.textContent = warning;\n text.appendChild(warningEl);\n }\n\n summary.appendChild(text);\n dock.appendChild(summary);\n\n const action = document.createElement('button');\n action.className = 'gengage-chat-comparison-floating-action gds-btn gds-btn-primary';\n action.type = 'button';\n action.textContent = label;\n action.disabled = !canCompare;\n if (!canCompare) action.classList.add('gengage-chat-comparison-floating-action--disabled');\n action.addEventListener('click', () => {\n if (!canCompare) return;\n ctx.onAction({\n title: label,\n type: 'getComparisonTable',\n payload: { sku_list: [...selectedSkus] },\n });\n });\n dock.appendChild(action);\n\n if (ctx.onToggleComparisonSku) {\n const close = document.createElement('button');\n close.className = 'gengage-chat-comparison-floating-close gds-btn gds-btn-ghost gds-icon-btn';\n close.dataset['gengagePart'] = 'comparison-dock-close';\n close.type = 'button';\n close.setAttribute('aria-label', ctx.i18n?.closeAriaLabel ?? 'Close');\n close.innerHTML =\n '<svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\" stroke-linecap=\"round\" aria-hidden=\"true\"><path d=\"M18 6L6 18\"/><path d=\"M6 6l12 12\"/></svg>';\n close.addEventListener('click', (e) => {\n e.preventDefault();\n e.stopPropagation();\n ctx.onToggleComparisonSku?.('');\n });\n dock.appendChild(close);\n }\n\n return dock;\n}\n","/**\n * Renders a Pros & Cons list for a product.\n *\n * XSS safety: All text is set via textContent. No innerHTML.\n */\n\nexport function renderProsAndCons(element: { props?: Record<string, unknown> }): HTMLElement {\n const container = document.createElement('div');\n container.className = 'gengage-chat-pros-cons gds-card-soft';\n container.dataset['gengagePart'] = 'pros-cons';\n\n const productName = element.props?.['productName'] as string | undefined;\n if (productName) {\n const heading = document.createElement('h4');\n heading.className = 'gengage-chat-pros-cons-heading';\n heading.textContent = productName;\n container.appendChild(heading);\n }\n\n const pros = element.props?.['pros'] as string[] | undefined;\n const cons = element.props?.['cons'] as string[] | undefined;\n\n if (pros && pros.length > 0) {\n const prosList = document.createElement('ul');\n prosList.className = 'gengage-chat-pros-cons-list';\n prosList.dataset['gengagePart'] = 'pros-list';\n for (const pro of pros) {\n const li = document.createElement('li');\n li.className = 'gengage-chat-pros-cons-item';\n li.dataset['gengagePart'] = 'pros-item';\n const icon = document.createElement('span');\n icon.className = 'gengage-chat-pros-cons-icon gengage-chat-pros-cons-icon--pro';\n icon.textContent = '\\u2713';\n li.appendChild(icon);\n const text = document.createElement('span');\n text.textContent = pro;\n li.appendChild(text);\n prosList.appendChild(li);\n }\n container.appendChild(prosList);\n }\n\n if (cons && cons.length > 0) {\n const consList = document.createElement('ul');\n consList.className = 'gengage-chat-pros-cons-list';\n consList.dataset['gengagePart'] = 'cons-list';\n for (const con of cons) {\n const li = document.createElement('li');\n li.className = 'gengage-chat-pros-cons-item';\n li.dataset['gengagePart'] = 'cons-item';\n const icon = document.createElement('span');\n icon.className = 'gengage-chat-pros-cons-icon gengage-chat-pros-cons-icon--con';\n icon.textContent = '\\u2717';\n li.appendChild(icon);\n const text = document.createElement('span');\n text.textContent = con;\n li.appendChild(text);\n consList.appendChild(li);\n }\n container.appendChild(consList);\n }\n\n return container;\n}\n","/**\n * CategoriesContainer — renders grouped product lists with tab navigation\n * and optional filter tags.\n *\n * Backend sends `groupList` with `{ group_list: [{ group_name, product_list }], filter_tags }`.\n * The protocol adapter normalizes this into a CategoriesContainer UISpec element.\n *\n * XSS safety: All text is set via textContent. No innerHTML.\n */\n\nimport type { UIElement } from '../../common/types.js';\nimport type { ChatUISpecRenderContext } from '../types.js';\nimport type { NormalizedProduct } from '../../common/protocol-adapter.js';\nimport { isSafeImageUrl } from '../../common/safe-html.js';\nimport { formatPrice } from '../../common/price-formatter.js';\nimport { addImageErrorHandler } from '../../common/product-utils.js';\n\ninterface GroupData {\n groupName: string;\n products: NormalizedProduct[];\n}\n\ninterface FilterTagData {\n title: string;\n action?: { title: string; type: string; payload?: unknown };\n}\n\nexport function renderCategoriesContainer(element: UIElement, context: ChatUISpecRenderContext): HTMLElement {\n const groups = (element.props?.['groups'] as GroupData[] | undefined) ?? [];\n const filterTags = (element.props?.['filterTags'] as FilterTagData[] | undefined) ?? [];\n\n const container = document.createElement('div');\n container.className = 'gengage-chat-categories';\n container.dataset['gengagePart'] = 'categories-container';\n\n if (groups.length === 0) return container;\n\n // Tab bar — WAI-ARIA tablist pattern\n const tabBar = document.createElement('div');\n tabBar.className = 'gengage-chat-categories-tabs gds-toolbar';\n tabBar.dataset['gengagePart'] = 'categories-tab-bar';\n tabBar.setAttribute('role', 'tablist');\n\n const tabs: HTMLButtonElement[] = [];\n const panels: HTMLElement[] = [];\n\n const activateTab = (index: number): void => {\n for (let j = 0; j < tabs.length; j++) {\n const isActive = j === index;\n tabs[j]!.classList.toggle('gengage-chat-categories-tab--active', isActive);\n tabs[j]!.classList.toggle('is-active', isActive);\n tabs[j]!.setAttribute('aria-selected', String(isActive));\n tabs[j]!.tabIndex = isActive ? 0 : -1;\n panels[j]!.style.display = isActive ? '' : 'none';\n }\n };\n\n for (let i = 0; i < groups.length; i++) {\n const group = groups[i]!;\n const tabId = `gengage-cat-tab-${i}`;\n const panelId = `gengage-cat-panel-${i}`;\n\n // Tab button\n const tab = document.createElement('button');\n tab.className = 'gengage-chat-categories-tab gds-tab';\n tab.type = 'button';\n tab.dataset['gengagePart'] = 'categories-tab';\n tab.id = tabId;\n tab.setAttribute('role', 'tab');\n tab.setAttribute('aria-controls', panelId);\n tab.setAttribute('aria-selected', String(i === 0));\n tab.tabIndex = i === 0 ? 0 : -1;\n if (i === 0) tab.classList.add('gengage-chat-categories-tab--active', 'is-active');\n tab.textContent = group.groupName;\n\n tab.addEventListener('click', () => activateTab(i));\n tab.addEventListener('keydown', (e: KeyboardEvent) => {\n let next = -1;\n if (e.key === 'ArrowRight' || e.key === 'ArrowDown') {\n next = (i + 1) % groups.length;\n } else if (e.key === 'ArrowLeft' || e.key === 'ArrowUp') {\n next = (i - 1 + groups.length) % groups.length;\n } else if (e.key === 'Home') {\n next = 0;\n } else if (e.key === 'End') {\n next = groups.length - 1;\n }\n if (next >= 0) {\n e.preventDefault();\n activateTab(next);\n tabs[next]!.focus();\n }\n });\n\n tabs.push(tab);\n tabBar.appendChild(tab);\n\n // Product grid panel\n const panel = document.createElement('div');\n panel.className = 'gengage-chat-categories-grid';\n panel.dataset['gengagePart'] = 'categories-panel';\n panel.id = panelId;\n panel.setAttribute('role', 'tabpanel');\n panel.setAttribute('aria-labelledby', tabId);\n if (i !== 0) panel.style.display = 'none';\n\n for (const product of group.products) {\n const card = renderCategoryProductCard(product, context);\n panel.appendChild(card);\n }\n\n panels.push(panel);\n }\n\n container.appendChild(tabBar);\n for (const panel of panels) container.appendChild(panel);\n\n // Filter tags\n if (filterTags.length > 0) {\n const tagsContainer = document.createElement('div');\n tagsContainer.className = 'gengage-chat-categories-filter-tags gds-toolbar';\n tagsContainer.dataset['gengagePart'] = 'categories-filter-tags';\n\n for (const tag of filterTags) {\n const tagBtn = document.createElement('button');\n tagBtn.className = 'gengage-chat-categories-filter-tag gds-chip';\n tagBtn.type = 'button';\n tagBtn.dataset['gengagePart'] = 'categories-filter-tag';\n tagBtn.textContent = tag.title;\n if (tag.action) {\n tagBtn.addEventListener('click', () => {\n context.onAction(tag.action!);\n });\n }\n tagsContainer.appendChild(tagBtn);\n }\n\n container.appendChild(tagsContainer);\n }\n\n return container;\n}\n\nfunction renderCategoryProductCard(product: NormalizedProduct, ctx: ChatUISpecRenderContext): HTMLElement {\n const card = document.createElement('div');\n card.className = 'gengage-chat-product-card gds-card gds-product-card gds-card-interactive';\n card.dataset['gengagePart'] = 'categories-product-card';\n\n if (product.imageUrl && isSafeImageUrl(product.imageUrl)) {\n const img = document.createElement('img');\n img.className = 'gengage-chat-product-card-img';\n img.src = product.imageUrl;\n img.alt = product.name;\n img.loading = 'lazy';\n addImageErrorHandler(img);\n card.appendChild(img);\n }\n\n const body = document.createElement('div');\n body.className = 'gengage-chat-product-card-body';\n\n const nameEl = document.createElement('div');\n nameEl.className = 'gengage-chat-product-card-name';\n nameEl.textContent = product.name;\n body.appendChild(nameEl);\n\n if (product.price) {\n const priceEl = document.createElement('div');\n priceEl.className = 'gengage-chat-product-card-price';\n priceEl.textContent = formatPrice(product.price, ctx.pricing);\n body.appendChild(priceEl);\n }\n\n card.appendChild(body);\n\n // Click → show product details\n if (ctx.onProductSelect || ctx.onAction) {\n card.classList.add('gds-clickable');\n card.addEventListener('click', () => {\n if (product.sku) {\n ctx.onAction({\n title: product.name,\n type: 'launchSingleProduct',\n payload: { sku: product.sku },\n });\n return;\n }\n ctx.onProductSelect?.(product as unknown as Record<string, unknown>);\n });\n }\n\n return card;\n}\n","/**\n * Renders a handoff notice when the backend escalates to a human agent.\n *\n * XSS safety: All text is set via textContent. No innerHTML.\n */\n\nimport type { ChatUISpecRenderContext } from '../types.js';\n\nexport function renderHandoffNotice(\n element: { props?: Record<string, unknown> },\n context: ChatUISpecRenderContext,\n): HTMLElement {\n const container = document.createElement('div');\n container.className = 'gengage-chat-handoff-notice gds-evidence-card gds-evidence-card-warning';\n container.dataset['gengagePart'] = 'handoff-notice';\n container.setAttribute('role', 'alert');\n\n const icon = document.createElement('span');\n icon.className = 'gengage-chat-handoff-notice-icon';\n icon.textContent = '\\u{1F464}'; // 👤\n icon.setAttribute('aria-hidden', 'true');\n container.appendChild(icon);\n\n const heading = document.createElement('h4');\n heading.className = 'gengage-chat-handoff-notice-heading';\n heading.textContent = context.i18n?.handoffHeading ?? 'Transferring to a support agent';\n container.appendChild(heading);\n\n const summary = element.props?.['summary'] as string | undefined;\n if (summary) {\n const summaryEl = document.createElement('p');\n summaryEl.className = 'gengage-chat-handoff-notice-summary';\n summaryEl.textContent = summary;\n container.appendChild(summaryEl);\n }\n\n return container;\n}\n","/**\n * Compact horizontal product card for inline chat-pane rendering.\n *\n * Production parity: mirrors the prior engine's `LaunchSingleProduct` component.\n * Renders when `productDetails` arrives — the full ProductDetailsPanel goes\n * to the left panel while this compact summary appears inline in chat messages.\n *\n * Layout: [image 64×64] [name · rating · price] [View link]\n */\n\nimport type { UIElement } from '../../common/types.js';\nimport type { ChatUISpecRenderContext } from '../types.js';\nimport { formatPrice } from '../../common/price-formatter.js';\nimport { isSafeUrl, safeSetAttribute } from '../../common/safe-html.js';\nimport { addImageErrorHandler, createStarRatingElement } from '../../common/product-utils.js';\n\nexport function renderProductSummaryCard(element: UIElement, ctx: ChatUISpecRenderContext): HTMLElement {\n const product = (element.props?.['product'] ?? element.props) as Record<string, unknown> | undefined;\n\n const card = document.createElement('div');\n card.className = 'gengage-chat-product-summary gds-card';\n card.dataset['gengagePart'] = 'product-summary-card';\n if (!product) return card;\n\n // Make entire card clickable to open product in panel\n card.classList.add('gds-clickable');\n card.addEventListener('click', (e) => {\n if ((e.target as HTMLElement).closest('a')) return;\n ctx.onProductSelect?.(product);\n });\n\n // --- Image (left side) ---\n const imageUrl = product['imageUrl'] as string | undefined;\n if (imageUrl && isSafeUrl(imageUrl)) {\n const imgWrap = document.createElement('div');\n imgWrap.className = 'gengage-chat-product-summary__image';\n imgWrap.dataset['gengagePart'] = 'product-summary-image';\n const img = document.createElement('img');\n img.loading = 'lazy';\n safeSetAttribute(img, 'src', imageUrl);\n const name = product['name'] as string | undefined;\n img.alt = name || 'Product image';\n addImageErrorHandler(img);\n imgWrap.appendChild(img);\n card.appendChild(imgWrap);\n }\n\n // --- Content (right side) ---\n const content = document.createElement('div');\n content.className = 'gengage-chat-product-summary__content';\n content.dataset['gengagePart'] = 'product-summary-content';\n\n // Product name (brand + name)\n const brand = product['brand'] as string | undefined;\n const name = product['name'] as string | undefined;\n if (name) {\n const nameEl = document.createElement('div');\n nameEl.className = 'gengage-chat-product-summary__name';\n nameEl.dataset['gengagePart'] = 'product-summary-name';\n // Only prepend brand if name doesn't already start with it\n const needsBrand = brand && !name.toLowerCase().startsWith(brand.toLowerCase());\n const fullName = needsBrand ? `${brand} ${name}` : name;\n nameEl.textContent = fullName;\n nameEl.title = fullName;\n content.appendChild(nameEl);\n }\n\n // Rating\n const rating = product['rating'];\n const reviewCount = product['reviewCount'];\n if (typeof rating === 'number' && Number.isFinite(rating) && rating > 0) {\n const ratingRow = document.createElement('div');\n ratingRow.className = 'gengage-chat-product-summary__rating';\n ratingRow.dataset['gengagePart'] = 'product-summary-rating';\n ratingRow.appendChild(createStarRatingElement(rating));\n if (typeof reviewCount === 'number' && Number.isFinite(reviewCount)) {\n const count = document.createElement('span');\n count.className = 'gengage-chat-product-summary__review-count';\n count.textContent = ` (${reviewCount})`;\n ratingRow.appendChild(count);\n }\n content.appendChild(ratingRow);\n }\n\n // Price row\n const price = product['price'] as string | undefined;\n const originalPrice = product['originalPrice'] as string | undefined;\n if (price) {\n const priceRow = document.createElement('div');\n priceRow.className = 'gengage-chat-product-summary__price';\n priceRow.dataset['gengagePart'] = 'product-summary-price';\n if (originalPrice && originalPrice !== price) {\n const orig = document.createElement('span');\n orig.className = 'gengage-chat-product-summary__price-original';\n orig.textContent = formatPrice(originalPrice, ctx.pricing);\n priceRow.appendChild(orig);\n priceRow.appendChild(document.createTextNode(' '));\n }\n const current = document.createElement('span');\n current.className = 'gengage-chat-product-summary__price-current';\n current.textContent = formatPrice(price, ctx.pricing);\n priceRow.appendChild(current);\n content.appendChild(priceRow);\n }\n\n card.appendChild(content);\n\n // --- CTA link (right edge) ---\n const url = product['url'] as string | undefined;\n if (url && isSafeUrl(url)) {\n const cta = document.createElement('a');\n cta.className = 'gengage-chat-product-summary__cta gds-chip gds-chip-active';\n cta.dataset['gengagePart'] = 'product-summary-cta';\n safeSetAttribute(cta, 'href', url);\n safeSetAttribute(cta, 'target', '_blank');\n safeSetAttribute(cta, 'rel', 'noopener noreferrer');\n cta.textContent = ctx.i18n?.productCtaLabel ?? 'View';\n card.appendChild(cta);\n }\n\n return card;\n}\n","/**\n * Renders a json-render UISpec into DOM elements.\n *\n * XSS safety: All text is set via textContent. URLs are validated for safe protocols.\n * No innerHTML is used anywhere in this module.\n */\n\nimport type { UISpec, UIElement, ActionPayload } from '../../common/types.js';\nimport { renderUISpecWithRegistry } from '../../common/renderer/index.js';\nimport type { UISpecDomRegistry, UISpecDomUnknownRenderer } from '../../common/renderer/index.js';\nimport type { ChatUISpecRenderContext, ProductSortState } from '../types.js';\nimport { formatPrice } from '../../common/price-formatter.js';\nimport type { PriceFormatConfig } from '../../common/price-formatter.js';\nimport { renderComparisonTable } from './ComparisonTable.js';\nimport type { ComparisonProduct, ComparisonAttribute } from './ComparisonTable.js';\nimport { renderReviewHighlights as renderReviewHighlightsComponent } from './ReviewHighlights.js';\nimport { renderAITopPicks } from './AITopPicks.js';\nimport { renderGroundingReviewCard } from './GroundingReviewCard.js';\nimport { renderAIGroupingCards } from './AIGroupingCards.js';\nimport { renderAISuggestedSearchCards } from './AISuggestedSearchCards.js';\nimport { renderFloatingComparisonButton } from './FloatingComparisonButton.js';\nimport { renderProsAndCons } from './ProsAndCons.js';\nimport { renderCategoriesContainer } from './CategoriesContainer.js';\nimport { renderHandoffNotice } from './HandoffNotice.js';\nimport { renderProductSummaryCard } from './ProductSummaryCard.js';\nimport { isSafeUrl, safeSetAttribute } from '../../common/safe-html.js';\nimport {\n clampRating,\n clampDiscount,\n addImageErrorHandler,\n createStarRatingElement,\n} from '../../common/product-utils.js';\n\nexport type UISpecRenderContext = ChatUISpecRenderContext;\n\nexport type ChatUISpecRegistry = UISpecDomRegistry<UISpecRenderContext>;\n\nexport type { PriceFormatConfig };\n\n/** @deprecated Use context.isMobile instead. Kept as fallback for custom renderers. */\nfunction isMobileViewport(): boolean {\n return window.innerWidth < 768;\n}\n\nconst DEFAULT_CHAT_UI_SPEC_REGISTRY: ChatUISpecRegistry = {\n ActionButtons: ({ element, context }) => renderActionButtons(element, context),\n ActionButton: ({ element, context }) => renderActionButton(element, context),\n ProductCard: ({ element, context }) => renderProductCard(element, context),\n ProductDetailsPanel: ({ element, context }) => renderProductDetailsPanel(element, context),\n ProductGrid: ({ element, spec, renderElement, context }) => renderProductGrid(element, spec, renderElement, context),\n ReviewHighlights: ({ element, context }) =>\n renderReviewHighlightsComponent(element, {\n emptyReviewsMessage: context.i18n?.emptyReviewsMessage,\n reviewFilterPositive: context.i18n?.reviewFilterPositive,\n reviewFilterNegative: context.i18n?.reviewFilterNegative,\n reviewCustomersMentionSingular: context.i18n?.reviewCustomersMentionSingular,\n reviewCustomersMentionPlural: context.i18n?.reviewCustomersMentionPlural,\n reviewSubjectsHeading: context.i18n?.reviewSubjectsHeading,\n }),\n ComparisonTable: ({ element, context }) => renderComparisonTableElement(element, context),\n AITopPicks: ({ element, context }) => renderAITopPicks(element, context),\n GroundingReviewCard: ({ element, context }) => renderGroundingReviewCard(element, context),\n AIGroupingCards: ({ element, context }) => renderAIGroupingCards(element, context),\n AISuggestedSearchCards: ({ element, context }) => renderAISuggestedSearchCards(element, context),\n ProsAndCons: ({ element }) => renderProsAndCons(element),\n CategoriesContainer: ({ element, context }) => renderCategoriesContainer(element, context),\n HandoffNotice: ({ element, context }) => renderHandoffNotice(element, context),\n ProductSummaryCard: ({ element, context }) => renderProductSummaryCard(element, context),\n Divider: ({ element }) => renderDivider(element),\n};\n\nexport const defaultChatUnknownUISpecRenderer: UISpecDomUnknownRenderer<UISpecRenderContext> = ({\n element,\n renderElement,\n}) => {\n if (import.meta.env?.DEV) {\n console.warn(`[gengage] Unknown ui_spec component type: ${element.type}`);\n }\n if (!element.children || element.children.length === 0) {\n return null;\n }\n const wrapper = document.createElement('div');\n for (const childId of element.children) {\n const rendered = renderElement(childId);\n if (rendered) wrapper.appendChild(rendered);\n }\n return wrapper;\n};\n\nexport function createDefaultChatUISpecRegistry(): ChatUISpecRegistry {\n return { ...DEFAULT_CHAT_UI_SPEC_REGISTRY };\n}\n\nexport function renderUISpec(\n spec: UISpec,\n ctx: UISpecRenderContext,\n registry = DEFAULT_CHAT_UI_SPEC_REGISTRY,\n unknownRenderer: UISpecDomUnknownRenderer<UISpecRenderContext> = defaultChatUnknownUISpecRenderer,\n): HTMLElement {\n return renderUISpecWithRegistry({\n spec,\n context: ctx,\n registry,\n containerClassName: 'gengage-chat-uispec',\n unknownRenderer,\n });\n}\n\nfunction renderActionButtons(element: UIElement, ctx: UISpecRenderContext): HTMLElement {\n const container = document.createElement('div');\n container.className = 'gengage-chat-action-buttons';\n\n const buttons = element.props?.['buttons'] as Array<{ label: string; action: ActionPayload }> | undefined;\n\n if (buttons) {\n for (const btn of buttons) {\n const button = document.createElement('button');\n button.className = 'gengage-chat-action-btn';\n button.textContent = btn.label;\n button.addEventListener('click', () => ctx.onAction(btn.action));\n container.appendChild(button);\n }\n }\n\n return container;\n}\n\nfunction renderActionButton(element: UIElement, ctx: UISpecRenderContext): HTMLElement {\n const button = document.createElement('button');\n button.className = 'gengage-chat-action-btn';\n const label = element.props?.['label'];\n if (typeof label === 'string') button.textContent = label;\n const action = element.props?.['action'] as ActionPayload | undefined;\n if (action) {\n button.addEventListener('click', () => ctx.onAction(action));\n }\n return button;\n}\n\nfunction renderProductCard(element: UIElement, ctx: UISpecRenderContext): HTMLElement {\n const card = document.createElement('div');\n card.className = 'gengage-chat-product-card gds-card gds-product-card gds-card-interactive';\n\n // Product data may be nested under `product` prop (adapter) or flat in props\n const product = (element.props?.['product'] ?? element.props) as Record<string, unknown> | undefined;\n if (!product) return card;\n\n // Store SKU as data attribute for comparison mode DOM refresh\n const productSku = product['sku'] as string | undefined;\n if (productSku) card.dataset['sku'] = productSku;\n const action = element.props?.['action'] as ActionPayload | undefined;\n\n // Make card clickable to show detail in panel (disabled in comparison select mode)\n if (ctx.onProductSelect || action) {\n card.classList.add('gds-clickable');\n card.addEventListener('click', (e) => {\n // Check live DOM: if card is inside a comparison wrapper, mode is active\n if (card.parentElement?.classList.contains('gengage-chat-comparison-select-wrapper')) return;\n if ((e.target as HTMLElement).closest('.gengage-chat-product-card-atc')) return;\n if ((e.target as HTMLElement).closest('.gengage-chat-product-card-cta')) return;\n if (action) {\n ctx.onAction(action);\n return;\n }\n ctx.onProductSelect?.(product);\n });\n }\n\n const imageUrl = product['imageUrl'] as string | undefined;\n if (imageUrl && isSafeUrl(imageUrl)) {\n const imgWrapper = document.createElement('div');\n imgWrapper.className = 'gengage-chat-product-card-img-wrapper';\n\n const img = document.createElement('img');\n img.className = 'gengage-chat-product-card-img';\n img.loading = 'lazy';\n safeSetAttribute(img, 'src', imageUrl);\n const name = product['name'] as string | undefined;\n if (name) img.alt = name;\n addImageErrorHandler(img);\n imgWrapper.appendChild(img);\n\n // Discount badge (top-left of image)\n const discountPercent = product['discountPercent'] as number | undefined;\n if (typeof discountPercent === 'number' && discountPercent > 0) {\n const badge = document.createElement('span');\n badge.className = 'gengage-chat-product-card-discount-badge';\n badge.textContent = `%${clampDiscount(discountPercent)}`;\n imgWrapper.appendChild(badge);\n }\n\n const imgActions = document.createElement('div');\n imgActions.className = 'gengage-chat-product-card-img-actions';\n\n // Favorite (top of action stack — matches retail card reference)\n const favSku = product['sku'] as string | undefined;\n if (favSku && ctx.onFavoriteToggle) {\n const heart = document.createElement('button');\n heart.className = 'gengage-chat-favorite-btn';\n heart.type = 'button';\n heart.dataset.gengageFavoriteSku = favSku;\n heart.setAttribute('aria-label', ctx.i18n?.addToFavoritesLabel ?? 'Add to favorites');\n const isFav = ctx.favoritedSkus?.has(favSku) ?? false;\n if (isFav) heart.classList.add('gengage-chat-favorite-btn--active');\n const svgFill = isFav ? 'currentColor' : 'none';\n heart.innerHTML = `<svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"${svgFill}\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z\"/></svg>`;\n heart.addEventListener('click', (e) => {\n e.stopPropagation();\n heart.classList.toggle('gengage-chat-favorite-btn--active');\n const svg = heart.querySelector('svg');\n if (svg) {\n svg.setAttribute(\n 'fill',\n heart.classList.contains('gengage-chat-favorite-btn--active') ? 'currentColor' : 'none',\n );\n }\n ctx.onFavoriteToggle!(favSku, product);\n });\n imgActions.appendChild(heart);\n }\n\n // Find similar — icon button + visually hidden label (panel: always visible stack)\n const findSimilarSku = product['sku'] as string | undefined;\n const findSimilarLabel = ctx.i18n?.findSimilarLabel ?? 'Find Similar';\n if (findSimilarSku) {\n const pill = document.createElement('button');\n pill.className = 'gengage-chat-find-similar-pill';\n pill.type = 'button';\n pill.setAttribute('aria-label', findSimilarLabel);\n pill.dataset['tooltip'] = findSimilarLabel;\n pill.innerHTML =\n `<span class=\"gengage-chat-find-similar-pill-icon\" aria-hidden=\"true\">` +\n `<svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.25\" stroke-linecap=\"round\" stroke-linejoin=\"round\">` +\n `<circle cx=\"10.5\" cy=\"10.5\" r=\"6.5\"/>` +\n `<path d=\"M16 16l5.5 5.5\"/>` +\n `</svg></span>`;\n const pillText = document.createElement('span');\n pillText.className = 'gengage-chat-find-similar-pill-text';\n pillText.textContent = findSimilarLabel;\n pill.appendChild(pillText);\n pill.addEventListener('click', (e) => {\n e.stopPropagation();\n ctx.onAction({\n title: findSimilarLabel,\n type: 'findSimilar',\n payload: { sku: findSimilarSku, ...(imageUrl ? { image_url: imageUrl } : {}) },\n });\n });\n imgActions.appendChild(pill);\n }\n\n if (imgActions.childElementCount > 0) {\n imgWrapper.appendChild(imgActions);\n }\n\n card.appendChild(imgWrapper);\n }\n\n const body = document.createElement('div');\n body.className = 'gengage-chat-product-card-body';\n\n const brand = product['brand'] as string | undefined;\n if (brand) {\n const brandEl = document.createElement('div');\n brandEl.className = 'gengage-chat-product-card-brand';\n brandEl.textContent = brand;\n body.appendChild(brandEl);\n }\n\n const rating = product['rating'];\n const reviewCount = product['reviewCount'];\n const price = product['price'] as string | undefined;\n const originalPrice = product['originalPrice'] as string | undefined;\n const priceAsync = product['price_async'] as boolean | undefined;\n\n const hasNumericRating = typeof rating === 'number' && Number.isFinite(rating) && rating > 0;\n const metaRow = document.createElement('div');\n metaRow.className = 'gengage-chat-product-card-meta-row';\n\n const priceBlock = document.createElement('div');\n // Keep .gengage-chat-product-card-price for tests and legacy selectors\n priceBlock.className = 'gengage-chat-product-card-price gengage-chat-product-card-price-block';\n\n if (priceAsync === true) {\n const skeleton = document.createElement('span');\n skeleton.className = 'gengage-chat-price-skeleton';\n priceBlock.appendChild(skeleton);\n setTimeout(() => {\n if (!skeleton.parentElement) return;\n if (price && parseFloat(price) > 0) {\n const op = product['originalPrice'] as string | undefined;\n if (op && op !== price) {\n const cur = document.createElement('span');\n cur.className = 'gengage-chat-product-card-current-price';\n cur.textContent = formatPrice(price, ctx.pricing);\n const origEl = document.createElement('span');\n origEl.className = 'gengage-chat-product-card-original-price';\n origEl.textContent = formatPrice(op, ctx.pricing);\n const frag = document.createDocumentFragment();\n frag.appendChild(cur);\n frag.appendChild(document.createTextNode(' '));\n frag.appendChild(origEl);\n skeleton.replaceWith(frag);\n } else {\n const cur = document.createElement('span');\n cur.className = 'gengage-chat-product-card-current-price';\n cur.textContent = formatPrice(price, ctx.pricing);\n skeleton.replaceWith(cur);\n }\n } else {\n skeleton.remove();\n }\n }, 300);\n } else if (price && parseFloat(price) > 0) {\n const current = document.createElement('span');\n current.className = 'gengage-chat-product-card-current-price';\n current.textContent = formatPrice(price, ctx.pricing);\n priceBlock.appendChild(current);\n if (originalPrice && originalPrice !== price) {\n priceBlock.appendChild(document.createTextNode(' '));\n const orig = document.createElement('span');\n orig.className = 'gengage-chat-product-card-original-price';\n orig.textContent = formatPrice(originalPrice, ctx.pricing);\n priceBlock.appendChild(orig);\n }\n }\n\n if (priceBlock.childElementCount > 0 || priceAsync === true) {\n metaRow.appendChild(priceBlock);\n }\n\n if (hasNumericRating) {\n const ratingCompact = document.createElement('div');\n ratingCompact.className = 'gengage-chat-product-card-rating gengage-chat-product-card-rating-compact';\n const rc = clampRating(rating);\n const labelParts = [`${rc.toFixed(1)}`, 'out of 5 stars'];\n if (typeof reviewCount === 'number' && Number.isFinite(reviewCount)) {\n labelParts.push(`(${reviewCount} reviews)`);\n }\n ratingCompact.setAttribute('aria-label', labelParts.join(' '));\n const starEl = document.createElement('span');\n starEl.className = 'gengage-chat-product-card-rating-compact-star';\n starEl.setAttribute('aria-hidden', 'true');\n starEl.innerHTML =\n '<svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M12 3.6l2.58 5.23 5.77.84-4.17 4.07.98 5.75L12 16.78l-5.16 2.71.99-5.75L3.66 9.67l5.76-.84L12 3.6z\"/></svg>';\n const valEl = document.createElement('span');\n valEl.className = 'gengage-chat-product-card-rating-compact-value';\n valEl.textContent = rc.toFixed(1);\n ratingCompact.appendChild(starEl);\n ratingCompact.appendChild(valEl);\n metaRow.appendChild(ratingCompact);\n }\n\n if (metaRow.childElementCount === 0) {\n metaRow.classList.add('gengage-chat-product-card-meta-row--empty');\n metaRow.setAttribute('aria-hidden', 'true');\n }\n body.appendChild(metaRow);\n\n const name = product['name'] as string | undefined;\n if (name) {\n const nameEl = document.createElement('div');\n nameEl.className = 'gengage-chat-product-card-name';\n nameEl.textContent = name;\n nameEl.title = name;\n body.appendChild(nameEl);\n }\n\n // Stock indicator\n const inStock = product['inStock'];\n if (typeof inStock === 'boolean') {\n const stock = document.createElement('div');\n stock.className = `gengage-chat-product-card-stock ${inStock ? 'is-in-stock' : 'is-out-of-stock'}`;\n stock.textContent = inStock\n ? (ctx.i18n?.inStockLabel ?? 'In Stock')\n : (ctx.i18n?.outOfStockLabel ?? 'Out of Stock');\n body.appendChild(stock);\n }\n\n card.appendChild(body);\n\n const url = product['url'] as string | undefined;\n const sku = product['sku'] as string | undefined;\n const cartCode = product['cartCode'] as string | undefined;\n\n const hasCart = !!(cartCode && sku && inStock !== false);\n const ctaLabel = ctx.i18n?.productCtaLabel ?? 'View';\n\n if (hasCart) {\n const buyFooter = document.createElement('div');\n buyFooter.className = 'gengage-chat-product-card-buy-footer';\n\n const trigger = document.createElement('button');\n trigger.type = 'button';\n trigger.className = 'gengage-chat-product-card-buy-trigger';\n trigger.textContent = ctaLabel;\n trigger.addEventListener('click', (e) => {\n e.stopPropagation();\n ctx.onAction({\n title: ctx.i18n?.addToCartButton ?? ctaLabel,\n type: 'addToCart',\n payload: { sku: sku!, cartCode: cartCode!, quantity: 1 },\n });\n });\n\n buyFooter.appendChild(trigger);\n card.appendChild(buyFooter);\n } else if (action) {\n const cta = document.createElement('button');\n cta.className = 'gengage-chat-product-card-cta';\n cta.type = 'button';\n cta.textContent = action.type === 'launchSingleProduct' ? ctaLabel : action.title || ctaLabel;\n cta.addEventListener('click', (e) => {\n if (card.parentElement?.classList.contains('gengage-chat-comparison-select-wrapper')) {\n e.stopPropagation();\n return;\n }\n ctx.onAction(action);\n });\n card.appendChild(cta);\n } else if (url && isSafeUrl(url)) {\n const cta = document.createElement('a');\n cta.className = 'gengage-chat-product-card-cta';\n safeSetAttribute(cta, 'href', url);\n safeSetAttribute(cta, 'target', '_blank');\n safeSetAttribute(cta, 'rel', 'noopener noreferrer');\n cta.textContent = ctaLabel;\n cta.addEventListener('click', (e) => {\n if (card.parentElement?.classList.contains('gengage-chat-comparison-select-wrapper')) {\n e.preventDefault();\n e.stopPropagation();\n return;\n }\n if (ctx.onProductClick && sku) {\n e.preventDefault();\n ctx.onProductClick({ sku, url });\n }\n });\n card.appendChild(cta);\n }\n\n // Wrap with checkbox overlay when comparison select mode is active\n if (ctx.comparisonSelectMode && sku && ctx.onToggleComparisonSku) {\n const wrapper = document.createElement('div');\n wrapper.className = 'gengage-chat-comparison-select-wrapper';\n const isSelected = ctx.comparisonSelectedSkus?.includes(sku) ?? false;\n if (isSelected) wrapper.classList.add('gengage-chat-comparison-select-wrapper--selected');\n\n const productName = (product['name'] as string | undefined) ?? sku;\n const hintText =\n ctx.i18n?.comparisonSelectCardHint ?? 'Tap anywhere on the card to add or remove it from comparison.';\n wrapper.setAttribute('role', 'group');\n wrapper.setAttribute('aria-label', `${String(productName)}. ${hintText}`);\n\n const toggle = document.createElement('button');\n toggle.type = 'button';\n toggle.className = 'gengage-chat-comparison-checkbox';\n toggle.dataset['selected'] = isSelected ? 'true' : 'false';\n toggle.setAttribute('aria-pressed', isSelected ? 'true' : 'false');\n const icon = document.createElement('span');\n icon.className = 'gengage-chat-comparison-checkbox-icon';\n icon.innerHTML = isSelected\n ? '<svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M20 6L9 17l-5-5\"/></svg>'\n : '<span class=\"gengage-chat-comparison-checkbox-dot\"></span>';\n const label = document.createElement('span');\n label.className = 'gengage-chat-comparison-checkbox-label';\n label.textContent = isSelected\n ? (ctx.i18n?.comparisonSelectedLabel ?? 'Selected')\n : (ctx.i18n?.comparisonSelectLabel ?? 'Select to compare');\n toggle.appendChild(icon);\n toggle.appendChild(label);\n toggle.addEventListener('click', (e) => {\n e.stopPropagation();\n ctx.onToggleComparisonSku?.(sku);\n });\n\n const hint = document.createElement('div');\n hint.className = 'gengage-chat-comparison-card-hint';\n hint.setAttribute('aria-hidden', 'true');\n hint.textContent = hintText;\n\n // Clicking anywhere on the card toggles comparison selection — no product detail navigation.\n // Do NOT manually flip checkbox.checked here: onToggleComparisonSku triggers\n // _refreshComparisonUI which syncs checkbox state from the canonical Set.\n wrapper.addEventListener('click', (e) => {\n if ((e.target as HTMLElement).closest('.gengage-chat-comparison-checkbox')) return;\n e.stopPropagation();\n ctx.onToggleComparisonSku?.(sku);\n });\n\n wrapper.appendChild(toggle);\n wrapper.appendChild(hint);\n wrapper.appendChild(card);\n return wrapper;\n }\n\n return card;\n}\n\n/* clampRating, clampDiscount, addImageErrorHandler, renderStarRating\n are imported from ../../common/product-utils.js */\n\ntype ProductFeatureEntry = { key: string; value: string };\ntype ProductDescriptionContent = { text: string; html?: string };\n\nconst COLOR_VARIANT_NAMES = new Set(['color', 'colour', 'renk', 'renk kodu', 'color code']);\nconst SIZE_VARIANT_NAMES = new Set(['size', 'beden', 'boyut']);\nconst FINISH_VARIANT_NAMES = new Set(['finish', 'bitiş', 'bitişi']);\nconst VARIANT_ARRAY_KEYS = [\n 'variants',\n 'variantOptions',\n 'variant_options',\n 'productVariants',\n 'product_variants',\n 'options',\n];\nconst PRODUCT_DESCRIPTION_ALLOWED_TAGS = new Set([\n 'H2',\n 'H3',\n 'H4',\n 'P',\n 'BR',\n 'UL',\n 'OL',\n 'LI',\n 'STRONG',\n 'B',\n 'EM',\n 'I',\n]);\nconst PRODUCT_DESCRIPTION_BLOCKED_TAGS = new Set(['SCRIPT', 'STYLE', 'NOSCRIPT', 'IFRAME', 'OBJECT', 'EMBED']);\n\nfunction productString(product: Record<string, unknown>, ...keys: string[]): string | undefined {\n for (const key of keys) {\n const value = product[key];\n if (typeof value === 'string') {\n const trimmed = value.trim();\n if (trimmed.length > 0) return trimmed;\n }\n }\n return undefined;\n}\n\nfunction productNumber(product: Record<string, unknown>, ...keys: string[]): number | undefined {\n for (const key of keys) {\n const value = product[key];\n if (typeof value === 'number' && Number.isFinite(value)) return value;\n if (typeof value === 'string') {\n const parsed = Number(value.replace(',', '.'));\n if (Number.isFinite(parsed)) return parsed;\n }\n }\n return undefined;\n}\n\nfunction productBoolean(product: Record<string, unknown>, ...keys: string[]): boolean | undefined {\n for (const key of keys) {\n const value = product[key];\n if (typeof value === 'boolean') return value;\n }\n return undefined;\n}\n\nfunction productRecord(product: Record<string, unknown>, ...keys: string[]): Record<string, unknown> | undefined {\n for (const key of keys) {\n const value = product[key];\n if (value && typeof value === 'object' && !Array.isArray(value)) {\n return value as Record<string, unknown>;\n }\n }\n return undefined;\n}\n\nfunction productStringArray(value: unknown): string[] {\n if (!Array.isArray(value)) return [];\n return value.filter((item): item is string => typeof item === 'string' && item.trim().length > 0);\n}\n\nfunction productImageList(product: Record<string, unknown>): string[] {\n const urls = [\n ...productStringArray(product['images']),\n productString(product, 'imageUrl', 'image_url', 'image'),\n ].filter((url): url is string => !!url && isSafeUrl(url));\n return Array.from(new Set(urls));\n}\n\nfunction htmlToPlainText(html: string): string {\n if (typeof DOMParser !== 'undefined') {\n const doc = new DOMParser().parseFromString(html, 'text/html');\n return (doc.body.textContent ?? '').replace(/\\s+/g, ' ').trim();\n }\n return html\n .replace(/<[^>]*>/g, ' ')\n .replace(/\\s+/g, ' ')\n .trim();\n}\n\nfunction productDescription(product: Record<string, unknown>): ProductDescriptionContent | undefined {\n const html = productString(product, 'description_html', 'descriptionHtml');\n if (html) {\n const text = htmlToPlainText(html);\n if (text) return { text, html };\n }\n\n const description = productString(product, 'description');\n return description ? { text: description } : undefined;\n}\n\nfunction normalizeProductFeatures(value: unknown): ProductFeatureEntry[] {\n if (Array.isArray(value)) {\n return value\n .map((entry): ProductFeatureEntry | null => {\n if (!entry || typeof entry !== 'object') return null;\n const record = entry as Record<string, unknown>;\n const key = productString(record, 'key', 'name', 'label', 'title');\n const rawValue = record['value'];\n const val =\n typeof rawValue === 'string' || typeof rawValue === 'number' || typeof rawValue === 'boolean'\n ? String(rawValue).trim()\n : undefined;\n if (!key || !val) return null;\n return { key, value: val };\n })\n .filter((entry): entry is ProductFeatureEntry => entry !== null);\n }\n\n if (value && typeof value === 'object') {\n return Object.entries(value as Record<string, unknown>)\n .map(([key, rawValue]): ProductFeatureEntry | null => {\n const val =\n typeof rawValue === 'string' || typeof rawValue === 'number' || typeof rawValue === 'boolean'\n ? String(rawValue).trim()\n : undefined;\n if (!key || !val) return null;\n return { key, value: val };\n })\n .filter((entry): entry is ProductFeatureEntry => entry !== null);\n }\n\n return [];\n}\n\nfunction productFeatureEntries(product: Record<string, unknown>): ProductFeatureEntry[] {\n const features = normalizeProductFeatures(product['features']);\n if (features.length > 0) return features;\n return normalizeProductFeatures(product['specifications']);\n}\n\nfunction productSpecifications(\n product: Record<string, unknown>,\n): Record<string, string> | Array<{ key: string; value: string }> | undefined {\n const explicit = product['specifications'];\n const explicitEntries = normalizeProductFeatures(explicit);\n if (explicitEntries.length > 0) {\n return Array.isArray(explicit)\n ? explicitEntries\n : Object.fromEntries(explicitEntries.map((item) => [item.key, item.value]));\n }\n\n const featureEntries = normalizeProductFeatures(product['features']);\n return featureEntries.length > 0 ? featureEntries : undefined;\n}\n\nfunction variantString(variant: Record<string, unknown>, ...keys: string[]): string | undefined {\n return productString(variant, ...keys);\n}\n\nfunction variantNumber(variant: Record<string, unknown>, ...keys: string[]): number | undefined {\n return productNumber(variant, ...keys);\n}\n\nfunction variantDisplayLabel(variant: Record<string, unknown>): string | undefined {\n return variantString(\n variant,\n 'value',\n 'option_value',\n 'attribute_value',\n 'label',\n 'title',\n 'name',\n 'variant_name',\n 'sku',\n );\n}\n\nfunction variantTypeName(variant: Record<string, unknown>): string | undefined {\n const explicitType = variantString(variant, 'type', 'attribute', 'option_name', 'attribute_name');\n if (explicitType) return explicitType;\n return variantString(variant, 'value') ? variantString(variant, 'name', 'variant_name') : undefined;\n}\n\nfunction isColorVariant(variant: Record<string, unknown>): boolean {\n const typeName = variantTypeName(variant)?.toLowerCase();\n return !!(\n productString(variant, 'color', 'colour', 'color_hex', 'hex', 'swatch', 'swatchColor') ||\n (typeName && COLOR_VARIANT_NAMES.has(typeName))\n );\n}\n\nfunction safeCssColor(value: string | undefined): string | undefined {\n if (!value) return undefined;\n const trimmed = value.trim();\n if (!trimmed || trimmed.includes(';')) return undefined;\n if (typeof CSS !== 'undefined' && CSS.supports?.('color', trimmed)) return trimmed;\n if (/^#(?:[0-9a-f]{3}|[0-9a-f]{6}|[0-9a-f]{8})$/i.test(trimmed)) return trimmed;\n return undefined;\n}\n\nfunction variantSwatchColor(variant: Record<string, unknown>): string | undefined {\n const explicit = safeCssColor(variantString(variant, 'swatchColor', 'swatch', 'color_hex', 'hex', 'color', 'colour'));\n if (explicit) return explicit;\n if (!isColorVariant(variant)) return undefined;\n return safeCssColor(variantDisplayLabel(variant));\n}\n\nfunction variantImage(variant: Record<string, unknown>): string | undefined {\n return variantString(variant, 'image', 'imageUrl', 'image_url', 'swatchImage', 'swatch_image');\n}\n\nfunction variantPrice(variant: Record<string, unknown>): number | string | undefined {\n return (\n variantNumber(variant, 'price_discounted', 'priceDiscounted') ??\n variantString(variant, 'price_discounted', 'priceDiscounted') ??\n variantNumber(variant, 'price') ??\n variantString(variant, 'price')\n );\n}\n\nfunction isVariantFacetName(key: string): boolean {\n const normalized = key.trim().toLowerCase();\n return (\n COLOR_VARIANT_NAMES.has(normalized) || SIZE_VARIANT_NAMES.has(normalized) || FINISH_VARIANT_NAMES.has(normalized)\n );\n}\n\nfunction canonicalVariantFacetName(key: string): string {\n const normalized = key.trim().toLowerCase();\n if (normalized === 'renk kodu') return 'Renk';\n if (normalized === 'color code') return 'Color';\n return key.trim();\n}\n\nfunction recordVariantArrays(product: Record<string, unknown>): Array<Record<string, unknown>> | undefined {\n for (const key of VARIANT_ARRAY_KEYS) {\n const value = product[key];\n if (!Array.isArray(value)) continue;\n const variants = value.filter(\n (item): item is Record<string, unknown> => !!item && typeof item === 'object' && !Array.isArray(item),\n );\n if (variants.length > 0) return variants;\n }\n return undefined;\n}\n\nfunction productVariants(product: Record<string, unknown>): Array<Record<string, unknown>> {\n const explicitVariants = recordVariantArrays(product);\n if (explicitVariants) return explicitVariants;\n\n const sku = productString(product, 'sku');\n const inStock = productBoolean(product, 'inStock', 'in_stock');\n const fallbackVariants: Array<Record<string, unknown>> = [];\n const seen = new Set<string>();\n\n const addVariant = (key: string, value: unknown) => {\n if (!isVariantFacetName(key)) return;\n if (typeof value !== 'string' && typeof value !== 'number' && typeof value !== 'boolean') return;\n const variantValue = String(value).trim();\n if (!variantValue) return;\n const variantName = canonicalVariantFacetName(key);\n const dedupeKey = `${variantName.toLowerCase()}:${variantValue.toLowerCase()}`;\n if (seen.has(dedupeKey)) return;\n seen.add(dedupeKey);\n fallbackVariants.push({\n name: variantName,\n value: variantValue,\n sku,\n inStock,\n });\n };\n\n const facetHits = productRecord(product, 'facetHits', 'facet_hits');\n if (facetHits) {\n for (const [key, value] of Object.entries(facetHits)) {\n addVariant(key, value);\n }\n }\n\n for (const feature of productFeatureEntries(product)) {\n addVariant(feature.key, feature.value);\n }\n\n return fallbackVariants;\n}\n\nfunction variantSectionLabel(variants: Array<Record<string, unknown>>, ctx: UISpecRenderContext): string {\n const types = Array.from(new Set(variants.map(variantTypeName).filter((label): label is string => !!label)));\n if (types.length === 1) return `${variants.length} ${types[0]}`;\n return ctx.i18n?.variantsLabel ?? 'Variants';\n}\n\nfunction renderProductDetailsPanel(element: UIElement, ctx: UISpecRenderContext): HTMLElement {\n const panel = document.createElement('article');\n panel.className = 'gengage-chat-product-details-panel';\n\n const product = (element.props?.['product'] ?? element.props) as Record<string, unknown> | undefined;\n if (!product) return panel;\n\n const name = productString(product, 'name');\n const brand = productString(product, 'brand');\n const sku = productString(product, 'sku');\n const cartCode = productString(product, 'cartCode', 'cart_code');\n const price = productString(product, 'price');\n const originalPrice = productString(product, 'originalPrice', 'price_original');\n const priceAsync = productBoolean(product, 'price_async');\n const inStock = productBoolean(product, 'inStock', 'in_stock');\n const reviewCount = productNumber(product, 'reviewCount', 'review_count');\n const rating = productNumber(product, 'rating');\n const safeImages = productImageList(product);\n const featureEntries = productFeatureEntries(product).slice(0, 4);\n\n // Image gallery or single image\n if (safeImages.length > 1) {\n // Gallery with thumbnails + prev/next arrows\n const media = document.createElement('div');\n media.className =\n 'gengage-chat-product-details-media gengage-chat-product-details-gallery gengage-chat-product-details-img-wrap';\n\n const mainImg = document.createElement('img');\n mainImg.className = 'gengage-chat-product-details-img';\n safeSetAttribute(mainImg, 'src', safeImages[0]!);\n mainImg.alt = name ?? 'Product image';\n addImageErrorHandler(mainImg);\n media.appendChild(mainImg);\n\n const thumbStrip = document.createElement('div');\n thumbStrip.className = 'gengage-chat-product-gallery-thumbs';\n\n const MAX_VISIBLE_THUMBNAILS = 6;\n let activeThumb: HTMLElement | null = null;\n let activeThumbIdx = 0;\n\n const i18n = ctx.i18n;\n const prevLabel = i18n?.galleryPrevAriaLabel ?? 'Previous image';\n const nextLabel = i18n?.galleryNextAriaLabel ?? 'Next image';\n\n const navSvg = (dir: 'prev' | 'next') =>\n dir === 'prev'\n ? '<svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><polyline points=\"15 18 9 12 15 6\"/></svg>'\n : '<svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><polyline points=\"9 18 15 12 9 6\"/></svg>';\n\n const prevBtn = document.createElement('button');\n prevBtn.type = 'button';\n prevBtn.className =\n 'gengage-chat-product-gallery-nav gengage-chat-product-gallery-nav--prev gds-btn gds-btn-ghost gds-icon-btn';\n prevBtn.setAttribute('aria-label', prevLabel);\n prevBtn.innerHTML = navSvg('prev');\n\n const nextBtn = document.createElement('button');\n nextBtn.type = 'button';\n nextBtn.className =\n 'gengage-chat-product-gallery-nav gengage-chat-product-gallery-nav--next gds-btn gds-btn-ghost gds-icon-btn';\n nextBtn.setAttribute('aria-label', nextLabel);\n nextBtn.innerHTML = navSvg('next');\n\n const updateNavDisabled = (): void => {\n prevBtn.disabled = activeThumbIdx <= 0;\n nextBtn.disabled = activeThumbIdx >= safeImages.length - 1;\n };\n\n const gotoIndex = (nextIdx: number): void => {\n if (nextIdx < 0 || nextIdx >= safeImages.length || nextIdx === activeThumbIdx) return;\n const nextUrl = safeImages[nextIdx];\n if (!nextUrl) return;\n safeSetAttribute(mainImg, 'src', nextUrl);\n const thumbEls = thumbStrip.querySelectorAll('.gengage-chat-product-gallery-thumb');\n if (activeThumb) activeThumb.classList.remove('gengage-chat-product-gallery-thumb--active');\n if (nextIdx < MAX_VISIBLE_THUMBNAILS && thumbEls[nextIdx]) {\n (thumbEls[nextIdx] as HTMLElement).classList.add('gengage-chat-product-gallery-thumb--active');\n activeThumb = thumbEls[nextIdx] as HTMLElement;\n } else {\n activeThumb = null;\n }\n activeThumbIdx = nextIdx;\n updateNavDisabled();\n };\n\n for (let i = 0; i < safeImages.length; i++) {\n const imgUrl = safeImages[i]!;\n if (i >= MAX_VISIBLE_THUMBNAILS) break;\n const thumb = document.createElement('img');\n thumb.className = 'gengage-chat-product-gallery-thumb';\n if (i === 0) {\n thumb.classList.add('gengage-chat-product-gallery-thumb--active');\n activeThumb = thumb;\n }\n safeSetAttribute(thumb, 'src', imgUrl);\n thumb.alt = `${name ?? 'Product'} ${i + 1}`;\n thumb.width = 48;\n thumb.height = 48;\n addImageErrorHandler(thumb);\n thumb.addEventListener('click', () => {\n gotoIndex(i);\n });\n thumbStrip.appendChild(thumb);\n }\n\n // \"+N more\" indicator when thumbnails exceed limit\n if (safeImages.length > MAX_VISIBLE_THUMBNAILS) {\n const more = document.createElement('span');\n more.className = 'gengage-chat-product-gallery-thumb-more';\n more.textContent = `+${safeImages.length - MAX_VISIBLE_THUMBNAILS}`;\n thumbStrip.appendChild(more);\n }\n\n prevBtn.addEventListener('click', (e) => {\n e.stopPropagation();\n gotoIndex(activeThumbIdx - 1);\n });\n nextBtn.addEventListener('click', (e) => {\n e.stopPropagation();\n gotoIndex(activeThumbIdx + 1);\n });\n updateNavDisabled();\n\n // Touch swipe gesture for gallery navigation\n let touchStartX = 0;\n const SWIPE_THRESHOLD = 50;\n\n mainImg.addEventListener(\n 'touchstart',\n (e: TouchEvent) => {\n touchStartX = e.changedTouches[0]!.clientX;\n },\n { passive: true },\n );\n\n mainImg.addEventListener('touchend', (e: TouchEvent) => {\n const touchEndX = e.changedTouches[0]!.clientX;\n const diff = touchStartX - touchEndX;\n if (Math.abs(diff) < SWIPE_THRESHOLD) return;\n\n const nextIdx =\n diff > 0\n ? Math.min(activeThumbIdx + 1, safeImages.length - 1) // swipe left → next\n : Math.max(activeThumbIdx - 1, 0); // swipe right → prev\n\n gotoIndex(nextIdx);\n });\n\n media.appendChild(prevBtn);\n media.appendChild(nextBtn);\n media.appendChild(thumbStrip);\n\n panel.appendChild(media);\n } else if (safeImages.length === 1) {\n // Single image fallback\n const media = document.createElement('div');\n media.className = 'gengage-chat-product-details-media gengage-chat-product-details-img-wrap';\n const img = document.createElement('img');\n img.className = 'gengage-chat-product-details-img';\n img.loading = 'lazy';\n safeSetAttribute(img, 'src', safeImages[0]!);\n addImageErrorHandler(img);\n img.alt = name ?? 'Product image';\n media.appendChild(img);\n\n panel.appendChild(media);\n }\n\n const content = document.createElement('div');\n content.className = 'gengage-chat-product-details-content';\n\n if (brand && (!name || !name.toLowerCase().startsWith(brand.toLowerCase()))) {\n const brandEl = document.createElement('div');\n brandEl.className = 'gengage-chat-product-details-brand';\n brandEl.textContent = brand;\n content.appendChild(brandEl);\n }\n\n if (name) {\n const title = document.createElement('h3');\n title.className = 'gengage-chat-product-details-title';\n title.textContent = name;\n title.title = name;\n content.appendChild(title);\n }\n\n if (typeof rating === 'number' && Number.isFinite(rating) && rating > 0) {\n const ratingRow = document.createElement(sku ? 'button' : 'div');\n ratingRow.className = 'gengage-chat-product-details-rating';\n if (sku) {\n (ratingRow as HTMLButtonElement).type = 'button';\n ratingRow.classList.add('gengage-chat-product-details-rating--clickable');\n ratingRow.setAttribute('aria-label', ctx.i18n?.groundingReviewCta ?? 'Read Reviews');\n ratingRow.addEventListener('click', () => {\n ctx.onAction({\n title: ctx.i18n?.customerReviewsTitle ?? 'Customer Reviews',\n type: 'reviewSummary',\n payload: { sku },\n });\n });\n }\n ratingRow.appendChild(createStarRatingElement(rating));\n const ratingValue = document.createElement('span');\n ratingValue.className = 'gengage-chat-product-details-rating-value';\n ratingValue.textContent = clampRating(rating).toFixed(1);\n ratingRow.appendChild(ratingValue);\n if (typeof reviewCount === 'number' && Number.isFinite(reviewCount)) {\n const count = document.createElement('span');\n count.className = 'gengage-chat-product-details-review-count';\n count.textContent = ` (${reviewCount})`;\n ratingRow.appendChild(count);\n }\n content.appendChild(ratingRow);\n }\n\n if (priceAsync === true) {\n const priceRow = document.createElement('div');\n priceRow.className = 'gengage-chat-product-details-price';\n const skeleton = document.createElement('span');\n skeleton.className = 'gengage-chat-price-skeleton';\n priceRow.appendChild(skeleton);\n content.appendChild(priceRow);\n // Replace skeleton with actual price after delay\n setTimeout(() => {\n if (!skeleton.parentElement) return; // Element removed from DOM\n if (price && parseFloat(price) > 0) {\n const currentPrice = document.createElement('span');\n currentPrice.className = 'gengage-chat-product-details-current-price';\n currentPrice.textContent = formatPrice(price, ctx.pricing);\n skeleton.replaceWith(currentPrice);\n } else {\n skeleton.remove();\n }\n }, 300);\n } else if (price && parseFloat(price) > 0) {\n const priceRow = document.createElement('div');\n priceRow.className = 'gengage-chat-product-details-price';\n if (originalPrice && originalPrice !== price) {\n const oldPrice = document.createElement('span');\n oldPrice.className = 'gengage-chat-product-details-original-price';\n oldPrice.textContent = formatPrice(originalPrice, ctx.pricing);\n priceRow.appendChild(oldPrice);\n priceRow.appendChild(document.createTextNode(' '));\n }\n const currentPrice = document.createElement('span');\n currentPrice.className = 'gengage-chat-product-details-current-price';\n currentPrice.textContent = formatPrice(price, ctx.pricing);\n priceRow.appendChild(currentPrice);\n const discountPercent = productNumber(product, 'discountPercent', 'price_discount_rate');\n if (typeof discountPercent === 'number' && discountPercent > 0) {\n const discountBadge = document.createElement('span');\n discountBadge.className = 'gengage-chat-product-details-discount-badge';\n discountBadge.textContent = `%${clampDiscount(discountPercent)}`;\n priceRow.appendChild(discountBadge);\n }\n content.appendChild(priceRow);\n }\n\n if (typeof inStock === 'boolean') {\n const stock = document.createElement('div');\n stock.className = `gengage-chat-product-details-stock ${inStock ? 'is-in-stock' : 'is-out-of-stock'}`;\n stock.textContent = inStock\n ? (ctx.i18n?.inStockLabel ?? 'In Stock')\n : (ctx.i18n?.outOfStockLabel ?? 'Out of Stock');\n content.appendChild(stock);\n }\n\n // Promotion badges (e.g. \"Free Shipping\", \"Flash Sale\") — max 3\n const promotions = product['promotions'] as string[] | undefined;\n if (promotions && promotions.length > 0) {\n const promoBadges = document.createElement('div');\n promoBadges.className = 'gengage-chat-product-details-promos';\n for (const promo of promotions.slice(0, 3)) {\n if (!promo || /%(0(\\.0+)?)\\s/.test(promo)) continue; // skip zero-value badges\n const badge = document.createElement('span');\n badge.className = 'gengage-chat-product-details-promo-badge';\n badge.textContent = promo;\n badge.title = promo;\n promoBadges.appendChild(badge);\n }\n if (promoBadges.childElementCount > 0) content.appendChild(promoBadges);\n }\n\n if (featureEntries.length > 0) {\n const facts = document.createElement('dl');\n facts.className = 'gengage-chat-product-details-facts';\n for (const feature of featureEntries) {\n const item = document.createElement('div');\n item.className = 'gengage-chat-product-details-fact';\n const key = document.createElement('dt');\n key.textContent = feature.key;\n const val = document.createElement('dd');\n val.textContent = feature.value;\n item.appendChild(key);\n item.appendChild(val);\n facts.appendChild(item);\n }\n content.appendChild(facts);\n }\n\n // Variant selector\n const variants = productVariants(product);\n if (variants.length > 0) {\n const variantSection = document.createElement('div');\n variantSection.className = 'gengage-chat-product-variants';\n\n const variantHeading = document.createElement('div');\n variantHeading.className = 'gengage-chat-product-variants-label';\n variantHeading.textContent = variantSectionLabel(variants, ctx);\n variantSection.appendChild(variantHeading);\n\n const variantList = document.createElement('div');\n variantList.className = 'gengage-chat-product-variants-list';\n\n for (const variant of variants) {\n const variantName = variantDisplayLabel(variant);\n const variantSku = variantString(variant, 'sku');\n if (!variantName && !variantSku) continue;\n\n const btn = document.createElement('button');\n btn.className = 'gengage-chat-product-variant-btn gds-chip';\n btn.type = 'button';\n const labelText = variantName ?? variantSku ?? '';\n btn.title = labelText;\n const variantInStock = productBoolean(variant, 'in_stock', 'inStock');\n if (variantSku && sku && variantSku === sku) {\n btn.classList.add('gengage-chat-product-variant-btn--active');\n btn.setAttribute('aria-pressed', 'true');\n } else {\n btn.setAttribute('aria-pressed', 'false');\n }\n if (variantInStock === false) {\n btn.classList.add('gengage-chat-product-variant-btn--out');\n btn.disabled = true;\n }\n\n const swatchImage = variantImage(variant);\n const swatchColor = variantSwatchColor(variant);\n if (swatchImage && isSafeUrl(swatchImage)) {\n const swatch = document.createElement('img');\n swatch.className = 'gengage-chat-product-variant-swatch gengage-chat-product-variant-swatch--image';\n safeSetAttribute(swatch, 'src', swatchImage);\n swatch.alt = '';\n swatch.setAttribute('aria-hidden', 'true');\n addImageErrorHandler(swatch);\n btn.appendChild(swatch);\n } else if (swatchColor) {\n const swatch = document.createElement('span');\n swatch.className = 'gengage-chat-product-variant-swatch';\n swatch.setAttribute('aria-hidden', 'true');\n swatch.style.backgroundColor = swatchColor;\n btn.appendChild(swatch);\n }\n\n const label = document.createElement('span');\n label.className = 'gengage-chat-product-variant-label';\n label.textContent = labelText;\n btn.appendChild(label);\n\n const nextVariantPrice = variantPrice(variant);\n if (nextVariantPrice && String(nextVariantPrice) !== String(price)) {\n const priceEl = document.createElement('span');\n priceEl.className = 'gengage-chat-product-variant-price';\n priceEl.textContent = formatPrice(String(nextVariantPrice), ctx.pricing);\n btn.appendChild(priceEl);\n }\n\n if (variantSku && variantSku !== sku) {\n const productName = name ?? '';\n const variantHuman =\n (typeof variant['value'] === 'string' ? variant['value'].trim() : '') ||\n (typeof variant['name'] === 'string' ? variant['name'].trim() : '') ||\n (typeof variant['variant_name'] === 'string' ? variant['variant_name'].trim() : '') ||\n '';\n const launchTitle =\n productName.length > 0\n ? variantHuman.length > 0 && variantHuman !== productName\n ? `${productName} (${variantHuman})`\n : productName\n : labelText;\n\n btn.addEventListener('click', () => {\n ctx.onAction({\n title: launchTitle,\n type: 'launchVariant',\n payload: { sku: variantSku },\n });\n });\n }\n variantList.appendChild(btn);\n }\n\n if (variantList.childElementCount > 0) {\n variantSection.appendChild(variantList);\n content.appendChild(variantSection);\n }\n }\n\n const actionRow = document.createElement('div');\n actionRow.className = 'gengage-chat-product-details-actions';\n\n const action = element.props?.['action'] as ActionPayload | undefined;\n if (action) {\n const actionBtn = document.createElement('button');\n actionBtn.className = 'gengage-chat-product-details-cta gds-btn gds-btn-primary';\n actionBtn.type = 'button';\n actionBtn.textContent = action.title || ctx.i18n?.productCtaLabel || 'View';\n actionBtn.addEventListener('click', () => ctx.onAction(action));\n actionRow.appendChild(actionBtn);\n }\n\n // Add to Cart — direct add with quantity 1\n if (cartCode && sku && inStock !== false) {\n const addToCartBtn = document.createElement('button');\n addToCartBtn.className = 'gengage-chat-product-details-atc gds-btn gds-btn-primary';\n addToCartBtn.type = 'button';\n addToCartBtn.textContent = ctx.i18n?.addToCartButton ?? 'Add to Cart';\n addToCartBtn.addEventListener('click', () => {\n ctx.onAction({\n title: ctx.i18n?.addToCartButton ?? 'Add to Cart',\n type: 'addToCart',\n payload: { sku, cartCode, quantity: 1 },\n });\n });\n actionRow.appendChild(addToCartBtn);\n }\n\n const url = productString(product, 'url');\n if (!action && url && isSafeUrl(url)) {\n const cta = document.createElement('a');\n cta.className = 'gengage-chat-product-details-cta gds-btn gds-btn-secondary';\n safeSetAttribute(cta, 'href', url);\n safeSetAttribute(cta, 'target', '_blank');\n safeSetAttribute(cta, 'rel', 'noopener noreferrer');\n cta.textContent = ctx.i18n?.viewOnSiteLabel ?? ctx.i18n?.productCtaLabel ?? 'View on Site';\n cta.addEventListener('click', (e) => {\n if (ctx.onProductClick && sku) {\n e.preventDefault();\n ctx.onProductClick({ sku, url });\n }\n });\n actionRow.appendChild(cta);\n }\n\n // Share button — copies product URL or triggers native share\n const shareUrl = url;\n if (shareUrl && isSafeUrl(shareUrl)) {\n const shareBtn = document.createElement('button');\n shareBtn.className = 'gengage-chat-product-details-share gds-btn gds-btn-ghost gds-icon-btn';\n shareBtn.type = 'button';\n const shareLabel = ctx.i18n?.shareButton ?? 'Share';\n shareBtn.title = shareLabel;\n shareBtn.setAttribute('aria-label', shareLabel);\n const svgNS = 'http://www.w3.org/2000/svg';\n const svg = document.createElementNS(svgNS, 'svg');\n svg.setAttribute('width', '18');\n svg.setAttribute('height', '18');\n svg.setAttribute('viewBox', '0 0 24 24');\n svg.setAttribute('fill', 'none');\n svg.setAttribute('stroke', 'currentColor');\n svg.setAttribute('stroke-width', '2');\n svg.setAttribute('stroke-linecap', 'round');\n svg.setAttribute('stroke-linejoin', 'round');\n function addCircle(cx: string, cy: string): void {\n const c = document.createElementNS(svgNS, 'circle');\n c.setAttribute('cx', cx);\n c.setAttribute('cy', cy);\n c.setAttribute('r', '3');\n svg.appendChild(c);\n }\n function addLine(x1: string, y1: string, x2: string, y2: string): void {\n const l = document.createElementNS(svgNS, 'line');\n l.setAttribute('x1', x1);\n l.setAttribute('y1', y1);\n l.setAttribute('x2', x2);\n l.setAttribute('y2', y2);\n svg.appendChild(l);\n }\n addCircle('18', '5');\n addCircle('6', '12');\n addCircle('18', '19');\n addLine('8.59', '13.51', '15.42', '17.49');\n addLine('15.41', '6.51', '8.59', '10.49');\n shareBtn.appendChild(svg);\n shareBtn.addEventListener('click', async () => {\n try {\n if (navigator.share) {\n await navigator.share({ title: name ?? '', url: shareUrl });\n } else if (navigator.clipboard) {\n await navigator.clipboard.writeText(shareUrl);\n shareBtn.classList.add('gengage-chat-product-details-share--copied');\n setTimeout(() => shareBtn.classList.remove('gengage-chat-product-details-share--copied'), 1500);\n }\n } catch {\n // Share cancelled or clipboard write denied — ignore\n }\n });\n actionRow.appendChild(shareBtn);\n }\n\n if (actionRow.childElementCount > 0) {\n content.appendChild(actionRow);\n }\n\n panel.appendChild(content);\n\n // Product detail tabs: \"Product Info\" / \"Specifications\"\n const description = productDescription(product);\n const specifications = productSpecifications(product);\n if (description || specifications) {\n panel.appendChild(renderProductDetailTabs(description, specifications, ctx));\n }\n\n return panel;\n}\n\nfunction sanitizeProductDescriptionNode(node: Node): Node | null {\n if (node.nodeType === Node.TEXT_NODE) {\n return document.createTextNode(node.textContent ?? '');\n }\n\n if (node.nodeType !== Node.ELEMENT_NODE) return null;\n\n const element = node as Element;\n const tagName = element.tagName.toUpperCase();\n if (PRODUCT_DESCRIPTION_BLOCKED_TAGS.has(tagName)) return null;\n\n if (!PRODUCT_DESCRIPTION_ALLOWED_TAGS.has(tagName)) {\n const fragment = document.createDocumentFragment();\n for (const child of Array.from(element.childNodes)) {\n const sanitized = sanitizeProductDescriptionNode(child);\n if (sanitized) fragment.appendChild(sanitized);\n }\n return fragment;\n }\n\n const sanitizedElement = document.createElement(tagName.toLowerCase());\n for (const child of Array.from(element.childNodes)) {\n const sanitized = sanitizeProductDescriptionNode(child);\n if (sanitized) sanitizedElement.appendChild(sanitized);\n }\n return sanitizedElement;\n}\n\nfunction appendPlainProductDescription(target: HTMLElement, text: string): void {\n const paragraphs = text\n .split(/\\n{2,}/)\n .map((paragraph) => paragraph.replace(/\\s+/g, ' ').trim())\n .filter(Boolean);\n\n if (paragraphs.length === 0) return;\n\n for (const paragraph of paragraphs) {\n const p = document.createElement('p');\n p.textContent = paragraph;\n target.appendChild(p);\n }\n}\n\nfunction appendProductDescription(target: HTMLElement, description: ProductDescriptionContent): void {\n target.classList.add('gengage-chat-product-description');\n\n if (description.html && typeof DOMParser !== 'undefined') {\n const doc = new DOMParser().parseFromString(description.html, 'text/html');\n const nodes = Array.from(doc.body.childNodes)\n .map((node) => sanitizeProductDescriptionNode(node))\n .filter((node): node is Node => !!node && (node.nodeType === Node.ELEMENT_NODE || !!node.textContent?.trim()));\n\n if (nodes.length > 0) {\n for (const node of nodes) target.appendChild(node);\n return;\n }\n }\n\n appendPlainProductDescription(target, description.text);\n}\n\nfunction renderProductDetailTabs(\n description: ProductDescriptionContent | undefined,\n specifications: Record<string, string> | Array<{ key: string; value: string }> | undefined,\n ctx: UISpecRenderContext,\n): HTMLElement {\n const container = document.createElement('div');\n container.className = 'gengage-chat-product-detail-tabs';\n\n const tabBar = document.createElement('div');\n tabBar.className = 'gengage-chat-product-detail-tab-bar gds-toolbar';\n\n const tabPanels: HTMLElement[] = [];\n\n // Product Info tab\n if (description) {\n const tab = document.createElement('button');\n tab.className = 'gengage-chat-product-detail-tab gds-tab gengage-chat-product-detail-tab--active is-active';\n tab.type = 'button';\n tab.setAttribute('aria-selected', 'true');\n tab.textContent = ctx.i18n?.productInfoTab ?? 'Product Info';\n tabBar.appendChild(tab);\n\n const panel = document.createElement('div');\n panel.className = 'gengage-chat-product-detail-tab-panel';\n appendProductDescription(panel, description);\n tabPanels.push(panel);\n }\n\n // Specifications tab\n if (specifications) {\n const tab = document.createElement('button');\n tab.className = `gengage-chat-product-detail-tab gds-tab${!description ? ' gengage-chat-product-detail-tab--active is-active' : ''}`;\n tab.type = 'button';\n tab.setAttribute('aria-selected', description ? 'false' : 'true');\n tab.textContent = ctx.i18n?.specificationsTab ?? 'Specifications';\n tabBar.appendChild(tab);\n\n const panel = document.createElement('div');\n panel.className = 'gengage-chat-product-detail-tab-panel';\n if (description) {\n panel.style.display = 'none';\n }\n\n const table = document.createElement('table');\n table.className = 'gengage-chat-product-specs-table';\n const entries = Array.isArray(specifications)\n ? specifications\n : Object.entries(specifications).map(([key, value]) => ({ key, value }));\n for (const entry of entries) {\n const row = document.createElement('tr');\n const keyCell = document.createElement('td');\n keyCell.className = 'gengage-chat-product-specs-key';\n keyCell.textContent = entry.key;\n const valCell = document.createElement('td');\n valCell.className = 'gengage-chat-product-specs-value';\n valCell.textContent = entry.value;\n row.appendChild(keyCell);\n row.appendChild(valCell);\n table.appendChild(row);\n }\n panel.appendChild(table);\n tabPanels.push(panel);\n }\n\n // Wire up tab switching\n const tabs = tabBar.querySelectorAll('.gengage-chat-product-detail-tab');\n tabs.forEach((tabEl, idx) => {\n tabEl.addEventListener('click', () => {\n tabs.forEach((t) => {\n t.classList.remove('gengage-chat-product-detail-tab--active', 'is-active');\n t.setAttribute('aria-selected', 'false');\n });\n tabEl.classList.add('gengage-chat-product-detail-tab--active', 'is-active');\n tabEl.setAttribute('aria-selected', 'true');\n tabPanels.forEach((p, pIdx) => {\n p.style.display = pIdx === idx ? '' : 'none';\n });\n });\n });\n\n container.appendChild(tabBar);\n for (const p of tabPanels) container.appendChild(p);\n return container;\n}\n\n/** Lucide-style stroke icons (matches ChatDrawer / header SVGs). */\ntype ProductSortIconKind = 'related' | 'priceAsc' | 'priceDesc';\n\nfunction productSortIconSvgHtml(kind: ProductSortIconKind): string {\n const a =\n 'width=\"15\" height=\"15\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"';\n switch (kind) {\n case 'related':\n return `<svg ${a} aria-hidden=\"true\"><path d=\"M13 2 3 14h9l-1 8 10-12h-9l1-8z\"/></svg>`;\n case 'priceAsc':\n return `<svg ${a} aria-hidden=\"true\"><polyline points=\"22 7 13.5 15.5 8.5 10.5 2 17\"/><polyline points=\"16 7 22 7 22 13\"/></svg>`;\n case 'priceDesc':\n return `<svg ${a} aria-hidden=\"true\"><polyline points=\"22 17 13.5 8.5 8.5 13.5 2 7\"/><polyline points=\"16 17 22 17 22 11\"/></svg>`;\n default:\n return '';\n }\n}\n\nfunction productSortChevronSvgHtml(): string {\n return `<svg class=\"gengage-chat-product-sort-chevron\" width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><polyline points=\"6 9 12 15 18 9\"/></svg>`;\n}\n\nfunction productSortCheckSvgHtml(): string {\n return `<svg class=\"gengage-chat-product-sort-check\" width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><polyline points=\"20 6 9 17 4 12\"/></svg>`;\n}\n\n/** Left-right arrows (compare / swap), Lucide arrow-left-right style. */\nfunction comparisonToggleIconSvgHtml(): string {\n const a =\n 'width=\"15\" height=\"15\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"';\n return `<svg ${a} aria-hidden=\"true\"><path d=\"M8 3 4 7l4 4\"/><path d=\"M16 21l4-4-4-4\"/><path d=\"M4 7h16\"/><path d=\"M20 17H4\"/></svg>`;\n}\n\nfunction productSortStatesEqual(a: ProductSortState, b: ProductSortState): boolean {\n return a.type === b.type && a.direction === b.direction;\n}\n\nfunction getSortedChildIds(childIds: string[], spec: UISpec, sort?: ProductSortState): string[] {\n if (!sort || sort.type === 'related') return childIds;\n\n const withPrice = childIds.map((id) => {\n const el = spec.elements[id];\n const product = el?.props?.['product'] as Record<string, unknown> | undefined;\n const price = product ? Number(product['price']) : NaN;\n return { id, price: Number.isFinite(price) ? price : Infinity };\n });\n\n withPrice.sort((a, b) => {\n if (a.price === Infinity && b.price === Infinity) return 0;\n if (a.price === Infinity) return 1;\n if (b.price === Infinity) return -1;\n return sort.direction === 'desc' ? b.price - a.price : a.price - b.price;\n });\n\n return withPrice.map((x) => x.id);\n}\n\nfunction resortGrid(grid: HTMLElement, childIds: string[], spec: UISpec, sort: ProductSortState): void {\n const sorted = getSortedChildIds(childIds, spec, sort);\n // Build map from element ID data attribute to DOM element\n const childMap = new Map<string, HTMLElement>();\n for (const child of Array.from(grid.children) as HTMLElement[]) {\n const elId = child.dataset['elementId'];\n if (elId) childMap.set(elId, child);\n }\n\n for (const id of sorted) {\n const el = childMap.get(id);\n if (el) grid.appendChild(el);\n }\n}\n\nfunction renderProductGrid(\n element: UIElement,\n spec: UISpec,\n renderElement: (elementId: string) => HTMLElement | null,\n ctx?: UISpecRenderContext,\n): HTMLElement {\n const wrapper = document.createElement('div');\n wrapper.className = 'gengage-chat-product-grid-wrapper';\n\n const childIds = element.children ?? [];\n const grid = document.createElement('div');\n grid.className = 'gengage-chat-product-grid';\n\n const inlineHead = ctx?.panelProductListHeading;\n const hasSortToolbar = childIds.length > 1 && ctx?.onSortChange;\n\n // Sort + compare toolbar (only when >1 children and context has sort support)\n if (hasSortToolbar) {\n const toolbar = document.createElement('div');\n toolbar.className = 'gengage-chat-product-sort-toolbar';\n toolbar.setAttribute('role', 'toolbar');\n toolbar.setAttribute('aria-label', ctx.i18n?.sortToolbarAriaLabel ?? 'Sort products');\n\n let currentSort: ProductSortState = ctx.productSort ?? { type: 'related' };\n\n const sortOptions: Array<{ label: string; sortState: ProductSortState; icon: ProductSortIconKind }> = [\n { label: ctx.i18n?.sortRelated ?? 'Related', sortState: { type: 'related' }, icon: 'related' },\n {\n label: ctx.i18n?.sortPriceAsc ?? 'Price ↑',\n sortState: { type: 'price', direction: 'asc' },\n icon: 'priceAsc',\n },\n {\n label: ctx.i18n?.sortPriceDesc ?? 'Price ↓',\n sortState: { type: 'price', direction: 'desc' },\n icon: 'priceDesc',\n },\n ];\n\n const dropdown = document.createElement('div');\n dropdown.className = 'gengage-chat-product-sort-dropdown';\n\n const trigger = document.createElement('button');\n trigger.type = 'button';\n trigger.className = 'gengage-chat-product-sort-trigger gds-btn gds-btn-ghost';\n trigger.setAttribute('aria-haspopup', 'listbox');\n trigger.setAttribute('aria-expanded', 'false');\n const sortAria = ctx.i18n?.sortToolbarAriaLabel ?? 'Sort products';\n\n const triggerIcon = document.createElement('span');\n triggerIcon.className = 'gengage-chat-product-sort-trigger-icon';\n const triggerLabel = document.createElement('span');\n triggerLabel.className = 'gengage-chat-product-sort-trigger-label';\n\n const syncTriggerFromSort = (s: ProductSortState): void => {\n const opt = sortOptions.find((o) => productSortStatesEqual(o.sortState, s)) ?? sortOptions[0]!;\n triggerLabel.textContent = opt.label;\n triggerIcon.innerHTML = productSortIconSvgHtml(opt.icon);\n dropdown.dataset['sortIcon'] = opt.icon;\n trigger.setAttribute('aria-label', `${sortAria}: ${opt.label}`);\n trigger.title = opt.label;\n };\n syncTriggerFromSort(currentSort);\n\n const chevWrap = document.createElement('span');\n chevWrap.className = 'gengage-chat-product-sort-trigger-chevron';\n chevWrap.innerHTML = productSortChevronSvgHtml();\n\n trigger.appendChild(triggerIcon);\n trigger.appendChild(triggerLabel);\n trigger.appendChild(chevWrap);\n\n const menu = document.createElement('div');\n menu.className = 'gengage-chat-product-sort-menu gds-menu';\n menu.hidden = true;\n menu.setAttribute('role', 'listbox');\n menu.setAttribute('aria-label', sortAria);\n\n const doc = toolbar.ownerDocument;\n let menuOverlayAbort: AbortController | null = null;\n\n const closeSortMenu = (): void => {\n menu.hidden = true;\n dropdown.classList.remove('gengage-chat-product-sort-dropdown--open');\n trigger.setAttribute('aria-expanded', 'false');\n menuOverlayAbort?.abort();\n menuOverlayAbort = null;\n };\n\n /** Bubble-phase click avoids fighting trigger mousedown/pointerdown (menu kapanıp tekrar açılması). */\n const onSortMenuOutsideClick = (e: MouseEvent): void => {\n if (!dropdown.classList.contains('gengage-chat-product-sort-dropdown--open')) return;\n if (dropdown.contains(e.target as Node)) return;\n closeSortMenu();\n };\n\n const onSortMenuEscape = (e: KeyboardEvent): void => {\n if (e.key === 'Escape') {\n e.preventDefault();\n closeSortMenu();\n }\n };\n\n const openSortMenu = (): void => {\n menu.hidden = false;\n dropdown.classList.add('gengage-chat-product-sort-dropdown--open');\n trigger.setAttribute('aria-expanded', 'true');\n menuOverlayAbort = new AbortController();\n const { signal } = menuOverlayAbort;\n doc.addEventListener('click', onSortMenuOutsideClick, { signal });\n doc.addEventListener('keydown', onSortMenuEscape, { capture: true, signal });\n };\n\n trigger.addEventListener('click', (e) => {\n e.stopPropagation();\n if (dropdown.classList.contains('gengage-chat-product-sort-dropdown--open')) {\n closeSortMenu();\n } else {\n openSortMenu();\n }\n });\n\n for (const opt of sortOptions) {\n const optionBtn = document.createElement('button');\n optionBtn.type = 'button';\n optionBtn.className = 'gengage-chat-product-sort-option gds-menu-option';\n optionBtn.setAttribute('role', 'option');\n const isActive = productSortStatesEqual(currentSort, opt.sortState);\n optionBtn.setAttribute('aria-selected', isActive ? 'true' : 'false');\n if (isActive) optionBtn.classList.add('gengage-chat-product-sort-option--active', 'gds-menu-option-active');\n const sortKey = opt.sortState.type === 'related' ? 'related' : `price-${opt.sortState.direction ?? ''}`;\n optionBtn.dataset['sortKey'] = sortKey;\n\n const oIcon = document.createElement('span');\n oIcon.className = 'gengage-chat-product-sort-option-icon';\n oIcon.innerHTML = productSortIconSvgHtml(opt.icon);\n\n const oLabel = document.createElement('span');\n oLabel.className = 'gengage-chat-product-sort-option-label';\n oLabel.textContent = opt.label;\n\n const oCheck = document.createElement('span');\n oCheck.className = 'gengage-chat-product-sort-option-check';\n oCheck.innerHTML = productSortCheckSvgHtml();\n oCheck.setAttribute('aria-hidden', 'true');\n if (!isActive) oCheck.classList.add('gengage-chat-product-sort-option-check--hidden');\n\n optionBtn.appendChild(oIcon);\n optionBtn.appendChild(oLabel);\n optionBtn.appendChild(oCheck);\n\n optionBtn.addEventListener('click', () => {\n currentSort = opt.sortState;\n ctx.onSortChange?.(opt.sortState);\n resortGrid(grid, childIds, spec, opt.sortState);\n menu.querySelectorAll('.gengage-chat-product-sort-option').forEach((el) => {\n const btn = el as HTMLButtonElement;\n const active = btn.dataset['sortKey'] === sortKey;\n btn.classList.toggle('gengage-chat-product-sort-option--active', active);\n btn.classList.toggle('gds-menu-option-active', active);\n btn.setAttribute('aria-selected', active ? 'true' : 'false');\n const check = btn.querySelector('.gengage-chat-product-sort-option-check');\n check?.classList.toggle('gengage-chat-product-sort-option-check--hidden', !active);\n });\n syncTriggerFromSort(opt.sortState);\n closeSortMenu();\n });\n\n menu.appendChild(optionBtn);\n }\n\n dropdown.appendChild(trigger);\n dropdown.appendChild(menu);\n toolbar.appendChild(dropdown);\n\n if (ctx.onToggleComparisonSku) {\n const compareBtn = document.createElement('button');\n compareBtn.className = 'gengage-chat-comparison-toggle-btn gds-btn gds-btn-ghost';\n compareBtn.type = 'button';\n if (ctx.comparisonSelectMode) {\n compareBtn.classList.add('gengage-chat-comparison-toggle-btn--active');\n }\n if (ctx.isStreaming) {\n compareBtn.classList.add('gengage-chat-comparison-toggle-btn--hidden');\n }\n const compareIcon = document.createElement('span');\n compareIcon.className = 'gengage-chat-comparison-toggle-icon';\n compareIcon.innerHTML = comparisonToggleIconSvgHtml();\n const compareLabel = document.createElement('span');\n compareLabel.className = 'gengage-chat-comparison-toggle-label';\n const compareText = ctx.i18n?.compareSelected ?? 'Compare';\n compareLabel.textContent = compareText;\n compareBtn.setAttribute('aria-label', compareText);\n compareBtn.title = compareText;\n compareBtn.appendChild(compareIcon);\n compareBtn.appendChild(compareLabel);\n compareBtn.addEventListener('click', () => {\n ctx.onToggleComparisonSku?.('');\n });\n toolbar.appendChild(compareBtn);\n }\n\n if (inlineHead) {\n toolbar.classList.add('gengage-chat-product-sort-toolbar--inline');\n const head = document.createElement('div');\n head.className = 'gengage-chat-product-grid-head';\n const titleEl = document.createElement('span');\n titleEl.className = 'gengage-chat-product-grid-head-title';\n titleEl.textContent = inlineHead;\n head.appendChild(titleEl);\n const actions = document.createElement('div');\n actions.className = 'gengage-chat-product-grid-head-actions';\n actions.appendChild(toolbar);\n head.appendChild(actions);\n wrapper.appendChild(head);\n } else {\n wrapper.appendChild(toolbar);\n }\n } else if (inlineHead) {\n const head = document.createElement('div');\n head.className = 'gengage-chat-product-grid-head';\n const titleEl = document.createElement('span');\n titleEl.className = 'gengage-chat-product-grid-head-title';\n titleEl.textContent = inlineHead;\n head.appendChild(titleEl);\n wrapper.appendChild(head);\n }\n\n const sortedIds = getSortedChildIds(childIds, spec, ctx?.productSort);\n for (const childId of sortedIds) {\n if (!spec.elements[childId]) continue;\n const rendered = renderElement(childId);\n if (rendered) {\n rendered.dataset['elementId'] = childId;\n grid.appendChild(rendered);\n }\n }\n\n // Mobile variant: horizontal scroll\n if (ctx?.isMobile ?? isMobileViewport()) {\n grid.classList.add('gengage-chat-product-grid--mobile');\n }\n\n wrapper.appendChild(grid);\n\n // \"View More\" button (only when endOfList is not true)\n const endOfList = element.props?.['endOfList'] as boolean | undefined;\n if (endOfList !== true && childIds.length > 0) {\n const viewMoreTitle = ctx?.i18n?.viewMoreLabel ?? 'Show More';\n const viewMoreBtn = document.createElement('button');\n viewMoreBtn.className = 'gengage-chat-product-grid-view-more';\n viewMoreBtn.type = 'button';\n viewMoreBtn.textContent = viewMoreTitle;\n viewMoreBtn.addEventListener('click', () => {\n ctx?.onAction({ title: viewMoreTitle, type: 'moreProductList', payload: {} });\n });\n wrapper.appendChild(viewMoreBtn);\n }\n\n // Floating comparison dock — desktop: inside grid wrapper; mobile: index _refreshComparisonUI mounts into drawer slot\n const isMobileGrid = ctx?.isMobile ?? isMobileViewport();\n if (ctx?.comparisonSelectMode && ctx.comparisonSelectedSkus && !isMobileGrid) {\n const floatingBtn = renderFloatingComparisonButton(ctx.comparisonSelectedSkus, ctx);\n wrapper.appendChild(floatingBtn);\n }\n\n return wrapper;\n}\n\nfunction renderComparisonTableElement(element: UIElement, ctx: UISpecRenderContext): HTMLElement {\n const props = element.props ?? {};\n const keyDifferencesHtml = props['keyDifferencesHtml'] as string | undefined;\n const recommended = props['recommended'] as ComparisonProduct | undefined;\n const products = (props['products'] as ComparisonProduct[] | undefined) ?? [];\n const attributes = (props['attributes'] as ComparisonAttribute[] | undefined) ?? [];\n const highlights = (props['highlights'] as string[] | undefined) ?? [];\n const specialCases = props['specialCases'] as string[] | undefined;\n const recommendedText = props['recommendedText'] as string | undefined;\n const winnerHits = props['winnerHits'] as Record<string, { positive?: string[]; negative?: string[] }> | undefined;\n const productActions = props['productActions'] as\n | Record<string, { title: string; type: string; payload?: unknown }>\n | undefined;\n\n if (!recommended) {\n const fallback = document.createElement('div');\n return fallback;\n }\n\n const options: import('./ComparisonTable.js').ComparisonTableOptions = {\n recommended,\n products,\n attributes,\n highlights,\n specialCases,\n onProductClick: ({ sku, name }) => {\n ctx.onProductClick?.({ sku, url: '', name });\n },\n pricing: ctx.pricing,\n };\n if (recommendedText !== undefined) options.recommendedText = recommendedText;\n if (winnerHits !== undefined) options.winnerHits = winnerHits;\n if (productActions !== undefined) options.productActions = productActions;\n if (keyDifferencesHtml !== undefined) options.keyDifferencesHtml = keyDifferencesHtml;\n if (ctx.i18n) {\n options.i18n = {\n comparisonHeading: ctx.i18n.panelTitleComparisonResults,\n recommendedChoiceLabel: ctx.i18n.recommendedChoiceLabel,\n highlightsLabel: ctx.i18n.highlightsLabel,\n keyDifferencesLabel: ctx.i18n.keyDifferencesLabel,\n viewMoreLabel: ctx.i18n.viewMoreLabel,\n specialCasesLabel: ctx.i18n.specialCasesLabel,\n addToCartButton: ctx.i18n.addToCartButton,\n };\n }\n\n const el = renderComparisonTable(options);\n\n // Mobile variant\n if (ctx.isMobile ?? isMobileViewport()) {\n el.classList.add('gengage-chat-comparison--mobile');\n }\n\n return el;\n}\n\nfunction renderDivider(element: UIElement): HTMLElement {\n const hr = document.createElement('hr');\n hr.className = 'gengage-chat-divider';\n const label = element.props?.['label'] as string | undefined;\n if (label) {\n const wrapper = document.createElement('div');\n wrapper.className = 'gengage-chat-divider-wrapper';\n const labelEl = document.createElement('span');\n labelEl.className = 'gengage-chat-divider-label';\n labelEl.textContent = label;\n wrapper.appendChild(hr);\n wrapper.appendChild(labelEl);\n const hr2 = document.createElement('hr');\n hr2.className = 'gengage-chat-divider';\n wrapper.appendChild(hr2);\n return wrapper;\n }\n return hr;\n}\n","/**\n * Block-by-block HTML typewriter effect.\n *\n * Parses sanitised HTML into top-level blocks and reveals them one at a time\n * with a configurable stagger delay. Respects prefers-reduced-motion.\n */\n\nexport interface TypewriterOptions {\n container: HTMLElement;\n html: string;\n /** Delay in ms between block reveals (default: 30). */\n delayMs?: number;\n /** Called after each block is revealed — useful for scroll tracking. */\n onTick?: () => void;\n /** Called when all blocks have been revealed. */\n onComplete?: () => void;\n}\n\nexport interface TypewriterHandle {\n /** Skip animation and show all content immediately. */\n complete(): void;\n /** Cancel animation, leave content as-is. */\n cancel(): void;\n readonly isRunning: boolean;\n}\n\nconst BLOCK_ELEMENTS = new Set([\n 'P',\n 'DIV',\n 'H1',\n 'H2',\n 'H3',\n 'H4',\n 'H5',\n 'H6',\n 'LI',\n 'UL',\n 'OL',\n 'BLOCKQUOTE',\n 'PRE',\n 'TABLE',\n 'SECTION',\n 'HR',\n 'FIGURE',\n 'FIGCAPTION',\n 'DL',\n 'DT',\n 'DD',\n]);\n\n/**\n * Split parsed DOM children into logical blocks for reveal animation.\n * Block-level elements each become their own block.\n * Adjacent inline/text nodes are grouped together.\n */\nfunction splitIntoBlocks(nodes: NodeList): Node[][] {\n const blocks: Node[][] = [];\n let currentInline: Node[] = [];\n\n for (const node of nodes) {\n if (node.nodeType === Node.ELEMENT_NODE && BLOCK_ELEMENTS.has((node as Element).tagName)) {\n // Flush any pending inline group\n if (currentInline.length > 0) {\n blocks.push(currentInline);\n currentInline = [];\n }\n blocks.push([node]);\n } else {\n currentInline.push(node);\n }\n }\n\n if (currentInline.length > 0) {\n blocks.push(currentInline);\n }\n\n return blocks;\n}\n\nfunction containsTable(nodes: Node[][]): boolean {\n for (const block of nodes) {\n for (const node of block) {\n if (\n node.nodeType === Node.ELEMENT_NODE &&\n ((node as Element).tagName === 'TABLE' || (node as Element).querySelector?.('table'))\n ) {\n return true;\n }\n }\n }\n return false;\n}\n\nfunction prefersReducedMotion(): boolean {\n if (typeof window === 'undefined' || typeof window.matchMedia !== 'function') return false;\n return window.matchMedia('(prefers-reduced-motion: reduce)').matches;\n}\n\nexport function typewriteHtml(options: TypewriterOptions): TypewriterHandle {\n const { container, html, delayMs = 30, onTick, onComplete } = options;\n\n // Parse HTML into DOM nodes\n const template = document.createElement('template');\n template.innerHTML = html;\n const blocks = splitIntoBlocks(template.content.childNodes);\n\n // Skip animation for: reduced motion, single block, contains table, empty\n if (prefersReducedMotion() || blocks.length <= 1 || containsTable(blocks)) {\n container.innerHTML = html;\n onComplete?.();\n return { complete() {}, cancel() {}, isRunning: false };\n }\n\n // Clear container and start reveal\n container.innerHTML = '';\n let currentIndex = 0;\n let timerId: ReturnType<typeof setTimeout> | null = null;\n let running = true;\n\n function revealNext(): void {\n if (!running || currentIndex >= blocks.length) {\n running = false;\n onComplete?.();\n return;\n }\n\n const block = blocks[currentIndex]!;\n const wrapper = document.createElement('span');\n wrapper.className = 'gengage-chat-typewriter-block';\n for (const node of block) {\n wrapper.appendChild(node.cloneNode(true));\n }\n container.appendChild(wrapper);\n\n currentIndex++;\n onTick?.();\n\n if (currentIndex < blocks.length) {\n timerId = setTimeout(revealNext, delayMs);\n } else {\n running = false;\n onComplete?.();\n }\n }\n\n // Start the first reveal immediately\n revealNext();\n\n return {\n complete() {\n if (!running) return;\n if (timerId !== null) clearTimeout(timerId);\n running = false;\n container.innerHTML = html;\n onComplete?.();\n },\n cancel() {\n if (timerId !== null) clearTimeout(timerId);\n running = false;\n },\n get isRunning() {\n return running;\n },\n };\n}\n","/**\n * Product mention linker.\n *\n * After sanitiseHtml() renders bot text, this module walks text nodes and\n * wraps product name occurrences with clickable links that dispatch a\n * `launchSingleProduct` action.\n *\n * XSS safety: Uses DOM text-node manipulation only — no innerHTML.\n */\n\nexport interface ProductMention {\n sku: string;\n short_name: string;\n}\n\nexport interface ProductMentionLinkerOptions {\n container: HTMLElement;\n mentions: ProductMention[];\n onProductClick: (sku: string) => void;\n}\n\nfunction isWordChar(char: string | undefined): boolean {\n return char !== undefined && /[\\p{L}\\p{N}_]/u.test(char);\n}\n\n/**\n * Walk text nodes in `container` and wrap occurrences of each mention's\n * `short_name` with a clickable `<a>` element.\n *\n * Only the first occurrence of each mention is linked to avoid visual clutter.\n */\nexport function linkProductMentions(options: ProductMentionLinkerOptions): void {\n const { container, mentions, onProductClick } = options;\n if (mentions.length === 0) return;\n\n // Build a map of lowercase short_name → mention for case-insensitive matching\n const mentionMap = new Map<string, ProductMention>();\n for (const m of mentions) {\n if (m.short_name.length === 0) continue;\n mentionMap.set(m.short_name.toLowerCase(), m);\n }\n\n if (mentionMap.size === 0) return;\n\n // Process one mention at a time, re-walking the tree each time\n // (DOM mutations invalidate the walker)\n for (const [lowerName, mention] of mentionMap) {\n const walker = document.createTreeWalker(container, NodeFilter.SHOW_TEXT);\n let node = walker.nextNode();\n let found = false;\n\n while (node && !found) {\n const text = node.textContent ?? '';\n const idx = text.toLowerCase().indexOf(lowerName);\n if (idx === -1) {\n node = walker.nextNode();\n continue;\n }\n\n const prevChar = idx > 0 ? text[idx - 1] : undefined;\n const nextChar = text[idx + mention.short_name.length];\n if (isWordChar(prevChar) || isWordChar(nextChar)) {\n node = walker.nextNode();\n continue;\n }\n\n const before = text.slice(0, idx);\n const match = text.slice(idx, idx + mention.short_name.length);\n const after = text.slice(idx + mention.short_name.length);\n\n const parent = node.parentNode;\n if (!parent) {\n node = walker.nextNode();\n continue;\n }\n\n const link = document.createElement('a');\n link.className = 'gengage-product-mention';\n link.textContent = match;\n link.href = '#';\n link.addEventListener('click', (e) => {\n e.preventDefault();\n onProductClick(mention.sku);\n });\n\n if (before) parent.insertBefore(document.createTextNode(before), node);\n parent.insertBefore(link, node);\n if (after) parent.insertBefore(document.createTextNode(after), node);\n parent.removeChild(node);\n\n found = true;\n }\n }\n}\n","/**\n * Classifies suggested actions into input-area chips vs message-flow pills.\n *\n * Input-area chips: compact shortcuts above the input field (search, info, review, similar).\n * Message-flow pills: larger interactive cards in the suggestion row.\n */\n\nconst INPUT_AREA_ICONS = new Set(['search', 'info', 'review', 'similar']);\nconst INPUT_AREA_TYPES = new Set([\n 'quickAnswer',\n 'reviewSummary',\n 'searchDiscovery',\n 'launchDiscovery',\n 'exploreTogetherV2',\n]);\n\nexport function isInputAreaAction(btn: { icon?: string; action?: { type?: string } }): boolean {\n if (btn.icon && INPUT_AREA_ICONS.has(btn.icon)) return true;\n if (btn.action?.type && INPUT_AREA_TYPES.has(btn.action.type)) return true;\n return false;\n}\n","/**\n * Floating card that suggests the user try comparison mode.\n *\n * Shows in the panel bottom-right when a ProductGrid is displayed and\n * comparison mode is not active.\n */\n\nconst SESSION_STORAGE_KEY = 'gengage_choice_prompter_dismissed';\n/** Eski sürümler — clearChoicePrompterDismissState kaldırır. */\nconst LEGACY_GLOBAL_DISMISS_KEY = 'gengage_choice_prompter_dismissed_global';\n\nexport interface ChoicePrompterOptions {\n heading: string;\n suggestion: string;\n ctaLabel: string;\n threadId: string;\n onCtaClick: () => void;\n onDismiss?: () => void;\n dismissAriaLabel?: string;\n}\n\nexport function createChoicePrompter(options: ChoicePrompterOptions): HTMLElement {\n const card = document.createElement('div');\n card.className = 'gengage-chat-choice-prompter gds-card';\n card.dataset['gengagePart'] = 'choice-prompter';\n\n const copy = document.createElement('div');\n copy.className = 'gengage-chat-choice-prompter-copy';\n\n const headingEl = document.createElement('div');\n headingEl.className = 'gengage-chat-choice-prompter-heading';\n headingEl.textContent = options.heading;\n copy.appendChild(headingEl);\n\n const suggestionEl = document.createElement('div');\n suggestionEl.className = 'gengage-chat-choice-prompter-suggestion';\n suggestionEl.textContent = options.suggestion;\n copy.appendChild(suggestionEl);\n\n card.appendChild(copy);\n\n const cta = document.createElement('button');\n cta.type = 'button';\n cta.className = 'gengage-chat-choice-prompter-cta gds-btn gds-btn-primary';\n cta.dataset['gengagePart'] = 'choice-prompter-cta';\n cta.textContent = options.ctaLabel;\n cta.addEventListener('click', () => {\n markDismissed(options.threadId);\n card.remove();\n options.onCtaClick();\n });\n card.appendChild(cta);\n\n const dismiss = document.createElement('button');\n dismiss.type = 'button';\n dismiss.className = 'gengage-chat-choice-prompter-dismiss';\n dismiss.dataset['gengagePart'] = 'choice-prompter-dismiss';\n dismiss.textContent = '\\u00D7'; // × close\n dismiss.setAttribute('aria-label', options.dismissAriaLabel ?? 'Dismiss');\n dismiss.addEventListener('click', () => {\n markDismissed(options.threadId);\n card.remove();\n options.onDismiss?.();\n });\n card.appendChild(dismiss);\n\n return card;\n}\n\n/** Yeni kullanıcı araması başlarken çağrılır — kart tekrar gösterilebilir. */\nexport function clearChoicePrompterDismissState(): void {\n try {\n sessionStorage.removeItem(SESSION_STORAGE_KEY);\n sessionStorage.removeItem(LEGACY_GLOBAL_DISMISS_KEY);\n } catch {\n /* */\n }\n}\n\nexport function isChoicePrompterDismissed(threadId: string): boolean {\n try {\n const raw = sessionStorage.getItem(SESSION_STORAGE_KEY);\n if (!raw) return false;\n const dismissed: string[] = JSON.parse(raw);\n return dismissed.includes(threadId);\n } catch {\n return false;\n }\n}\n\nfunction markDismissed(threadId: string): void {\n try {\n const raw = sessionStorage.getItem(SESSION_STORAGE_KEY);\n const dismissed: string[] = raw ? (JSON.parse(raw) as string[]) : [];\n if (!dismissed.includes(threadId)) {\n dismissed.push(threadId);\n }\n sessionStorage.setItem(SESSION_STORAGE_KEY, JSON.stringify(dismissed));\n } catch {\n // sessionStorage unavailable — silently ignore\n }\n}\n\n/** Toolbar “Karşılaştır” gibi dış aksiyonlarda CTA ile aynı dismiss kaydı. */\nexport function recordChoicePrompterDismissedForThread(threadId: string): void {\n if (!threadId) return;\n markDismissed(threadId);\n}\n","/**\n * IndexedDB persistence for chat sessions, backend context, and message payloads.\n *\n * Three stores:\n * - `sessions` — full session state (messages, thread cursors, timestamps)\n * - `context` — per-thread backend context snapshots (compound key)\n * - `payload` — message UISpec payloads (lean message pattern)\n *\n * All operations are best-effort: IndexedDB unavailability is non-fatal.\n */\n\nimport type { SerializableChatMessage } from '../chat/types.js';\nimport type { UISpec } from './types.js';\n\n// ---------------------------------------------------------------------------\n// Data shapes stored in each object store\n// ---------------------------------------------------------------------------\n\nexport interface SessionData {\n userId: string;\n appId: string;\n sessionId: string;\n messages: SerializableChatMessage[];\n currentThreadId: string | null;\n lastThreadId: string | null;\n createdAt: string;\n /** Thread IDs that produced panel content, in creation order. */\n panelThreads?: string[] | undefined;\n /** Product thumbnails for ThumbnailsColumn. */\n thumbnailEntries?: Array<{ sku: string; imageUrl: string; threadId: string }> | undefined;\n /** Serialized panel HTML keyed by bot message ID (for panel snapshot restore). */\n panelSnapshotHtml?: Record<string, string> | undefined;\n /** SKU active when session was saved — prevents cross-SKU restore. */\n sku?: string | undefined;\n}\n\nexport interface ContextData {\n sessionId: string;\n threadId: string;\n context: import('./types.js').BackendContext;\n}\n\nexport interface PayloadData {\n threadId: string;\n messageId: string;\n uiSpec: UISpec;\n}\n\nexport interface FavoriteData {\n userId: string;\n appId: string;\n sku: string;\n name?: string | undefined;\n imageUrl?: string | undefined;\n price?: string | undefined;\n savedAt: string;\n}\n\n// ---------------------------------------------------------------------------\n// GengageIndexedDB\n// ---------------------------------------------------------------------------\n\nconst DB_NAME = 'gengage_assistant';\nconst DB_VERSION = 3;\n\nconst STORE_SESSIONS = 'sessions';\nconst STORE_CONTEXT = 'context';\nconst STORE_PAYLOAD = 'payload';\nconst STORE_FAVORITES = 'favorites';\n\n/**\n * Wrap an IDBRequest in a Promise.\n */\nfunction requestToPromise<T>(request: IDBRequest<T>): Promise<T> {\n return new Promise((resolve, reject) => {\n request.onsuccess = () => resolve(request.result);\n request.onerror = () => reject(request.error);\n });\n}\n\n/**\n * Wrap an IDBTransaction completion in a Promise.\n */\nfunction transactionComplete(tx: IDBTransaction): Promise<void> {\n return new Promise((resolve, reject) => {\n tx.oncomplete = () => resolve();\n tx.onerror = () => reject(tx.error);\n tx.onabort = () => reject(tx.error ?? new DOMException('Transaction aborted'));\n });\n}\n\nexport class GengageIndexedDB {\n private _db: IDBDatabase | null = null;\n private _dbName: string;\n private _version: number;\n\n constructor(dbName: string = DB_NAME, version: number = DB_VERSION) {\n this._dbName = dbName;\n this._version = version;\n }\n\n // -------------------------------------------------------------------------\n // Lifecycle\n // -------------------------------------------------------------------------\n\n async open(): Promise<IDBDatabase> {\n if (this._db) return this._db;\n\n return new Promise((resolve, reject) => {\n const request = indexedDB.open(this._dbName, this._version);\n\n request.onupgradeneeded = (event) => {\n const db = request.result;\n const oldVersion = (event as IDBVersionChangeEvent).oldVersion;\n\n if (oldVersion < 1) {\n // Fresh install — create stores with v3 schema directly\n db.createObjectStore(STORE_SESSIONS, { keyPath: ['userId', 'appId', 'sessionId'] });\n db.createObjectStore(STORE_CONTEXT, { keyPath: ['sessionId', 'threadId'] });\n const payloadStore = db.createObjectStore(STORE_PAYLOAD, {\n keyPath: ['threadId', 'messageId'],\n });\n payloadStore.createIndex('threadId', 'threadId', { unique: false });\n db.createObjectStore(STORE_FAVORITES, { keyPath: ['userId', 'appId', 'sku'] });\n }\n\n if (oldVersion >= 1 && oldVersion < 2) {\n // Upgrade from v1 — drop old stores, recreate with new keys\n if (db.objectStoreNames.contains(STORE_SESSIONS)) db.deleteObjectStore(STORE_SESSIONS);\n if (db.objectStoreNames.contains(STORE_PAYLOAD)) db.deleteObjectStore(STORE_PAYLOAD);\n db.createObjectStore(STORE_SESSIONS, { keyPath: ['userId', 'appId', 'sessionId'] });\n const payloadStore = db.createObjectStore(STORE_PAYLOAD, {\n keyPath: ['threadId', 'messageId'],\n });\n payloadStore.createIndex('threadId', 'threadId', { unique: false });\n // Ensure context store exists (may be absent in early v1 schemas)\n if (!db.objectStoreNames.contains(STORE_CONTEXT)) {\n db.createObjectStore(STORE_CONTEXT, { keyPath: ['sessionId', 'threadId'] });\n }\n }\n\n if (oldVersion < 3) {\n if (!db.objectStoreNames.contains(STORE_FAVORITES)) {\n db.createObjectStore(STORE_FAVORITES, { keyPath: ['userId', 'appId', 'sku'] });\n }\n }\n };\n\n request.onsuccess = () => {\n this._db = request.result;\n resolve(this._db);\n };\n\n request.onerror = () => reject(request.error);\n });\n }\n\n close(): void {\n this._db?.close();\n this._db = null;\n }\n\n // -------------------------------------------------------------------------\n // Sessions\n // -------------------------------------------------------------------------\n\n async saveSession(data: SessionData): Promise<void> {\n const db = this._requireDb();\n const tx = db.transaction(STORE_SESSIONS, 'readwrite');\n tx.objectStore(STORE_SESSIONS).put(data);\n await transactionComplete(tx);\n }\n\n async loadSession(userId: string, appId: string, sessionId: string): Promise<SessionData | null> {\n const db = this._requireDb();\n const tx = db.transaction(STORE_SESSIONS, 'readonly');\n const result = await requestToPromise(tx.objectStore(STORE_SESSIONS).get([userId, appId, sessionId]));\n return (result as SessionData | undefined) ?? null;\n }\n\n // -------------------------------------------------------------------------\n // Context (compound key: [sessionId, threadId])\n // -------------------------------------------------------------------------\n\n async saveContext(data: ContextData): Promise<void> {\n const db = this._requireDb();\n const tx = db.transaction(STORE_CONTEXT, 'readwrite');\n tx.objectStore(STORE_CONTEXT).put(data);\n await transactionComplete(tx);\n }\n\n async loadContext(sessionId: string, threadId: string): Promise<ContextData | null> {\n const db = this._requireDb();\n const tx = db.transaction(STORE_CONTEXT, 'readonly');\n const result = await requestToPromise(tx.objectStore(STORE_CONTEXT).get([sessionId, threadId]));\n return (result as ContextData | undefined) ?? null;\n }\n\n /**\n * Delete all context entries for a session whose threadId is lexicographically\n * greater than the given threadId. Used during rollback to prune future branches.\n *\n * Thread IDs are UUIDv7 (lexicographically sortable by time), so string\n * comparison is sufficient.\n */\n async deleteContextsAfterThread(sessionId: string, threadId: string): Promise<void> {\n const db = this._requireDb();\n const tx = db.transaction(STORE_CONTEXT, 'readwrite');\n const store = tx.objectStore(STORE_CONTEXT);\n\n // Open cursor over all entries and filter by sessionId + threadId comparison\n const request = store.openCursor();\n\n await new Promise<void>((resolve, reject) => {\n request.onsuccess = () => {\n const cursor = request.result;\n if (!cursor) {\n resolve();\n return;\n }\n const entry = cursor.value as ContextData;\n if (entry.sessionId === sessionId && entry.threadId > threadId) {\n try {\n cursor.delete();\n } catch {\n // cursor.delete() may fail on read-only or version-change transactions — non-fatal\n }\n }\n cursor.continue();\n };\n request.onerror = () => reject(request.error);\n });\n\n await transactionComplete(tx);\n }\n\n /**\n * Load the most recent context for a session (latest threadId).\n * Uses lexicographic ordering of UUIDv7 threadIds for chronological sort.\n */\n async loadLatestContext(sessionId: string): Promise<ContextData | null> {\n const db = this._requireDb();\n const tx = db.transaction(STORE_CONTEXT, 'readonly');\n const store = tx.objectStore(STORE_CONTEXT);\n\n // UUIDv7 thread IDs are ASCII hex+hyphens, so \\uffff is a safe upper bound\n const range = IDBKeyRange.bound([sessionId, ''], [sessionId, '\\uffff']);\n\n return new Promise((resolve, reject) => {\n const request = store.openCursor(range, 'prev');\n request.onsuccess = () => {\n const cursor = request.result;\n resolve(cursor ? (cursor.value as ContextData) : null);\n };\n request.onerror = () => reject(request.error);\n });\n }\n\n // -------------------------------------------------------------------------\n // Payload\n // -------------------------------------------------------------------------\n\n async savePayload(data: PayloadData): Promise<void> {\n const db = this._requireDb();\n const tx = db.transaction(STORE_PAYLOAD, 'readwrite');\n tx.objectStore(STORE_PAYLOAD).put(data);\n await transactionComplete(tx);\n }\n\n async loadPayload(threadId: string, messageId: string): Promise<PayloadData | null> {\n const db = this._requireDb();\n const tx = db.transaction(STORE_PAYLOAD, 'readonly');\n const result = await requestToPromise(tx.objectStore(STORE_PAYLOAD).get([threadId, messageId]));\n return (result as PayloadData | undefined) ?? null;\n }\n\n async loadPayloadsByThread(threadId: string): Promise<PayloadData[]> {\n const db = this._requireDb();\n const tx = db.transaction(STORE_PAYLOAD, 'readonly');\n const index = tx.objectStore(STORE_PAYLOAD).index('threadId');\n const results: PayloadData[] = [];\n\n return new Promise((resolve, reject) => {\n const request = index.openCursor(IDBKeyRange.only(threadId));\n request.onsuccess = () => {\n const cursor = request.result;\n if (!cursor) {\n resolve(results);\n return;\n }\n results.push(cursor.value as PayloadData);\n cursor.continue();\n };\n request.onerror = () => reject(request.error);\n });\n }\n\n // -------------------------------------------------------------------------\n // Favorites\n // -------------------------------------------------------------------------\n\n async saveFavorite(data: FavoriteData): Promise<void> {\n const db = this._requireDb();\n const tx = db.transaction(STORE_FAVORITES, 'readwrite');\n tx.objectStore(STORE_FAVORITES).put(data);\n await transactionComplete(tx);\n }\n\n async removeFavorite(userId: string, appId: string, sku: string): Promise<void> {\n const db = this._requireDb();\n const tx = db.transaction(STORE_FAVORITES, 'readwrite');\n tx.objectStore(STORE_FAVORITES).delete([userId, appId, sku]);\n await transactionComplete(tx);\n }\n\n async loadFavorites(userId: string, appId: string): Promise<FavoriteData[]> {\n const db = this._requireDb();\n const tx = db.transaction(STORE_FAVORITES, 'readonly');\n const all = await requestToPromise(tx.objectStore(STORE_FAVORITES).getAll());\n return (all as FavoriteData[]).filter((f) => f.userId === userId && f.appId === appId);\n }\n\n async isFavorite(userId: string, appId: string, sku: string): Promise<boolean> {\n const db = this._requireDb();\n const tx = db.transaction(STORE_FAVORITES, 'readonly');\n const result = await requestToPromise(tx.objectStore(STORE_FAVORITES).get([userId, appId, sku]));\n return result !== undefined;\n }\n\n // -------------------------------------------------------------------------\n // Internal\n // -------------------------------------------------------------------------\n\n private _requireDb(): IDBDatabase {\n if (!this._db) {\n throw new Error('GengageIndexedDB: database not open. Call open() first.');\n }\n return this._db;\n }\n}\n","/**\n * Extended Mode Manager — production lock-count system for panel extension.\n *\n * Controls whether the host page PDP area should be maximized or minimized\n * based on a combination of conditions: lock count, user visibility preference,\n * favorites mode, panel content type, and chat visibility.\n */\n\nexport type PanelContentType =\n | 'comparisonTable'\n | 'groupList'\n | 'productDetails'\n | 'productDetailsSimilars'\n | 'productList';\n\n/**\n * Base content types that always trigger panel extension.\n * `productDetails` is only added when `productDetailsInPanel` is true\n * (i.e. demo websites). Regular accounts show product details inline in ChatPane.\n */\nconst BASE_PANEL_CONTENT_TYPES: readonly PanelContentType[] = [\n 'comparisonTable',\n 'groupList',\n 'productDetailsSimilars',\n 'productList',\n];\n\nexport interface ExtendedModeManagerOptions {\n onChange: (extended: boolean) => void;\n /** Whether `productDetails` should trigger panel extension (default: false, true for demo sites). */\n productDetailsInPanel?: boolean;\n}\n\nexport class ExtendedModeManager {\n private _lockCount = 1; // starts locked\n private _hiddenByUser = false;\n private _lastPanelContentType: PanelContentType | null = null;\n private _chatShown = false;\n private _isFavoritesMode = false;\n private _lastExtended = false;\n private _onChange: (extended: boolean) => void;\n private _panelContentTypes: ReadonlySet<string>;\n\n constructor(options: ExtendedModeManagerOptions) {\n this._onChange = options.onChange;\n\n const types = new Set<string>(BASE_PANEL_CONTENT_TYPES);\n if (options.productDetailsInPanel) {\n types.add('productDetails');\n }\n this._panelContentTypes = types;\n }\n\n get isExtended(): boolean {\n return (\n this._lockCount === 0 &&\n !this._hiddenByUser &&\n !this._isFavoritesMode &&\n this._lastPanelContentType !== null &&\n this._panelContentTypes.has(this._lastPanelContentType) &&\n this._chatShown\n );\n }\n\n unlock(): void {\n if (this._lockCount > 0) {\n this._lockCount--;\n }\n this._checkStateChange();\n }\n\n lock(): void {\n this._lockCount++;\n this._checkStateChange();\n }\n\n setHiddenByUser(hidden: boolean): void {\n this._hiddenByUser = hidden;\n this._checkStateChange();\n }\n\n setChatShown(shown: boolean): void {\n this._chatShown = shown;\n this._checkStateChange();\n }\n\n setFavoritesMode(fav: boolean): void {\n this._isFavoritesMode = fav;\n this._checkStateChange();\n }\n\n setPanelContentType(type: PanelContentType | null): void {\n this._lastPanelContentType = type;\n this._checkStateChange();\n }\n\n private _checkStateChange(): void {\n const current = this.isExtended;\n if (current !== this._lastExtended) {\n this._lastExtended = current;\n this._onChange(current);\n }\n }\n}\n","/**\n * PanelManager — manages the two-pane panel state for the chat widget.\n *\n * Owns panel snapshots, snapshot types, active message highlighting,\n * panel thread navigation, topbar updates, and extended mode coordination.\n *\n * Extracted from chat/index.ts to improve cohesion and reduce file size.\n */\n\nimport type { ChatDrawer } from './components/ChatDrawer.js';\nimport type { CommunicationBridge } from '../common/communication-bridge.js';\nimport type { ExtendedModeManager, PanelContentType } from './extendedModeManager.js';\nimport type { ChatI18n } from './types.js';\nimport type { UISpec, UIElement } from '../common/types.js';\n\n/** Minimal interface the panel manager needs from its host widget. */\nexport interface PanelManagerDeps {\n drawer: () => ChatDrawer | null;\n shadow: () => ShadowRoot | null;\n currentThreadId: () => string | null;\n bridge: () => CommunicationBridge | null;\n extendedModeManager: () => ExtendedModeManager | null;\n i18n: () => ChatI18n;\n rollbackToThread: (threadId: string) => void;\n}\n\nexport class PanelManager {\n /** Panel content snapshots keyed by bot message ID for history navigation. */\n readonly snapshots = new Map<string, HTMLElement>();\n /** Rebuild functions keyed by message ID — produce fresh DOM with live event listeners. */\n private readonly _snapshotRebuilders = new Map<string, () => HTMLElement>();\n /** Component type for each panel snapshot (for topbar title restoration). */\n readonly snapshotTypes = new Map<string, string>();\n /** Currently active (highlighted) message ID in the chat pane. */\n activePanelMessageId: string | null = null;\n /** Current panel component type. */\n currentType: string | null = null;\n /** Thread IDs that have panel content, in order of creation. */\n threads: string[] = [];\n /** Action type that triggered the current stream (for panel title disambiguation). */\n lastActionType: string | null = null;\n\n constructor(private readonly deps: PanelManagerDeps) {}\n\n /**\n * Snapshot the current panel state for a message. Stores a rebuild function\n * (preferred — produces fresh DOM with live event listeners) alongside a\n * static DOM clone as fallback.\n */\n snapshotForMessage(messageId: string, rebuild?: (() => HTMLElement) | undefined): void {\n const drawer = this.deps.drawer();\n if (!drawer?.hasPanelContent()) return;\n // Never snapshot loading skeleton — it must not be persisted or restored\n if (drawer.isPanelLoading()) return;\n const contentEl = drawer.getPanelContentElement();\n if (!contentEl) return;\n const clone = contentEl.cloneNode(true) as HTMLElement;\n this.snapshots.set(messageId, clone);\n if (rebuild) this._snapshotRebuilders.set(messageId, rebuild);\n // Store the component type so topbar can be restored with the right title\n if (this.currentType) {\n this.snapshotTypes.set(messageId, this.currentType);\n }\n }\n\n /**\n * Attach a click handler to a bot message bubble so clicking it restores\n * the panel content that was active when that message was received.\n */\n attachClickHandler(messageId: string): void {\n const shadow = this.deps.shadow();\n const bubble = shadow?.querySelector(`[data-message-id=\"${CSS.escape(messageId)}\"]`);\n if (!bubble) return;\n (bubble as HTMLElement).classList.add('gds-clickable');\n bubble.addEventListener('click', () => this.restoreForMessage(messageId));\n }\n\n /**\n * Restore the panel content snapshot associated with a given message ID.\n * Highlights the active message and de-highlights the previous one.\n * Also restores the panel topbar title for the snapshot's component type.\n * Returns true if the snapshot was found and restored.\n */\n restoreForMessage(messageId: string): boolean {\n // Prefer rebuild function (live event listeners) over static DOM clone\n const rebuild = this._snapshotRebuilders.get(messageId);\n const snapshot = this.snapshots.get(messageId);\n if (!rebuild && !snapshot) return false;\n\n const shadow = this.deps.shadow();\n const drawer = this.deps.drawer();\n\n // De-highlight previous active message\n if (this.activePanelMessageId) {\n const prev = shadow?.querySelector(`[data-message-id=\"${CSS.escape(this.activePanelMessageId)}\"]`);\n prev?.classList.remove('gengage-chat-bubble--active');\n }\n\n // Highlight the clicked message\n const current = shadow?.querySelector(`[data-message-id=\"${CSS.escape(messageId)}\"]`);\n current?.classList.add('gengage-chat-bubble--active');\n this.activePanelMessageId = messageId;\n\n // Restore panel content — prefer rebuild for live interactions, fallback to clone\n const el = rebuild ? rebuild() : (snapshot!.cloneNode(true) as HTMLElement);\n drawer?.setPanelContent(el);\n\n // Restore component type and topbar\n const snapshotType = this.snapshotTypes.get(messageId);\n drawer?.setDividerPreviewEnabled(snapshotType === 'ProductGrid');\n if (snapshotType) {\n this.currentType = snapshotType;\n const inlineGridHead = el.querySelector('.gengage-chat-product-grid-head-title');\n if (inlineGridHead && snapshotType === 'ProductGrid') {\n const currentThreadId = this.deps.currentThreadId();\n if (currentThreadId) {\n const idx = this.threads.indexOf(currentThreadId);\n const canBack = idx > 0;\n const canForward = idx >= 0 && idx < this.threads.length - 1;\n this.deps.drawer()?.updatePanelTopBar(canBack, canForward, '');\n }\n } else {\n this.updateTopBar(snapshotType);\n }\n }\n return true;\n }\n\n /**\n * Send maximize-pdp / minify-pdp bridge messages with production-matching delays.\n * Called by the extended mode manager when panel extension state changes.\n */\n notifyExtension(extended: boolean): void {\n const bridge = this.deps.bridge();\n if (extended) {\n setTimeout(() => bridge?.send('maximize-pdp', {}), 350);\n } else {\n setTimeout(() => bridge?.send('minify-pdp', {}), 200);\n }\n }\n\n /**\n * Derive panel title from UISpec root element type using i18n strings.\n * When the backend provides a `panelTitle` prop, it takes precedence.\n */\n titleForComponent(componentType: string, backendTitle?: string): string {\n if (backendTitle) return backendTitle;\n const i18n = this.deps.i18n();\n switch (componentType) {\n case 'ProductDetailsPanel':\n return i18n.panelTitleProductDetails;\n case 'ProductGrid':\n // User-typed queries produce search results; product actions produce similar products\n return isSearchLikeActionType(this.lastActionType)\n ? i18n.panelTitleSearchResults\n : i18n.panelTitleSimilarProducts;\n case 'ComparisonTable':\n return i18n.panelTitleComparisonResults;\n case 'AIGroupingCards':\n return i18n.panelTitleCategories;\n default:\n return '';\n }\n }\n\n /**\n * Update the panel top bar navigation state and title.\n * When the backend provides a `panelTitle`, it takes precedence over i18n defaults.\n */\n /** @param displayTitle When passed (including empty string), used instead of titleForComponent. */\n updateTopBar(componentType: string, backendTitle?: string, displayTitle?: string): void {\n const currentThreadId = this.deps.currentThreadId();\n if (!currentThreadId) return;\n const idx = this.threads.indexOf(currentThreadId);\n const canBack = idx > 0;\n const canForward = idx >= 0 && idx < this.threads.length - 1;\n const title = displayTitle !== undefined ? displayTitle : this.titleForComponent(componentType, backendTitle);\n this.deps.drawer()?.updatePanelTopBar(canBack, canForward, title);\n }\n\n /**\n * Set panel topbar title during loading (before actual panel content arrives).\n * Maps backend pending types to the same i18n titles used for final content.\n */\n updateTopBarForLoading(pendingType: string): void {\n const i18n = this.deps.i18n();\n const loadingTitleMap: Record<string, string> = {\n productDetails: i18n.panelTitleProductDetails,\n productList: isSearchLikeActionType(this.lastActionType)\n ? i18n.panelTitleSearchResults\n : i18n.panelTitleSimilarProducts,\n comparisonTable: i18n.panelTitleComparisonResults,\n groupList: i18n.panelTitleCategories,\n };\n const title = loadingTitleMap[pendingType] ?? '';\n if (title) {\n const currentThreadId = this.deps.currentThreadId();\n const idx = currentThreadId ? this.threads.indexOf(currentThreadId) : -1;\n const canBack = idx > 0;\n const canForward = idx >= 0 && idx < this.threads.length - 1;\n this.deps.drawer()?.updatePanelTopBar(canBack, canForward, title);\n }\n }\n\n /**\n * Map UISpec component types to PanelContentType for the extended mode manager.\n */\n updateExtendedMode(componentType: string): void {\n const mapping: Record<string, PanelContentType> = {\n ComparisonTable: 'comparisonTable',\n AIGroupingCards: 'groupList',\n ProductDetailsPanel: 'productDetails',\n ProductGrid: 'productList',\n };\n const panelType = mapping[componentType] ?? null;\n this.deps.extendedModeManager()?.setPanelContentType(panelType);\n }\n\n /** Navigate to the previous panel thread. */\n navigateBack(): void {\n const currentThreadId = this.deps.currentThreadId();\n if (!currentThreadId) return;\n const idx = this.threads.indexOf(currentThreadId);\n if (idx > 0) {\n const target = this.threads[idx - 1];\n if (target) this.deps.rollbackToThread(target);\n }\n }\n\n /** Navigate to the next panel thread. */\n navigateForward(): void {\n const currentThreadId = this.deps.currentThreadId();\n if (!currentThreadId) return;\n const idx = this.threads.indexOf(currentThreadId);\n if (idx >= 0 && idx < this.threads.length - 1) {\n const target = this.threads[idx + 1];\n if (target) this.deps.rollbackToThread(target);\n }\n }\n\n /**\n * Panel route shaping:\n * - product details => expanded LHS panel (`ProductDetailsPanel`)\n * - all other panel-routed specs keep their original component types\n */\n toPanelSpec(spec: UISpec): UISpec {\n const root = spec.elements[spec.root];\n if (!root || root.type !== 'ProductCard') return spec;\n\n const panelRoot: UIElement = {\n ...root,\n type: 'ProductDetailsPanel',\n };\n\n return {\n root: spec.root,\n elements: {\n ...spec.elements,\n [spec.root]: panelRoot,\n },\n };\n }\n\n destroy(): void {\n this.snapshots.clear();\n this._snapshotRebuilders.clear();\n this.snapshotTypes.clear();\n this.activePanelMessageId = null;\n this.currentType = null;\n this.threads = [];\n }\n}\n\nfunction isSearchLikeActionType(actionType: string | null): boolean {\n return actionType === 'user_message' || actionType === 'inputText';\n}\n\n/**\n * Decide how to apply incoming panel UISpec content.\n *\n * - `'replace'` — set as sole panel content (new search, comparison, product details)\n * - `'append'` — add below existing content (similar products grid after product details)\n * - `'appendSimilars'` — append via the dedicated similars helper\n */\nexport type PanelUpdateAction = 'replace' | 'append' | 'appendSimilars';\n\nexport function determinePanelUpdateAction(opts: {\n componentType: string;\n similarsAppend: boolean;\n currentPanelType: string | null;\n hasPanelContent: boolean;\n isPanelLoading: boolean;\n isFirstPanelContentInStream: boolean;\n}): PanelUpdateAction {\n // productDetailsSimilars: append to existing ProductDetailsPanel\n if (\n opts.similarsAppend &&\n opts.currentPanelType === 'ProductDetailsPanel' &&\n opts.hasPanelContent &&\n !opts.isPanelLoading\n ) {\n return 'appendSimilars';\n }\n\n // Append ProductGrid only when it follows other panel content in the SAME stream\n // (e.g. similar products below product details). A ProductGrid that is the first\n // panel content in a new stream (search results) must REPLACE.\n if (\n opts.componentType === 'ProductGrid' &&\n !opts.isFirstPanelContentInStream &&\n opts.hasPanelContent &&\n !opts.isPanelLoading\n ) {\n return 'append';\n }\n\n return 'replace';\n}\n","/**\n * SessionPersistence — IndexedDB persistence helpers for the chat widget.\n *\n * Encapsulates session save/load, payload caching, favorite toggling,\n * and URL navigation with pre-save.\n *\n * Extracted from chat/index.ts to improve cohesion and reduce file size.\n */\n\nimport type { GengageIndexedDB, FavoriteData } from '../common/indexed-db.js';\nimport type { CommunicationBridge } from '../common/communication-bridge.js';\nimport { isSafeUrl } from '../common/safe-html.js';\nimport type { BackendContext, UISpec } from '../common/types.js';\nimport type { ChatMessage, SerializableChatMessage } from './types.js';\nimport type { ThumbnailEntry } from './components/ThumbnailsColumn.js';\n\nexport type { FavoriteData };\n\nexport interface PersistSessionParams {\n userId: string;\n appId: string;\n sessionId: string;\n messages: ChatMessage[];\n currentThreadId: string | null;\n lastThreadId: string | null;\n chatCreatedAt: string;\n panelSnapshots: Map<string, HTMLElement>;\n panelThreads: string[];\n thumbnailEntries: ThumbnailEntry[];\n lastBackendContext: BackendContext | null;\n sku?: string | undefined;\n}\n\nexport class SessionPersistence {\n private _db: GengageIndexedDB | null;\n /** Favorited product SKUs (loaded from IDB). */\n readonly favoritedSkus: Set<string> = new Set();\n /** Full favorite data cache (loaded from IDB alongside favoritedSkus). */\n private _favoritesCache: Map<string, FavoriteData> = new Map();\n /** Async mutex: serializes persist() calls to prevent interleaved IDB writes. */\n private _persistLock: Promise<void> = Promise.resolve();\n\n constructor(db: GengageIndexedDB | null) {\n this._db = db;\n }\n\n get db(): GengageIndexedDB | null {\n return this._db;\n }\n\n set db(value: GengageIndexedDB | null) {\n this._db = value;\n }\n\n /**\n * Persist current session state to IndexedDB.\n * Called after each stream completion (onDone). Non-fatal on failure.\n */\n async persist(params: PersistSessionParams): Promise<void> {\n if (!this._db) return;\n // Serialize through mutex to prevent interleaved IDB writes\n const prev = this._persistLock;\n let unlock: () => void;\n this._persistLock = new Promise<void>((r) => {\n unlock = r;\n });\n await prev;\n try {\n await this._persistImpl(params);\n } finally {\n unlock!();\n }\n }\n\n private async _persistImpl(params: PersistSessionParams): Promise<void> {\n if (!this._db) return;\n\n const serializableMessages: SerializableChatMessage[] = params.messages.map((m) => {\n const sm: SerializableChatMessage = {\n id: m.id,\n role: m.role,\n timestamp: m.timestamp,\n status: m.status === 'streaming' ? 'done' : m.status,\n };\n if (m.threadId !== undefined) sm.threadId = m.threadId;\n if (m.content !== undefined) sm.content = m.content;\n if (m.silent) sm.silent = true;\n return sm;\n });\n\n // Serialize panel snapshots to HTML strings (never persist loading skeletons)\n const panelSnapshotHtml: Record<string, string> = {};\n for (const [msgId, el] of params.panelSnapshots) {\n if (el.querySelector('.gengage-chat-panel-skeleton')) continue;\n panelSnapshotHtml[msgId] = el.innerHTML;\n }\n\n await this._db.saveSession({\n userId: params.userId,\n appId: params.appId,\n sessionId: params.sessionId,\n messages: serializableMessages,\n currentThreadId: params.currentThreadId,\n lastThreadId: params.lastThreadId,\n createdAt: params.chatCreatedAt,\n panelThreads: params.panelThreads.length > 0 ? params.panelThreads : undefined,\n thumbnailEntries: params.thumbnailEntries.length > 0 ? params.thumbnailEntries : undefined,\n panelSnapshotHtml: Object.keys(panelSnapshotHtml).length > 0 ? panelSnapshotHtml : undefined,\n sku: params.sku,\n });\n\n // Save latest context snapshot for current thread\n if (params.lastBackendContext && params.currentThreadId) {\n await this._db.saveContext({\n sessionId: params.sessionId,\n threadId: params.currentThreadId,\n context: params.lastBackendContext,\n });\n }\n\n // Save UISpec payloads separately\n // Note: we intentionally do NOT delete m.uiSpec from the caller's live objects.\n // The caller's message array must remain intact for re-rendering and snapshot logic.\n for (const m of params.messages) {\n if (m.uiSpec && m.threadId) {\n await this._db.savePayload({\n threadId: m.threadId,\n messageId: m.id,\n uiSpec: m.uiSpec,\n });\n }\n }\n }\n\n /**\n * Persist session to IndexedDB, then navigate to the URL.\n * Sends an 'openURLInNewTab' bridge message so the host page can intercept\n * (e.g. for SPA routing), then navigates directly as fallback.\n *\n * Legacy compatibility: the prior engine navigated via window.location.href\n * after posting saveSessionAndOpenURL to the iframe. The clean-room runs in\n * the same window (Shadow DOM, not iframe), so it navigates directly.\n */\n async saveAndOpenURL(url: string, persistFn: () => Promise<void>, bridge: CommunicationBridge | null): Promise<void> {\n try {\n await persistFn();\n } catch {\n // Non-fatal — still navigate\n }\n // Notify host so SPAs can intercept (e.g. router.push instead of full reload).\n // NOTE: The bridge message is fire-and-forget. The subsequent location.href\n // assignment may trigger a page unload before the host processes the message.\n // This matches legacy behavior. SPAs that need to intercept\n // should handle the bridge event synchronously or call event.preventDefault()\n // on the gengage:navigate CustomEvent to suppress the fallback navigation.\n bridge?.send('openURLInNewTab', { url });\n if (isSafeUrl(url)) {\n window.location.href = url;\n }\n }\n\n /**\n * Load a UISpec payload from IndexedDB with retry logic.\n * Returns null if not found or all retries fail.\n */\n async loadPayload(threadId: string, messageId: string): Promise<UISpec | null> {\n if (!this._db) return null;\n\n for (let attempt = 0; attempt < 3; attempt++) {\n try {\n const payload = await this._db.loadPayload(threadId, messageId);\n if (payload) return payload.uiSpec;\n } catch {\n // Retry after delay\n }\n if (attempt < 2) {\n await new Promise((r) => setTimeout(r, 100));\n }\n }\n return null;\n }\n\n /**\n * Load favorited SKUs from IDB into the in-memory set.\n */\n async loadFavorites(userId: string, appId: string): Promise<void> {\n if (!this._db) return;\n try {\n const favs = await this._db.loadFavorites(userId, appId);\n for (const f of favs) {\n this.favoritedSkus.add(f.sku);\n this._favoritesCache.set(f.sku, f);\n }\n } catch {\n // Non-fatal — continue without favorites\n }\n }\n\n /** Returns favorited products ordered newest-first. */\n getFavoriteProducts(): FavoriteData[] {\n return [...this._favoritesCache.values()].sort((a, b) => {\n return (b.savedAt ?? '').localeCompare(a.savedAt ?? '');\n });\n }\n\n /**\n * Toggle a product's favorited state in IDB and in-memory set.\n */\n async toggleFavorite(userId: string, appId: string, sku: string, product: Record<string, unknown>): Promise<void> {\n // Always update in-memory state first — IDB is optional (persistence only)\n if (this.favoritedSkus.has(sku)) {\n this.favoritedSkus.delete(sku);\n this._favoritesCache.delete(sku);\n if (this._db) await this._db.removeFavorite(userId, appId, sku);\n } else {\n const data: FavoriteData = {\n userId,\n appId,\n sku,\n name: product['name'] as string | undefined,\n imageUrl: product['imageUrl'] as string | undefined,\n price: product['price'] as string | undefined,\n savedAt: new Date().toISOString(),\n };\n this.favoritedSkus.add(sku);\n this._favoritesCache.set(sku, data);\n if (this._db) await this._db.saveFavorite(data);\n }\n }\n\n close(): void {\n this._db?.close();\n this._db = null;\n }\n}\n","/**\n * Centralized chat presentation state (focus thread, scroll requests, read hints).\n * Purpose-aligned with the legacy Redux presentation slice, without Redux.\n */\n\nexport type GroupReadState = 'seen' | 'unseen' | 'active_unread' | 'overflow_unread';\n\nexport type PresentationGroupMeta = {\n id: string;\n threadId: string;\n readState: GroupReadState;\n status: 'streaming' | 'complete';\n updatedAt: number;\n};\n\nexport type ScrollRequest = {\n id: number;\n type: 'thread' | 'bottom';\n behavior: ScrollBehavior;\n threadId?: string;\n};\n\nconst assistantGroupId = (threadId: string) => `${threadId}:assistant`;\n\nexport class ChatPresentationState {\n /** Per-assistant-thread read / streaming metadata */\n groups: Record<string, PresentationGroupMeta> = {};\n pinnedToBottom = true;\n userInteracting = false;\n /** When set, transcript UI may collapse other threads until released */\n focusedThreadId: string | null = null;\n /** Drawer / widget considered visible */\n shown = false;\n lastAutoAnchoredGroupId: string | null = null;\n scrollRequest: ScrollRequest | null = null;\n private _nextScrollId = 1;\n\n setShown(shown: boolean): void {\n this.shown = shown;\n if (!shown) {\n this.userInteracting = false;\n this.pinnedToBottom = true;\n this.focusedThreadId = null;\n this.scrollRequest = null;\n }\n }\n\n reset(): void {\n this.groups = {};\n this.pinnedToBottom = true;\n this.userInteracting = false;\n this.focusedThreadId = null;\n this.lastAutoAnchoredGroupId = null;\n this.scrollRequest = null;\n }\n\n registerAssistantActivity(threadId: string): void {\n const id = assistantGroupId(threadId);\n const existing = this.groups[id];\n const nextRead: GroupReadState =\n this.shown && this.pinnedToBottom && !this.userInteracting ? 'active_unread' : 'unseen';\n\n this.groups[id] = {\n id,\n threadId,\n readState: existing?.readState === 'seen' ? nextRead : (existing?.readState ?? nextRead),\n status: 'streaming',\n updatedAt: Date.now(),\n };\n if (existing?.readState === 'seen' && this.lastAutoAnchoredGroupId === id) {\n this.lastAutoAnchoredGroupId = null;\n }\n }\n\n finalizeAssistantGroup(threadId: string): void {\n const id = assistantGroupId(threadId);\n const existing = this.groups[id];\n if (!existing) return;\n existing.status = 'complete';\n existing.updatedAt = Date.now();\n }\n\n setGroupReadStates(updates: Array<{ groupId: string; readState: GroupReadState }>): void {\n const t = Date.now();\n for (const { groupId, readState } of updates) {\n const g = this.groups[groupId];\n if (!g || g.readState === readState) continue;\n g.readState = readState;\n g.updatedAt = t;\n }\n }\n\n requestThreadFocus(threadId: string, behavior: ScrollBehavior = 'smooth'): void {\n this.focusedThreadId = threadId;\n this.scrollRequest = {\n id: this._nextScrollId++,\n type: 'thread',\n threadId,\n behavior,\n };\n }\n\n requestScrollToBottom(behavior: ScrollBehavior = 'smooth'): void {\n this.scrollRequest = {\n id: this._nextScrollId++,\n type: 'bottom',\n behavior,\n };\n }\n\n consumeScrollRequest(id: number): void {\n if (this.scrollRequest?.id === id) {\n this.scrollRequest = null;\n }\n }\n\n releaseFocusedThread(): void {\n this.focusedThreadId = null;\n }\n\n /** Align focus with rollback / thread without queuing scroll */\n setFocusedThreadId(threadId: string | null): void {\n this.focusedThreadId = threadId;\n }\n\n markGroupAutoAnchored(groupId: string): void {\n this.lastAutoAnchoredGroupId = groupId;\n }\n\n /** Block soft stream scroll when user pulled away from bottom */\n shouldBlockStreamAutoScroll(): boolean {\n return this.shown && this.userInteracting && !this.pinnedToBottom;\n }\n\n getAssistantReadState(threadId: string): GroupReadState | undefined {\n return this.groups[assistantGroupId(threadId)]?.readState;\n }\n}\n\n/**\n * Latest assistant “group” with unread semantics (for auto-anchor).\n */\nexport function getLatestUnreadAssistantThreadId(\n orderedThreadIds: string[],\n state: ChatPresentationState,\n): string | null {\n for (let i = orderedThreadIds.length - 1; i >= 0; i--) {\n const tid = orderedThreadIds[i]!;\n const rs = state.getAssistantReadState(tid);\n if (rs !== undefined && rs !== 'seen') {\n return tid;\n }\n }\n return null;\n}\n","import { isLikelyConnectivityIssue } from '../common/global-error-toast.js';\n\nconst HTTP_STATUS_PREFIX = /^HTTP\\s+\\d{3}\\b/i;\n\nconst INFRA_MESSAGE_PATTERNS = [\n /response body is null/i,\n /streaming not supported/i,\n /ECONNREFUSED/i,\n /ECONNRESET/i,\n /ETIMEDOUT/i,\n /socket hang up/i,\n /net::ERR_/i,\n /502\\s+bad gateway/i,\n /503\\s+service unavailable/i,\n /504\\s+gateway timeout/i,\n] as const;\n\nconst TECHNICAL_DUMP_PATTERNS = [\n /Traceback\\s*\\(/i,\n /\\bSyntaxError\\b/,\n /\\bReferenceError\\b/,\n /\\bTypeError\\s*:/,\n /\\bat\\s+[\\w./()[\\]<>-]+:\\d+:\\d+/,\n /\\n\\s+at\\s+\\w/,\n /undefined is not/i,\n /is not a function/i,\n /<anonymous>/i,\n /ChunkLoadError/i,\n /UnhandledPromiseRejection/i,\n] as const;\n\n/** Above this length, treat as dump / transport payload — show red strip instead of a chat bubble. */\nconst MAX_CHAT_BUBBLE_ERROR_LENGTH = 720;\n\n/**\n * Use the red inline error strip (+ recovery pills) instead of a normal assistant bubble when the\n * failure looks like transport, HTTP, or an obvious technical dump. User-facing backend messages\n * (e.g. output_text with is_error) stay in the conversation as a chat message.\n */\nexport function shouldShowStreamErrorAsRedStrip(err: Error, displayText: string): boolean {\n if (isLikelyConnectivityIssue(err)) return true;\n\n const raw = err.message.trim();\n if (HTTP_STATUS_PREFIX.test(raw)) return true;\n if (INFRA_MESSAGE_PATTERNS.some((p) => p.test(raw))) return true;\n\n const text = displayText.trim();\n if (text.length > MAX_CHAT_BUBBLE_ERROR_LENGTH) return true;\n\n const lines = text.split(/\\n/).length;\n if (lines >= 6) return true;\n if (TECHNICAL_DUMP_PATTERNS.some((p) => p.test(text))) return true;\n if (lines >= 3 && /\\bat\\s+/.test(text)) return true;\n\n return false;\n}\n","/**\n * KVKK notice filtering and caching helpers.\n *\n * KVKK (Kişisel Verilerin Korunması Kanunu) is Turkey's data protection law.\n * When the backend streams a response containing a KVKK notice, we:\n * 1. Strip the KVKK block from the visible bot text\n * 2. Show a banner on first encounter (per account)\n * 3. Mark it as shown in localStorage to avoid repeat banners\n */\n\nconst KVKK_STORAGE_KEY = 'gengage_kvkk_shown';\nconst KVKK_TEXT_MARKERS = ['kvkk', 'kişisel veri', 'kisisel veri'];\nconst KVKK_LAW_NUMBER_RE = /\\b6698\\b/;\n\nexport function containsKvkk(html: string): boolean {\n const lower = html.toLowerCase();\n return KVKK_TEXT_MARKERS.some((m) => lower.includes(m)) || KVKK_LAW_NUMBER_RE.test(lower);\n}\n\nexport function isKvkkShown(accountId: string): boolean {\n try {\n return localStorage.getItem(`${KVKK_STORAGE_KEY}_${accountId}`) === '1';\n } catch {\n return false;\n }\n}\n\nexport function markKvkkShown(accountId: string): void {\n try {\n localStorage.setItem(`${KVKK_STORAGE_KEY}_${accountId}`, '1');\n } catch {\n // localStorage unavailable — silently ignore\n }\n}\n\n/**\n * Strip the KVKK portion from bot HTML.\n * KVKK is typically wrapped in a `<div style=\"...\">` at the start.\n * We remove the first block-level element that contains a KVKK marker.\n */\nexport function stripKvkkBlock(html: string): string {\n const doc = new DOMParser().parseFromString(html, 'text/html');\n const body = doc.body;\n const children = Array.from(body.children);\n for (const child of children) {\n if (containsKvkk(child.textContent ?? '')) {\n child.remove();\n break; // Only strip the first KVKK block\n }\n }\n return body.innerHTML.trim();\n}\n\nexport function extractKvkkBlock(html: string): string | null {\n const doc = new DOMParser().parseFromString(html, 'text/html');\n for (const child of Array.from(doc.body.children)) {\n if (containsKvkk(child.textContent ?? '')) {\n return child.outerHTML;\n }\n }\n return null;\n}\n\nconst LOCALE_TO_LANGUAGE: Record<string, string> = {\n tr: 'TURKISH',\n en: 'ENGLISH',\n de: 'GERMAN',\n fr: 'FRENCH',\n};\n\nexport function localeToOutputLanguage(locale?: string): string {\n if (!locale) return 'TURKISH';\n return LOCALE_TO_LANGUAGE[locale.toLowerCase().slice(0, 2)] ?? 'TURKISH';\n}\n","@import '../../design-system/index.css';\n\n:host {\n all: initial;\n font-family: var(\n --gengage-font-family,\n 'Plus Jakarta Sans',\n -apple-system,\n BlinkMacSystemFont,\n 'Segoe UI',\n Roboto,\n sans-serif\n );\n font-size: var(--gengage-font-size, 14px);\n}\n\n.gengage-chat-root button,\n.gengage-chat-root [role='button'],\n.gengage-chat-root a {\n cursor: pointer;\n}\n\n/* Screen reader only — visually hidden, accessible to assistive tech */\n.gengage-sr-only {\n position: absolute;\n width: 1px;\n height: 1px;\n padding: 0;\n margin: -1px;\n overflow: hidden;\n clip: rect(0, 0, 0, 0);\n white-space: nowrap;\n border-width: 0;\n}\n\n.gengage-chat-root {\n position: fixed;\n z-index: var(--gengage-z-index, 2147483647);\n\n /* Shared geometry tokens (overridable via theme) */\n --_gengage-chat-offset: var(--gengage-chat-offset, 20px);\n --_gengage-chat-launcher-bottom: var(--gengage-chat-launcher-bottom, 20px);\n --_gengage-chat-launcher-right: var(--gengage-chat-launcher-right, 20px);\n --_gengage-chat-launcher-size: var(--gengage-chat-launcher-size, 56px);\n --_gengage-chat-drawer-width: var(--gengage-chat-width, 400px);\n --_gengage-chat-radius: var(--gengage-chat-shell-radius, 1rem);\n --_gengage-chat-header-height: var(--gengage-chat-header-height, 72px);\n --_gengage-chat-conversation-width: var(--gengage-chat-conversation-width, 396px);\n --_gengage-chat-panel-min-width: var(--gengage-chat-panel-min-width, 320px);\n --_gengage-chat-panel-max-width: var(--gengage-chat-panel-max-width, 1200px);\n --_gengage-chat-input-height: var(--gengage-chat-input-height, 48px);\n --_gengage-chat-shadow: var(--gengage-chat-shadow, var(--shadow-3));\n\n /* ── Editorial Commerce — Semantic Color Tokens ────────────────────── */\n /* Tonal layering: borders defined by background shift, not strokes */\n --_gengage-border-color: var(--border-default);\n --_gengage-discount-color: var(--gengage-discount-color, var(--client-primary));\n --_gengage-success-color: var(--success);\n --_gengage-text-primary: var(--text-primary);\n --_gengage-text-secondary: var(--text-secondary);\n --_gengage-surface-secondary: var(--surface-card-soft);\n --_gengage-surface-muted: var(--surface-card-soft);\n --_gengage-surface-dark: var(--surface-shell);\n --_gengage-rating-color: var(--rating);\n --_gengage-error: var(--error);\n --_gengage-danger-color: var(--error);\n\n /* Ghost border (outline_variant @ 20% opacity — for input fields) */\n --_gengage-ghost-border: var(--gengage-ghost-border, color-mix(in srgb, var(--client-primary) 16%, transparent));\n}\n\n/* Full-viewport layer: dim + click-outside only when `.gengage-chat-root--maximized-host-chrome`\n (floating + side panel open, or overlay modal). Otherwise transparent and pointer-events none. */\n.gengage-chat-backdrop {\n display: none;\n position: fixed;\n inset: 0;\n z-index: 0;\n margin: 0;\n padding: 0;\n border: none;\n background: transparent;\n pointer-events: none;\n cursor: pointer;\n box-sizing: border-box;\n -webkit-tap-highlight-color: transparent;\n}\n\n.gengage-chat-root--open:not(.gengage-chat--inline) .gengage-chat-backdrop {\n display: block;\n pointer-events: none;\n background: var(--gengage-chat-float-backdrop, transparent);\n}\n\n.gengage-chat-root--open.gengage-chat-root--maximized-host-chrome:not(.gengage-chat--inline) .gengage-chat-backdrop {\n pointer-events: auto;\n background: var(--gengage-chat-float-backdrop, color-mix(in srgb, var(--surface-overlay) 72%, transparent));\n}\n\n.gengage-chat-root.gengage-chat--overlay.gengage-chat-root--open .gengage-chat-backdrop {\n background: var(--gengage-chat-overlay-backdrop, var(--surface-overlay));\n pointer-events: auto;\n}\n\n/* Launcher container — fixed-positioned wrapper for button + content areas */\n.gengage-chat-launcher-container {\n position: fixed;\n bottom: var(--_gengage-chat-launcher-bottom);\n right: var(--_gengage-chat-launcher-right);\n display: flex;\n flex-direction: column;\n align-items: flex-end;\n gap: 10px;\n z-index: var(--gengage-z-index, 2147483647);\n}\n\n/* Content area slots — QNA widget or host page injects engagement actions here */\n.gengage-chat-launcher-content-area,\n.gengage-chat-launcher-content-area-bottom {\n display: flex;\n flex-direction: column;\n align-items: flex-end;\n gap: 8px;\n}\n\n/* Collapse empty content areas so they don't affect layout */\n.gengage-chat-launcher-content-area:empty,\n.gengage-chat-launcher-content-area-bottom:empty {\n display: none;\n}\n\n/* Hide content areas when drawer is open */\n.gengage-chat-root--open .gengage-chat-launcher-content-area,\n.gengage-chat-root--open .gengage-chat-launcher-content-area-bottom {\n display: none;\n}\n\n/* Hide launcher button on desktop when drawer is open — prevents overlap with send button */\n.gengage-chat-root--open .gengage-chat-launcher-container {\n pointer-events: none;\n opacity: 0;\n transition: opacity 0.2s ease;\n}\n\n/* Launcher button */\n.gengage-chat-launcher {\n width: var(--_gengage-chat-launcher-size);\n height: var(--_gengage-chat-launcher-size);\n border-radius: 50%;\n border: 1px solid var(--ds-button-primary-border);\n background: var(--ds-button-primary-bg);\n color: var(--ds-button-primary-fg);\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n box-shadow: var(--shadow-3);\n transition:\n transform 0.18s cubic-bezier(0.2, 0.8, 0.2, 1),\n box-shadow 0.18s ease;\n position: relative;\n flex-shrink: 0;\n}\n\n.gengage-chat-launcher:hover {\n transform: none;\n box-shadow: var(--shadow-3);\n}\n\n.gengage-chat-launcher svg {\n width: 28px;\n height: 28px;\n}\n\n/* Image-mode launcher — full-size image, no circular bg */\n.gengage-chat-launcher--image-mode {\n background: transparent;\n border: none;\n border-radius: 0;\n box-shadow: none;\n padding: 0;\n overflow: hidden;\n}\n\n.gengage-chat-launcher--image-mode:hover {\n box-shadow: none;\n}\n\n.gengage-chat-launcher--image-mode img {\n width: 100%;\n height: 100%;\n object-fit: contain;\n}\n\n.gengage-chat-launcher--hidden-mobile {\n display: none !important;\n}\n\n/* Launcher tooltip */\n.gengage-chat-launcher-tooltip {\n position: absolute;\n right: calc(100% + 12px);\n top: 50%;\n transform: translateY(-50%);\n padding: 6px 12px;\n border-radius: var(--radius-control);\n background: var(--surface-shell);\n color: var(--text-inverse);\n font-size: 13px;\n font-weight: 500;\n white-space: nowrap;\n opacity: 0;\n pointer-events: none;\n transition: opacity 0.2s ease;\n box-shadow: var(--shadow-2);\n}\n\n.gengage-chat-launcher-tooltip::after {\n content: '';\n position: absolute;\n left: 100%;\n top: 50%;\n transform: translateY(-50%);\n border: 5px solid transparent;\n border-left-color: var(--surface-shell);\n}\n\n.gengage-chat-launcher:hover .gengage-chat-launcher-tooltip {\n opacity: 1;\n}\n\n/* Hide tooltip when drawer is open */\n.gengage-chat-root--open .gengage-chat-launcher-tooltip {\n display: none;\n}\n\n/* Drawer — always right-anchored, full height, slide in/out from the right */\n.gengage-chat-drawer {\n position: fixed;\n top: 0;\n bottom: 0;\n right: 0;\n width: var(--_gengage-chat-drawer-width);\n max-height: -webkit-fill-available;\n max-height: 100dvh;\n border-radius: 0;\n background: var(--surface-elevated, var(--surface-card));\n border-left: 1px solid var(--border-default);\n box-shadow: var(--_gengage-chat-shadow);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n z-index: var(--gengage-z-index, 2147483647);\n /* Drawer slide-in animation */\n transform: translateX(0);\n transition:\n transform 0.25s cubic-bezier(0.4, 0, 0.2, 1),\n opacity 0.2s ease;\n opacity: 1;\n}\n\n.gengage-chat-root--mobile .gengage-chat-launcher-container {\n right: var(--gengage-chat-launcher-mobile-right, 16px);\n bottom: calc(var(--gengage-chat-launcher-mobile-bottom, 16px) + env(safe-area-inset-bottom, 0px));\n}\n\n.gengage-chat-root--mobile .gengage-chat-drawer {\n left: 0;\n right: 0;\n bottom: 0;\n width: 100%;\n max-width: none;\n max-height: -webkit-fill-available;\n max-height: 100dvh;\n border-radius: 16px 16px 0 0;\n border: 1px solid var(--border-default);\n border-bottom: none;\n box-shadow: var(--shadow-3);\n padding-bottom: env(safe-area-inset-bottom, 0px);\n background: var(--surface-elevated, var(--surface-card));\n}\n\n/* Drag handle pill — visible only on mobile */\n.gengage-chat-drawer-handle {\n display: none;\n}\n\n/* Handle lives inside the header on mobile — visual pill only, pointer-events disabled via JS */\n.gengage-chat-root--mobile .gengage-chat-drawer-handle {\n display: flex;\n align-items: center;\n justify-content: center;\n position: absolute;\n top: 6px;\n left: 0;\n right: 0;\n height: 14px;\n pointer-events: none;\n z-index: 1;\n}\n\n/* Header row is the actual drag target on mobile */\n.gengage-chat-root--mobile .gengage-chat-header {\n cursor: grab;\n touch-action: none;\n user-select: none;\n -webkit-user-select: none;\n}\n\n/* Buttons/links inside the header keep normal pointer behaviour */\n.gengage-chat-root--mobile .gengage-chat-header button,\n.gengage-chat-root--mobile .gengage-chat-header a {\n cursor: pointer;\n touch-action: auto;\n user-select: auto;\n -webkit-user-select: auto;\n}\n\n.gengage-chat-root--mobile .gengage-chat-drawer-handle::before {\n content: '';\n width: 36px;\n height: 4px;\n border-radius: 999px;\n background: var(--border-strong);\n pointer-events: none;\n}\n\n.gengage-chat-root--mobile.gengage-chat-root--mobile-half .gengage-chat-drawer {\n top: auto;\n height: min(72dvh, 620px);\n max-height: min(72dvh, 620px);\n}\n\n.gengage-chat-root--mobile.gengage-chat-root--mobile-full .gengage-chat-drawer {\n top: 0;\n height: 100dvh;\n max-height: 100dvh;\n border-radius: 0;\n}\n\n.gengage-chat-root--mobile .gengage-chat-input-area {\n padding-bottom: calc(10px + env(safe-area-inset-bottom, 0px));\n border-top: 1px solid var(--border-default);\n transform: translateY(calc(-1 * var(--gengage-keyboard-offset, 0px)));\n}\n\n/* (panel-mode-in animation removed — drawer is always full-size, no entry animation needed) */\n\n/* Header — calm neutral shell; brand belongs in action and badge layers */\n.gengage-chat-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n box-sizing: border-box;\n height: var(--_gengage-chat-header-height);\n padding: 14px 16px;\n background: var(--gengage-chat-header-bg, var(--ds-header-bg));\n color: var(--gengage-chat-header-foreground, var(--ds-header-fg));\n box-shadow: none;\n border-bottom: 1px solid var(--gengage-chat-header-border-bottom, var(--ds-header-border));\n position: relative;\n z-index: 2;\n}\n\n.gengage-chat-header-left {\n display: flex;\n align-items: center;\n gap: 10px;\n min-width: 0;\n}\n\n.gengage-chat-header-avatar {\n width: 44px;\n height: 44px;\n border-radius: 999px;\n background: var(--surface-card-soft);\n border: 1px solid var(--border-default);\n object-fit: contain;\n flex-shrink: 0;\n}\n\n.gengage-chat-header-avatar--logo {\n width: auto;\n height: 45px;\n max-width: 92px;\n border-radius: 0;\n border: none;\n background: transparent;\n object-fit: contain;\n}\n\n.gengage-chat-header-info {\n display: flex;\n flex-direction: column;\n gap: 2px;\n min-width: 0;\n}\n\n.gengage-chat-header-title-row {\n display: flex;\n align-items: center;\n gap: 6px;\n min-width: 0;\n}\n\n.gengage-chat-header-title {\n display: block;\n font-weight: 700;\n font-size: 16px;\n line-height: 1.25;\n letter-spacing: 0.01em;\n color: inherit;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.gengage-chat-header-badge {\n display: inline-flex;\n align-items: center;\n min-height: 26px;\n padding: 0 10px;\n border-radius: var(--radius-pill);\n background: var(--ds-badge-brand-bg);\n color: var(--ds-badge-brand-fg);\n border: 1px solid var(--ds-badge-brand-border);\n font-size: 10px;\n font-weight: 800;\n letter-spacing: 0.04em;\n text-transform: uppercase;\n line-height: 1.5;\n}\n\n.gengage-chat-header-powered {\n display: flex;\n align-items: center;\n gap: 4px;\n font-size: 11px;\n color: var(--ds-header-muted);\n text-decoration: none;\n transition: color 0.15s;\n line-height: 1.2;\n}\n\n.gengage-chat-header-powered:hover {\n color: var(--client-primary);\n}\n\n.gengage-chat-header-powered svg {\n width: 12px;\n height: 12px;\n opacity: 0.6;\n flex-shrink: 0;\n}\n\n.gengage-chat-header-right {\n display: flex;\n align-items: center;\n gap: 8px;\n flex-shrink: 0;\n}\n\n/* Header action buttons (cart, favorites) */\n.gengage-chat-header-btn {\n width: 42px;\n height: 42px;\n border-radius: var(--radius-control, 12px);\n background: var(--surface-card-soft);\n border: 1px solid var(--border-default);\n color: var(--text-primary);\n cursor: pointer;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n text-decoration: none;\n line-height: 1;\n transition:\n background-color 0.15s ease,\n color 0.15s ease,\n border-color 0.15s ease;\n}\n\n.gengage-chat-header-btn:hover {\n background: var(--client-primary-subtle);\n color: var(--client-primary);\n border-color: color-mix(in srgb, var(--client-primary) 18%, white);\n}\n\n/* Favorites button with badge */\n.gengage-chat-header-btn--fav {\n position: relative;\n}\n\n/* Reopen-panel button: hidden by default, shown on mobile via JS when panel is hidden */\n.gengage-chat-header-btn--reopen-panel {\n display: none;\n}\n\n/* New-chat button: hide on mobile (reopen-panel button takes its role) */\n.gengage-chat-root--mobile .gengage-chat-new-chat {\n display: none;\n}\n\n.gengage-chat-header-fav-badge {\n position: absolute;\n top: -4px;\n right: -4px;\n min-width: 16px;\n height: 16px;\n border-radius: 999px;\n background: var(--error);\n color: var(--text-inverse);\n font-size: 9px;\n font-weight: 700;\n line-height: 16px;\n text-align: center;\n padding: 0 3px;\n pointer-events: none;\n}\n\n.gengage-chat-close {\n width: 42px;\n height: 42px;\n min-width: 42px;\n min-height: 42px;\n border-radius: var(--radius-control, 12px);\n background: var(--surface-card-soft);\n border: 1px solid var(--border-default);\n color: var(--text-primary);\n font-size: 18px;\n cursor: pointer;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n line-height: 1;\n transition:\n background-color 0.15s ease,\n color 0.15s ease,\n border-color 0.15s ease;\n}\n\n.gengage-chat-close:hover {\n background: var(--client-primary-subtle);\n color: var(--client-primary);\n border-color: color-mix(in srgb, var(--client-primary) 18%, white);\n}\n\n/* Body: flex container for panel + conversation */\n.gengage-chat-body {\n display: flex;\n flex: 1;\n overflow: hidden;\n min-height: 0;\n position: relative; /* needed for mobile side-panel absolute positioning */\n}\n\n/* Panel (left) — hidden by default */\n.gengage-chat-panel {\n display: none;\n width: 0;\n overflow: hidden;\n transition: width 0.22s cubic-bezier(0.2, 0.72, 0.2, 1);\n}\n\n.gengage-chat-panel--visible {\n display: flex;\n flex-direction: column;\n position: relative;\n box-sizing: border-box;\n width: 440px;\n min-width: 440px;\n background: var(--gengage-chat-panel-bg, var(--surface-card));\n border-right: 1px solid var(--border-default);\n overflow-y: auto;\n padding: 16px;\n scrollbar-width: thin;\n scrollbar-color: color-mix(in srgb, var(--text-muted) 24%, transparent) transparent;\n}\n\n.gengage-chat-panel--visible::-webkit-scrollbar {\n width: 6px;\n}\n\n.gengage-chat-panel--visible::-webkit-scrollbar-track {\n background: transparent;\n}\n\n.gengage-chat-panel--visible::-webkit-scrollbar-thumb {\n background: color-mix(in srgb, var(--text-muted) 24%, transparent);\n border-radius: 3px;\n}\n\n.gengage-chat-panel--visible::-webkit-scrollbar-thumb:hover {\n background: color-mix(in srgb, var(--text-muted) 36%, transparent);\n}\n\n/* Above main panel body: analyzing strip + AITopPicks / AIGroupingCards (desktop) */\n.gengage-chat-panel-ai-zone {\n flex-shrink: 0;\n width: 100%;\n box-sizing: border-box;\n padding: 8px 0 12px;\n margin-bottom: 4px;\n border-bottom: 1px solid var(--border-subtle);\n max-height: min(42vh, 320px);\n overflow: auto;\n}\n\n/* Category rail / AI Top Picks: title + scroll row */\n.gengage-chat-panel-ai-zone:has(.gengage-chat-grouping-cards) {\n max-height: min(52vh, 520px);\n}\n\n/* Top Picks: full height — no inner vertical scroll / clipped borders (lean grid parity) */\n.gengage-chat-panel-ai-zone:has(.gengage-chat-ai-top-picks) {\n max-height: none;\n overflow: visible;\n}\n\n.gengage-chat-panel-ai-zone[hidden] {\n display: none !important;\n}\n\n.gengage-chat-panel-ai-zone-inner {\n display: flex;\n align-items: center;\n gap: 10px;\n}\n\n.gengage-chat-panel-ai-zone-text {\n font-size: 13px;\n color: var(--text-muted);\n}\n\n/* Panel AI zone: grouping cards — title fixed row, cards in horizontal scroll rail */\n.gengage-chat-grouping-section-title {\n display: none;\n}\n\n.gengage-chat-panel-ai-zone .gengage-chat-grouping-section-title {\n display: block;\n flex-shrink: 0;\n align-self: stretch;\n width: 100%;\n margin: 0 0 10px;\n font-size: 15px;\n font-weight: 700;\n color: var(--text-primary);\n letter-spacing: -0.01em;\n}\n\n.gengage-chat-panel-ai-zone .gengage-chat-grouping-cards {\n display: flex;\n flex-direction: column;\n align-items: stretch;\n gap: 0;\n width: 100%;\n min-width: 0;\n max-height: none;\n}\n\n.gengage-chat-panel-ai-zone .gengage-chat-grouping-cards-scroll {\n display: flex;\n flex-direction: row;\n flex-wrap: nowrap;\n align-items: stretch;\n gap: 10px;\n width: 100%;\n min-width: 0;\n overflow-x: auto;\n overflow-y: hidden;\n padding: 2px 2px 10px;\n margin: 0 -2px;\n -webkit-overflow-scrolling: touch;\n scrollbar-width: thin;\n}\n\n.gengage-chat-panel-ai-zone .gengage-chat-grouping-card {\n flex: 0 0 auto;\n width: 136px;\n min-width: 136px;\n max-width: 150px;\n box-sizing: border-box;\n flex-direction: column;\n align-items: stretch;\n gap: 8px;\n padding: 12px 10px 14px;\n background: var(--surface-card);\n border: 1px solid var(--border-default);\n border-radius: var(--radius-card);\n box-shadow: var(--shadow-1);\n}\n\n.gengage-chat-panel-ai-zone .gengage-chat-grouping-card:hover {\n border-color: color-mix(in srgb, var(--client-primary) 18%, var(--border-default));\n box-shadow: var(--shadow-2);\n}\n\n.gengage-chat-panel-ai-zone .gengage-chat-grouping-card-arrow {\n display: none;\n}\n\n.gengage-chat-panel-ai-zone .gengage-chat-grouping-card-img {\n width: 100%;\n max-width: 88px;\n height: 72px;\n min-width: 0;\n min-height: 72px;\n margin: 0 auto;\n border-radius: 8px;\n object-fit: cover;\n}\n\n.gengage-chat-panel-ai-zone .gengage-chat-grouping-card-body {\n flex-direction: column;\n align-items: center;\n text-align: center;\n gap: 4px;\n width: 100%;\n}\n\n.gengage-chat-panel-ai-zone .gengage-chat-grouping-card-name {\n font-size: 13px;\n font-weight: 700;\n line-height: 1.25;\n}\n\n.gengage-chat-panel-ai-zone .gengage-chat-grouping-card-desc {\n font-size: 11px;\n color: var(--text-muted);\n line-height: 1.3;\n}\n\n.gengage-chat-panel-ai-zone .gengage-chat-grouping-card-labels {\n justify-content: center;\n margin-top: 2px;\n white-space: normal;\n text-overflow: unset;\n overflow: visible;\n line-height: 1.35;\n}\n\n/* Divider between panel and conversation */\n.gengage-chat-panel-divider {\n display: flex;\n align-items: center;\n width: 4px;\n cursor: pointer;\n background: var(--_gengage-border-color);\n position: relative;\n flex-shrink: 0;\n transition: background 0.15s;\n z-index: 2;\n}\n\n.gengage-chat-panel-divider:hover {\n background: var(--border-strong);\n}\n\n.gengage-chat-panel-divider--hidden {\n display: none;\n}\n\n.gengage-chat-panel-divider-toggle {\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n width: 28px;\n height: 48px;\n border: 1px solid var(--_gengage-border-color);\n border-radius: 6px;\n background: var(--surface-card);\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 14px;\n color: var(--text-secondary);\n box-shadow: var(--shadow-1);\n transition:\n background 0.15s,\n color 0.15s;\n padding: 0;\n line-height: 1;\n}\n\n.gengage-chat-panel-divider-toggle:hover {\n background: var(--surface-card-muted);\n color: var(--client-primary);\n}\n\n.gengage-chat-panel-divider-preview {\n position: absolute;\n top: 50%;\n left: 50%;\n width: 38px;\n height: 192px;\n display: none;\n flex-direction: column;\n gap: 2px;\n padding: 6px 4px;\n border-radius: 999px;\n overflow: hidden;\n pointer-events: none;\n transform: translate(-50%, -50%);\n border: 1px solid var(--border-default);\n background: color-mix(in srgb, var(--surface-elevated) 96%, transparent);\n box-shadow: var(--shadow-2);\n z-index: 1;\n}\n\n.gengage-chat-panel-divider-preview::after {\n content: '';\n position: absolute;\n inset: 0;\n background: linear-gradient(180deg, transparent, color-mix(in srgb, var(--surface-shell) 10%, transparent));\n pointer-events: none;\n}\n\n.gengage-chat-panel-divider-preview-thumb {\n position: relative;\n flex: 1 1 0;\n min-height: 0;\n border-radius: 999px;\n overflow: hidden;\n}\n\n.gengage-chat-panel-divider-preview-img {\n width: 100%;\n height: 100%;\n object-fit: cover;\n filter: grayscale(0.55) saturate(0.8) brightness(0.92);\n transform: scale(1.14);\n opacity: 0.9;\n}\n\n/* Collapsed panel — compound selector for specificity over .gengage-chat-panel--visible */\n.gengage-chat-panel.gengage-chat-panel--collapsed {\n flex: 0 0 0px;\n width: 0;\n min-width: 0;\n max-width: 0;\n padding: 0;\n border-right: none;\n overflow: hidden;\n}\n\n/* When collapsed, keep toggle button fully visible inside the chat pane */\n.gengage-chat-panel--collapsed ~ .gengage-chat-panel-divider {\n width: 0;\n background: transparent;\n}\n\n.gengage-chat-panel--collapsed ~ .gengage-chat-panel-divider .gengage-chat-panel-divider-toggle {\n left: 50%;\n transform: translate(-50%, -50%);\n}\n\n.gengage-chat-panel--collapsed\n ~ .gengage-chat-panel-divider.gengage-chat-panel-divider--preview-active\n .gengage-chat-panel-divider-preview {\n display: flex;\n left: 50%;\n transform: translate(-50%, -50%);\n}\n\n/* Conversation (right) — always visible */\n.gengage-chat-conversation {\n display: flex;\n flex-direction: column;\n flex: 1;\n min-width: 0;\n overflow: hidden;\n background: var(--surface-card);\n position: relative;\n z-index: 1;\n}\n\n/* Full-height, right-rail panel mode — overlay on top of the host page.\n Header stays inside the conversation column; the results panel is a\n natural drawer extension that slides out from the left of the chat.\n Conversation right edge is kept at the same offset as normal mode\n so the chat pane does not jump when the panel opens. */\n.gengage-chat-drawer--with-panel {\n width: auto;\n background: transparent;\n box-shadow: none;\n overflow: visible;\n}\n\n/* Header flows naturally inside conversation column in panel mode */\n.gengage-chat-drawer--with-panel .gengage-chat-header {\n border-radius: 0;\n flex-shrink: 0;\n}\n\n/* Body takes full height — no gap: panes stick together */\n.gengage-chat-drawer--with-panel .gengage-chat-body {\n flex: 1 1 0;\n min-height: 0;\n gap: 0;\n padding: 0;\n align-items: stretch;\n overflow: visible;\n}\n\n/* Panel: attached to conversation, rounded only on the left side */\n.gengage-chat-drawer--with-panel .gengage-chat-panel--visible {\n flex: 0 0 auto;\n width: clamp(var(--_gengage-chat-panel-min-width), 56vw, var(--_gengage-chat-panel-max-width));\n min-width: var(--_gengage-chat-panel-min-width);\n max-width: var(--_gengage-chat-panel-max-width);\n border-right: none;\n background: var(--gengage-chat-panel-bg, var(--surface-card));\n padding: 0 16px 16px;\n overflow-y: auto;\n margin: 0;\n border-radius: 0;\n box-shadow: none;\n max-height: 100dvh;\n}\n\n/* Divider in panel mode: invisible line, only the toggle button shows */\n.gengage-chat-drawer--with-panel .gengage-chat-panel-divider {\n width: 0;\n background: transparent;\n}\n\n/* Toggle button: \"band-aid\" connecting the two panes */\n.gengage-chat-drawer--with-panel .gengage-chat-panel-divider-toggle {\n width: 20px;\n height: 48px;\n border-radius: 10px;\n background: var(--surface-card);\n color: var(--client-primary);\n border: 1px solid color-mix(in srgb, var(--client-primary) 20%, var(--border-default));\n box-shadow: var(--shadow-1);\n font-size: 12px;\n z-index: 10;\n}\n\n.gengage-chat-drawer--with-panel .gengage-chat-panel-divider-toggle:hover {\n background: var(--client-primary-subtle);\n color: var(--client-primary);\n}\n\n.gengage-chat-drawer--with-panel .gengage-chat-panel-divider--preview-active .gengage-chat-panel-divider-toggle:hover {\n background: var(--client-primary-subtle);\n}\n\n.gengage-chat-drawer--with-panel .gengage-chat-conversation {\n flex: 0 0 var(--_gengage-chat-conversation-width);\n width: var(--_gengage-chat-conversation-width);\n border-left: none;\n background: var(--surface-card);\n box-shadow: var(--shadow-2);\n}\n\n/* Conversation sits flush against viewport edge in panel mode */\n.gengage-chat-drawer--with-panel .gengage-chat-messages {\n padding-right: 6px;\n scrollbar-width: thin;\n scrollbar-color: color-mix(in srgb, var(--text-muted) 22%, transparent) transparent;\n}\n\n.gengage-chat-drawer--with-panel .gengage-chat-messages::-webkit-scrollbar {\n width: 0;\n}\n\n/* In --with-panel mode, collapsed panel must override the visible sizing above */\n.gengage-chat-drawer--with-panel .gengage-chat-panel.gengage-chat-panel--collapsed {\n flex: 0 0 0px;\n width: 0;\n min-width: 0;\n max-width: 0;\n padding: 0;\n overflow: hidden;\n}\n\n/* When panel is collapsed, shrink drawer to just the conversation width */\n.gengage-chat-drawer--with-panel:has(.gengage-chat-panel--collapsed) {\n width: var(--_gengage-chat-conversation-width);\n background: var(--surface-elevated, var(--surface-card));\n box-shadow: var(--_gengage-chat-shadow);\n}\n\n.gengage-chat-drawer--with-panel .gengage-chat-footer {\n display: none;\n}\n\n/* Messages */\n.gengage-chat-messages {\n flex: 1;\n overflow-y: auto;\n padding: 16px;\n display: flex;\n flex-direction: column;\n gap: 16px;\n min-height: 0;\n scroll-behavior: smooth;\n -webkit-overflow-scrolling: touch;\n overscroll-behavior: contain;\n touch-action: pan-y;\n}\n\n.gengage-chat-messages::-webkit-scrollbar {\n width: 6px;\n}\n.gengage-chat-messages::-webkit-scrollbar-track {\n background: transparent;\n}\n.gengage-chat-messages::-webkit-scrollbar-thumb {\n background: color-mix(in srgb, var(--text-muted) 22%, transparent);\n border-radius: 3px;\n}\n.gengage-chat-messages::-webkit-scrollbar-thumb:hover {\n background: color-mix(in srgb, var(--text-muted) 36%, transparent);\n}\n\n/* Thread focus: collapse older turns until user expands history */\n.gengage-chat-bubble--presentation-collapsed {\n display: none !important;\n}\n\n.gengage-chat-former-messages-btn {\n position: sticky;\n top: 0;\n z-index: 2;\n flex-shrink: 0;\n align-self: center;\n margin-bottom: 4px;\n padding: 8px 14px;\n border-radius: var(--radius-pill);\n border: 1px solid var(--ds-chip-border);\n background: color-mix(in srgb, var(--surface-card) 96%, transparent);\n color: var(--text-primary);\n font-size: 13px;\n font-weight: 600;\n cursor: pointer;\n box-shadow: var(--shadow-1);\n}\n\n.gengage-chat-former-messages-btn:hover {\n background: var(--surface-card-muted);\n}\n\n.gengage-chat-bubble {\n position: relative;\n}\n\n@keyframes gengage-chat-msg-in {\n from {\n opacity: 0;\n transform: translateY(8px);\n }\n to {\n opacity: 1;\n transform: translateY(0);\n }\n}\n\n.gengage-chat-bubble--user {\n max-width: 85%;\n}\n\n.gengage-chat-bubble--assistant {\n max-width: 85%;\n margin-left: 2px;\n}\n\n/* Active message highlight (clicked to show its panel content) */\n.gengage-chat-bubble--active {\n outline: 2px solid var(--ds-message-active-outline);\n outline-offset: -2px;\n}\n\n.gengage-chat-bubble--assistant:empty {\n display: none;\n}\n\n/* Rich HTML content inside assistant bubbles */\n.gengage-chat-bubble--assistant .gengage-chat-bubble-text a {\n color: var(--client-primary);\n text-decoration: underline;\n}\n.gengage-chat-bubble--assistant .gengage-chat-bubble-text a:hover {\n text-decoration: none;\n}\n.gengage-chat-bubble--assistant .gengage-chat-bubble-text p {\n margin: 0 0 8px;\n}\n.gengage-chat-bubble--assistant .gengage-chat-bubble-text p:last-child {\n margin-bottom: 0;\n}\n.gengage-chat-bubble--assistant .gengage-chat-bubble-text ul,\n.gengage-chat-bubble--assistant .gengage-chat-bubble-text ol {\n margin: 6px 0;\n padding-left: 20px;\n}\n.gengage-chat-bubble--assistant .gengage-chat-bubble-text li {\n margin-bottom: 6px;\n line-height: 1.5;\n}\n.gengage-chat-bubble--assistant .gengage-chat-bubble-text code {\n background: color-mix(in srgb, var(--text-primary) 8%, transparent);\n padding: 1px 4px;\n border-radius: 6px;\n font-size: 0.9em;\n}\n.gengage-chat-bubble--assistant .gengage-chat-bubble-text pre {\n background: color-mix(in srgb, var(--surface-card) 78%, var(--surface-card-soft));\n border: 1px solid var(--border-subtle);\n padding: 10px 12px;\n border-radius: 12px;\n overflow-x: auto;\n margin: 4px 0;\n}\n.gengage-chat-bubble--assistant .gengage-chat-bubble-text table {\n border-collapse: collapse;\n margin: 4px 0;\n font-size: 0.9em;\n}\n.gengage-chat-bubble--assistant .gengage-chat-bubble-text th,\n.gengage-chat-bubble--assistant .gengage-chat-bubble-text td {\n border: 1px solid var(--border-default);\n padding: 4px 8px;\n}\n.gengage-chat-bubble--assistant .gengage-chat-bubble-text img {\n max-width: 100%;\n height: auto;\n border-radius: 12px;\n margin: 4px 0;\n}\n.gengage-chat-bubble--assistant .gengage-chat-bubble-text h1,\n.gengage-chat-bubble--assistant .gengage-chat-bubble-text h2,\n.gengage-chat-bubble--assistant .gengage-chat-bubble-text h3,\n.gengage-chat-bubble--assistant .gengage-chat-bubble-text h4,\n.gengage-chat-bubble--assistant .gengage-chat-bubble-text h5,\n.gengage-chat-bubble--assistant .gengage-chat-bubble-text h6 {\n margin: 8px 0 4px;\n line-height: 1.3;\n color: var(--text-primary);\n}\n.gengage-chat-bubble--assistant .gengage-chat-bubble-text blockquote {\n border-left: 3px solid var(--border-default);\n padding-left: 12px;\n margin: 4px 0;\n color: var(--text-secondary);\n}\n\n/* GAP-077: Prevent bot response text from clipping on narrow viewports */\n.gengage-chat-bubble--assistant .gengage-chat-bubble-text {\n overflow-wrap: break-word;\n}\n\n/* First bot message in thread — subtle accent */\n.gengage-chat-bubble--first {\n border-left: 1px solid var(--ds-message-assistant-border);\n}\n\n.gengage-chat-bubble--first .gengage-chat-bubble-text {\n font-size: inherit;\n}\n\n@media (max-width: 768px) {\n .gengage-chat-bubble--assistant .gengage-chat-bubble-text {\n line-height: 1.5;\n }\n\n /* GAP-054: Prevent long user messages from overflowing on mobile */\n .gengage-chat-bubble--user {\n max-width: calc(100% - 16px);\n word-break: break-word;\n }\n}\n\n/* Typing indicator */\n.gengage-chat-typing {\n align-self: flex-start;\n max-width: 85%;\n}\n\n.gengage-chat-typing-dots {\n flex-shrink: 0;\n}\n\n.gengage-chat-typing-text {\n flex: 1;\n min-width: 0;\n}\n\n.gengage-chat-panel-loading-status {\n margin-bottom: 16px;\n}\n\n/* Panel loading strip: stay inside padded panel (desktop + mobile) */\n.gengage-chat-panel-loading-status.gds-progress-loader-panel {\n max-width: 100%;\n min-width: 0;\n box-sizing: border-box;\n}\n\n/* Mobile: single-line label with ellipsis */\n.gengage-chat-root--mobile .gengage-chat-panel-loading-status.gds-progress-loader-panel {\n max-width: 100%;\n}\n\n.gengage-chat-root--mobile .gengage-chat-panel-loading-status .gengage-chat-panel-loading-label {\n flex: 1 1 auto;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n/* Offline status bar */\n.gengage-chat-offline-bar {\n display: none;\n align-items: center;\n gap: 6px;\n padding: 6px 12px;\n font-size: 12px;\n line-height: 1.4;\n color: color-mix(in srgb, var(--warning) 72%, var(--text-primary));\n background: color-mix(in srgb, var(--warning) 10%, white);\n border-bottom: 1px solid color-mix(in srgb, var(--warning) 22%, var(--border-default));\n}\n.gengage-chat-offline-bar--visible {\n display: flex;\n}\n.gengage-chat-offline-bar::before {\n content: '';\n display: inline-block;\n width: 8px;\n height: 8px;\n background: var(--_gengage-rating-color);\n border-radius: 50%;\n flex-shrink: 0;\n}\n\n/* Thinking steps (legacy checklist style kept for compatibility) */\n.gengage-chat-thinking-steps {\n display: flex;\n flex-direction: column;\n gap: 4px;\n padding: 4px 0;\n}\n\n.gengage-chat-thinking-step {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 13px;\n color: var(--text-secondary);\n}\n\n.gengage-chat-thinking-step-marker {\n font-size: 12px;\n width: 16px;\n text-align: center;\n flex-shrink: 0;\n}\n\n.gengage-chat-thinking-step-marker--done {\n color: var(--_gengage-success-color);\n}\n\n.gengage-chat-thinking-step-marker--active {\n color: var(--ai-accent-start);\n animation: gengage-thinking-pulse 1.5s ease-in-out infinite;\n}\n\n@keyframes gengage-thinking-pulse {\n 0%,\n 100% {\n opacity: 1;\n }\n 50% {\n opacity: 0.4;\n }\n}\n\n.gengage-chat-thinking-step-text {\n line-height: 1.4;\n}\n\n/* Error */\n.gengage-chat-error {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 10px;\n padding: 10px 14px;\n border-radius: var(--radius-control);\n background: color-mix(in srgb, var(--error) 10%, white);\n color: color-mix(in srgb, var(--error) 84%, var(--text-primary));\n font-size: 13px;\n text-align: center;\n border: 1px solid color-mix(in srgb, var(--error) 22%, var(--border-default));\n}\n\n.gengage-chat-error-retry {\n flex-shrink: 0;\n padding: 4px 12px;\n border: 1px solid color-mix(in srgb, var(--error) 24%, var(--border-default));\n border-radius: var(--radius-control);\n background: var(--surface-card);\n color: color-mix(in srgb, var(--error) 84%, var(--text-primary));\n font-size: 12px;\n font-weight: 500;\n cursor: pointer;\n transition:\n background 0.15s,\n border-color 0.15s;\n}\n\n.gengage-chat-error-retry:hover {\n background: color-mix(in srgb, var(--error) 8%, white);\n border-color: color-mix(in srgb, var(--error) 32%, var(--border-default));\n}\n\n/* Input area — pill container matching production cockpit */\n.gengage-chat-input-area {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n flex-shrink: 0;\n gap: 0;\n padding: 10px 12px 12px;\n border-top: 1px solid var(--border-subtle);\n background: var(--surface-card);\n /* Keep attach menu (overflows upward) below the message list */\n position: relative;\n z-index: 2;\n}\n\n.gengage-chat-input-pill {\n display: flex;\n align-items: center;\n gap: 12px;\n flex: 1;\n min-width: 0;\n min-height: 52px;\n background: var(--surface-card);\n border: 1px solid var(--border-default);\n border-radius: 16px;\n padding: 6px 6px 6px 14px;\n /* overflow:visible — attach menu overflows upward; hidden would clip it */\n overflow: visible;\n transition:\n border-color 0.2s ease,\n box-shadow 0.2s ease;\n box-shadow: none;\n}\n\n.gengage-chat-input-pill:focus-within {\n border-color: color-mix(in srgb, var(--client-primary) 28%, white);\n box-shadow: 0 0 0 4px var(--client-focus-ring);\n}\n\n.gengage-chat-input {\n flex: 1;\n min-width: 0;\n min-height: 40px;\n max-height: 120px;\n padding: 0;\n border: none;\n border-radius: 0;\n font-size: 14px;\n font-family: inherit;\n line-height: 1.4;\n align-content: center;\n outline: none;\n background: transparent;\n color: var(--_gengage-text-primary);\n resize: none;\n overflow-y: hidden;\n}\n\n/* On mobile, keep single-line appearance and meet WCAG 2.5.5 touch targets */\n.gengage-chat-root--mobile .gengage-chat-input-pill {\n min-height: 52px;\n padding: 6px 6px 6px 14px;\n}\n\n.gengage-chat-root--mobile .gengage-chat-input {\n min-height: 44px;\n max-height: 44px;\n font-size: 16px;\n white-space: nowrap;\n overflow: hidden;\n}\n\n.gengage-chat-root--mobile .gengage-chat-input::placeholder {\n font-size: 15px;\n}\n\n.gengage-chat-input::placeholder {\n font-size: 14px;\n color: var(--text-muted);\n line-height: normal;\n}\n\n.gengage-chat-send {\n width: 40px;\n height: 40px;\n min-width: 40px;\n padding: 0;\n border: 1px solid var(--ds-button-primary-border);\n border-radius: 12px;\n background: var(--ds-button-primary-bg);\n color: var(--ds-button-primary-fg);\n font-weight: 700;\n font-size: 0;\n font-family: inherit;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n transition:\n background-color 0.2s cubic-bezier(0.4, 0, 0.2, 1),\n box-shadow 0.2s ease,\n transform 0.15s ease;\n box-shadow: none;\n position: relative;\n}\n\n.gengage-chat-send:hover {\n background: var(--ds-button-primary-bg-hover);\n transform: none;\n box-shadow: none;\n}\n\n.gengage-chat-send:active {\n transform: translateY(0);\n box-shadow: none;\n}\n\n.gengage-chat-send svg {\n width: 18px;\n height: 18px;\n}\n\n.gengage-chat-send--stop {\n background: var(--surface-card-soft);\n border-color: color-mix(in srgb, var(--client-primary) 18%, var(--border-default));\n color: var(--text-primary);\n}\n\n.gengage-chat-send--stop:hover {\n background: color-mix(in srgb, var(--surface-card-soft) 86%, var(--client-primary-subtle));\n border-color: color-mix(in srgb, var(--client-primary) 24%, var(--border-default));\n color: var(--client-primary);\n}\n\n.gengage-chat-send-stop-icon {\n display: inline-block;\n width: 12px;\n height: 12px;\n border-radius: 4px;\n background: currentColor;\n}\n\n.gengage-chat-send--stop::after {\n content: attr(data-tooltip);\n position: absolute;\n right: calc(100% + 10px);\n top: 50%;\n transform: translateY(-50%);\n padding: 7px 10px;\n border-radius: 10px;\n background: color-mix(in srgb, var(--surface-shell) 98%, black);\n border: 1px solid color-mix(in srgb, var(--surface-shell) 60%, white);\n box-shadow: var(--shadow-2);\n color: var(--text-inverse);\n font-size: 12px;\n font-weight: 600;\n line-height: 1.2;\n white-space: nowrap;\n opacity: 0;\n pointer-events: none;\n transition: opacity 0.16s ease;\n z-index: 12;\n}\n\n@media (hover: hover) and (pointer: fine) {\n .gengage-chat-send--stop:hover::after,\n .gengage-chat-send--stop:focus-visible::after {\n opacity: 1;\n }\n}\n\n.gengage-chat-send:disabled {\n background: var(--ds-neutral-200);\n border-color: var(--ds-neutral-200);\n opacity: 0.5;\n box-shadow: none;\n cursor: not-allowed;\n}\n\n/* Footer — minimal, clean */\n.gengage-chat-footer {\n padding: 4px 10px;\n text-align: center;\n font-size: 10px;\n color: var(--text-muted);\n border-top: 1px solid var(--border-subtle);\n background: var(--surface-card);\n}\n\n/* UI Spec rendered components */\n.gengage-chat-uispec {\n width: 100%;\n}\n\n.gengage-chat-uispec:empty {\n display: none;\n}\n\n.gengage-chat-uispec > * {\n animation: gengage-chat-widget-enter 0.16s cubic-bezier(0.2, 0.7, 0.2, 1) both;\n}\n\n@keyframes gengage-chat-widget-enter {\n from {\n opacity: 0;\n transform: translateY(5px) scale(0.99);\n }\n to {\n opacity: 1;\n transform: translateY(0) scale(1);\n }\n}\n\n.gengage-chat-action-buttons {\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n padding: 6px 0 4px;\n /* Even distribution when buttons wrap to a new line */\n justify-content: flex-start;\n}\n\n/* When action buttons wrap, last orphan row fills available space */\n.gengage-chat-action-buttons::after {\n content: '';\n flex: 1 0 auto;\n}\n\n.gengage-chat-action-btn {\n flex: 0 0 auto;\n padding: 8px 16px;\n border: 1px solid var(--ds-button-primary-border);\n border-radius: var(--radius-pill, 999px);\n background: var(--ds-button-primary-bg);\n color: var(--ds-button-primary-fg);\n font-size: 13px;\n font-weight: 700;\n line-height: 1.25;\n font-family: inherit;\n cursor: pointer;\n white-space: nowrap;\n transition:\n filter 0.15s ease,\n transform 0.1s ease;\n}\n\n.gengage-chat-action-btn:hover {\n background: var(--ds-button-primary-bg-hover);\n}\n\n.gengage-chat-action-btn:active {\n transform: scale(0.97);\n}\n\n.gengage-chat-product-card {\n position: relative;\n display: flex;\n flex-direction: column;\n border: 1px solid var(--ds-card-border);\n border-radius: var(--ds-card-radius);\n overflow: hidden;\n background: var(--ds-card-bg);\n width: 160px;\n min-width: 160px;\n max-width: 160px;\n box-shadow: var(--ds-card-shadow);\n transition:\n transform 0.1s ease,\n box-shadow 0.2s ease;\n}\n\n.gengage-chat-product-card:hover {\n transform: translateY(-2px);\n box-shadow: var(--ds-card-shadow-hover);\n}\n\n.gengage-chat-product-card-img {\n width: 100%;\n height: 120px;\n object-fit: contain;\n display: block;\n background: var(--surface-card);\n padding: 8px;\n box-sizing: border-box;\n}\n\n.gengage-chat-product-card-body {\n display: flex;\n flex-direction: column;\n flex: 1;\n gap: 4px;\n padding: 8px 10px 10px;\n text-align: center;\n}\n\n.gengage-chat-product-card-name {\n font-size: 13px;\n font-weight: 600;\n color: var(--_gengage-text-primary);\n line-height: 1.35;\n margin-bottom: 0;\n min-height: calc(2 * 1.35em);\n display: -webkit-box;\n -webkit-line-clamp: 2;\n line-clamp: 2;\n -webkit-box-orient: vertical;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.gengage-chat-product-card-brand {\n font-size: 10px;\n color: var(--text-secondary);\n}\n\n.gengage-chat-product-card-rating {\n font-size: 11px;\n color: var(--_gengage-rating-color);\n line-height: 1;\n}\n\n.gengage-star-half {\n display: inline-block;\n position: relative;\n}\n\n.gengage-star-half > span {\n position: absolute;\n left: 0;\n top: 0;\n overflow: hidden;\n width: 0.5em;\n}\n\n.gengage-chat-product-card-review-count {\n color: var(--text-secondary);\n font-size: 10px;\n}\n\n.gengage-chat-product-card-price,\n.gengage-chat-product-card-price-block {\n display: flex;\n flex-wrap: wrap;\n align-items: baseline;\n justify-content: center;\n gap: 2px 6px;\n font-size: 16px;\n font-weight: 800;\n color: var(--text-primary);\n}\n\n.gengage-chat-product-card-current-price {\n font: inherit;\n font-weight: 800;\n color: inherit;\n}\n\n.gengage-chat-product-card-original-price {\n text-decoration: line-through;\n color: var(--_gengage-text-secondary);\n font-weight: 400;\n font-size: 11px;\n margin-right: 0;\n}\n\n/* Price row + compact rating (★ 4.5) */\n.gengage-chat-product-card-meta-row {\n display: flex;\n flex-direction: row;\n align-items: flex-start;\n justify-content: space-between;\n gap: 8px;\n width: 100%;\n min-height: 34px;\n}\n\n.gengage-chat-product-card-meta-row--empty {\n visibility: hidden;\n}\n\n.gengage-chat-product-card-rating-compact {\n display: inline-flex;\n align-items: center;\n gap: 5px;\n flex-shrink: 0;\n padding: 3px 8px;\n border: 1px solid var(--border-subtle);\n border-radius: 999px;\n background: var(--surface-card-muted);\n font-size: 11px;\n font-weight: 700;\n color: var(--text-secondary);\n line-height: 1.2;\n}\n\n.gengage-chat-product-card-rating-compact-star {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n color: color-mix(in srgb, var(--warning) 60%, var(--text-primary));\n line-height: 0;\n}\n\n.gengage-chat-product-card-rating-compact-value {\n font-variant-numeric: tabular-nums;\n color: var(--text-primary);\n}\n\n/* Image corner actions (fav + find similar) */\n.gengage-chat-product-card-img-actions {\n position: absolute;\n top: 8px;\n right: 8px;\n z-index: 4;\n display: flex;\n flex-direction: column;\n align-items: flex-end;\n gap: 6px;\n pointer-events: none;\n}\n\n.gengage-chat-product-card-img-actions > * {\n pointer-events: auto;\n}\n\n.gengage-chat-product-card-img-actions .gengage-chat-favorite-btn {\n position: static;\n}\n\n.gengage-chat-product-card-img-actions .gengage-chat-find-similar-pill {\n position: static;\n}\n\n/* Async pricing skeleton */\n.gengage-chat-price-skeleton {\n display: inline-block;\n width: 80px;\n height: 16px;\n background: linear-gradient(90deg, var(--ds-neutral-200) 25%, var(--ds-neutral-100) 50%, var(--ds-neutral-200) 75%);\n background-size: 200% 100%;\n animation: gengage-skeleton-price-pulse 1.5s ease-in-out infinite;\n border-radius: 4px;\n vertical-align: middle;\n}\n\n@keyframes gengage-skeleton-price-pulse {\n 0% {\n background-position: 200% 0;\n }\n 100% {\n background-position: -200% 0;\n }\n}\n\n.gengage-chat-product-card-cta {\n display: block;\n margin-top: auto;\n padding: 8px 10px;\n text-align: center;\n background: transparent;\n color: var(--client-primary);\n text-decoration: none;\n font-size: 12px;\n font-weight: 700;\n letter-spacing: 0.01em;\n border-top: 1px solid var(--border-default);\n cursor: pointer;\n transition:\n color 0.15s ease,\n background 0.15s ease;\n}\n\n.gengage-chat-product-card-cta:hover {\n color: var(--client-primary-hover);\n background: var(--surface-card-soft);\n}\n\n.gengage-chat-product-card-cta:active {\n background: var(--surface-card-soft);\n}\n\nbutton.gengage-chat-product-card-cta {\n appearance: none;\n -webkit-appearance: none;\n font: inherit;\n margin: 0;\n width: 100%;\n box-sizing: border-box;\n border-left: none;\n border-right: none;\n border-bottom: none;\n}\n\n/*\n * ProductSummaryCard — compact horizontal card for inline chat-pane rendering.\n * Mirrors production's LaunchSingleProduct: image left, info right.\n */\n.gengage-chat-product-summary {\n display: flex;\n align-items: center;\n flex-wrap: wrap;\n gap: 12px;\n padding: 10px 12px;\n border: 1px solid var(--border-default);\n border-radius: var(--radius-control);\n background: var(--surface-card);\n cursor: pointer;\n transition:\n background 0.15s,\n box-shadow 0.15s,\n border-color 0.15s;\n margin: 4px 0;\n}\n.gengage-chat-product-summary:hover {\n background: var(--surface-card-soft);\n border-color: color-mix(in srgb, var(--client-primary) 18%, var(--border-default));\n box-shadow: var(--shadow-1);\n}\n\n.gengage-chat-product-summary__image {\n flex-shrink: 0;\n width: 64px;\n height: 64px;\n border-radius: var(--radius-control);\n overflow: hidden;\n border: 1px solid var(--border-subtle);\n background: var(--surface-card-soft);\n}\n.gengage-chat-product-summary__image img {\n width: 100%;\n height: 100%;\n object-fit: cover;\n display: block;\n}\n\n.gengage-chat-product-summary__content {\n flex: 1 1 180px;\n min-width: 0;\n display: flex;\n flex-direction: column;\n gap: 2px;\n}\n\n.gengage-chat-product-summary__name {\n font-size: 13px;\n font-weight: 500;\n line-height: 1.3;\n color: var(--text-primary);\n display: -webkit-box;\n -webkit-line-clamp: 2;\n line-clamp: 2;\n -webkit-box-orient: vertical;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.gengage-chat-product-summary__rating {\n font-size: 11px;\n color: var(--_gengage-rating-color);\n line-height: 1;\n}\n.gengage-chat-product-summary__review-count {\n color: var(--text-secondary);\n font-size: 11px;\n}\n\n.gengage-chat-product-summary__price {\n font-size: 13px;\n font-weight: 600;\n color: var(--text-primary);\n line-height: 1;\n margin-top: 2px;\n}\n.gengage-chat-product-summary__price-original {\n text-decoration: line-through;\n color: var(--_gengage-text-secondary);\n font-weight: 400;\n font-size: 11px;\n}\n.gengage-chat-product-summary__price-current {\n color: var(--client-primary);\n}\n\n.gengage-chat-product-summary__cta {\n flex-shrink: 0;\n margin-left: auto;\n font-size: 12px;\n font-weight: 600;\n color: var(--client-primary);\n text-decoration: none;\n padding: 4px 10px;\n border-radius: var(--radius-pill);\n background: var(--client-primary-subtle);\n transition:\n background 0.15s,\n color 0.15s;\n white-space: nowrap;\n}\n.gengage-chat-product-summary__cta:hover {\n background: var(--client-primary-soft);\n}\n.gengage-chat-product-summary__cta:focus-visible {\n outline: 2px solid var(--client-focus-ring);\n outline-offset: 2px;\n}\n\n.gengage-chat-product-grid {\n display: flex;\n gap: 12px;\n overflow-x: auto;\n /* Right padding hints at horizontal scrollability (scroll affordance) */\n padding: 8px 16px 8px 0;\n -webkit-overflow-scrolling: touch;\n scrollbar-width: none;\n scroll-snap-type: x proximity;\n min-height: 180px;\n}\n\n.gengage-chat-product-grid > * {\n scroll-snap-align: start;\n flex: 0 0 auto;\n}\n\n.gengage-chat-product-grid::-webkit-scrollbar {\n display: none;\n}\n\n.gengage-chat-review-highlights {\n padding: 4px 0 8px;\n}\n\n.gengage-chat-review-empty {\n font-size: 12px;\n color: var(--_gengage-text-secondary);\n}\n\n.gengage-chat-review-subjects-heading {\n font-size: 14px;\n font-weight: 700;\n color: var(--text-primary);\n margin-bottom: 10px;\n}\n\n.gengage-chat-review-subjects {\n margin-bottom: 12px;\n}\n\n.gengage-chat-review-subject {\n gap: 6px;\n padding: 7px 11px;\n font-size: 13px;\n font-weight: 600;\n line-height: 1.2;\n color: var(--text-primary);\n box-shadow: none;\n}\n\n.gengage-chat-review-subject--active {\n border-color: var(--ds-chip-border-active);\n background: var(--ds-chip-bg-active);\n color: var(--ds-chip-fg-active);\n}\n\n.gengage-chat-review-subject-icon {\n width: 14px;\n height: 14px;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n}\n\n.gengage-chat-review-subject-icon svg {\n width: 14px;\n height: 14px;\n display: block;\n}\n\n.gengage-chat-review-subject[data-tone='positive'] .gengage-chat-review-subject-icon {\n color: color-mix(in srgb, var(--success) 82%, var(--text-primary));\n}\n\n.gengage-chat-review-subject[data-tone='negative'] .gengage-chat-review-subject-icon {\n color: color-mix(in srgb, var(--error) 84%, var(--text-primary));\n}\n\n.gengage-chat-review-subject[data-tone='neutral'] .gengage-chat-review-subject-icon {\n color: var(--text-secondary);\n}\n\n.gengage-chat-review-subject-label {\n color: inherit;\n}\n\n.gengage-chat-review-subject-count {\n color: var(--text-secondary);\n font-size: 12px;\n font-weight: 700;\n}\n\n.gengage-chat-review-detail {\n padding: 12px;\n}\n\n.gengage-chat-review-detail-meta {\n display: flex;\n flex-wrap: wrap;\n gap: 10px 16px;\n align-items: baseline;\n margin-bottom: 10px;\n}\n\n.gengage-chat-review-detail-mentions {\n font-size: 13px;\n font-weight: 700;\n color: var(--_gengage-text-primary);\n}\n\n.gengage-chat-review-detail-positive {\n font-size: 13px;\n font-weight: 700;\n color: var(--_gengage-success-color);\n}\n\n.gengage-chat-review-detail-negative {\n font-size: 13px;\n font-weight: 700;\n color: var(--error);\n}\n\n.gengage-chat-review-snippets {\n display: grid;\n gap: 10px;\n}\n\n.gengage-chat-review-snippet {\n border: 1px solid var(--border-subtle);\n border-left: 3px solid var(--border-default);\n border-radius: var(--radius-control);\n background: var(--surface-card-soft);\n padding: 8px 10px;\n}\n\n.gengage-chat-review-snippet[data-tone='positive'] {\n border-left-color: color-mix(in srgb, var(--success) 40%, var(--border-default));\n}\n\n.gengage-chat-review-snippet[data-tone='negative'] {\n border-left-color: color-mix(in srgb, var(--error) 40%, var(--border-default));\n}\n\n.gengage-chat-review-snippet-text {\n font-size: 13px;\n line-height: 1.4;\n color: var(--_gengage-text-primary);\n}\n\n.gengage-chat-review-snippet-rating {\n margin-top: 4px;\n font-size: 12px;\n font-weight: 700;\n color: var(--text-secondary);\n}\n\n/* -- Favorites Page -------------------------------------------------------- */\n\n.gengage-chat-favorites-page {\n padding: 12px;\n}\n\n.gengage-chat-favorites-empty {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 12px;\n padding: 48px 24px;\n text-align: center;\n color: var(--_gengage-text-secondary);\n}\n\n.gengage-chat-favorites-empty-icon {\n opacity: 0.25;\n color: var(--error);\n}\n\n.gengage-chat-favorites-empty p {\n font-size: 13px;\n margin: 0;\n line-height: 1.5;\n}\n\n.gengage-chat-favorites-list {\n display: flex;\n flex-direction: column;\n gap: 10px;\n}\n\n.gengage-chat-favorites-card {\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 10px 12px;\n border: 1px solid var(--border-default);\n border-radius: var(--radius-control);\n background: var(--surface-card);\n position: relative;\n box-shadow: var(--shadow-1);\n transition:\n opacity 0.2s,\n transform 0.2s,\n box-shadow 0.2s,\n border-color 0.2s;\n}\n\n.gengage-chat-favorites-card:hover {\n border-color: color-mix(in srgb, var(--client-primary) 18%, var(--border-default));\n box-shadow: var(--shadow-1);\n}\n\n.gengage-chat-favorites-card--removing {\n opacity: 0;\n transform: translateX(8px);\n}\n\n.gengage-chat-favorites-card__image {\n width: 56px;\n height: 56px;\n flex-shrink: 0;\n border-radius: var(--radius-control);\n overflow: hidden;\n background: var(--surface-card-muted);\n}\n\n.gengage-chat-favorites-card__image img {\n width: 100%;\n height: 100%;\n object-fit: contain;\n}\n\n.gengage-chat-favorites-card__info {\n flex: 1;\n min-width: 0;\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n\n.gengage-chat-favorites-card__name {\n font-size: 13px;\n font-weight: 500;\n color: var(--text-primary);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.gengage-chat-favorites-card__price {\n font-size: 13px;\n font-weight: 700;\n color: var(--client-primary);\n}\n\n.gengage-chat-favorites-remove {\n flex-shrink: 0;\n width: 28px;\n height: 28px;\n border-radius: var(--radius-pill);\n background: transparent;\n border: none;\n color: var(--error);\n cursor: pointer;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n opacity: 0.7;\n transition:\n opacity 0.15s,\n background 0.15s;\n}\n\n.gengage-chat-favorites-remove:hover {\n opacity: 1;\n background: color-mix(in srgb, var(--error) 10%, white);\n}\n\n/* -- Divider --------------------------------------------------------------- */\n\n.gengage-chat-divider {\n border: none;\n border-top: 1px solid var(--_gengage-border-color);\n margin: 8px 0;\n}\n\n.gengage-chat-divider-wrapper {\n display: flex;\n align-items: center;\n gap: 8px;\n margin: 8px 0;\n}\n\n.gengage-chat-divider-wrapper hr {\n flex: 1;\n border: none;\n border-top: 1px solid var(--_gengage-border-color);\n margin: 0;\n}\n\n.gengage-chat-divider-label {\n font-size: 11px;\n color: var(--text-muted);\n white-space: nowrap;\n}\n\n/* Overlay variant — full-screen modal with backdrop */\n.gengage-chat--overlay .gengage-chat-drawer {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n width: 100%;\n max-width: 480px;\n max-height: 100%;\n margin: 0 auto;\n border-radius: 0;\n animation: gengage-chat-overlay-in 0.24s cubic-bezier(0.2, 0.72, 0.2, 1);\n}\n\n@keyframes gengage-chat-overlay-in {\n from {\n opacity: 0;\n transform: translateY(12px);\n }\n to {\n opacity: 1;\n transform: translateY(0);\n }\n}\n\n/* Panel loading skeleton */\n.gengage-chat-panel-skeleton {\n display: flex;\n flex-direction: column;\n gap: 14px;\n padding: 6px 0;\n min-width: 0;\n max-width: 100%;\n box-sizing: border-box;\n}\n\n.gengage-chat-panel-skeleton-block {\n height: 110px;\n border-radius: 10px;\n background: linear-gradient(90deg, var(--ds-neutral-200) 25%, var(--ds-neutral-100) 50%, var(--ds-neutral-200) 75%);\n background-size: 200% 100%;\n animation: gengage-panel-shimmer 1.4s infinite;\n}\n\n@keyframes gengage-panel-shimmer {\n 0% {\n background-position: 200% 0;\n }\n 100% {\n background-position: -200% 0;\n }\n}\n\n/* Skeleton variant: product details */\n.gengage-chat-panel-skeleton-block--image {\n height: 200px;\n border-radius: 8px;\n margin-bottom: 12px;\n}\n\n.gengage-chat-panel-skeleton-block--text {\n height: 16px;\n border-radius: 4px;\n margin-bottom: 8px;\n}\n\n.gengage-chat-panel-skeleton-block--text:nth-child(3) {\n width: 80%;\n}\n\n.gengage-chat-panel-skeleton-block--text:nth-child(4) {\n width: 60%;\n}\n\n/* Skeleton variant: product grid */\n.gengage-chat-panel-skeleton-grid {\n display: grid;\n grid-template-columns: 1fr 1fr;\n gap: 8px;\n}\n\n.gengage-chat-panel-skeleton-block--card {\n height: 120px;\n border-radius: 8px;\n}\n\n/* Skeleton variant: comparison — panel wrapper (matches .gengage-chat-comparison dimensions) */\n.gengage-chat-panel-skeleton--comparison {\n padding: 0;\n gap: 0;\n}\n\n.gengage-chat-panel-skeleton--comparison .gengage-chat-panel-loading-status {\n margin: 0;\n padding: 16px 16px 0;\n}\n\n.gengage-chat-comparison--skeleton {\n pointer-events: none;\n width: 100%;\n box-sizing: border-box;\n}\n\n/* Shimmer — same animation as .gengage-chat-panel-skeleton-block */\n.gengage-chat-comparison-skeleton-shimmer {\n background: linear-gradient(90deg, var(--ds-neutral-200) 25%, var(--ds-neutral-100) 50%, var(--ds-neutral-200) 75%);\n background-size: 200% 100%;\n animation: gengage-panel-shimmer 1.4s infinite;\n}\n\n.gengage-chat-comparison-skeleton-rec-label {\n height: 12px;\n width: 132px;\n border-radius: 4px;\n margin-bottom: 12px;\n}\n\n.gengage-chat-comparison-skeleton-rec-img {\n width: 80px;\n height: 80px;\n flex-shrink: 0;\n border-radius: 8px;\n}\n\n.gengage-chat-comparison-skeleton-rec-title {\n height: 14px;\n border-radius: 4px;\n margin-bottom: 6px;\n max-width: 100%;\n}\n\n.gengage-chat-comparison-skeleton-rec-title--short {\n width: 72%;\n margin-bottom: 8px;\n}\n\n.gengage-chat-comparison-skeleton-rec-price {\n height: 18px;\n width: 112px;\n border-radius: 4px;\n}\n\n.gengage-chat-comparison-skeleton-hl-label {\n height: 12px;\n width: 96px;\n border-radius: 4px;\n margin-bottom: 6px;\n}\n\n.gengage-chat-comparison-skeleton-hl-list {\n margin: 0;\n padding-left: 18px;\n list-style: none;\n}\n\n.gengage-chat-comparison-skeleton-hl-list li {\n margin-bottom: 8px;\n}\n\n.gengage-chat-comparison-skeleton-hl-list li:last-child {\n margin-bottom: 0;\n}\n\n.gengage-chat-comparison-skeleton-hl-line {\n height: 13px;\n border-radius: 4px;\n width: 100%;\n}\n\n.gengage-chat-comparison-skeleton-hl-line--medium {\n width: 92%;\n}\n\n.gengage-chat-comparison-skeleton-hl-line--short {\n width: 78%;\n}\n\n.gengage-chat-comparison-skeleton-kd-heading {\n height: 14px;\n width: 42%;\n border-radius: 4px;\n margin: 0 0 8px;\n}\n\n.gengage-chat-comparison-skeleton-kd-line {\n height: 13px;\n border-radius: 4px;\n margin-bottom: 8px;\n max-width: 100%;\n}\n\n.gengage-chat-comparison-skeleton-kd-line:last-child {\n margin-bottom: 0;\n}\n\n.gengage-chat-comparison-skeleton-kd-line:nth-child(odd) {\n width: 100%;\n}\n\n.gengage-chat-comparison-skeleton-kd-line:nth-child(even) {\n width: 90%;\n}\n\n.gengage-chat-comparison-special--skeleton {\n display: flex;\n align-items: center;\n min-height: 44px;\n cursor: default;\n}\n\n.gengage-chat-comparison-skeleton-special-line {\n height: 14px;\n width: 56%;\n max-width: 280px;\n border-radius: 4px;\n}\n\n.gengage-chat-comparison-skeleton-table-wrap {\n margin-top: 12px;\n border-radius: 8px;\n border: 1px solid var(--border-default);\n background: var(--surface-card);\n overflow-x: auto;\n -webkit-overflow-scrolling: touch;\n}\n\n.gengage-chat-comparison-skeleton-table-head,\n.gengage-chat-comparison-skeleton-table-row {\n display: grid;\n grid-template-columns: 72px repeat(3, minmax(0, 1fr));\n gap: 8px;\n align-items: start;\n padding: 10px 12px;\n}\n\n.gengage-chat-comparison-skeleton-table-head {\n border-bottom: 1px solid var(--_gengage-surface-muted);\n background: var(--_gengage-surface-muted);\n}\n\n.gengage-chat-comparison-skeleton-table-corner {\n min-height: 92px;\n}\n\n.gengage-chat-comparison-skeleton-table-col {\n display: flex;\n flex-direction: column;\n align-items: center;\n text-align: center;\n gap: 6px;\n min-width: 0;\n}\n\n.gengage-chat-comparison-skeleton-table-th-img {\n width: 60px;\n height: 60px;\n border-radius: 6px;\n flex-shrink: 0;\n}\n\n.gengage-chat-comparison-skeleton-table-th-name {\n height: 11px;\n width: 100%;\n max-width: 100%;\n border-radius: 3px;\n}\n\n.gengage-chat-comparison-skeleton-table-th-price {\n height: 13px;\n width: 64%;\n border-radius: 3px;\n}\n\n.gengage-chat-comparison-skeleton-table-row {\n border-bottom: 1px solid var(--_gengage-surface-muted);\n}\n\n.gengage-chat-comparison-skeleton-table-row:last-child {\n border-bottom: none;\n}\n\n.gengage-chat-comparison-skeleton-table-label {\n height: 12px;\n width: 100%;\n max-width: 56px;\n border-radius: 3px;\n align-self: center;\n}\n\n.gengage-chat-comparison-skeleton-table-cell {\n height: 14px;\n width: 100%;\n border-radius: 3px;\n}\n\n.gengage-chat-panel-skeleton-block--row {\n height: 32px;\n border-radius: 4px;\n margin-bottom: 4px;\n}\n\n/* Panel content: retail-style product cards (e-commerce reference layout) */\n.gengage-chat-panel .gengage-chat-product-card {\n width: 100%;\n max-width: 100%;\n min-width: 0;\n display: flex;\n flex-direction: column;\n border-radius: var(--radius-card);\n border: 1px solid var(--border-default);\n box-shadow: var(--shadow-1);\n overflow: hidden;\n padding: 0;\n background: var(--surface-card);\n}\n\n.gengage-chat-panel .gengage-chat-product-card:hover {\n transform: translateY(-2px);\n box-shadow: var(--shadow-2);\n border-color: var(--border-strong);\n}\n\n.gengage-chat-panel .gengage-chat-product-card-img-actions {\n top: 10px;\n right: 10px;\n gap: 6px;\n}\n\n.gengage-chat-panel .gengage-chat-product-card-img-actions .gengage-chat-favorite-btn,\n.gengage-chat-panel .gengage-chat-product-card-img-actions .gengage-chat-find-similar-pill {\n width: 28px;\n height: 28px;\n border-radius: 8px;\n}\n\n.gengage-chat-panel .gengage-chat-product-card-discount-badge {\n top: 10px;\n left: 10px;\n min-width: 36px;\n height: 36px;\n padding: 0 5px;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n border-radius: var(--radius-pill);\n font-size: 11px;\n font-weight: 700;\n background: var(--client-primary);\n color: var(--client-on-primary);\n box-shadow: var(--shadow-1);\n}\n\n.gengage-chat-panel .gengage-chat-product-card-brand {\n display: none;\n}\n\n.gengage-chat-panel .gengage-chat-product-grid {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(176px, 1fr));\n overflow-x: visible;\n gap: 12px;\n min-height: auto;\n padding: 4px 0;\n}\n\n/*\n * Mobile panel search results: panel grid with `minmax(176px)` collapsed to single column on narrow screens.\n * `--mobile` forces two equal columns; no horizontal scroll, vertical list.\n */\n.gengage-chat-root--mobile .gengage-chat-panel .gengage-chat-product-grid.gengage-chat-product-grid--mobile {\n display: grid;\n grid-template-columns: repeat(2, minmax(0, 1fr));\n overflow-x: visible;\n gap: 8px;\n padding: 6px 0 10px;\n min-height: auto;\n}\n\n.gengage-chat-root--mobile\n .gengage-chat-panel\n .gengage-chat-product-grid.gengage-chat-product-grid--mobile\n .gengage-chat-product-card {\n width: 100%;\n min-width: 0;\n max-width: none;\n box-sizing: border-box;\n}\n\n.gengage-chat-panel .gengage-chat-product-card-img-wrapper {\n position: relative;\n width: 100%;\n aspect-ratio: 1;\n flex-shrink: 0;\n background: var(--surface-card-muted);\n border-radius: var(--radius-card) var(--radius-card) 0 0;\n overflow: hidden;\n}\n\n.gengage-chat-panel .gengage-chat-product-card-img {\n display: block;\n width: 100%;\n height: 100%;\n min-height: 0;\n border-radius: inherit;\n object-fit: cover;\n object-position: center;\n background: transparent;\n padding: 0;\n box-sizing: border-box;\n}\n\n.gengage-chat-panel .gengage-chat-product-card-body {\n padding: 12px 14px 10px;\n text-align: left;\n gap: 8px;\n}\n\n.gengage-chat-panel .gengage-chat-product-card-meta-row {\n align-items: center;\n margin-bottom: 0;\n min-height: 38px;\n}\n\n.gengage-chat-panel .gengage-chat-product-card-price,\n.gengage-chat-panel .gengage-chat-product-card-price-block {\n justify-content: flex-start;\n font-size: 15px;\n font-weight: 600;\n color: var(--text-primary);\n}\n\n.gengage-chat-panel .gengage-chat-product-card-current-price {\n color: var(--client-primary);\n font-weight: 600;\n font-size: 15px;\n}\n\n.gengage-chat-panel .gengage-chat-product-card-original-price {\n font-size: 12px;\n font-weight: 400;\n color: var(--text-muted);\n}\n\n.gengage-chat-panel .gengage-chat-product-card-rating-compact {\n font-size: 11px;\n font-weight: 700;\n}\n\n.gengage-chat-panel .gengage-chat-product-card-rating-compact-star {\n color: color-mix(in srgb, var(--warning) 58%, var(--text-primary));\n}\n\n.gengage-chat-panel .gengage-chat-product-card-name {\n min-height: 0;\n margin: 0 0 2px;\n font-size: 14px;\n font-weight: 600;\n line-height: 1.35;\n color: var(--text-primary);\n -webkit-line-clamp: 2;\n line-clamp: 2;\n}\n\n/* Primary listing CTA lives in the card footer; action-only cards still use outline CTA */\n.gengage-chat-panel .gengage-chat-product-card-buy-footer {\n position: relative;\n margin-top: auto;\n padding: 0 14px 16px;\n flex-shrink: 0;\n display: flex;\n min-width: 0;\n}\n\n.gengage-chat-panel .gengage-chat-product-card-buy-trigger {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 100%;\n min-width: 0;\n margin: 0;\n min-height: 42px;\n padding: 9px 14px;\n box-sizing: border-box;\n border: 1.5px solid var(--client-primary);\n border-radius: 12px;\n background: var(--surface-card);\n color: var(--client-primary);\n font-weight: 600;\n font-size: 13px;\n font-family: inherit;\n line-height: 1.2;\n white-space: nowrap;\n cursor: pointer;\n transition:\n background 0.15s ease,\n box-shadow 0.15s ease;\n}\n\n.gengage-chat-panel .gengage-chat-product-card-buy-trigger:hover {\n background: color-mix(in srgb, var(--client-primary) 6%, white);\n box-shadow: var(--shadow-1);\n}\n\n/* Panel: action-only cards still use full-width outline CTA */\n.gengage-chat-panel .gengage-chat-product-card .gengage-chat-product-card-cta {\n display: flex;\n align-items: center;\n justify-content: center;\n margin: 0 14px 16px;\n padding: 9px 14px;\n width: calc(100% - 28px);\n min-width: 0;\n border: 1.5px solid var(--client-primary);\n border-radius: 12px;\n background: var(--surface-card);\n color: var(--client-primary);\n font-weight: 600;\n font-size: 13px;\n min-height: 42px;\n box-sizing: border-box;\n}\n\n.gengage-chat-panel .gengage-chat-product-card .gengage-chat-product-card-cta:hover {\n background: color-mix(in srgb, var(--client-primary) 6%, white);\n}\n\n/* Selected card highlight state */\n.gengage-chat-product-card--selected {\n border-color: var(--client-primary);\n background: color-mix(in srgb, var(--client-primary) 8%, transparent);\n box-shadow: 0 0 0 1px var(--client-primary);\n}\n\n/* Panel content layout — prevent empty space below grid */\n.gengage-chat-panel--visible {\n min-height: 0;\n}\n\n.gengage-chat-panel--visible > * {\n flex-shrink: 0;\n}\n\n/* \"Load more\" button in product grids */\n.gengage-chat-load-more-btn {\n display: block;\n margin: 12px auto;\n padding: 8px 24px;\n border: 1px solid var(--border-default);\n border-radius: 8px;\n background: transparent;\n color: var(--client-primary);\n cursor: pointer;\n font-size: 14px;\n}\n\n.gengage-chat-load-more-btn:hover {\n background: var(--surface-card-muted);\n}\n\n.gengage-chat-product-details-panel {\n display: flex;\n flex-direction: column;\n gap: 12px;\n width: 100%;\n background: var(--surface-card);\n border: 1px solid var(--border-default);\n border-radius: 16px;\n padding: 14px;\n box-sizing: border-box;\n}\n\n.gengage-chat-product-details-media {\n border: 1px solid var(--border-subtle);\n border-radius: 12px;\n background: var(--surface-card-muted);\n padding: 12px;\n display: flex;\n flex-direction: column;\n align-items: center;\n}\n\n.gengage-chat-product-details-img {\n width: 100%;\n height: auto;\n max-height: 300px;\n object-fit: contain;\n border-radius: 8px;\n}\n\n.gengage-chat-product-details-content {\n display: flex;\n flex-direction: column;\n gap: 8px;\n min-width: 0;\n}\n\n.gengage-chat-product-details-title {\n margin: 0;\n font-size: 17px;\n font-weight: 700;\n line-height: 1.3;\n color: var(--text-primary);\n}\n\n.gengage-chat-product-details-rating {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n width: fit-content;\n padding: 3px 8px;\n border-radius: 999px;\n background: color-mix(in srgb, var(--warning) 10%, white);\n color: color-mix(in srgb, var(--warning) 72%, var(--text-primary));\n font-size: 13px;\n font-weight: 700;\n}\n\n.gengage-chat-product-details-review-count {\n color: color-mix(in srgb, var(--warning) 84%, var(--text-primary));\n font-weight: 600;\n}\n\n.gengage-chat-product-details-price {\n display: flex;\n align-items: baseline;\n gap: 6px;\n}\n\n.gengage-chat-product-details-original-price {\n font-size: 14px;\n color: var(--text-secondary);\n text-decoration: line-through;\n}\n\n.gengage-chat-product-details-current-price {\n font-size: 22px;\n font-weight: 800;\n line-height: 1.1;\n color: var(--text-primary);\n}\n\n.gengage-chat-product-details-stock {\n width: fit-content;\n padding: 4px 10px;\n border-radius: 999px;\n font-size: 12px;\n font-weight: 700;\n}\n\n.gengage-chat-product-details-stock.is-in-stock {\n background: color-mix(in srgb, var(--success) 12%, white);\n color: color-mix(in srgb, var(--success) 82%, var(--text-primary));\n}\n\n.gengage-chat-product-details-stock.is-out-of-stock {\n background: color-mix(in srgb, var(--error) 10%, white);\n color: color-mix(in srgb, var(--error) 82%, var(--text-primary));\n}\n\n.gengage-chat-product-details-actions {\n display: flex;\n align-items: center;\n gap: 8px;\n flex-wrap: wrap;\n margin-top: 4px;\n}\n\n.gengage-chat-product-details-cta {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n min-height: 40px;\n padding: 0 20px;\n border-radius: var(--radius-control, 12px);\n border: 1px solid var(--ds-button-primary-border);\n text-decoration: none;\n background: var(--ds-button-primary-bg);\n color: var(--ds-button-primary-fg);\n font-size: 14px;\n font-weight: 700;\n cursor: pointer;\n transition:\n filter 0.14s ease,\n transform 0.12s ease;\n}\n\n.gengage-chat-product-details-cta:hover {\n background: var(--ds-button-primary-bg-hover);\n}\n\n.gengage-chat-product-details-cta:active {\n background: var(--ds-button-primary-bg-active);\n}\n\n.gengage-chat-product-details-atc {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 6px;\n padding: 10px 20px;\n border: none;\n border-radius: var(--radius-control);\n background: var(--_gengage-success-color);\n color: var(--client-on-primary);\n font-size: 14px;\n font-weight: 700;\n font-family: inherit;\n cursor: pointer;\n transition:\n filter 0.12s ease,\n transform 0.12s ease;\n}\n\n.gengage-chat-product-details-atc:hover {\n filter: brightness(1.08);\n transform: translateY(-1px);\n}\n\n.gengage-chat-product-details-atc:active {\n transform: translateY(1px);\n}\n\n/* (Find Similar is now a hover pill on the image — see .gengage-chat-find-similar-pill) */\n\n/* Product detail tabs (Product Info / Tech Specs) */\n.gengage-chat-product-detail-tabs {\n margin-top: 4px;\n border-top: 1px solid var(--border-default);\n padding-top: 12px;\n}\n\n.gengage-chat-product-detail-tab-bar {\n display: flex;\n gap: 6px;\n border-bottom: 1px solid var(--border-default);\n margin-bottom: 12px;\n padding-bottom: 8px;\n}\n\n.gengage-chat-product-detail-tab {\n flex: 1;\n padding: 8px 12px;\n border: 1px solid var(--border-default);\n background: var(--surface-card);\n color: var(--text-secondary);\n font-size: 13px;\n font-weight: 600;\n font-family: inherit;\n cursor: pointer;\n transition:\n color 0.15s,\n border-color 0.15s,\n background 0.15s;\n}\n\n.gengage-chat-product-detail-tab:hover {\n color: var(--text-primary);\n background: color-mix(in srgb, var(--surface-card-soft) 82%, transparent);\n}\n\n.gengage-chat-product-detail-tab--active {\n color: var(--client-primary);\n border-color: color-mix(in srgb, var(--client-primary) 26%, var(--border-default));\n background: color-mix(in srgb, var(--client-primary) 8%, white);\n}\n\n.gengage-chat-product-detail-tab-panel {\n font-size: 13px;\n line-height: 1.6;\n color: var(--text-secondary);\n}\n\n.gengage-chat-product-description {\n display: flex;\n flex-direction: column;\n gap: 10px;\n color: var(--text-primary);\n}\n\n.gengage-chat-product-description p,\n.gengage-chat-product-description h2,\n.gengage-chat-product-description h3,\n.gengage-chat-product-description h4,\n.gengage-chat-product-description ul,\n.gengage-chat-product-description ol {\n margin: 0;\n}\n\n.gengage-chat-product-description h2,\n.gengage-chat-product-description h3,\n.gengage-chat-product-description h4 {\n color: var(--text-primary);\n font-size: 15px;\n font-weight: 800;\n line-height: 1.35;\n}\n\n.gengage-chat-product-description ul,\n.gengage-chat-product-description ol {\n padding-left: 20px;\n}\n\n.gengage-chat-product-description li + li {\n margin-top: 6px;\n}\n\n.gengage-chat-product-description strong,\n.gengage-chat-product-description b {\n font-weight: 800;\n}\n\n.gengage-chat-product-specs-table {\n width: 100%;\n border-collapse: collapse;\n font-size: 13px;\n}\n\n.gengage-chat-product-specs-table tr:nth-child(even) {\n background: var(--surface-card-muted);\n}\n\n.gengage-chat-product-specs-key {\n padding: 6px 10px;\n font-weight: 600;\n color: var(--text-secondary);\n white-space: nowrap;\n border-bottom: 1px solid var(--border-subtle);\n width: 40%;\n}\n\n.gengage-chat-product-specs-value {\n padding: 6px 10px;\n color: var(--text-primary);\n border-bottom: 1px solid var(--border-subtle);\n}\n\n/* Product card ATC stepper */\n.gengage-chat-product-card-atc {\n position: absolute;\n bottom: 42px;\n right: 6px;\n opacity: 0;\n transition: opacity 0.15s ease;\n z-index: 1;\n}\n\n.gengage-chat-product-card:hover .gengage-chat-product-card-atc {\n opacity: 1;\n}\n\n/* Touch devices: always show ATC stepper since :hover doesn't fire */\n@media (hover: none), (pointer: coarse) {\n /* Edge-to-edge strip at card bottom */\n .gengage-chat-product-card-atc {\n opacity: 1;\n position: relative;\n bottom: auto;\n right: auto;\n margin: 0;\n display: flex;\n width: 100%;\n }\n}\n\n/* Hidden state — slide out to right (desktop) or down (mobile) */\n.gengage-chat-drawer.gengage-chat-drawer--hidden {\n transform: translateX(100%);\n opacity: 0;\n pointer-events: none;\n box-shadow: none;\n visibility: hidden;\n}\n\n/* Mobile hidden: slide down */\n.gengage-chat-root--mobile .gengage-chat-drawer.gengage-chat-drawer--hidden {\n transform: translateY(100%);\n opacity: 0;\n pointer-events: none;\n box-shadow: none;\n visibility: hidden;\n}\n\n/* Mobile and medium breakpoints */\n@media (max-width: 900px) {\n .gengage-chat-drawer--with-panel .gengage-chat-panel--visible {\n width: clamp(280px, calc(100vw - var(--_gengage-chat-conversation-width)), 560px);\n max-width: 560px;\n }\n}\n\n/* Attachment preview strip */\n.gengage-chat-attachment-preview {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 6px 10px;\n background: var(--surface-card-muted);\n border: 1px solid var(--border-default);\n border-radius: 8px;\n width: 100%;\n box-sizing: border-box;\n}\n\n.gengage-chat-attachment-preview--hidden {\n display: none;\n}\n\n.gengage-chat-attachment-preview-thumb {\n width: 40px;\n height: 40px;\n border-radius: 4px;\n object-fit: cover;\n flex-shrink: 0;\n}\n\n.gengage-chat-attachment-name {\n flex: 1;\n font-size: 12px;\n color: var(--text-secondary);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.gengage-chat-attachment-remove {\n background: none;\n border: none;\n cursor: pointer;\n font-size: 18px;\n color: var(--text-secondary);\n padding: 0 4px;\n line-height: 1;\n flex-shrink: 0;\n}\n\n.gengage-chat-attachment-remove:hover {\n color: var(--error);\n}\n\n/* Attach image button */\n.gengage-chat-attach-btn {\n background: var(--surface-card-soft);\n border: 1px solid var(--border-default);\n cursor: pointer;\n width: 40px;\n height: 40px;\n padding: 0;\n line-height: 1;\n flex-shrink: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n border-radius: 12px;\n color: var(--text-primary);\n transition:\n color 0.15s,\n background 0.15s,\n border-color 0.15s;\n}\n\n.gengage-chat-attach-btn:hover {\n color: var(--client-primary);\n background: var(--client-primary-subtle);\n border-color: color-mix(in srgb, var(--client-primary) 18%, white);\n}\n\n.gengage-chat-attach-btn svg {\n width: 20px;\n height: 20px;\n}\n\n.gengage-chat-attach-wrap {\n position: relative;\n display: flex;\n align-items: center;\n flex-shrink: 0;\n}\n\n.gengage-chat-attach-menu {\n position: absolute;\n bottom: calc(100% + 8px);\n left: 0;\n z-index: 50;\n min-width: 196px;\n padding: 4px 0;\n margin: 0;\n list-style: none;\n background: var(--surface-elevated);\n border: 1px solid var(--border-default);\n border-radius: var(--radius-card);\n box-shadow: var(--shadow-3);\n}\n\n.gengage-chat-attach-menu[hidden] {\n display: none !important;\n}\n\n.gengage-chat-attach-menu-item {\n display: flex;\n align-items: center;\n gap: 10px;\n width: 100%;\n padding: 10px 14px;\n border: none;\n background: transparent;\n cursor: pointer;\n font-size: 14px;\n font-weight: 500;\n color: var(--text-primary);\n font-family: inherit;\n text-align: left;\n transition: background 0.12s ease;\n}\n\n.gengage-chat-attach-menu-item:hover {\n background: var(--surface-card-muted);\n}\n\n.gengage-chat-attach-menu-icon {\n display: flex;\n align-items: center;\n justify-content: center;\n color: var(--text-secondary);\n flex-shrink: 0;\n}\n\n.gengage-chat-attach-menu-label {\n flex: 1;\n min-width: 0;\n}\n\n.gengage-chat-attach-menu-sep {\n height: 1px;\n margin: 4px 10px;\n background: var(--border-default);\n}\n\n/* Voice input mic button */\n.gengage-chat-mic-btn {\n background: transparent;\n border: 1px solid transparent;\n cursor: pointer;\n width: 32px;\n height: 32px;\n padding: 0;\n line-height: 1;\n flex-shrink: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n border-radius: var(--radius-control);\n color: var(--text-secondary);\n transition:\n color 0.15s,\n background 0.15s;\n}\n\n.gengage-chat-mic-btn:hover {\n color: var(--text-primary);\n background: var(--surface-card-muted);\n border-color: var(--border-default);\n}\n\n.gengage-chat-mic-btn svg {\n width: 20px;\n height: 20px;\n}\n\n.gengage-chat-mic-btn--active {\n color: var(--error);\n animation: gengage-mic-pulse 1.2s ease-in-out infinite;\n}\n\n.gengage-chat-mic-btn--active:hover {\n color: color-mix(in srgb, var(--error) 84%, black);\n}\n\n@keyframes gengage-mic-pulse {\n 0%,\n 100% {\n transform: scale(1);\n opacity: 1;\n }\n 50% {\n transform: scale(1.15);\n opacity: 0.8;\n }\n}\n\n/* Drag-over highlight on input area */\n.gengage-chat-input-area--dragover {\n outline: 2px dashed var(--client-primary);\n outline-offset: -2px;\n background: color-mix(in srgb, var(--client-primary) 8%, white);\n}\n\n/* Thumbnail in user bubble */\n.gengage-chat-attachment-thumb {\n width: 120px;\n max-width: 100%;\n height: auto;\n border-radius: 8px;\n margin-bottom: 6px;\n display: block;\n}\n\n/* Mobile layout — driven by JS .gengage-chat-root--mobile class so the breakpoint\n is configurable via config.mobileBreakpoint (default 768px). */\n\n.gengage-chat-root--mobile .gengage-chat-header-btn,\n.gengage-chat-root--mobile .gengage-chat-close {\n width: 44px;\n height: 44px;\n border-radius: 12px;\n}\n\n.gengage-chat-root--mobile .gengage-chat-attach-btn,\n.gengage-chat-root--mobile .gengage-chat-mic-btn,\n.gengage-chat-root--mobile .gengage-chat-send {\n width: 44px;\n height: 44px;\n min-width: 44px;\n}\n\n.gengage-chat-root--mobile .gengage-chat-attach-btn svg,\n.gengage-chat-root--mobile .gengage-chat-mic-btn svg {\n width: 22px;\n height: 22px;\n}\n\n.gengage-chat-root--mobile .gengage-chat-panel-divider {\n display: flex;\n width: 100%;\n height: 20px;\n align-items: center;\n justify-content: center;\n cursor: ns-resize;\n background: transparent;\n touch-action: pan-y;\n position: relative;\n overflow: visible;\n}\n\n.gengage-chat-root--mobile .gengage-chat-panel-divider--hidden {\n display: none;\n}\n\n.gengage-chat-root--mobile .gengage-chat-drawer--with-panel {\n left: 0;\n right: 0;\n width: 100%;\n border-radius: 0;\n}\n\n/* Panel becomes a full-screen side overlay (slides in from the right) */\n.gengage-chat-root--mobile .gengage-chat-panel {\n display: flex;\n flex-direction: column;\n position: absolute;\n inset: 0;\n z-index: 10;\n width: 100%;\n min-width: 0;\n max-width: none;\n max-height: none;\n border: none;\n box-shadow: none;\n border-radius: 0;\n background: var(--gengage-chat-panel-bg, var(--surface-card));\n overflow-y: auto;\n /* overflow-x intentionally not set: inner elements (comparison table) must be able to scroll horizontally */\n transform: translateX(100%);\n transition: transform 0.32s cubic-bezier(0.4, 0, 0.2, 1);\n pointer-events: none;\n padding: 0 12px 16px;\n}\n\n.gengage-chat-root--mobile .gengage-chat-panel--visible {\n transform: translateX(0);\n pointer-events: auto;\n}\n\n/* Divider is not needed for overlay panel on mobile */\n.gengage-chat-root--mobile .gengage-chat-panel-divider,\n.gengage-chat-root--mobile .gengage-chat-drawer--with-panel .gengage-chat-panel-divider {\n display: none;\n}\n\n/* Drawer layout stays unaffected by panel on mobile */\n.gengage-chat-root--mobile .gengage-chat-drawer--with-panel .gengage-chat-body {\n flex-direction: row;\n}\n\n/* Conversation always takes full width on mobile */\n.gengage-chat-root--mobile .gengage-chat-drawer--with-panel .gengage-chat-conversation {\n width: 100%;\n flex: 1;\n border-left: none;\n}\n\n.gengage-chat-root--mobile .gengage-chat-product-details-share {\n width: 44px;\n height: 44px;\n}\n\n.gengage-chat-root--mobile .gengage-chat-panel .gengage-chat-product-card-buy-footer {\n width: 100%;\n max-width: 100%;\n box-sizing: border-box;\n}\n\n.gengage-chat-root--mobile .gengage-chat-panel .gengage-chat-product-card-cta {\n width: 100%;\n max-width: 100%;\n box-sizing: border-box;\n}\n\n.gengage-chat-root--mobile .gengage-chat-product-details-panel {\n padding: 10px;\n gap: 8px;\n}\n\n.gengage-chat-root--mobile .gengage-chat-product-details-media {\n min-height: 0;\n padding: 8px;\n}\n\n.gengage-chat-root--mobile .gengage-chat-product-details-content {\n gap: 6px;\n}\n\n.gengage-chat-root--mobile .gengage-chat-product-details-title {\n font-size: 14px;\n line-height: 1.25;\n}\n\n.gengage-chat-root--mobile .gengage-chat-product-details-rating {\n padding: 2px 6px;\n font-size: 12px;\n}\n\n.gengage-chat-root--mobile .gengage-chat-product-details-original-price {\n font-size: 12px;\n}\n\n.gengage-chat-root--mobile .gengage-chat-product-details-current-price {\n font-size: 18px;\n}\n\n.gengage-chat-root--mobile .gengage-chat-product-details-actions {\n margin-top: 2px;\n gap: 6px;\n}\n\n.gengage-chat-root--mobile .gengage-chat-product-details-cta {\n min-height: 38px;\n padding: 0 14px;\n font-size: 13px;\n}\n\n/* Mobile product grid: 2 columns (--mobile) or horizontal strip (legacy, when --mobile absent) */\n.gengage-chat-root--mobile .gengage-chat-product-grid:not(.gengage-chat-product-grid--mobile) {\n scroll-snap-type: x mandatory;\n}\n\n.gengage-chat-root--mobile .gengage-chat-product-grid.gengage-chat-product-grid--mobile {\n scroll-snap-type: none;\n}\n\n.gengage-chat-root--mobile .gengage-chat-product-card {\n width: 280px;\n min-width: 280px;\n max-width: 100%;\n box-sizing: border-box;\n scroll-snap-align: center;\n}\n\n.gengage-chat-root--mobile .gengage-chat-product-grid--mobile .gengage-chat-product-card {\n width: 100%;\n min-width: 0;\n max-width: none;\n scroll-snap-align: start;\n}\n\n/* Horizontal product strip + any mobile card: vertically center image in its frame */\n.gengage-chat-root--mobile .gengage-chat-product-card-img-wrapper {\n display: flex;\n align-items: center;\n justify-content: center;\n min-height: 0;\n}\n\n.gengage-chat-root--mobile .gengage-chat-product-card-img {\n width: auto;\n max-width: 100%;\n height: auto;\n max-height: 180px;\n min-height: 0;\n object-fit: contain;\n object-position: center;\n display: block;\n}\n\n.gengage-chat-root--mobile .gengage-chat-product-card-discount-badge {\n padding: 2px 6px;\n font-size: 10px;\n font-weight: 700;\n line-height: 1.2;\n}\n\n.gengage-chat-root--mobile .gengage-chat-drawer {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n width: 100%;\n max-height: 100%;\n border-radius: 0;\n}\n\n.gengage-chat-root--mobile .gengage-chat-bubble {\n max-width: 100%;\n}\n\n/* Mobile panel cards: center product image vertically in the media band (avoid “stuck to top”) */\n.gengage-chat-root--mobile .gengage-chat-panel .gengage-chat-product-card-img-wrapper {\n display: flex;\n align-items: center;\n justify-content: center;\n aspect-ratio: 1;\n min-height: 0;\n}\n\n.gengage-chat-root--mobile .gengage-chat-panel .gengage-chat-product-card-img {\n width: auto;\n max-width: 100%;\n height: auto;\n max-height: min(200px, 72%);\n min-height: 0;\n object-fit: contain;\n object-position: center;\n}\n\n/* Product card overlay: keep fav + find-similar square (global mobile rule sets fav to 44px for header/details) */\n.gengage-chat-root--mobile .gengage-chat-product-card-img-actions .gengage-chat-favorite-btn,\n.gengage-chat-root--mobile .gengage-chat-product-card-img-actions .gengage-chat-find-similar-pill {\n width: 28px;\n height: 28px;\n min-width: 28px;\n min-height: 28px;\n flex-shrink: 0;\n}\n\n.gengage-chat-root--mobile .gengage-chat-panel .gengage-chat-product-card-discount-badge {\n min-width: 28px;\n height: 28px;\n padding: 0 6px;\n font-size: 10px;\n font-weight: 700;\n}\n\n.gengage-chat-root--mobile .gengage-chat-panel .gengage-chat-product-card-buy-footer {\n padding: 0 12px 14px;\n}\n\n.gengage-chat-root--mobile .gengage-chat-panel .gengage-chat-product-card-buy-trigger {\n max-width: 100%;\n min-width: 0;\n box-sizing: border-box;\n white-space: normal;\n text-align: center;\n line-height: 1.25;\n hyphens: auto;\n}\n\n.gengage-chat-root--mobile .gengage-chat-panel .gengage-chat-product-card .gengage-chat-product-card-cta {\n margin-left: 12px;\n margin-right: 12px;\n width: calc(100% - 24px);\n max-width: calc(100% - 24px);\n min-width: 0;\n box-sizing: border-box;\n white-space: normal;\n}\n\n.gengage-chat-root--mobile .gengage-chat-panel .gengage-chat-product-card-rating-compact {\n padding: 2px 6px;\n font-size: 10px;\n gap: 3px;\n}\n\n.gengage-chat-root--mobile .gengage-chat-panel .gengage-chat-product-card-stock {\n font-size: 10px;\n padding: 2px 5px;\n}\n\n/* GAP-012: Hide native scrollbar on mobile */\n.gengage-chat-root--mobile .gengage-chat-messages {\n scrollbar-width: none;\n /* GAP-013: Improve mobile chat padding */\n padding: 12px 16px;\n}\n\n.gengage-chat-root--mobile .gengage-chat-messages::-webkit-scrollbar {\n display: none;\n}\n\n/* GAP-011: Fix drawer header white sliver on mobile */\n.gengage-chat-root--mobile .gengage-chat-drawer {\n border-top: none;\n}\n\n.gengage-chat-root--mobile .gengage-chat-header {\n border-radius: 16px 16px 0 0;\n /* Extra top padding to give the drag handle pill room at the very top */\n padding-top: 22px;\n padding-bottom: 8px;\n min-height: 72px;\n}\n\n.gengage-chat-root--mobile.gengage-chat-root--mobile-full .gengage-chat-header {\n border-radius: 0;\n}\n\n/* GAP-036: Fix chat content cut off at panel boundary */\n.gengage-chat-root--mobile .gengage-chat-conversation {\n overflow-y: auto;\n -webkit-overflow-scrolling: touch;\n min-height: 0;\n}\n\n/* GAP-047: Mobile product image — compact frame, still readable */\n.gengage-chat-root--mobile .gengage-chat-product-details-img {\n min-height: 0;\n max-height: 200px;\n object-fit: contain;\n}\n\n/* GAP-063: Fix panel action buttons crowding */\n.gengage-chat-root--mobile .gengage-chat-product-details-actions {\n flex-wrap: wrap;\n gap: 8px;\n}\n\n/* GAP-065: Fix badge overlap on mobile */\n.gengage-chat-root--mobile .gengage-chat-product-details-promos {\n flex-wrap: wrap;\n gap: 4px;\n}\n\n/* GAP-086: Fix placeholder text too long on mobile */\n.gengage-chat-root--mobile .gengage-chat-input::placeholder {\n font-size: 13px;\n}\n\n/* KVKK disclaimer banner */\n.gengage-chat-kvkk-banner {\n display: flex;\n align-items: flex-start;\n gap: 8px;\n padding: 10px 14px;\n background: color-mix(in srgb, var(--warning) 10%, white);\n border-bottom: 1px solid color-mix(in srgb, var(--warning) 24%, var(--border-default));\n font-size: 12px;\n line-height: 1.5;\n color: color-mix(in srgb, var(--warning) 72%, var(--text-primary));\n}\n\n.gengage-chat-kvkk-content {\n flex: 1;\n min-width: 0;\n}\n\n.gengage-chat-kvkk-content a {\n color: color-mix(in srgb, var(--warning) 82%, var(--text-primary));\n text-decoration: underline;\n}\n\n.gengage-chat-kvkk-dismiss {\n flex-shrink: 0;\n width: 20px;\n height: 20px;\n border: none;\n background: transparent;\n color: color-mix(in srgb, var(--warning) 84%, var(--text-primary));\n font-size: 18px;\n cursor: pointer;\n padding: 0;\n line-height: 1;\n}\n\n/* Comparison table */\n.gengage-chat-comparison {\n display: flex;\n flex-direction: column;\n gap: 12px;\n padding: 16px;\n box-sizing: border-box;\n}\n\n.gengage-chat-comparison-heading {\n font-size: 14px;\n font-weight: 700;\n letter-spacing: 0.05em;\n color: var(--text-primary);\n margin: 0 0 16px;\n}\n\n.gengage-chat-comparison-recommended {\n border: 1px solid color-mix(in srgb, var(--client-primary) 18%, var(--border-default));\n border-radius: var(--radius-card);\n padding: 16px;\n background: var(--surface-card);\n box-shadow: var(--shadow-1);\n}\n\n.gengage-chat-comparison-recommended-label {\n font-size: 12px;\n font-weight: 700;\n color: var(--client-primary);\n text-transform: uppercase;\n letter-spacing: 0.04em;\n margin-bottom: 12px;\n}\n\n.gengage-chat-comparison-recommended-body {\n display: grid;\n grid-template-columns: minmax(108px, 148px) minmax(0, 1fr);\n gap: 16px;\n align-items: flex-start;\n}\n\n.gengage-chat-comparison-recommended-media {\n display: flex;\n align-items: center;\n justify-content: center;\n aspect-ratio: 1 / 1;\n border-radius: 14px;\n border: 1px solid var(--border-subtle);\n background: var(--surface-card);\n overflow: hidden;\n}\n\n.gengage-chat-comparison-recommended-body img,\n.gengage-chat-comparison-recommended-placeholder {\n width: 100%;\n height: 100%;\n object-fit: contain;\n padding: 8px;\n box-sizing: border-box;\n}\n\n.gengage-chat-comparison-recommended-placeholder {\n background: var(--surface-card-muted);\n}\n\n.gengage-chat-comparison-recommended-info {\n flex: 1;\n min-width: 0;\n display: flex;\n flex-direction: column;\n gap: 10px;\n}\n\n.gengage-chat-comparison-recommended-title {\n font-size: 24px;\n line-height: 1.2;\n font-weight: 800;\n color: var(--text-primary);\n margin: 0;\n}\n\n.gengage-chat-comparison-recommended-meta {\n display: flex;\n align-items: center;\n gap: 10px;\n flex-wrap: wrap;\n}\n\n.gengage-chat-comparison-recommended-rating {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 6px 10px;\n border-radius: 999px;\n border: 1px solid var(--border-subtle);\n background: var(--surface-card-muted);\n color: color-mix(in srgb, var(--warning) 78%, var(--text-primary));\n font-size: 13px;\n font-weight: 700;\n}\n\n.gengage-chat-comparison-recommended-rating-value {\n color: var(--text-primary);\n}\n\n.gengage-chat-comparison-recommended-price {\n font-size: 20px;\n font-weight: 700;\n color: var(--text-primary);\n}\n\n.gengage-chat-comparison-highlights {\n padding-top: 12px;\n border-top: 1px solid var(--border-subtle);\n}\n\n.gengage-chat-comparison-highlights-label {\n font-size: 12px;\n font-weight: 600;\n color: var(--text-secondary);\n margin-bottom: 6px;\n}\n\n.gengage-chat-comparison-highlights ul {\n margin: 0;\n padding-left: 18px;\n font-size: 14px;\n color: var(--text-primary);\n line-height: 1.55;\n}\n\n.gengage-chat-comparison-special {\n background: color-mix(in srgb, var(--warning) 10%, white);\n border: 1px solid color-mix(in srgb, var(--warning) 24%, var(--border-default));\n border-radius: var(--radius-card);\n padding: 12px 16px;\n margin-bottom: 12px;\n font-size: 13px;\n color: color-mix(in srgb, var(--warning) 72%, var(--text-primary));\n}\n\n.gengage-chat-comparison-special summary {\n cursor: pointer;\n font-weight: 600;\n}\n\n.gengage-chat-comparison-special ul {\n margin: 8px 0 0;\n padding-left: 18px;\n line-height: 1.6;\n}\n\n.gengage-chat-comparison-table {\n width: 100%;\n border-collapse: collapse;\n font-size: 13px;\n background: var(--surface-card);\n}\n\n.gengage-chat-comparison-table th,\n.gengage-chat-comparison-table td {\n padding: 10px 12px;\n text-align: center;\n border-bottom: 1px solid var(--border-subtle);\n vertical-align: top;\n}\n\n.gengage-chat-comparison-table th {\n font-weight: 600;\n font-size: 12px;\n color: var(--text-secondary);\n}\n\n/*\n * display:grid must not be used on th — table-cell breaks and columns stack vertically.\n * Grid is only on inner wrappers; prices stay horizontally aligned across the row.\n */\n.gengage-chat-comparison-table thead th:not(:first-child) {\n vertical-align: top;\n}\n\n.gengage-chat-comparison-table-header-cell {\n display: grid;\n grid-template-rows: auto 1fr auto;\n align-items: start;\n justify-items: center;\n gap: 6px;\n min-height: 0;\n height: 100%;\n box-sizing: border-box;\n}\n\n.gengage-chat-comparison-table-header-cell--clickable {\n cursor: pointer;\n border-radius: var(--radius-control);\n padding: 6px;\n transition:\n background 0.16s ease,\n box-shadow 0.16s ease,\n transform 0.16s ease;\n}\n\n.gengage-chat-comparison-table-header-cell--clickable:hover {\n background: var(--surface-card-soft);\n box-shadow: var(--shadow-1);\n transform: translateY(-1px);\n}\n\n.gengage-chat-comparison-table-header-cell--clickable:focus-visible {\n outline: 2px solid var(--client-focus-ring);\n outline-offset: 2px;\n}\n\n.gengage-chat-comparison-table-header-cell img {\n width: 60px;\n height: 60px;\n object-fit: contain;\n border-radius: calc(var(--radius-control) - 4px);\n background: var(--surface-card-muted);\n}\n\n.gengage-chat-comparison-table-header-img-placeholder {\n width: 60px;\n height: 60px;\n border-radius: calc(var(--radius-control) - 4px);\n background: var(--surface-card-muted);\n}\n\n.gengage-chat-comparison-table-product-name {\n width: 100%;\n min-width: 0;\n font-weight: 600;\n font-size: 12px;\n color: var(--text-secondary);\n line-height: 1.35;\n text-align: center;\n align-self: start;\n display: -webkit-box;\n -webkit-line-clamp: 2;\n line-clamp: 2;\n -webkit-box-orient: vertical;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.gengage-chat-comparison-table-price {\n font-weight: 700;\n color: var(--text-primary);\n width: 100%;\n text-align: center;\n}\n\n.gengage-chat-comparison-label {\n text-align: left;\n font-weight: 600;\n color: var(--text-secondary);\n white-space: nowrap;\n}\n\n.gengage-chat-comparison-selected {\n background: color-mix(in srgb, var(--client-primary) 6%, white);\n}\n\n/* Comparison Table Enhancements */\n.gengage-chat-comparison-recommended-text {\n margin: 0;\n font-size: 14px;\n line-height: 1.6;\n color: var(--text-secondary);\n}\n\n.gengage-chat-comparison-key-differences {\n border: 1px solid var(--border-subtle);\n border-radius: var(--radius-card);\n background: var(--surface-card-muted);\n}\n\n.gengage-chat-comparison-key-differences-summary {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n padding: 14px 16px;\n cursor: pointer;\n list-style: none;\n font-weight: 700;\n}\n\n.gengage-chat-comparison-key-differences-summary::-webkit-details-marker {\n display: none;\n}\n\n.gengage-chat-comparison-key-differences-summary-label {\n color: var(--text-primary);\n}\n\n.gengage-chat-comparison-key-differences-summary-meta {\n color: var(--client-primary);\n font-size: 13px;\n font-weight: 700;\n}\n\n.gengage-chat-comparison-key-differences[open] .gengage-chat-comparison-key-differences-summary-meta {\n opacity: 0;\n}\n\n.gengage-chat-comparison-key-differences-content {\n padding: 0 16px 16px;\n font-size: 14px;\n line-height: 1.7;\n color: var(--text-primary);\n}\n\n.gengage-chat-comparison-key-differences-content ul,\n.gengage-chat-comparison-key-differences-content ol {\n margin: 0;\n padding-left: 20px;\n}\n\n.gengage-chat-comparison-key-differences-content li + li {\n margin-top: 8px;\n}\n\n@media (max-width: 960px) {\n .gengage-chat-comparison-recommended-body {\n grid-template-columns: 1fr;\n }\n\n .gengage-chat-comparison-recommended-title {\n font-size: 20px;\n }\n}\n\n/* Suggestion pills row */\n.gengage-chat-pills {\n position: relative;\n padding: 8px 12px 6px;\n border-top: 1px solid var(--border-subtle);\n background: var(--surface-card);\n}\n\n.gengage-chat-pills-scroll {\n display: flex;\n gap: 8px;\n overflow-x: auto;\n scrollbar-width: none;\n -webkit-overflow-scrolling: touch;\n padding-right: 28px;\n}\n\n.gengage-chat-pills-scroll::-webkit-scrollbar {\n display: none;\n}\n\n.gengage-chat-root--mobile .gengage-chat-pills-scroll {\n flex-wrap: wrap;\n gap: 6px;\n}\n\n.gengage-chat-root--mobile .gengage-chat-pill {\n white-space: normal;\n max-width: 100%;\n}\n\n.gengage-chat-pill {\n flex: 0 0 auto;\n padding: 10px 14px;\n border: 1px solid var(--ds-chip-border-active);\n border-radius: 999px;\n background: var(--ds-chip-bg-active);\n color: var(--ds-chip-fg-active);\n font-size: 13px;\n font-weight: 700;\n font-family: inherit;\n white-space: nowrap;\n cursor: pointer;\n transition:\n background 0.15s ease,\n border-color 0.15s ease,\n color 0.15s ease;\n}\n\n.gengage-chat-pill:hover {\n background: color-mix(in srgb, var(--client-primary) 12%, white);\n border-color: color-mix(in srgb, var(--client-primary) 36%, white);\n}\n\n.gengage-chat-pill:active {\n transform: none;\n}\n\n/* Rich pills (with image/description, e.g. AI groupings) */\n.gengage-chat-pill--rich {\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 8px 14px 8px 8px;\n border-radius: 18px;\n}\n\n.gengage-chat-pill-img {\n width: 32px;\n height: 32px;\n border-radius: 12px;\n object-fit: cover;\n flex-shrink: 0;\n background: var(--surface-card);\n}\n\n.gengage-chat-pill-text {\n font-size: 13px;\n font-weight: 600;\n line-height: 1.2;\n}\n\n.gengage-chat-pill-desc {\n display: none;\n}\n\n.gengage-chat-pill--rich .gengage-chat-pill-desc {\n display: block;\n font-size: 11px;\n font-weight: 500;\n color: var(--text-muted);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n max-width: 160px;\n}\n\n.gengage-chat-pills-arrow {\n position: absolute;\n right: 8px;\n top: 50%;\n transform: translateY(-50%);\n width: 36px;\n height: 36px;\n border-radius: 12px;\n border: 1px solid var(--border-default);\n background: var(--surface-card);\n color: var(--text-secondary);\n font-size: 16px;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n box-shadow: var(--shadow-1);\n z-index: 1;\n}\n\n.gengage-chat-pills-arrow:hover {\n background: var(--surface-card-soft);\n color: var(--client-primary);\n}\n\n@media (prefers-reduced-motion: reduce) {\n .gengage-chat-uispec > *,\n .gengage-chat-action-btn,\n .gengage-chat-product-card,\n .gengage-chat-product-card-cta,\n .gengage-chat-send,\n .gengage-chat-launcher,\n .gengage-chat-pill,\n .gengage-chat-drawer,\n .gengage-chat-typing-dots span,\n .gengage-chat-thinking-step-marker--active,\n .gengage-chat-bubble,\n .gengage-chat-panel-skeleton-block,\n .gengage-chat-ai-toppick-card,\n .gengage-chat-mic-btn--active,\n .gengage-chat-ai-toppick-spinner::after,\n .gengage-skeleton-card,\n .gengage-skeleton-line,\n .gengage-skeleton-img,\n .gengage-skeleton-text,\n .gengage-skeleton-price,\n .gengage-chat-cart-toast,\n .gengage-chat-header-btn--cart-flash {\n animation: none !important;\n transition: none !important;\n transform: none !important;\n }\n}\n\n/* ---------------------------------------------------------------------------\n * Rollback button (on user message bubbles)\n * ---------------------------------------------------------------------------*/\n\n.gengage-chat-bubble--user {\n position: relative;\n}\n\n.gengage-chat-rollback-btn {\n position: absolute;\n left: -28px;\n top: 50%;\n transform: translateY(-50%);\n width: 24px;\n height: 24px;\n border: none;\n border-radius: var(--radius-pill);\n background: var(--surface-card-muted);\n color: var(--text-secondary);\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n opacity: 0;\n transition: opacity 0.15s ease;\n padding: 0;\n}\n\n.gengage-chat-bubble--user:hover .gengage-chat-rollback-btn {\n opacity: 1;\n}\n\n.gengage-chat-rollback-btn:hover {\n background: var(--client-primary);\n color: var(--client-on-primary);\n}\n\n/* Hidden state for rolled-back messages */\n.gengage-chat-bubble--hidden {\n display: none;\n}\n\n/* ---------------------------------------------------------------------------\n * AI Top Picks\n * ---------------------------------------------------------------------------*/\n\n.gengage-chat-ai-top-picks {\n display: flex;\n flex-direction: column;\n gap: 12px;\n padding: 8px 0;\n /* GAP-078: Keep AI cards visible below sticky header when scroll-linked */\n scroll-margin-top: 60px;\n}\n\n.gengage-chat-ai-top-picks-title {\n font-size: 15px;\n font-weight: 600;\n color: var(--text-primary);\n margin: 0 0 4px;\n}\n\n.gengage-chat-ai-top-picks-scroll {\n display: flex;\n flex-direction: column;\n gap: 10px;\n min-width: 0;\n}\n\n/* 2+ picks: wraps non-hero cards. Desktop/panel: transparent to layout (grid still sees cards). */\n.gengage-chat-ai-top-picks-rest {\n display: contents;\n}\n\n/* Top row: image + (name / rating / price / chips). Transparent on desktop — children merge into card flow. */\n.gengage-chat-ai-toppick-top-row {\n display: contents;\n}\n\n.gengage-chat-ai-toppick-detail {\n width: 100%;\n min-width: 0;\n}\n\n/* Panel AI zone: title row + horizontal scroll rail (parity with grouping cards / legacy Top Picks) */\n.gengage-chat-panel-ai-zone .gengage-chat-ai-top-picks {\n gap: 0;\n padding-top: 4px;\n}\n\n.gengage-chat-panel-ai-zone .gengage-chat-ai-top-picks-title {\n flex-shrink: 0;\n width: 100%;\n margin: 0 0 10px;\n font-size: 18px;\n line-height: 1.2;\n font-weight: 700;\n}\n\n.gengage-chat-panel-ai-zone .gengage-chat-ai-top-picks-scroll {\n display: grid;\n grid-template-columns: repeat(2, minmax(0, 1fr));\n grid-auto-rows: minmax(0, auto);\n align-items: stretch;\n gap: 12px; /* gap-3 */\n overflow: visible;\n /* Top padding for the border badge */\n padding: 12px 0 12px;\n margin: 0;\n scrollbar-width: none;\n}\n\n.gengage-chat-panel-ai-zone .gengage-chat-ai-toppick-card--winner,\n.gengage-chat-panel-ai-zone .gengage-chat-ai-toppick-card--compact {\n width: 100%;\n min-width: 0;\n max-width: none;\n flex: initial;\n scroll-snap-align: none;\n}\n\n.gengage-chat-panel-ai-zone .gengage-chat-ai-top-picks-scroll > :first-child {\n grid-column: 1 / -1;\n display: grid;\n grid-template-columns: minmax(260px, 340px) minmax(0, 1fr);\n grid-template-rows: auto auto;\n align-items: start;\n column-gap: 18px;\n row-gap: 12px;\n}\n\n.gengage-chat-panel-ai-zone .gengage-chat-ai-top-picks-scroll > :first-child:has(.gengage-chat-ai-toppick-detail) {\n grid-template-rows: auto auto auto;\n}\n\n.gengage-chat-panel-ai-zone .gengage-chat-ai-toppick-card--compact {\n flex-direction: column;\n align-items: stretch;\n}\n\n/* Panel: visual max height (narrow column) — maintain lean aspect-square */\n.gengage-chat-panel-ai-zone .gengage-chat-ai-toppick-card--compact .gengage-chat-ai-toppick-media,\n.gengage-chat-panel-ai-zone .gengage-chat-ai-toppick-card--winner .gengage-chat-ai-toppick-media {\n max-height: 140px;\n}\n\n.gengage-chat-panel-ai-zone .gengage-chat-ai-top-picks-scroll > :first-child .gengage-chat-ai-toppick-media {\n grid-column: 1;\n grid-row: 1 / span 2;\n max-height: 260px;\n aspect-ratio: 4 / 3;\n}\n\n.gengage-chat-panel-ai-zone\n .gengage-chat-ai-top-picks-scroll\n > :first-child:has(.gengage-chat-ai-toppick-detail)\n .gengage-chat-ai-toppick-media {\n grid-row: 1 / span 3;\n}\n\n.gengage-chat-panel-ai-zone .gengage-chat-ai-top-picks-scroll > :first-child .gengage-chat-ai-toppick-detail {\n grid-column: 2;\n grid-row: 2;\n min-width: 0;\n padding-top: 0;\n}\n\n.gengage-chat-panel-ai-zone .gengage-chat-ai-toppick-card--compact .gengage-chat-ai-toppick-img,\n.gengage-chat-panel-ai-zone .gengage-chat-ai-toppick-card--winner .gengage-chat-ai-toppick-img {\n width: 100%;\n height: 100%;\n max-height: 140px;\n object-fit: contain;\n flex-shrink: 0;\n}\n\n.gengage-chat-panel-ai-zone .gengage-chat-ai-top-picks-scroll > :first-child .gengage-chat-ai-toppick-img {\n max-height: 260px;\n}\n\n.gengage-chat-panel-ai-zone .gengage-chat-ai-toppick-card--winner .gengage-chat-ai-toppick-body,\n.gengage-chat-panel-ai-zone .gengage-chat-ai-toppick-card--compact .gengage-chat-ai-toppick-body {\n padding: 0;\n flex: 1;\n min-height: 0;\n}\n\n.gengage-chat-panel-ai-zone .gengage-chat-ai-top-picks-scroll > :first-child .gengage-chat-ai-toppick-body {\n grid-column: 2;\n grid-row: 1;\n padding-top: 6px;\n}\n\n.gengage-chat-panel-ai-zone\n .gengage-chat-ai-top-picks-scroll\n > :first-child:has(.gengage-chat-ai-toppick-detail)\n .gengage-chat-ai-toppick-cta {\n grid-row: 3;\n}\n\n.gengage-chat-panel-ai-zone .gengage-chat-ai-toppick-card--compact .gengage-chat-ai-toppick-cta {\n width: calc(100% - 24px);\n margin: 14px 12px 12px;\n align-self: stretch;\n border: 1px solid var(--ds-button-secondary-border);\n background: var(--ds-button-secondary-bg);\n color: var(--ds-button-secondary-fg);\n}\n\n.gengage-chat-panel-ai-zone .gengage-chat-ai-toppick-card--winner .gengage-chat-ai-toppick-cta {\n grid-column: 2;\n grid-row: 2;\n width: auto;\n min-width: 180px;\n margin: 0;\n justify-self: start;\n align-self: end;\n border: 1px solid var(--ds-button-secondary-border);\n background: var(--ds-button-secondary-bg);\n color: var(--ds-button-secondary-fg);\n}\n\n.gengage-chat-panel-ai-zone .gengage-chat-ai-toppick-card--winner .gengage-chat-ai-toppick-cta:hover,\n.gengage-chat-panel-ai-zone .gengage-chat-ai-toppick-card--compact .gengage-chat-ai-toppick-cta:hover {\n background: var(--ds-button-secondary-bg-hover);\n filter: none;\n}\n\n.gengage-chat-panel-ai-zone .gengage-chat-ai-toppick-card--winner {\n border-color: color-mix(in srgb, var(--client-primary) 26%, var(--border-default));\n box-shadow: var(--shadow-2);\n}\n\n.gengage-chat-ai-toppick-rating {\n display: inline-flex;\n align-items: center;\n gap: 5px;\n align-self: flex-start;\n padding: 3px 8px;\n margin-top: 4px;\n border: 1px solid var(--border-subtle);\n border-radius: 999px;\n background: var(--surface-card-muted);\n font-size: 11px;\n font-weight: 700;\n color: var(--text-secondary);\n}\n\n.gengage-chat-ai-toppick-rating-icon {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n color: color-mix(in srgb, var(--warning) 60%, var(--text-primary));\n line-height: 0;\n}\n\n.gengage-chat-ai-toppick-rating-value {\n color: var(--text-primary);\n}\n\n/* Discount: lean top-1 left-1, text-[9px] font-extrabold */\n.gengage-chat-ai-toppick-media .gengage-chat-ai-toppick-discount-badge {\n position: absolute;\n top: 4px;\n left: 4px;\n right: auto;\n background: var(--client-primary);\n color: var(--client-on-primary);\n font-size: 9px;\n font-weight: 800;\n padding: 2px 6px;\n border-radius: 999px;\n z-index: 1;\n line-height: 1.2;\n}\n\n/*\n * AI Top Picks cards — same layout as robot-engine-lean TopPicksResults:\n * relative p-3 rounded-xl border | badge absolute -top-2.5 left-3 |\n * media: aspect-square rounded-lg border-gray-100 mt-1 mb-2\n */\n.gengage-chat-ai-toppick-card--winner,\n.gengage-chat-ai-toppick-card--compact {\n position: relative;\n display: flex;\n flex-direction: column;\n align-items: stretch;\n box-sizing: border-box;\n padding: 12px;\n border-radius: var(--radius-card);\n background: var(--surface-card);\n overflow: visible;\n transition:\n box-shadow 0.2s ease,\n background 0.15s ease,\n transform 0.15s ease;\n}\n\n.gengage-chat-ai-toppick-card--winner {\n border: 1.5px solid color-mix(in srgb, var(--client-primary) 24%, var(--border-default));\n}\n\n.gengage-chat-ai-toppick-card--winner:hover {\n background: var(--surface-card-muted);\n box-shadow: var(--shadow-2);\n}\n\n.gengage-chat-ai-toppick-card--compact {\n border: 1px solid var(--border-default);\n}\n\n.gengage-chat-ai-toppick-card--compact:hover {\n background: var(--surface-card-muted);\n box-shadow: var(--shadow-1);\n transform: translateY(-1px);\n}\n\n/* Role badge: absolute -top-2.5 left-3 (no translateY — prevent overlap) */\n.gengage-chat-ai-toppick-badge {\n position: absolute;\n top: -10px;\n left: 12px;\n z-index: 2;\n max-width: calc(100% - 24px);\n box-sizing: border-box;\n font-size: 10px;\n font-weight: 700;\n line-height: 1.25;\n padding: 2px 10px;\n border-radius: 999px;\n text-transform: uppercase;\n letter-spacing: 0.05em;\n pointer-events: none;\n white-space: normal;\n}\n\n.gengage-chat-ai-toppick-card--winner .gengage-chat-ai-toppick-badge {\n background: var(--client-primary);\n color: var(--client-on-primary);\n}\n\n.gengage-chat-ai-toppick-card--compact .gengage-chat-ai-toppick-badge {\n background: var(--surface-card-muted);\n color: var(--text-secondary);\n}\n\n.gengage-chat-ai-toppick-media {\n position: relative;\n width: 100%;\n flex-shrink: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n margin-top: 4px;\n margin-bottom: 8px;\n aspect-ratio: 1 / 1;\n border-radius: 8px;\n border: 1px solid var(--border-subtle);\n overflow: hidden;\n background: var(--surface-card);\n}\n\n.gengage-chat-ai-toppick-img {\n max-width: 100%;\n max-height: 100%;\n width: auto;\n height: auto;\n object-fit: contain;\n padding: 4px;\n box-sizing: border-box;\n}\n\n/* Body — padding inside card (p-3); no extra wrapper in lean mode */\n.gengage-chat-ai-toppick-body {\n display: flex;\n flex-direction: column;\n gap: 0;\n padding: 0;\n flex: 1;\n min-width: 0;\n}\n\n.gengage-chat-ai-toppick-name {\n font-size: 14px;\n font-weight: 700;\n line-height: 1.375;\n color: var(--text-primary);\n margin-bottom: 4px;\n overflow: hidden;\n text-overflow: ellipsis;\n display: -webkit-box;\n -webkit-line-clamp: 2;\n line-clamp: 2;\n -webkit-box-orient: vertical;\n}\n\n.gengage-chat-ai-toppick-role {\n font-size: 11px;\n font-weight: 600;\n color: var(--client-primary);\n text-transform: uppercase;\n letter-spacing: 0.3px;\n}\n\n/* Sentiment chips */\n.gengage-chat-ai-toppick-labels {\n display: flex;\n flex-wrap: wrap;\n gap: 6px;\n margin-top: 10px;\n}\n\n.gengage-chat-ai-toppick-label {\n font-size: 10px;\n padding: 4px 8px;\n border-radius: 999px;\n font-weight: 700;\n}\n\n.gengage-chat-ai-toppick-label[data-sentiment='positive'] {\n background: color-mix(in srgb, var(--success) 12%, white);\n color: color-mix(in srgb, var(--success) 82%, var(--text-primary));\n}\n\n.gengage-chat-ai-toppick-label[data-sentiment='negative'] {\n background: color-mix(in srgb, var(--error) 10%, white);\n color: color-mix(in srgb, var(--error) 82%, var(--text-primary));\n}\n\n.gengage-chat-ai-toppick-label[data-sentiment='neutral'] {\n background: var(--surface-card-muted);\n color: var(--text-secondary);\n}\n\n.gengage-chat-ai-toppick-reason {\n margin: 10px 0 0;\n font-size: 13px;\n line-height: 1.5;\n color: var(--text-secondary);\n}\n\n.gengage-chat-ai-toppick-review {\n margin-top: 10px;\n padding: 10px 12px;\n border: 1px solid var(--border-subtle);\n border-radius: 14px;\n background: var(--surface-card-muted);\n font-size: 12px;\n line-height: 1.45;\n color: var(--text-primary);\n}\n\n.gengage-chat-ai-toppick-snippet {\n margin: 8px 0 0;\n font-size: 12px;\n line-height: 1.45;\n color: var(--text-secondary);\n}\n\n/* Price */\n.gengage-chat-ai-toppick-price {\n font-size: 14px;\n font-weight: 700;\n color: var(--text-primary);\n margin-top: 4px;\n}\n\n.gengage-chat-ai-toppick-original-price {\n text-decoration: line-through;\n color: var(--text-secondary);\n font-weight: 400;\n font-size: 12px;\n}\n\n/* CTA — clear margin from the content above (View Details) */\n.gengage-chat-ai-toppick-cta {\n display: block;\n width: calc(100% - 24px);\n margin: 14px 12px 12px;\n padding: 8px 0;\n border: 1px solid var(--ds-button-primary-border);\n border-radius: var(--radius-control, 12px);\n background: var(--ds-button-primary-bg);\n color: var(--ds-button-primary-fg);\n font-size: 13px;\n font-weight: 700;\n cursor: pointer;\n text-align: center;\n transition:\n opacity 0.18s ease,\n transform 0.18s ease,\n background 0.15s ease;\n flex-shrink: 0;\n align-self: stretch;\n box-sizing: border-box;\n opacity: 0;\n transform: translateY(6px);\n pointer-events: none;\n}\n\n.gengage-chat-ai-toppick-card--winner:hover .gengage-chat-ai-toppick-cta,\n.gengage-chat-ai-toppick-card--compact:hover .gengage-chat-ai-toppick-cta,\n.gengage-chat-ai-toppick-card--winner:focus-within .gengage-chat-ai-toppick-cta,\n.gengage-chat-ai-toppick-card--compact:focus-within .gengage-chat-ai-toppick-cta {\n opacity: 1;\n transform: translateY(0);\n pointer-events: auto;\n}\n\n.gengage-chat-ai-toppick-cta:hover {\n background: var(--ds-button-primary-bg-hover);\n}\n\n/* Per-card loading spinner overlay */\n.gengage-chat-ai-toppick-spinner {\n position: absolute;\n inset: 0;\n background: color-mix(in srgb, var(--surface-card) 78%, transparent);\n display: flex;\n align-items: center;\n justify-content: center;\n border-radius: 12px;\n z-index: 6;\n}\n\n.gengage-chat-ai-toppick-spinner::after {\n content: '';\n width: 24px;\n height: 24px;\n border: 3px solid var(--border-default);\n border-top-color: var(--ai-accent-start);\n border-radius: 50%;\n animation: gengage-spin 0.7s linear infinite;\n}\n\n@keyframes gengage-spin {\n to {\n transform: rotate(360deg);\n }\n}\n\n/* Mobile: hero pick full width, next two half-width (1 + 2 column row) */\n.gengage-chat-root--mobile .gengage-chat-ai-top-picks {\n gap: 8px;\n padding: 4px 0;\n}\n\n.gengage-chat-root--mobile .gengage-chat-ai-top-picks-title {\n font-size: 14px;\n margin: 0 0 2px;\n}\n\n.gengage-chat-root--mobile .gengage-chat-ai-top-picks-scroll {\n display: flex;\n flex-direction: column;\n gap: 8px;\n overflow: visible;\n padding: 6px 0 2px;\n min-width: 0;\n}\n\n.gengage-chat-root--mobile .gengage-chat-ai-top-picks-scroll > :first-child {\n display: flex;\n flex-direction: column;\n align-items: stretch;\n width: 100%;\n min-width: 0;\n}\n\n/* Mobile: top row (image + text) — winner gets detail + CTA below */\n.gengage-chat-root--mobile .gengage-chat-ai-top-picks-scroll > :first-child.gengage-chat-ai-toppick-card--winner {\n flex-direction: column;\n}\n\n/* Non-hero picks: horizontally scrollable strip (mobile only) */\n.gengage-chat-root--mobile .gengage-chat-ai-top-picks-rest {\n display: flex;\n flex-direction: row;\n flex-wrap: nowrap;\n gap: 8px;\n overflow-x: auto;\n overflow-y: visible;\n padding: 2px 2px 8px;\n margin: 0 -2px;\n -webkit-overflow-scrolling: touch;\n scroll-snap-type: x proximity;\n scrollbar-width: none;\n -ms-overflow-style: none;\n min-width: 0;\n}\n\n.gengage-chat-root--mobile .gengage-chat-ai-top-picks-rest::-webkit-scrollbar {\n display: none;\n width: 0;\n height: 0;\n}\n\n.gengage-chat-root--mobile .gengage-chat-ai-top-picks-rest .gengage-chat-ai-toppick-card--compact {\n flex: 0 0 min(82vw, 272px);\n width: min(82vw, 272px);\n max-width: 272px;\n min-width: 0;\n scroll-snap-align: start;\n}\n\n.gengage-chat-root--mobile .gengage-chat-ai-toppick-card--winner {\n width: 100%;\n min-width: 0;\n max-width: none;\n flex-shrink: 1;\n scroll-snap-align: none;\n padding: 10px;\n}\n\n.gengage-chat-root--mobile .gengage-chat-ai-toppick-card--compact {\n width: 100%;\n min-width: 0;\n max-width: none;\n flex-shrink: 1;\n scroll-snap-align: none;\n padding: 6px 8px;\n}\n\n.gengage-chat-root--mobile .gengage-chat-ai-toppick-card--winner {\n margin-bottom: 8px;\n border: 1.5px solid color-mix(in srgb, var(--client-primary) 28%, var(--border-default));\n}\n\n.gengage-chat-root--mobile .gengage-chat-ai-toppick-card--compact {\n flex-direction: column;\n align-items: stretch;\n border: 1px solid var(--border-default);\n box-shadow: none;\n}\n\n/* Winner top row: square image on the left, title / rating / price on the right */\n.gengage-chat-root--mobile .gengage-chat-ai-toppick-card--winner .gengage-chat-ai-toppick-top-row {\n display: flex;\n flex-direction: row;\n align-items: flex-start;\n gap: 10px;\n width: 100%;\n min-width: 0;\n}\n\n.gengage-chat-root--mobile .gengage-chat-ai-toppick-card--winner .gengage-chat-ai-toppick-media {\n flex: 0 0 clamp(96px, 30vw, 124px);\n width: clamp(96px, 30vw, 124px);\n height: clamp(96px, 30vw, 124px);\n max-height: none;\n margin: 0;\n aspect-ratio: 1 / 1;\n border-radius: 10px;\n}\n\n.gengage-chat-root--mobile .gengage-chat-ai-toppick-card--winner .gengage-chat-ai-toppick-img {\n max-height: 100%;\n width: 100%;\n height: 100%;\n object-fit: contain;\n padding: 4px;\n}\n\n.gengage-chat-root--mobile .gengage-chat-ai-toppick-card--winner .gengage-chat-ai-toppick-body {\n flex: 1;\n min-width: 0;\n padding-top: 2px;\n}\n\n.gengage-chat-root--mobile .gengage-chat-ai-toppick-card--winner .gengage-chat-ai-toppick-detail {\n width: 100%;\n margin-top: 10px;\n}\n\n.gengage-chat-root--mobile .gengage-chat-ai-toppick-card--winner .gengage-chat-ai-toppick-reason {\n display: block !important;\n margin: 0;\n font-size: 12px;\n line-height: 1.45;\n color: var(--text-primary);\n}\n\n.gengage-chat-root--mobile .gengage-chat-ai-toppick-card--winner .gengage-chat-ai-toppick-review {\n display: block !important;\n margin-top: 8px;\n padding: 8px 10px;\n font-size: 12px;\n line-height: 1.4;\n font-style: italic;\n color: var(--text-secondary);\n border: 1px solid var(--border-subtle);\n border-radius: 12px;\n background: var(--surface-card-muted);\n}\n\n/* Compact cards (horizontal strip): small square on left, text on right; snippet below */\n.gengage-chat-root--mobile .gengage-chat-ai-toppick-card--compact .gengage-chat-ai-toppick-top-row {\n display: flex;\n flex-direction: row;\n align-items: flex-start;\n gap: 6px;\n width: 100%;\n min-width: 0;\n}\n\n.gengage-chat-root--mobile .gengage-chat-ai-toppick-card--compact .gengage-chat-ai-toppick-media {\n flex: 0 0 64px;\n width: 64px;\n height: 64px;\n max-height: 64px;\n margin: 0;\n aspect-ratio: 1 / 1;\n border-radius: 8px;\n}\n\n.gengage-chat-root--mobile .gengage-chat-ai-toppick-card--compact .gengage-chat-ai-toppick-img {\n max-height: 100%;\n width: 100%;\n object-fit: contain;\n padding: 2px;\n}\n\n.gengage-chat-root--mobile .gengage-chat-ai-toppick-card--compact .gengage-chat-ai-toppick-body {\n flex: 1;\n min-width: 0;\n}\n\n.gengage-chat-root--mobile .gengage-chat-ai-toppick-card--compact .gengage-chat-ai-toppick-rating {\n display: none;\n}\n\n.gengage-chat-root--mobile .gengage-chat-ai-toppick-card--compact .gengage-chat-ai-toppick-detail {\n margin-top: 4px;\n width: 100%;\n}\n\n.gengage-chat-root--mobile .gengage-chat-ai-toppick-card--compact .gengage-chat-ai-toppick-snippet {\n margin: 0;\n font-size: 10px;\n line-height: 1.3;\n color: var(--text-secondary);\n display: -webkit-box;\n -webkit-line-clamp: 2;\n line-clamp: 2;\n -webkit-box-orient: vertical;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.gengage-chat-root--mobile .gengage-chat-ai-toppick-card--compact .gengage-chat-ai-toppick-role-line {\n font-size: 9px;\n font-weight: 600;\n line-height: 1.15;\n letter-spacing: 0.04em;\n text-transform: uppercase;\n color: var(--text-muted);\n margin: 0 0 2px;\n}\n\n.gengage-chat-root--mobile .gengage-chat-ai-toppick-snippet {\n margin: 0;\n font-size: 11px;\n line-height: 1.35;\n color: var(--text-secondary);\n display: -webkit-box;\n -webkit-line-clamp: 2;\n line-clamp: 2;\n -webkit-box-orient: vertical;\n overflow: hidden;\n}\n\n/* Compact card fav + similar actions — top-right corner of image */\n.gengage-chat-root--mobile\n .gengage-chat-ai-toppick-card--compact\n .gengage-chat-ai-toppick-media\n .gengage-chat-product-card-img-actions {\n top: 3px;\n right: 5px;\n gap: 5px;\n}\n\n.gengage-chat-root--mobile\n .gengage-chat-ai-toppick-card--winner\n .gengage-chat-ai-toppick-media\n .gengage-chat-product-card-img-actions {\n top: 5px;\n right: 6px;\n gap: 5px;\n}\n\n/* Winner badge: solid client color + white text; smaller padding — font size unchanged */\n.gengage-chat-root--mobile .gengage-chat-ai-toppick-card--winner .gengage-chat-ai-toppick-badge {\n top: -14px;\n left: 8px;\n max-width: calc(100% - 18px);\n font-size: 8px;\n font-weight: 700;\n line-height: 1.15;\n padding: 2px 6px;\n letter-spacing: 0.04em;\n background: var(--client-primary);\n color: var(--client-on-primary);\n border: none;\n box-shadow: none;\n}\n\n.gengage-chat-root--mobile .gengage-chat-ai-toppick-card--compact .gengage-chat-ai-toppick-name {\n font-size: 12px;\n line-height: 1.22;\n margin-bottom: 1px;\n}\n\n.gengage-chat-root--mobile .gengage-chat-ai-toppick-name {\n font-size: 13px;\n line-height: 1.28;\n margin-bottom: 2px;\n}\n\n.gengage-chat-root--mobile .gengage-chat-ai-toppick-rating {\n margin-top: 2px;\n padding: 2px 6px;\n gap: 3px;\n font-size: 10px;\n}\n\n.gengage-chat-root--mobile .gengage-chat-ai-toppick-card--compact .gengage-chat-ai-toppick-price {\n font-size: 13px;\n margin-top: 1px;\n}\n\n.gengage-chat-root--mobile .gengage-chat-ai-toppick-price {\n font-size: 13px;\n margin-top: 2px;\n}\n\n.gengage-chat-root--mobile .gengage-chat-ai-toppick-original-price {\n font-size: 11px;\n}\n\n/* Mobile: hide sentiment chips; reason/review shown inside winner detail instead */\n.gengage-chat-root--mobile .gengage-chat-ai-toppick-labels {\n display: none !important;\n}\n\n.gengage-chat-root--mobile .gengage-chat-ai-toppick-card--compact .gengage-chat-ai-toppick-cta {\n display: none !important;\n}\n\n.gengage-chat-root--mobile .gengage-chat-ai-toppick-card--winner .gengage-chat-ai-toppick-cta {\n width: calc(100% - 16px);\n margin: 8px 8px 6px;\n padding: 6px 0;\n font-size: 12px;\n font-weight: 600;\n align-self: stretch;\n opacity: 1;\n transform: none;\n pointer-events: auto;\n border: 1px solid var(--client-primary);\n background: var(--surface-card);\n color: var(--client-primary);\n box-shadow: none;\n filter: none;\n}\n\n.gengage-chat-root--mobile .gengage-chat-ai-toppick-card--winner .gengage-chat-ai-toppick-cta:hover {\n background: color-mix(in srgb, var(--client-primary) 10%, var(--surface-card));\n color: var(--client-primary);\n border-color: var(--client-primary);\n filter: none;\n}\n\n.gengage-chat-root--mobile .gengage-chat-rollback-btn {\n left: -24px;\n}\n\n/* ---------------------------------------------------------------------------\n * GroundingReviewCard\n * ---------------------------------------------------------------------------*/\n.gengage-chat-grounding-review {\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 12px 16px;\n transition:\n background 0.15s ease,\n border-color 0.15s ease,\n box-shadow 0.15s ease;\n}\n\n.gengage-chat-grounding-review:hover {\n background: var(--ds-card-bg-hover);\n border-color: var(--ds-card-border-hover);\n box-shadow: var(--ds-card-shadow-hover);\n}\n\n.gengage-chat-grounding-review-icon {\n width: 32px;\n height: 32px;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n border-radius: 999px;\n background: color-mix(in srgb, var(--client-primary) 8%, var(--surface-card));\n color: var(--client-primary);\n flex-shrink: 0;\n}\n\n.gengage-chat-grounding-review-icon svg {\n width: 18px;\n height: 18px;\n display: block;\n}\n\n.gengage-chat-grounding-review-body {\n flex: 1;\n min-width: 0;\n}\n\n.gengage-chat-grounding-review-title {\n font-weight: 600;\n font-size: 13px;\n color: var(--text-primary);\n}\n\n.gengage-chat-grounding-review-subtitle {\n font-size: 12px;\n color: var(--text-muted);\n margin-top: 2px;\n}\n\n.gengage-chat-grounding-review-cta {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n font-size: 13px;\n font-weight: 600;\n color: var(--client-primary);\n white-space: nowrap;\n flex-shrink: 0;\n}\n\n.gengage-chat-grounding-review-cta-icon {\n display: inline-flex;\n align-items: center;\n}\n\n.gengage-chat-grounding-review-cta-icon svg {\n width: 14px;\n height: 14px;\n display: block;\n}\n\n/* ---------------------------------------------------------------------------\n * AIGroupingCards\n * ---------------------------------------------------------------------------*/\n.gengage-chat-grouping-cards {\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n\n/* Card list: chat = vertical stack; panel AI zone overrides row + overflow-x */\n.gengage-chat-grouping-cards-scroll {\n display: flex;\n flex-direction: column;\n gap: 6px;\n min-width: 0;\n}\n\n.gengage-chat-grouping-card {\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 8px 12px;\n background: var(--surface-card-soft);\n border: 1px solid var(--border-default);\n border-radius: var(--radius-control);\n transition:\n background 0.15s ease,\n border-color 0.15s ease;\n}\n\n.gengage-chat-grouping-card:hover {\n background: color-mix(in srgb, var(--client-primary) 6%, white);\n border-color: color-mix(in srgb, var(--client-primary) 24%, var(--border-default));\n}\n\n.gengage-chat-grouping-card-arrow {\n display: none;\n font-size: 14px;\n color: var(--client-primary);\n flex-shrink: 0;\n}\n\n.gengage-chat-grouping-card-img {\n width: 48px;\n height: 48px;\n min-width: 48px;\n min-height: 48px;\n border-radius: calc(var(--radius-control) - 4px);\n object-fit: cover;\n flex-shrink: 0;\n}\n\n.gengage-chat-grouping-card-body {\n flex: 1;\n min-width: 0;\n display: flex;\n /* Always stack name + description vertically for consistent card height */\n flex-direction: column;\n align-items: flex-start;\n gap: 2px;\n}\n\n.gengage-chat-grouping-card-name {\n font-weight: 600;\n font-size: 13px;\n color: var(--text-primary);\n}\n\n.gengage-chat-grouping-card-desc {\n font-size: 12px;\n color: var(--text-muted);\n}\n\n.gengage-chat-grouping-card-labels {\n display: flex;\n flex-wrap: wrap;\n gap: 6px;\n margin-top: 4px;\n}\n\n.gengage-chat-grouping-card-label {\n font-size: 10px;\n font-weight: 700;\n line-height: 1.2;\n padding: 4px 8px;\n color: var(--text-secondary);\n background: var(--surface-card-muted);\n border: 1px solid var(--border-subtle);\n border-radius: 999px;\n}\n\n/* Mobile: hide image, show arrow prefix */\n.gengage-chat-root--mobile .gengage-chat-grouping-card-img {\n display: none;\n}\n.gengage-chat-root--mobile .gengage-chat-grouping-card-arrow {\n display: inline;\n}\n\n/* ---------------------------------------------------------------------------\n * AISuggestedSearchCards\n * ---------------------------------------------------------------------------*/\n.gengage-chat-suggested-search-cards {\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n\n.gengage-chat-suggested-search-card {\n display: flex;\n align-items: center;\n gap: 14px;\n padding: 14px 16px;\n background: var(--surface-card);\n border: 1px solid var(--border-default);\n border-radius: var(--radius-card);\n box-shadow: var(--shadow-1);\n transition:\n background 0.15s ease,\n border-color 0.15s ease,\n box-shadow 0.15s ease,\n transform 0.15s ease;\n}\n\n.gengage-chat-suggested-search-card:hover {\n background: var(--surface-card);\n border-color: color-mix(in srgb, var(--client-primary) 24%, var(--border-default));\n box-shadow: var(--shadow-2);\n transform: translateY(-1px);\n}\n\n.gengage-chat-suggested-search-card-img {\n width: 64px;\n height: 64px;\n border-radius: 14px;\n object-fit: contain;\n padding: 4px;\n background: var(--surface-card-muted);\n border: 1px solid var(--border-subtle);\n flex-shrink: 0;\n}\n\n.gengage-chat-suggested-search-card-body {\n flex: 1;\n min-width: 0;\n}\n\n.gengage-chat-suggested-search-card-name {\n font-weight: 700;\n font-size: 15px;\n line-height: 1.35;\n color: var(--text-primary);\n display: -webkit-box;\n -webkit-line-clamp: 2;\n line-clamp: 2;\n -webkit-box-orient: vertical;\n overflow: hidden;\n}\n\n/* Product Gallery — hero image + thumbnail strip below */\n.gengage-chat-product-details-gallery {\n position: relative;\n}\n\n.gengage-chat-product-details-gallery .gengage-chat-product-details-img {\n touch-action: pan-y;\n}\n\n.gengage-chat-product-gallery-nav {\n position: absolute;\n top: 50%;\n transform: translateY(-50%);\n z-index: 3;\n width: 36px;\n height: 36px;\n min-width: 36px;\n min-height: 36px;\n border: 1px solid var(--border-default);\n border-radius: var(--radius-pill);\n display: flex;\n align-items: center;\n justify-content: center;\n cursor: pointer;\n background: color-mix(in srgb, var(--surface-card) 92%, transparent);\n color: var(--text-primary);\n padding: 0;\n transition:\n background 0.15s ease,\n opacity 0.15s ease;\n box-shadow: var(--shadow-1);\n}\n\n.gengage-chat-product-gallery-nav:hover:not(:disabled) {\n background: var(--surface-card);\n color: var(--client-primary);\n}\n\n.gengage-chat-product-gallery-nav:focus-visible {\n outline: 2px solid var(--client-focus-ring);\n outline-offset: 2px;\n}\n\n.gengage-chat-product-gallery-nav:disabled {\n opacity: 0.35;\n cursor: not-allowed;\n}\n\n.gengage-chat-product-gallery-nav--prev {\n left: 8px;\n}\n\n.gengage-chat-product-gallery-nav--next {\n right: 8px;\n}\n\n.gengage-chat-product-gallery-thumbs {\n display: flex;\n gap: 8px;\n padding: 10px 0 4px;\n overflow-x: auto;\n scrollbar-width: thin;\n justify-content: center;\n width: 100%;\n}\n\n.gengage-chat-product-gallery-thumb {\n width: 56px;\n height: 56px;\n object-fit: cover;\n border-radius: 8px;\n border: 2px solid transparent;\n cursor: pointer;\n flex-shrink: 0;\n transition: border-color 0.15s ease;\n background: var(--surface-card);\n}\n\n.gengage-chat-product-gallery-thumb:hover {\n border-color: var(--border-strong);\n}\n\n.gengage-chat-product-gallery-thumb--active {\n border-color: var(--client-primary);\n}\n\n.gengage-chat-product-gallery-thumb-more {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 56px;\n height: 56px;\n border-radius: 8px;\n background: var(--surface-card-muted);\n color: var(--text-secondary);\n font-size: 13px;\n font-weight: 600;\n flex-shrink: 0;\n}\n\n/* Variant Selector */\n.gengage-chat-product-variants {\n margin-top: 12px;\n}\n\n.gengage-chat-product-variants-label {\n font-size: 12px;\n font-weight: 600;\n color: var(--text-muted);\n margin-bottom: 6px;\n text-transform: none;\n letter-spacing: 0.5px;\n}\n\n.gengage-chat-product-variants-list {\n display: flex;\n flex-wrap: wrap;\n gap: 6px;\n}\n\n.gengage-chat-product-variant-btn {\n padding: 6px 12px;\n border: 1px solid var(--border-default);\n border-radius: var(--radius-pill);\n background: var(--surface-card);\n color: var(--text-secondary);\n font-size: 12px;\n cursor: pointer;\n transition:\n background 0.15s ease,\n border-color 0.15s ease;\n}\n\n.gengage-chat-product-variant-btn:hover {\n background: var(--surface-card-muted);\n border-color: color-mix(in srgb, var(--client-primary) 20%, var(--border-default));\n color: var(--client-primary);\n}\n\n/* Product details PDP redesign */\n.gengage-chat-product-details-panel {\n gap: 16px;\n padding: 16px;\n border-radius: var(--radius-card);\n background: var(--surface-card);\n box-shadow: var(--shadow-1);\n}\n\n.gengage-chat-product-details-media {\n min-height: 320px;\n justify-content: center;\n border-color: var(--border-default);\n border-radius: var(--radius-card);\n background: var(--surface-card-soft);\n}\n\n.gengage-chat-product-details-img {\n max-height: 420px;\n}\n\n.gengage-chat-product-details-content {\n gap: 12px;\n}\n\n.gengage-chat-product-details-brand {\n color: var(--client-primary);\n font-size: 12px;\n font-weight: 800;\n line-height: 1.2;\n}\n\n.gengage-chat-product-details-title {\n font-size: 22px;\n line-height: 1.2;\n}\n\n.gengage-chat-product-details-rating {\n gap: 6px;\n padding: 6px 10px;\n border: 1px solid color-mix(in srgb, var(--warning) 24%, var(--border-default));\n background: color-mix(in srgb, var(--warning) 10%, var(--surface-card));\n color: color-mix(in srgb, var(--warning) 72%, var(--text-primary));\n font-family: inherit;\n}\n\nbutton.gengage-chat-product-details-rating {\n border: 1px solid color-mix(in srgb, var(--warning) 24%, var(--border-default));\n cursor: pointer;\n}\n\n.gengage-chat-product-details-rating--clickable:hover {\n border-color: color-mix(in srgb, var(--warning) 42%, var(--border-default));\n background: color-mix(in srgb, var(--warning) 14%, var(--surface-card));\n}\n\n.gengage-chat-product-details-rating .gengage-star-rating {\n line-height: 1;\n}\n\n.gengage-chat-product-details-rating-value {\n color: var(--text-primary);\n}\n\n.gengage-chat-product-details-price {\n gap: 10px;\n flex-wrap: wrap;\n}\n\n.gengage-chat-product-details-original-price {\n order: 2;\n}\n\n.gengage-chat-product-details-current-price {\n order: 1;\n font-size: 30px;\n}\n\n.gengage-chat-product-details-discount-badge {\n order: 3;\n display: inline-flex;\n align-items: center;\n min-height: 24px;\n padding: 2px 8px;\n border-radius: var(--radius-pill);\n background: color-mix(in srgb, var(--error) 12%, white);\n color: color-mix(in srgb, var(--error) 84%, var(--text-primary));\n font-size: 12px;\n font-weight: 800;\n}\n\n.gengage-chat-product-details-facts {\n display: grid;\n grid-template-columns: repeat(2, minmax(0, 1fr));\n gap: 8px;\n margin: 0;\n}\n\n.gengage-chat-product-details-fact {\n min-width: 0;\n padding: 8px 10px;\n border: 1px solid var(--border-subtle);\n border-radius: var(--radius-control);\n background: var(--surface-card-soft);\n}\n\n.gengage-chat-product-details-fact dt,\n.gengage-chat-product-details-fact dd {\n margin: 0;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.gengage-chat-product-details-fact dt {\n color: var(--text-muted);\n font-size: 11px;\n font-weight: 700;\n}\n\n.gengage-chat-product-details-fact dd {\n margin-top: 2px;\n color: var(--text-primary);\n font-size: 13px;\n font-weight: 700;\n}\n\n.gengage-chat-product-variants {\n margin-top: 4px;\n}\n\n.gengage-chat-product-variants-label {\n margin-bottom: 8px;\n color: var(--text-primary);\n font-size: 14px;\n font-weight: 800;\n letter-spacing: normal;\n}\n\n.gengage-chat-product-variants-list {\n gap: 8px;\n}\n\n.gengage-chat-product-variant-btn {\n min-height: 44px;\n gap: 8px;\n padding: 7px 10px;\n border-radius: var(--radius-control);\n color: var(--text-primary);\n font-size: 13px;\n font-weight: 700;\n}\n\n.gengage-chat-product-variant-btn--active {\n border-color: var(--client-primary);\n background: color-mix(in srgb, var(--client-primary) 8%, var(--surface-card));\n box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--client-primary) 36%, transparent);\n color: var(--client-primary);\n}\n\n.gengage-chat-product-variant-btn--out {\n opacity: 0.48;\n cursor: not-allowed;\n}\n\n.gengage-chat-product-variant-swatch {\n width: 22px;\n height: 22px;\n flex: 0 0 22px;\n border: 1px solid var(--border-default);\n border-radius: 50%;\n background: var(--surface-card-muted);\n box-shadow: inset 0 0 0 2px var(--surface-card);\n}\n\n.gengage-chat-product-variant-swatch--image {\n object-fit: cover;\n box-shadow: none;\n}\n\n.gengage-chat-product-variant-label,\n.gengage-chat-product-variant-price {\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.gengage-chat-product-variant-price {\n color: var(--text-secondary);\n font-size: 12px;\n font-weight: 700;\n}\n\n.gengage-chat-product-details-actions {\n gap: 10px;\n margin-top: 6px;\n}\n\n.gengage-chat-product-details-atc,\n.gengage-chat-product-details-cta {\n min-height: 44px;\n border-radius: var(--radius-control);\n}\n\n.gengage-chat-product-details-atc {\n border: 1px solid var(--ds-button-primary-border);\n background: var(--ds-button-primary-bg);\n color: var(--ds-button-primary-fg);\n}\n\n.gengage-chat-product-details-atc:hover {\n background: var(--ds-button-primary-bg-hover);\n filter: none;\n}\n\n.gengage-chat-product-details-cta.gds-btn-secondary {\n border-color: var(--ds-button-secondary-border);\n background: var(--ds-button-secondary-bg);\n color: var(--ds-button-secondary-fg);\n}\n\n.gengage-chat-product-details-cta.gds-btn-secondary:hover {\n background: var(--ds-button-secondary-bg-hover);\n}\n\n@media (min-width: 960px) {\n .gengage-chat-product-details-panel {\n display: grid;\n grid-template-columns: minmax(0, 1.15fr) minmax(300px, 0.85fr);\n align-items: start;\n }\n\n .gengage-chat-product-details-media {\n position: sticky;\n top: 0;\n }\n\n .gengage-chat-product-detail-tabs {\n grid-column: 1 / -1;\n }\n}\n\n.gengage-chat-root--mobile .gengage-chat-product-details-panel {\n padding: 10px;\n gap: 10px;\n box-shadow: none;\n}\n\n.gengage-chat-root--mobile .gengage-chat-product-details-media {\n min-height: 220px;\n}\n\n.gengage-chat-root--mobile .gengage-chat-product-details-title {\n font-size: 18px;\n}\n\n.gengage-chat-root--mobile .gengage-chat-product-details-current-price {\n font-size: 24px;\n}\n\n.gengage-chat-root--mobile .gengage-chat-product-details-facts {\n grid-template-columns: 1fr;\n}\n\n.gengage-chat-root--mobile .gengage-chat-product-details-actions {\n display: grid;\n grid-template-columns: 1fr 1fr 44px;\n align-items: stretch;\n}\n\n.gengage-chat-root--mobile .gengage-chat-product-details-actions > .gengage-chat-product-details-share {\n width: 44px;\n min-width: 44px;\n}\n\n.gengage-chat-root--mobile .gengage-chat-product-details-atc,\n.gengage-chat-root--mobile .gengage-chat-product-details-cta {\n width: 100%;\n padding-inline: 10px;\n}\n\n/* Product grid: optional heading row + sort/compare (no outer box — deneysel) */\n.gengage-chat-product-grid-wrapper {\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n\n.gengage-chat-product-grid-head {\n display: flex;\n align-items: center;\n gap: 8px;\n justify-content: space-between;\n min-width: 0;\n margin-bottom: 2px;\n}\n\n.gengage-chat-product-grid-head-title {\n flex: 1 1 auto;\n min-width: 0;\n font-size: 18px;\n font-weight: 700;\n line-height: 1.2;\n color: var(--text-primary);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.gengage-chat-product-grid-head-actions {\n display: flex;\n align-items: center;\n gap: 6px;\n flex-shrink: 0;\n max-width: min(100%, 52%);\n}\n\n.gengage-chat-product-sort-toolbar {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 6px 8px;\n padding: 0;\n margin: 0;\n background: transparent;\n border: none;\n border-radius: 0;\n box-shadow: none;\n}\n\n.gengage-chat-product-sort-toolbar--inline {\n flex-wrap: nowrap;\n justify-content: flex-end;\n}\n\n/* Sort dropdown */\n.gengage-chat-product-sort-dropdown {\n position: relative;\n flex: 0 1 auto;\n min-width: 0;\n max-width: 200px;\n width: auto;\n z-index: 5;\n}\n\n.gengage-chat-product-sort-toolbar:not(.gengage-chat-product-sort-toolbar--inline) .gengage-chat-product-sort-dropdown {\n flex: 1 1 auto;\n max-width: min(200px, 100%);\n}\n\n.gengage-chat-product-sort-dropdown--open {\n z-index: 25;\n}\n\n.gengage-chat-product-sort-trigger {\n display: flex;\n align-items: center;\n gap: 6px;\n width: 100%;\n min-height: 32px;\n padding: 5px 8px;\n border: 1px solid var(--border-default);\n border-radius: var(--radius-control);\n background: var(--surface-card);\n color: var(--text-primary);\n font-size: 12px;\n font-weight: 600;\n line-height: 1.2;\n cursor: pointer;\n text-align: left;\n transition:\n border-color 0.15s ease,\n box-shadow 0.15s ease,\n background 0.15s ease;\n}\n\n.gengage-chat-product-sort-trigger:hover {\n border-color: color-mix(in srgb, var(--client-primary) 22%, var(--border-default));\n background: var(--surface-card-soft);\n}\n\n.gengage-chat-product-sort-trigger:focus-visible {\n outline: 2px solid var(--client-focus-ring);\n outline-offset: 2px;\n}\n\n.gengage-chat-product-sort-dropdown--open .gengage-chat-product-sort-trigger {\n border-color: color-mix(in srgb, var(--client-primary) 32%, var(--border-default));\n box-shadow: var(--shadow-2);\n}\n\n.gengage-chat-product-sort-trigger-icon {\n flex-shrink: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n color: var(--text-secondary);\n}\n\n.gengage-chat-product-sort-dropdown[data-sort-icon='related'] .gengage-chat-product-sort-trigger-icon {\n color: var(--client-primary);\n}\n\n.gengage-chat-product-sort-trigger-icon svg {\n display: block;\n}\n\n.gengage-chat-product-sort-trigger-label {\n flex: 1 1 auto;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.gengage-chat-product-sort-trigger-chevron {\n flex-shrink: 0;\n display: flex;\n color: var(--text-muted);\n transition: transform 0.2s ease;\n}\n\n.gengage-chat-product-sort-dropdown--open .gengage-chat-product-sort-trigger-chevron {\n transform: rotate(180deg);\n}\n\n.gengage-chat-product-sort-menu {\n position: absolute;\n left: 0;\n right: auto;\n top: calc(100% + 4px);\n min-width: 100%;\n width: max-content;\n max-width: min(260px, calc(100vw - 48px));\n margin: 0;\n list-style: none;\n}\n\n.gengage-chat-product-sort-option {\n display: flex;\n align-items: center;\n gap: 8px;\n width: 100%;\n padding: 8px 10px;\n border: none;\n border-radius: calc(var(--radius-control) - 4px);\n background: transparent;\n color: var(--text-primary);\n font-size: 12px;\n font-weight: 500;\n line-height: 1.25;\n cursor: pointer;\n text-align: left;\n transition: background 0.12s ease;\n}\n\n.gengage-chat-product-sort-option:hover {\n background: var(--surface-card-muted);\n}\n\n.gengage-chat-product-sort-option--active {\n color: var(--client-primary);\n font-weight: 600;\n background: var(--client-primary-subtle);\n}\n\n.gengage-chat-product-sort-option-icon {\n flex-shrink: 0;\n display: flex;\n color: var(--text-secondary);\n}\n\n.gengage-chat-product-sort-option--active .gengage-chat-product-sort-option-icon {\n color: var(--client-primary);\n}\n\n.gengage-chat-product-sort-option--active[data-sort-key='related'] .gengage-chat-product-sort-option-icon {\n color: var(--client-primary);\n}\n\n.gengage-chat-product-sort-option-icon svg {\n display: block;\n}\n\n.gengage-chat-product-sort-option-label {\n flex: 1 1 auto;\n min-width: 0;\n}\n\n.gengage-chat-product-sort-option-check {\n flex-shrink: 0;\n display: flex;\n width: 22px;\n justify-content: flex-end;\n color: var(--client-primary);\n}\n\n.gengage-chat-product-sort-option-check--hidden {\n visibility: hidden;\n}\n\n/* Sort toolbar separator (legacy — no longer rendered; kept for older DOM snapshots) */\n.gengage-chat-product-sort-separator {\n width: 1px;\n background: var(--border-default);\n margin: 2px 2px;\n}\n\n/* Comparison toggle — icon + label, matches sort trigger weight */\n.gengage-chat-comparison-toggle-btn {\n display: inline-flex;\n align-items: center;\n gap: 5px;\n flex: none;\n min-height: 32px;\n padding: 5px 8px;\n border: 1px solid var(--border-default);\n border-radius: var(--radius-control, 12px);\n background: var(--surface-card);\n color: var(--text-primary);\n font-size: 12px;\n font-weight: 700;\n cursor: pointer;\n transition:\n background 0.15s ease,\n border-color 0.15s ease,\n color 0.15s ease;\n white-space: nowrap;\n}\n\n.gengage-chat-comparison-toggle-icon {\n flex-shrink: 0;\n display: flex;\n color: var(--text-secondary);\n}\n\n.gengage-chat-comparison-toggle-icon svg {\n display: block;\n}\n\n.gengage-chat-comparison-toggle-btn:hover {\n background: var(--surface-card-soft);\n border-color: color-mix(in srgb, var(--client-primary) 18%, white);\n color: var(--client-primary);\n}\n\n.gengage-chat-comparison-toggle-btn--active {\n border-color: color-mix(in srgb, var(--client-primary) 26%, white);\n color: var(--client-primary);\n background: color-mix(in srgb, var(--client-primary) 8%, white);\n}\n\n.gengage-chat-comparison-toggle-btn--active .gengage-chat-comparison-toggle-icon {\n color: var(--client-primary);\n}\n\n.gengage-chat-comparison-toggle-btn--active:hover {\n background: var(--client-primary-hover);\n color: var(--client-on-primary);\n border-color: var(--client-primary-hover);\n}\n\n.gengage-chat-comparison-toggle-btn--active:hover .gengage-chat-comparison-toggle-icon {\n color: var(--client-on-primary);\n}\n\n/* Comparison select wrapper (checkbox overlay on product cards) */\n.gengage-chat-comparison-select-wrapper {\n position: relative;\n cursor: pointer;\n border-radius: var(--radius-control);\n transition:\n box-shadow 0.2s ease,\n transform 0.2s ease;\n}\n\n.gengage-chat-comparison-select-wrapper:hover {\n box-shadow: 0 0 0 2px var(--client-focus-ring);\n}\n\n.gengage-chat-comparison-select-wrapper--selected {\n box-shadow: none;\n}\n\n.gengage-chat-comparison-select-wrapper--selected:hover {\n box-shadow: none;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .gengage-chat-comparison-select-wrapper {\n transition: none;\n }\n}\n\n/* Pass clicks to wrapper so CTA / favorite / find-similar / stepper do not activate */\n.gengage-chat-comparison-select-wrapper > .gengage-chat-product-card {\n pointer-events: none;\n transition:\n opacity 0.2s ease,\n transform 0.2s ease,\n filter 0.2s ease;\n}\n\n.gengage-chat-comparison-select-wrapper > .gengage-chat-product-card .gengage-chat-product-card-img-actions,\n.gengage-chat-comparison-select-wrapper > .gengage-chat-product-card .gengage-chat-product-card-discount-badge,\n.gengage-chat-comparison-select-wrapper > .gengage-chat-product-card .gengage-chat-product-card-buy-footer,\n.gengage-chat-comparison-select-wrapper > .gengage-chat-product-card .gengage-chat-product-card-cta {\n opacity: 0;\n}\n\n.gengage-chat-comparison-select-wrapper > .gengage-chat-product-card .gengage-chat-product-card-body {\n opacity: 0.82;\n}\n\n.gengage-chat-comparison-select-wrapper:hover > .gengage-chat-product-card,\n.gengage-chat-comparison-select-wrapper--selected > .gengage-chat-product-card {\n opacity: 1;\n transform: translateY(-1px);\n}\n\n.gengage-chat-comparison-select-wrapper--selected > .gengage-chat-product-card {\n border-color: transparent;\n outline: none;\n box-shadow:\n inset 0 0 0 2px color-mix(in srgb, var(--client-primary) 26%, white),\n var(--shadow-1);\n filter: saturate(1.02);\n}\n\n.gengage-chat-comparison-select-wrapper--selected > .gengage-chat-product-card .gengage-chat-product-card-body {\n opacity: 1;\n}\n\n.gengage-chat-comparison-card-hint {\n display: none;\n}\n\n.gengage-chat-comparison-checkbox {\n position: absolute;\n left: 16px;\n right: 16px;\n bottom: 16px;\n z-index: 4;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 8px;\n min-height: 40px;\n padding: 0 14px;\n cursor: pointer;\n pointer-events: auto;\n border: 1px solid var(--border-default);\n background: color-mix(in srgb, var(--surface-card) 94%, white);\n color: var(--text-primary);\n border-radius: 999px;\n box-shadow: var(--shadow-2);\n}\n\n.gengage-chat-comparison-checkbox[data-selected='true'] {\n background: color-mix(in srgb, var(--client-primary) 10%, white);\n border-color: color-mix(in srgb, var(--client-primary) 22%, var(--border-default));\n color: var(--client-primary);\n}\n\n.gengage-chat-comparison-checkbox-icon {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 18px;\n height: 18px;\n border-radius: 999px;\n background: var(--surface-card-soft);\n color: currentColor;\n flex-shrink: 0;\n}\n\n.gengage-chat-comparison-checkbox[data-selected='true'] .gengage-chat-comparison-checkbox-icon {\n background: var(--client-primary);\n color: var(--client-on-primary);\n}\n\n.gengage-chat-comparison-checkbox-dot {\n width: 8px;\n height: 8px;\n border-radius: 999px;\n background: color-mix(in srgb, currentColor 75%, white);\n}\n\n.gengage-chat-comparison-checkbox-label {\n font-size: 12px;\n font-weight: 700;\n line-height: 1.2;\n}\n\n/* Hidden state for comparison toggle during streaming — revealed after stream end */\n.gengage-chat-comparison-toggle-btn--hidden {\n opacity: 0;\n pointer-events: none;\n}\n\n@keyframes gengage-comparison-fade-in {\n from {\n opacity: 0;\n }\n to {\n opacity: 1;\n }\n}\n\n.gengage-chat-comparison-toggle-btn--reveal {\n animation: gengage-comparison-fade-in 0.5s ease forwards;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .gengage-chat-comparison-toggle-btn--reveal {\n animation: none;\n opacity: 1;\n }\n}\n\n/* Floating comparison button — sticky at bottom of product grid */\n.gengage-chat-comparison-floating-btn {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 14px;\n width: min(100%, 520px);\n margin: 16px auto 0;\n padding: 12px 14px;\n border: 1px solid var(--border-default);\n border-radius: 18px;\n background: color-mix(in srgb, var(--surface-card) 92%, white);\n color: var(--text-primary);\n position: sticky;\n bottom: 12px;\n z-index: 5;\n box-shadow: var(--shadow-2);\n backdrop-filter: blur(10px);\n}\n\n/* Mobile: dock mounts here (body child) so it stacks above the transformed panel overlay */\n.gengage-chat-comparison-dock-slot {\n display: none;\n}\n\n.gengage-chat-root--mobile .gengage-chat-comparison-dock-slot:not(:empty) {\n display: block;\n position: absolute;\n left: 0;\n right: 0;\n bottom: 0;\n z-index: 30;\n padding: 0 10px max(6px, env(safe-area-inset-bottom, 0px));\n max-width: 100%;\n box-sizing: border-box;\n pointer-events: none;\n}\n\n.gengage-chat-root--mobile .gengage-chat-comparison-dock-slot > * {\n pointer-events: auto;\n}\n\n.gengage-chat-comparison-floating-summary {\n display: flex;\n align-items: center;\n gap: 12px;\n min-width: 0;\n flex: 1 1 auto;\n}\n\n.gengage-chat-comparison-floating-count {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n min-width: 32px;\n height: 32px;\n padding: 0 10px;\n border-radius: 999px;\n background: var(--client-primary-subtle);\n color: var(--client-primary);\n font-size: 13px;\n font-weight: 800;\n}\n\n.gengage-chat-comparison-floating-copy {\n min-width: 0;\n}\n\n.gengage-chat-comparison-floating-title {\n font-size: 14px;\n font-weight: 700;\n color: var(--text-primary);\n line-height: 1.2;\n}\n\n.gengage-chat-comparison-floating-warning {\n margin-top: 4px;\n font-size: 12px;\n color: var(--error);\n line-height: 1.35;\n}\n\n.gengage-chat-comparison-floating-subtitle {\n margin-top: 2px;\n font-size: 12px;\n color: var(--text-secondary);\n line-height: 1.35;\n}\n\n.gengage-chat-comparison-floating-action {\n flex: 0 0 auto;\n min-width: 132px;\n}\n\n.gengage-chat-comparison-floating-close {\n flex: 0 0 auto;\n width: 40px;\n height: 40px;\n padding: 0;\n border-radius: 14px;\n}\n\n.gengage-chat-comparison-floating-action--disabled {\n opacity: 0.6;\n}\n\n/* Mobile: bar lives in .gengage-chat-comparison-dock-slot (above panel z-index, not inside scroll) */\n.gengage-chat-root--mobile .gengage-chat-comparison-dock-slot .gengage-chat-comparison-floating-btn {\n position: relative;\n bottom: auto;\n margin: 0;\n width: 100%;\n max-width: 100%;\n min-width: 0;\n box-sizing: border-box;\n border-radius: 14px;\n flex-direction: column;\n align-items: stretch;\n justify-content: flex-start;\n gap: 10px;\n padding: 10px 10px 10px;\n box-shadow: var(--shadow-3);\n}\n\n.gengage-chat-root--mobile .gengage-chat-comparison-dock-slot .gengage-chat-comparison-floating-summary {\n display: flex;\n gap: 8px;\n padding-right: 34px; /* clear close (top-right) */\n align-items: center; /* badge and text on the same vertical axis */\n min-height: 26px;\n}\n\n.gengage-chat-root--mobile .gengage-chat-comparison-dock-slot .gengage-chat-comparison-floating-count {\n min-width: 26px;\n height: 26px;\n padding: 0 7px;\n font-size: 12px;\n font-weight: 800;\n line-height: 1;\n flex-shrink: 0;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n}\n\n.gengage-chat-root--mobile .gengage-chat-comparison-dock-slot .gengage-chat-comparison-floating-copy {\n display: flex;\n flex-direction: column;\n justify-content: center;\n min-width: 0;\n}\n\n.gengage-chat-root--mobile .gengage-chat-comparison-dock-slot .gengage-chat-comparison-floating-title {\n font-size: 13px;\n line-height: 1.25;\n margin: 0;\n}\n\n.gengage-chat-root--mobile .gengage-chat-comparison-dock-slot .gengage-chat-comparison-floating-warning {\n margin-top: 2px;\n font-size: 11px;\n}\n\n.gengage-chat-root--mobile .gengage-chat-comparison-dock-slot .gengage-chat-comparison-floating-action {\n width: 100%;\n min-width: 0;\n max-width: 100%;\n box-sizing: border-box;\n min-height: 42px;\n padding: 0 12px;\n font-size: 13px;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n/* Close (✕) — top-right, fixed square; overrides gds-btn min-height and gds-icon-btn sizing */\n.gengage-chat-root--mobile .gengage-chat-comparison-dock-slot .gengage-chat-comparison-floating-close {\n position: absolute;\n top: 9px;\n right: 9px;\n box-sizing: border-box;\n width: 26px;\n height: 26px;\n min-width: 26px;\n min-height: 26px;\n max-width: 26px;\n max-height: 26px;\n padding: 0;\n border-radius: 8px;\n flex: none;\n align-self: unset;\n z-index: 10;\n line-height: 0;\n touch-action: manipulation;\n}\n\n.gengage-chat-root--mobile .gengage-chat-comparison-dock-slot .gengage-chat-comparison-floating-close svg {\n width: 11px;\n height: 11px;\n display: block;\n}\n\n/* Panel TopBar — navigation arrows + title */\n.gengage-chat-panel-topbar {\n display: flex;\n align-items: center;\n flex-wrap: nowrap;\n gap: 12px;\n box-sizing: border-box;\n height: var(--_gengage-chat-header-height);\n padding: 14px 16px;\n border-bottom: none;\n background: var(--surface-card);\n flex-shrink: 0;\n position: sticky;\n top: 0;\n z-index: 3;\n}\n\n.gengage-chat-panel-topbar-title-wrap {\n display: flex;\n align-items: center;\n min-width: 0;\n flex: 1 1 0;\n}\n\n.gengage-chat-panel-topbar-back,\n.gengage-chat-panel-topbar-forward {\n width: 42px;\n height: 42px;\n min-width: 42px;\n min-height: 42px;\n padding: 0;\n border: 1px solid var(--border-default);\n border-radius: var(--radius-control);\n background: var(--surface-card);\n color: var(--text-primary);\n font-size: 0;\n font-family: inherit;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n transition:\n background 0.15s ease,\n border-color 0.15s ease,\n opacity 0.15s ease;\n}\n\n.gengage-chat-panel-topbar-back[hidden],\n.gengage-chat-panel-topbar-forward[hidden] {\n display: none;\n}\n\n.gengage-chat-panel-topbar-back:hover:not(:disabled),\n.gengage-chat-panel-topbar-forward:hover:not(:disabled) {\n background: var(--surface-card-soft);\n border-color: color-mix(in srgb, var(--client-primary) 18%, var(--border-default));\n}\n\n.gengage-chat-panel-topbar-back:disabled,\n.gengage-chat-panel-topbar-forward:disabled {\n opacity: 0.3;\n cursor: default;\n}\n\n.gengage-chat-panel-topbar-title {\n display: block;\n width: 100%;\n font-size: 17px;\n font-weight: 700;\n line-height: 1.2;\n color: var(--text-primary);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n text-align: left;\n letter-spacing: -0.01em;\n}\n\n.gengage-chat-panel-topbar-actions {\n display: flex;\n align-items: center;\n justify-content: flex-end;\n gap: 8px;\n min-width: 0;\n flex: 0 0 auto;\n}\n\n.gengage-chat-panel-topbar-actions:empty {\n display: none;\n}\n\n.gengage-chat-panel-topbar-toolbar-host {\n display: flex;\n align-items: center;\n justify-content: flex-end;\n width: 100%;\n}\n\n.gengage-chat-panel-topbar-toolbar-host .gengage-chat-product-sort-toolbar {\n flex-wrap: nowrap;\n justify-content: flex-end;\n gap: 8px;\n}\n\n/* Close button — hidden on desktop, shown on mobile as an escape hatch to\n dismiss all panel layers at once without tapping Back repeatedly. */\n.gengage-chat-panel-topbar-close {\n display: none;\n}\n\n/* ── Typewriter block reveal animation ── */\n@keyframes gengage-typewriter-fade-in {\n from {\n opacity: 0;\n transform: translateY(4px);\n }\n to {\n opacity: 1;\n transform: translateY(0);\n }\n}\n\n.gengage-chat-typewriter-block {\n display: block;\n animation: gengage-typewriter-fade-in 0.15s ease both;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .gengage-chat-typewriter-block {\n animation: none;\n }\n}\n\n/* ── Product mention links in bot text ── */\n.gengage-product-mention {\n color: var(--client-primary);\n text-decoration: none;\n cursor: pointer;\n font-weight: 500;\n border-bottom: 1px dashed var(--client-primary);\n transition: border-color 0.15s;\n}\n\n.gengage-product-mention:hover {\n border-bottom-style: solid;\n}\n\n/* ── Panel skeleton pulse (analyze animation) ── */\n@keyframes gengage-skeleton-pulse {\n 0%,\n 100% {\n opacity: 0.4;\n }\n 50% {\n opacity: 0.8;\n }\n}\n\n.gengage-chat-panel-skeleton-block {\n animation: gengage-skeleton-pulse 1.5s ease-in-out infinite;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .gengage-chat-panel-skeleton-block {\n animation: none;\n }\n}\n\n/* ── Input-area chips (compact shortcuts above input) ── */\n.gengage-chat-input-chips {\n display: flex;\n gap: 6px;\n padding: 4px 12px 2px;\n flex-wrap: nowrap;\n overflow-x: auto;\n scrollbar-width: none;\n -ms-overflow-style: none;\n}\n\n.gengage-chat-input-chips::-webkit-scrollbar {\n display: none;\n}\n\n.gengage-chat-input-chip {\n display: inline-flex;\n align-items: center;\n padding: 8px 12px;\n border-radius: var(--radius-pill);\n border: 1px solid var(--border-default);\n background: var(--surface-card);\n color: var(--text-secondary);\n font-size: 12px;\n font-weight: 700;\n font-family: inherit;\n cursor: pointer;\n transition:\n background 0.12s,\n border-color 0.12s,\n color 0.12s;\n white-space: nowrap;\n}\n\n.gengage-chat-input-chip:hover {\n background: var(--client-primary-subtle);\n border-color: color-mix(in srgb, var(--client-primary) 24%, var(--border-default));\n color: var(--client-primary);\n}\n\n/* Suggested action icons */\n.gengage-chat-icon {\n width: 14px;\n height: 14px;\n flex-shrink: 0;\n}\n\n.gengage-chat-input-chip-icon {\n display: inline-flex;\n align-items: center;\n margin-right: 4px;\n}\n\n.gengage-chat-pill-icon {\n display: inline-flex;\n align-items: center;\n margin-right: 4px;\n}\n\n/* ── Product card/details image wrapper (positioned container for overlays) ── */\n.gengage-chat-product-card-img-wrapper,\n.gengage-chat-product-details-img-wrap {\n position: relative;\n overflow: hidden;\n}\n\n/* ── Product card enrichment: discount badge ── */\n.gengage-chat-product-card-discount-badge {\n position: absolute;\n top: 8px;\n left: 8px;\n padding: 3px 8px;\n border-radius: var(--radius-pill);\n background: var(--_gengage-discount-color);\n color: var(--text-inverse);\n font-size: 11px;\n font-weight: 700;\n line-height: 1.3;\n z-index: 1;\n}\n\n/* ── Product card enrichment: stock indicator ── */\n.gengage-chat-product-card-stock {\n font-size: 11px;\n font-weight: 600;\n margin-top: 2px;\n padding: 2px 6px;\n border-radius: var(--radius-pill);\n width: fit-content;\n}\n\n.gengage-chat-product-card-stock.is-in-stock {\n background: color-mix(in srgb, var(--success) 12%, white);\n color: color-mix(in srgb, var(--success) 82%, var(--text-primary));\n}\n\n.gengage-chat-product-card-stock.is-out-of-stock {\n background: color-mix(in srgb, var(--error) 10%, white);\n color: var(--error);\n}\n\n/* ── Find Similar (icon in action stack; details panel keeps hover pill) ── */\n.gengage-chat-find-similar-pill {\n position: relative;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 28px;\n height: 28px;\n padding: 0;\n font-size: 11px;\n font-weight: 500;\n font-family: inherit;\n border: 1px solid var(--border-subtle);\n border-radius: var(--radius-control);\n background: color-mix(in srgb, var(--surface-elevated) 96%, transparent);\n color: var(--text-primary);\n box-shadow: var(--shadow-1);\n cursor: pointer;\n z-index: 2;\n transition:\n opacity 0.2s ease,\n box-shadow 0.15s ease;\n}\n\n.gengage-chat-find-similar-pill:hover {\n box-shadow: var(--shadow-2);\n border-color: color-mix(in srgb, var(--client-primary) 18%, var(--border-default));\n}\n\n@media (hover: hover) and (pointer: fine) {\n .gengage-chat-find-similar-pill::after {\n content: attr(data-tooltip);\n position: absolute;\n right: calc(100% + 8px);\n top: 50%;\n transform: translateY(-50%);\n padding: 6px 10px;\n border-radius: 10px;\n background: color-mix(in srgb, var(--surface-shell) 98%, black);\n border: 1px solid color-mix(in srgb, var(--surface-shell) 60%, white);\n box-shadow: var(--shadow-2);\n color: var(--text-inverse);\n font-size: 11px;\n font-weight: 600;\n line-height: 1.2;\n white-space: nowrap;\n opacity: 0;\n pointer-events: none;\n transition: opacity 0.14s ease;\n z-index: 12;\n }\n\n .gengage-chat-find-similar-pill:hover::after,\n .gengage-chat-find-similar-pill:focus-visible::after {\n opacity: 1;\n }\n}\n\n.gengage-chat-find-similar-pill-icon {\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n.gengage-chat-find-similar-pill-icon svg {\n display: block;\n}\n\n/* Screen-reader label; visible text mode only in wide / panel contexts */\n.gengage-chat-find-similar-pill-text {\n position: absolute;\n width: 1px;\n height: 1px;\n padding: 0;\n margin: -1px;\n overflow: hidden;\n clip: rect(0, 0, 0, 0);\n white-space: nowrap;\n border: 0;\n}\n\n/* Fine pointer: tuck actions until hover on narrow cards; touch always shows */\n.gengage-chat-product-card-img-wrapper .gengage-chat-product-card-img-actions,\n.gengage-chat-ai-toppick-media .gengage-chat-product-card-img-actions {\n transition: opacity 0.2s ease;\n}\n\n@media (hover: hover) and (pointer: fine) {\n .gengage-chat-product-card-img-wrapper:not(:hover) .gengage-chat-product-card-img-actions,\n .gengage-chat-ai-toppick-media:not(:hover) .gengage-chat-product-card-img-actions {\n opacity: 0;\n pointer-events: none;\n }\n\n .gengage-chat-product-card-img-wrapper:hover .gengage-chat-product-card-img-actions,\n .gengage-chat-ai-toppick-media:hover .gengage-chat-product-card-img-actions {\n opacity: 1;\n pointer-events: auto;\n }\n}\n\n@media (hover: none), (pointer: coarse) {\n .gengage-chat-product-card-img-wrapper .gengage-chat-product-card-img-actions,\n .gengage-chat-ai-toppick-media .gengage-chat-product-card-img-actions {\n opacity: 1;\n pointer-events: auto;\n }\n}\n\n/* Product details: legacy dark hover pill (not using img-actions stack) */\n.gengage-chat-product-details-img-wrap .gengage-chat-find-similar-pill {\n position: absolute;\n top: 40px;\n right: 8px;\n width: auto;\n height: auto;\n padding: 4px 10px;\n border-radius: var(--radius-pill);\n background: color-mix(in srgb, var(--surface-shell) 88%, transparent);\n color: var(--text-inverse);\n box-shadow: none;\n opacity: 0;\n pointer-events: none;\n}\n\n.gengage-chat-product-details-img-wrap .gengage-chat-find-similar-pill-icon {\n display: none;\n}\n\n.gengage-chat-product-details-img-wrap .gengage-chat-find-similar-pill-text {\n position: static;\n width: auto;\n height: auto;\n margin: 0;\n overflow: visible;\n clip: auto;\n white-space: nowrap;\n}\n\n.gengage-chat-product-card-img-wrapper:hover .gengage-chat-find-similar-pill,\n.gengage-chat-ai-toppick-media:hover .gengage-chat-find-similar-pill,\n.gengage-chat-product-details-img-wrap:hover .gengage-chat-find-similar-pill {\n opacity: 1;\n pointer-events: auto;\n}\n\n/* ── Favorite (stacked with find-similar on product cards) ── */\n.gengage-chat-favorite-btn {\n position: absolute;\n top: 8px;\n right: 8px;\n background: color-mix(in srgb, var(--surface-elevated) 96%, transparent);\n border: 1px solid var(--border-subtle);\n border-radius: var(--radius-control);\n width: 28px;\n height: 28px;\n display: flex;\n align-items: center;\n justify-content: center;\n cursor: pointer;\n color: var(--text-secondary);\n box-shadow: var(--shadow-1);\n z-index: 3;\n transition:\n color 0.2s,\n box-shadow 0.15s ease;\n padding: 0;\n}\n\n.gengage-chat-favorite-btn:hover {\n box-shadow: var(--shadow-2);\n color: var(--error);\n border-color: color-mix(in srgb, var(--error) 20%, var(--border-default));\n}\n\n.gengage-chat-favorite-btn--active {\n color: var(--error);\n}\n\n/* ── Product-detail promotion badges ── */\n.gengage-chat-product-details-promos {\n display: flex;\n flex-wrap: wrap;\n gap: 4px;\n margin-top: 4px;\n}\n\n.gengage-chat-product-details-promo-badge {\n display: inline-block;\n padding: 2px 6px;\n font-size: 10px;\n font-weight: 500;\n border-radius: var(--radius-pill);\n background: var(--gengage-chat-promo-bg, color-mix(in srgb, var(--success) 12%, white));\n color: var(--gengage-chat-promo-text, color-mix(in srgb, var(--success) 82%, var(--text-primary)));\n white-space: nowrap;\n min-height: 20px;\n box-sizing: border-box;\n}\n\n/* ── ThumbnailsColumn ──\n * Logic/DOM preserved; strip not shown in product panel (legacy parity). */\n.gengage-chat-thumbnails-column {\n display: none !important;\n pointer-events: none;\n}\n\n.gengage-chat-thumbnail-btn {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 40px;\n height: 40px;\n padding: 0;\n border: 2px solid var(--border-default);\n border-radius: calc(var(--radius-control) - 4px);\n background: var(--surface-card);\n cursor: pointer;\n overflow: hidden;\n transition: border-color 0.15s ease;\n}\n\n.gengage-chat-thumbnail-btn:hover {\n border-color: var(--client-primary);\n}\n\n.gengage-chat-thumbnail-img {\n width: 100%;\n height: 100%;\n object-fit: cover;\n}\n\n/* ── Panel floating overlay: sticky zero-height anchor at the bottom of the scroll area.\n Children (e.g. ChoicePrompter) use position:absolute and stay fixed to the visible area.\n margin-top:auto: when the grid is shorter than the panel (few products), consume flex free\n space so the anchor sits at the bottom of the pane; sticky+bottom keeps it there on scroll. ── */\n.gengage-chat-panel-float {\n position: sticky;\n bottom: 0;\n align-self: stretch;\n width: 100%;\n box-sizing: border-box;\n margin-top: auto;\n flex-shrink: 0;\n height: 0;\n overflow: visible;\n pointer-events: none;\n z-index: 10;\n}\n\n/* ── ChoicePrompter ── */\n.gengage-chat-choice-prompter {\n position: absolute;\n left: 16px;\n right: 16px;\n bottom: 12px;\n pointer-events: none;\n width: auto;\n max-width: none;\n margin-left: 0;\n margin-right: 0;\n padding: 12px 14px;\n background: var(--surface-card);\n border: 1px solid var(--border-default);\n border-radius: 16px;\n box-shadow: var(--shadow-2);\n z-index: 5;\n display: grid;\n grid-template-columns: minmax(0, 1fr) auto auto;\n gap: 12px;\n align-items: center;\n opacity: 0;\n transform: translateY(12px);\n transition:\n opacity 0.2s ease,\n transform 0.2s ease;\n}\n\n.gengage-chat-panel--scrolled .gengage-chat-choice-prompter {\n pointer-events: auto;\n opacity: 1;\n transform: translateY(0);\n}\n\n.gengage-chat-choice-prompter-heading {\n font-size: 12px;\n font-weight: 700;\n color: var(--text-primary);\n margin-bottom: 2px;\n}\n\n.gengage-chat-choice-prompter-suggestion {\n font-size: 12px;\n color: var(--text-secondary);\n margin-bottom: 0;\n line-height: 1.35;\n}\n\n.gengage-chat-choice-prompter-copy {\n min-width: 0;\n}\n\n.gengage-chat-choice-prompter-cta {\n display: inline-flex;\n width: auto;\n min-width: 180px;\n min-height: 40px;\n padding: 0 18px;\n border: 1px solid var(--ds-button-primary-border);\n border-radius: var(--radius-control);\n background: var(--ds-button-primary-bg);\n color: var(--ds-button-primary-fg);\n font-size: 13px;\n font-weight: 700;\n font-family: inherit;\n cursor: pointer;\n text-align: center;\n transition: background 0.15s ease;\n}\n\n.gengage-chat-choice-prompter-cta:hover {\n background: var(--ds-button-primary-bg-hover);\n}\n\n.gengage-chat-choice-prompter-dismiss {\n position: absolute;\n top: 10px;\n right: 10px;\n padding: 0;\n min-width: 32px;\n min-height: 32px;\n display: flex;\n align-items: center;\n justify-content: center;\n border: 1px solid var(--border-default);\n border-radius: 12px;\n background: var(--surface-card-soft);\n color: var(--text-muted);\n font-size: 16px;\n cursor: pointer;\n line-height: 1;\n}\n\n.gengage-chat-choice-prompter-dismiss:hover {\n color: var(--client-primary);\n border-color: color-mix(in srgb, var(--client-primary) 18%, white);\n background: var(--client-primary-subtle);\n}\n\n@media (prefers-reduced-motion: reduce) {\n .gengage-chat-choice-prompter {\n animation: none;\n }\n}\n\n/* Pros & Cons */\n.gengage-chat-pros-cons {\n padding: 12px 14px;\n background: var(--surface-card-soft);\n border: 1px solid var(--border-subtle);\n border-radius: var(--radius-control);\n margin: 8px 0;\n}\n\n.gengage-chat-pros-cons-heading {\n margin: 0 0 10px;\n font-size: 14px;\n font-weight: 600;\n color: var(--text-primary);\n}\n\n.gengage-chat-pros-cons-list {\n list-style: none;\n margin: 0 0 8px;\n padding: 0;\n}\n\n.gengage-chat-pros-cons-list:last-child {\n margin-bottom: 0;\n}\n\n.gengage-chat-pros-cons-item {\n display: flex;\n align-items: flex-start;\n gap: 8px;\n padding: 4px 0;\n font-size: 13px;\n line-height: 1.5;\n color: var(--text-secondary);\n}\n\n.gengage-chat-pros-cons-icon {\n flex-shrink: 0;\n width: 18px;\n height: 18px;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 12px;\n font-weight: 700;\n border-radius: 50%;\n}\n\n.gengage-chat-pros-cons-icon--pro {\n color: var(--success);\n background: color-mix(in srgb, var(--success) 15%, white);\n}\n\n.gengage-chat-pros-cons-icon--con {\n color: var(--error);\n background: color-mix(in srgb, var(--error) 12%, white);\n}\n\n/* ===== CategoriesContainer ===== */\n.gengage-chat-categories {\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n\n.gengage-chat-categories-tabs {\n display: flex;\n gap: 4px;\n overflow-x: auto;\n padding: 4px 0;\n -webkit-overflow-scrolling: touch;\n}\n\n.gengage-chat-categories-tab {\n padding: 6px 14px;\n border: 1px solid var(--border-default);\n border-radius: var(--radius-pill);\n background: var(--surface-card);\n color: var(--text-secondary);\n cursor: pointer;\n white-space: nowrap;\n font-size: 13px;\n transition:\n background 0.15s,\n color 0.15s,\n border-color 0.15s;\n}\n\n.gengage-chat-categories-tab:hover {\n background: var(--surface-card-soft);\n border-color: var(--border-strong);\n}\n\n.gengage-chat-categories-tab--active {\n background: var(--client-primary-subtle);\n color: var(--client-primary);\n border-color: color-mix(in srgb, var(--client-primary) 26%, var(--border-default));\n}\n\n.gengage-chat-categories-grid {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));\n gap: 8px;\n}\n\n.gengage-chat-categories-filter-tags {\n display: flex;\n gap: 6px;\n flex-wrap: wrap;\n margin-top: 4px;\n}\n\n.gengage-chat-categories-filter-tag {\n padding: 4px 10px;\n border: 1px solid var(--border-default);\n border-radius: var(--radius-pill);\n background: var(--surface-card);\n color: var(--text-secondary);\n font-size: 12px;\n cursor: pointer;\n transition:\n background 0.15s,\n border-color 0.15s,\n color 0.15s;\n}\n\n.gengage-chat-categories-filter-tag:hover {\n background: var(--client-primary-subtle);\n border-color: color-mix(in srgb, var(--client-primary) 18%, var(--border-default));\n color: var(--client-primary);\n}\n\n/* ===== View More button ===== */\n.gengage-chat-product-grid-view-more {\n display: block;\n width: 100%;\n padding: 10px;\n margin-top: 8px;\n border: 1px solid var(--ds-button-secondary-border);\n border-radius: var(--radius-control);\n background: var(--ds-button-secondary-bg);\n color: var(--ds-button-secondary-fg);\n font-size: 14px;\n font-weight: 500;\n cursor: pointer;\n text-align: center;\n transition: background 0.15s;\n grid-column: 1 / -1;\n}\n\n.gengage-chat-product-grid-view-more:hover {\n background: var(--ds-button-secondary-bg-hover);\n}\n\n/* ===== Mobile product grid (narrow screen: 2 products per row, no horizontal scroll) ===== */\n.gengage-chat-product-grid--mobile {\n display: grid;\n grid-template-columns: repeat(2, minmax(0, 1fr));\n gap: 8px;\n padding: 8px 0;\n overflow-x: visible;\n}\n\n.gengage-chat-product-grid--mobile .gengage-chat-product-card {\n width: 100%;\n min-width: 0;\n max-width: none;\n}\n\n/* ===== Mobile comparison ===== */\n/* The table-wrapper scrolls horizontally; the outer container clips without scrolling. */\n.gengage-chat-comparison--mobile {\n overflow-x: clip; /* prevent the container from overflowing the page */\n position: relative;\n}\n\n/* Scrollable wrapper around the table only */\n.gengage-chat-comparison--mobile .gengage-chat-comparison-table-wrapper {\n overflow-x: auto;\n -webkit-overflow-scrolling: touch;\n touch-action: pan-x pan-y;\n /* Right-edge fade signals horizontal scrollability */\n -webkit-mask-image: linear-gradient(to right, black 88%, transparent);\n mask-image: linear-gradient(to right, black 88%, transparent);\n}\n\n.gengage-chat-comparison--mobile .gengage-chat-comparison-table {\n min-width: 480px;\n table-layout: fixed;\n}\n\n/* Sticky first column: label column stays fixed while products scroll */\n.gengage-chat-comparison--mobile .gengage-chat-comparison-table th:first-child,\n.gengage-chat-comparison--mobile .gengage-chat-comparison-table .gengage-chat-comparison-label {\n position: sticky;\n left: 0;\n background: var(--surface-card);\n z-index: 1;\n /* Shadow instead of border to avoid border-collapse artefacts */\n box-shadow: 2px 0 4px color-mix(in srgb, var(--surface-shell) 8%, transparent);\n}\n\n/* First column: left-aligned and narrow — long criterion names truncate with ellipsis */\n.gengage-chat-comparison--mobile .gengage-chat-comparison-table thead th:first-child {\n width: 22ch;\n max-width: 22ch;\n min-width: 0;\n padding: 8px 6px;\n box-sizing: border-box;\n}\n\n.gengage-chat-comparison--mobile .gengage-chat-comparison-table td.gengage-chat-comparison-label {\n text-align: left;\n width: 22ch;\n max-width: 22ch;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n box-sizing: border-box;\n vertical-align: middle;\n}\n\n/* ===== Similar Products heading in panel ===== */\n.gengage-chat-product-details-similars-heading {\n margin: 16px 0 8px 0;\n font-size: 18px;\n font-weight: 700;\n line-height: 1.2;\n color: var(--text-primary);\n}\n\n.gengage-chat-product-details-similars {\n margin-top: 8px;\n}\n\n/* Handoff Notice */\n.gengage-chat-handoff-notice {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 6px;\n padding: 16px;\n margin: 8px 0;\n background: color-mix(in srgb, var(--warning) 10%, white);\n border: 1px solid color-mix(in srgb, var(--warning) 28%, var(--border-default));\n border-radius: var(--radius-control);\n text-align: center;\n}\n\n.gengage-chat-handoff-notice-icon {\n font-size: 24px;\n}\n\n.gengage-chat-handoff-notice-heading {\n margin: 0;\n font-size: 14px;\n font-weight: 600;\n color: color-mix(in srgb, var(--warning) 72%, var(--text-primary));\n}\n\n.gengage-chat-handoff-notice-summary {\n margin: 0;\n font-size: 13px;\n line-height: 1.5;\n color: color-mix(in srgb, var(--warning) 84%, var(--text-primary));\n}\n\n/* ---- Share button ---- */\n.gengage-chat-product-details-share {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 36px;\n height: 36px;\n border: 1px solid var(--border-default);\n border-radius: var(--radius-control);\n background: var(--surface-card);\n color: var(--text-muted);\n cursor: pointer;\n transition:\n color 0.12s,\n border-color 0.12s,\n background-color 0.12s;\n position: relative;\n flex-shrink: 0;\n}\n\n.gengage-chat-product-details-share:hover {\n color: var(--client-primary);\n border-color: var(--client-primary);\n background: var(--client-primary-subtle);\n}\n\n/* CSS-only tooltip for share button */\n.gengage-chat-product-details-share::before {\n content: attr(aria-label);\n position: absolute;\n bottom: calc(100% + 6px);\n left: 50%;\n transform: translateX(-50%);\n padding: 4px 8px;\n border-radius: calc(var(--radius-control) - 4px);\n background: var(--surface-shell);\n color: var(--text-inverse);\n font-size: 11px;\n font-weight: 500;\n white-space: nowrap;\n pointer-events: none;\n opacity: 0;\n transition: opacity 0.15s ease;\n z-index: 2;\n}\n\n.gengage-chat-product-details-share:hover::before {\n opacity: 1;\n}\n\n.gengage-chat-product-details-share--copied::after {\n content: '\\2713';\n position: absolute;\n inset: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n background: var(--_gengage-success-color);\n color: var(--client-on-primary);\n border-radius: var(--radius-control);\n font-size: 16px;\n font-weight: 700;\n}\n\n/* ── Share button SVG sizing ── */\n.gengage-chat-product-details-share svg {\n width: 20px;\n height: 20px;\n}\n\n/* ── Review / grounding link styling ── */\n.gengage-chat-review-link,\n.gengage-chat-grounding-link {\n text-decoration: underline;\n color: var(--client-primary);\n}\n\n/* ── Product-detail promotion badge mobile clamp ── */\n@media (max-width: 768px) {\n .gengage-chat-product-details-promo-badge {\n -webkit-line-clamp: 2;\n line-clamp: 2;\n display: -webkit-box;\n -webkit-box-orient: vertical;\n overflow: hidden;\n white-space: normal;\n }\n}\n\n/* ---------------------------------------------------------------------------\n * Focus-visible outlines (WCAG 2.4.7)\n * ---------------------------------------------------------------------------*/\n.gengage-chat-launcher:focus-visible,\n.gengage-chat-close:focus-visible,\n.gengage-chat-header-btn:focus-visible,\n.gengage-chat-header-powered:focus-visible,\n.gengage-chat-send:focus-visible,\n.gengage-chat-action-btn:focus-visible,\n.gengage-chat-pill:focus-visible,\n.gengage-chat-input-chip:focus-visible,\n.gengage-chat-pills-arrow:focus-visible,\n.gengage-chat-error-retry:focus-visible,\n.gengage-chat-product-card-cta:focus-visible,\n.gengage-chat-product-details-cta:focus-visible,\n.gengage-chat-product-details-atc:focus-visible,\n.gengage-chat-product-detail-tab:focus-visible,\n.gengage-chat-panel-divider-toggle:focus-visible,\n.gengage-chat-panel-topbar-back:focus-visible,\n.gengage-chat-panel-topbar-forward:focus-visible,\n.gengage-chat-rollback-btn:focus-visible,\n.gengage-chat-ai-toppick-cta:focus-visible,\n.gengage-chat-comparison-toggle-btn:focus-visible,\n.gengage-chat-comparison-floating-btn:focus-visible,\n.gengage-chat-comparison-checkbox:focus-visible,\n.gengage-chat-product-sort-trigger:focus-visible,\n.gengage-chat-product-sort-option:focus-visible,\n.gengage-chat-product-variant-btn:focus-visible,\n.gengage-chat-product-gallery-thumb:focus-visible,\n.gengage-chat-thumbnail-btn:focus-visible,\n.gengage-chat-find-similar-pill:focus-visible,\n.gengage-chat-favorite-btn:focus-visible,\n.gengage-chat-attach-btn:focus-visible,\n.gengage-chat-attach-menu-item:focus-visible,\n.gengage-chat-mic-btn:focus-visible,\n.gengage-chat-attachment-remove:focus-visible,\n.gengage-chat-kvkk-dismiss:focus-visible,\n.gengage-chat-choice-prompter-cta:focus-visible,\n.gengage-chat-choice-prompter-dismiss:focus-visible,\n.gengage-chat-categories-tab:focus-visible,\n.gengage-chat-categories-filter-tag:focus-visible,\n.gengage-chat-product-grid-view-more:focus-visible,\n.gengage-chat-product-details-share:focus-visible {\n outline: 2px solid var(--client-focus-ring);\n outline-offset: 2px;\n}\n\n/* Mobile touch targets — WCAG 2.5.5 (44px minimum) */\n.gengage-chat-root--mobile .gengage-chat-choice-prompter {\n left: 12px;\n right: 12px;\n width: auto;\n max-width: none;\n bottom: 12px;\n max-height: 40vh;\n overflow-y: auto;\n grid-template-columns: 1fr;\n}\n\n.gengage-chat-root--mobile .gengage-chat-choice-prompter-cta {\n width: 100%;\n}\n\n.gengage-chat-root--mobile .gengage-chat-product-sort-trigger,\n.gengage-chat-root--mobile .gengage-chat-product-sort-option,\n.gengage-chat-root--mobile .gengage-chat-comparison-toggle-btn {\n min-height: 40px;\n}\n\n/* Panel (side sheet): sort + compare — icon only on mobile */\n.gengage-chat-root--mobile .gengage-chat-panel .gengage-chat-product-sort-trigger-label,\n.gengage-chat-root--mobile .gengage-chat-panel .gengage-chat-product-sort-trigger-chevron,\n.gengage-chat-root--mobile .gengage-chat-panel .gengage-chat-comparison-toggle-label {\n display: none;\n}\n\n.gengage-chat-root--mobile .gengage-chat-panel .gengage-chat-product-sort-dropdown {\n flex: 0 0 auto;\n max-width: none;\n width: auto;\n}\n\n.gengage-chat-root--mobile .gengage-chat-panel .gengage-chat-product-sort-trigger {\n width: auto;\n min-width: 44px;\n justify-content: center;\n padding: 0 10px;\n gap: 0;\n}\n\n.gengage-chat-root--mobile .gengage-chat-panel .gengage-chat-comparison-toggle-btn {\n min-width: 44px;\n width: 44px;\n padding: 0;\n justify-content: center;\n gap: 0;\n}\n\n.gengage-chat-root--mobile .gengage-chat-input-chip {\n min-height: 40px;\n padding: 8px 12px;\n}\n\n.gengage-chat-root--mobile .gengage-chat-choice-prompter-dismiss {\n width: 32px;\n height: 32px;\n}\n\n.gengage-chat-root--mobile .gengage-chat-panel-divider-toggle {\n min-height: 44px;\n}\n\n.gengage-chat-root--mobile .gengage-chat-product-card-cta,\n.gengage-chat-root--mobile .gengage-chat-choice-prompter-cta,\n.gengage-chat-root--mobile .gengage-chat-product-grid-view-more {\n min-height: 44px;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n.gengage-chat-root--mobile .gengage-chat-panel-topbar-back,\n.gengage-chat-root--mobile .gengage-chat-panel-topbar-forward,\n.gengage-chat-root--mobile .gengage-chat-favorite-btn,\n.gengage-chat-root--mobile .gengage-chat-product-details-share {\n width: 44px;\n height: 44px;\n min-width: 44px;\n}\n\n/* Panel close (✕) — hidden on mobile; use header “reopen panel” / back stack instead */\n.gengage-chat-root--mobile .gengage-chat-panel-topbar-close {\n display: none;\n}\n\n/* ---------------------------------------------------------------------------\n * Shared loading skeleton (src/common/skeleton.ts)\n * ---------------------------------------------------------------------------*/\n.gengage-skeleton {\n display: flex;\n gap: 12px;\n padding: 12px;\n}\n\n.gengage-skeleton--message {\n flex-direction: column;\n gap: 8px;\n padding: 16px;\n}\n\n.gengage-skeleton-card {\n flex: 1;\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n\n.gengage-skeleton-img {\n height: 120px;\n border-radius: var(--radius-control);\n}\n\n.gengage-skeleton-text {\n height: 14px;\n border-radius: 4px;\n}\n\n.gengage-skeleton-price {\n height: 14px;\n width: 60%;\n border-radius: 4px;\n}\n\n.gengage-skeleton-line {\n height: 12px;\n border-radius: 4px;\n}\n\n@media (prefers-reduced-motion: no-preference) {\n .gengage-skeleton-card,\n .gengage-skeleton-line,\n .gengage-skeleton-img,\n .gengage-skeleton-text,\n .gengage-skeleton-price {\n background: linear-gradient(90deg, var(--ds-neutral-100) 25%, var(--ds-neutral-200) 50%, var(--ds-neutral-100) 75%);\n background-size: 200% 100%;\n animation: gengage-shimmer 1.5s infinite;\n }\n\n @keyframes gengage-shimmer {\n 0% {\n background-position: 200% 0;\n }\n 100% {\n background-position: -200% 0;\n }\n }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .gengage-skeleton-card,\n .gengage-skeleton-line,\n .gengage-skeleton-img,\n .gengage-skeleton-text,\n .gengage-skeleton-price {\n background: var(--ds-neutral-100);\n }\n}\n\n/* ---------------------------------------------------------------------------\n * Panel scroll affordance (bottom fade gradient)\n * ---------------------------------------------------------------------------*/\n.gengage-chat-panel--has-scroll::after {\n content: '';\n position: sticky;\n bottom: 0;\n display: block;\n height: 40px;\n background: linear-gradient(transparent, var(--surface-card-soft));\n pointer-events: none;\n}\n\n/* ---------------------------------------------------------------------------\n * Panel content crossfade transition\n * ---------------------------------------------------------------------------*/\n@media (prefers-reduced-motion: no-preference) {\n .gengage-chat-panel--transitioning {\n opacity: 0;\n transition: opacity 150ms ease;\n }\n}\n\n/* ---------------------------------------------------------------------------\n * Cart success toast\n * ---------------------------------------------------------------------------*/\n.gengage-chat-cart-toast {\n position: absolute;\n bottom: 72px;\n left: 50%;\n transform: translateX(-50%) translateY(8px);\n background: var(--success);\n color: var(--text-inverse);\n font-size: 13px;\n font-weight: 600;\n padding: 8px 18px;\n border-radius: var(--radius-pill);\n box-shadow: var(--shadow-3);\n pointer-events: none;\n opacity: 0;\n transition:\n opacity 0.2s ease,\n transform 0.2s ease;\n z-index: 10;\n white-space: nowrap;\n}\n\n.gengage-chat-cart-toast--visible {\n opacity: 1;\n transform: translateX(-50%) translateY(0);\n}\n\n/* Cart button flash animation */\n@keyframes gengage-cart-flash {\n 0% {\n transform: scale(1);\n }\n 30% {\n transform: scale(1.25);\n }\n 60% {\n transform: scale(0.95);\n }\n 100% {\n transform: scale(1);\n }\n}\n\n.gengage-chat-header-btn--cart-flash {\n animation: gengage-cart-flash 0.5s ease;\n}\n","/**\n * Chat widget — json-render catalog definition.\n *\n * This catalog describes all UI components the chat widget understands.\n * The backend streams `ui_spec` events whose element types must match names\n * defined here. Component implementations live in ./registry.tsx.\n *\n * HOW IT WORKS:\n * 1. Backend streams NDJSON events including `ui_spec` events.\n * 2. Each `ui_spec` event contains a `spec: UISpec` field.\n * 3. The `<ChatRenderer>` component feeds specs into json-render's `<Renderer>`.\n * 4. json-render looks up the element `type` in the registry and renders it.\n *\n * CUSTOMISING:\n * Fork this repo, edit ./registry.tsx to swap in your own components.\n * The catalog schema below stays the same — only the visual implementation changes.\n *\n * See: https://github.com/vercel-labs/json-render\n */\n\nimport { z } from 'zod';\n\n// ---------------------------------------------------------------------------\n// Component schemas (Zod)\n// ---------------------------------------------------------------------------\n\nexport const MessageBubbleSchema = z.object({\n role: z.enum(['user', 'assistant']),\n content: z.string(),\n timestamp: z.number().optional(),\n});\n\nexport const ProductCardSchema = z.object({\n sku: z.string(),\n name: z.string(),\n imageUrl: z.string().url().optional(),\n price: z.string().optional(),\n originalPrice: z.string().optional(),\n url: z.string().url(),\n /** Override CTA label (default: \"View product\") */\n ctaLabel: z.string().optional(),\n});\n\nexport const ActionButtonsSchema = z.object({\n buttons: z.array(\n z.object({\n label: z.string(),\n /** Opaque action payload forwarded to the backend when clicked. */\n action: z.object({\n title: z.string(),\n type: z.string(),\n payload: z.unknown().optional(),\n }),\n }),\n ),\n});\n\nexport const TypingIndicatorSchema = z.object({});\n\nexport const DividerSchema = z.object({\n label: z.string().optional(),\n});\n\nconst ComparisonProductSchema = z.object({\n sku: z.string(),\n name: z.string(),\n price: z.string(),\n imageUrl: z.string().optional(),\n rating: z.number().optional(),\n reviewCount: z.number().optional(),\n});\n\nexport const ComparisonTableSchema = z.object({\n recommended: ComparisonProductSchema,\n products: z.array(ComparisonProductSchema),\n attributes: z.array(\n z.object({\n label: z.string(),\n values: z.array(z.string()),\n }),\n ),\n highlights: z.array(z.string()),\n specialCases: z.array(z.string()).optional(),\n recommendedText: z.string().optional(),\n winnerHits: z\n .record(\n z.string(),\n z.object({\n positive: z.array(z.string()).optional(),\n negative: z.array(z.string()).optional(),\n }),\n )\n .optional(),\n productActions: z\n .record(\n z.string(),\n z.object({\n title: z.string(),\n type: z.string(),\n payload: z.unknown().optional(),\n }),\n )\n .optional(),\n});\n\nexport const SentimentLabelSchema = z.object({\n label: z.string(),\n sentiment: z.enum(['positive', 'negative', 'neutral']).optional(),\n});\n\nexport const AITopPickItemSchema = z.object({\n product: z.record(z.string(), z.unknown()),\n role: z.string().optional(),\n reason: z.string().optional(),\n labels: z.array(SentimentLabelSchema).optional(),\n expertQualityScore: z.number().optional(),\n reviewHighlight: z.string().optional(),\n action: z\n .object({\n title: z.string(),\n type: z.string(),\n payload: z.unknown().optional(),\n })\n .optional(),\n});\n\nexport const AITopPicksSchema = z.object({\n suggestions: z.array(AITopPickItemSchema),\n});\n\nexport const GroundingReviewCardSchema = z.object({\n title: z.string().optional(),\n text: z.string().optional(),\n reviewCount: z.string().optional(),\n action: z.object({ title: z.string(), type: z.string(), payload: z.unknown().optional() }),\n});\n\nexport const AIGroupingCardsSchema = z.object({\n entries: z.array(\n z.object({\n name: z.string(),\n image: z.string().optional(),\n description: z.string().optional(),\n action: z.object({ title: z.string(), type: z.string(), payload: z.unknown().optional() }),\n }),\n ),\n});\n\nexport const AISuggestedSearchCardsSchema = z.object({\n entries: z.array(\n z.object({\n shortName: z.string(),\n detailedMessage: z.string().optional(),\n whyDifferent: z.string().optional(),\n image: z.string().optional(),\n action: z.object({ title: z.string(), type: z.string(), payload: z.unknown().optional() }),\n }),\n ),\n});\n\nconst ActionPayloadSchema = z.object({\n title: z.string(),\n type: z.string(),\n payload: z.unknown().optional(),\n});\n\nconst ProductVariantSchema = z.object({\n name: z.string().optional(),\n value: z.string().optional(),\n option_value: z.string().optional(),\n attribute_value: z.string().optional(),\n type: z.string().optional(),\n attribute: z.string().optional(),\n option_name: z.string().optional(),\n attribute_name: z.string().optional(),\n variant_name: z.string().optional(),\n sku: z.string().optional(),\n price: z.union([z.number(), z.string()]).optional(),\n price_discounted: z.union([z.number(), z.string()]).optional(),\n image: z.string().optional(),\n imageUrl: z.string().optional(),\n image_url: z.string().optional(),\n color: z.string().optional(),\n colour: z.string().optional(),\n color_hex: z.string().optional(),\n hex: z.string().optional(),\n swatch: z.string().optional(),\n swatchColor: z.string().optional(),\n in_stock: z.boolean().optional(),\n inStock: z.boolean().optional(),\n});\n\nexport const ProductDetailsPanelSchema = z.object({\n product: z\n .object({\n sku: z.string().optional(),\n name: z.string().optional(),\n images: z.array(z.string()).optional(),\n imageUrl: z.string().optional(),\n rating: z.number().optional(),\n reviewCount: z.number().optional(),\n price: z.string().optional(),\n originalPrice: z.string().optional(),\n price_discount_rate: z.number().optional(),\n price_async: z.boolean().optional(),\n inStock: z.boolean().optional(),\n promotions: z.array(z.string()).optional(),\n variants: z.array(ProductVariantSchema).optional(),\n variantOptions: z.array(ProductVariantSchema).optional(),\n variant_options: z.array(ProductVariantSchema).optional(),\n productVariants: z.array(ProductVariantSchema).optional(),\n product_variants: z.array(ProductVariantSchema).optional(),\n url: z.string().optional(),\n cartCode: z.string().optional(),\n description: z.string().optional(),\n description_html: z.string().optional(),\n descriptionHtml: z.string().optional(),\n facet_hits: z.record(z.string(), z.unknown()).optional(),\n facetHits: z.record(z.string(), z.unknown()).optional(),\n features: z\n .array(\n z.object({\n name: z.string().optional(),\n key: z.string().optional(),\n label: z.string().optional(),\n value: z.union([z.string(), z.number(), z.boolean()]).optional(),\n }),\n )\n .optional(),\n specifications: z\n .union([z.record(z.string(), z.string()), z.array(z.object({ key: z.string(), value: z.string() }))])\n .optional(),\n })\n .optional(),\n action: ActionPayloadSchema.optional(),\n});\n\nexport const ProductGridSchema = z.object({\n endOfList: z.boolean().optional(),\n});\n\nconst ReviewItemSchema = z.object({\n review_class: z.string().optional(),\n review_text: z.string().optional(),\n review_rating: z.union([z.string(), z.number()]).optional(),\n review_tag: z.string().optional(),\n});\n\nexport const ReviewHighlightsSchema = z.object({\n reviews: z.array(ReviewItemSchema).optional(),\n});\n\nexport const ProsAndConsSchema = z.object({\n productName: z.string().optional(),\n pros: z.array(z.string()).optional(),\n cons: z.array(z.string()).optional(),\n});\n\nexport const CategoriesContainerSchema = z.object({\n groups: z\n .array(\n z.object({\n groupName: z.string(),\n products: z.array(z.record(z.string(), z.unknown())).optional(),\n }),\n )\n .optional(),\n filterTags: z\n .array(\n z.object({\n title: z.string(),\n action: ActionPayloadSchema.optional(),\n }),\n )\n .optional(),\n});\n\nexport const HandoffNoticeSchema = z.object({\n summary: z.string().optional(),\n products_discussed: z.array(z.string()).optional(),\n user_sentiment: z.string().optional(),\n});\n\n// ---------------------------------------------------------------------------\n// Catalog definition\n//\n// When @json-render/core ships a stable defineCatalog() API, replace this\n// plain object with the official defineCatalog() call. For now the schema\n// map acts as both documentation and runtime validation.\n// ---------------------------------------------------------------------------\n\nexport const chatCatalog = {\n components: {\n MessageBubble: {\n schema: MessageBubbleSchema,\n description: 'A single chat message bubble for user or assistant turns.',\n },\n ProductCard: {\n schema: ProductCardSchema,\n description: 'A product card rendered inline in the chat stream.',\n },\n ActionButtons: {\n schema: ActionButtonsSchema,\n description: 'A horizontal row of quick-reply action buttons.',\n },\n TypingIndicator: {\n schema: TypingIndicatorSchema,\n description: 'An animated indicator shown while the assistant is typing.',\n },\n Divider: {\n schema: DividerSchema,\n description: 'A horizontal rule with an optional label.',\n },\n ComparisonTable: {\n schema: ComparisonTableSchema,\n description: 'A product comparison table with recommended pick, attribute rows, and highlights.',\n },\n AITopPicks: {\n schema: AITopPicksSchema,\n description: 'Rich AI-curated product suggestion cards with roles, sentiment labels, scores, and review quotes.',\n },\n GroundingReviewCard: {\n schema: GroundingReviewCardSchema,\n description: 'A card showing review grounding data with review count and CTA.',\n },\n AIGroupingCards: {\n schema: AIGroupingCardsSchema,\n description: 'Category grouping cards with images and labels for product discovery.',\n },\n AISuggestedSearchCards: {\n schema: AISuggestedSearchCardsSchema,\n description: 'Suggested search cards with images, descriptions, and differentiation.',\n },\n ProductDetailsPanel: {\n schema: ProductDetailsPanelSchema,\n description: 'Full product detail view with images, specs, variants, and purchase actions.',\n },\n ProductGrid: {\n schema: ProductGridSchema,\n description: 'A scrollable grid of ProductCard children with optional \"more\" pagination.',\n },\n ReviewHighlights: {\n schema: ReviewHighlightsSchema,\n description: 'A list of highlighted customer reviews with sentiment and ratings.',\n },\n ProsAndCons: {\n schema: ProsAndConsSchema,\n description: 'A pros and cons list for a product.',\n },\n CategoriesContainer: {\n schema: CategoriesContainerSchema,\n description: 'Tabbed product groups with optional filter tag buttons.',\n },\n HandoffNotice: {\n schema: HandoffNoticeSchema,\n description: 'A notice shown when the conversation is escalated to a human agent.',\n },\n },\n} as const;\n\nexport type ChatCatalog = typeof chatCatalog;\nexport type ChatComponentName = keyof ChatCatalog['components'];\n","/**\n * Chat widget -- public entry point.\n *\n * Renders a floating launcher button + slide-in chat drawer inside Shadow DOM\n * for CSS isolation. Handles streaming NDJSON from the backend.\n */\n\nimport type { ActionPayload, PageContext, StreamEvent, StreamEventAction, UISpec } from '../common/types.js';\nimport type { ChatTransportConfig } from '../common/api-paths.js';\nimport type { ActionRouterOptions } from '../common/action-router.js';\nimport type { UISpecRenderHelpers } from '../common/renderer/index.js';\nimport type { BridgeMessage, CommunicationBridgeOptions } from '../common/communication-bridge.js';\nimport type { BackendRequestMeta } from './api.js';\nimport { mergeUISpecRegistry } from '../common/renderer/index.js';\nimport { BaseWidget } from '../common/widget-base.js';\nimport { dispatch } from '../common/events.js';\nimport { uuidv7 } from '../common/uuidv7.js';\nimport { CommunicationBridge } from '../common/communication-bridge.js';\nimport { routeStreamAction } from '../common/action-router.js';\nimport {\n streamStartEvent,\n streamChunkEvent,\n streamDoneEvent,\n streamErrorEvent,\n streamUiSpecEvent,\n llmUsageEvent,\n meteringIncrementEvent,\n chatHistorySnapshotEvent,\n basketAddEvent,\n} from '../common/analytics-events.js';\nimport { sanitizeHtml, isSafeUrl } from '../common/safe-html.js';\nimport { validateImageFile } from './attachment-utils.js';\nimport { sendChatMessage, enrichActionPayload } from './api.js';\nimport { ChatDrawer } from './components/ChatDrawer.js';\nimport { createLauncher } from './components/Launcher.js';\nimport type { LauncherElements } from './components/Launcher.js';\nimport { playTtsAudio } from '../common/tts-player.js';\nimport type { AudioHandle } from '../common/tts-player.js';\nimport {\n renderUISpec,\n createDefaultChatUISpecRegistry,\n defaultChatUnknownUISpecRenderer,\n} from './components/renderUISpec.js';\nimport { renderFloatingComparisonButton } from './components/FloatingComparisonButton.js';\nimport type { TypewriterHandle } from './components/typewriter.js';\nimport { typewriteHtml } from './components/typewriter.js';\nimport { linkProductMentions } from './components/productMentionLinker.js';\nimport { isInputAreaAction } from './components/actionClassifier.js';\nimport type { ThumbnailEntry } from './components/ThumbnailsColumn.js';\nimport {\n clearChoicePrompterDismissState,\n createChoicePrompter,\n isChoicePrompterDismissed,\n recordChoicePrompterDismissedForThread,\n} from './components/ChoicePrompter.js';\nimport type {\n ChatActionChip,\n OpeningContextKey,\n ChatWidgetConfig,\n ChatMessage,\n ChatI18n,\n ChatUISpecRenderContext,\n ChatUISpecRegistry,\n ProductSortState,\n} from './types.js';\nimport { GengageIndexedDB } from '../common/indexed-db.js';\nimport { CHAT_I18N_TR, resolveChatLocale } from './locales/index.js';\nimport { ExtendedModeManager } from './extendedModeManager.js';\nimport { PanelManager, determinePanelUpdateAction } from './panel-manager.js';\nimport { SessionPersistence } from './session-persistence.js';\nimport { ChatPresentationState, getLatestUnreadAssistantThreadId } from './chat-presentation-state.js';\nimport { invalidateChatScrollCache } from './utils/get-chat-scroll-element.js';\nimport { isLikelyConnectivityIssue } from '../common/global-error-toast.js';\nimport { shouldShowStreamErrorAsRedStrip } from './stream-error-display.js';\nimport {\n containsKvkk,\n isKvkkShown,\n markKvkkShown,\n stripKvkkBlock,\n extractKvkkBlock,\n localeToOutputLanguage,\n} from './kvkk.js';\n\nimport chatStyles from './components/chat.css?inline';\nimport * as ga from '../common/ga-datalayer.js';\n\n/**\n * Panel rebuild source for local drilldown history and stream-end snapshots.\n * `productDetailsWithSimilars` keeps PDP + appended similar grid in sync when\n * similars arrive in a second stream chunk (DOM append does not update `spec` alone).\n */\ntype PanelSource =\n | { kind: 'spec'; spec: UISpec }\n | { kind: 'productDetailsWithSimilars'; pdpSpec: UISpec; similarsSpec: UISpec }\n | { kind: 'favorites' };\ntype SendActionOptions = {\n silent?: boolean;\n attachment?: File;\n preservePanel?: boolean;\n isPdpPrime?: boolean;\n preservePills?: boolean;\n};\n\n/** Validate that a string is a safe CSS color value (no url(), expression(), etc.). */\nfunction isSafeCSSColor(value: string): boolean {\n if (value.length > 120) return false;\n // Only allow characters found in valid CSS colors: hex digits, letters, parens,\n // commas, dots, spaces, percent, slash (modern color syntax), and hyphens.\n return /^[a-zA-Z0-9#(),.\\s%/\\-]+$/.test(value);\n}\n\n/** Lightweight runtime check: value looks like an ActionPayload (has a string `type`). */\nfunction isActionLike(value: unknown): value is ActionPayload {\n return typeof value === 'object' && value !== null && typeof (value as Record<string, unknown>).type === 'string';\n}\n\n/**\n * Floating AI chat widget with streaming NDJSON responses, product cards, and comparison tables.\n *\n * @example\n * ```ts\n * import { GengageChat, bootstrapSession } from '@gengage/assistant-fe';\n *\n * const chat = new GengageChat();\n * await chat.init({\n * accountId: 'mystore',\n * middlewareUrl: 'https://chat.gengage.ai',\n * session: { sessionId: bootstrapSession() },\n * });\n * chat.open(); // Programmatically open the drawer\n * ```\n */\nexport class GengageChat extends BaseWidget<ChatWidgetConfig> {\n private static readonly _MAX_COMPARISON_SELECTION = 5;\n private _shadow: ShadowRoot | null = null;\n private _rootEl: HTMLElement | null = null;\n /** Full-viewport scrim when drawer is open (floating/overlay); not used for inline. */\n private _backdropEl: HTMLElement | null = null;\n private _launcher: LauncherElements | null = null;\n private _drawer: ChatDrawer | null = null;\n private _bridge: CommunicationBridge | null = null;\n private _drawerVisible = false;\n /**\n * Host scroll blocked via capture-phase `touchmove`/`wheel` + preventDefault.\n * Does not set `overflow` on html/body (avoids breaking host layouts).\n */\n private _hostScrollLockActive = false;\n private readonly _preventHostDocumentTouchMove = (e: TouchEvent): void => {\n if (!this._hostScrollLockActive) return;\n if (this._hostScrollEventShouldReachChatScroller(e)) return;\n e.preventDefault();\n };\n private readonly _preventHostDocumentWheel = (e: WheelEvent): void => {\n if (!this._hostScrollLockActive) return;\n if (this._hostScrollEventShouldReachChatScroller(e)) return;\n e.preventDefault();\n };\n private _messages: ChatMessage[] = [];\n // Bot text accumulation is now closure-local inside _sendAction to prevent\n // corruption when concurrent preservePanel streams write simultaneously.\n private _currentMessageId = 0;\n private _abortControllers = new Set<AbortController>();\n /** Current thread cursor — only messages with threadId <= this are visible. */\n private _currentThreadId: string | null = null;\n /** Most recent threadId ever created — used to detect branch points. */\n private _lastThreadId: string | null = null;\n /** Timestamp when the chat session was created (ISO 8601). */\n private _chatCreatedAt = '';\n /** Last backend-streamed context object — sent back with every request. */\n private _lastBackendContext: import('../common/types.js').BackendContext | null = null;\n private _productSort: ProductSortState = { type: 'related' };\n private _lastSku: string | undefined;\n private _comparisonSelectMode = false;\n private _comparisonSelectedSkus: string[] = [];\n private _comparisonSelectionWarning: string | null = null;\n private _comparisonRefreshRafId: number | null = null;\n /** SKUs of products the user has viewed across panel product grids. */\n private _viewedProductSkus = new Set<string>();\n private _thumbnailEntries: ThumbnailEntry[] = [];\n private _choicePrompterEl: HTMLElement | null = null;\n private _openState: 'full' | 'half' = 'full';\n private _mobileBreakpoint = 768;\n private _isMobileViewport = false;\n private _pdpLaunched = false;\n private _entryContextPrimed = false;\n /** True while the initial silent PDP launch request is in flight. */\n private _pdpPrimingInFlight = false;\n /** User messages queued until silent PDP priming completes. */\n private _queuedUserMessages: Array<{ text: string; attachment?: File }> = [];\n private _productContextUnavailableSku: string | null = null;\n private _i18n: ChatI18n = CHAT_I18N_TR;\n private _extendedModeManager: ExtendedModeManager | null = null;\n /** Active typewriter animation handle — cancelled on new action or drawer close. */\n private _activeTypewriter: TypewriterHandle | null = null;\n /** Active TTS audio handle — stopped on new stream start or drawer close. */\n private _activeTtsHandle: AudioHandle | null = null;\n /** Active request thread ID — guards against stale stream events from cancelled requests. */\n private _activeRequestThreadId: string | null = null;\n /** Accumulated SKU → product item map from outputText events. */\n private _skuToProductItem: Record<string, Record<string, unknown>> = {};\n /** Current conversation mode from the latest outputText event. */\n private _conversationMode: string | null = null;\n /** Whether initialization (including IDB restore) has completed. */\n private _initComplete = false;\n /** Queue of actions received before init completes. Max 10, FIFO discard. */\n private _pendingActions: Array<{\n action: ActionPayload;\n options?: SendActionOptions | undefined;\n }> = [];\n /** Supplemental context received from host via bridge (e.g. PDP detail context). */\n private _bridgeContext: Record<string, unknown> | null = null;\n /** Last cart quantity received from host via bridge. */\n private _cartQuantity: number | null = null;\n private _threadsWithFirstBot: Set<string> = new Set();\n /** Panel state manager (snapshots, topbar, navigation). */\n private _panel: PanelManager | null = null;\n /** Client-side panel navigation stack for local drilldowns (e.g. card → detail). Max 10 entries.\n * Stores rebuild info (UISpec or kind) instead of DOM clones so back navigation\n * produces fresh elements with live event listeners. */\n private _localPanelHistory: Array<{\n source: PanelSource;\n title: string;\n }> = [];\n private static readonly _MAX_PANEL_HISTORY = 10;\n /** Tracks how the current panel content was produced, for history/error-recovery rebuild. */\n private _currentPanelSource: PanelSource | null = null;\n /** IndexedDB session persistence manager. */\n private _session: SessionPersistence | null = null;\n /** Transcript focus, pin-to-bottom, and scroll request coordination. */\n private readonly _presentation = new ChatPresentationState();\n /** Registered event callbacks (GA4 event hooks). Key = event name, value = set of callbacks. */\n private _eventCallbacks = new Map<string, Set<(detail: Record<string, unknown>) => boolean | Promise<boolean>>>();\n /** Last sent action+options for retry on error. */\n private _lastSentAction: {\n action: ActionPayload;\n options?: SendActionOptions | undefined;\n } | null = null;\n /** Consecutive identical error counter for account-inactive detection. */\n private _consecutiveErrorCount = 0;\n /** Last error message text for deduplication. */\n private _lastErrorMessage = '';\n\n protected async onInit(config: ChatWidgetConfig): Promise<void> {\n this._i18n = this._resolveI18n(config);\n this._chatCreatedAt = new Date().toISOString();\n\n // Create Shadow DOM for CSS isolation\n this._shadow = this.root.attachShadow({ mode: 'open' });\n\n // Inject styles\n const style = document.createElement('style');\n style.textContent = chatStyles;\n this._shadow.appendChild(style);\n\n // Apply theme CSS custom properties on the shadow host so they cascade into the shadow tree.\n // Priority: explicit config fields > theme object entries > CSS defaults.\n const host = this.root as HTMLElement;\n const applyVar = (cssVar: string, value: string | undefined): void => {\n if (value && isSafeCSSColor(value)) host.style.setProperty(cssVar, value);\n };\n // Explicit header color shortcuts\n applyVar('--gengage-chat-header-bg', config.headerBg);\n applyVar('--gengage-chat-header-foreground', config.headerForeground);\n // Arbitrary theme tokens (e.g. theme['--gengage-primary-color'])\n if (config.theme) {\n for (const [key, value] of Object.entries(config.theme)) {\n if (key.startsWith('--') && typeof value === 'string') {\n applyVar(key, value);\n }\n }\n }\n\n // Create root container\n const rootEl = document.createElement('div');\n rootEl.className = 'gengage-chat-root';\n this._rootEl = rootEl;\n this._shadow.appendChild(rootEl);\n\n const variant = config.variant ?? 'floating';\n if (variant === 'inline') {\n rootEl.classList.add('gengage-chat--inline');\n }\n\n // Dim page + click outside to close (not for inline — drawer is embedded)\n if (variant !== 'inline') {\n const backdropEl = document.createElement('div');\n backdropEl.className = 'gengage-chat-backdrop';\n backdropEl.setAttribute('aria-hidden', 'true');\n backdropEl.setAttribute('role', 'button');\n backdropEl.setAttribute('tabindex', '-1');\n backdropEl.setAttribute('aria-label', this._i18n.closeAriaLabel);\n backdropEl.addEventListener('click', (e) => {\n e.preventDefault();\n e.stopPropagation();\n this.close();\n });\n this._backdropEl = backdropEl;\n rootEl.prepend(backdropEl);\n }\n\n // Create launcher (floating variant only — inline/overlay are triggered programmatically)\n if (variant === 'floating') {\n const launcherOpts: import('./components/Launcher.js').LauncherOptions = {\n onClick: () => this.open(),\n ariaLabel: this._i18n.openButton,\n };\n if (config.launcherImageUrl !== undefined) launcherOpts.imageUrl = config.launcherImageUrl;\n else if (config.launcherSvg !== undefined) launcherOpts.svgMarkup = config.launcherSvg;\n if (config.hideMobileLauncher !== undefined) launcherOpts.hideMobile = config.hideMobileLauncher;\n if (config.mobileBreakpoint !== undefined) launcherOpts.mobileBreakpoint = config.mobileBreakpoint;\n if (config.launcherTooltip !== undefined) launcherOpts.tooltip = config.launcherTooltip;\n this._launcher = createLauncher(launcherOpts);\n rootEl.appendChild(this._launcher.container);\n }\n\n // Overlay variant wraps drawer in a backdrop for full-screen modal\n if (variant === 'overlay') {\n rootEl.classList.add('gengage-chat--overlay');\n }\n\n // Create drawer (hidden initially for floating/overlay, visible for inline)\n const drawerContainer = document.createElement('div');\n rootEl.appendChild(drawerContainer);\n\n this._drawer = new ChatDrawer(drawerContainer, {\n i18n: this._i18n,\n onSend: (text, attachment) => this._sendMessage(text, attachment),\n onClose: () => this.close(),\n onAttachment: (file) => this._handleAttachment(file),\n onPanelToggle: () => {\n this._drawer?.persistPanelState(config.accountId);\n },\n onHostShellSync: () => {\n this._applyOpenStateClasses();\n },\n onRollback: (messageId) => this._handleRollback(messageId),\n onPanelBack: () => this._navigatePanelBack(),\n onPanelForward: () => this._panel?.navigateForward(),\n onPanelClose: () => {\n // Mobile ✕: panel is only hidden (content kept); header reopen stays meaningful.\n if (this._isMobileViewport) {\n this._comparisonSelectMode = false;\n this._comparisonSelectedSkus = [];\n return;\n }\n this._localPanelHistory = [];\n this._comparisonSelectMode = false;\n this._comparisonSelectedSkus = [];\n this._currentPanelSource = null;\n },\n headerTitle: config.headerTitle,\n headerAvatarUrl: config.headerAvatarUrl,\n launcherImageUrl: config.launcherImageUrl,\n headerBadge: config.headerBadge,\n headerCartUrl: config.headerCartUrl,\n showHeaderFavorites: typeof config.onFavoritesClick === 'function' || config.headerFavoritesToggle === true,\n onCartClick: () => {\n if (config.headerCartUrl) {\n this._saveSessionAndOpenURL(config.headerCartUrl);\n } else {\n config.onCartClick?.();\n }\n },\n onFavoritesClick: () => {\n ga.trackLikeList();\n if (typeof config.onFavoritesClick === 'function') {\n config.onFavoritesClick();\n return;\n }\n this._openFavoritesPanel();\n },\n getMobileState: () => this._openState ?? 'full',\n getMobileViewport: () => this._isMobileViewport,\n onMobileSnap: (state) => {\n if (state === 'close') {\n this.close();\n } else {\n this._openState = state;\n this._applyOpenStateClasses();\n }\n },\n onThumbnailClick: (threadId) => this._rollbackToThread(threadId),\n onLinkClick: (url) => {\n this._saveSessionAndOpenURL(url);\n },\n voiceEnabled: config.voiceEnabled,\n voiceLang: config.locale\n ? `${config.locale.split('-')[0] ?? 'tr'}-${(config.locale.split('-')[1] ?? config.locale.split('-')[0] ?? 'TR').toUpperCase()}`\n : undefined,\n presentation: {\n onPinnedToBottomChange: (pinned) => {\n this._presentation.pinnedToBottom = pinned;\n },\n onUserInteractingChange: (interacting) => {\n this._presentation.userInteracting = interacting;\n },\n onFormerMessagesHint: () => {\n if (this._presentation.focusedThreadId && this._hasMultipleThreadIds()) {\n this._drawer?.setFormerMessagesButtonVisible(true);\n }\n },\n shouldBlockSoftAutoScroll: () => this._presentation.shouldBlockStreamAutoScroll(),\n onReleasePresentationFocus: () => this._releasePresentationFocus(),\n },\n });\n\n // Extended mode manager for host PDP maximize/minimize\n this._extendedModeManager = new ExtendedModeManager({\n onChange: (extended) => this._panel?.notifyExtension(extended),\n productDetailsInPanel: (config.isDemoWebsite ?? false) && (config.productDetailsExtended ?? false),\n });\n\n // Panel manager for snapshot/topbar/navigation state\n this._panel = new PanelManager({\n drawer: () => this._drawer,\n shadow: () => this._shadow,\n currentThreadId: () => this._currentThreadId,\n bridge: () => this._bridge,\n extendedModeManager: () => this._extendedModeManager,\n i18n: () => this._i18n,\n rollbackToThread: (tid) => this._rollbackToThread(tid),\n });\n\n // Hide drawer initially for floating/overlay variants\n if (variant !== 'inline') {\n this._drawer.getElement().classList.add('gengage-chat-drawer--hidden');\n }\n\n // Restore panel state from session\n const restoredCollapsedPanel = this._drawer.restorePanelState(config.accountId);\n\n // Panel mode: 'collapsed' starts hidden, 'expanded' starts open, 'auto' (default) expands when content arrives\n const panelMode = config.panelMode ?? 'auto';\n if (panelMode === 'collapsed') {\n this._drawer.setPanelCollapsed(true);\n } else if (panelMode === 'expanded') {\n this._drawer.setForceExpanded();\n } else if (!restoredCollapsedPanel) {\n // 'auto': panel starts collapsed — will expand when content arrives via stream\n // (prevents empty panel being visible on page load/session restore)\n }\n\n // Restore session if an explicit handoff signal exists (e.g. SimRel product navigation)\n const restoreSessionId = sessionStorage.getItem('gengage_restore_session_id');\n const restoreSku = sessionStorage.getItem('gengage_restore_sku');\n const hasHandoff = !!(restoreSessionId && restoreSku);\n if (hasHandoff) {\n sessionStorage.removeItem('gengage_restore_session_id');\n sessionStorage.removeItem('gengage_restore_sku');\n }\n\n // IndexedDB persistence — best-effort, non-fatal\n try {\n const idb = new GengageIndexedDB();\n await idb.open();\n this._session = new SessionPersistence(idb);\n await this._restoreFromIndexedDB(hasHandoff);\n } catch {\n // IndexedDB unavailable — continue without persistence\n this._session = new SessionPersistence(null);\n }\n\n // Register public API on window\n this._registerPublicAPI();\n\n // Apply mobileInitialState if configured\n if (config.mobileInitialState !== undefined) {\n this._openState = config.mobileInitialState;\n }\n this._mobileBreakpoint = config.mobileBreakpoint ?? 768;\n\n this._syncViewportState();\n const onResize = () => this._syncViewportState();\n window.addEventListener('resize', onResize, { passive: true });\n this.addCleanup(() => window.removeEventListener('resize', onResize));\n\n // iOS visualViewport keyboard handling\n if (window.visualViewport) {\n const onViewportResize = () => {\n if (!this._drawerVisible || !this._isMobileViewport) return;\n const el = this._drawer?.getElement();\n if (!el) return;\n const offset = window.innerHeight - (window.visualViewport?.height ?? window.innerHeight);\n el.style.setProperty('--gengage-keyboard-offset', `${Math.max(0, offset)}px`);\n };\n window.visualViewport.addEventListener('resize', onViewportResize);\n this.addCleanup(() => window.visualViewport?.removeEventListener('resize', onViewportResize));\n }\n\n // Inline variant starts visible (onShow() is not invoked — mirror presentation state here)\n if (variant === 'inline') {\n this._drawerVisible = true;\n this.isVisible = true;\n this._applyOpenStateClasses();\n this._presentation.setShown(true);\n setTimeout(() => this._maybeAutoAnchorUnreadAssistant(), 60);\n }\n\n // Communication bridge for host ↔ widget messaging\n const bridgeOpts: CommunicationBridgeOptions = {\n namespace: 'chat',\n onMessage: (msg) => this._handleBridgeMessage(msg),\n };\n if (config.allowedOrigins !== undefined) bridgeOpts.allowedOrigins = config.allowedOrigins;\n this._bridge = new CommunicationBridge(bridgeOpts);\n\n // Track initial SKU for page-change detection\n this._lastSku = this.config.pageContext?.sku;\n\n // Mark init complete and drain pending actions queue\n this._initComplete = true;\n for (const pending of this._pendingActions) {\n this._sendAction(pending.action, pending.options);\n }\n this._pendingActions = [];\n\n dispatch('gengage:chat:ready', {});\n ga.trackInit('chat');\n config.onReady?.();\n }\n\n protected onUpdate(context: Partial<PageContext>): void {\n if (context.sku !== undefined && context.sku !== this._lastSku) {\n this._lastSku = context.sku;\n this._resetForNewPage();\n }\n }\n\n protected onShow(): void {\n this._showDrawer();\n this.emit('open');\n dispatch('gengage:chat:open', { state: this._openState });\n ga.trackShow('chat');\n this.config.onOpen?.();\n\n // Show welcome message on first open with empty history\n this._showWelcomeIfNeeded();\n\n // Prime a contextual opening for blank / homepage / listing states when\n // the merchant configured startup journeys in the SaaS.\n this._maybePrimeEntryContextOpening();\n\n // Auto-launch PDP context on first open when SKU is available\n if (!this._pdpLaunched && this.config.pageContext?.sku) {\n this._pdpLaunched = true;\n this._pdpPrimingInFlight = true;\n this._sendAction(\n {\n title: '',\n type: 'launchSingleProduct',\n payload: {\n sku: this.config.pageContext.sku,\n ...(this._resolveContextualOpeningMessage('product')\n ? { opening_message: this._resolveContextualOpeningMessage('product') }\n : {}),\n ...(this._resolveContextualOpeningGuidance('product')\n ? { opening_guidance: this._resolveContextualOpeningGuidance('product') }\n : {}),\n },\n },\n { silent: true, isPdpPrime: true, preservePills: true },\n );\n }\n }\n\n protected onHide(): void {\n // Floating mode should only hide the drawer, not the launcher button.\n // BaseWidget.hide() toggles `root.style.display = 'none'`, so undo it here.\n if ((this.config.variant ?? 'floating') === 'floating') {\n this.root.style.display = '';\n }\n this._hideDrawer();\n this.emit('close');\n dispatch('gengage:chat:close', {});\n this.config.onClose?.();\n }\n\n protected onDestroy(): void {\n this._releaseHostDocumentScrollLock();\n this._abortAllActiveRequests();\n this._activeTypewriter?.cancel();\n this._activeTypewriter = null;\n this._activeTtsHandle?.stop();\n this._activeTtsHandle = null;\n if (this._comparisonRefreshRafId !== null) {\n cancelAnimationFrame(this._comparisonRefreshRafId);\n this._comparisonRefreshRafId = null;\n }\n this._drawer?.destroy();\n invalidateChatScrollCache();\n this._drawer = null;\n this._bridge?.destroy();\n this._bridge = null;\n this._extendedModeManager = null;\n this._panel?.destroy();\n this._panel = null;\n this._session?.close();\n this._session = null;\n this._localPanelHistory = [];\n this._currentPanelSource = null;\n if (window.gengage) {\n delete window.gengage.chat;\n }\n if (this._shadow) {\n this._shadow.innerHTML = '';\n this._shadow = null;\n }\n this._rootEl = null;\n }\n\n // ---------------------------------------------------------------------------\n // Public API\n // ---------------------------------------------------------------------------\n\n open(options?: { state?: 'full' | 'half'; initialMessage?: string }): void {\n if (options?.state !== undefined) {\n this._openState = options.state;\n if (this._drawerVisible) {\n this._applyOpenStateClasses();\n }\n }\n this.show();\n if (options?.initialMessage !== undefined) {\n this._sendMessage(options.initialMessage);\n }\n }\n\n openWithAction(action: ActionPayload, options?: { sku?: string; state?: 'full' | 'half' }): void {\n if (options?.sku !== undefined) {\n this.update({ sku: options.sku });\n }\n // Mark PDP as launched since we're sending an explicit action\n this._pdpLaunched = true;\n // Action-triggered opens default to half-sheet on mobile\n if (options?.state !== undefined) {\n this._openState = options.state;\n } else if (this._isMobileViewport) {\n this._openState = 'half';\n }\n this.show();\n if (this._drawerVisible) {\n this._applyOpenStateClasses();\n }\n this._sendAction(action);\n }\n\n /** Send a user message programmatically (same flow as typing + submit). */\n sendMessage(text: string): void {\n this._sendMessage(text);\n }\n\n /** Send a backend action programmatically. */\n sendAction(action: ActionPayload, options?: { silent?: boolean }): void {\n this._sendAction(action, options);\n }\n\n close(): void {\n this.hide();\n }\n\n saveSession(sessionId: string, sku: string): void {\n sessionStorage.setItem('gengage_restore_session_id', sessionId);\n sessionStorage.setItem('gengage_restore_sku', sku);\n }\n\n get isOpen(): boolean {\n return this._drawerVisible;\n }\n\n /**\n * Register a callback for integration events (e.g. 'gengage-cart-add', 'gengage-product-favorite').\n * The callback receives the event detail and should return true (success) or false (failure).\n * For add-to-cart, failure triggers an error message in the chat.\n * For product-favorite, failure reverts the heart on the card and shows an error message.\n * @returns unsubscribe function\n */\n addCallback(\n eventName: string,\n callback: (detail: Record<string, unknown>) => boolean | Promise<boolean>,\n ): () => void {\n let set = this._eventCallbacks.get(eventName);\n if (!set) {\n set = new Set();\n this._eventCallbacks.set(eventName, set);\n }\n set.add(callback);\n return () => {\n set!.delete(callback);\n if (set!.size === 0) this._eventCallbacks.delete(eventName);\n };\n }\n\n // ---------------------------------------------------------------------------\n // Private\n // ---------------------------------------------------------------------------\n\n private _abortAllActiveRequests(): void {\n for (const controller of this._abortControllers) {\n controller.abort();\n }\n this._abortControllers.clear();\n }\n\n /**\n * Drop assistant rows that are still \"streaming\" with no text when the user starts\n * a new turn. Otherwise each superseded request leaves an empty model message in\n * `_messages` — it is included in `chatHistory` and can confuse the backend and\n * delay or distort the next response.\n */\n private _pruneEmptyStreamingAssistantPlaceholders(): void {\n const next: ChatMessage[] = [];\n let thumbnailsChanged = false;\n for (const m of this._messages) {\n // Also catch status === 'done': the Stop button handler transitions streaming → done\n // before _sendAction fires, so a stop-then-Enter sequence would otherwise leave an\n // empty assistant turn in chatHistory and confuse the backend.\n const drop =\n m.role === 'assistant' &&\n (m.status === 'streaming' || m.status === 'done') &&\n (m.content == null || m.content.length === 0);\n if (drop) {\n if (m.threadId) {\n this._threadsWithFirstBot.delete(m.threadId);\n this._presentation.finalizeAssistantGroup(m.threadId);\n // Remove inline UISpec nodes (data-thread-id but no data-message-id) that may\n // have arrived before any outputText — removeMessageBubble only targets the bubble.\n this._shadow\n ?.querySelectorAll(`[data-thread-id=\"${CSS.escape(m.threadId)}\"]:not([data-message-id])`)\n .forEach((el) => el.remove());\n if (this._panel) {\n this._panel.threads = this._panel.threads.filter((t) => t !== m.threadId);\n }\n const before = this._thumbnailEntries.length;\n this._thumbnailEntries = this._thumbnailEntries.filter((e) => e.threadId !== m.threadId);\n if (this._thumbnailEntries.length !== before) thumbnailsChanged = true;\n }\n this._drawer?.removeMessageBubble(m.id);\n this._panel?.snapshots.delete(m.id);\n this._panel?.snapshotTypes.delete(m.id);\n continue;\n }\n next.push(m);\n }\n this._messages.length = 0;\n this._messages.push(...next);\n if (thumbnailsChanged) this._drawer?.setThumbnails(this._thumbnailEntries);\n }\n\n /** Reset all chat state when navigating to a different SKU/page. */\n private _resetForNewPage(): void {\n // Invalidate any in-flight stream callbacks so they discard themselves\n // via the `threadId !== this._activeRequestThreadId` guard.\n this._activeRequestThreadId = null;\n // Abort in-flight streams\n this._abortAllActiveRequests();\n // Cancel active typewriter/TTS\n this._activeTypewriter?.cancel();\n this._activeTypewriter = null;\n this._activeTtsHandle?.stop();\n this._activeTtsHandle = null;\n // Clear messages\n this._messages.length = 0;\n this._drawer?.clearMessages();\n // Clear panel\n this._drawer?.clearPanel();\n this._currentPanelSource = null;\n this._panel!.snapshots.clear();\n this._panel!.threads = [];\n // Clear thumbnails\n this._thumbnailEntries = [];\n this._drawer?.setThumbnails([]);\n // Reset comparison state\n this._comparisonSelectMode = false;\n this._comparisonSelectedSkus = [];\n this._viewedProductSkus.clear();\n // Reset thread cursors\n this._currentThreadId = null;\n this._lastThreadId = null;\n this._lastBackendContext = null;\n this._chatCreatedAt = new Date().toISOString();\n // Allow PDP auto-launch for new SKU\n this._pdpLaunched = false;\n this._entryContextPrimed = false;\n this._pdpPrimingInFlight = false;\n this._queuedUserMessages = [];\n this._productContextUnavailableSku = null;\n this._presentation.reset();\n this._drawer?.setPresentationFocus(null);\n this._drawer?.setFormerMessagesButtonVisible(false);\n }\n\n /**\n * Same side effects as stream UISpec `clearPanel`: hide/clear the assistant panel without\n * `restoreOrClearPanel()`. Used for `clearPanel` chunks and PDP chat layout (`productDetailsExtended` off).\n */\n private _clearAssistantPanelLikeStreamClearPanel(): void {\n this._choicePrompterEl?.remove();\n this._choicePrompterEl = null;\n this._drawer?.clearPanel();\n this._currentPanelSource = null;\n this._thumbnailEntries = [];\n this._drawer?.setThumbnails([]);\n this._comparisonSelectMode = false;\n this._comparisonSelectedSkus = [];\n this._comparisonSelectionWarning = null;\n if (this._panel) {\n this._panel.currentType = null;\n this._panel.updateExtendedMode('');\n this._panel.updateTopBar('');\n }\n }\n\n private _flushPresentationScroll(): void {\n const req = this._presentation.scrollRequest;\n if (!req || !this._drawer) return;\n let handled = false;\n if (req.type === 'thread' && req.threadId) {\n handled = this._drawer.scrollThreadIntoView(req.threadId, req.behavior);\n if (!handled) {\n this._drawer.scrollToBottomPresentation(req.behavior);\n handled = true;\n }\n } else if (req.type === 'bottom') {\n this._drawer.scrollToBottomPresentation(req.behavior);\n handled = true;\n }\n if (handled) {\n this._presentation.consumeScrollRequest(req.id);\n }\n }\n\n private _releasePresentationFocus(): void {\n this._presentation.releaseFocusedThread();\n this._drawer?.setPresentationFocus(null);\n this._drawer?.setFormerMessagesButtonVisible(false);\n }\n\n private _hasMultipleThreadIds(): boolean {\n const ids = new Set<string>();\n for (const m of this._messages) {\n if (m.threadId) ids.add(m.threadId);\n }\n return ids.size > 1;\n }\n\n private _orderedThreadIds(): string[] {\n const ordered: string[] = [];\n const seen = new Set<string>();\n for (const m of this._messages) {\n if (m.threadId && !seen.has(m.threadId)) {\n seen.add(m.threadId);\n ordered.push(m.threadId);\n }\n }\n return ordered;\n }\n\n private _maybeAutoAnchorUnreadAssistant(): void {\n if (!this._drawer || !this._presentation.shown) return;\n const threadIds = this._orderedThreadIds();\n if (threadIds.length === 0) return;\n const latestUnread = getLatestUnreadAssistantThreadId(threadIds, this._presentation);\n if (!latestUnread) return;\n const gid = `${latestUnread}:assistant`;\n if (this._presentation.lastAutoAnchoredGroupId === gid) return;\n if (this._presentation.userInteracting && !this._presentation.pinnedToBottom) return;\n if (this._drawer.scrollThreadIntoView(latestUnread, 'smooth')) {\n this._presentation.markGroupAutoAnchored(gid);\n }\n }\n\n private _handleBridgeMessage(msg: BridgeMessage): void {\n switch (msg.type) {\n case 'openChat':\n this.open();\n break;\n case 'closeChat':\n this.close();\n break;\n case 'startNewChatWithLauncherAction': {\n // Start a new chat with a preset launcher action.\n // Legacy sends payload as the action directly: { type, title, requestDetails }\n // Clean-room also accepts nested: { action: { type, title, ... } }\n const payload = msg.payload as Record<string, unknown> | undefined;\n const nested = payload?.action;\n // Prefer nested shape, fall back to payload-is-action (legacy compat)\n const action = isActionLike(nested) ? nested : isActionLike(payload) ? payload : null;\n if (action) {\n this._sendAction(action, { silent: true });\n }\n this.open();\n break;\n }\n case 'startNewChatWithDetailContext': {\n // Start chat with product detail context (e.g., from PDP page).\n // Legacy shape: { action: {...}, sku: \"...\" }\n const ctx = msg.payload as Record<string, unknown> | undefined;\n if (ctx && typeof ctx === 'object') {\n this._bridgeContext = ctx;\n // Extract SKU and update page context so auto-PDP launch uses it\n const sku = ctx.sku as string | undefined;\n if (sku) {\n this.update({ sku });\n }\n // Extract and send the action (legacy sent action + premade context)\n if (isActionLike(ctx.action)) {\n this._pdpLaunched = true; // Skip auto-launch, explicit action takes priority\n this.open();\n this._sendAction(ctx.action);\n break;\n }\n }\n this.open();\n break;\n }\n case 'launcherAction': {\n // Send action to active chat from external trigger\n const payload = msg.payload as Record<string, unknown> | undefined;\n const action = payload?.action;\n if (action && typeof action === 'object' && 'type' in (action as Record<string, unknown>)) {\n this._sendAction(action as ActionPayload);\n }\n break;\n }\n case 'scrollToBottom':\n this._presentation.requestScrollToBottom('smooth');\n setTimeout(() => this._flushPresentationScroll(), 40);\n break;\n case 'addToCardHandler': {\n // Host notifies widget of add-to-cart result\n this._bridge?.send('addToCardResult', msg.payload);\n break;\n }\n case 'cartQuantityHandler': {\n // Host sends updated cart quantity\n const payload = msg.payload as Record<string, unknown> | undefined;\n if (payload && 'quantity' in payload && typeof payload.quantity === 'number') {\n this._cartQuantity = payload.quantity;\n }\n break;\n }\n case 'favoritesCountHandler': {\n const payload = msg.payload as Record<string, unknown> | undefined;\n const count = payload?.count;\n if (typeof count === 'number' && count >= 0) {\n this._drawer?.updateFavoritesBadge(count);\n }\n break;\n }\n case 'minimizeRequestedByUser':\n this._extendedModeManager?.setHiddenByUser(true);\n break;\n case 'bgColorChange': {\n const payload = msg.payload as Record<string, unknown> | undefined;\n const color = payload?.color;\n if (typeof color === 'string' && isSafeCSSColor(color) && this._shadow) {\n (this._shadow.host as HTMLElement).style.setProperty('--gengage-chat-bg', color);\n }\n break;\n }\n default:\n break;\n }\n }\n\n private _registerPublicAPI(): void {\n if (!window.gengage) window.gengage = {};\n window.gengage.chat = {\n open: (opts) => this.open(opts),\n openWithAction: (action, opts) => this.openWithAction(action, opts),\n sendMessage: (text) => this.sendMessage(text),\n sendAction: (action, opts) => this.sendAction(action, opts),\n close: () => this.close(),\n saveSession: (sid, sku) => this.saveSession(sid, sku),\n get isOpen() {\n return false;\n }, // Placeholder, overridden below\n on: (event, handler) => this.on(event, handler),\n trackCheckout: (type, data) => this.trackCheckout(type, data),\n flushMeteringSummary: (data) => this.flushMeteringSummary(data),\n addCallback: (eventName, callback) => this.addCallback(eventName, callback),\n };\n // Fix isOpen getter to reflect actual state\n Object.defineProperty(window.gengage.chat, 'isOpen', {\n get: () => this._drawerVisible,\n });\n }\n\n private _showDrawer(): void {\n if (this._drawerVisible) return;\n this._drawerVisible = true;\n const el = this._drawer?.getElement();\n if (el) {\n el.classList.remove('gengage-chat-drawer--hidden');\n }\n this._applyOpenStateClasses();\n this._drawer?.trapFocus();\n if (!(this._isMobileViewport && this._openState === 'half')) {\n this._drawer?.focusInput();\n }\n this._extendedModeManager?.setChatShown(true);\n this._presentation.setShown(true);\n setTimeout(() => this._maybeAutoAnchorUnreadAssistant(), 60);\n }\n\n /** Show welcome message and starter pills on first open with empty history. */\n private _showWelcomeIfNeeded(): void {\n if (this._messages.length !== 0) return;\n\n const contextKey = this._resolveOpeningContextKey();\n const openingActions = this._resolveContextualOpeningActions(contextKey);\n if (openingActions.length > 0) {\n this._drawer?.setPills(\n openingActions.map((action) => ({\n label: action.title,\n onAction: () => this._sendAction(this._resolveContextualOpeningAction(action, contextKey)),\n })),\n );\n }\n\n if (contextKey === 'product' && this.config.pageContext?.sku) {\n return;\n }\n\n if (this._shouldPrimeContextualOpening(contextKey)) {\n return;\n }\n\n const welcomeMessage = this._resolveContextualOpeningMessage(contextKey);\n if (!welcomeMessage) return;\n\n const welcomeMsg: ChatMessage = {\n id: uuidv7(),\n role: 'assistant',\n content: welcomeMessage,\n timestamp: Date.now(),\n status: 'done',\n };\n this._messages.push(welcomeMsg);\n this._drawer?.addMessage(welcomeMsg);\n }\n\n private _resolveOpeningContextKey(): OpeningContextKey {\n switch (this.config.pageContext?.pageType) {\n case 'home':\n return 'home';\n case 'search':\n case 'plp':\n return 'listing';\n case 'pdp':\n return 'product';\n default:\n return 'default';\n }\n }\n\n private _resolveContextualOpeningMessage(contextKey = this._resolveOpeningContextKey()): string | undefined {\n return (\n this.config.openingMessagesByContext?.[contextKey] ??\n (contextKey !== 'default' ? this.config.openingMessagesByContext?.default : undefined) ??\n this.config.welcomeMessage\n );\n }\n\n private _resolveContextualOpeningGuidance(contextKey = this._resolveOpeningContextKey()): string | undefined {\n return (\n this.config.openingGuidanceByContext?.[contextKey] ??\n (contextKey !== 'default' ? this.config.openingGuidanceByContext?.default : undefined)\n );\n }\n\n private _resolveContextualOpeningActions(contextKey = this._resolveOpeningContextKey()): ChatActionChip[] {\n const contextualActions =\n this.config.welcomeActionsByContext?.[contextKey] ??\n (contextKey !== 'default' ? this.config.welcomeActionsByContext?.default : undefined);\n\n if (contextualActions?.length) {\n return contextualActions.filter((action) => typeof action?.title === 'string' && action.title.trim().length > 0);\n }\n\n return (this.config.welcomeActions ?? [])\n .filter((title) => typeof title === 'string' && title.trim().length > 0)\n .map((title) => ({ title }));\n }\n\n private _resolveContextualOpeningAction(\n action: ChatActionChip,\n contextKey = this._resolveOpeningContextKey(),\n ): ActionPayload {\n const title = action.title.trim();\n const primarySku = this.config.pageContext?.sku ?? this._readContextStringField('sku');\n const visibleSkus = this._readContextStringListField('visible_skus') ?? [];\n\n if (action.icon === 'review' && primarySku) {\n return {\n title,\n type: 'reviewSummary',\n payload: { sku: primarySku },\n };\n }\n\n if (action.icon === 'similar' && primarySku) {\n return {\n title,\n type: 'findSimilar',\n payload: { sku: primarySku },\n };\n }\n\n if (action.icon === 'compare' && contextKey === 'listing' && visibleSkus.length >= 2) {\n return {\n title,\n type: 'getComparisonTable',\n payload: { sku_list: visibleSkus.slice(0, 2) },\n };\n }\n\n return {\n title,\n type: 'user_message',\n payload: title,\n };\n }\n\n private _readContextStringField(key: string): string | undefined {\n const pageContext = this.config.pageContext as Record<string, unknown> | undefined;\n const extra =\n pageContext?.extra && typeof pageContext.extra === 'object' && !Array.isArray(pageContext.extra)\n ? (pageContext.extra as Record<string, unknown>)\n : undefined;\n const altCamel = key.replace(/_([a-z])/g, (_match, letter: string) => letter.toUpperCase());\n const candidate = pageContext?.[key] ?? extra?.[key] ?? pageContext?.[altCamel] ?? extra?.[altCamel];\n return typeof candidate === 'string' && candidate.trim().length > 0 ? candidate.trim() : undefined;\n }\n\n private _readContextStringListField(key: string): string[] | undefined {\n const pageContext = this.config.pageContext as Record<string, unknown> | undefined;\n const extra =\n pageContext?.extra && typeof pageContext.extra === 'object' && !Array.isArray(pageContext.extra)\n ? (pageContext.extra as Record<string, unknown>)\n : undefined;\n const altCamel = key.replace(/_([a-z])/g, (_match, letter: string) => letter.toUpperCase());\n const candidate = pageContext?.[key] ?? extra?.[key] ?? pageContext?.[altCamel] ?? extra?.[altCamel];\n if (!Array.isArray(candidate)) return undefined;\n const values = candidate.filter((item): item is string => typeof item === 'string' && item.trim().length > 0);\n return values.length > 0 ? values : undefined;\n }\n\n private _buildEntryOpeningPageDetails(): Record<string, unknown> | undefined {\n const pageDetails: Record<string, unknown> = {};\n\n pageDetails.url = this.config.pageContext?.url ?? window.location.href;\n\n const pageTitle = this._readContextStringField('page_title');\n if (pageTitle) pageDetails.page_title = pageTitle;\n\n const pageDescription = this._readContextStringField('page_description');\n if (pageDescription) pageDetails.page_description = pageDescription;\n\n const searchQuery = this._readContextStringField('search_query');\n if (searchQuery) pageDetails.search_query = searchQuery;\n\n const visibleSkus = this._readContextStringListField('visible_skus');\n if (visibleSkus?.length) pageDetails.visible_skus = visibleSkus;\n\n const popularSearches = this._readContextStringListField('popular_searches');\n if (popularSearches?.length) pageDetails.popular_searches = popularSearches;\n\n const categoryPath = this.config.pageContext?.categoryTree ?? this._readContextStringListField('category_path');\n if (categoryPath?.length) pageDetails.category_path = categoryPath;\n\n return Object.keys(pageDetails).length > 0 ? pageDetails : undefined;\n }\n\n private _shouldPrimeContextualOpening(contextKey = this._resolveOpeningContextKey()): boolean {\n if (this._messages.length !== 0 || this._entryContextPrimed || this._pdpPrimingInFlight) return false;\n if (contextKey === 'product') return false;\n return Boolean(\n this.config.openingMessagesByContext ||\n this.config.openingGuidanceByContext ||\n this.config.welcomeActionsByContext,\n );\n }\n\n private _maybePrimeEntryContextOpening(): void {\n const contextKey = this._resolveOpeningContextKey();\n if (!this._shouldPrimeContextualOpening(contextKey)) return;\n\n this._entryContextPrimed = true;\n\n const payload: Record<string, unknown> = {\n text: '',\n is_entry_context_opening: 1,\n opening_context_key: contextKey,\n };\n\n const pageDetails = this._buildEntryOpeningPageDetails();\n if (pageDetails) payload.page_details = pageDetails;\n\n const openingMessage = this._resolveContextualOpeningMessage(contextKey);\n if (openingMessage) payload.opening_message = openingMessage;\n\n const openingGuidance = this._resolveContextualOpeningGuidance(contextKey);\n if (openingGuidance) payload.opening_guidance = openingGuidance;\n\n this._sendAction(\n {\n title: '',\n type: 'user_message',\n payload,\n },\n { silent: true, preservePills: true },\n );\n }\n\n private _hideDrawer(): void {\n if (!this._drawerVisible) return;\n this._drawer?.releaseFocus();\n this._activeTypewriter?.cancel();\n this._activeTypewriter = null;\n this._activeTtsHandle?.stop();\n this._activeTtsHandle = null;\n this._drawerVisible = false;\n // Reset mobile open state so next launcher open defaults to full-screen\n this._openState = 'full';\n // Don't clear panel — preserve it so reopening the drawer shows the same\n // panel state. clearPanel() is reserved for SKU/page resets and stale\n // loading skeleton cleanup after streams.\n const el = this._drawer?.getElement();\n if (el) {\n el.classList.add('gengage-chat-drawer--hidden');\n }\n this._applyOpenStateClasses();\n this._extendedModeManager?.setChatShown(false);\n this._presentation.setShown(false);\n this._drawer?.setPresentationFocus(null);\n this._drawer?.setFormerMessagesButtonVisible(false);\n }\n\n private _syncViewportState(): void {\n if (!this._rootEl) return;\n this._isMobileViewport = window.innerWidth <= this._mobileBreakpoint;\n this._rootEl.classList.toggle('gengage-chat-root--mobile', this._isMobileViewport);\n\n if (this._launcher) {\n const hideLauncher = this._isMobileViewport && this.config.hideMobileLauncher === true;\n this._launcher.container.classList.toggle('gengage-chat-launcher--hidden-mobile', hideLauncher);\n }\n\n this._applyOpenStateClasses();\n }\n\n /**\n * Scroll lock + dimming backdrop apply only in this state:\n * - overlay: drawer open (full-screen modal)\n * - floating: drawer open and side panel visible (split / “maximized” layout)\n */\n private _isMaximizedForHostChrome(): boolean {\n if (!this._drawerVisible) return false;\n const variant = this.config.variant ?? 'floating';\n if (variant === 'inline') return false;\n if (variant === 'overlay') return true;\n return this._drawer?.isPanelVisible() ?? false;\n }\n\n private _applyOpenStateClasses(): void {\n if (!this._rootEl) return;\n const mobileHalf = this._drawerVisible && this._isMobileViewport && this._openState === 'half';\n const mobileFull = this._drawerVisible && this._isMobileViewport && this._openState === 'full';\n const maximizedHost = this._isMaximizedForHostChrome();\n this._rootEl.classList.toggle('gengage-chat-root--open', this._drawerVisible);\n this._rootEl.classList.toggle('gengage-chat-root--mobile-half', mobileHalf);\n this._rootEl.classList.toggle('gengage-chat-root--mobile-full', mobileFull);\n this._rootEl.classList.toggle('gengage-chat-root--maximized-host-chrome', maximizedHost);\n if (this._backdropEl) {\n const backdropIsScrim = maximizedHost && (this.config.variant ?? 'floating') !== 'inline';\n this._backdropEl.setAttribute('aria-hidden', backdropIsScrim ? 'false' : 'true');\n }\n this._syncHostDocumentScrollLock();\n }\n\n private _syncHostDocumentScrollLock(): void {\n if (typeof document === 'undefined') return;\n const variant = this.config.variant ?? 'floating';\n if (variant === 'inline') {\n this._releaseHostDocumentScrollLock();\n return;\n }\n if (this._isMaximizedForHostChrome()) {\n this._applyHostDocumentScrollLock();\n } else {\n this._releaseHostDocumentScrollLock();\n }\n }\n\n /**\n * When true, we do not preventDefault — the event may drive chat-internal scrolling.\n * Full-viewport backdrop sits inside the shadow tree, so `composedPath()` always hits\n * the host; we must not treat \"inside widget\" as \"allow scroll\" for that case.\n */\n private _hostScrollEventShouldReachChatScroller(ev: Event): boolean {\n try {\n const path = ev.composedPath();\n if (!path.includes(this.root)) return false;\n\n for (const n of path) {\n if (n === this.root) break;\n if (!(n instanceof HTMLElement)) continue;\n\n if (this._backdropEl && (n === this._backdropEl || this._backdropEl.contains(n))) {\n return false;\n }\n\n const cs = window.getComputedStyle(n);\n const canY = (cs.overflowY === 'auto' || cs.overflowY === 'scroll') && n.scrollHeight > n.clientHeight + 1;\n const canX = (cs.overflowX === 'auto' || cs.overflowX === 'scroll') && n.scrollWidth > n.clientWidth + 1;\n if (canY || canX) return true;\n }\n return false;\n } catch {\n return false;\n }\n }\n\n private _applyHostDocumentScrollLock(): void {\n if (typeof document === 'undefined' || this._hostScrollLockActive) return;\n document.addEventListener('touchmove', this._preventHostDocumentTouchMove, { capture: true, passive: false });\n document.addEventListener('wheel', this._preventHostDocumentWheel, { capture: true, passive: false });\n this._hostScrollLockActive = true;\n }\n\n private _releaseHostDocumentScrollLock(): void {\n if (typeof document === 'undefined' || !this._hostScrollLockActive) return;\n document.removeEventListener('touchmove', this._preventHostDocumentTouchMove, { capture: true });\n document.removeEventListener('wheel', this._preventHostDocumentWheel, { capture: true });\n this._hostScrollLockActive = false;\n }\n\n private _handleAttachment(file: File): void {\n const result = validateImageFile(file);\n if (!result.ok) {\n const message = result.reason === 'invalid_type' ? this._i18n.invalidFileType : this._i18n.fileTooLarge;\n dispatch('gengage:global:error', {\n message,\n source: 'chat' as const,\n });\n return;\n }\n this._drawer?.stageAttachment(file);\n }\n\n private _sendMessage(text: string, attachment?: File): void {\n if (this._pdpPrimingInFlight) {\n this._queuedUserMessages.push(attachment !== undefined ? { text, attachment } : { text });\n return;\n }\n\n ga.trackMessageSent();\n // Track conversation start on first user message in a new thread\n const hasUserMessages = this._messages.some((m) => m.role === 'user');\n if (!hasUserMessages) {\n ga.trackConversationStart();\n }\n // Image upload from Chat Input uses findSimilar (not inputText) per wire protocol\n const action: ActionPayload =\n attachment !== undefined\n ? { title: text, type: 'findSimilar', payload: text ? { text } : {} }\n : { title: text, type: 'user_message', payload: text };\n if (attachment !== undefined) {\n this._sendAction(action, { attachment });\n } else {\n this._sendAction(action);\n }\n }\n\n private _flushQueuedUserMessages(): void {\n if (this._pdpPrimingInFlight || this._queuedUserMessages.length === 0) return;\n const queued = [...this._queuedUserMessages];\n this._queuedUserMessages = [];\n for (const item of queued) {\n this._sendMessage(item.text, item.attachment);\n }\n }\n\n private _sendAction(action: ActionPayload, options?: SendActionOptions): void {\n // Cancel any running typewriter animation and TTS playback\n this._activeTypewriter?.cancel();\n this._activeTypewriter = null;\n this._activeTtsHandle?.stop();\n this._activeTtsHandle = null;\n\n // Track last action for retry on error\n this._lastSentAction = { action, options };\n\n // Defer actions until init completes\n if (!this._initComplete) {\n if (this._pendingActions.length < 10) {\n this._pendingActions.push({ action, options });\n }\n return;\n }\n\n // Remove ChoicePrompter on new action\n this._choicePrompterEl?.remove();\n this._choicePrompterEl = null;\n // Fresh user request: reset ChoicePrompter dismiss so it can show again on the next grid\n if (!options?.preservePanel) {\n clearChoicePrompterDismissState();\n }\n\n // Clear comparison mode when starting a new request (unless preservePanel).\n // Exception: getComparisonTable actions keep comparison state visible during\n // the request so the floating button and checkboxes stay as loading feedback.\n // State is cleared once the ComparisonTable UISpec actually renders in the panel.\n if (!options?.preservePanel && this._comparisonSelectMode && action.type !== 'getComparisonTable') {\n this._comparisonSelectMode = false;\n this._comparisonSelectedSkus = [];\n }\n\n // Clear local panel history on new requests (fresh panel context)\n if (!options?.preservePanel) {\n this._localPanelHistory = [];\n }\n\n // Branch deletion: if user is sending from a rewound position, prune future messages\n if (this._currentThreadId && this._lastThreadId && this._lastThreadId > this._currentThreadId) {\n const cutoff = this._currentThreadId;\n // Remove future messages from array\n const removed = this._messages.filter((m) => m.threadId !== undefined && m.threadId > cutoff);\n this._messages = this._messages.filter((m) => !m.threadId || m.threadId <= cutoff);\n // Remove their DOM nodes\n for (const msg of removed) {\n this._shadow?.querySelector(`[data-message-id=\"${CSS.escape(msg.id)}\"]`)?.remove();\n this._panel!.snapshots.delete(msg.id);\n this._panel!.snapshotTypes.delete(msg.id);\n }\n // Remove orphaned inline UISpec elements\n const orphanedUISpecs = this._shadow?.querySelectorAll(`[data-thread-id]`);\n orphanedUISpecs?.forEach((el) => {\n if (el instanceof HTMLElement && el.dataset['threadId'] && el.dataset['threadId'] > cutoff) {\n el.remove();\n }\n });\n }\n\n // Clear previous suggestion pills and input chips\n if (!options?.preservePills) {\n this._drawer?.setPills([]);\n }\n this._drawer?.clearInputAreaChips();\n\n // Notify host that assistant is responding\n this._bridge?.send('isResponding', true);\n\n // Generate thread ID for this request-response cycle\n const threadId = uuidv7();\n this._currentThreadId = threadId;\n this._lastThreadId = threadId;\n // Preserve the active grid intent during product drilldowns. A product click\n // should not relabel an existing search-result panel as \"similar products\".\n if (this._panel && action.type !== 'launchSingleProduct') {\n this._panel.lastActionType = action.type;\n }\n // For preservePanel actions (like/addToCart), don't overwrite _activeRequestThreadId\n // to avoid silencing concurrent streams. Instead, track validity locally.\n const isPreservePanel = options?.preservePanel === true;\n const isPdpAutoLaunch =\n action.type === 'launchSingleProduct' && options?.silent === true && options?.isPdpPrime === true;\n if (!isPreservePanel) {\n this._activeRequestThreadId = threadId;\n }\n\n // Align presentation focus with this thread *before* appending the user bubble.\n // Otherwise addMessage()'s collapse pass uses the previous focus and marks the\n // new bubble as presentation-collapsed until a later setPresentationFocus — which\n // can yield wrong scroll targets (offsetTop on display:none) and “empty” transcript.\n if (!options?.silent && !isPreservePanel) {\n this._drawer?.setPresentationFocus(threadId);\n }\n\n // Add user message to UI (skip for silent/auto-launch actions)\n if (!options?.silent) {\n const userText =\n typeof action.payload === 'string'\n ? action.payload\n : typeof (action.payload as Record<string, unknown>)?.['text'] === 'string'\n ? ((action.payload as Record<string, unknown>)['text'] as string)\n : action.title;\n // Retry deduplication: skip adding a duplicate user bubble when retrying\n const lastMsg = this._messages.length > 0 ? this._messages[this._messages.length - 1] : undefined;\n const isDuplicate = lastMsg !== undefined && lastMsg.role === 'user' && lastMsg.content === userText;\n if (!isDuplicate) {\n const userMsg = this._createMessage('user', userText);\n userMsg.threadId = threadId;\n if (options?.attachment !== undefined) {\n userMsg.attachment = options.attachment;\n }\n this._drawer?.addMessage(userMsg);\n this._messages.push(userMsg);\n }\n }\n\n const shouldShortCircuitUnavailableContext =\n !options?.silent &&\n this._hasUnavailableProductContext() &&\n (action.type === 'user_message' || action.type === 'inputText');\n if (shouldShortCircuitUnavailableContext) {\n const fallback = this._i18n.productNotFoundMessage;\n const botMsg = this._createMessage('assistant', fallback);\n botMsg.threadId = threadId;\n botMsg.status = 'done';\n this._messages.push(botMsg);\n this._ensureAssistantMessageRendered(botMsg);\n this._drawer?.updateBotMessage(botMsg.id, fallback);\n this._drawer?.setPresentationFocus(threadId);\n this._bridge?.send('isResponding', false);\n this.emit('message', botMsg);\n this._persistToIndexedDB().catch(() => {\n /* non-fatal */\n });\n return;\n }\n\n // Preserve panel during the request — don't clear or show loading skeleton\n // until the backend explicitly signals new panel content (panelLoading event).\n // Exception: getComparisonTable shows the panel skeleton immediately (desktop + mobile)\n // so users see progress while the table streams in.\n // Captures the panel source (UISpec/kind) so it can be re-rendered with fresh\n // event listeners if the backend fails to deliver new panel content.\n let prePanelSource = this._currentPanelSource;\n let prePanelSourceCaptured = false;\n const capturePanelSourceIfNeeded = (): void => {\n if (prePanelSourceCaptured || options?.preservePanel) return;\n prePanelSource = this._currentPanelSource;\n prePanelSourceCaptured = true;\n };\n const restoreOrClearPanel = (): void => {\n if (!this._drawer?.isPanelLoading()) return;\n if (prePanelSource) {\n const ctx = this._buildRenderContext();\n const el = this._renderPanelFromSource(prePanelSource, ctx);\n this._drawer.setPanelContent(el);\n this._drawer.setDividerPreviewEnabled(this._shouldUseDividerPreviewForSource(prePanelSource));\n this._currentPanelSource = prePanelSource;\n } else {\n this._drawer.clearPanel();\n this._currentPanelSource = null;\n }\n prePanelSource = null;\n };\n\n if (action.type === 'getComparisonTable') {\n this._drawer?.showPanelLoading('comparisonTable');\n this._panel?.updateTopBarForLoading('comparisonTable');\n }\n\n if (!options?.silent && !isPreservePanel) {\n this._pruneEmptyStreamingAssistantPlaceholders();\n }\n\n // Show typing indicator\n this._drawer?.showTypingIndicator();\n // Use a local text accumulator per stream invocation to prevent corruption\n // when multiple concurrent preservePanel streams write to the same state.\n let localBotText = '';\n\n // Create bot message placeholder\n const botMsg = this._createMessage('assistant', '');\n botMsg.threadId = threadId;\n botMsg.status = 'streaming';\n if (options?.silent) botMsg.silent = true;\n // Note: silent flag only skips the USER message above — bot response is always rendered\n this._messages.push(botMsg);\n\n this._presentation.registerAssistantActivity(threadId);\n this._presentation.requestThreadFocus(threadId, 'smooth');\n // User-visible non-preservePanel: focus was already set before the user bubble.\n if (options?.silent || isPreservePanel) {\n this._drawer?.setPresentationFocus(threadId);\n }\n this._drawer?.setFormerMessagesButtonVisible(false);\n setTimeout(() => this._flushPresentationScroll(), 40);\n\n // Abort previous request(s) — skip for preservePanel to avoid killing concurrent streams\n if (!options?.preservePanel) {\n this._abortAllActiveRequests();\n }\n\n const transport: ChatTransportConfig = {\n middlewareUrl: this.config.middlewareUrl,\n ...(this.config.accountId ? { accountId: this.config.accountId } : {}),\n };\n if (options?.attachment !== undefined) {\n transport.attachment = options.attachment;\n }\n\n const visibleMessages = this._getVisibleMessages();\n const chatHistory = visibleMessages\n // Keep assistant messages even when empty (panel-only responses) so the\n // backend sees the proper alternating user/model turn structure. Exclude\n // the current bot placeholder (just created, not yet populated).\n .filter((m) => m !== botMsg && (m.content || m.role === 'assistant'))\n .slice(-50)\n .map((m) => ({\n role: m.role === 'user' ? 'user' : 'model',\n content: m.content ?? '',\n }));\n\n // Build meta object for backend\n const meta: BackendRequestMeta = {\n outputLanguage: localeToOutputLanguage(this.config.locale),\n parentUrl: window.location.href,\n windowWidth: String(window.innerWidth),\n windowHeight: String(window.innerHeight),\n selfUrl: '',\n id: this.config.session?.sessionId ?? '',\n userId: this.config.session?.userId ?? '',\n appId: this.config.accountId,\n threads: [],\n createdAt: this._chatCreatedAt,\n kvkkApproved: isKvkkShown(this.config.accountId),\n voiceEnabled: this.config.voiceEnabled ?? false,\n threadId,\n isControlGroup: this.config.session?.abTestVariant === 'control',\n isMobile: this._isMobileViewport,\n };\n if (this.config.session?.viewId !== undefined) {\n meta.viewId = this.config.session.viewId;\n }\n\n // Enrich action payload with fields the backend expects\n const enrichedAction = enrichActionPayload(action, {\n pageContext: this.config.pageContext,\n backendContext: this._lastBackendContext,\n isMobile: this._isMobileViewport,\n });\n\n const request: import('./api.js').ProcessActionRequest = {\n account_id: this.config.accountId,\n session_id: this.config.session?.sessionId ?? '',\n correlation_id: this.config.session?.sessionId ?? '',\n type: enrichedAction.type,\n locale: this.config.locale ?? 'tr',\n meta,\n context: {\n // Spread backend context (panel, message_id, etc.) but preserve FE's\n // authoritative chatHistory — the backend's stale `messages` must not\n // overwrite the up-to-date conversation history built by the widget.\n ...(this._lastBackendContext ?? {}),\n messages: chatHistory,\n // Backend reads session_id from context.\n session_id: this.config.session?.sessionId ?? '',\n },\n };\n\n // Only set optional fields when values exist (exactOptionalPropertyTypes)\n if (this.config.session?.userId !== undefined) {\n request.user_id = this.config.session.userId;\n }\n if (this.config.session?.viewId !== undefined) {\n request.view_id = this.config.session.viewId;\n }\n if (enrichedAction.payload !== undefined) {\n request.payload = enrichedAction.payload;\n }\n if (this.config.pageContext?.sku !== undefined) {\n request.sku = this.config.pageContext.sku;\n }\n if (this.config.pageContext?.pageType !== undefined) {\n request.page_type = this.config.pageContext.pageType;\n }\n\n // Analytics tracking state for this request\n const requestId = crypto.randomUUID();\n const streamStart = Date.now();\n let chunkIndex = 0;\n let panelLoadingSeen = false;\n let panelContentReceived = false;\n /** Desktop: panel shows ProductGrid / Categories — AI picks can render above instead of in chat. */\n let panelListEligibleForAiZone = false;\n let aiAnalysisUiReceivedForPanel = false;\n let streamDone = false;\n /** AITopPicks / AIGroupingCards often arrive before product_list; flush when grid mounts. */\n let pendingPanelAiSpec: UISpec | null = null;\n\n const syncPanelAiAnalysisZone = (): void => {\n if (!this._drawer) return;\n if (this._isMobileViewport || !panelListEligibleForAiZone) {\n this._drawer.setPanelAiZoneState('hidden');\n return;\n }\n if (aiAnalysisUiReceivedForPanel) return;\n if (!streamDone) {\n this._drawer.setPanelAiZoneState('analyzing', { analyzingLabel: this._i18n.aiAnalysisAnalyzingLabel });\n } else {\n this._drawer.setPanelAiZoneState('hidden');\n }\n };\n\n this.track(\n streamStartEvent(this.analyticsContext(), {\n endpoint: 'process_action',\n request_id: requestId,\n widget: 'chat',\n }),\n );\n\n let streamController: AbortController | null = null;\n streamController = sendChatMessage(\n request,\n {\n onTextChunk: (content, isFinal, extra) => {\n if (!isPreservePanel && threadId !== this._activeRequestThreadId) return;\n localBotText += content;\n this._drawer?.removeTypingIndicator();\n\n // Store enrichment data from outputText for downstream rendering\n if (extra?.skuToProductItem) {\n this._skuToProductItem = { ...this._skuToProductItem, ...extra.skuToProductItem };\n }\n if (extra?.conversationMode) {\n this._conversationMode = extra.conversationMode;\n }\n\n this.track(\n streamChunkEvent(this.analyticsContext(), {\n request_id: requestId,\n chunk_index: chunkIndex++,\n widget: 'chat',\n }),\n );\n\n if (!this._drawer) return;\n\n // KVKK filtering: always strip KVKK block from display text.\n // Show banner on first encounter only.\n let displayText = localBotText;\n if (isFinal && containsKvkk(displayText)) {\n const acctId = this.config.accountId;\n if (!isKvkkShown(acctId)) {\n const kvkkHtml = extractKvkkBlock(displayText);\n if (kvkkHtml) {\n this._drawer?.showKvkkBanner(kvkkHtml, () => {\n this._drawer?.hideKvkkBanner();\n markKvkkShown(acctId);\n });\n } else {\n // No KVKK block found — mark as shown so we don't re-check\n markKvkkShown(acctId);\n }\n }\n displayText = stripKvkkBlock(displayText);\n }\n\n // Check if we already have a bot bubble in the DOM (query by message ID, not :last-child)\n const existingBubble = this._shadow?.querySelector(\n `[data-message-id=\"${botMsg.id}\"] .gengage-chat-bubble-text`,\n );\n if (existingBubble) {\n existingBubble.innerHTML = sanitizeHtml(displayText);\n } else {\n botMsg.content = displayText;\n if (botMsg.role === 'assistant' && botMsg.threadId && !this._threadsWithFirstBot.has(botMsg.threadId)) {\n this._threadsWithFirstBot.add(botMsg.threadId);\n this._drawer.markFirstBotMessage(botMsg.id);\n }\n this._drawer.addMessage(botMsg);\n }\n\n if (isFinal) {\n botMsg.content = displayText;\n botMsg.status = 'done';\n ga.trackMessageReceived();\n\n // Apply typewriter animation to the final bot text\n const bubbleTextEl = this._shadow?.querySelector(\n `[data-message-id=\"${botMsg.id}\"] .gengage-chat-bubble-text`,\n ) as HTMLElement | null;\n if (bubbleTextEl) {\n this._activeTypewriter?.cancel();\n const mentions = extra?.productMentions;\n this._activeTypewriter = typewriteHtml({\n container: bubbleTextEl,\n html: sanitizeHtml(displayText),\n onTick: () => this._drawer?.scrollToBottomIfNeeded(),\n onComplete: () => {\n this._activeTypewriter = null;\n // Link product mentions after typewriter finishes\n if (mentions && mentions.length > 0 && bubbleTextEl) {\n linkProductMentions({\n container: bubbleTextEl,\n mentions,\n onProductClick: (sku) => {\n this._sendAction({\n title: mentions.find((m) => m.sku === sku)?.short_name ?? sku,\n type: 'launchSingleProduct',\n payload: { sku },\n });\n },\n });\n }\n },\n });\n }\n }\n },\n onUISpec: (spec, widget, panelHint, clearPanel) => {\n if (!isPreservePanel && threadId !== this._activeRequestThreadId) return;\n if (widget !== 'chat') return;\n\n // StreamEventUISpec.clearPanel: hide/clear the assistant panel. Do not use\n // restoreOrClearPanel() here — that restores the pre-loading snapshot while the skeleton\n // is shown and breaks intended panel-dismiss behavior.\n if (clearPanel) {\n this._clearAssistantPanelLikeStreamClearPanel();\n panelLoadingSeen = false;\n }\n\n const rootElement = spec.elements[spec.root];\n const componentType = rootElement?.type ?? 'unknown';\n const similarsAppendGrid = componentType === 'ProductGrid' && rootElement?.props?.['similarsAppend'] === true;\n /** PDP akışında yan panel kapalı: tam detay + benzer ürün grid’i yalnızca sohbette. */\n const skipSidePanelForUISpec =\n this.config.productDetailsExtended !== true &&\n (componentType === 'ProductDetailsPanel' || similarsAppendGrid);\n if (skipSidePanelForUISpec && !clearPanel) {\n this._clearAssistantPanelLikeStreamClearPanel();\n panelLoadingSeen = false;\n }\n const effectivePanelHint =\n componentType === 'ProductDetailsPanel' && panelHint !== 'panel' ? ('panel' as const) : panelHint;\n this.track(\n streamUiSpecEvent(this.analyticsContext(), {\n request_id: requestId,\n chunk_index: chunkIndex,\n component_type: componentType,\n widget: 'chat',\n }),\n );\n\n const renderContext = this._buildRenderContext();\n renderContext.isStreaming = true;\n\n // GA dataLayer: track component-specific events\n if (componentType === 'ComparisonTable') {\n const products = rootElement?.props?.['products'];\n ga.trackCompareReceived(Array.isArray(products) ? products.length : 0);\n }\n if (componentType === 'ProductGrid') {\n const childCount = rootElement?.children?.length ?? 0;\n ga.trackSearch(undefined, childCount);\n }\n\n const panelSpec = effectivePanelHint === 'panel' && this._panel ? this._panel.toPanelSpec(spec) : spec;\n\n if (effectivePanelHint === 'panel' && this._panel && !skipSidePanelForUISpec) {\n const isFirstPanelContentInStream = !panelContentReceived;\n panelContentReceived = true;\n\n const panelAction = determinePanelUpdateAction({\n componentType,\n similarsAppend: rootElement?.props?.['similarsAppend'] === true,\n currentPanelType: this._panel.currentType,\n hasPanelContent: this._drawer?.hasPanelContent() ?? false,\n isPanelLoading: this._drawer?.isPanelLoading() ?? false,\n isFirstPanelContentInStream,\n });\n\n renderContext.panelProductListHeading = undefined;\n if (componentType === 'ProductGrid') {\n if (panelAction === 'appendSimilars') {\n renderContext.panelProductListHeading = this._i18n.similarProductsLabel ?? 'Similar Products';\n } else {\n this._applyPanelListHeadingToContext(renderContext, { kind: 'spec', spec: panelSpec });\n }\n }\n\n if (panelAction === 'appendSimilars') {\n this._appendSimilarsToPanel(panelSpec, renderContext);\n } else if (panelAction === 'append') {\n this._drawer?.appendPanelContent(this._renderUISpec(panelSpec, renderContext));\n if (this._comparisonSelectMode) {\n this._refreshComparisonUI();\n }\n } else {\n // Reset comparison state when new panel content replaces the grid\n this._comparisonSelectMode = false;\n this._comparisonSelectedSkus = [];\n this._comparisonSelectionWarning = null;\n this._drawer?.setComparisonDockContent(null);\n this._drawer?.setPanelContent(this._renderUISpec(panelSpec, renderContext));\n this._currentPanelSource = { kind: 'spec', spec: panelSpec };\n this._panel.currentType = componentType;\n }\n this._drawer?.setDividerPreviewEnabled((this._panel.currentType ?? componentType) === 'ProductGrid');\n\n if (componentType === 'ProductDetailsPanel' && action.type === 'launchSingleProduct') {\n this._clearUnavailableProductContext();\n }\n\n // Track panel thread and update topbar + extended mode\n if (botMsg.threadId && !this._panel.threads.includes(botMsg.threadId)) {\n this._panel.threads.push(botMsg.threadId);\n }\n // Use the primary panel type for title (don't let appended grids overwrite it).\n // Backend-provided panelTitle (e.g. search results title) takes precedence.\n const titleType = this._panel.currentType ?? componentType;\n const backendTitle = rootElement?.props?.['panelTitle'] as string | undefined;\n this._panel.updateTopBar(titleType, backendTitle);\n this._panel.updateExtendedMode(componentType);\n\n // Desktop AI analysis zone: list/grid in panel → analyzing strip until Top Picks / groupings\n if (componentType === 'ProductGrid' || componentType === 'CategoriesContainer') {\n panelListEligibleForAiZone = !this._isMobileViewport;\n // Top Picks / groupings may have streamed before product_list — apply now that panel + zone exist\n if (pendingPanelAiSpec) {\n const flushCtx = this._buildRenderContext();\n flushCtx.isStreaming = true;\n const aiEl = this._renderUISpec(pendingPanelAiSpec, flushCtx);\n aiAnalysisUiReceivedForPanel = true;\n this._drawer?.setPanelAiZoneState('results', { resultEl: aiEl });\n pendingPanelAiSpec = null;\n }\n } else if (panelAction !== 'appendSimilars' && panelAction !== 'append') {\n panelListEligibleForAiZone = false;\n pendingPanelAiSpec = null;\n this._drawer?.setPanelAiZoneState('hidden');\n }\n }\n\n // ProductDetailsPanel goes to the panel, but also render a compact\n // horizontal ProductSummaryCard in chat messages (production parity\n // with the prior engine's LaunchSingleProduct component).\n // Silent PDP auto-prime (`isPdpAutoLaunch`) still uses a silent bot row for\n // history, but users should see the same inline summary + chip row as when\n // they open a product from chat (non-silent launchSingleProduct).\n if (\n componentType === 'ProductDetailsPanel' &&\n effectivePanelHint === 'panel' &&\n (!botMsg.silent || isPdpAutoLaunch)\n ) {\n const product = rootElement?.props?.['product'] as Record<string, unknown> | undefined;\n if (product) {\n const inlineSpec: UISpec = {\n root: 'root',\n elements: {\n root: {\n type: 'ProductSummaryCard',\n props: { product },\n },\n },\n };\n const messagesContainer = this._shadow?.querySelector('.gengage-chat-messages');\n if (messagesContainer) {\n const inline = this._renderUISpec(inlineSpec, renderContext);\n if (botMsg.threadId) {\n inline.dataset['threadId'] = botMsg.threadId;\n }\n const bubble = this._shadow?.querySelector(`[data-message-id=\"${botMsg.id}\"]`) as HTMLElement | null;\n if (bubble && bubble.parentNode === messagesContainer) {\n bubble.after(inline);\n } else {\n messagesContainer.appendChild(inline);\n }\n inline.scrollIntoView({ behavior: 'auto', block: 'end' });\n this._drawer?.refreshPresentationCollapsed();\n panelContentReceived = true;\n }\n }\n }\n\n const isAiAnalysisComponent = componentType === 'AITopPicks' || componentType === 'AIGroupingCards';\n let routeAiAnalysisToPanel = false;\n let deferAiPanelUntilGrid = false;\n if (skipSidePanelForUISpec && similarsAppendGrid) {\n renderContext.panelProductListHeading = this._i18n.similarProductsLabel ?? 'Similar Products';\n }\n if (isAiAnalysisComponent && !this._isMobileViewport && !botMsg.silent) {\n if (panelListEligibleForAiZone) {\n const aiEl = this._renderUISpec(spec, renderContext);\n aiAnalysisUiReceivedForPanel = true;\n this._drawer?.setPanelAiZoneState('results', { resultEl: aiEl });\n routeAiAnalysisToPanel = true;\n pendingPanelAiSpec = null;\n } else {\n pendingPanelAiSpec = spec;\n deferAiPanelUntilGrid = true;\n }\n }\n\n const inlineOkWhenSilentPrime = isPdpAutoLaunch && componentType === 'GroundingReviewCard';\n const shouldRenderInline =\n (!botMsg.silent || inlineOkWhenSilentPrime) &&\n (effectivePanelHint !== 'panel' ||\n componentType === 'ProductCard' ||\n (skipSidePanelForUISpec && componentType === 'ProductGrid')) &&\n componentType !== 'ActionButtons' && // ActionButtons render as bottom pills only\n !routeAiAnalysisToPanel &&\n !(deferAiPanelUntilGrid && isAiAnalysisComponent);\n\n if (shouldRenderInline) {\n const messagesContainer = this._shadow?.querySelector('.gengage-chat-messages');\n if (messagesContainer) {\n const inline = this._renderUISpec(spec, renderContext);\n if (botMsg.threadId) {\n inline.dataset['threadId'] = botMsg.threadId;\n }\n messagesContainer.appendChild(inline);\n inline.scrollIntoView({ behavior: 'auto', block: 'end' });\n this._drawer?.refreshPresentationCollapsed();\n if (skipSidePanelForUISpec && componentType === 'ProductGrid') {\n panelContentReceived = true;\n }\n }\n }\n\n // Track product thumbnails for ThumbnailsColumn\n if ((componentType === 'ProductGrid' || componentType === 'ProductCard') && botMsg.threadId) {\n const childIds = rootElement?.children ?? [];\n const products =\n componentType === 'ProductGrid'\n ? (childIds\n .map((id) => spec.elements[id]?.props?.['product'] as Record<string, unknown> | undefined)\n .filter(Boolean) as Record<string, unknown>[])\n : ([rootElement?.props?.['product'] as Record<string, unknown> | undefined].filter(Boolean) as Record<\n string,\n unknown\n >[]);\n\n for (const product of products) {\n const sku = product['sku'] as string | undefined;\n const imageUrl = product['imageUrl'] as string | undefined;\n if (sku && imageUrl) {\n this._thumbnailEntries.push({ sku, imageUrl, threadId: botMsg.threadId });\n }\n if (sku) {\n this._viewedProductSkus.add(sku);\n }\n }\n this._drawer?.setThumbnails(this._thumbnailEntries);\n }\n\n // Send preview images to host for launcher thumbnails\n if (componentType === 'ProductGrid' || componentType === 'ProductDetailsPanel') {\n const previewProducts =\n componentType === 'ProductGrid'\n ? ((rootElement?.children ?? [])\n .map((id) => spec.elements[id]?.props?.['product'] as Record<string, unknown> | undefined)\n .filter(Boolean) as Record<string, unknown>[])\n : ([\n (rootElement?.props?.['product'] ?? rootElement?.props) as Record<string, unknown> | undefined,\n ].filter(Boolean) as Record<string, unknown>[]);\n const previewImageUrls = previewProducts\n .map((p) => p['imageUrl'] as string | undefined)\n .filter((url): url is string => typeof url === 'string')\n .slice(0, 5);\n if (previewImageUrls.length > 0) {\n this._bridge?.send('previewImages', { images: previewImageUrls });\n }\n }\n\n // ChoicePrompter: panel ProductGrid with 2+ products, comparison mode off\n const productGridChildCount = rootElement?.children?.length ?? 0;\n if (\n componentType === 'ProductGrid' &&\n effectivePanelHint === 'panel' &&\n !skipSidePanelForUISpec &&\n productGridChildCount > 1 &&\n !this._comparisonSelectMode &&\n !isChoicePrompterDismissed(this._currentThreadId ?? '')\n ) {\n this._choicePrompterEl?.remove();\n this._shadow?.querySelectorAll('.gengage-chat-choice-prompter').forEach((el) => el.remove());\n this._choicePrompterEl = createChoicePrompter({\n heading: this._i18n.choicePrompterHeading,\n suggestion: this._i18n.choicePrompterSuggestion,\n ctaLabel: this._i18n.choicePrompterCta,\n threadId: this._currentThreadId ?? '',\n dismissAriaLabel: this._i18n.dismissAriaLabel,\n onCtaClick: () => {\n this._comparisonSelectMode = true;\n this._choicePrompterEl = null;\n this._refreshComparisonUI();\n },\n onDismiss: () => {\n this._choicePrompterEl = null;\n },\n });\n // Mount in the panel float anchor — the prompter floats at the\n // bottom-right of the details pane (panel), not the conversation pane.\n const mountEl = this._shadow?.querySelector('.gengage-chat-panel-float');\n if (mountEl) {\n mountEl.appendChild(this._choicePrompterEl);\n\n // Dismiss ChoicePrompter when the mobile keyboard opens (viewport shrinks)\n if (this._isMobileViewport && window.visualViewport) {\n const prompterRef = this._choicePrompterEl;\n const dismissOnKeyboard = (): void => {\n const heightRatio = window.visualViewport!.height / window.innerHeight;\n if (heightRatio < 0.75) {\n prompterRef.remove();\n if (this._choicePrompterEl === prompterRef) {\n this._choicePrompterEl = null;\n }\n window.visualViewport!.removeEventListener('resize', dismissOnKeyboard);\n }\n };\n window.visualViewport.addEventListener('resize', dismissOnKeyboard);\n }\n } else {\n this._choicePrompterEl = null;\n }\n }\n\n // Extract suggestion pills / input-area chips from ActionButtons UISpec\n if (componentType === 'ActionButtons') {\n const buttons = rootElement?.props?.['buttons'] as\n | Array<{\n label: string;\n action: ActionPayload;\n icon?: string;\n image?: string;\n description?: string;\n }>\n | undefined;\n if (buttons && buttons.length > 0) {\n const inputChips: Array<{ label: string; icon?: string | undefined; action: ActionPayload }> = [];\n const pillButtons: typeof buttons = [];\n\n for (const btn of buttons) {\n if (isInputAreaAction(btn)) {\n const chip: { label: string; icon?: string | undefined; action: ActionPayload } = {\n label: btn.label,\n action: btn.action,\n };\n if (btn.icon) chip.icon = btn.icon;\n inputChips.push(chip);\n } else {\n pillButtons.push(btn);\n }\n }\n\n if (inputChips.length > 0) {\n this._drawer?.setInputAreaChips(\n inputChips.map((chip) => ({\n label: chip.label,\n onAction: () => this._sendAction(chip.action),\n ...(chip.icon ? { icon: chip.icon } : {}),\n })),\n );\n }\n\n if (pillButtons.length > 0) {\n this._drawer?.setPills(\n pillButtons.map((btn) => {\n const pill: {\n label: string;\n onAction: () => void;\n icon?: string;\n image?: string;\n description?: string;\n } = {\n label: btn.label,\n onAction: () => this._sendAction(btn.action),\n };\n if (btn.icon) pill.icon = btn.icon;\n if (btn.image) pill.image = btn.image;\n if (btn.description) pill.description = btn.description;\n return pill;\n }),\n );\n }\n }\n }\n\n syncPanelAiAnalysisZone();\n botMsg.uiSpec = spec;\n },\n onAction: (event: StreamEvent) => {\n if (!isPreservePanel && threadId !== this._activeRequestThreadId) return;\n if (event.type === 'action') {\n const routerOpts: ActionRouterOptions = {};\n if (this.config.actionHandling?.unknownActionPolicy !== undefined) {\n routerOpts.unknownActionPolicy = this.config.actionHandling.unknownActionPolicy;\n }\n if (this.config.actionHandling?.allowScriptCall !== undefined) {\n routerOpts.allowScriptCall = this.config.actionHandling.allowScriptCall;\n }\n routeStreamAction(\n event as StreamEventAction,\n {\n openChat: () => this.open(),\n navigate: (params) => {\n if (!isSafeUrl(params.url)) return;\n this._bridge?.send('navigate', params);\n if (params.newTab) {\n window.open(params.url, '_blank', 'noopener,noreferrer');\n } else {\n window.location.href = params.url;\n }\n },\n saveSession: (params) => this.saveSession(params.sessionId, params.sku),\n addToCart: (params) => {\n dispatch('gengage:similar:add-to-cart', params);\n },\n scriptCall: (params) => {\n dispatch('gengage:chat:script-call', params);\n this.config.onScriptCall?.(params);\n },\n },\n routerOpts,\n );\n }\n },\n onMetadata: (event: StreamEvent) => {\n if (!isPreservePanel && threadId !== this._activeRequestThreadId) return;\n if (event.type === 'metadata' && event.meta) {\n // Store backend context for sending with next request\n if (\n event.meta.panel !== undefined ||\n event.meta.messages !== undefined ||\n event.meta.message_id !== undefined\n ) {\n this._lastBackendContext = event.meta as import('../common/types.js').BackendContext;\n }\n\n // Panel loading indicator\n if (event.meta.panelLoading) {\n const pendingType =\n typeof event.meta.panelPendingType === 'string' ? event.meta.panelPendingType : undefined;\n const suppressProductDetailsSkeleton =\n this.config.productDetailsExtended !== true &&\n (pendingType === 'productDetails' || pendingType === 'productDetailsSimilars');\n if (!suppressProductDetailsSkeleton) {\n panelLoadingSeen = true;\n panelContentReceived = false;\n // Snapshot current panel before replacing with skeleton\n capturePanelSourceIfNeeded();\n if (this._panel) this._panel.currentType = null;\n this._drawer?.showPanelLoading(pendingType);\n // Set panel topbar title immediately so it's not an empty white bar\n if (pendingType) {\n this._panel?.updateTopBarForLoading(pendingType);\n }\n }\n }\n\n // Optional voice payload emitted by backend when voiceEnabled is true.\n if (event.meta.voice) {\n // Dispatch cancelable event — host can call preventDefault() to suppress built-in playback\n const voiceEvent = new CustomEvent('gengage:chat:voice', {\n detail: { payload: event.meta.voice },\n bubbles: false,\n cancelable: true,\n });\n const allowed = window.dispatchEvent(voiceEvent);\n ga.trackVoiceInput();\n\n // Built-in TTS playback (skipped if host called preventDefault)\n if (allowed) {\n const voicePayload = event.meta.voice as { audio_base64?: string; content_type?: string };\n if (voicePayload.audio_base64) {\n this._activeTtsHandle?.stop();\n this._activeTtsHandle = playTtsAudio(\n voicePayload.audio_base64,\n voicePayload.content_type ?? 'audio/ogg',\n );\n }\n }\n }\n\n if (event.meta.redirectTarget || event.meta.redirect) {\n dispatch('gengage:chat:redirect', {\n target: event.meta.redirectTarget ?? null,\n payload: event.meta.redirect ?? null,\n });\n }\n\n // Analyze animation — show panel loading skeleton with pulse\n if (event.meta.analyzeAnimation && this.config.productDetailsExtended === true) {\n panelLoadingSeen = true;\n panelContentReceived = false;\n capturePanelSourceIfNeeded();\n if (this._panel) this._panel.currentType = null;\n this._drawer?.showPanelLoading();\n // Default to product details title during analyze\n this._panel?.updateTopBarForLoading('productDetails');\n }\n\n // Thinking step messages — accumulate as checklist in typing indicator\n if (event.meta.loading) {\n const thinkingMessages = Array.isArray(event.meta.thinkingMessages)\n ? event.meta.thinkingMessages.filter((item): item is string => typeof item === 'string')\n : [];\n if (thinkingMessages.length > 0) {\n this._drawer?.setThinkingSteps(thinkingMessages);\n }\n if (typeof event.meta.loadingText === 'string') {\n this._drawer?.addThinkingStep(event.meta.loadingText);\n this._bridge?.send('loadingMessage', { text: event.meta.loadingText });\n }\n }\n\n // Forward visitor engagement data to host page\n if (event.meta.visitorDataResponse) {\n this._bridge?.send('engagingMessage', event.meta.visitorDataResponse);\n }\n\n // Forward form events to host via bridge (Otokoc-specific)\n if (event.meta.formType) {\n this._bridge?.send('glovOtokoc', {\n type: event.meta.formType,\n data: event.meta.formPayload,\n });\n }\n\n // Forward launcher content to host via bridge\n if (event.meta.launcherContent) {\n this._bridge?.send('launcherContent', event.meta.launcherContent);\n }\n\n dispatch('gengage:chat:metadata', { payload: event.meta });\n\n // Extract LLM usage from metadata if present\n const meta = event.meta;\n if (typeof meta.prompt_tokens === 'number' && typeof meta.completion_tokens === 'number') {\n this.track(\n llmUsageEvent(this.analyticsContext(), {\n model: event.model ?? 'unknown',\n prompt_tokens: meta.prompt_tokens as number,\n completion_tokens: meta.completion_tokens as number,\n total_tokens:\n (meta.total_tokens as number) ??\n (meta.prompt_tokens as number) + (meta.completion_tokens as number),\n }),\n );\n }\n }\n },\n onError: (err) => {\n if (streamController) this._abortControllers.delete(streamController);\n // Skip error handling for aborted/superseded requests (including when no active request)\n if (!isPreservePanel && threadId !== this._activeRequestThreadId) return;\n streamDone = true;\n this._activeTypewriter?.cancel();\n this._activeTypewriter = null;\n syncPanelAiAnalysisZone();\n pendingPanelAiSpec = null;\n this._bridge?.send('isResponding', false);\n this._bridge?.send('loadingMessage', { text: null });\n this._drawer?.removeTypingIndicator();\n this._drawer?.clearInputAreaChips();\n // Capture panel state before resetting — needed for error gating below\n const hadPanelContent = panelContentReceived;\n if (panelLoadingSeen && !panelContentReceived) restoreOrClearPanel();\n panelLoadingSeen = false;\n panelContentReceived = false;\n // When the stream already delivered partial content (bot text, panel, etc.),\n // still show backend error text + recovery pills — not only clear stale chips.\n const hasContent =\n botMsg.silent ||\n (botMsg.content != null && botMsg.content.length > 0) ||\n localBotText.length > 0 ||\n hadPanelContent;\n const shouldSuppressOfflineError =\n typeof navigator !== 'undefined' && navigator.onLine === false && isLikelyConnectivityIssue(err);\n\n const removeAssistantPlaceholderBubble = (): void => {\n this._shadow?.querySelector(`[data-message-id=\"${CSS.escape(botMsg.id)}\"]`)?.remove();\n const idx = this._messages.indexOf(botMsg);\n if (idx >= 0) this._messages.splice(idx, 1);\n };\n\n let placeholderBubbleRemoved = false;\n\n const applyStreamErrorRecovery = (): void => {\n if (shouldSuppressOfflineError) return;\n this.emit('error', err);\n const errMsg = err.message;\n if (errMsg === this._lastErrorMessage) {\n this._consecutiveErrorCount++;\n } else {\n this._consecutiveErrorCount = 1;\n this._lastErrorMessage = errMsg;\n }\n const backendDetail = err.message.trim();\n const displayText = backendDetail.length > 0 ? backendDetail : this._i18n.errorMessage;\n const recoveryActions = {\n onRetry: () => {\n if (this._lastSentAction) {\n this._sendAction(this._lastSentAction.action, this._lastSentAction.options);\n }\n },\n onNewQuestion: () => {\n this._drawer?.focusInput();\n },\n };\n\n if (this._consecutiveErrorCount >= 2) {\n removeAssistantPlaceholderBubble();\n placeholderBubbleRemoved = true;\n this._drawer?.showErrorWithRecovery(this._i18n.accountInactiveMessage, recoveryActions);\n return;\n }\n\n if (shouldShowStreamErrorAsRedStrip(err, displayText)) {\n removeAssistantPlaceholderBubble();\n placeholderBubbleRemoved = true;\n this._drawer?.showErrorWithRecovery(displayText, recoveryActions);\n return;\n }\n\n botMsg.content = displayText;\n botMsg.status = 'done';\n const bubbleHtml = sanitizeHtml(displayText.replace(/\\r\\n/g, '\\n').split('\\n').join('<br />'));\n this._ensureAssistantMessageRendered(botMsg);\n this._drawer?.updateBotMessage(botMsg.id, bubbleHtml);\n this._drawer?.showRecoveryPillsOnly(recoveryActions);\n };\n\n if (!hasContent) {\n if (isPdpAutoLaunch || this._hasUnavailableProductContext()) {\n this._drawer?.setPills([]);\n // Show soft fallback instead of generic error for auto-launch\n const fallback = this._i18n.productNotFoundMessage;\n botMsg.content = fallback;\n botMsg.status = 'done';\n this._ensureAssistantMessageRendered(botMsg);\n this._drawer?.updateBotMessage(botMsg.id, fallback);\n this._markUnavailableProductContext();\n } else {\n applyStreamErrorRecovery();\n if (shouldSuppressOfflineError) {\n return;\n }\n }\n } else {\n this._drawer?.setPills([]);\n if (!botMsg.silent) {\n applyStreamErrorRecovery();\n }\n }\n if (isPdpAutoLaunch) {\n this._pdpPrimingInFlight = false;\n this._flushQueuedUserMessages();\n }\n if (!placeholderBubbleRemoved && botMsg.status === 'streaming') {\n botMsg.status = 'error';\n }\n\n // Skip analytics for suppressed offline errors (consistent with the\n // early return in the !hasContent branch above).\n if (!shouldSuppressOfflineError) {\n this.track(\n streamErrorEvent(this.analyticsContext(), {\n request_id: requestId,\n error_code: 'STREAM_ERROR',\n error_message: err.message,\n widget: 'chat',\n }),\n );\n }\n },\n onDone: () => {\n if (streamController) this._abortControllers.delete(streamController);\n // Skip cleanup for aborted/superseded requests\n if (!isPreservePanel && threadId !== this._activeRequestThreadId) return;\n streamDone = true;\n syncPanelAiAnalysisZone();\n // product_list never arrived but AI Top Picks / groupings were deferred — show in chat\n if (pendingPanelAiSpec) {\n const flushCtx = this._buildRenderContext();\n flushCtx.isStreaming = false;\n const messagesContainer = this._shadow?.querySelector('.gengage-chat-messages');\n if (messagesContainer) {\n const inline = this._renderUISpec(pendingPanelAiSpec, flushCtx);\n if (botMsg.threadId) inline.dataset['threadId'] = botMsg.threadId;\n messagesContainer.appendChild(inline);\n inline.scrollIntoView({ behavior: 'auto', block: 'end' });\n this._drawer?.refreshPresentationCollapsed();\n }\n pendingPanelAiSpec = null;\n }\n this._activeRequestThreadId = null;\n // Reset consecutive error counter on successful stream completion\n this._consecutiveErrorCount = 0;\n this._lastErrorMessage = '';\n this._bridge?.send('isResponding', false);\n this._bridge?.send('loadingMessage', { text: null });\n this._drawer?.removeTypingIndicator();\n if (panelLoadingSeen && !panelContentReceived) restoreOrClearPanel();\n panelLoadingSeen = false;\n panelContentReceived = false;\n // Detect failed PDP auto-launch: silent launch action that produced\n // no visible content (no bot text, no panel). Show a soft fallback\n // message so the user isn't left with an empty chat.\n if (isPdpAutoLaunch && !localBotText && !panelContentReceived) {\n const fallback = this._i18n.productNotFoundMessage;\n botMsg.content = fallback;\n this._ensureAssistantMessageRendered(botMsg);\n this._drawer?.updateBotMessage(botMsg.id, fallback);\n this._markUnavailableProductContext();\n }\n if (isPdpAutoLaunch) {\n this._pdpPrimingInFlight = false;\n const hadQueuedMessages = this._queuedUserMessages.length > 0;\n this._flushQueuedUserMessages();\n if (!hadQueuedMessages) {\n this._ensurePdpPrimeSuggestedUiIfNeeded();\n }\n }\n\n if (botMsg.status === 'streaming') {\n botMsg.status = 'done';\n ga.trackMessageReceived();\n }\n this._presentation.finalizeAssistantGroup(threadId);\n\n // Reveal the comparison toggle button (hidden during streaming) with fade-in\n const hiddenCompareBtn = this._shadow?.querySelector('.gengage-chat-comparison-toggle-btn--hidden');\n if (hiddenCompareBtn) {\n hiddenCompareBtn.classList.remove('gengage-chat-comparison-toggle-btn--hidden');\n hiddenCompareBtn.classList.add('gengage-chat-comparison-toggle-btn--reveal');\n }\n\n this.emit('message', botMsg);\n\n // Snapshot current panel content for this message's history.\n // Pass a rebuild function so restored panels have live event listeners.\n const panelSource = this._currentPanelSource;\n this._panel?.snapshotForMessage(\n botMsg.id,\n panelSource\n ? () => {\n const ctx = this._buildRenderContext();\n return this._renderPanelFromSource(panelSource, ctx);\n }\n : undefined,\n );\n // Make the bot message bubble clickable to restore its panel state\n this._panel?.attachClickHandler(botMsg.id);\n\n this.track(\n streamDoneEvent(this.analyticsContext(), {\n request_id: requestId,\n latency_ms: Date.now() - streamStart,\n chunk_count: chunkIndex,\n widget: 'chat',\n }),\n );\n\n this.track(\n meteringIncrementEvent(this.analyticsContext(), {\n meter_key: 'chat_request',\n quantity: 1,\n unit: 'request',\n }),\n );\n\n this.track(\n chatHistorySnapshotEvent(this.analyticsContext(), {\n message_count: this._messages.length,\n history_ref: this.config.session?.sessionId ?? '',\n redaction_level: 'none',\n }),\n );\n\n // Persist session to IndexedDB (fire-and-forget)\n this._persistToIndexedDB().catch(() => {\n /* non-fatal */\n });\n },\n },\n transport,\n );\n this._abortControllers.add(streamController);\n\n // Show \"Stop generating\" button for user-visible streams\n if (!options?.silent && !isPreservePanel) {\n const ctrl = streamController;\n this._drawer?.showStopButton(() => {\n ctrl.abort();\n this._abortControllers.delete(ctrl);\n this._drawer?.removeTypingIndicator();\n this._bridge?.send('isResponding', false);\n this._bridge?.send('loadingMessage', { text: null });\n if (botMsg.status === 'streaming') {\n botMsg.status = 'done';\n }\n });\n }\n }\n\n /** Return messages visible at the current thread cursor. */\n private _getVisibleMessages(): ChatMessage[] {\n const msgs = this._messages.filter((m) => !m.silent);\n if (!this._currentThreadId) return msgs;\n const cutoff = this._currentThreadId;\n return msgs.filter((m) => !m.threadId || m.threadId <= cutoff);\n }\n\n /** Handle rollback-on-click from a user message bubble. */\n private _appendSimilarsToPanel(spec: UISpec, ctx: import('../chat/types.js').ChatUISpecRenderContext): void {\n if (!this._drawer) return;\n const panelEl = this._drawer.getPanelContentElement();\n if (!panelEl) return;\n ctx.panelProductListHeading = this._i18n.similarProductsLabel ?? 'Similar Products';\n const grid = this._renderUISpec(spec, ctx);\n grid.classList.add('gengage-chat-product-details-similars');\n panelEl.appendChild(grid);\n this._mergePanelSourceWithSimilars(spec);\n }\n\n /** After similars grid is appended, extend panel source so back/history/snapshot rebuild includes it. */\n private _mergePanelSourceWithSimilars(similarsSpec: UISpec): void {\n const prev = this._currentPanelSource;\n if (prev?.kind === 'spec' && this._panel?.currentType === 'ProductDetailsPanel') {\n this._currentPanelSource = {\n kind: 'productDetailsWithSimilars',\n pdpSpec: prev.spec,\n similarsSpec,\n };\n }\n }\n\n /** Re-render PDP plus similar-products block (matches `_appendSimilarsToPanel` structure). */\n private _renderProductDetailsWithSimilars(\n pdpSpec: UISpec,\n similarsSpec: UISpec,\n ctx: ChatUISpecRenderContext,\n ): HTMLElement {\n this._applyPanelListHeadingToContext(ctx, {\n kind: 'productDetailsWithSimilars',\n pdpSpec,\n similarsSpec,\n });\n const panelEl = this._renderUISpec(pdpSpec, ctx);\n const grid = this._renderUISpec(similarsSpec, ctx);\n grid.classList.add('gengage-chat-product-details-similars');\n panelEl.appendChild(grid);\n return panelEl;\n }\n\n /** Sets ctx.panelProductListHeading for ProductGrid / PDP+similars rebuilds. */\n private _applyPanelListHeadingToContext(ctx: ChatUISpecRenderContext, source: PanelSource): void {\n ctx.panelProductListHeading = undefined;\n if (!this._panel) return;\n if (source.kind === 'spec') {\n const root = source.spec.elements[source.spec.root];\n if (root?.type === 'ProductGrid') {\n const n = root.children?.length ?? 0;\n if (n > 0) {\n ctx.panelProductListHeading = this._panel.titleForComponent(\n 'ProductGrid',\n (root.props?.['panelTitle'] as string | undefined) ?? undefined,\n );\n }\n }\n } else if (source.kind === 'productDetailsWithSimilars') {\n const simRoot = source.similarsSpec.elements[source.similarsSpec.root];\n if (simRoot?.type === 'ProductGrid' && (simRoot.children?.length ?? 0) > 0) {\n ctx.panelProductListHeading = this._i18n.similarProductsLabel ?? 'Similar Products';\n }\n }\n }\n\n private _renderPanelFromSource(source: PanelSource, ctx: ChatUISpecRenderContext): HTMLElement {\n this._applyPanelListHeadingToContext(ctx, source);\n if (source.kind === 'favorites') {\n return this._buildFavoritesPageEl();\n }\n if (source.kind === 'productDetailsWithSimilars') {\n return this._renderProductDetailsWithSimilars(source.pdpSpec, source.similarsSpec, ctx);\n }\n return this._renderUISpec(source.spec, ctx);\n }\n\n private _handleRollback(messageId: string): void {\n const msg = this._messages.find((m) => m.id === messageId);\n if (!msg?.threadId) return;\n this._rollbackToThread(msg.threadId);\n }\n\n private _ensurePdpPrimeSuggestedUiIfNeeded(): void {\n const sku = this.config.pageContext?.sku;\n if (!sku || !this._drawer) return;\n if (this._hasUnavailableProductContext()) return;\n\n const contextKey: OpeningContextKey = 'product';\n const configured = this._resolveContextualOpeningActions(contextKey);\n if (configured.length > 0) {\n this._drawer.setInputAreaChips(\n configured.map((chip) => ({\n label: chip.title,\n onAction: () => this._sendAction(this._resolveContextualOpeningAction(chip, contextKey)),\n ...(chip.icon ? { icon: chip.icon } : {}),\n })),\n );\n return;\n }\n\n this._drawer.setInputAreaChips([\n {\n label: this._i18n.groundingReviewCta,\n icon: 'review',\n onAction: () =>\n this._sendAction({\n title: this._i18n.customerReviewsTitle,\n type: 'reviewSummary',\n payload: { sku },\n }),\n },\n {\n label: this._i18n.findSimilarLabel,\n icon: 'similar',\n onAction: () =>\n this._sendAction({\n title: this._i18n.findSimilarLabel,\n type: 'findSimilar',\n payload: { sku },\n }),\n },\n ]);\n }\n\n /** Rewind the conversation to the given thread. */\n private _rollbackToThread(threadId: string): void {\n // Validate thread ID exists in known threads\n if (this._panel && this._panel.threads.length > 0 && !this._panel.threads.includes(threadId)) {\n // Check if any message has this threadId as fallback\n if (!this._messages.some((m) => m.threadId === threadId)) {\n return; // Invalid thread ID — silently ignore\n }\n }\n this._currentThreadId = threadId;\n this._extendedModeManager?.setHiddenByUser(false);\n\n // Presentation collapse (single-thread focus) only at the conversation tip.\n // When navigating back to an older panel thread, show the full transcript up to\n // this thread — same effect as \"show earlier messages\" without an extra tap.\n // Forward navigation to the tip restores collapse + scroll-to-reveal UX.\n const atConversationTip = this._lastThreadId != null && threadId === this._lastThreadId;\n if (atConversationTip) {\n this._presentation.setFocusedThreadId(threadId);\n this._drawer?.setPresentationFocus(threadId);\n } else {\n this._presentation.releaseFocusedThread();\n this._drawer?.setPresentationFocus(null);\n }\n this._drawer?.setFormerMessagesButtonVisible(false);\n\n // Toggle visibility of messages after the cutoff\n for (const msg of this._messages) {\n const bubble = this._shadow?.querySelector(`[data-message-id=\"${CSS.escape(msg.id)}\"]`);\n if (!bubble) continue;\n if (msg.threadId && msg.threadId > threadId) {\n bubble.classList.add('gengage-chat-bubble--hidden');\n } else {\n bubble.classList.remove('gengage-chat-bubble--hidden');\n }\n }\n\n // Hide inline UISpec elements from future threads\n this._shadow?.querySelectorAll('[data-thread-id]').forEach((el) => {\n if (el instanceof HTMLElement && el.dataset['threadId'] && el.dataset['threadId'] > threadId) {\n el.classList.add('gengage-chat-bubble--hidden');\n } else if (el instanceof HTMLElement) {\n el.classList.remove('gengage-chat-bubble--hidden');\n }\n });\n\n // Restore panel snapshot from the target thread's bot message\n const targetBot = this._messages.find((m) => m.role === 'assistant' && m.threadId === threadId);\n const restored = targetBot ? this._panel?.restoreForMessage(targetBot.id) : false;\n if (!restored) {\n this._drawer?.clearPanel();\n this._currentPanelSource = null;\n }\n if (restored && targetBot) {\n // Update panel source so drilldown history captures the correct context.\n // We can't reconstruct the exact spec, so clear it to prevent stale history pushes.\n this._currentPanelSource = null;\n }\n // Always update topbar navigation state for the new thread position\n const panelType = this._panel!.currentType ?? '';\n this._panel?.updateTopBar(panelType);\n\n // Clear suggestion pills (they belong to the latest thread)\n this._drawer?.setPills([]);\n\n requestAnimationFrame(() => {\n this._drawer?.scrollThreadIntoView(threadId, 'auto');\n });\n\n // Load context from IndexedDB for the target thread so the next request\n // sends the correct historical context, then prune future entries.\n if (this._session?.db && this.config.session?.sessionId) {\n const sid = this.config.session.sessionId;\n void (async () => {\n try {\n const ctx = await this._session?.db?.loadContext(sid, threadId);\n if (ctx) this._lastBackendContext = ctx.context;\n await this._session?.db?.deleteContextsAfterThread(sid, threadId);\n } catch {\n /* non-fatal */\n }\n })();\n }\n }\n\n // ---------------------------------------------------------------------------\n // IndexedDB persistence (delegates to SessionPersistence)\n // ---------------------------------------------------------------------------\n\n private async _persistToIndexedDB(): Promise<void> {\n if (!this._session || !this.config.session?.sessionId) return;\n await this._session.persist({\n userId: this.config.session.userId ?? '',\n appId: this.config.accountId,\n sessionId: this.config.session.sessionId,\n messages: this._messages,\n currentThreadId: this._currentThreadId,\n lastThreadId: this._lastThreadId,\n chatCreatedAt: this._chatCreatedAt,\n panelSnapshots: this._panel?.snapshots ?? new Map(),\n panelThreads: this._panel?.threads ?? [],\n thumbnailEntries: this._thumbnailEntries,\n lastBackendContext: this._lastBackendContext,\n sku: this.config.pageContext?.sku,\n });\n }\n\n private _isSameOriginUrl(url: string): boolean {\n try {\n if (!url.trim()) return false;\n const parsed = new URL(url, window.location.href);\n return parsed.origin === window.location.origin;\n } catch {\n return false;\n }\n }\n\n private _markUnavailableProductContext(): void {\n this._productContextUnavailableSku = this.config.pageContext?.sku ?? null;\n }\n\n private _clearUnavailableProductContext(): void {\n this._productContextUnavailableSku = null;\n }\n\n private _hasUnavailableProductContext(): boolean {\n const currentSku = this.config.pageContext?.sku;\n return currentSku !== undefined && currentSku.length > 0 && this._productContextUnavailableSku === currentSku;\n }\n\n private _ensureAssistantMessageRendered(msg: ChatMessage): void {\n const bubble = this._shadow?.querySelector(`[data-message-id=\"${CSS.escape(msg.id)}\"]`);\n if (bubble || !this._drawer) return;\n if (msg.role === 'assistant' && msg.threadId && !this._threadsWithFirstBot.has(msg.threadId)) {\n this._threadsWithFirstBot.add(msg.threadId);\n this._drawer.addMessage(msg);\n this._drawer.markFirstBotMessage(msg.id);\n return;\n }\n this._drawer.addMessage(msg);\n }\n\n private async _saveSessionAndOpenURL(url: string): Promise<void> {\n if (!this._session) return;\n await this._session.saveAndOpenURL(url, () => this._persistToIndexedDB(), this._bridge);\n }\n\n private async _loadPayload(threadId: string, messageId: string): Promise<import('../common/types.js').UISpec | null> {\n if (!this._session) return null;\n return this._session.loadPayload(threadId, messageId);\n }\n\n /**\n * Attempt to restore chat session from IndexedDB.\n * Always restores when IDB has session data for the current sessionId.\n * Best-effort — failures are silently ignored.\n */\n private async _restoreFromIndexedDB(shouldRestore: boolean): Promise<void> {\n if (!this._session?.db) return;\n const sessionId = this.config.session?.sessionId;\n if (!sessionId) return;\n\n const userId = this.config.session?.userId ?? '';\n const appId = this.config.accountId;\n\n // Always restore favorites (user preference, not session state)\n await this._session.loadFavorites(userId, appId);\n this._drawer?.updateFavoritesBadge(this._session.favoritedSkus.size);\n\n // Only restore chat state on explicit handoff (e.g. SimRel product navigation)\n if (!shouldRestore) return;\n\n const session = await this._session.db?.loadSession(userId, appId, sessionId);\n if (!session || session.messages.length === 0) return;\n\n // Don't restore a session saved for a different SKU\n const currentSku = this.config.pageContext?.sku;\n if (currentSku && session.sku && session.sku !== currentSku) return;\n\n // Prevent duplicate auto-launch: session already has messages, so PDP launch already happened\n this._pdpLaunched = true;\n\n // Lock auto-scroll during restore to prevent visual jump\n this._drawer?.lockScrollForRestore();\n\n // Restore thread cursors and creation timestamp\n this._currentThreadId = session.currentThreadId;\n this._lastThreadId = session.lastThreadId;\n // Validate thread invariants — corrupted IDB data must not break navigation\n if (this._currentThreadId && this._lastThreadId && this._currentThreadId > this._lastThreadId) {\n this._currentThreadId = this._lastThreadId;\n }\n this._chatCreatedAt = session.createdAt;\n\n // Restore panel threads and thumbnail entries\n if (session.panelThreads) {\n this._panel!.threads = session.panelThreads;\n }\n if (session.thumbnailEntries) {\n this._thumbnailEntries = session.thumbnailEntries;\n this._drawer?.setThumbnails(this._thumbnailEntries);\n }\n\n // Restore panel snapshots from serialized HTML (sanitize for defense-in-depth)\n if (session.panelSnapshotHtml) {\n for (const [msgId, html] of Object.entries(session.panelSnapshotHtml)) {\n const container = document.createElement('div');\n container.innerHTML = sanitizeHtml(html);\n this._panel!.snapshots.set(msgId, container);\n }\n }\n\n // Track highest message ID to avoid collisions with new messages\n let maxMsgNum = 0;\n\n // Replay messages into DOM\n for (const msg of session.messages) {\n const chatMsg: ChatMessage = {\n id: msg.id,\n role: msg.role,\n timestamp: msg.timestamp,\n status: msg.status,\n };\n if (msg.threadId !== undefined) chatMsg.threadId = msg.threadId;\n if (msg.content !== undefined) chatMsg.content = msg.content;\n if (msg.silent) chatMsg.silent = true;\n\n this._messages.push(chatMsg);\n\n // Skip silent messages from DOM rendering\n if (chatMsg.silent) continue;\n\n if (chatMsg.role === 'assistant' && chatMsg.threadId && !this._threadsWithFirstBot.has(chatMsg.threadId)) {\n this._threadsWithFirstBot.add(chatMsg.threadId);\n this._drawer?.markFirstBotMessage(chatMsg.id);\n }\n this._drawer?.addMessage(chatMsg);\n\n // Track message ID counter\n const idNum = parseInt(msg.id.replace('msg-', ''), 10);\n if (!isNaN(idNum) && idNum > maxMsgNum) maxMsgNum = idNum;\n\n // Re-render inline UISpec elements for bot messages (load from payload store)\n if (chatMsg.role === 'assistant' && chatMsg.threadId) {\n const uiSpec = await this._loadPayload(chatMsg.threadId, chatMsg.id);\n if (uiSpec) {\n chatMsg.uiSpec = uiSpec;\n this._restoreInlineUISpec(chatMsg);\n this._panel?.attachClickHandler(chatMsg.id);\n // Clear after render to maintain lean pattern\n delete chatMsg.uiSpec;\n }\n }\n }\n\n // Advance message ID counter past restored messages\n if (maxMsgNum > this._currentMessageId) {\n this._currentMessageId = maxMsgNum;\n }\n\n // Restore backend context with fallback chain\n if (this._currentThreadId) {\n let ctx = await this._session.db?.loadContext(sessionId, this._currentThreadId);\n if (!ctx) {\n ctx = await this._session.db?.loadLatestContext(sessionId);\n }\n if (ctx) this._lastBackendContext = ctx.context;\n }\n\n // Restore panel for the current thread's latest bot message\n if (this._currentThreadId) {\n const panelBot = [...this._messages]\n .reverse()\n .find((m) => m.role === 'assistant' && m.threadId === this._currentThreadId && !m.silent);\n if (panelBot && this._panel!.snapshots.has(panelBot.id)) {\n this._panel?.restoreForMessage(panelBot.id);\n }\n }\n\n // Apply thread visibility — hide messages from future threads\n if (this._currentThreadId) {\n const cutoff = this._currentThreadId;\n for (const msg of this._messages) {\n if (msg.threadId && msg.threadId > cutoff) {\n const bubble = this._shadow?.querySelector(`[data-message-id=\"${CSS.escape(msg.id)}\"]`);\n bubble?.classList.add('gengage-chat-bubble--hidden');\n }\n }\n this._shadow?.querySelectorAll('[data-thread-id]').forEach((el) => {\n if (el instanceof HTMLElement && el.dataset['threadId'] && el.dataset['threadId'] > cutoff) {\n el.classList.add('gengage-chat-bubble--hidden');\n }\n });\n }\n\n // Update panel topbar if we have panel threads\n if (this._panel!.threads.length > 0 && this._currentThreadId) {\n const lastPanelThread = this._panel!.threads[this._panel!.threads.length - 1];\n if (lastPanelThread) {\n const lastPanelBot = [...this._messages]\n .reverse()\n .find((m) => m.role === 'assistant' && m.threadId === lastPanelThread);\n if (lastPanelBot?.threadId) {\n const uiSpec = await this._loadPayload(lastPanelBot.threadId, lastPanelBot.id);\n if (uiSpec) {\n const rootEl = uiSpec.elements[uiSpec.root];\n if (rootEl) {\n this._panel?.updateTopBar(rootEl.type);\n }\n }\n }\n }\n }\n\n this._presentation.releaseFocusedThread();\n this._drawer?.setPresentationFocus(null);\n\n // After lockout expires, scroll to last thread boundary instead of absolute bottom\n setTimeout(() => {\n this._drawer?.scrollToLastThread();\n }, 550);\n }\n\n /**\n * Toggle comparison mode or individual SKU selection, then refresh the DOM.\n * Extracted so both the render-context callback and DOM-created checkboxes\n * share the same state-mutation + refresh path.\n */\n /**\n * Panel back navigation: pop local drilldown history first (e.g. card→detail),\n * then fall back to thread-level history.\n */\n private _navigatePanelBack(): void {\n const prev = this._localPanelHistory.pop();\n if (prev) {\n const ctx = this._buildRenderContext();\n const el = this._renderPanelFromSource(prev.source, ctx);\n this._drawer?.setPanelContent(el);\n this._drawer?.setDividerPreviewEnabled(this._shouldUseDividerPreviewForSource(prev.source));\n this._currentPanelSource = prev.source;\n const canBack = this._localPanelHistory.length > 0 || (this._panel?.threads.length ?? 0) > 1;\n this._drawer?.updatePanelTopBar(canBack, false, prev.title);\n return;\n }\n // On mobile, when there is no local history left, back = hide the side panel\n // (content is preserved so it can be reopened via the header button)\n if (this._isMobileViewport) {\n this._drawer?.hideMobilePanel();\n return;\n }\n this._panel?.navigateBack();\n }\n\n private _shouldUseDividerPreviewForSpec(spec: UISpec): boolean {\n return spec.elements[spec.root]?.type === 'ProductGrid';\n }\n\n private _shouldUseDividerPreviewForSource(source: PanelSource | null): boolean {\n return source?.kind === 'spec' ? this._shouldUseDividerPreviewForSpec(source.spec) : false;\n }\n\n private _toggleComparisonSku(sku: string): void {\n if (sku === '') {\n this._comparisonSelectMode = !this._comparisonSelectMode;\n this._comparisonSelectionWarning = null;\n if (this._comparisonSelectMode) {\n recordChoicePrompterDismissedForThread(this._currentThreadId ?? '');\n this._choicePrompterEl?.remove();\n this._choicePrompterEl = null;\n this._shadow?.querySelectorAll('.gengage-chat-choice-prompter').forEach((el) => el.remove());\n }\n if (!this._comparisonSelectMode) {\n this._comparisonSelectedSkus = [];\n this._comparisonSelectionWarning = null;\n ga.trackCompareClear();\n }\n } else {\n const idx = this._comparisonSelectedSkus.indexOf(sku);\n if (idx >= 0) {\n this._comparisonSelectedSkus = this._comparisonSelectedSkus.filter((s) => s !== sku);\n this._comparisonSelectionWarning = null;\n } else {\n if (this._comparisonSelectedSkus.length >= GengageChat._MAX_COMPARISON_SELECTION) {\n this._comparisonSelectionWarning =\n this._i18n.compareMaxHint ?? `You can select up to ${GengageChat._MAX_COMPARISON_SELECTION} products`;\n if (this._comparisonRefreshRafId !== null) {\n cancelAnimationFrame(this._comparisonRefreshRafId);\n }\n this._comparisonRefreshRafId = requestAnimationFrame(() => {\n this._comparisonRefreshRafId = null;\n this._refreshComparisonUI();\n });\n return;\n }\n this._comparisonSelectedSkus = [...this._comparisonSelectedSkus, sku];\n this._comparisonSelectionWarning = null;\n ga.trackComparePreselection(sku);\n }\n }\n // Debounce: cancel any pending refresh and schedule a new one to batch rapid toggles\n if (this._comparisonRefreshRafId !== null) {\n cancelAnimationFrame(this._comparisonRefreshRafId);\n }\n this._comparisonRefreshRafId = requestAnimationFrame(() => {\n this._comparisonRefreshRafId = null;\n this._refreshComparisonUI();\n });\n }\n\n /**\n * Refresh the panel DOM to reflect the current comparison state without\n * full re-render. Updates: toggle button active class, checkbox overlays\n * on product cards, and the floating comparison button.\n */\n private _refreshComparisonUI(): void {\n const panelEl = this._shadow?.querySelector('.gengage-chat-panel');\n if (!panelEl) {\n this._drawer?.setComparisonDockContent(null);\n return;\n }\n\n const gridWrapper = panelEl.querySelector('.gengage-chat-product-grid-wrapper');\n const grid = gridWrapper?.querySelector('.gengage-chat-product-grid');\n if (!gridWrapper || !grid) {\n this._drawer?.setComparisonDockContent(null);\n return;\n }\n\n // 1. Toggle comparison button active state\n const toggleBtn = gridWrapper.querySelector('.gengage-chat-comparison-toggle-btn');\n if (toggleBtn) {\n toggleBtn.classList.toggle('gengage-chat-comparison-toggle-btn--active', this._comparisonSelectMode);\n }\n\n // 2. Add or remove checkbox overlays on product cards\n if (this._comparisonSelectMode) {\n const cards = grid.querySelectorAll<HTMLElement>('.gengage-chat-product-card[data-sku]');\n for (const card of cards) {\n if (card.parentElement?.classList.contains('gengage-chat-comparison-select-wrapper')) {\n // Already wrapped — sync selected state\n const wrapper = card.parentElement;\n const selected = this._comparisonSelectedSkus.includes(card.dataset['sku']!);\n wrapper.classList.toggle('gengage-chat-comparison-select-wrapper--selected', selected);\n const toggle = wrapper.querySelector<HTMLButtonElement>('.gengage-chat-comparison-checkbox');\n if (toggle) {\n toggle.dataset['selected'] = selected ? 'true' : 'false';\n toggle.setAttribute('aria-pressed', selected ? 'true' : 'false');\n const icon = toggle.querySelector('.gengage-chat-comparison-checkbox-icon');\n const label = toggle.querySelector('.gengage-chat-comparison-checkbox-label');\n if (icon) {\n icon.innerHTML = selected\n ? '<svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M20 6L9 17l-5-5\"/></svg>'\n : '<span class=\"gengage-chat-comparison-checkbox-dot\"></span>';\n }\n if (label) {\n label.textContent = selected\n ? (this._i18n.comparisonSelectedLabel ?? 'Selected')\n : (this._i18n.comparisonSelectLabel ?? 'Select to compare');\n }\n }\n continue;\n }\n const sku = card.dataset['sku']!;\n const wrapper = document.createElement('div');\n wrapper.className = 'gengage-chat-comparison-select-wrapper';\n const selected = this._comparisonSelectedSkus.includes(sku);\n if (selected) wrapper.classList.add('gengage-chat-comparison-select-wrapper--selected');\n const toggle = document.createElement('button');\n toggle.type = 'button';\n toggle.className = 'gengage-chat-comparison-checkbox';\n toggle.dataset['selected'] = selected ? 'true' : 'false';\n toggle.setAttribute('aria-pressed', selected ? 'true' : 'false');\n const icon = document.createElement('span');\n icon.className = 'gengage-chat-comparison-checkbox-icon';\n icon.innerHTML = selected\n ? '<svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M20 6L9 17l-5-5\"/></svg>'\n : '<span class=\"gengage-chat-comparison-checkbox-dot\"></span>';\n const label = document.createElement('span');\n label.className = 'gengage-chat-comparison-checkbox-label';\n label.textContent = selected\n ? (this._i18n.comparisonSelectedLabel ?? 'Selected')\n : (this._i18n.comparisonSelectLabel ?? 'Select to compare');\n toggle.appendChild(icon);\n toggle.appendChild(label);\n toggle.addEventListener('click', (e) => {\n e.stopPropagation();\n this._toggleComparisonSku(sku);\n });\n card.parentNode!.insertBefore(wrapper, card);\n wrapper.appendChild(toggle);\n wrapper.appendChild(card);\n // Allow clicking anywhere on the card (not just the tiny checkbox) to toggle selection\n wrapper.classList.add('gds-clickable');\n wrapper.addEventListener('click', (e) => {\n if ((e.target as HTMLElement).closest('.gengage-chat-comparison-checkbox')) return;\n e.stopPropagation();\n this._toggleComparisonSku(sku);\n });\n }\n } else {\n // Remove all checkbox wrappers\n const wrappers = grid.querySelectorAll('.gengage-chat-comparison-select-wrapper');\n for (const wrapper of wrappers) {\n const card = wrapper.querySelector('.gengage-chat-product-card');\n if (card && wrapper.parentNode) {\n wrapper.parentNode.insertBefore(card, wrapper);\n wrapper.remove();\n }\n }\n }\n\n // 3. Update the slim bottom-docked comparison bar\n const existingFloating = gridWrapper.querySelector('.gengage-chat-comparison-floating-btn');\n existingFloating?.remove();\n if (this._comparisonSelectMode) {\n const dock = renderFloatingComparisonButton(this._comparisonSelectedSkus, this._buildRenderContext());\n if (this._isMobileViewport) {\n this._drawer?.setComparisonDockContent(dock);\n } else {\n this._drawer?.setComparisonDockContent(null);\n gridWrapper.appendChild(dock);\n }\n } else {\n this._drawer?.setComparisonDockContent(null);\n }\n }\n\n private _parseAddToCartActionPayload(payload: unknown): { sku: string; cartCode: string; quantity: number } | null {\n if (typeof payload !== 'object' || payload === null) return null;\n const rec = payload as Record<string, unknown>;\n const sku = typeof rec['sku'] === 'string' ? rec['sku'] : '';\n const cartCode = typeof rec['cart_code'] === 'string' ? rec['cart_code'] : '';\n let quantity = 1;\n if (typeof rec['quantity'] === 'number' && Number.isFinite(rec['quantity']) && rec['quantity'] > 0) {\n quantity = Math.max(1, Math.floor(rec['quantity']));\n }\n if (!sku || !cartCode) return null;\n return { sku, cartCode, quantity };\n }\n\n private _runChatAddToCartFlow(params: { sku: string; cartCode: string; quantity: number }): void {\n if (this.config.onAddToCart !== undefined) {\n try {\n const result: unknown = this.config.onAddToCart(params);\n if (result instanceof Promise) result.catch((err: unknown) => console.error('[gengage] onAddToCart', err));\n } catch (err) {\n console.error('[gengage] onAddToCart', err);\n }\n }\n ga.trackCartAdd(params.sku, params.quantity);\n const detail = {\n ...params,\n sessionId: this.config.session?.sessionId ?? null,\n };\n dispatch('gengage:chat:add-to-cart', detail);\n this._bridge?.send('addToCart', params);\n void this._runEventCallbacks('gengage-cart-add', detail as unknown as Record<string, unknown>);\n this.track(\n basketAddEvent(this.analyticsContext(), {\n attribution_source: 'chat',\n attribution_action_id: crypto.randomUUID(),\n cart_value: 0, // Host page should enrich via event listener\n currency: this.config.pricing?.currencyCode ?? 'TRY',\n line_items: params.quantity,\n sku: params.sku,\n }),\n );\n // Sends a normalized payload to the backend regardless of the original action's\n // title or extra fields — the backend expects exactly { sku, cart_code, quantity }.\n this._sendAction(\n {\n title: this._i18n.addToCartButton ?? 'Add to Cart',\n type: 'addToCart',\n payload: { sku: params.sku, cart_code: params.cartCode, quantity: params.quantity },\n },\n { preservePanel: true },\n );\n const toastMsg = this._i18n.addedToCartToast ?? 'Added to cart';\n this._drawer?.showCartToast(toastMsg);\n this._drawer?.flashCartBadge();\n }\n\n /**\n * Build a ChatUISpecRenderContext with all callbacks wired up.\n * Used both during streaming and during session restore.\n */\n private _buildRenderContext(): ChatUISpecRenderContext {\n const ctx: ChatUISpecRenderContext = {\n onAction: (action) => {\n ga.trackSuggestedQuestion(action.title, action.type);\n if (action.type === 'addToCart') {\n const addParams = this._parseAddToCartActionPayload(action.payload);\n if (addParams) {\n this._runChatAddToCartFlow(addParams);\n return; // handled — skip the generic _sendAction fallthrough below\n }\n // Parse failed (missing sku/cartCode): fall through to _sendAction as a best-effort\n }\n if (action.type === 'launchSingleProduct') {\n this._drawer?.setDividerPreviewEnabled(false);\n const sku =\n typeof action.payload === 'object' && action.payload !== null && 'sku' in action.payload\n ? String((action.payload as Record<string, unknown>).sku)\n : '';\n if (sku) ga.trackProductDetail(sku, action.title);\n }\n if (action.type === 'findSimilar') {\n const sku =\n typeof action.payload === 'object' && action.payload !== null && 'sku' in action.payload\n ? String((action.payload as Record<string, unknown>).sku)\n : '';\n ga.trackFindSimilars(sku);\n }\n if (action.type === 'getComparisonTable') {\n ga.trackCompareSelected(this._comparisonSelectedSkus);\n }\n // addToCart/like actions should preserve the current panel (product cards stay visible)\n const preservePanel = action.type === 'addToCart' || action.type === 'like';\n this._sendAction(action, preservePanel ? { preservePanel: true } : undefined);\n },\n onProductClick: (params) => {\n ga.trackProductDetail(params.sku);\n // Demo mode: load product in-chat via launchSingleProduct (no navigation)\n // Production mode: navigate to product page (chat auto-opens on new page)\n const shouldNavigate = this.config.isDemoWebsite !== true && this._isSameOriginUrl(params.url);\n if (!shouldNavigate) {\n const displayTitle = params.name?.trim() ? params.name.trim() : params.sku;\n this._sendAction({\n title: displayTitle,\n type: 'launchSingleProduct',\n payload: { sku: params.sku },\n });\n } else {\n dispatch('gengage:similar:product-click', {\n sku: params.sku,\n url: params.url,\n sessionId: this.config.session?.sessionId ?? null,\n });\n this._saveSessionAndOpenURL(params.url);\n }\n },\n onAddToCart: (params) => {\n this._runChatAddToCartFlow(params);\n },\n onProductSelect: (product) => {\n // Save current panel source to local history so back button can re-render it\n if (this._currentPanelSource) {\n const currentTitle = this._drawer?.getPanelTopBarTitle() ?? '';\n this._localPanelHistory.push({ source: this._currentPanelSource, title: currentTitle });\n if (this._localPanelHistory.length > GengageChat._MAX_PANEL_HISTORY) this._localPanelHistory.shift();\n }\n const summaryCtx = this._buildRenderContext();\n const summarySpec: import('../common/types.js').UISpec = {\n root: 'root',\n elements: {\n root: { type: 'ProductSummaryCard', props: { product } },\n },\n };\n const messagesContainer = this._shadow?.querySelector('.gengage-chat-messages');\n if (messagesContainer) {\n const summaryEl = this._renderUISpec(summarySpec, summaryCtx);\n if (this._currentThreadId) {\n summaryEl.dataset['threadId'] = this._currentThreadId;\n }\n messagesContainer.appendChild(summaryEl);\n summaryEl.scrollIntoView({ behavior: 'auto', block: 'end' });\n this._drawer?.refreshPresentationCollapsed();\n }\n if (this.config.productDetailsExtended !== true) {\n this._clearAssistantPanelLikeStreamClearPanel();\n return;\n }\n const detailSpec: import('../common/types.js').UISpec = {\n root: 'root',\n elements: {\n root: {\n type: 'ProductDetailsPanel',\n props: { product },\n },\n },\n };\n this._drawer?.setPanelContent(this._renderUISpec(detailSpec, ctx));\n this._drawer?.setDividerPreviewEnabled(false);\n this._currentPanelSource = { kind: 'spec', spec: detailSpec };\n this._drawer?.updatePanelTopBar(true, false, this._i18n.panelTitleProductDetails);\n },\n i18n: this._i18n,\n pricing: this.config.pricing,\n productSort: this._productSort,\n onSortChange: (sort) => {\n this._productSort = sort;\n },\n comparisonSelectMode: this._comparisonSelectMode,\n comparisonSelectedSkus: this._comparisonSelectedSkus,\n comparisonMaxSelection: GengageChat._MAX_COMPARISON_SELECTION,\n comparisonSelectionWarning: this._comparisonSelectionWarning,\n onToggleComparisonSku: (sku) => {\n this._toggleComparisonSku(sku);\n },\n favoritedSkus: this._session?.favoritedSkus ?? new Set(),\n onFavoriteToggle: (sku, product) => {\n void this._toggleProductFavorite(sku, product);\n },\n isMobile: this._isMobileViewport,\n };\n return ctx;\n }\n\n private async _toggleFavorite(sku: string, product: Record<string, unknown>): Promise<void> {\n if (!this._session) return;\n const userId = this.config.session?.userId ?? '';\n const appId = this.config.accountId;\n await this._session.toggleFavorite(userId, appId, sku, product);\n this._drawer?.updateFavoritesBadge(this._session.favoritedSkus.size);\n }\n\n /** Revert optimistic heart UI after a failed host favorite callback. */\n private _revertFavoriteHeartUi(sku: string): void {\n const btns = this._shadow?.querySelectorAll(`[data-gengage-favorite-sku=\"${CSS.escape(sku)}\"]`);\n if (!btns?.length) return;\n for (const btn of btns) {\n if (!(btn instanceof HTMLButtonElement)) continue;\n btn.classList.toggle('gengage-chat-favorite-btn--active');\n const svg = btn.querySelector('svg');\n if (svg) {\n svg.setAttribute('fill', btn.classList.contains('gengage-chat-favorite-btn--active') ? 'currentColor' : 'none');\n }\n }\n }\n\n /**\n * Product-card favorite: dispatches window + bridge for the host, then runs `addCallback('gengage-product-favorite')`\n * handlers when registered. If none are registered, falls back to IDB favorites + optional `like` backend action.\n */\n private async _toggleProductFavorite(sku: string, product: Record<string, unknown>): Promise<void> {\n const wasLiked = this._session?.favoritedSkus.has(sku) ?? false;\n const favorited = !wasLiked;\n const detail = {\n sku,\n product,\n favorited,\n sessionId: this.config.session?.sessionId ?? null,\n };\n\n dispatch('gengage:chat:product-favorite', detail);\n this._bridge?.send('productFavorite', detail);\n\n const callbacks = this._eventCallbacks.get('gengage-product-favorite');\n if (callbacks && callbacks.size > 0) {\n for (const cb of callbacks) {\n try {\n const result = cb(detail);\n const success = result instanceof Promise ? await result : result;\n if (success === false) {\n this._revertFavoriteHeartUi(sku);\n this._handleCallbackFailure('gengage-product-favorite', detail);\n return;\n }\n } catch {\n this._revertFavoriteHeartUi(sku);\n this._handleCallbackFailure('gengage-product-favorite', detail);\n return;\n }\n }\n // All callbacks succeeded — sync in-memory state so the next click reads the correct direction.\n // IDB is intentionally skipped: the host owns persistence in callback mode.\n if (this._session) {\n if (favorited) {\n this._session.favoritedSkus.add(sku);\n } else {\n this._session.favoritedSkus.delete(sku);\n }\n this._drawer?.updateFavoritesBadge(this._session.favoritedSkus.size);\n }\n return;\n }\n\n await this._toggleFavorite(sku, product);\n if (favorited) {\n ga.trackLikeProduct(sku);\n const productName = (product['name'] as string | undefined) ?? sku;\n this._sendAction(\n {\n title: productName,\n type: 'like',\n payload: { sku },\n },\n { preservePanel: true },\n );\n }\n }\n\n private _openFavoritesPanel(): void {\n if (!this._drawer) return;\n\n // Save current panel source to local history so back button can re-render it\n if (this._currentPanelSource) {\n const currentTitle = this._drawer.getPanelTopBarTitle() ?? '';\n this._localPanelHistory.push({ source: this._currentPanelSource, title: currentTitle });\n if (this._localPanelHistory.length > GengageChat._MAX_PANEL_HISTORY) this._localPanelHistory.shift();\n }\n\n this._drawer.setPanelContent(this._buildFavoritesPageEl());\n this._drawer.setDividerPreviewEnabled(false);\n this._currentPanelSource = { kind: 'favorites' };\n this._drawer.updatePanelTopBar(true, false, this._i18n.favoritesPageTitle);\n }\n\n private _buildFavoritesPageEl(): HTMLElement {\n const favorites = this._session?.getFavoriteProducts() ?? [];\n\n if (favorites.length === 0) {\n const empty = document.createElement('div');\n empty.className = 'gengage-chat-favorites-empty';\n\n const icon = document.createElement('div');\n icon.className = 'gengage-chat-favorites-empty-icon';\n icon.innerHTML = `<svg width=\"40\" height=\"40\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z\"/></svg>`;\n empty.appendChild(icon);\n\n const text = document.createElement('p');\n text.textContent = this._i18n.emptyFavoritesMessage;\n empty.appendChild(text);\n\n return empty;\n }\n\n // Convert favorites to product records and render as ProductGrid UISpec\n const elements: import('../common/types.js').UISpec['elements'] = {};\n const childKeys: string[] = [];\n\n for (const [i, fav] of favorites.entries()) {\n const key = `card_${i}`;\n childKeys.push(key);\n elements[key] = {\n type: 'ProductCard',\n props: {\n product: {\n sku: fav.sku,\n name: fav.name,\n imageUrl: fav.imageUrl,\n price: fav.price,\n } as Record<string, unknown>,\n },\n };\n }\n\n elements['grid'] = { type: 'ProductGrid', children: childKeys };\n\n const spec: import('../common/types.js').UISpec = { root: 'grid', elements };\n return this._renderUISpec(spec, this._buildRenderContext());\n }\n\n /**\n * Run registered callbacks for a GA4 event.\n * If any callback returns false or throws, handle the failure (e.g. show error for cart-add).\n */\n private async _runEventCallbacks(eventName: string, detail: Record<string, unknown>): Promise<void> {\n const callbacks = this._eventCallbacks.get(eventName);\n if (!callbacks || callbacks.size === 0) return;\n\n for (const cb of callbacks) {\n try {\n const result = cb(detail);\n const success = result instanceof Promise ? await result : result;\n if (success === false) {\n this._handleCallbackFailure(eventName, detail);\n return;\n }\n } catch {\n this._handleCallbackFailure(eventName, detail);\n return;\n }\n }\n }\n\n /**\n * Handle a callback failure — for add-to-cart, show an error message in chat.\n */\n private _handleCallbackFailure(eventName: string, _detail: Record<string, unknown>): void {\n if (eventName === 'gengage-cart-add') {\n const errorText = this._i18n.cartAddErrorMessage;\n const botMsg = this._createMessage('assistant', errorText);\n if (this._currentThreadId) botMsg.threadId = this._currentThreadId;\n this._messages.push(botMsg);\n this._drawer?.addMessage(botMsg);\n // Note: _sendAction is NOT called here — onAddToCart already sent the backend\n // request unconditionally. Sending again would duplicate the cart-add action.\n }\n if (eventName === 'gengage-product-favorite') {\n const errorText = this._i18n.favoriteToggleErrorMessage;\n const botMsg = this._createMessage('assistant', errorText);\n if (this._currentThreadId) botMsg.threadId = this._currentThreadId;\n this._messages.push(botMsg);\n this._drawer?.addMessage(botMsg);\n }\n }\n\n /**\n * Re-render inline UISpec elements for a restored bot message.\n * Inserts them into the messages container after the message bubble.\n */\n private _restoreInlineUISpec(chatMsg: ChatMessage): void {\n if (!chatMsg.uiSpec || !this._drawer) return;\n const spec = chatMsg.uiSpec;\n const rootElement = spec.elements[spec.root];\n if (!rootElement) return;\n\n const componentType = rootElement.type;\n\n // ActionButtons are rendered as pills/chips, not inline\n if (componentType === 'ActionButtons') return;\n\n // Panel-only components should not be rendered inline.\n // Note: panelHint is a StreamEvent property not stored in UISpec elements,\n // so we identify panel-only status by component type.\n // ProductDetailsPanel is panel-only but gets a compact ProductSummaryCard below.\n // ComparisonTable is always panel-only.\n // ProductGrid with similarsAppend is panel-appended unless PDP is chat-only layout.\n if (componentType === 'ComparisonTable') return;\n if (\n componentType === 'ProductGrid' &&\n rootElement.props?.['similarsAppend'] === true &&\n this.config.productDetailsExtended === true\n )\n return;\n\n const renderContext = this._buildRenderContext();\n const messagesContainer = this._shadow?.querySelector('.gengage-chat-messages');\n if (!messagesContainer) return;\n\n // ProductDetailsPanel: synthesize a compact ProductSummaryCard for inline rendering\n if (componentType === 'ProductDetailsPanel') {\n const product = rootElement.props?.['product'] as Record<string, unknown> | undefined;\n if (!product) return;\n const inlineSpec: UISpec = {\n root: 'root',\n elements: { root: { type: 'ProductSummaryCard', props: { product } } },\n };\n const inline = this._renderUISpec(inlineSpec, renderContext);\n if (chatMsg.threadId) inline.dataset['threadId'] = chatMsg.threadId;\n messagesContainer.appendChild(inline);\n this._drawer?.refreshPresentationCollapsed();\n return;\n }\n\n const inline = this._renderUISpec(spec, renderContext);\n if (chatMsg.threadId) {\n inline.dataset['threadId'] = chatMsg.threadId;\n }\n messagesContainer.appendChild(inline);\n this._drawer?.refreshPresentationCollapsed();\n }\n\n private _createMessage(role: 'user' | 'assistant', content: string): ChatMessage {\n this._currentMessageId++;\n return {\n id: `msg-${this._currentMessageId}`,\n role,\n content,\n timestamp: Date.now(),\n status: 'done',\n };\n }\n\n private _resolveI18n(config: ChatWidgetConfig): ChatI18n {\n const base = resolveChatLocale(config.locale);\n return { ...base, ...config.i18n };\n }\n\n private _resolveUISpecRegistry(): ChatUISpecRegistry {\n const baseRegistry = createDefaultChatUISpecRegistry();\n return mergeUISpecRegistry(baseRegistry, this.config.renderer?.registry);\n }\n\n private _renderUISpec(spec: UISpec, context: ChatUISpecRenderContext): HTMLElement {\n const registry = this._resolveUISpecRegistry();\n const unknownRenderer = this.config.renderer?.unknownRenderer ?? defaultChatUnknownUISpecRenderer;\n const defaultRender = (inputSpec: UISpec, inputContext: ChatUISpecRenderContext) =>\n renderUISpec(inputSpec, inputContext, registry, unknownRenderer);\n\n const override = this.config.renderer?.renderUISpec;\n if (!override) return defaultRender(spec, context);\n\n const helpers: UISpecRenderHelpers<ChatUISpecRenderContext> = {\n registry,\n unknownRenderer,\n defaultRender,\n };\n return override(spec, context, helpers);\n }\n}\n\n// ---------------------------------------------------------------------------\n// Convenience factory\n// ---------------------------------------------------------------------------\n\nexport function createChatWidget(): GengageChat {\n return new GengageChat();\n}\n\nexport type {\n ChatWidgetConfig,\n ChatMessage,\n ChatSession,\n ChatUIComponents,\n ChatI18n,\n ChatRendererConfig,\n ChatUISpecRenderContext,\n ChatUISpecRegistry,\n ProductSortState,\n SerializableChatMessage,\n} from './types.js';\nexport {\n renderUISpec,\n createDefaultChatUISpecRegistry,\n defaultChatUnknownUISpecRenderer,\n} from './components/renderUISpec.js';\nexport type { UISpecRenderContext } from './components/renderUISpec.js';\nexport { chatCatalog } from './catalog.js';\nexport type { ChatCatalog, ChatComponentName } from './catalog.js';\nexport {\n getChatScrollElement,\n invalidateChatScrollCache,\n CHAT_SCROLL_ELEMENT_ID,\n} from './utils/get-chat-scroll-element.js';\nexport { ChatPresentationState } from './chat-presentation-state.js';\nexport type { GroupReadState, PresentationGroupMeta, ScrollRequest } from './chat-presentation-state.js';\n"],"mappings":"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"}
|