1// ======================================================================= Namespace 2var miyazaki = miyazaki || {}, 3 $ = jQuery; 4 5 6// ======================================================================= Helper Functions 7 8/* OUTPUT AJAX ERRORS */ 9 10function miyazakiAjaxErrors( jqXHR, exception ) { 11 if ( jqXHR.status === 0 ) { 12 alert( 'Not connect.n Verify Network.' ); 13 } else if ( jqXHR.status == 404 ) { 14 alert( 'Requested page not found. [404]' ); 15 } else if ( jqXHR.status == 500 ) { 16 alert( 'Internal Server Error [500].' ); 17 } else if ( exception === 'parsererror' ) { 18 alert( 'Requested JSON parse failed.' ); 19 } else if ( exception === 'timeout' ) { 20 alert( 'Time out error.' ); 21 } else if ( exception === 'abort' ) { 22 alert( 'Ajax request aborted.' ); 23 } else { 24 alert( 'Uncaught Error.n' + jqXHR.responseText ); 25 } 26} 27 28 29// ======================================================================= Interval Scroll 30miyazaki.intervalScroll = { 31 32 init: function() { 33 34 didScroll = false; 35 36 // Check for the scroll event 37 $( window ).on( 'scroll load', function() { 38 didScroll = true; 39 } ); 40 41 // Once every 100ms, check if we have scrolled, and if we have, do the intensive stuff 42 setInterval( function() { 43 if ( didScroll ) { 44 didScroll = false; 45 46 // When this triggers, we know that we have scrolled 47 $( window ).triggerHandler( 'did-interval-scroll' ); 48 49 } 50 51 }, 250 ); 52 53 }, 54 55} // miyazaki.intervalScroll 56 57 58// ======================================================================= Toggles 59miyazaki.toggles = { 60 61 init: function() { 62 63 // Do the toggle 64 miyazaki.toggles.toggle(); 65 66 }, 67 68 toggle: function() { 69 70 $( '*[data-toggle-target]' ).on( 'click toggle', function( e ) { 71 72 var $toggle = $( this ); 73 74 // Get our targets 75 var targetString = $toggle.data( 'toggle-target' ); 76 77 if ( targetString == 'next' ) { 78 var $target = $toggle.next(); 79 } else { 80 var $target = $( targetString ); 81 } 82 83 $target.trigger( 'will-be-toggled' ); 84 85 // Get the class to toggle, if specified 86 var classToToggle = $toggle.data( 'class-to-toggle' ) ? $toggle.data( 'class-to-toggle' ) : 'active'; 87 88 // Toggle the target of the clicked toggle 89 if ( $toggle.data( 'toggle-type' ) == 'slidetoggle' ) { 90 var duration = $toggle.data( 'toggle-duration' ) ? $toggle.data( 'toggle-duration' ) : '400'; 91 $target.slideToggle( duration ); 92 } else { 93 $target.toggleClass( classToToggle ); 94 } 95 96 // Toggle the toggles 97 $( '*[data-toggle-target="' + targetString + '"]' ).each( function() { 98 $( this ).toggleClass( 'active' ); 99 } ); 100 101 // Check whether to set focus 102 if ( $toggle.is( '.active' ) && $toggle.data( 'set-focus' ) ) { 103 var $focusElement = $( $toggle.data( 'set-focus' ) ); 104 105 if ( $focusElement.length ) { 106 $focusElement.focus(); 107 } 108 } 109 110 // Check whether to lock the scroll 111 if ( $toggle.data( 'lock-scroll' ) ) { 112 miyazaki.scrollLock.setTo( true ); 113 } else if ( $toggle.data( 'unlock-scroll' ) ) { 114 miyazaki.scrollLock.setTo( false ); 115 } else if ( $toggle.data( 'toggle-scroll-lock' ) ) { 116 miyazaki.scrollLock.setTo(); 117 } 118 119 $target.trigger( 'toggled' ); 120 121 return false; 122 123 } ); 124 } 125 126} // miyazaki.toggles 127 128 129// ======================================================================= Cover Modals 130miyazaki.coverModals = { 131 132 init: function() { 133 134 if ( $( '.cover-modal' ).length ) { 135 136 // Add body class when cover modals are active 137 miyazaki.coverModals.addBodyClassOnActive(); 138 139 // Reset modal classes on resize 140 miyazaki.coverModals.hideModalOnResize(); 141 142 // Close the search modal when the escape key is pressed 143 miyazaki.coverModals.closeSearchOnEscape(); 144 145 } 146 147 }, 148 149 addBodyClassOnActive: function() { 150 151 $( '.cover-modal' ).on( 'toggled', function() { 152 153 if ( $( this ).hasClass( 'active' ) ) { 154 $( 'body' ).addClass( 'showing-modal' ); 155 } else { 156 $( 'body' ).removeClass( 'showing-modal' ); 157 } 158 159 } ); 160 161 }, 162 163 hideModalOnResize: function() { 164 165 $( window ).on( 'resize', function() { 166 167 var $activeModal = $( '.cover-modal.active' ); 168 169 if ( $activeModal.length ) { 170 171 var winWidth = $( window ).width(), 172 untoggleAbove = $activeModal.data( 'untoggle-above' ), 173 untoggleBelow = $activeModal.data( 'untoggle-below' ); 174 175 if ( untoggleAbove && ( winWidth > untoggleAbove ) || untoggleBelow && ( winWidth < untoggleBelow ) ) { 176 miyazaki.coverModals.untoggleModal( $activeModal ); 177 } 178 } 179 180 } ); 181 182 }, 183 184 closeSearchOnEscape: function() { 185 186 $( document ).keyup( function( e ) { 187 if ( e.keyCode == 27 && $( '.search-overlay' ).hasClass( 'active' ) ) { 188 $( '.search-toggle' ).trigger( 'click' ); 189 } 190 } ); 191 192 }, 193 194 // Untoggle a modal 195 untoggleModal: function( $modal ) { 196 197 $modalToggle = false;
198 199 // If the modal has specified the string (ID or class) used by toggles to target it, untoggle the toggles with that target string. 200 // The modal-target-string must match the string toggles use to target the modal. 201 if ( $modal.data( 'modal-target-string' ) ) { 202 var modalTargetClass = $modal.data( 'modal-target-string' ), 203 $modalToggle = $( '*[data-toggle-target="' + modalTargetClass + '"]' ).first(); 204 } 205 206 // If a modal toggle exists, trigger it so all of the toggle options are included 207 if ( $modalToggle && $modalToggle.length ) { 208 $modalToggle.trigger( 'click' ); 209 210 // If one doesn't exist, just hide the modal 211 } else { 212 $modal.removeClass( 'active' ); 213 $( 'body' ).removeClass( 'showing-modal' ); 214 } 215 216 } 217 218} // miyazaki.coverModals 219 220 221// ======================================================================= Element in View 222miyazaki.elementInView = { 223 224 init: function() { 225 226 $targets = $( '.do-spot' ); 227 miyazaki.elementInView.run( $targets ); 228 229 // Rerun on AJAX content loaded 230 $( window ).on( 'ajax-content-loaded', function() { 231 $targets = $( '.do-spot' ); 232 miyazaki.elementInView.run( $targets ); 233 } ); 234 235 }, 236 237 run: function( $targets ) { 238 239 if ( $targets.length ) { 240 241 // Add class indicating the elements will be spotted 242 $targets.each( function() { 243 $( this ).addClass( 'will-be-spotted' ); 244 } ); 245 246 miyazaki.elementInView.handleFocus( $targets ); 247 } 248 249 }, 250 251 handleFocus: function( $targets ) { 252 253 winHeight = $( window ).height(); 254 255 // Get dimensions of window outside of scroll for performance 256 $( window ).on( 'load resize orientationchange', function() { 257 winHeight = $( window ).height(); 258 } ); 259 260 $( window ).on( 'resize orientationchange did-interval-scroll', function() { 261 262 var winTop = $( window ).scrollTop(); 263 winBottom = winTop + winHeight; 264 265 // Check for our targets 266 $targets.each( function() { 267 268 var $this = $( this ); 269 270 if ( miyazaki.elementInView.isVisible( $this, checkAbove = true ) ) { 271 $this.addClass( 'spotted' ).triggerHandler( 'spotted' ); 272 } 273 274 } ); 275 276 } ); 277 278 }, 279 280 // Determine whether the element is in view 281 isVisible: function( $elem, checkAbove ) { 282 283 if ( typeof checkAbove === 'undefined' ) { 284 checkAbove = false; 285 } 286 287 var winHeight = $( window ).height(); 288 289 var docViewTop = $( window ).scrollTop(), 290 docViewBottom = docViewTop + winHeight, 291 docViewLimit = docViewBottom - 50; 292 293 var elemTop = $elem.offset().top, 294 elemBottom = $elem.offset().top + $elem.outerHeight(); 295 296 // If checkAbove is set to true, which is default, return true if the browser has already scrolled past the element 297 if ( checkAbove && ( elemBottom <= docViewBottom ) ) { 298 return true; 299 } 300 301 // If not, check whether the scroll limit exceeds the element top 302 return ( docViewLimit >= elemTop ); 303 304 } 305 306} // miyazaki.elementInView 307 308 309// ======================================================================= Mobile Menu 310miyazaki.mobileMenu = { 311 312 init: function() { 313 314 // On mobile menu toggle, scroll to the top 315 miyazaki.mobileMenu.onToggle(); 316 317 // On screen resize, check whether to unlock scroll and match the mobile menu wrapper padding to the site header 318 miyazaki.mobileMenu.resizeChecks(); 319 320 }, 321 322 onToggle: function() { 323 324 $( '.mobile-menu-wrapper' ).on( 'will-be-toggled', function() { 325 window.scrollTo( 0, 0 ); 326 } ); 327 328 }, 329 330 resizeChecks: function() { 331 332 $( window ).on( 'load resize orientationchange', function() { 333 334 // Update the mobile menu wrapper top padding to match the height of the header 335 var $siteHeader = $( '#site-header' ), 336 $mobileMenuWrapper = $( '.mobile-menu-wrapper' ); 337 338 var paddedHeaderHeight = $siteHeader.outerHeight() + 35; 339 340 $mobileMenuWrapper.css( { 'padding-top': paddedHeaderHeight + 'px' } ); 341 } ); 342 343 } 344 345} // miyazaki.mobileMenu 346 347 348// ======================================================================= Resize videos 349miyazaki.intrinsicRatioEmbeds = { 350 351 init: function() { 352 353 // Resize videos after their container 354 var vidSelector = 'iframe, object, video'; 355 var resizeVideo = function( sSel ) { 356 $( sSel ).each( function() { 357 var $video = $( this ), 358 $container = $video.parent(), 359 iTargetWidth = $container.width(); 360 361 if ( ! $video.attr( 'data-origwidth' ) ) { 362 $video.attr( 'data-origwidth', $video.attr( 'width' ) ); 363 $video.attr( 'data-origheight', $video.attr( 'height' ) ); 364 } 365 366 var ratio = iTargetWidth / $video.attr( 'data-origwidth' ); 367 368 $video.css( 'width', iTargetWidth + 'px' ); 369 $video.css( 'height', ( $video.attr( 'data-origheight' ) * ratio ) + 'px' ); 370 }); 371 }; 372 373 resizeVideo( vidSelector ); 374 375 $( window ).resize( function() { 376 resizeVideo( vidSelector ); 377 } ); 378 379 }, 380 381} // miyazaki.intrinsicRatioEmbeds 382 383 384// ======================================================================= Masonry 385miyazaki.masonry = { 386 387 init: function() { 388 389 $wrapper = $( '.posts' ); 390 391 if ( $wrapper.length ) { 392 393 $grid = $wrapper.imagesLoaded( function() { 394 395 $grid = $wrapper.masonry( { 396 columnWidth: '.grid-sizer', 397 itemSelector: '.preview', 398 percentPosition: true, 399 stagger: 0, 400 transitionDuration: 0, 401 } ); 402 403 } ); 404 405 $grid.on( 'layoutComplete', function() { 406 $( '.posts' ).css( 'opacity', 1 ); 407 $( window ).triggerHandler( 'scroll' ); 408 } ); 409 410 } 411 412 } 413 414} // miyazaki.masonry 415 416 417// ======================================================================= Smooth Scroll 418miyazaki.smoothScroll = { 419 420 init: function() { 421 422 // Scroll to on-page elements by hash. 423 $( 'a[href*="#"]' ).not( '[href="#"]' ).not( '[href="#0"]' ).not( '.disable-hash-scroll' ).on( 'click', function( e ) { 424 if ( location.pathname.replace(/^\//, '' ) == this.pathname.replace(/^\//, '' ) && location.hostname == this.hostname ) { 425 $target = $( this.hash ).length ? $( this.hash ) : $( '[name=' + this.hash.slice(1) + ']' ); 426 miyazaki.smoothScroll.scrollToTarget( $target, $( this ) ); 427 e.preventDefault(); 428 } 429 } ); 430 431 // Scroll to elements specified with a data attribute. 432 $( document ).on( 'click', '*[data-scroll-to]', function( e ) { 433 var $target = $( $( this ).data( 'scroll-to' ) ); 434 miyazaki.smoothScroll.scrollToTarget( $target, $( this ) ); 435 e.preventDefault(); 436 } ); 437 438 }, 439 440 // Scroll to target. 441 scrollToTarget: function( $target, $clickElem ) { 442 443 if ( $target.length ) { 444 445 var additionalOffset = 0, 446 timeOutTime = 0; 447 448 // Close any parent modal before calculating offset and scrolling. 449 // Also, set a timeout, to make sure elements have the correct offset before we scroll. 450 if ( $clickElem && $clickElem.closest( '.cover-modal' ) ) { 451 miyazaki.coverModals.untoggleModal( $clickElem.closest( '.cover-modal' ) ); 452 timeOutTime = 5; 453 } 454 455 setTimeout( function() { 456 457 // Determine offset. 458 var scrollOffset = $target.offset().top + additionalOffset; 459 460 // Scroll to position. 461 miyazaki.smoothScroll.scrollToPosition( scrollOffset ); 462 463 }, timeOutTime ); 464 465 } 466 467 }, 468 469 // Scroll to position. 470 scrollToPosition: function( scrollOffset ) { 471 472 // Animate. 473 $( 'html, body' ).animate( { 474 scrollTop: scrollOffset, 475 }, 500, function() { 476 $( window ).trigger( 'did-interval-scroll' ); 477 } ); 478 479 } 480 481} // miyazaki.smoothScroll 482 483 484// ======================================================================= Scroll Lock 485miyazaki.scrollLock = { 486 487 init: function() { 488 489 // Init variables 490 window.scrollLocked = false, 491 window.prevScroll = { 492 scrollLeft : $( window ).scrollLeft(), 493 scrollTop : $( window ).scrollTop() 494 }, 495 window.prevLockStyles = {}, 496 window.lockStyles = { 497 'overflow-y' : 'scroll', 498 'position' : 'fixed', 499 'width' : '100%' 500 }; 501 502 // Instantiate cache in case someone tries to unlock before locking 503 miyazaki.scrollLock.saveStyles(); 504 505 }, 506 507 // Save context's inline styles in cache 508 saveStyles: function() { 509 510 var styleAttr = $( 'html' ).attr( 'style' ), 511 styleStrs = [], 512 styleHash = {}; 513 514 if ( ! styleAttr ) { 515 return; 516 } 517 518 styleStrs = styleAttr.split( /;\s/ ); 519
520 $.each( styleStrs, function serializeStyleProp( styleString ) { 521 if ( ! styleString ) { 522 return; 523 } 524 525 var keyValue = styleString.split( /\s:\s/ ); 526 527 if ( keyValue.length < 2 ) { 528 return; 529 } 530 531 styleHash[ keyValue[ 0 ] ] = keyValue[ 1 ]; 532 } ); 533 534 $.extend( prevLockStyles, styleHash ); 535 }, 536 537 // Lock the scroll (do not call this directly) 538 lock: function() { 539 540 var appliedLock = {}; 541 542 if ( scrollLocked ) { 543 return; 544 } 545 546 // Save scroll state and styles 547 prevScroll = { 548 scrollLeft : $( window ).scrollLeft(), 549 scrollTop : $( window ).scrollTop() 550 }; 551 552 miyazaki.scrollLock.saveStyles(); 553 554 // Compose our applied CSS, with scroll state as styles 555 $.extend( appliedLock, lockStyles, { 556 'left' : - prevScroll.scrollLeft + 'px', 557 'top' : - prevScroll.scrollTop + 'px' 558 } ); 559 560 // Then lock styles and state 561 $( 'html' ).css( appliedLock ); 562 $( window ).scrollLeft( 0 ).scrollTop( 0 ); 563 564 scrollLocked = true; 565 }, 566 567 // Unlock the scroll (do not call this directly) 568 unlock: function() { 569 570 if ( ! scrollLocked ) { 571 return; 572 } 573 574 // Revert styles and state 575 $( 'html' ).attr( 'style', $( '<x>' ).css( prevLockStyles ).attr( 'style' ) || '' ); 576 $( window ).scrollLeft( prevScroll.scrollLeft ).scrollTop( prevScroll.scrollTop ); 577 578 scrollLocked = false; 579 }, 580 581 // Call this to lock or unlock the scroll 582 setTo: function( on ) { 583 584 // If an argument is passed, lock or unlock accordingly 585 if ( arguments.length ) { 586 if ( on ) { 587 miyazaki.scrollLock.lock(); 588 } else { 589 miyazaki.scrollLock.unlock(); 590 } 591 // If not, toggle to the inverse state 592 } else { 593 if ( scrollLocked ) { 594 miyazaki.scrollLock.unlock(); 595 } else { 596 miyazaki.scrollLock.lock(); 597 } 598 } 599 600 }, 601 602} // miyazaki.scrollLock 603 604 605// ==================================================================== Load More 606miyazaki.loadMore = { 607 608 init: function() { 609 610 var $pagination = $( '#pagination' ); 611 612 // First, check that there's a pagination 613 if ( $pagination.length ) { 614 615 // Default values for variables 616 window.loading = false; 617 window.lastPage = false; 618 619 miyazaki.loadMore.prepare( $pagination ); 620 621 } 622 623 }, 624 625 prepare: function( $pagination ) { 626 627 // Get the query arguments from the pagination element 628 var query_args = JSON.parse( $pagination.attr( 'data-query-args' ) ); 629 630 // If we're already at the last page, exit out here 631 if ( query_args.paged == query_args.max_num_pages ) { 632 $pagination.addClass( 'last-page' ); 633 } else { 634 $pagination.removeClass( 'last-page' ); 635 } 636 637 // Get the load more setting 638 var loadMoreType = 'button'; 639 if ( $( 'body' ).hasClass( 'pagination-type-scroll' ) ) { 640 loadMoreType = 'scroll'; 641 } else if ( $( 'body' ).hasClass( 'pagination-type-links' ) ) { 642 // No JS needed â exit out 643 return; 644 } 645 646 // Do the appropriate load more detection, depending on the type 647 if ( loadMoreType == 'scroll' ) { 648 miyazaki.loadMore.detectScroll( $pagination, query_args ); 649 } else if ( loadMoreType == 'button' ) { 650 miyazaki.loadMore.detectButtonClick( $pagination, query_args ); 651 } 652 653 }, 654 655 // Load more on scroll 656 detectScroll: function( $pagination, query_args ) { 657 658 $( window ).on( 'did-interval-scroll', function() { 659 660 // If it's the last page, or we're already loading, we're done here 661 if ( lastPage || loading ) { 662 return; 663 } 664 665 var paginationOffset = $pagination.offset().top, 666 winOffset = $( window ).scrollTop() + $( window ).outerHeight(); 667 668 // If the bottom of the window is below the top of the pagination, start loading 669 if ( ( winOffset > paginationOffset ) ) { 670 miyazaki.loadMore.loadPosts( $pagination, query_args ); 671 } 672 673 } ); 674 675 }, 676 677 // Load more on click 678 detectButtonClick: function( $pagination, query_args ) { 679 680 // Load on click 681 $( '#load-more' ).on( 'click', function() { 682 683 // Make sure we aren't already loading 684 if ( loading ) { 685 return; 686 } 687 688 miyazaki.loadMore.loadPosts( $pagination, query_args ); 689 return false; 690 } ); 691 692 }, 693 694 // Load the posts 695 loadPosts: function( $pagination, query_args ) { 696 697 // We're now loading 698 loading = true; 699 $pagination.addClass( 'loading' ).removeClass( 'last-page' ); 700 701 // Increment paged to indicate another page has been loaded 702 query_args.paged++; 703 704 // Prepare the query args for submission 705 var json_query_args = JSON.stringify( query_args ); 706 707 $.ajax({ 708 url: miyazaki_ajax_load_more.ajaxurl, 709 type: 'post', 710 data: { 711 action: 'miyazaki_ajax_load_more', 712 json_data: json_query_args 713 }, 714 success: function( result ) { 715 716 // Get the results 717 var $result = $( result ), 718 $articleWrapper = $( $pagination.data( 'load-more-target' ) ); 719 720 // If there are no results, we're at the last page 721 if ( ! $result.length ) { 722 loading = false;
723 $articleWrapper.addClass( 'no-results' ); 724 $pagination.addClass( 'last-page' ).removeClass( 'loading' ); 725 } 726 727 if ( $result.length ) { 728 729 $articleWrapper.removeClass( 'no-results' ); 730 731 // Wait for the images to load 732 $result.imagesLoaded( function() { 733 734 // Append the results 735 $articleWrapper.append( $result ).masonry( 'appended', $result ).masonry(); 736 737 $( window ).triggerHandler( 'ajax-content-loaded' ); 738 $( window ).triggerHandler( 'did-interval-scroll' ); 739 740 // Update history 741 miyazaki.loadMore.updateHistory( query_args.paged ); 742 743 // We're now finished with the loading 744 loading = false; 745 $pagination.removeClass( 'loading' ); 746 747 // If that was the last page, make sure we don't check for any more 748 if ( query_args.paged == query_args.max_num_pages ) { 749 $pagination.addClass( 'last-page' ); 750 lastPage = true; 751 return; 752 } else { 753 $pagination.removeClass( 'last-page' ); 754 lastPage = false; 755 } 756 757 758 759 } ); 760 761 } 762 763 }, 764 765 error: function( jqXHR, exception ) { 766 miyazakiAjaxErrors( jqXHR, exception ); 767 } 768 } ); 769 770 }, 771 772 // Update browser history 773 updateHistory: function( paged ) { 774 775 var newUrl, 776 currentUrl = document.location.href; 777 778 // If currentUrl doesn't end with a slash, append one 779 if ( currentUrl.substr( currentUrl.length - 1 ) !== '/' ) { 780 currentUrl += '/'; 781 } 782 783 var hasPaginationRegexp = new RegExp( '^(.*/page)/[0-9]*/(.*$)' ); 784 785 if ( hasPaginationRegexp.test( currentUrl ) ) { 786 newUrl = currentUrl.replace( hasPaginationRegexp, '$1/' + paged + '/$2' ); 787 } else { 788 var beforeSearchReplaceRegexp = new RegExp( '^([^?]*)(\\??.*$)' ); 789 newUrl = currentUrl.replace( beforeSearchReplaceRegexp, '$1page/' + paged + '/$2' ); 790 } 791 792 history.pushState( {}, '', newUrl ); 793 794 } 795 796} // Load More 797 798 799 800 801/* ----------------------------------------------------------------------------------------------- 802 Focus Management 803--------------------------------------------------------------------------------------------------- */ 804 805miyazaki.focusManagement = { 806 807 init: function() { 808 809 // Add and remove a class from dropdown menu items on focus 810 miyazaki.focusManagement.dropdownFocus(); 811 812 }, 813 814 // Add and remove a class from dropdown menu items on focus 815 dropdownFocus: function() { 816 $( '.main-menu a' ).on( 'blur focus', function( e ) { 817 $( this ).parents( 'li.menu-item-has-children' ).toggleClass( 'focus' ); 818 } ); 819 } 820 821} // miyazaki.focusManagement 822 823 824// ======================================================================= Function calls 825$( document ).ready( function( ) { 826 827 miyazaki.intervalScroll.init(); // Interval scroll 828 miyazaki.toggles.init(); // Toggles 829 miyazaki.coverModals.init(); // Cover modal specifics 830 miyazaki.elementInView.init(); // Check for element in view 831 miyazaki.mobileMenu.init(); // Mobile menu 832 miyazaki.intrinsicRatioEmbeds.init(); // Resize embeds 833 miyazaki.masonry.init(); // Masonry grid 834 miyazaki.smoothScroll.init(); // Smooth scrolls to anchor links
835 miyazaki.loadMore.init(); // Load more posts 836 miyazaki.scrollLock.init(); // Handle locking of the scroll 837 miyazaki.focusManagement.init(); // Focus functionality 838 839} );
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.