1/** 2 * Sound Trip Pulse â load place pulse metrics, H3 heatmap, and demo audio. 3 * Supports full /pulse page and embedded hero panels via SoundTripPulse.createController(). 4 */ 5(function () { 6 'use strict'; 7 8 var MAP_TILE_URL = 9 'https://{s}.basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}{r}.png?key=cb1_2y2u_1_e5c34798af34e3bfaca29519'; 10 var MAP_TILE_ATTRIBUTION = 11 '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> © <a href="https://carto.com/">CARTO</a>'; 12 var FILL_COLOR = '#7c5cff'; 13 var ZERO_FILL = 0.055; 14 var MIN_FILL = 0.12; 15 var MAX_FILL = 0.58; 16 var GAMMA = 0.5; 17 var LOW_DATA_THRESHOLD = 20; 18 var DEFAULT_ZOOM = 14; 19 var PULSE_DISPLAY_MULTIPLIER = 4; 20 21 var DEFAULT_IDS = { 22 loading: 'pulse-loading', 23 error: 'pulse-error', 24 search: 'pulse-search', 25 content: 'pulse-content', 26 searchInput: 'pulse-search-input', 27 searchResults: 'pulse-search-results', 28 errorTitle: 'pulse-error-title', 29 errorMessage: 'pulse-error-message', 30 placeName: 'pulse-place-name', 31 placeAddress: 'pulse-place-address', 32 disclaimer: 'pulse-disclaimer', 33 statRideCount: 'stat-ride-count', 34 statUniqueDrivers: 'stat-unique-drivers', 35 statPeakHour: 'stat-peak-hour', 36 statPeakDay: 'stat-peak-day', 37 statUnmet: 'stat-unmet', 38 statPaid: 'stat-paid', 39 statRadiusHint: 'stat-radius-hint', 40 map: 'pulse-map', 41 mapLoading: 'pulse-map-loading', 42 audioContainer: 'audio-player-container', 43 }; 44 45 function pulseT(key) { 46 return window.SoundTripI18n ? SoundTripI18n.t(key) : key; 47 } 48 49 function pulseTWithVars(key, vars) { 50 return window.SoundTripI18n ? SoundTripI18n.tWithVars(key, vars) : key; 51 } 52 53 function getNumberLocale() { 54 if (!window.SoundTripI18n) return 'en-US'; 55 var lang = SoundTripI18n.detectLanguage(); 56 if (lang === 'pt-BR') return 'pt-BR'; 57 if (lang === 'es') return 'es'; 58 return 'en-US'; 59 } 60 61 function getSearchLanguageCode() { 62 if (!window.SoundTripI18n) return 'en'; 63 var lang = SoundTripI18n.detectLanguage(); 64 if (lang === 'pt-BR') return 'pt-BR'; 65 if (lang === 'es') return 'es'; 66 return 'en'; 67 } 68 69 function isValidPlaceId(placeId) { 70 return typeof placeId === 'string' && /^[a-zA-Z0-9_-]{1,200}$/.test(placeId); 71 } 72 73 function extractPlaceId() { 74 var pathMatch = window.location.pathname.match(/\/pulse\/([^/?#]+)/); 75 if (pathMatch && pathMatch[1]) { 76 return decodeURIComponent(pathMatch[1]); 77 } 78 var params = new URLSearchParams(window.location.search); 79 return params.get('id') || params.get('placeId') || ''; 80 } 81 82 function buildQueryPlaceUrl(placeId) { 83 var url = new URL(window.location.href); 84 url.searchParams.set('placeId', placeId); 85 url.searchParams.delete('id'); 86 return url.pathname + url.search + url.hash; 87 } 88 89 function clearQueryPlaceUrl() { 90 var url = new URL(window.location.href); 91 url.searchParams.delete('placeId'); 92 url.searchParams.delete('id'); 93 var search = url.searchParams.toString(); 94 return url.pathname + (search ? '?' + search : '') + url.hash; 95 } 96 97 function createPulseController(options) { 98 options = options || {}; 99 var ids = Object.assign({}, DEFAULT_IDS, options.ids || {}); 100 var updateHistory = options.updateHistory !== false; 101 var historyMode = options.historyMode || 'path'; 102 var syncMeta = options.syncMeta !== false; 103 var hotelRedirect = options.hotelRedirect !== false; 104 var hiddenClass = options.hiddenClass || 'pulse-hidden'; 105 106 var lastPulse = null; 107 var lastPlaceName = null; 108 var lastError = null; 109 var searchInitialized = false; 110 var searchTimeout = null; 111 var currentMap = null; 112 113 function el(key) { 114 return document.getElementById(ids[key]); 115 } 116 117 function show(node) { 118 if (node) node.classList.remove(hiddenClass); 119 } 120 121 function hide(node) { 122 if (node) node.classList.add(hiddenClass); 123 } 124 125 function formatNumber(n) { 126 var x = Number(n); 127 if (!Number.isFinite(x)) return 'â'; 128 try { 129 return x.toLocaleString(getNumberLocale()); 130 } catch (e) { 131 return String(x); 132 } 133 } 134 135 function applyErrorCopy() { 136 if (!lastError) return; 137 var titleEl = el('errorTitle'); 138 var msgEl = el('errorMessage'); 139 if (titleEl) titleEl.removeAttribute('data-i18n'); 140 if (msgEl) msgEl.removeAttribute('data-i18n'); 141 if (lastError.raw) { 142 if (titleEl) titleEl.textContent = lastError.title; 143 if (msgEl) msgEl.textContent = lastError.message; 144 return; 145 } 146 if (titleEl) titleEl.textContent = pulseT(lastError.titleKey); 147 if (msgEl) { 148 msgEl.textContent = lastError.messageVars 149 ? pulseTWithVars(lastError.messageKey, lastError.messageVars) 150 : pulseT(lastError.messageKey); 151 } 152 } 153 154 function showError(titleKey, messageKey, messageVars) { 155 lastError = { titleKey: titleKey, messageKey: messageKey, messageVars: messageVars }; 156 hide(el('loading')); 157 hide(el('content')); 158 hide(el('search')); 159 show(el('error')); 160 applyErrorCopy(); 161 } 162 163 function showErrorRaw(title, message) { 164 lastError = { raw: true, title: title, message: message }; 165 hide(el('loading')); 166 hide(el('content')); 167 hide(el('search')); 168 show(el('error')); 169 applyErrorCopy(); 170 } 171 172 function updatePlaceHistory(placeId) { 173 if (!updateHistory || !placeId) return; 174 if (historyMode === 'query') { 175 window.history.replaceState({}, '', buildQueryPlaceUrl(placeId)); 176 return; 177 } 178 window.history.replaceState({}, '', '/pulse/' + encodeURIComponent(placeId)); 179 } 180 181 function clearPlaceHistory() { 182 if (!updateHistory) return; 183 if (historyMode === 'query') { 184 window.history.replaceState({}, '', clearQueryPlaceUrl()); 185 } 186 } 187 188 function showSearch() { 189 lastError = null; 190 if (currentMap) { 191 currentMap.remove(); 192 currentMap = null; 193 } 194 clearPlaceHistory(); 195 hide(el('loading')); 196 hide(el('content')); 197 hide(el('error')); 198 show(el('search')); 199 initPlaceSearch(); 200 var input = el('searchInput'); 201 if (input) { 202 setTimeout(function () { 203 input.focus(); 204 }, 0); 205 } 206 } 207 208 async function searchPlaces(query) {
209 try { 210 var res = await fetch('/api/places/search', { 211 method: 'POST', 212 headers: { 'Content-Type': 'application/json' }, 213 body: JSON.stringify({ 214 textQuery: query, 215 maxResultCount: 10, 216 languageCode: getSearchLanguageCode(), 217 }), 218 }); 219 var json = await res.json(); 220 if (json.success && json.data && json.data.places) { 221 return json.data.places; 222 } 223 return []; 224 } catch (err) { 225 console.error('Place search failed', err); 226 return []; 227 } 228 } 229 230 function beginLoad(placeId) { 231 updatePlaceHistory(placeId); 232 hide(el('search')); 233 show(el('loading')); 234 } 235 236 function displaySearchResults(places) { 237 var resultsContainer = el('searchResults'); 238 if (!resultsContainer) return; 239 240 resultsContainer.textContent = ''; 241 242 if (!places || !places.length) { 243 var noResults = document.createElement('div'); 244 noResults.className = 'search-no-results'; 245 noResults.textContent = pulseT('personalize.searchNoResults'); 246 resultsContainer.appendChild(noResults); 247 resultsContainer.classList.add('show'); 248 return; 249 } 250 251 places.forEach(function (place) { 252 var name = (place.displayName && place.displayName.text) || place.name || 'Unknown Place'; 253 var address = 254 place.formattedAddress || place.formatted_address || 'Address unavailable'; 255 var placeId = place.id || place.place_id; 256 257 var item = document.createElement('div'); 258 item.className = 'search-result-item'; 259 item.setAttribute('data-place-id', placeId); 260 item.setAttribute('data-place-name', name); 261 262 var nameEl = document.createElement('div'); 263 nameEl.className = 'search-result-name'; 264 nameEl.textContent = name; 265 266 var addressEl = document.createElement('div'); 267 addressEl.className = 'search-result-address'; 268 addressEl.textContent = address; 269 270 item.appendChild(nameEl); 271 item.appendChild(addressEl); 272 item.addEventListener('click', function () { 273 var selectedId = item.getAttribute('data-place-id'); 274 var selectedName = item.getAttribute('data-place-name'); 275 resultsContainer.classList.remove('show'); 276 277 if (typeof gtag !== 'undefined') { 278 gtag('event', 'place_search', { 279 event_category: 'engagement', 280 event_label: selectedName, 281 }); 282 } 283 284 beginLoad(selectedId); 285 loadPulse(selectedId); 286 }); 287 288 resultsContainer.appendChild(item); 289 }); 290 291 resultsContainer.classList.add('show'); 292 } 293 294 function initPlaceSearch() { 295 if (searchInitialized) return; 296 searchInitialized = true; 297 298 var input = el('searchInput'); 299 var resultsContainer = el('searchResults'); 300 var searchSection = el('search'); 301 if (!input || !resultsContainer) return; 302 303 input.addEventListener('input', function (e) { 304 var query = e.target.value.trim(); 305 306 if (searchTimeout) { 307 clearTimeout(searchTimeout); 308 } 309 310 if (!query || query.length < 2) { 311 resultsContainer.classList.remove('show'); 312 resultsContainer.textContent = ''; 313 return; 314 } 315 316 resultsContainer.textContent = ''; 317 var loading = document.createElement('div'); 318 loading.className = 'search-loading'; 319 loading.textContent = pulseT('pulse.search.searching'); 320 resultsContainer.appendChild(loading); 321 resultsContainer.classList.add('show'); 322 323 searchTimeout = setTimeout(async function () { 324 var places = await searchPlaces(query); 325 displaySearchResults(places); 326 }, 500); 327 }); 328 329 input.addEventListener('focus', function () { 330 if (resultsContainer.children.length > 0 && input.value.trim().length >= 2) { 331 resultsContainer.classList.add('show'); 332 } 333 }); 334 335 document.addEventListener('click', function (e) { 336 if ( 337 searchSection && 338 (!searchSection.contains(e.target) || !e.target.closest('.place-search-input-wrapper')) 339 ) { 340 resultsContainer.classList.remove('show'); 341 } 342 }); 343 } 344 345 function computeDisplayMax(positives) { 346 var rawMax = positives.length ? positives[positives.length - 1] : 0; 347 var displayMax = Math.max(rawMax, 1); 348 if (positives.length >= 5) { 349 var pIdx = Math.min( 350 positives.length - 1, 351 Math.max(0, Math.floor(0.9 * (positives.length - 1))), 352 ); 353 var p90 = positives[pIdx]; 354 if (p90 > 0 && rawMax > p90 * 2.5) { 355 displayMax = Math.max(p90, 1); 356 } 357 }
358 return displayMax; 359 } 360 361 function cellActivityCount(cell) { 362 if (cell == null) return 0; 363 var activity = Number(cell.activityCount); 364 if (Number.isFinite(activity)) return activity; 365 return Number(cell.rideCount) || 0; 366 } 367 368 function renderHexCoverage(map, hexCells) { 369 if (map._hexLayerGroup) { 370 map.removeLayer(map._hexLayerGroup); 371 } 372 var hexLayerGroup = L.layerGroup(); 373 var positives = []; 374 (hexCells || []).forEach(function (cell) { 375 var n = cellActivityCount(cell); 376 if (n > 0) positives.push(n); 377 }); 378 positives.sort(function (a, b) { 379 return a - b; 380 }); 381 var displayMax = computeDisplayMax(positives); 382 383 (hexCells || []).forEach(function (cell) { 384 var ring = cell.ring; 385 if (!ring || ring.length < 3) return; 386 var count = cellActivityCount(cell); 387 var fillOp; 388 if (count <= 0) { 389 fillOp = ZERO_FILL; 390 } else { 391 var norm = Math.min(count, displayMax) / displayMax; 392 var t = Math.pow(norm, GAMMA); 393 fillOp = MIN_FILL + t * (MAX_FILL - MIN_FILL); 394 } 395 hexLayerGroup.addLayer( 396 L.polygon(ring, { 397 stroke: false, 398 fillColor: FILL_COLOR, 399 fillOpacity: fillOp, 400 interactive: false, 401 }), 402 ); 403 }); 404 405 if (hexLayerGroup.getLayers().length) { 406 hexLayerGroup.addTo(map); 407 } 408 map._hexLayerGroup = hexLayerGroup; 409 } 410 411 function setMapLoading(on) { 412 var mapLoadingEl = el('mapLoading'); 413 if (!mapLoadingEl) return; 414 if (on) mapLoadingEl.classList.add('is-visible'); 415 else mapLoadingEl.classList.remove('is-visible'); 416 } 417 418 function initMap(pulse) { 419 if (typeof L === 'undefined') { 420 setMapLoading(false); 421 return null; 422 } 423 if (currentMap) { 424 currentMap.remove(); 425 currentMap = null; 426 } 427 var lat = pulse.place.latitude; 428 var lng = pulse.place.longitude; 429 var map = L.map(ids.map, { 430 zoomControl: true, 431 attributionControl: true, 432 scrollWheelZoom: false, 433 }).setView([lat, lng], DEFAULT_ZOOM); 434 435 L.tileLayer(MAP_TILE_URL, { 436 attribution: MAP_TILE_ATTRIBUTION, 437 maxZoom: 19, 438 }).addTo(map); 439 440 L.circleMarker([lat, lng], { 441 radius: 8, 442 color: '#ffffff', 443 weight: 2, 444 fillColor: FILL_COLOR, 445 fillOpacity: 1, 446 }).addTo(map); 447 448 L.circle([lat, lng], { 449 radius: pulse.radiusM || 1000, 450 color: FILL_COLOR, 451 weight: 1.5, 452 fillColor: FILL_COLOR, 453 fillOpacity: 0.08, 454 interactive: false, 455 }).addTo(map); 456 457 setTimeout(function () { 458 map.invalidateSize(); 459 }, 120); 460 461 currentMap = map; 462 return map; 463 } 464 465 async function loadCoverage(map, viewport) { 466 if (!map || !viewport) return; 467 setMapLoading(true);
468 try { 469 var params = new URLSearchParams({ 470 centerLat: String(viewport.centerLat), 471 centerLng: String(viewport.centerLng), 472 minLat: String(viewport.minLat), 473 maxLat: String(viewport.maxLat), 474 minLng: String(viewport.minLng), 475 maxLng: String(viewport.maxLng), 476 }); 477 var res = await fetch('/api/public/coverage-preview?' + params.toString()); 478 var json = await res.json(); 479 if (json.success && json.data && json.data.hexCells) { 480 renderHexCoverage(map, json.data.hexCells); 481 } 482 } catch (err) { 483 console.error('Coverage preview failed', err); 484 } finally { 485 setMapLoading(false); 486 } 487 } 488 489 var PEAK_DOW_LABELS = [ 490 'Sunday', 491 'Monday', 492 'Tuesday', 493 'Wednesday', 494 'Thursday', 495 'Friday', 496 'Saturday', 497 ]; 498 499 function resolvePeakDow(pulse) { 500 if (!pulse) return null; 501 if (pulse.patterns && pulse.patterns.byDayOfWeek && pulse.patterns.byDayOfWeek.length) { 502 var best = null; 503 pulse.patterns.byDayOfWeek.forEach(function (day) { 504 if (!best || day.rides > best.rides) best = day; 505 }); 506 if (best && best.rides > 0 && Number.isFinite(best.dow)) return best.dow; 507 } 508 if (pulse.peakDayLabel) { 509 var idx = PEAK_DOW_LABELS.indexOf(pulse.peakDayLabel); 510 if (idx >= 0) return idx; 511 } 512 return null; 513 } 514 515 function formatPeakDayHint(pulse) { 516 var dow = resolvePeakDow(pulse); 517 if (dow == null) return pulseT('pulse.stats.localTime'); 518 return pulseTWithVars('pulse.stats.busiestDay', { day: pulseT('pulse.weekdays.' + dow) }); 519 } 520 521 function displayPulseValue(value) { 522 var n = Number(value); 523 if (!Number.isFinite(n)) return value; 524 return n * PULSE_DISPLAY_MULTIPLIER; 525 } 526 527 function pulseActivityCount(pulse) { 528 if (!pulse) return 0; 529 var activity = Number(pulse.activityCount); 530 if (Number.isFinite(activity)) return activity; 531 return Number(pulse.rideCount) || 0; 532 } 533 534 function fillStats(pulse) { 535 lastPulse = pulse; 536 var rideCountEl = el('statRideCount'); 537 if (rideCountEl) { 538 rideCountEl.textContent = formatNumber(displayPulseValue(pulseActivityCount(pulse))); 539 } 540 var driversEl = el('statUniqueDrivers'); 541 if (driversEl) driversEl.textContent = formatNumber(displayPulseValue(pulse.uniqueDrivers)); 542 var peakHourEl = el('statPeakHour'); 543 if (peakHourEl) peakHourEl.textContent = pulse.peakHourLabel || 'â'; 544 var peakDayEl = el('statPeakDay'); 545 if (peakDayEl) peakDayEl.textContent = formatPeakDayHint(pulse); 546 var unmetEl = el('statUnmet'); 547 if (unmetEl) unmetEl.textContent = formatNumber(displayPulseValue(pulse.unmetAdDemand)); 548 var paidEl = el('statPaid'); 549 if (paidEl) paidEl.textContent = formatNumber(displayPulseValue(pulse.paidAdRidesNearby)); 550 var radiusEl = el('statRadiusHint'); 551 if (radiusEl) { 552 radiusEl.textContent = pulseTWithVars('pulse.stats.withinRadius', { 553 radius: String(pulse.radiusM || 1000), 554 }); 555 } 556 557 var disclaimer = el('disclaimer'); 558 if (disclaimer) { 559 if (pulseActivityCount(pulse) < LOW_DATA_THRESHOLD) { 560 disclaimer.classList.add('is-visible'); 561 } else { 562 disclaimer.classList.remove('is-visible'); 563 } 564 } 565 } 566 567 function refreshPlaceHeading() { 568 var nameEl = el('placeName'); 569 if (!nameEl) return; 570 if (lastPlaceName) { 571 nameEl.removeAttribute('data-i18n'); 572 nameEl.textContent = lastPlaceName; 573 return; 574 } 575 nameEl.textContent = pulseT('pulse.defaultBusinessName'); 576 } 577 578 function refreshDynamicCopy() { 579 applyErrorCopy(); 580 refreshPlaceHeading(); 581 if (lastPulse) fillStats(lastPulse); 582 var mapLoading = el('mapLoading'); 583 if (mapLoading && mapLoading.classList.contains('is-visible')) { 584 mapLoading.textContent = pulseT('pulse.map.loading'); 585 } 586 var mapEl = el('map'); 587 if (mapEl) { 588 mapEl.setAttribute('aria-label', pulseT('pulse.map.ariaLabel')); 589 } 590 } 591 592 function initAudio(placeId) { 593 if (!window.SoundTripAudioPlayer) return; 594 var containerId = ids.audioContainer; 595 SoundTripAudioPlayer.init({ 596 containerId: containerId, 597 showVideo: false, 598 variant: 'slim', 599 }); 600 SoundTripAudioPlayer.loadPlaceContent(placeId, containerId); 601 } 602 603 async function fetchPlace(placeId) { 604 var res = await fetch('/api/places/' + encodeURIComponent(placeId)); 605 var json = await res.json(); 606 if (!res.ok || !json.success || !json.data) { 607 throw new Error(
608 (json && (json.message || json.error)) || pulseT('pulse.error.placeNotFound'), 609 ); 610 } 611 return json.data; 612 } 613 614 async function fetchPulse(placeId) { 615 var res = await fetch('/api/public/places/' + encodeURIComponent(placeId) + '/pulse'); 616 var json = await res.json(); 617 if (!res.ok || !json.success || !json.data) { 618 throw new Error( 619 (json && (json.message || json.error)) || pulseT('pulse.error.pulseUnavailable'), 620 ); 621 } 622 return json.data; 623 } 624 625 async function loadPulse(placeId) { 626 if (!isValidPlaceId(placeId)) { 627 showError('pulse.error.invalidId', 'pulse.error.invalidIdMessage'); 628 return; 629 } 630 631 if ( 632 updateHistory && 633 historyMode === 'path' && 634 !window.location.pathname.match(/\/pulse\/[^/?#]+/) && 635 placeId 636 ) { 637 updatePlaceHistory(placeId); 638 } 639 640 try { 641 var place = await fetchPlace(placeId); 642 if (hotelRedirect && place && place.hotelPage) { 643 window.location.href = '/for-hospitality/' + encodeURIComponent(placeId); 644 return; 645 } 646 647 var pulse = await fetchPulse(placeId); 648 649 var name = 650 (pulse.place && pulse.place.name) || 651 (place.displayName && place.displayName.text) || 652 place.displayName || 653 pulseT('pulse.defaultBusinessName'); 654 var address = (pulse.place && pulse.place.address) || place.formattedAddress || ''; 655 656 lastPlaceName = name; 657 if (syncMeta) { 658 window.__pulsePlaceName = name; 659 } 660 refreshPlaceHeading(); 661 var addressEl = el('placeAddress'); 662 if (addressEl) addressEl.textContent = address; 663 if (syncMeta && typeof window.__pulseSyncMeta === 'function') { 664 window.__pulseSyncMeta(name); 665 } 666 667 fillStats(pulse); 668 669 hide(el('loading')); 670 hide(el('search')); 671 hide(el('error')); 672 show(el('content')); 673 674 var createHref = 675 '/advertiser/campaigns/create?placeId=' + encodeURIComponent(placeId); 676 if (window.SoundTripI18n) { 677 var pulseLocale = SoundTripI18n.detectLanguage(); 678 if (pulseLocale) { 679 createHref += '&locale=' + encodeURIComponent(pulseLocale); 680 } 681 } 682 document.querySelectorAll('.hero-pulse-cta, .pulse-cta-card a.button').forEach(function (cta) { 683 cta.setAttribute('href', createHref); 684 }); 685 686 var map = initMap(pulse); 687 await loadCoverage(map, pulse.viewport); 688 initAudio(placeId); 689 } catch (err) { 690 console.error(err); 691 showErrorRaw( 692 pulseT('pulse.error.unableToLoad'), 693 (err && err.message) || pulseT('pulse.error.generic'), 694 ); 695 } 696 } 697 698 async function boot() { 699 var placeId = extractPlaceId(); 700 if (!placeId) { 701 showSearch(); 702 return; 703 } 704 show(el('loading')); 705 await loadPulse(placeId); 706 } 707 708 return { 709 loadPulse: loadPulse, 710 showSearch: showSearch, 711 initPlaceSearch: initPlaceSearch, 712 refreshDynamicCopy: refreshDynamicCopy, 713 boot: boot, 714 }; 715 } 716 717 window.SoundTripPulse = { 718 createController: createPulseController, 719 DEFAULT_IDS: DEFAULT_IDS, 720 }; 721 722 if (document.getElementById('pulse-loading') || document.getElementById('pulse-search')) { 723 var pageController = createPulseController({ 724 updateHistory: true, 725 syncMeta: true, 726 }); 727 window.__pulseRefreshDynamicCopy = pageController.refreshDynamicCopy; 728 window.addEventListener('languageChanged', pageController.refreshDynamicCopy); 729 730 if (document.readyState === 'loading') { 731 document.addEventListener('DOMContentLoaded', pageController.boot); 732 } else { 733 pageController.boot(); 734 } 735 } 736})();
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.