PageSourceSearch

https://www.visitasilomar.com/scripts/analytics.js

js visitasilomar.com collected 2026-09-24 10:19:06 UTC 21,625 bytes, 641 lines download raw bytes

1import {
2  ANALYTICS_EVENTS,
3  ANALYTICS_WEB_INTERACTION_TYPES,
4  ANALYTICS_WEB_INTERACTION_HERO_BOOKING,
5  ANALYTICS_WEB_INTERACTION_FORM_SUBMIT,
6  CUSTOM_WEB_INTERACTION_TYPES,
7} from './constants.js';
8import { isExternalHttpUrl } from './link-utils.js';
9
10/** File extensions treated as document downloads (not in-page navigation). */
11const DOWNLOAD_FILE_EXTENSIONS = new Set([
12  'pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'zip', 'csv', 'txt', 'rtf', 'odt', 'ods', 'odp',
13]);
14
15/**
16 * Classify a URL for `web.webInteraction.type`: in-site link, exit (external), or download.
17 * @param {string} href - Absolute or relative URL
18 * @returns {typeof ANALYTICS_WEB_INTERACTION_TYPES[keyof typeof ANALYTICS_WEB_INTERACTION_TYPES]}
19 */
20export function getWebInteractionTypeForHref(href) {
21  if (!href || typeof href !== 'string') return ANALYTICS_WEB_INTERACTION_TYPES.LINK;
22  let parsed;
23  try {
24    const base = typeof window !== 'undefined' && window.location?.href
25      ? window.location.href
26      : '';
27    parsed = new URL(href, base);
28  } catch {
29    return ANALYTICS_WEB_INTERACTION_TYPES.LINK;
30  }
31  const pathname = parsed.pathname.toLowerCase();
32  if (pathname.includes('/content/dam/')) {
33    return ANALYTICS_WEB_INTERACTION_TYPES.DOWNLOAD;
34  }
35  const lastSegment = pathname.split('/').pop() || '';
36  const dot = lastSegment.lastIndexOf('.');
37  const ext = dot > -1 ? lastSegment.slice(dot + 1).toLowerCase() : '';
38  if (ext && DOWNLOAD_FILE_EXTENSIONS.has(ext)) {
39    return ANALYTICS_WEB_INTERACTION_TYPES.DOWNLOAD;
40  }
41  if (isExternalHttpUrl(parsed)) {
42    return ANALYTICS_WEB_INTERACTION_TYPES.EXIT;
43  }
44  return ANALYTICS_WEB_INTERACTION_TYPES.LINK;
45}
46
47/**
48 * @param {HTMLAnchorElement} anchor
49 * @returns {boolean}
50 */
51function isCtaAnchor(anchor) {
52  if (anchor.tagName !== 'A') return false;
53  if (anchor.matches('.button, [class*="button"]')) return true;
54  return Boolean(anchor.closest('.button-container, .cta'));
55}
56
57/**
58 * Classify click intent for `_aramarksports.web.webInteraction.type` (link vs CTA vs form).
59 * @param {Element} [element] - Click target (anchor, submit control, or click origin)
60 * @returns {typeof CUSTOM_WEB_INTERACTION_TYPES[keyof typeof CUSTOM_WEB_INTERACTION_TYPES]}
61 */
62export function getCustomWebInteractionTypeForElement(element) {
63  if (!element || !(element instanceof Element)) {
64    return CUSTOM_WEB_INTERACTION_TYPES.LINK;
65  }
66
67  if (element.matches('button[type="submit"], input[type="submit"], input[type="image"]')) {
68    return CUSTOM_WEB_INTERACTION_TYPES.FORM;
69  }
70
71  if (element.matches('.booking-trigger')) {
72    return CUSTOM_WEB_INTERACTION_TYPES.CLICK;
73  }
74
75  const anchor = element.tagName === 'A' ? element : element.closest('a');
76  if (anchor && isCtaAnchor(anchor)) {
77    return CUSTOM_WEB_INTERACTION_TYPES.CLICK;
78  }
79  if (anchor) {
80    return CUSTOM_WEB_INTERACTION_TYPES.LINK;
81  }
82
83  return CUSTOM_WEB_INTERACTION_TYPES.LINK;
84}
85
86/**
87 * Formats AEM product category paths for analytics display.
88 * e.g. `aramark-destinations:global/product-category/houseboat-xl` → `Houseboat Xl`
89 * @param {string} productCategory
90 * @returns {string}
91 */
92export function formatProductCategoryName(productCategory) {
93  if (!productCategory || typeof productCategory !== 'string') return '';
94  const trimmed = productCategory.trim();
95  if (!trimmed) return '';
96  const slug = trimmed.split('/').pop() || '';
97  if (!slug) return '';
98  return slug
99    .replace(/-/g, ' ')
100    .replace(/\b\w/g, (char) => char.toUpperCase());
101}
102
103/**
104 * Shared Analytics Utilities
105 * Consent-gated helpers for pushing events to the Adobe Data Layer (ACDL).
106 * Import these instead of calling window.adobeDataLayer directly in blocks.
107 */
108
109/**
110 * Client Hints (`navigator.userAgentData`) are Chromium-only. Read via Reflect so
111 * eslint-plugin-compat does not treat bare property access as unconditional use,
112 * and fall back to `navigator.userAgent` elsewhere (Safari, Firefox).
113 *
114 * @returns {object|undefined}
115 */
116function getNavigatorUaData() {
117  if (typeof navigator === 'undefined') return undefined;
118  return Reflect.get(navigator, 'userAgentData');
119}
120
121function inferDeviceTypeFromUserAgent(ua) {
122  if (/tablet|ipad|playbook|silk/i.test(ua)) return 'tablet';
123  if (/mobile|iphone|ipod|android.*mobile|blackberry|opera mini|iemobile|wpdesktop/i.test(ua)) {
124    return 'mobile';
125  }
126  return 'desktop';
127}
128
129/**
130 * Get the device type from the user agent data.
131 * @param {object} uaData - The user agent data.
132 * @param {string} ua - The user agent.
133 * @returns {string} The device type.
134 */
135function getAnalyticsDeviceType(uaData, ua) {
136  if (uaData && typeof uaData.mobile === 'boolean') {
137    return uaData.mobile ? 'mobile' : 'desktop';
138  }
139  return inferDeviceTypeFromUserAgent(ua);
140}
141
142/**
143 * Get the platform name from the user agent data.
144 * @param {object} uaData - The user agent data.
145 * @returns {string} The platform name.
146 */
147function getAnalyticsPlatformName(uaData) {
148  const fromHints = uaData && typeof uaData.platform === 'string' ? uaData.platform.trim() : '';
149  if (fromHints) return fromHints;
150  if (typeof navigator !== 'undefined' && typeof navigator.platform === 'string') {
151    return navigator.platform;
152  }
153  return undefined;
154}
155
156/**
157 * Infer the browser name from the user agent.
158 * @param {string} ua - The user agent.
159 * @returns {string} The browser name.
160 */
161function inferBrowserNameFromUserAgent(ua) {
162  if (/edg\//i.test(ua)) return 'Microsoft Edge';
163  if (/opr\/|opera/i.test(ua)) return 'Opera';
164  if (/chrome|crios/i.test(ua)) return 'Chrome';
165  if (/firefox|fxios/i.test(ua)) return 'Firefox';
166  if (/safari/i.test(ua) && !/chrome|crios/i.test(ua)) return 'Safari';
167  return undefined;
168}
169
170/**
171 * Get the browser name from the user agent data.
172 * @param {object} uaData - The user agent data.
173 * @param {string} ua - The user agent.
174 * @returns {string} The browser name.
175 */
176function getAnalyticsBrowserName(uaData, ua) {
177  const brands = uaData && uaData.brands;
178  if (Array.isArray(brands) && brands.length > 0) {
179    const meaningful = brands.find((b) => b && typeof b.brand === 'string' && !/^Not[./]/i.test(b.brand));
180    const pick = meaningful ?? brands[0];
181    if (pick && typeof pick.brand === 'string') return pick.brand;
182  }
183  return inferBrowserNameFromUserAgent(ua);
184}
185
186/**
187 * Check if analytics consent has been granted.
188 * @returns {boolean} True if consent granted.
189 */
190export function checkAnalyticsConsent() {
191  // OneTrust
192  if (window.OnetrustActiveGroups) {
193    return window.OnetrustActiveGroups.includes('C0002'); // Performance cookies
194  }
195
196  // Adobe Privacy JavaScript
197  if (window.adobe?.optIn) {
198    return window.adobe.optIn.isApproved(window.adobe.OptInCategories.ANALYTICS);
199  }
200
201  // TrustArc
202  if (window.truste?.eu?.bindMap) {
203    return window.truste.eu.bindMap.performance === '1';
204  }
205
206  // No consent framework detected — default to allowed (opt-out model)
207  return true;
208}
209
210/** sessionStorage key for the last product page the user viewed. */
211const LAST_VIEWED_PRODUCT_NAME_KEY = 'lastViewedProductName';
212
213let productPageLeaveTrackingAttached = false;
214
215/**
216 * Reads the previously viewed product name from sessionStorage.
217 * @returns {string}
218 */
219export function getLastViewedProductName() {
220  if (typeof sessionStorage === 'undefined') return '';
221  try {
222    return sessionStorage.getItem(LAST_VIEWED_PRODUCT_NAME_KEY)?.trim() || '';
223  } catch {
224    return '';
225  }
226}
227
228/**
229 * Persists the last viewed product name when analytics consent is granted.
230 * @param {string} name
231 */
232function setLastViewedProductName(name) {
233  if (!checkAnalyticsConsent() || typeof sessionStorage === 'undefined') return;
234  try {
235    sessionStorage.setItem(LAST_VIEWED_PRODUCT_NAME_KEY, name);
236  } catch {
237    // Private mode / quota — ignore
238  }
239}
240
241/**
242 * Push an event to the Adobe Data Layer if ACDL is present and consent is granted.
243 * @param {string} eventName - Event name in snake_case (e.g. 'card_click')
244 * @param {Object} eventInfo - Event-specific metadata
245 * @param {Object} contextData - pageContext data to merge
246 */
247export function pushAnalyticsEvent(eventName, eventInfo, _contextData) {
248  if (!checkAnalyticsConsent()) return;
249
250  // Queue before Launch/ACDL finishes loading — same pattern as blocks (navigation, breadcrumbs).
251  window.adobeDataLayer = window.adobeDataLayer || [];
252
253  const uaData = getNavigatorUaData();
254  const ua = typeof navigator !== 'undefined' ? navigator.userAgent : '';
255
256  switch (eventName) {
257    case ANALYTICS_EVENTS.PAGE_VIEW.name:
258      window.adobeDataLayer.push({
259        event: eventName,
260        eventType: ANALYTICS_EVENTS.PAGE_VIEW.type,
261        web: {
262          webPageDetails: {
263            URL: window.location.href,
264            name: document.title,
265          },
266        },
267        device: {
268          type: getAnalyticsDeviceType(uaData, ua),
269        },
270        environment: {
271          operatingSystem: getAnalyticsPlatformName(uaData),
272          browserDetails: {
273            name: getAnalyticsBrowserName(uaData, ua),
274          },
275        },
276      });
277      break;
278    case ANALYTICS_EVENTS.LINK_CLICK.name: {
279      const url = eventInfo && typeof eventInfo.url === 'string' ? eventInfo.url : '';
280      if (!url) break;
281      const interactionName = eventInfo && typeof eventInfo.name === 'string' ? eventInfo.name : '';
282      const interactionType = getWebInteractionTypeForHref(url);
283      const customType = eventInfo && typeof eventInfo.customWebInteractionType === 'string'
284        ? eventInfo.customWebInteractionType
285        : getCustomWebInteractionTypeForElement(eventInfo?.element);
286      window.adobeDataLayer.push({
287        event: eventName,
288        eventType: ANALYTICS_EVENTS.LINK_CLICK.type,
289        web: {
290          webInteraction: {
291            name: interactionName,
292            type: interactionType,
293            URL: url,
294          },
295        },
296        _aramarksports: {
297          web: {
298            webInteraction: {
299              type: customType,
300            },
301          },
302        },
303      });
304      break;
305    }
306    case ANALYTICS_EVENTS.HERO_BOOKING_SUBMIT.name: {
307      const url = eventInfo && typeof eventInfo.url === 'string' ? eventInfo.url : '';
308      if (!url) break;
309      window.adobeDataLayer.push({
310        event: ANALYTICS_EVENTS.LINK_CLICK.name,
311        eventType: ANALYTICS_EVENTS.HERO_BOOKING_SUBMIT.type,
312        web: {
313          webInteraction: {
314            name: ANALYTICS_WEB_INTERACTION_HERO_BOOKING.NAME,
315            type: ANALYTICS_WEB_INTERACTION_HERO_BOOKING.TYPE,
316            URL: url,
317          },
318        },
319        _aramarksports: {
320          web: {
321            webInteraction: {
322              type: 'Booking Submit',
323            },
324          },
325        },
326      });
327      break;
328    }
329    case ANALYTICS_EVENTS.PRODUCT_COMPARE.name: {
330      const interactionName = eventInfo && typeof eventInfo.name === 'string'
331        ? eventInfo.name
332        : '';
333      const interactionType = eventInfo && typeof eventInfo.type === 'string'
334        ? eventInfo.type
335        : '';
336      const interactionUrl = eventInfo && typeof eventInfo.url === 'string'
337        ? eventInfo.url
338        : '';
339      const productNames = Array.isArray(eventInfo?.productNames)
340        ? eventInfo.productNames
341          .map((entry) => (typeof entry === 'string' ? entry.trim() : ''))
342          .filter(Boolean)
343        : [];
344
345      window.adobeDataLayer.push({
346        event: eventName,
347        eventType: ANALYTICS_EVENTS.PRODUCT_COMPARE.type,
348        web: {
349          webInteraction: {
350            name: interactionName,
351            type: interactionType,
352            URL: interactionUrl,
353          },
354        },
355        productListItems: {
356          name: productNames,
357        },
358      });
359      break;
360    }
361    case ANALYTICS_EVENTS.PRODUCT_VIEW.name: {
362      const productName = eventInfo && typeof eventInfo.productName === 'string'
363        ? eventInfo.productName.trim()
364        : '';
365      if (!productName) break;
366      const productCategory = eventInfo && typeof eventInfo.productCategory === 'string'
367        ? eventInfo.productCategory.trim()
368        : '';
369      const lastViewedProductName = eventInfo && typeof eventInfo.lastViewedProductName === 'string'
370        ? eventInfo.lastViewedProductName
371        : getLastViewedProductName();
372
373      window.adobeDataLayer.push({
374        event: eventName,
375        eventType: ANALYTICS_EVENTS.PRODUCT_VIEW.type,
376        productListItems: {
377          name: productName,
378          productCategories: {
379            categoryName: formatProductCategoryName(productCategory),
380          },
381        },
382        _aramarksports: {
383          web: {
384            products: {
385              lastViewedProductName,
386            },
387          },
388        },
389      });
390      break;
391    }
392    case ANALYTICS_EVENTS.FORM_SUBMIT.name: {
393      const url = eventInfo && typeof eventInfo.url === 'string' ? eventInfo.url : '';
394      if (!url) break;
395      const pageName = (eventInfo && typeof eventInfo.name === 'string' && eventInfo.name)
396        || (typeof document !== 'undefined' ? document.title : '')
397        || '';
398      window.adobeDataLayer.push({
399        event: ANALYTICS_EVENTS.LINK_CLICK.name,
400        eventType: ANALYTICS_EVENTS.FORM_SUBMIT.type,
401        web: {
402          webInteraction: {
403            name: pageName,
404            type: ANALYTICS_WEB_INTERACTION_FORM_SUBMIT.TYPE,
405            URL: url,
406          },
407        },
408        _aramarksports: {
409          web: {
410            webInteraction: {
411              type: 'Form Submit',
412            },
413          },
414        },
415      });
416      break;
417    }
418    case ANALYTICS_EVENTS.SEARCH.name: {
419      const term = eventInfo && typeof eventInfo.term === 'string' ? eventInfo.term.trim() : '';
420      if (term.length < 3) break;
421      const rawCount = eventInfo?.resultCount;
422      const resultCount = Number.isFinite(rawCount) ? Math.max(0, Math.floor(rawCount)) : 0;
423      window.adobeDataLayer.push({
424        event: eventName,
425        eventType: ANALYTICS_EVENTS.SEARCH.type,
426        siteSearch: {
427          query: term,
428          numberOfResults: resultCount,
429        },
430      });
431      break;
432    }
433    case ANALYTICS_EVENTS.SEARCH_RESULT_CLICK.name: {
434      const term = eventInfo && typeof eventInfo.term === 'string' ? eventInfo.term.trim() : '';
435      if (term.length < 3) break;
436      const resultURL = eventInfo && typeof eventInfo.resultURL === 'string'
437        ? eventInfo.resultURL.trim()
438        : '';
439      if (!resultURL) break;
440      const resultName = eventInfo && typeof eventInfo.resultName === 'string'
441        ? eventInfo.resultName.trim()
442        : '';
443      const rawRank = eventInfo?.resultRank;
444      const resultRank = Number.isFinite(rawRank)
445        ? Math.max(1, Math.floor(rawRank))
446        : 1;
447      window.adobeDataLayer.push({
448        event: eventName,
449        eventType: ANALYTICS_EVENTS.SEARCH_RESULT_CLICK.type,
450        siteSearch: {
451          query: term,
452        },
453        _aramarksports: {
454          web: {
455            search: {
456              resultURL,
457              resultName,
458              resultRank: resultRank.toString(),
459            },
460          },
461        },
462      });
463      break;
464    }
465    default:
466      break;
467  }
468}
469
470/** Avoid literal `javascript:` in source (eslint no-script-url). */
471const SCRIPT_PROTOCOL_PREFIX = `${'java'}${'script'}:`;
472
473/**
474 * Visible label text for a link — excludes `.sr-only` supplements (see cards `buildCardCta`
475 * where the full accessible phrase is duplicated in an sr-only span).
476 * @param {HTMLAnchorElement} link
477 * @returns {string}
478 */
479export function getLinkVisibleText(link) {
480  const clone = link.cloneNode(true);
481  clone.querySelectorAll('.sr-only').forEach((el) => el.remove());
482  return clone.textContent.replace(/\s+/g, ' ').trim();
483}
484
485/**
486 * Pushes link-click ACDL data (`component_interaction` / LINK_CLICK) for an anchor.
487 * Skips empty href and `javascript:` URLs.
488 *
489 * @param {HTMLAnchorElement} link
490 * @param {Object} [options]
491 * @param {string} [options.customWebInteractionType] - `_aramarksports.web.webInteraction.type`
492 * @returns {boolean} Whether an analytics event was pushed
493 */
494export function pushLinkClickAnalytics(link, options = {}) {
495  const rawHref = link.getAttribute('href') || '';
496  if (!rawHref || rawHref.trim().toLowerCase().startsWith(SCRIPT_PROTOCOL_PREFIX)) {
497    return false;
498  }
499  const url = link.href || rawHref;
500  const name = getLinkVisibleText(link) || link.getAttribute('aria-label') || '';
501  const eventInfo = {
502    name,
503    url,
504    element: link,
505  };
506  if (typeof options.customWebInteractionType === 'string') {
507    eventInfo.customWebInteractionType = options.customWebInteractionType;
508  }
509  pushAnalyticsEvent(ANALYTICS_EVENTS.LINK_CLICK.name, eventInfo);
510  return true;
511}
512
513/**
514 * Pushes form-submit ACDL data (`component_interaction` / FORM_SUBMIT) for native form blocks.
515 *
516 * @param {{ url: string, element?: Element, name?: string }} options
517 * @returns {boolean} Whether an analytics event was pushed
518 */
519export function pushFormSubmitAnalytics({ url, element, name }) {
520  if (!url || typeof url !== 'string') return false;
521  const pageName = (typeof name === 'string' && name)
522    || (typeof document !== 'undefined' ? document.title : '')
523    || '';
524  pushAnalyticsEvent(ANALYTICS_EVENTS.FORM_SUBMIT.name, {
525    name: pageName,
526    url,
527    element,
528  });
529  return true;
530}
531
532/**
533 * Pushes search-result-click ACDL data when the user selects a search result.
534 *
535 * @param {{ term: string, resultURL: string, resultName?: string, resultRank?: number }} options
536 * @returns {boolean} Whether a search result click analytics event was requested
537 */
538export function pushSearchResultClickAnalytics({
539  term, resultURL, resultName = '', resultRank = 1,
540}) {
541  const normalizedTerm = typeof term === 'string' ? term.trim() : '';
542  if (normalizedTerm.length < 3) return false;
543  const normalizedURL = typeof resultURL === 'string' ? resultURL.trim() : '';
544  if (!normalizedURL) return false;
545  const normalizedName = typeof resultName === 'string' ? resultName.trim() : '';
546  pushAnalyticsEvent(ANALYTICS_EVENTS.SEARCH_RESULT_CLICK.name, {
547    term: normalizedTerm,
548    resultURL: normalizedURL,
549    resultName: normalizedName,
550    resultRank,
551  });
552  return true;
553}
554
555/**
556 * Pushes product-view ACDL data when the page carries `product_name` metadata.
557 *
558 * @param {{ productName: string, productCategory?: string }} options
559 * @returns {boolean} Whether an analytics event was pushed
560 */
561export function pushProductViewAnalytics({ productName, productCategory = '' }) {
562  const name = typeof productName === 'string' ? productName.trim() : '';
563  if (!name) return false;
564  const category = typeof productCategory === 'string' ? productCategory.trim() : '';
565  pushAnalyticsEvent(ANALYTICS_EVENTS.PRODUCT_VIEW.name, {
566    productName: name,
567    productCategory: category,
568    lastViewedProductName: getLastViewedProductName(),
569  });
570  return true;
571}
572
573/**
574 * Pushes search-performed ACDL data when the user leaves the search field with a valid term.
575 *
576 * @param {{ term: string, resultCount?: number }} options
577 * @returns {boolean} Whether a search analytics event was requested
578 */
579export function pushSearchPerformedAnalytics({ term, resultCount = 0 }) {
580  const normalizedTerm = typeof term === 'string' ? term.trim() : '';
581  if (normalizedTerm.length < 3) return false;
582  pushAnalyticsEvent(ANALYTICS_EVENTS.SEARCH.name, {
583    term: normalizedTerm,
584    resultCount,
585  });
586  return true;
587}
588
589/**
590 * On product pages, persist `product_name` to sessionStorage when the user leaves.
591 * @param {string} productName
592 */
593export function attachProductPageLeaveTracking(productName) {
594  const name = typeof productName === 'string' ? productName.trim() : '';
595  if (!name || productPageLeaveTrackingAttached || typeof window === 'undefined') return;
596  productPageLeaveTrackingAttached = true;
597  window.addEventListener('pagehide', () => {
598    setLastViewedProductName(name);
599  });
600}
601
602/** RTE blocks that carry prose links outside `.default-content-wrapper` (UE). */
603const INLINE_DEFAULT_LINK_BLOCK_NAMES = new Set(['text', 'title']);
604
605/**
606 * Delegates link clicks to {@link pushLinkClickAnalytics} for inline / default prose only:
607 * anchors under `.default-content-wrapper`, and under `text` / `title` blocks (by
608 * `data-block-name`). Skips when the anchor sits under a different `.block` than the
609 * listener host (nested block inside RTE or malformed DOM).
610 *
611 * **Fragment mains:** `loadFragment` in `blocks/fragment/fragment.js` calls
612 * `decorateMain(main)` on the fragment root, so this runs there automatically.
613 *
614 * **Header / footer / other chrome:** `decorateMain` only receives `doc.querySelector('main')`.
615 * If those regions need the same behavior, call `attachDefaultContentLinkAnalytics(root)` again
616 * once their DOM is stable (same helper, extra root).
617 *
618 * @param {ParentNode} [root] - Subtree to scan (e.g. `main`); no-op if missing `querySelectorAll`.
619 */
620export function attachDefaultContentLinkAnalytics(root) {
621  if (!root || typeof root.querySelectorAll !== 'function') return;
622
623  const onClick = (event) => {
624    const a = event.target.closest('a[href]');
625    if (!a) return;
626    const innerBlock = a.closest('.block');
627    const host = event.currentTarget;
628    if (innerBlock && innerBlock !== host) return;
629    pushLinkClickAnalytics(a);
630  };
631
632  root.querySelectorAll('.default-content-wrapper').forEach((el) => {
633    el.addEventListener('click', onClick);
634  });
635
636  root.querySelectorAll('.block').forEach((block) => {
637    if (INLINE_DEFAULT_LINK_BLOCK_NAMES.has(block.dataset.blockName)) {
638      block.addEventListener('click', onClick);
639    }
640  });
641}

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.