PageSourceSearch

https://wearebombo.com/wp-content/plugins/events-addon-for-elementor/assets/js/juxtapose.js?ver=1.1.2

js wearebombo.com collected 2026-09-24 10:46:36 UTC 23,630 bytes, 743 lines download raw bytes

1/* juxtapose - v1.1.2 - 2015-07-16
2 * Copyright (c) 2015 Alex Duner and Northwestern University Knight Lab
3 */
4
5(function (document, window) {
6
7  var juxtapose = {
8    sliders: [],
9    OPTIMIZATION_ACCEPTED: 1,
10    OPTIMIZATION_WAS_CONSTRAINED: 2
11  };
12
13  var flickr_key = "d90fc2d1f4acc584e08b8eaea5bf4d6c";
14  var FLICKR_SIZE_PREFERENCES = ['Large', 'Medium'];
15
16  function Graphic(properties, slider) {
17    var self = this;
18    this.image = new Image();
19
20    this.loaded = false;
21    this.image.onload = function() {
22      self.loaded = true;
23      slider._onLoaded();
24    };
25
26    this.image.src = properties.src;
27    this.image.alt = properties.alt || '';
28    this.label = properties.label || false;
29    this.credit = properties.credit || false;
30  }
31
32  function FlickrGraphic(properties, slider) {
33    var self = this;
34    this.image = new Image();
35
36    this.loaded = false;
37    this.image.onload = function() {
38      self.loaded = true;
39      slider._onLoaded();
40    };
41
42    this.flickrID = this.getFlickrID(properties.src);
43    this.callFlickrAPI(this.flickrID, self);
44
45    this.label = properties.label || false;
46    this.credit = properties.credit || false;
47  }
48
49  FlickrGraphic.prototype = {
50    getFlickrID: function(url) {
51      if (url.match(/flic.kr\/.+/i)) {
52        var encoded = url.split('/').slice(-1)[0];
53        return base58Decode(encoded);
54      }
55      var idx = url.indexOf("flickr.com/photos/");
56      var pos = idx + "flickr.com/photos/".length;
57      var photo_info = url.substr(pos);
58      if (photo_info.indexOf('/') == -1) return null;
59      if (photo_info.indexOf('/') === 0) photo_info = photo_info.substr(1);
60      id = photo_info.split("/")[1];
61      return id;
62    },
63
64    callFlickrAPI: function(id, self) {
65      var url = 'https://api.flickr.com/services/rest/?method=flickr.photos.getSizes' +
66          '&api_key=' + flickr_key +
67          '&photo_id=' + id + '&format=json&nojsoncallback=1';
68
69      var request = new XMLHttpRequest();
70      request.open('GET', url, true);
71      request.onload = function() {
72        if (request.status >= 200 && request.status < 400){
73          data = JSON.parse(request.responseText);
74          var flickr_url = self.bestFlickrUrl(data.sizes.size);
75          self.setFlickrImage(flickr_url);
76        } else {
77          console.error("There was an error getting the picture from Flickr");
78        }
79      };
80      request.onerror = function() {
81        console.error("There was an error getting the picture from Flickr");
82      };
83      request.send();
84    },
85
86    setFlickrImage: function(src) {
87      this.image.src = src;
88    },
89
90    bestFlickrUrl: function(ary) {
91      var dict = {};
92      for (var i = 0; i < ary.length; i++) {
93        dict[ary[i].label] = ary[i].source;
94      }
95      for (var j = 0; j < FLICKR_SIZE_PREFERENCES.length; j++) {
96        if (FLICKR_SIZE_PREFERENCES[j] in dict) {
97          return dict[FLICKR_SIZE_PREFERENCES[j]];
98        }
99      }
100      return ary[0].source;
101    }
102  };
103
104  function getNaturalDimensions(DOMelement) {
105    if (DOMelement.naturalWidth && DOMelement.naturalHeight) {
106      return {width: DOMelement.naturalWidth, height: DOMelement.naturalHeight};
107    }
108    // http://www.jacklmoore.com/notes/naturalwidth-and-naturalheight-in-ie/
109    var img = new Image();
110    img.src = DOMelement.src;
111    return {width: img.width, height: img.height};
112  }
113
114  function getImageDimensions(img) {
115    var dimensions = {
116      width: getNaturalDimensions(img).width,
117      height: getNaturalDimensions(img).height,
118      aspect: function() { return (this.width / this.height); }
119    };
120    return dimensions;
121  }
122
123  function addClass(element, c) {
124    if (element.classList) {
125      element.classList.add(c);
126    } else {
127      element.className += " " + c;
128    }
129  }
130
131  function removeClass(element, c) {
132    element.className = element.className.replace(/(\S+)\s*/g, function (w, match) {
133      if (match === c) {
134        return '';
135      }
136      return w;
137    }).replace(/^\s+/, '');
138  }
139
140  function setText(element, text) {
141    if (document.body.textContent) {
142      element.textContent = text;
143    } else {
144      element.innerText = text;
145    }
146  }
147
148  function getComputedWidthAndHeight(element) {
149    if (window.getComputedStyle) {
150      return {
151        width: parseInt(getComputedStyle(element).width, 10),
152        height: parseInt(getComputedStyle(element).height, 10)
153      };
154    } else {
155      w = element.getBoundingClientRect().right - element.getBoundingClientRect().left;
156      h = element.getBoundingClientRect().bottom - element.getBoundingClientRect().top;
157      return {
158        width: parseInt(w, 10) || 0,
159        height: parseInt(h, 10) || 0
160      };
161    }
162  }
163
164  function viewport() {
165    var e = window, a = 'inner';
166    if ( !( 'innerWidth' in window ) ) {
167      a = 'client';
168      e = document.documentElement || document.body;
169    }
170    return { width : e[ a+'Width' ] , height : e[ a+'Height' ] }
171  }
172
173  function getPageX(e) {
174    var pageX;
175    if (e.pageX) {
176      pageX = e.pageX;
177    } else if (e.touches) {
178      pageX = e.touches[0].pageX;
179    } else {
180      pageX = e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft;
181    }
182    return pageX;
183  }
184
185  function getPageY(e) {
186    var pageY;
187    if (e.pageY) {
188      pageY = e.pageY;
189    } else if (e.touches) {
190      pageY = e.touches[0].pageY;
191    } else {
192      pageY = e.clientY + document.body.scrollTop + document.documentElement.scrollTop;
193    }
194    return pageY;
195  }
196
197  function checkFlickr(url) {
198    if (url.match(/flic.kr\/.+/i)) {
199      return true;
200    }
201    var idx = url.indexOf("flickr.com/photos/");
202    if (idx == -1) {
203      return false;
204    } else {
205      return true;
206    }
207  }
208
209  function base58Decode(encoded) {
210    var alphabet = '123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ',
211        base = alphabet.length;
212    if (typeof encoded !== 'string') {
213      throw '"base58Decode" only accepts strings.';
214    }
215    var decoded = 0;
216    while (encoded) {
217      var alphabetPosition = alphabet.indexOf(encoded[0]);
218      if (alphabetPosition < 0) {
219        throw '"base58Decode" can\'t find "' + encoded[0] + '" in the alphabet: "' + alphabet + '"';
220      }
221      var powerOf = encoded.length - 1;
222      decoded += alphabetPosition * (Math.pow(base, powerOf));
223      encoded = encoded.substring(1);
224    }
225    return decoded.toString();
226  }
227
228  function getLeftPercent(slider, input) {
229    if (typeof(input) === "string" || typeof(input) === "number") {
230      leftPercent = parseInt(input, 10);
231    } else {
232      var sliderRect = slider.getBoundingClientRect();
233      var offset = {
234        top: sliderRect.top + document.body.scrollTop + document.documentElement.scrollTop,
235        left: sliderRect.left + document.body.scrollLeft + document.documentElement.scrollLeft
236      };
237      var width = slider.offsetWidth;
238      var pageX = getPageX(input);
239      var relativeX = pageX - offset.left;
240      leftPercent = (relativeX / width) * 100;
241    }
242    return leftPercent;
243  }
244
245  function getTopPercent(slider, input) {
246    if (typeof(input) === "string" || typeof(input) === "number") {
247      topPercent = parseInt(input, 10);
248    } else {
249      var sliderRect = slider.getBoundingClientRect();
250      var offset = {
251        top: sliderRect.top + document.body.scrollTop + document.documentElement.scrollTop,
252        left: sliderRect.left + document.body.scrollLeft + document.documentElement.scrollLeft
253      };
254      var width = slider.offsetHeight;
255      var pageY = getPageY(input);
256      var relativeY = pageY - offset.top;
257      topPercent = (relativeY / width) * 100;
258    }
259    return topPercent;
260  }
261
262  // values of BOOLEAN_OPTIONS are ignored. just used for 'in' test on keys
263  var BOOLEAN_OPTIONS =  {'animate': true, 'showLabels': true, 'showCredits': true, 'makeResponsive': true };
264  function interpret_boolean(x) {
265    if (typeof(x) != 'string') {
266      return Boolean(x);
267    }
268    return !(x === 'false' || x === '');
269  }
270
271  function JXSlider(selector, images, options) {
272
273    this.selector = selector;
274
275    var i;
276    this.options = { // new options must have default values set here.
277      animate: true,
278      showLabels: true,
279      showCredits: true,
280      makeResponsive: true,
281      startingPosition: "50%",
282      mode: 'horizontal',
283      callback: null // pass a callback function if you like
284    };
285
286    for (i in this.options) {
287      if(i in options) {
288        if (i in BOOLEAN_OPTIONS) {
289          this.options[i] = interpret_boolean(options[i]);
290        } else {
291          this.options[i] = options[i];
292        }
293      }
294    }
295
296    if (images.length == 2) {
297
298      if(checkFlickr(images[0].src)) {
299        this.imgBefore = new FlickrGraphic(images[0], this);
300      } else {
301        this.imgBefore = new Graphic(images[0], this);
302      }
303
304      if(checkFlickr(images[1].src)) {
305        this.imgAfter = new FlickrGraphic(images[1], this);
306      } else {
307        this.imgAfter = new Graphic(images[1], this);
308      }
309
310    } else {
311      console.warn("The images parameter takes two Image objects.");
312    }
313
314    if (this.imgBefore.credit || this.imgAfter.credit) {
315      this.options.showCredits = true;
316    } else {
317      this.options.showCredits = false;
318    }
319  }
320
321  JXSlider.prototype = {
322
323    updateSlider: function(input, animate) {
324      var leftPercent, rightPercent;
325
326      if (this.options.mode === "vertical") {
327        leftPercent = getTopPercent(this.slider, input);
328      } else {
329        leftPercent = getLeftPercent(this.slider, input);
330      }
331
332      leftPercent = leftPercent.toFixed(2) + "%";
333      leftPercentNum = parseFloat(leftPercent);
334      rightPercent = (100 - leftPercentNum) + "%";
335
336      if (leftPercentNum > 0 && leftPercentNum < 100) {
337        removeClass(this.handle, 'transition');
338        removeClass(this.rightImage, 'transition');
339        removeClass(this.leftImage, 'transition');
340
341        if (this.options.animate && animate) {
342          addClass(this.handle, 'transition');
343          addClass(this.leftImage, 'transition');
344          addClass(this.rightImage, 'transition');
345        }
346
347        if (this.options.mode === "vertical") {
348          this.handle.style.top = leftPercent;
349          this.leftImage.style.height = leftPercent;
350          this.rightImage.style.height = rightPercent;
351        } else {
352          this.handle.style.left = leftPercent;
353          this.leftImage.style.width = leftPercent;
354          this.rightImage.style.width = rightPercent;
355        }
356        this.sliderPosition = leftPercent;
357      }
358    },
359
360    getPosition: function() {
361      return this.sliderPosition;
362    },
363
364    displayLabel: function(element, labelText) {
365      label = document.createElement("div");
366      label.className = 'jx-label';
367      label.setAttribute('tabindex', 0); //put the controller in the natural tab order of the document
368
369      setText(label, labelText);
370      element.appendChild(label);
371    },
372
373    displayCredits: function() {
374      credit = document.createElement("div");
375      credit.className = "jx-credit";
376
377      text = "<em>Photo Credits:</em>";
378      if (this.imgBefore.credit) { text += " <em>Before</em> " + this.imgBefore.credit; }
379      if (this.imgAfter.credit) { text += " <em>After</em> " + this.imgAfter.credit; }
380
381      credit.innerHTML = text;
382
383      this.wrapper.appendChild(credit);
384    },
385
386    setStartingPosition: function(s) {
387      this.options.startingPosition = s;
388    },
389
390    checkImages: function() {
391      if (getImageDimensions(this.imgBefore.image).aspect() ==
392        getImageDimensions(this.imgAfter.image).aspect()) {
393        return true;
394      } else {
395        return false;
396      }
397    },
398
399    calculateDims: function(width, height){
400      var ratio = getImageDimensions(this.imgBefore.image).aspect();
401      if (width) {
402        height = width / ratio;
403      } else if (height) {
404        width = height * ratio;
405      }
406      return {
407        width: width,
408        height: height,
409        ratio: ratio
410      };
411    },
412
413    responsivizeIframe: function(dims){
414      //Check the slider dimensions against the iframe (window) dimensions
415      if (dims.height < window.innerHeight){
416        //If the aspect ratio is greater than 1, imgs are landscape, so letterbox top and bottom
417        if (dims.ratio >= 1){
418          this.wrapper.style.paddingTop = parseInt((window.innerHeight - dims.height) / 2) + "px";
419        }
420      } else if (dims.height > window.innerHeight) {
421        /* If the image is too tall for the window, which happens at 100% width on large screens,
422         * force dimension recalculation based on height instead of width */
423        dims = this.calculateDims(0, window.innerHeight);
424        this.wrapper.style.paddingLeft = parseInt((window.innerWidth - dims.width) / 2) + "px";
425      }
426      if (this.options.showCredits) {
427        // accommodate the credits box within the iframe
428        dims.height -= 13;
429      }
430      return dims;
431    },
432
433    setWrapperDimensions: function() {
434      var wrapperWidth = getComputedWidthAndHeight(this.wrapper).width;
435      var wrapperHeight = getComputedWidthAndHeight(this.wrapper).height;
436      var dims = this.calculateDims(wrapperWidth, wrapperHeight);
437      // if window is in iframe, make sure images don't overflow boundaries
438      if (window.location !== window.parent.location && !this.options.makeResponsive) {
439        dims = this.responsivizeIframe(dims);
440      }
441
442      this.wrapper.style.height = parseInt(dims.height) + "px";
443      this.wrapper.style.width = parseInt(dims.width) + "px";
444    },
445
446    optimizeWrapper: function(maxWidth){
447      var result = juxtapose.OPTIMIZATION_ACCEPTED;
448      if ((this.imgBefore.image.naturalWidth >= maxWidth) && (this.imgAfter.image.naturalWidth >= maxWidth)) {
449        this.wrapper.style.width = maxWidth + "px";
450        result = juxtapose.OPTIMIZATION_WAS_CONSTRAINED;
451      } else if (this.imgAfter.image.naturalWidth < maxWidth) {
452        this.wrapper.style.width = this.imgAfter.image.naturalWidth + "px";
453      } else {
454        this.wrapper.style.width = this.imgBefore.image.naturalWidth + "px";
455      }
456      this.setWrapperDimensions();
457      return result;
458    },
459
460    _onLoaded: function() {
461
462      if (this.imgBefore && this.imgBefore.loaded === true &&
463        this.imgAfter && this.imgAfter.loaded === true) {
464
465        this.wrapper = document.querySelector(this.selector);
466        addClass(this.wrapper, 'juxtapose');
467
468        this.wrapper.style.width = getNaturalDimensions(this.imgBefore.image).width;
469        this.setWrapperDimensions();
470
471        this.slider = document.createElement("div");
472        this.slider.className = 'jx-slider';
473        this.wrapper.appendChild(this.slider);
474
475        if (this.options.mode != "horizontal") {
476          addClass(this.slider, this.options.mode);
477        }
478
479        this.handle = document.createElement("div");
480        this.handle.className = 'jx-handle';
481
482        this.rightImage = document.createElement("div");
483        this.rightImage.className = 'jx-image jx-right';
484        this.rightImage.appendChild(this.imgAfter.image);
485
486
487        this.leftImage = document.createElement("div");
488        this.leftImage.className = 'jx-image jx-left';
489        this.leftImage.appendChild(this.imgBefore.image);
490
491        this.slider.appendChild(this.handle);
492        this.slider.appendChild(this.leftImage);
493        this.slider.appendChild(this.rightImage);
494
495        this.leftArrow = document.createElement("div");
496        this.rightArrow = document.createElement("div");
497        this.control = document.createElement("div");
498        this.controller = document.createElement("div");
499
500        this.leftArrow.className = 'jx-arrow jx-left';
501        this.rightArrow.className = 'jx-arrow jx-right';
502        this.control.className = 'jx-control';
503        this.controller.className = 'jx-controller';
504
505        this.controller.setAttribute('tabindex', 0); //put the controller in the natural tab order of the document
506        this.controller.setAttribute('role', 'slider');
507        this.controller.setAttribute('aria-valuenow', 50);
508        this.controller.setAttribute('aria-valuemin', 0);
509        this.controller.setAttribute('aria-valuemax', 100);
510
511        this.handle.appendChild(this.leftArrow);
512        this.handle.appendChild(this.control);
513        this.handle.appendChild(this.rightArrow);
514        this.control.appendChild(this.controller);
515
516        this._init();
517      }
518    },
519
520    _init: function() {
521
522      if (this.checkImages() === false) {
523        console.warn(this, "Check that the two images have the same aspect ratio for the slider to work correctly.");
524      }
525
526      this.updateSlider(this.options.startingPosition, false);
527
528      if (this.options.showLabels === true) {
529        if (this.imgBefore.label) { this.displayLabel(this.leftImage, this.imgBefore.label); }
530        if (this.imgAfter.label) { this.displayLabel(this.rightImage, this.imgAfter.label); }
531      }
532
533      if (this.options.showCredits === true) {
534        this.displayCredits();
535      }
536
537      var self = this;
538      window.addEventListener("resize", function() {
539        self.setWrapperDimensions();
540      });
541
542
543      // Set up Javascript Events
544      // On mousedown, call updateSlider then set animate to false
545      // (if animate is true, adds css transition when updating).
546
547      this.slider.addEventListener("mousedown", function(e) {
548        e = e || window.event;
549        e.preventDefault();
550        self.updateSlider(e, true);
551        animate = true;
552
553        this.addEventListener("mousemove", function(e) {
554          e = e || window.event;
555          e.preventDefault();
556          if (animate) { self.updateSlider(e, false); }
557        });
558
559        this.addEventListener('mouseup', function(e) {
560          e = e || window.event;
561          e.preventDefault();
562          e.stopPropagation();
563          this.removeEventListener('mouseup', arguments.callee);
564          animate = false;
565        });
566      });
567
568      this.slider.addEventListener("touchstart", function(e) {
569        e = e || window.event;
570        e.preventDefault();
571        e.stopPropagation();
572        self.updateSlider(e, true);
573
574        this.addEventListener("touchmove", function(e) {
575          e = e || window.event;
576          e.preventDefault();
577          e.stopPropagation();
578          self.updateSlider(event, false);
579        });
580
581      });
582
583      /* keyboard accessibility */
584
585      this.handle.addEventListener("keydown", function (e) {
586        e = e || window.event;
587        var key = e.which || e.keyCode;
588        var ariaValue = parseFloat(this.style.left);
589
590          //move jx-controller left
591          if (key == 37) {
592            ariaValue = ariaValue - 1;
593          var leftStart = parseFloat(this.style.left) - 1;
594          self.updateSlider(leftStart, false);
595          self.controller.setAttribute('aria-valuenow', ariaValue);
596          }
597
598          //move jx-controller right
599          if (key == 39) {
600            ariaValue = ariaValue + 1;
601          var rightStart = parseFloat(this.style.left) + 1;
602          self.updateSlider(rightStart, false);
603          self.controller.setAttribute('aria-valuenow', ariaValue);
604          }
605      });
606
607      //toggle right-hand image visibility
608      this.leftImage.addEventListener("keydown", function (event) {
609           var key = event.which || event.keyCode;
610            if ((key == 13) || (key ==32)) {
611              self.updateSlider("90%", true);
612                self.controller.setAttribute('aria-valuenow', 90);
613            }
614      });
615
616      //toggle left-hand image visibility
617      this.rightImage.addEventListener("keydown", function (event) {
618           var key = event.which || event.keyCode;
619            if ((key == 13) || (key ==32)) {
620            self.updateSlider("10%", true);
621            self.controller.setAttribute('aria-valuenow', 10);
622            }
623      });
624
625      juxtapose.sliders.push(this);
626
627      if (this.options.callback && typeof(this.options.callback) == 'function') {
628        this.options.callback(this);
629      }
630    }
631
632  };
633
634  /*
635    Given an element that is configured with the proper data elements, make a slider out of it.
636    Normally this will just be used by scanPage.
637  */
638  juxtapose.makeSlider = function (element, idx) {
639    if (typeof idx == 'undefined') {
640      idx = juxtapose.sliders.length; // not super threadsafe...
641    }
642
643    var w = element;
644
645    var images = w.querySelectorAll('img');
646
647    var options = {};
648    // don't set empty string into options, that's a false false.
649    if (w.getAttribute('data-animate')) {
650      options.animate = w.getAttribute('data-animate');
651    }
652    if (w.getAttribute('data-showlabels')) {
653      options.showLabels = w.getAttribute('data-showlabels');
654    }
655    if (w.getAttribute('data-showcredits')) {
656      options.showCredits = w.getAttribute('data-showcredits');
657    }
658    if (w.getAttribute('data-startingposition')) {
659      options.startingPosition = w.getAttribute('data-startingposition');
660    }
661    if (w.getAttribute('data-mode')) {
662      options.mode = w.getAttribute('data-mode');
663    }
664    if (w.getAttribute('data-makeresponsive')) {
665      options.mode = w.getAttribute('data-makeresponsive');
666    }
667
668    specificClass = 'juxtapose-' + idx;
669    addClass(element, specificClass);
670
671    selector = '.' + specificClass;
672
673    if (w.innerHTML) {
674      w.innerHTML = '';
675    } else {
676      w.innerText = '';
677    }
678
679    slider = new juxtapose.JXSlider(
680      selector,
681      [
682        {
683          src: images[0].src,
684          label: images[0].getAttribute('data-label'),
685          credit: images[0].getAttribute('data-credit'),
686          alt: images[0].alt
687        },
688        {
689          src: images[1].src,
690          label: images[1].getAttribute('data-label'),
691          credit: images[1].getAttribute('data-credit'),
692          alt: images[1].alt
693        }
694      ],
695      options
696    );
697  };
698
699  //Enable HTML Implementation
700  juxtapose.scanPage = function() {
701      var elements = document.querySelectorAll('.juxtapose');
702      for (var i = 0; i < elements.length; i++) {
703      juxtapose.makeSlider(elements[i], i);
704    }
705  };
706
707  juxtapose.JXSlider = JXSlider;
708  window.juxtapose = juxtapose;
709
710  juxtapose.scanPage();
711
712}(document, window));
713
714
715// addEventListener polyfill / jonathantneal
716!window.addEventListener && (function (WindowPrototype, DocumentPrototype, ElementPrototype, addEventListener, removeEventListener, dispatchEvent, registry) {
717	WindowPrototype[addEventListener] = DocumentPrototype[addEventListener] = ElementPrototype[addEventListener] = function (type, listener) {
718		var target = this;
719
720		registry.unshift([target, type, listener, function (event) {
721			event.currentTarget = target;
722			event.preventDefault = function () { event.returnValue = false };
723			event.stopPropagation = function () { event.cancelBubble = true };
724			event.target = event.srcElement || target;
725
726			listener.call(target, event);
727		}]);
728
729		this.attachEvent("on" + type, registry[0][3]);
730	};
731
732	WindowPrototype[removeEventListener] = DocumentPrototype[removeEventListener] = ElementPrototype[removeEventListener] = function (type, listener) {
733		for (var index = 0, register; register = registry[index]; ++index) {
734			if (register[0] == this && register[1] == type && register[2] == listener) {
735				return this.detachEvent("on" + type, registry.splice(index, 1)[0][3]);
736			}
737		}
738	};
739
740	WindowPrototype[dispatchEvent] = DocumentPrototype[dispatchEvent] = ElementPrototype[dispatchEvent] = function (eventObject) {
741		return this.fireEvent("on" + eventObject.type, eventObject);
742	};
743})(Window.prototype, HTMLDocument.prototype, Element.prototype, "addEventListener", "removeEventListener", "dispatchEvent", []);

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.