1/*global $, BookingsCommon */ 2/** 3 * bookings-request.js 4 * Main request/cart page: path /request but not schedule, confirmation, or /requests/. 5 * Handles: service cards, system/furnace quantity modal, add-to-cart (per product index), checkbox cart, 6 * positive/negative buttons, loadCart. Commercial users are redirected to confirmation. 7 */ 8console.warn("[BookingsRequest] script loaded"); 9 10var BookingsRequest = { 11 init: function () { 12 "use strict"; 13 var path = window.location.pathname; 14 var href = window.location.href; 15 console.warn("book test"); 16 if ((!path.includes("/request") && !path.includes("/hvac-duct-cleaning-services")) || href.includes("/request/schedule") || href.includes("/request/confirmation") || path.includes("/requests/")) return; 17 18 19 try{ 20 $("#ductcare-mobile-cta-bar").hide() 21 } 22 catch(ex){ 23 console.warn("Error "+ex); 24 } 25 26 BookingsCommon.fetchPromotions(); 27 localStorage.setItem("squareFootage", 0); 28 localStorage.setItem("systemRequests", 1); 29 BookingsCommon.checkBlankCustomerInfo(); 30 $("#page-header-image, #block-olivero-pageheader").css("max-height", "20vh"); 31 32 $('.grid-full').css({ 33 'display': 'grid', 34 'grid-template-rows': 'unset', 35 'grid-template-columns': 'unset', 36 'grid-column-gap': 'unset' 37 }); 38 39 $("#wb_bookingStep1").addClass("booking_progress_current"); 40 $("#wb_bookingStep2").addClass("booking_progress_next_prev"); 41 $("#wb_bookingStep3").addClass("booking_progress_next_prev"); 42 $("#wb_bookingStep4").addClass("booking_progress_next_prev"); 43 44 var customerInfo = JSON.parse(sessionStorage.getItem('customerInfo') || '{}'); 45 if (customerInfo.service_type === "Commercial") { 46 window.location.href = '/request/confirmation'; 47 return; 48 } 49 50 // Build package cards from grid items (icons, features, hidden add-to-cart button) 51 try { 52 $(".request-service-grid .views-view-responsive-grid__item").each(function (index,item) { 53 var $item = $(this); 54 var item_id = $item.find(".service-request-type-id")[0].innerText; 55 var item_name = $item.find(".services-title")[0].innerText.trim(); 56 var item_price = $item.find(".booking-price")[0].innerHTML; 57 var modelEl = $(item).find(".pricing-model")[0]; 58 var model = modelEl ? modelEl.innerText.trim() : ""; 59 var additionalFurnacePrice = BookingsCommon.getAdditionalFurnacePriceFromGridItem($item); 60 $item.find(".additional-furnace-price").hide(); 61 console.warn("Pricing model 7: ", model); 62 var pricingModel = BookingsCommon.getPricingModelFromGridItem(model); 63 console.warn("Pricing model 6: ", pricingModel); 64 var unitLabel = BookingsCommon.pricingModelUnitLabel(pricingModel); 65 var descEl = $(".services-description")[index]; 66 var desc_first_line = descEl && descEl.children[0] ? descEl.children[0].innerText.trim() : ''; 67 var featureLines = []; 68 $(descEl).find('p, li').each(function () { 69 var t = $(this).text().trim(); 70 if (t) featureLines.push(t); 71 }); 72 if (featureLines.length === 0 && desc_first_line) featureLines = [desc_first_line]; 73 74 var iconClasses = ['request-service-card__icon--blue', 'request-service-card__icon--green', 'request-service-card__icon--purple']; 75 var iconFa = ['fa-fan', 'fa-shield-alt', 'fa-bolt']; 76 var iconClass = iconClasses[index % iconClasses.length]; 77 var iconFaClass = iconFa[index % iconFa.length]; 78 var iconHtml =""// '<div class="request-service-card__icon ' + iconClass + '"><i class="fas ' + iconFaClass + '"></i></div>'; 79 var featuresHtml = featureLines.map(function (line) { 80 return '<div class="request-service-card__feature"><span class="request-service-card__check">â</span> ' + line + '</div>'; 81 }).join(''); 82 83 var $midHl = $item.find(".service-middle-highlight").first(); 84 var $footHl = $item.find(".service-footer-highlight").first(); 85 var middleHlHtml = $midHl.length ? $.trim($midHl.html()) : ""; 86 var footerHlHtml = $footHl.length ? $.trim($footHl.html()) : ""; 87 var middleHlBlock = middleHlHtml 88 ? '<div class="request-service-card__middle-highlight">' + middleHlHtml + "</div>" 89 : ""; 90 var footerHlBlock = footerHlHtml 91 ? '<div class="request-service-card__footer-highlight">' + footerHlHtml + "</div>" 92 : ""; 93 94 var packageCardHtml = 95 '<div class="request-service-card">' + iconHtml + 96 '<h3 class="request-service-card__title">' + item_name + '</h3>' + 97 '<div class="request-service-card__price-row"><span class="request-service-card__price">' + item_price + '</span><span class="request-service-card__starting"> ' + unitLabel + '</span></div>' + 98 middleHlBlock + 99 '<p class="request-service-card__desc">' + desc_first_line + '</p>' + 100 footerHlBlock + 101 '<div class="request-service-card__features">' + featuresHtml + '</div>' + 102 '<div class="request-service-card__cta">SELECT PACKAGE <i class="fas fa-chevron-right"></i></div></div>'; 103 104 $item.find(".services-title").html(packageCardHtml); 105 $item.addClass("request-service-grid__item--card"); 106 $item.append('<div class="services-description-expand">' + ($(".services-description")[index] ? $(".services-description")[index].innerHTML : '') + '</div>'); 107 $item.append('<button type="button" item="' + item_name + '" price="' + item_price + '" data-pricing-model="' + pricingModel + '" data-additional-furnace-price="' + additionalFurnacePrice + '" data-drupal-selector="edit-field-service-requested-' + item_id + '" id="edit-field-service-requested-' + item_id + '" name="field_service_requested' + item_id + '" value="' + item_id + '" class="button-blue add-to-cart add-to-cart--hidden" aria-hidden="true" tabindex="-1">Add To Cart</button>'); 108 }); 109 110 if ($(".request-service-grid").length && !$(".request-service-features").length) { 111 var featuresHtml = '<div class="request-service-features">' + 112 '<div class="request-service-features__item"><div class="request-service-features__icon request-service-features__icon--green"><i class="fas fa-shield-alt"></i></div><div class="request-service-features__text"><strong>Certified Techs</strong><span>NADCA certified professionals with 10+ years experience.</span></div></div>' + 113 '<div class="request-service-features__item"><div class="request-service-features__icon request-service-features__icon--blue"><i class="fas fa-fan"></i></div><div class="request-service-features__text"><strong>HEPA Filtration</strong><span>We use medical-grade HEPA filters to capture 99.97% of particles.</span></div></div>' + 114 '<div class="request-service-features__item"><div class="request-service-features__icon request-service-features__icon--purple"><i class="fas fa-bolt"></i></div><div class="request-service-features__text"><strong>Quick Service</strong><span>Most cleanings completed in 2-4 hours with zero mess left behind.</span></div></div></div>'; 115 $(".request-service-grid").append(featuresHtml); 116 } 117 } catch (ex) {} 118 119 // Hide raw fields; card UI is shown instead 120 $(".services-description").hide(); 121 $(".booking-measurements").hide(); 122 $(".booking-price").hide(); 123 $(".service-footer-highlight").hide(); 124 $(".service-middle-highlight").hide(); 125 $(".services-description-expand").hide(); 126 $(".pricing-model, .additional-furnace-price").hide(); 127 128 // Quantity modal: per-system (systems + furnaces) or per-vent (vent quantity only) 129 var $ductModal = $( 130 '<div id="duct-count-overlay" class="duct-modal-overlay" style="display:none;">' + 131 '<div id="duct-count-modal" class="duct-modal-box">' + 132 '<div class="duct-modal-vent-group" style="display:none;">' + 133 '<h3 class="duct-modal-title">How many vents do you have?</h3>' + 134 '<p class="duct-modal-subtitle">Pricing is per vent. Enter the quantity of vents to calculate your total.</p>' + 135 '<label class="duct-modal-label" for="vent-count-input">Quantity of vents</label>' + 136 '<input type="number" id="vent-count-input" class="duct-modal-input" min="1" value="1" />' + 137 '</div>' + 138 '<div class="duct-modal-system-group">' + 139 '<h3 class="duct-modal-title">How many HVAC systems?</h3>' + 140 '<p class="duct-modal-subtitle">Pricing is per system. Enter systems and furnaces to calculate your total.</p>' + 141 '<label class="duct-modal-label" for="duct-count-input">Systems</label>' + 142 '<input type="number" id="duct-count-input" class="duct-modal-input duct-modal-input--systems" min="1" value="1" />' + 143 '<div class="duct-modal-furnace-group">' + 144 '<label class="duct-modal-label" for="furnace-count-input">Furnaces</label>' + 145 '<input type="number" id="furnace-count-input" class="duct-modal-input duct-modal-input--fur
145nace" min="1" value="1" />' + 146 '<p class="duct-modal-furnace-note"></p>' + 147 '</div>' + 148 '</div>' + 149 '<p class="duct-modal-total">Total: $<span id="duct-total-amount">0.00</span></p>' + 150 '<div class="duct-modal-actions">' + 151 '<button type="button" class="duct-modal-select-date button-orange">Select service date</button>' + 152 '<br><button type="button" class="duct-modal-submit button-blue">Add another product</button>' + 153 '<br><button type="button" class="duct-modal-cancel button-grey">Cancel</button></div></div></div>' 154 ); 155 $("body").append($ductModal); 156 var pendingDuctAdd = null; 157 158 function furnaceExtraUsd() { 159 if (pendingDuctAdd && pendingDuctAdd.furnaceExtraUsd != null) { 160 return pendingDuctAdd.furnaceExtraUsd; 161 } 162 return (typeof BookingsCommon !== "undefined" && BookingsCommon.FURNACE_EXTRA_SURCHARGE_USD != null) 163 ? BookingsCommon.FURNACE_EXTRA_SURCHARGE_USD 164 : 145; 165 } 166 167 function updateFurnaceModalNote(amount) { 168 $(".duct-modal-furnace-note").text( 169 "The first furnace is included with your quote. Each additional furnace adds $" + amount + " to the total." 170 ); 171 } 172 173 function isPerVentPricing(model) { 174 return BookingsCommon.normalizePricingModel(model) === "per_vent"; 175 } 176 177 function setModalPricingMode(pricingModel) { 178 var perVent = isPerVentPricing(pricingModel); 179 $(".duct-modal-vent-group").toggle(perVent); 180 $(".duct-modal-system-group").toggle(!perVent); 181 } 182 183 function qtyForTotal($input, minVal) { 184 var qty = parseInt($input.val(), 10); 185 // Only overwrite input when valid; leave empty/partial so mobile users can type digits 186 if (!isNaN(qty) && qty >= minVal) { 187 $input.val(qty); 188 return qty; 189 } 190 return minVal; 191 } 192 193 function updateDuctTotal() { 194 if (!pendingDuctAdd) return; 195 196 if (isPerVentPricing(pendingDuctAdd.pricingModel)) { 197 var vents = qtyForTotal($("#vent-count-input"), 1); 198 $("#duct-total-amount").text((pendingDuctAdd.unitPrice * vents).toFixed(2)); 199 return; 200 } 201 202 var systems = qtyForTotal($("#duct-count-input"), 1); 203 var furnaces = qtyForTotal($("#furnace-count-input"), 1); 204 var base = pendingDuctAdd.unitPrice * systems; 205 var addon = Math.max(0, furnaces - 1) * furnaceExtraUsd(); 206 $("#duct-total-amount").text((base + addon).toFixed(2)); 207 } 208 209 function openDuctModal(unitPrice, value, name, pricingModel, furnaceExtraUsdAmount) { 210 pricingModel = BookingsCommon.normalizePricingModel(pricingModel || "per_system"); 211 var furnaceExtra = BookingsCommon.getAdditionalFurnacePriceFromGridItem(furnaceExtraUsdAmount); 212 pendingDuctAdd = { 213 unitPrice: unitPrice, 214 value: value, 215 name: name, 216 pricingModel: pricingModel, 217 furnaceExtraUsd: furnaceExtra 218 }; 219 updateFurnaceModalNote(furnaceExtra); 220 setModalPricingMode(pricingModel); 221 $("#vent-count-input").val(1); 222 $("#duct-count-input").val(1); 223 $("#furnace-count-input").val(1); 224 $("#duct-total-amount").text(unitPrice.toFixed(2)); 225 $("#duct-count-overlay").fadeIn(200); 226 if (isPerVentPricing(pricingModel)) { 227 $("#vent-count-input").focus(); 228 } else { 229 $("#duct-count-input").focus(); 230 } 231 } 232 233 function getPricingModelForItem($item) { 234 var $btn = $item.find(".add-to-cart"); 235 if ($btn.attr("data-pricing-model")) { 236 return BookingsCommon.normalizePricingModel($btn.attr("data-pricing-model")); 237 } 238 var modelEl = $item.find(".pricing-model")[0]; 239 var model = modelEl ? modelEl.innerText.trim() : ""; 240 return BookingsCommon.getPricingModelFromGridItem(model); 241 } 242 243 function getAdditionalFurnacePriceForItem($item) { 244 var $btn = $item.find(".add-to-cart"); 245 var fromAttr = parseFloat($btn.attr("data-additional-furnace-price")); 246 if (!isNaN(fromAttr) && fromAttr >= 0) { 247 return fromAttr; 248 } 249 return BookingsCommon.getAdditionalFurnacePriceFromGridItem($item); 250 } 251 252 function closeDuctModal() { 253 pendingDuctAdd = null; 254 $("#duct-count-overlay").fadeOut(200); 255 } 256 257 $("#duct-count-overlay").on("click", function (e) { 258 if (e.target === this) closeDuctModal(); 259 }); 260 $(".duct-modal-cancel").on("click", closeDuctModal); 261 function readModalQuantities(pricingModel) { 262 if (isPerVentPricing(pricingModel)) { 263 return { vents: parseInt($("#vent-count-input").val(), 10) }; 264 } 265 return { 266 systems: parseInt($("#duct-count-input").val(), 10), 267 furnaces: parseInt($("#furnace-count-input").val(), 10) 268 }; 269 } 270 271 function validateModalQuantities(q, pricingModel) { 272 if (isPerVentPricing(pricingModel)) { 273 if (isNaN(q.vents) || q.vents < 1) { 274 alert("Please enter a valid quantity of vents (1 or more)."); 275 return false; 276 } 277 return true; 278 } 279 if (isNaN(q.systems) || q.systems < 1) { 280 alert("Please enter a valid number of systems (1 or more)."); 281 return false; 282 } 283 if (isNaN(q.furnaces) || q.furnaces < 1) { 284 alert("Please enter a valid number of furnaces (1 or more)."); 285 return false; 286 } 287 return true; 288 } 289 290 function addPendingModalToCartThenClose(goSchedule) { 291 if (!pendingDuctAdd) return; 292 var q = readModalQuantities(pendingDuctAdd.pricingModel); 293 if (!validateModalQuantities(q, pendingDuctAdd.pricingModel)) return; 294 var unitPrice = pendingDuctAdd.unitPrice; 295 var cartItemID = { 296 value: pendingDuctAdd.value, 297 name: pendingDuctAdd.name, 298 price: unitPrice, 299 sqft: 0, 300 pricingModel: pendingDuctAdd.pricingModel 301 }; 302 var total; 303 if (isPerVentPricing(pendingDuctAdd.pricingModel)) { 304 cartItemID.qty = q.vents; 305 total = unitPrice * q.vents; 306 } else { 307 cartItemID.qty = q.systems; 308 cartItemID.furnaceQty = q.furnaces; 309 cartItemID.furnaceExtraUsd = furnaceExtraUsd(); 310 total = unitPrice * q.systems + Math.max(0, q.furnaces - 1) * furnaceExtraUsd(); 311 } 312 BookingsCommon.updateCart(cartItemID, true, { itemPrice: unitPrice, itemTotal: total }); 313 closeDuctModal(); 314 if (goSchedule) window.location.href = "/request/schedule"; 315 } 316 317 $(".duct-modal-submit").on("click", function () { 318 addPendingModalToCartThenClose(false); 319 }); 320 $(".duct-modal-select-date").on("click", function () { 321 addPendingModalToCartThenClose(true); 322 }); 323 $("#vent-count-input, #duct-count-input, #furnace-count-input").on("input change keyup", updateDuctTotal); 324 $("#duct-count-overlay").on("keydown", "#vent-count-input, #duct-count-input, #furnace-count-input", function (e) { 325 if (e.which === 13) $(".duct-modal-submit").click(); 326 if (e.which === 27) closeDuctModal(); 327 }); 328 329 // Card click opens quantity modal (per-system or per-vent) 330 $(".services-title").on("click", function () { 331 var $item = $(this).closest(".views-view-responsive-grid__item"); 332 var $btn = $item.find(".add-to-cart"); 333 var priceStr = $btn.attr("price") || ""; 334 var unitPrice = parseFloat(priceStr.replace(/[^0-9.]/g, "")) || 0; 335 if (!unitPrice) return; 336 var pricingModel = getPricingModelForItem($item); 337 openDuctModal(unitPrice, $btn.val(), $btn.attr("item"), pricingModel, getAdditionalFurnacePriceForItem($item)); 338 }); 339 340 $('.views-field-name .field-content a').contents().unwrap(); 341 $(".service-request-type-id").hide(); 342 343 var cartItemID = {}; 344 var itemPrice = 0, itemTotal = 0, itemSqft = 0; 345 346 /** Use common pricing then add item via BookingsCommon.updateCart. */ 347 function submitAddToCart(squareFootage, system_requests, service) { 348 var results = BookingsCommon.totalCalculation(system_requests, squareFootage, service); 349 if (results && results.pass) { 350 cartItemID.sqft = squareFootage; 351 BookingsCommon.updateCart(cartItemID, true, { itemPrice: results.itemPrice, itemTotal: results.itemTotal, itemSqft: results.itemSqft }); 352 } 353 } 354 355 $('.add-to-cart').each(function (index) { 356 var system_requests = $("#bookingSystems").length ? parseFloat($("#bookingSystems")[0].value) : 1; 357 if (index === 5) { 358 $(this).on("click", function (e) { 359 e.preventDefault(); 360 cartItemID.value = $(this).val(); 361 cartItemID.name = $(this).attr('item'); 362 cartItemID.price = $(this).attr('price'); 363 cartItemID.qty = system_requests; 364 submitAddToCart($("#bookingSqft")[0].value, system_requests, 0); 365 }); 366 } else if (index === 0) { 367 $(this).on("click", function (e) { 368 e.preventDefault(); 369 system_requests = $("#bookingSystems").length ? parseFloat($("#bookingSystems")[0].value) : 1; 370 cartItemID.value = $(this).val(); 371 cartItemID.name = $(this).attr('item'); 372 cartItemID.price = $(this).attr('price'); 373 cartItemID.qty = system_requests; 374 submitAddToCart($("#bookingSqft")[0].value, system_requests, index + 1); 375 }); 376 } else if (index === 1 || index === 2) { 377 $(this).on("click", function (e) { 378 e.preventDefault(); 379 system_requests = $("#bookingSystems").length ? parseFloat($("#bookingSystems")[0].value) : 1; 380 cartItemID.value = $(this).val(); 381 cartItemID.name = $(this).attr('item'); 382 cartItemID.price = $(this).attr('price'); 383 cartItemID.qty = system_requests; 384 submitAddToCart($("#bookingSqft")[0].value, system_requests, index + 1); 385 }); 386 } else { 387 $(this).on("click", function (e) { 388 e.preventDefault(); 389 system_requests = $("#bookingSystems").length ? parseFloat($("#bookingSystems")[0].value) : 1; 390 cartItemID.value = $(this).val(); 391 cartItemID.name = $(this).attr('item'); 392 cartItemID.price = parseFloat($(this).attr('price').replace("$", "")) || 0; 393 cartItemID.qty = system_requests; 394 cartItemID.sqft = 0; 395 var total = cartItemID.price * system_requests; 396 BookingsCommon.updateCart(cartItemID, true, { itemPrice: cartItemID.price, itemTotal: total }); 397 }); 398 } 399 }); 400 401 // Grid layout; 3-col if more than 2 products 402 $('.views-view-responsive-grid').addClass('row'); 403 $('.views-view-responsive-grid .views-view-responsive-grid__item').addClass('col-md-6'); 404 $(".views-view-responsive-grid--horizontal").css("gap", "0px"); 405 var requestGridCount = $(".request-service-grid .views-view-responsive-grid__item").length; 406 if (requestGridCount > 2) { 407 $(".left-content-holder").remove(); 408 $(".main-content-holder").removeClass("col-md-6").addClass("col-md-9"); 409 $(".request-service-grid .views-view-responsive-grid__item").removeClass("col-md-6").addClass("col-md-4"); 410 } 411 412 // Preselect checkboxes from session when returning with clear_cart 413 var selectedItems = BookingsCommon.getSelectedItems();
414 if (sessionStorage.getItem('clear_cart')) { 415 selectedItems = BookingsCommon.getSelectedItems(); 416 if (selectedItems.length > 0) { 417 $.each(selectedItems, function (i, item) { 418 var $checkbox = $('.service-requested-checkbox').filter(function () { return $(this).val().trim() === item.id; }); 419 if ($checkbox.length) $checkbox.prop('checked', true); 420 }); 421 } 422 $("#edit-field-service-requested-197").prop("checked", true); 423 } 424 425 $('.service-requested-checkbox').on('change', function () { 426 var val = $(this).val(); 427 if (this.checked) { 428 var $card = $('.add-to-cart[value="' + val + '"]').closest('.views-view-responsive-grid__item'); 429 var name = $card.length ? $card.find('.add-to-cart').attr('item') || 'Item' : 'Item'; 430 var priceStr = $card.length ? $card.find('.add-to-cart').attr('price') || '0' : '0'; 431 var price = parseFloat(priceStr.replace(/[^0-9.]/g, '')) || 0; 432 var qty = $("#bookingSystems").length ? parseInt($("#bookingSystems")[0].value, 10) || 1 : 1; 433 BookingsCommon.updateCart({ value: val, name: name, price: price, qty: qty, sqft: 0 }, true, { itemPrice: price, itemTotal: price * qty }); 434 } else { 435 BookingsCommon.updateCart({ value: val }, false); 436 } 437 }); 438 439 // Proceed to calendar or show "select at least one" modal 440 $(".button-positive").on("click", function () { 441 var items = BookingsCommon.getSelectedItems(); 442 if (!items || items.length === 0) { 443 $("#popup-body").html('<p class="error-modal-message" style="margin:0 0 1rem 0; color: #333;">Please select at least one service before proceeding to the scheduler page.</p><button type="button" class="button-orange error-modal-ok">OK</button>'); 444 $("#popup-overlay, #popup-container").fadeIn(); 445 $("#popup-container .error-modal-ok").on("click", function () { $("#popup-overlay, #popup-container").fadeOut(); }); 446 } else { 447 window.location.href = "/calendar-clear-cache"; 448 } 449 }); 450 451 // Clear cart and refresh display 452 $(".button-negative").on("click", function () { 453 sessionStorage.setItem('selectedItems', JSON.stringify([])); 454 BookingsCommon.loadCart(); 455 }); 456 457 // Recalculate and refresh cart 458 $(".calculate-rate").on("click", function () { 459 localStorage.setItem("squareFootage", 0); 460 localStorage.setItem("systemRequests", 0); 461 BookingsCommon.loadCart(); 462 }); 463 464 BookingsCommon.loadCart(); 465 $("#bookingSqft, #bookingSystems").prop("readonly", false); 466 467 // Deep link: e.g. /request?itemIdEl=217 â open quantity modal for preselected package 468 (function openPackageFromItemIdElQuery() { 469 var params; 470 try { 471 params = new URLSearchParams(window.location.search); 472 } catch (e) { 473 return; 474 } 475 var rawId = params.get("itemIdEl"); 476 if (rawId == null || String(rawId).trim() === "") { 477 return; 478 } 479 var itemId = String(rawId).trim(); 480 var $btn = $(".request-service-grid .add-to-cart").filter(function () { 481 return String($(this).val()).trim() === itemId; 482 }); 483 if (!$btn.length) { 484 return; 485 } 486 var $item = $btn.closest(".views-view-responsive-grid__item"); 487 var $title = $item.find(".services-title").first(); 488 if (!$title.length) { 489 return; 490 } 491 setTimeout(function () { 492 var el = $item.get(0); 493 if (el && typeof el.scrollIntoView === "function") { 494 el.scrollIntoView({ behavior: "smooth", block: "center" }); 495 } 496 $title.trigger("click"); 497 }, 0); 498 })(); 499 } 500};
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.