PageSourceSearch

https://chatbot.nagasakistadiumcity.com/loader.js

js nagasakistadiumcity.com collected 2026-10-01 12:17:02 UTC 22,719 bytes, 718 lines download raw bytes

1(function () {
2  'use strict';
3
4  const scriptTag = document.currentScript;
5
6  const scriptSrc = (scriptTag && scriptTag.src) || '';
7  const baseUrl = scriptSrc.substring(0, scriptSrc.lastIndexOf('/'));
8
9  const widgetOrigin = (scriptTag && scriptTag.getAttribute('data-origin')) || baseUrl;
10  const theme = (scriptTag && scriptTag.getAttribute('data-theme')) || 'light';
11
12  const hasDataMode  = scriptTag && scriptTag.hasAttribute('data-mode');
13  const dataModeRaw  = hasDataMode ? (scriptTag.getAttribute('data-mode') || '') : null;
14  const pageSourceRaw = (scriptTag && scriptTag.getAttribute('data-page-source')) || '';
15
16  const APP_PREFIXES = ['app'];
17  function isAppMode(val) {
18    return APP_PREFIXES.some(function (p) {
19      return val === p || val.startsWith(p + '/');
20    });
21  }
22  const embedMode    = (!hasDataMode || isAppMode(dataModeRaw))
23                         ? 'app'
24                         : 'site';
25  const pageSource   = pageSourceRaw || (hasDataMode && !isAppMode(dataModeRaw)
26    ? (dataModeRaw || '/')
27    : 'app');
28
29  const BRAND_REGISTRY_URL = widgetOrigin + '/config/brand-registry.json';
30  const DEFAULT_BRAND_ID = 'nsc';
31  const parentOrigin = window.location.origin;
32  let brandId = DEFAULT_BRAND_ID;
33  let brandConfig = null;
34
35  let isOpen = false;
36  let iframeLoaded = false;
37  let sizeReady = false;
38  let pendingOpen = false;
39
40  const DEFAULT_WIDGET_WIDTH  = 380;
41  const DEFAULT_WIDGET_HEIGHT = 560;
42  const DEFAULT_TOGGLE_SIZE   = 60;
43  const DEFAULT_TOGGLE_COLOR  = '#1a365d';
44  const DEFAULT_SMALL_BUBBLE_MESSAGE = '';
45
46  const style = document.createElement('style');
47  style.textContent = `
48    :root {
49      --nsc-widget-width: ${DEFAULT_WIDGET_WIDTH}px;
50      --nsc-widget-height: ${DEFAULT_WIDGET_HEIGHT}px;
51      --nsc-toggle-size: ${DEFAULT_TOGGLE_SIZE}px;
52      --nsc-toggle-icon-size: ${DEFAULT_TOGGLE_SIZE}px;
53      --nsc-toggle-color: ${DEFAULT_TOGGLE_COLOR};
54    }
55    .nsc-chat-toggle {
56      position: fixed;
57      bottom: 24px;
58      right: 24px;
59      width: var(--nsc-toggle-size);
60      height: var(--nsc-toggle-size);
61      border-radius: 50%;
62      background: transparent;
63      color: var(--nsc-toggle-color);
64      border: none;
65      cursor: pointer;
66      box-shadow: none;
67      opacity: 0;
68      pointer-events: none;
69      font-size: 28px;
70      z-index: 99998;
71      display: flex;
72      align-items: center;
73      justify-content: center;
74      transition: transform 0.2s ease, box-shadow 0.2s ease;
75      line-height: 1;
76    }
77    .nsc-chat-toggle:hover {
78      transform: scale(1.08);
79      box-shadow: 0 6px 20px rgba(0, 0, 0, 0.25);
80    }
81    .nsc-chat-toggle-icon {
82      width: var(--nsc-toggle-icon-size);
83      height: var(--nsc-toggle-icon-size);
84      object-fit: contain;
85      border-radius: 50%;
86      pointer-events: none;
87    }
88    .nsc-chat-toggle .nsc-close-icon {
89      font-size: 22px;
90      font-weight: bold;
91      font-style: normal;
92    }
93    .nsc-chat-container {
94      position: fixed;
95      bottom: 96px;
96      right: 24px;
97      width: var(--nsc-widget-width);
98      height: var(--nsc-widget-height);
99      max-height: calc(100vh - 48px);
100      max-width: calc(100vw - 48px);
101      border: none;
102      border-radius: 16px;
103      overflow: hidden;
104      box-shadow: 0 8px 32px rgba(0, 0, 0, 0.18);
105      z-index: 99999;
106      opacity: 0;
107      transform: translateY(16px) scale(0.95);
108      pointer-events: none;
109      transition: opacity 0.25s ease, transform 0.25s ease;
110    }
111    .nsc-chat-container.nsc-open {
112      opacity: 1;
113      transform: translateY(0) scale(1);
114      pointer-events: auto;
115    }
116    .nsc-chat-iframe {
117      width: 100%;
118      height: 100%;
119      border: none;
120    }
121    .nsc-chat-resize-handle {
122      position: absolute;
123      top: 0;
124      left: 0;
125      width: 6px;
126      height: 100%;
127      cursor: ew-resize;
128      z-index: 1;
129      border-radius: 16px 0 0 16px;
130      transition: background 0.15s;
131    }
132    .nsc-chat-resize-handle:hover {
133      background: rgba(0, 0, 0, 0.08);
134    }
135    .nsc-chat-resize-handle-bottom {
136      position: absolute;
137      top: 0;
138      left: 16px;
139      right: 0;
140      height: 6px;
141      cursor: ns-resize;
142      z-index: 1;
143      border-radius: 16px 16px 0 0;
144      transition: background 0.15s;
145    }
146    .nsc-chat-resize-handle-bottom:hover {
147      background: rgba(0, 0, 0, 0.08);
148    }
149    .nsc-chat-resize-handle-corner {
150      position: absolute;
151      top: 0;
152      left: 0;
153      width: 16px;
154      height: 16px;
155      cursor: nwse-resize;
156      z-index: 2;
157      border-radius: 16px 0 0 0;
158      transition: background 0.15s;
159    }
160    .nsc-chat-resize-handle-corner:hover {
161      background: rgba(0, 0, 0, 0.12);
162    }
163    .nsc-chat-bubble {
164      position: fixed;
165      bottom: 92px;
166      right: 24px;
167      background: #fff;
168      color: #333;
169      padding: 10px 32px 10px 14px;
170      border-radius: 12px;
171      box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
172      border: 1.5px solid var(--nsc-bubble-border-color, #1a365d);
173      font-size: 14px;
174      font-family: sans-serif;
175      z-index: 99998;
176      white-space: nowrap;
177      pointer-events: auto;
178      animation: nsc-bubble-in 0.3s ease;
179    }
180    .nsc-chat-bubble::before {
181      content: '';
182      position: absolute;
183      bottom: -10px;
184      right: 27px;
185      width: 0;
186      height: 0;
187      border-left: 9px solid transparent;
188      border-right: 9px solid transparent;
189      border-top: 9px solid var(--nsc-bubble-border-color, #1a365d);
190    }
191    .nsc-chat-bubble::after {
192      content: '';
193      position: absolute;
194      bottom: -7px;
195      right: 28px;
196      width: 0;
197      height: 0;
198      border-left: 8px solid transparent;
199      border-right: 8px solid transparent;
200      border-top: 8px solid #fff;
201    }
202    .nsc-chat-bubble-close {
203      position: absolute;
204      top: -10px;
205      right: -10px;
206      background: #fff;
207      border: 1.5px solid var(--nsc-bubble-border-color, #1a365d);
208      border-radius: 50%;
209      cursor: pointer;
210      font-size: 10px;
211      color: var(--nsc-bubble-border-color, #1a365d);
212      line-height: 1;
213      width: 22px;
214      height: 22px;
215      display: flex;
216      align-items: center;
217      justify-content: center;
218      padding: 0;
219    }
220    .nsc-chat-bubble-close:hover {
221      background: #f0f0f0;
222    }
223    .nsc-chat-bubble.nsc-hidden {
224      display: none;
225    }
226    .nsc-chat-bubble.nsc-bubble-collapsed {
227      width: 8em;
228      height: 20px;
229      padding: 0;
230      border-radius: 10px;
231      font-size: 14px;
232      line-height: 18px;
233      text-align: center;
234    }
235    .nsc-chat-bubble.nsc-bubble-collapsed::before {
236      display: block;
237      bottom: -7px;
238      right: 4px;
239      border-left-width: 7px;
240      border-right-width: 7px;
241      border-top-width: 7px;
242    }
243    .nsc-chat-bubble.nsc-bubble-collapsed::after {
244      display: block;
245      bottom: -5px;
246      right: 5px;
247      border-left-width: 6px;
248      border-right-width: 6px;
249      border-top-width: 6px;
250    }
251    @keyframes nsc-bubble-in {
252      from { opacity: 0; transform: translateY(8px); }
253      to { opacity: 1; transform: translateY(0); }
254    }
255    @media (max-width: 480px) {
256      .nsc-chat-container {
257        bottom: 0;
258        right: 0;
259        width: 100vw;
260        height: ${embedMode === 'site' ? 'calc(100vh - 140px)' : '100vh'};
261        max-height: ${embedMode === 'site' ? 'calc(100vh - 140px)' : '100vh'};
262        max-width: 100vw;
263        border-radius: 0;
264        ${embedMode === 'site' ? 'top: auto;' : ''}
265      }
266      .nsc-chat-toggle {
267        bottom: 16px;
268        right: 16px;
269        width: 52px;
270        height: 52px;
271        font-size: 24px;
272      }
273      .nsc-chat-bubble {
274        bottom: 76px;
275        right: 16px;
276      }
277      .nsc-chat-bubble::before {
278        right: 17px;
279      }
280      .nsc-chat-bubble::after {
281        right: 18px;
282      }
283      .nsc-chat-bubble.nsc-bubble-collapsed::before {
284        right: 4px;
285      }
286      .nsc-chat-bubble.nsc-bubble-collapsed::after {
287        right: 5px;
288      }
289    }`;
290  document.head.appendChild(style);
291
292  let savedBorderEnabled = false;
293
294  function applySizeConfig(cfg) {
295    var root = document.documentElement;
296    var w = parseInt(cfg.widgetWidth)  || DEFAULT_WIDGET_WIDTH;
297    var h = parseInt(cfg.widgetHeight) || DEFAULT_WIDGET_HEIGHT;
298    var t = parseInt(cfg.toggleSize)   || DEFAULT_TOGGLE_SIZE;
299    var c = cfg.toggleColor            || DEFAULT_TOGGLE_COLOR;
300    if (cfg.toggleBorderEnabled !== undefined) {
301      savedBorderEnabled = cfg.toggleBorderEnabled !== false;
302    }
303    var borderOn = savedBorderEnabled;
304    root.style.setProperty('--nsc-widget-width',     w + 'px');
305    root.style.setProperty('--nsc-widget-height',    h + 'px');
306    root.style.setProperty('--nsc-toggle-size',      t + 'px');
307    root.style.setProperty('--nsc-toggle-icon-size', borderOn ? Math.round(t * 58 / 60) + 'px' : t + 'px');
308    root.style.setProperty('--nsc-toggle-color',     c);
309    var toggle = document.querySelector('.nsc-chat-toggle');
310    if (toggle) {
311      toggle.style.background = borderOn ? '#fff' : 'transparent';
312      toggle.style.border = borderOn ? '2.5px solid ' + c : 'none';
313      toggle.style.boxShadow = borderOn ? '0 4px 16px rgba(0, 0, 0, 0.15)' : 'none';
314      toggle.style.opacity = '1';
315      toggle.style.pointerEvents = 'auto';
316    }
317  }
318
319  let toggleIconUrl = '';
320  const fallbackToggleIconUrl = widgetOrigin + '/config/ChatToggleIcon.png';
321  let savedBubbleMessage = '';
322  let savedSmallBubbleMessage = '';
323  let savedBubbleSizeMode = 'large';
324  let savedSmallBubbleClickAction = 'openChat';
325  let savedBubbleBoldFirstLine = true;
326  let savedBubbleFirstLineColor = '#1a365d';
327
328  const toggleBtn = document.createElement('button');
329  toggleBtn.className = 'nsc-chat-toggle';
330  toggleBtn.setAttribute('aria-label', 'チャットを開く');
331
332  const toggleImg = document.createElement('img');
333  toggleImg.alt = 'チャット';
334  toggleImg.className = 'nsc-chat-toggle-icon';
335  toggleImg.style.display = 'none';
336  toggleBtn.appendChild(toggleImg);
337
338  const bubble = document.createElement('div');
339  bubble.className = 'nsc-chat-bubble nsc-bubble-collapsed';
340  bubble.innerHTML = DEFAULT_SMALL_BUBBLE_MESSAGE;
341  const bubbleClose = document.createElement('button');
342  bubbleClose.className = 'nsc-chat-bubble-close';
343  bubbleClose.innerHTML = '\u2715';
344  bubbleClose.setAttribute('aria-label', '吹き出しを閉じる');
345  bubbleClose.addEventListener('click', function (e) {
346    e.stopPropagation();
347    bubble.classList.add('nsc-hidden');
348  });
349  bubble.addEventListener('click', function () {
350    if (savedSmallBubbleClickAction === 'showLargeBubble'
351      && bubble.classList.contains('nsc-bubble-collapsed')) {
352      bubble.classList.remove('nsc-bubble-collapsed');
353      renderBubbleMessage(savedBubbleMessage);
354      return;
355    }
356    openChat();
357  });
358  bubble.style.cursor = 'pointer';
359
360  const container = document.createElement('div');
361  container.className = 'nsc-chat-container';
362
363  const iframe = document.createElement('iframe');
364  iframe.className = 'nsc-chat-iframe';
365  iframe.title = 'AIコンシェルジュチャットボット';
366  iframe.setAttribute(
367    'sandbox',
368    'allow-scripts allow-same-origin allow-forms allow-popups allow-popups-to-escape-sandbox'
369  );
370  iframe.setAttribute('allow', 'clipboard-write; microphone');
371  iframe.setAttribute('referrerpolicy', 'no-referrer');
372
373  container.appendChild(iframe);
374
375  const resizeHandle = document.createElement('div');
376  resizeHandle.className = 'nsc-chat-resize-handle';
377  container.appendChild(resizeHandle);
378
379  const resizeHandleBottom = document.createElement('div');
380  resizeHandleBottom.className = 'nsc-chat-resize-handle-bottom';
381  container.appendChild(resizeHandleBottom);
382
383  const resizeHandleCorner = document.createElement('div');
384  resizeHandleCorner.className = 'nsc-chat-resize-handle-corner';
385  container.appendChild(resizeHandleCorner);
386
387  document.body.appendChild(container);
388  document.body.appendChild(bubble);
389  document.body.appendChild(toggleBtn);
390
391  function fetchJson(url) {
392    return fetch(url).then(function (res) {
393      return res.ok ? res.json() : Promise.reject(new Error(String(res.status)));
394    });
395  }
396
397  function resolveBrandId(registry) {
398    if (!registry || !Array.isArray(registry.brands)) return DEFAULT_BRAND_ID;
399    if (!hasDataMode || isAppMode(dataModeRaw)) return registry.defaultBrandId || DEFAULT_BRAND_ID;
400
401    const raw = String(dataModeRaw || '').toLowerCase();
402    const matched = registry.brands.find(function (brand) {
403      return brand && brand.enabled !== false && Array.isArray(brand.domains) && brand.domains.some(function (domain) {
404        const normalizedDomain = String(domain || '').trim().toLowerCase();
405        return normalizedDomain && raw.includes(normalizedDomain);
406      });
407    });
408    return matched && matched.brandId ? matched.brandId : (registry.defaultBrandId || DEFAULT_BRAND_ID);
409  }
410
411  function findBrandConfig(registry, id) {
412    if (!registry || !Array.isArray(registry.brands)) return null;
413    return registry.brands.find(function (brand) {
414      return brand && brand.brandId === id;
415    }) || null;
416  }
417
418  function setToggleIcon(url) {
419    if (!url) return;
420    toggleIconUrl = url;
421    toggleImg.onerror = function () {
422      toggleImg.onerror = null;
423      if (toggleImg.src !== fallbackToggleIconUrl) {
424        toggleIconUrl = fallbackToggleIconUrl;
425        toggleImg.src = fallbackToggleIconUrl;
426      } else {
427        toggleIconUrl = '';
428        toggleImg.style.display = 'none';
429      }
430    };
431    toggleImg.onload = function () {
432      toggleImg.style.display = '';
433    };
434    toggleImg.src = url;
435  }
436
437  function renderBubbleMessage(msg) {
438    bubble.style.width = '';
439    bubble.innerHTML = '';
440    if (msg) {
441      var lines = msg.split('\n');
442      var firstStyle = (savedBubbleBoldFirstLine ? 'font-weight:bold;' : '')
443        + 'color:' + savedBubbleFirstLineColor + ';';
444      if (lines.length > 1) {
445        bubble.innerHTML = '<span style="' + firstStyle + '">' + lines[0] + '</span><br>' + lines.slice(1).join('<br>');
446      } else {
447        bubble.innerHTML = '<span style="' + firstStyle + '">' + lines[0] + '</span>';
448      }
449    }
450    if (msg) {
451      bubble.appendChild(bubbleClose);
452    }
453  }
454
455  function getFirstLine(msg) {
456    return String(msg || '').split(/\r?\n/, 1)[0];
457  }
458
459  function renderCollapsedBubble() {
460    var configuredMessage = getFirstLine(savedSmallBubbleMessage);
461    var fallbackMessage = getFirstLine(savedBubbleMessage);
462    var message = configuredMessage.trim()
463      ? configuredMessage
464      : (fallbackMessage.trim() ? fallbackMessage : DEFAULT_SMALL_BUBBLE_MESSAGE);
465    bubble.innerHTML = '';
466    bubble.textContent = message;
467    bubble.style.width = (Array.from(message).length + 1) + 'em';
468  }
469
470  function applyWidgetSettings(data, registryBrand) {
471    applySizeConfig({
472      widgetWidth:  data.widgetWidth  || null,
473      widgetHeight: data.widgetHeight || null,
474      toggleSize:   data.toggleSize   || null,
475      toggleColor:  data.toggleColor  || null,
476      toggleBorderEnabled: data.toggleBorderEnabled
477    });
478
479    savedBubbleMessage = typeof data.bubbleMessage === 'string' ? data.bubbleMessage : '';
480    savedSmallBubbleMessage = typeof data.smallBubbleMessage === 'string' ? data.smallBubbleMessage : '';
481    savedBubbleSizeMode = data.bubbleSizeMode === 'large' || data.bubbleSizeMode === 'small'
482      ? data.bubbleSizeMode
483      : (brandId === DEFAULT_BRAND_ID ? 'large' : 'small');
484    savedSmallBubbleClickAction = data.smallBubbleClickAction === 'showLargeBubble'
485      || data.smallBubbleClickAction === 'openChat'
486      ? data.smallBubbleClickAction
487      : (brandId === DEFAULT_BRAND_ID ? 'openChat' : 'showLargeBubble');
488
489    const assetBasePath = (registryBrand && registryBrand.assetBasePath) || 'config/';
490    const iconPath = (registryBrand && registryBrand.toggleIcon) || 'ChatToggleIcon.png';
491    setToggleIcon(widgetOrigin + '/' + assetBasePath.replace(/\/$/, '') + '/' + iconPath.replace(/^\//, ''));
492
493    if (data.bubbleEnabled === false && bubble) {
494      bubble.classList.add('nsc-hidden');
495    } else if (bubble) {
496      if (data.bubbleBorderColor) {
497        document.documentElement.style.setProperty('--nsc-bubble-border-color', data.bubbleBorderColor);
498      }
499      savedBubbleBoldFirstLine = data.bubbleBoldFirstLine !== false;
500      savedBubbleFirstLineColor = data.bubbleFirstLineColor || '#1a365d';
501      if (savedBubbleSizeMode === 'large') {
502        bubble.classList.remove('nsc-bubble-collapsed');
503        renderBubbleMessage(savedBubbleMessage);
504      } else {
505        bubble.classList.add('nsc-bubble-collapsed');
506        renderCollapsedBubble();
507      }
508    }
509  }
510
511  fetchJson(BRAND_REGISTRY_URL)
512    .then(function (registry) {
513      brandId = resolveBrandId(registry);
514      brandConfig = findBrandConfig(registry, brandId);
515      const configPath = (brandConfig && brandConfig.configPath) || 'config/messages.json';
516      return fetchJson(widgetOrigin + '/' + configPath.replace(/^\//, ''))
517        .then(function (data) {
518          applyWidgetSettings(data, brandConfig);
519        });
520    })
521    .catch(function () {
522      brandId = DEFAULT_BRAND_ID;
523      brandConfig = null;
524      return fetchJson(widgetOrigin + '/config/messages.json')
525        .then(function (data) {
526          applyWidgetSettings(data, null);
527        })
528        .catch(function () {
529          setToggleIcon(fallbackToggleIconUrl);
530        });
531    })
532    .finally(function () {
533      sizeReady = true;
534      if (pendingOpen) {
535        pendingOpen = false;
536        container.classList.add('nsc-open');
537      }
538    });
539
540  function openChat() {
541
542    if (!iframeLoaded) {
543      const iframeParams = '?theme=' + encodeURIComponent(theme)
544                         + '&mode=' + encodeURIComponent(embedMode)
545                         + '&pageSource=' + encodeURIComponent(pageSource)
546                         + '&brandId=' + encodeURIComponent(brandId)
547                         + '&parentOrigin=' + encodeURIComponent(parentOrigin);
548      iframe.src = widgetOrigin + '/index.html' + iframeParams;
549      iframeLoaded = true;
550    }
551    isOpen = true;
552    bubble.classList.add('nsc-hidden');
553
554    var hideToggle = embedMode === 'site' && window.innerWidth <= 480;
555    if (hideToggle) {
556      toggleBtn.style.display = 'none';
557    } else {
558      toggleBtn.innerHTML = '<span class="nsc-close-icon">\u2715</span>';
559      toggleBtn.setAttribute('aria-label', 'チャットを閉じる');
560    }
561
562    if (sizeReady) {
563      container.classList.add('nsc-open');
564    } else {
565      pendingOpen = true;
566
567      setTimeout(function () {
568        if (pendingOpen) {
569          pendingOpen = false;
570          sizeReady = true;
571          container.classList.add('nsc-open');
572        }
573      }, 2000);
574    }
575  }
576
577  function closeChat() {
578    isOpen = false;
579    container.classList.remove('nsc-open');
580
581    toggleBtn.style.display = '';
582    toggleBtn.textContent = '';
583    const img = document.createElement('img');
584    if (toggleIconUrl) img.src = toggleIconUrl;
585    img.onerror = function () {
586      img.onerror = null;
587      if (img.src !== fallbackToggleIconUrl) {
588        toggleIconUrl = fallbackToggleIconUrl;
589        img.src = fallbackToggleIconUrl;
590      } else {
591        toggleIconUrl = '';
592        img.style.display = 'none';
593      }
594    };
595    img.alt = 'チャット';
596    img.className = 'nsc-chat-toggle-icon';
597    img.style.display = toggleIconUrl ? '' : 'none';
598    toggleBtn.appendChild(img);
599    toggleBtn.setAttribute('aria-label', 'チャットを開く');
600  }
601
602  toggleBtn.addEventListener('click', function () {
603    if (isOpen) {
604      closeChat();
605    } else {
606      openChat();
607    }
608  });
609
610  const MIN_WIDTH  = 280;
611  const MIN_HEIGHT = 200;
612
613  let isResizing   = false;
614  let resizeDir    = '';
615  let resizeStartX = 0;
616  let resizeStartY = 0;
617  let resizeStartWidth  = 0;
618  let resizeStartHeight = 0;
619
620  function startResize(e, dir) {
621    isResizing = true;
622    resizeDir  = dir;
623    resizeStartX = e.clientX;
624    resizeStartY = e.clientY;
625    resizeStartWidth  = container.offsetWidth;
626    resizeStartHeight = container.offsetHeight;
627    document.body.style.userSelect = 'none';
628
629    iframe.style.pointerEvents = 'none';
630    e.preventDefault();
631  }
632
633  resizeHandle.addEventListener('mousedown',       function (e) { startResize(e, 'left');   });
634  resizeHandleBottom.addEventListener('mousedown', function (e) { startResize(e, 'bottom'); });
635  resizeHandleCorner.addEventListener('mousedown', function (e) { startResize(e, 'corner'); });
636
637  document.addEventListener('mousemove', function (e) {
638    if (!isResizing) return;
639    if (resizeDir === 'left' || resizeDir === 'corner') {
640
641      const dx = resizeStartX - e.clientX;
642      const maxW = window.innerWidth;
643      const newWidth = Math.min(Math.max(resizeStartWidth + dx, MIN_WIDTH), maxW);
644      container.style.width = newWidth + 'px';
645    }
646    if (resizeDir === 'bottom' || resizeDir === 'corner') {
647
648      const dy = resizeStartY - e.clientY;
649      const maxHeight = window.innerHeight - 24;
650      const newHeight = Math.min(Math.max(resizeStartHeight + dy, MIN_HEIGHT), maxHeight);
651      container.style.height    = newHeight + 'px';
652      container.style.maxHeight = newHeight + 'px';
653    }
654  });
655
656  document.addEventListener('mouseup', function () {
657    if (isResizing) {
658      isResizing = false;
659      resizeDir  = '';
660      document.body.style.userSelect = '';
661      iframe.style.pointerEvents = '';
662    }
663  });
664
665  container.addEventListener('wheel', function (e) {
666    if (isOpen) {
667      e.preventDefault();
668    }
669  }, { passive: false });
670
671  window.addEventListener('message', function (event) {
672
673    if (widgetOrigin && event.origin !== widgetOrigin) {
674      return;
675    }
676
677    if (event.data && event.data.type === 'nsc-chat-close') {
678      closeChat();
679    }
680
681    if (event.data && event.data.type === 'nsc-chat-open-url') {
682      if (event.source !== iframe.contentWindow) {
683        return;
684      }
685
686      const rawUrl = event.data.url;
687      if (typeof rawUrl !== 'string' || !/^https?:\/\//i.test(rawUrl)) {
688        return;
689      }
690
691      let url;
692      try {
693        url = new URL(rawUrl);
694      } catch (_) {
695        return;
696      }
697
698      if (!['http:', 'https:'].includes(url.protocol)) {
699        return;
700      }
701
702      const newWindow = window.open(url.href, '_blank');
703      if (!newWindow) {
704        window.location.href = url.href;
705      }
706    }
707
708    if (event.data && event.data.type === 'nsc-widget-size') {
709      applySizeConfig(event.data);
710      sizeReady = true;
711
712      if (pendingOpen) {
713        pendingOpen = false;
714        container.classList.add('nsc-open');
715      }
716    }
717  });
718})();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.