File: trnpix/_thumbspage/1998-puertorico-2.jpg.html

<!DOCTYPE HTML>
<html>

<head>
<!-- Generated 2024-01-22 @10:07:38, by thumbspage 2.3: learning-python.com/thumbspage.html -->

<!-- Be Unicode and mobile friendly (and in the first 1k bytes here) -->
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">

    <!-- 
    ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    Context: thumbspage.py generates viewer pages from this template
    file - a combination of HTML, CSS, and JavaScript.  Python expands 
    this for each image, replacing all its 'percent(uppercase)s' keys.
    Edit with _care_ - some of this code is both subtle and fragile.
    This file's Unicode encoding defaults to UTF-8 in user_configs.py. 

    Recent noteworthy mods here:
    [2.3] Note button+popup, up-swipe=Note, popup colors and opacity.
    [2.3] Drop hrefs in toolbar, more tooltips, tooltips on by default.
    [2.2] Add swipe gestures for touch screens, and optional tooltips.
    [2.1] Use underline font for Auto button when slideshow is on.
    [2.1] Use underline for Full for parity (but fullscreen obvious).
    [2.0] Add slideshow via Auto toggle, one-page fullscreen via Full.
    [2.0] Cut Raw (it's now image tap); iOS Chrome history bug fixed.
    [2.0] Use custom dialog for 1.7's info popup (on filename tap).
    [1.7] Add generation date+version above, and info popup display.
    ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    -->

<!-- Plus analytics code, custom styles, etc. (replace me) -->
<!-- 
Anonymous analytics to prioritize work, enabled in online resources 
only.  Automatically inserted at publish time by insert-analytics.py.
-->

<!-- 1) Universal Analytics tag (custom): stops collecting data on Jul-1-2023 -->
<SCRIPT>
  // Start async JS-file fetch, if not already cached

  (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
  (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
  m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
  })(window,document,'script','//www.google-analytics.com/analytics.js','ga');

  // Queue actions to run in order after async JS-file fetch finished

  ga('create', 'UA-52579036-1', 'auto');       // Create tracker object (and queue)
  ga('set', 'anonymizeIp', true);              // Anonymize IP addr (&aip) [Jun-2019]
  ga('send', 'pageview');                      // Send page-view event now 
</SCRIPT>

<!-- 2) Google Analytics 4 tag: added to site Oct-2022 (okay to keep UA tag) -->
<!-- Google tag (gtag.js) -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-J8CTEZHX3L"></script>
<script>
  window.dataLayer = window.dataLayer || [];
  function gtag(){dataLayer.push(arguments);}
  gtag('js', new Date());

  gtag('config', 'G-J8CTEZHX3L');
</script>

<!-- End analytics insert -->






<!--
====================================================================================
CSS: style HTML elements by id, class, or element type
====================================================================================
-->



<style type="text/css">    /* in head (or elsewhere per HTML 5.2), type optional */

    /*
    ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    Context: this is CSS in HTML expanded and generated by Python.  
    The all-upper names here are Python dict-key replacement targets.  
    The JavaScript ahead uses both HTML attributes and CSS properties,
    forming a nest of cross-language coupling.  This file is close 
    to incomprehensible if embedded in the .py script itself. 
    ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    */



/* For all elements in doc (unless redef, includes load note) */
* {
    font-family: Arial, Helvetica, sans-serif;
    background-color: black;               /* bg is entire background */
    color: white;                          /* text, not border or no-JS note */
}


/* Don't display scrollbars (else Chrome on Windows/Linux may, briefly) */
body {
    overflow: hidden;    /* added late in 1.X, but no other display impact anywhere */
}


/* Don't upscale text in landscape mode on iOS Safari */
@media screen and (max-device-width: 640px) { 
html {
    -webkit-text-size-adjust: 100%;     /* webkit browsers (old chrome too) */
}
}




/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
/* Navigation toolbar and its buttons (bottom of page)              */
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/


.navdiv {
    width: 100%;                  /* span page: else no scrolls */
    position: fixed;               /* anchor to page bottom */
    bottom: 0; 
    overflow-x: auto;              /* auto-scroll on viewport overflow, iff min-width */
    left: 4px;                     /* use more empty space on left [2.0] */
}

.navtable {
    width: 100%;                  /* span page: else won't fill entire page */
    border-collapse: collapse;
    table-layout: auto;            /* 'fixed' less uniform? + fails on .optional [2.0] */
    min-width: 150px;              /* usable min: else no scrolls [2.0] */
}

.navlink {
    display: block;                /* buttons; nicer links through ad hoc css? */
    text-decoration: none;         /* no link colors or underlines */
    text-align: center;            /* how did this work without this before [2.3]? */
    /*min-width: 3em;*/            /* this changes layout slightly for the worse */
}


/* Add horizontal padding for large fonts in Android Firefox */
/* This also ensures space on right, despite new min-width [2.0] */
.navtable td {
    padding-right: 10px;      /* for all <td> nested in a class=navtable */
}


/* [2.0] Optional toolbar buttons: not if no JS, or disabled in configs */ 
.optional {
    display: none;    /* initially HIDDEN (changed on load if JS + enabled) */
}


/* [1.7] Filename+image cursor change on desktop, in lieu of hover effects */
.cursorpointer {
    cursor: pointer;
}


/* [1.7] But no cursor change if no JS, for no-op widgets: image, filename */
</style>
<noscript>
<style>
.cursorpointer {
    cursor: auto;
}
</style>
</noscript>
<style>


/* See also "Dead/olde/abandoned" code ahead for toolbar history */




/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
/* Message posted when JavaScript disabled (top of page)            */
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/


.nojsnote {
    text-align: center; 
    font-style: italic; 
    margin-top: 0px;        /* other: text-shadow */
    color: red;     /* allow user configs */
    /*color: red;*/         /* no: may be BGCOLOR */
}




/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
/* Filename-related styles (top of page)                            */
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/


.filename {
    text-align: center;
    margin-top: 4px; 
    margin-bottom: 10px;
}

.overflowscroll {             /* also auto-scroll via this style in <div> */
    overflow-x: auto;         /* used by JS warning too, but wraps at ' ' */
}  




/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
/* Image-related styles (middle of page)                            */
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/


img {
    max-width:  100%;               /* for before onload resize: else Chrome starts.. */
    max-height: 100%;               /* fullsize, and botches sizes on Android later;  */
    display: block;                  /* allow centering sans <p>; [kill bottom space]  */
    /*vertical-align: text-bottom;   /* [didn't work fully, but bottom space now moot] */
}

#theimg {
    border: thin solid white;    /* not 1px, else Chrome may not draw (see .py) */ 
    margin-left: auto;                 /* center-align image, but horizontally only */
    margin-right: auto;                /* set bdcolor=bgcolor to hide image border */
}




/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
/* Dead/olde/abandoned navigation toolbar code (delete me soon)     */
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/


/* Switch toolbar button's font effects on mouseover as a visual indicator */
    /*
    [1.7] PUNT: drop hover, because it's broken on mobile browsers, and 
    may still appear in mobile landscape mode's larger screens; but force
    cursor=pointer for an indicator on desktops; see UserGuide.html#1.7.
    */

/*--DEFUNCT
.navlink:hover {                   -- [1.7] punt; mobile browsers get stuck 'on' 
    text-decoration: underline;    -- was: desktop browsers only (mobiles may botch)
    font-style: italic;
}

.navlink:active {   
    font-style: italic;
}
DEFUNCT--*/


/* Smaller mobile devices: scale up toolbar links and no hover */
    /*
    [1.7] PUNT: abandon former toolbar button scale up here, because it
    allows less space for images, and can lead to button text running 
    together on small screens when font size is set very high by users.
    */

/*--DEFUNCT
@media screen and (max-device-width: 640px) {
.navtable { 
    height: 1.25em;           -- [1.7] punt on scale up for touch (per above) 
}                             -- was: for 0..640 pixel screens only 

.navlink {
    font-size: 1.25em;        -- [1.7] punt on scale up for touch (per above) 
}

.navlink:hover {              -- [1.7] punt: abandon hover altogether (per earlier)
    text-decoration: none;    -- else underline/italic may get stuck on
    font-style: normal;
}
}
DEFUNCT--*/


</style>




<!-- Iff JavaScript not enabled: use former 1.5 CSS scaling -->
<noscript>    
<style>
#theimg {
    width:  100%; 
    height: auto;
}
</style>
</noscript>



<!-- There's more CSS ahead (info+note popups): search for "<style" -->





<!--
====================================================================================
JavaScript: dynamically scale HTML image element per DOM view size (etc.)
====================================================================================
-->



<script type="text/javascript">    /* ok anywhere in page, type optional */

    /* 
    ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    Context: thumbspage is now a four-language "stew" - Python code
    generates HTML with embedded CSS for styling and the JavaScript 
    here for dynamic image scaling - the last two of which regularly
    vary per browser.  Web coding has become an interoperability 
    nightmare.  Getting the comments right alone is brutal; handling
    the many browser-specific quirks seems the stuff of madness...
    None of the following code is used if JavaScript is disabled.
    ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    */





//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// TOUCH SWIPES
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~



/*
----------------------------------------------------------------------
[2.2] Update: image-viewer pages now support left/right and up/down
swipe gestures.  Left/right swipes trigger previous- and next-image 
events (like the Prev/Next menu buttons) per config; down invokes 
the image-info dialog (like filename taps); and up opens raw image
view (like image tap) where supported.  Touch events are simple
but glitchy (swipes to raw-view pages aren't supported on Chrome);
work on both mobiles/smartphones and Windows PCs with touchscreens;
and are no-ops on non-touch displays.  Simple taps, browser gestures
(e.g., pinch/spread zoom, back/forward swipe), and mice still work.

[2.3] Up-swipe is now changed to be the same as a new Note toolbar
button tap, if Notes are enabled via config and .note presence.  Else,
up-swipe is as in 2.2, including the browser-specific support levels.
----------------------------------------------------------------------
*/



// state of one-touch gestures we care about
var touchStartX = null;
var touchStartY = null;



function onTouchStart(event) {

    //--------------------------------------------------------------
    // Finger down: save screen-position state in globals.
    // These are used in the move and end event handlers.
    // Assume a swipe is starting; move may abandon it.
    // event's touches[] and targetTouches[] have no pageX/Y.
    //--------------------------------------------------------------

    // use only first touch within image's area
    touchStartX = event.changedTouches[0].pageX;
    touchStartY = event.changedTouches[0].pageY;
}



function onTouchMove(event) {

    //--------------------------------------------------------------
    // Finger move: see if it's still a one-touch event.
    // Some browsers may do pinch/zoom, back/forward, etc.
    // To allow pinch/spread gestures: clear swipe's state 
    // if any 2+ touch events fire between start and end.
    //--------------------------------------------------------------

    // don't do this: it prevents pinch/zoom
    // event.preventDefault();

    if (event.touches.length > 1) {
        // abandon 2+ point gestures: pinch/spread
        touchStartX = null;
        touchStartY = null;
    }
}



function onTouchEnd(event) {
    
    //--------------------------------------------------------------
    // Finger up: classify the swipe, and trigger page action.
    // The LRSWIPESPERBUTTONS config mods the meaning of left/right 
    // swipes: true=order of Prev/Next in toolbar; false='natural'.
    //
    // Horizontal|vertical direction is determined by the dimension
    // with the most change.  This also must ignore motions below 
    // a pixel-distance threshold, else simple taps may trigger swipe 
    // actions (and even both in some cases).  A 25-pixel threshold 
    // seems adequate for both finger and more-precise stylus.
    //
    // Caveat: up-swipe is enabled only for browsers which support
    // it today.  Chrome and its derivatives (Opera and Edge) do not.
    // In all browsers, [window.location.href = 'url'] works fine in 
    // tap/click handlers.  In the unsupporting browsers, though, the
    // same code in touch handlers like this frequently fails to add 
    // the calling page to history, such that Back doesn't return to 
    // the gallery's viewer page.  Assigning to just window.location 
    // or using .assign() or a setTimeout() callback doesn't help.  
    //
    // Hence: rather than penalizing all browsers, up-swipe is coded 
    // to work on Firefox (all platforms), iOS (all browsers), and 
    // Samsung Browser on Android.  Other touch browsers get alerts.
    // This will have to be revisited if Chrome ever fixes its bug;
    // UPSWIPEONALL can be used to test/enable up-swipe everywhere.
    //
    // [2.3] the preceding caveat is moot if Note popups are enabled  
    // via config and .note presence - up-swipe is then the same as a 
    // Note tap, not raw-image view (and image tap is still raw view).
    // Up-swipe to Note is in-page, and so supported on all browsers.
    //--------------------------------------------------------------

    // ignore 2+ point gestures: pinch/spread 
    if (touchStartX == null) {
        return;
    }

    // use only first touch within image's area
    var touchEndX = event.changedTouches[0].pageX; 
    var touchEndY = event.changedTouches[0].pageY;

    // how far was the motion, in pixels?
    var diffX = touchStartX - touchEndX;   // horizontal
    var diffY = touchStartY - touchEndY;   // vertical

    // to see the distances
    // alert('hori, vert: ' + diffX + ', ' + diffY);

    // ignore if not far enough to be a swipe: tap
    var threshold = 25;
    if (Math.abs(diffX) < threshold && Math.abs(diffY) < threshold) {
        return; 
    }

    // disable any other touch events: we're all in
    event.preventDefault();
    
    // which changed most?
    if (Math.abs(diffX) > Math.abs(diffY)) {
        // horizontal swipe
        if (touchStartX > touchEndX) {
            // left swipe
            if (false)
                // prev image
                onNavClick('1998-puertorico-1.jpg.html');
            else 
                // next image
                onNavClick('1998-pyref1e-book.jpg.html');
        }
        else {
            // right swipe
            if (false)
                // next image
    	        onNavClick('1998-pyref1e-book.jpg.html');
            else
                // prev image
                onNavClick('1998-puertorico-1.jpg.html');
        } 
    }

    else {
        // vertical swipe
        if (touchStartY < touchEndY) {
            // down swipe: image info popup
            imageInfoDialog();
        }
        else {
            // up swipe: Note popup [2.3], or raw-image view [2.2]
            if (true)
                showNote();
            else 
                if (runningInFirefox() || runningInSamsung() || runningOniOS() 
                    || false)
                    // where currently supported
                    window.location.href = "../1998-puertorico-2.jpg";
                else
                    // punt with notice
                    alert('Use tap instead of up-swipe on this browser.');
        }
    }

    // moot if start or move always precede end, but...
    touchStartX = null;
    touchStartY = null;
}



document.addEventListener('DOMContentLoaded', function() {

    //--------------------------------------------------------------
    // Register touch-event handlers on image, after image created. 
    // Don't register on whole window: wrongly generates on buttons.
    // A load callback avoids moving this past the image build code.
    //--------------------------------------------------------------

    // the image now exists
    var theimg  = document.getElementById('theimg');

    // this coding worked on mobile but not Windows; hmm
    // theimg.ontouchstart = onTouchStart;
 
    // this coding works universally
    theimg.addEventListener('touchstart', onTouchStart);
    theimg.addEventListener('touchmove',  onTouchMove);
    theimg.addEventListener('touchend',   onTouchEnd);
});





//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// SLIDESHOW
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~



/*
----------------------------------------------------------------------
[2.1] Updates:

1) The Auto button is now changed to underlined font as an 'on' 
indicator, whenever a slideshow is active.  Else it can be hard to 
tell the state of the show, especially when first started, and after
coming back from another page later.  Italics alone seems too subtle 
(and makes other buttons shift noticeably on Android), bold changes
the toolbar's layout jarringly, and colors are arbitrary via configs.

2) Prototyped but did not use half the normal delay (showDelayMS / 2)
for the page on which Auto is first tapped to start a show.  This page
is already open and perhaps already viewed, and the full delay might
make the show seem inoperable.  However, the new Auto font change (1) 
provides ample indication that the show is on; and assuming that the
first page has been viewed seems too presumptuous - it may have been 
jumped to from the index in order to start a show there immediately.
----------------------------------------------------------------------
*/  



// Global delay for timer loop, per-gallery configurable
var showDelayMS = 5000;

// Values have to be strings: ternary op inconvenient
var toggleKey = 'thumbspageAuto';
var toggleOn = 'show-timer-on', toggleOff = 'show-timer-off';

// Cancel id, global to this page
var showTimerID;

// Auto button HTML element, for font change [2.1]
var autoBtn;

// No-op or alert or console.log
var trace = function(msg) {};



function hasSessionStore() {
    /*
    ------------------------------------------------------------------
    Return true iff HTML5 session storage is present and enabled.
    Very old browsers (e.g., IE7-) don't support the API, and some
    browsers may allow users to disable session storage manually.
    This seems overkill, but allows for browser-specific disables.
    ------------------------------------------------------------------
    */

    try {
        sessionStorage.setItem('testkey', 'testval');
        return (sessionStorage.getItem('testkey') == 'testval');
    }
    catch (err) {
        return false;
    }
}



/*
------------------------------------------------------------------
Factor out common start/stop code, shared by button-click handler
(in two state-retention flavors), and initial page-show handler.
Global autoBtn has been fetched by autoContinue() post Auto build.

Update: don't use italics - it causes buttons to shift noticeably 
on Android, and underline is enough (and looks more like a link).
------------------------------------------------------------------
*/


function startSlideShow(delay) {
  //autoBtn.style['font-style'] = "italic";
    autoBtn.style['text-decoration'] = "underline";
    showTimerID = setTimeout(function() {onNavClick('1998-pyref1e-book.jpg.html');}, delay);
}


function stopSlideShow() {
  //autoBtn.style['font-style'] = "normal";
    autoBtn.style['text-decoration'] = "none";
    clearTimeout(showTimerID);
}



function onAutoClick() {
    /*
    ------------------------------------------------------------------
    [2.0] On toolbar Auto button clicks: toggle per-tab, cross-page
    slideshow state, and schedule or clear timer event to flip image.
    This is essentially automatic Next clicks, run on timer events.

    The cross-page toggle's state is per-tab, and used by both the 
    current and following viewer pages.  The slideshow persists until
    cancelled by a toggle or the gallery is exited, and is restarted
    on gallery returns (in most contexts).  To loop: exiting a viewer 
    page stops the timer automatically, but new pages reschedule the 
    timer on load if the toggle is on.  Subject to the vagaries of 
    browser back-forward caches, but it works on all browsers tested. 

    Auto requires JavaScript.  About cross-page state in serverless
    pages: this uses window.name, because it's simple and cannot be 
    disabled.  Alternatives: client-side cookies, and HTML5 stores 
    (window.sessionStorage.{getItem('key'), setItem('key', 'val')}).

    Coding note: unlike window.name, page-defined attributes (e.g.,
    window.autoToggle) do not persist across pages: Auto switches 
    pages once, but the attribute is 'undefined' in the next page.  

    UPDATE, Jul-18-20: this now uses HTML5 session storage, not 
    window.name.  Else, state fails in Safari (only) when viewing 
    galleries locally/offline (only).  The new scheme works the same 
    as window.name, which is still used as a fallback where session 
    storage is unsupported or disabled.  More: UserGuide.html#_20C.

    Caveat: with this fix, local-gallery slideshows work on Safari,
    but stop (and rarely even crash) when returning with Back from
    another page in some contexts (e.g., from remote); see ahead.

    [2.1] Change Auto button font to underline/normal for slideshow 
    on/off; this happens in the factored utility functions above.
    [2.1] Do _not_ use half the normal delay for the page on which 
    Auto is first clicked to start the show; this is too presumptuous.
    ------------------------------------------------------------------
    */
 
    if (hasSessionStore()) {
        // use HTML5 cross-page session storage where available (newer, safari ok)
        trace('onAutoClick: using session storage');

        // if set and on: clear flag, cancel next timer event
        if (sessionStorage.getItem(toggleKey) == toggleOn) {
            sessionStorage.setItem(toggleKey, toggleOff);
            stopSlideShow();
        }
        // if off or not set: start loop (rescheduled by next page)
        else {
            sessionStorage.setItem(toggleKey, toggleOn);
            startSlideShow(showDelayMS);
        }
    }
    else {
        // use cross-page window.name as a fallback (original, Safari fail) 
        trace('onAutoClick: using window.name');

        // if set and on: clear flag, cancel next timer event
        if (window.name == toggleOn) {
            window.name = toggleOff;
            stopSlideShow();
        }
        // if off or not set: start loop (rescheduled by next page)
        else if (window.name == toggleOff || window.name == '') {
            window.name = toggleOn;
            startSlideShow(showDelayMS);
        }
    }
}



function autoContinue(event) {
    /*
    ------------------------------------------------------------------
    On page load: reset timer if show on, but not if off or not set 
    (any 'falsy').  Run on first open, but also any navigation entry,
    and might be run on back-forward cache restores (Safari issue?).
    [2.1] Change Auto button to underline font if slideshow is active.
    [2.1] The next page (and thereafter) always uses the full delay.
    [2.1] Now called just after Auto built so font change unnoticeable.
    ------------------------------------------------------------------
    */

    var reschedule;
    trace('autoContinue');

    // assume it's built by now
    autoBtn = document.getElementById("autoBtn");   // global

    // on Safari Back stop, deferred: 'null::false'
    persisted = event ? event.persisted : 'none';
    trace(sessionStorage.getItem(toggleKey) +':'+ window.name +':'+ persisted);

    if (hasSessionStore()) {
        reschedule = (sessionStorage.getItem(toggleKey) == toggleOn);   // HTML5 storage
    }
    else {
        reschedule = (window.name == toggleOn);   // else fallback
    }

    if (reschedule) {
        trace('reschedule');
        startSlideShow(showDelayMS);
    }
    else {
        trace('NOT rescheduled');   // off, or Safari Back stop
    }
}



/*
------------------------------------------------------------------------------------
[2.1] The call to autoContinue() has been moved to end of this file, so it occurs
immediately after the Auto button is built.  It cannot be called here because the
button must exist for its font to be changed, and the pageshow event-handler
code below made the button's font change noticeable on some browsers/platforms
(a timer event is another option, but would similarly flash and is heuristic).
 
[2.0] Safari (only) work-around attempt: run timer reschedule code in a pageshow 
load-time event, not immediately.  This was harmless in other browsers; for Safari, 
it tried to both restore the show on a next page or Back return, and avoid Safari 
Back-failure popup messages: "... Operation not permitted" (NSPOSIXErrorDomain:1)".
DID NOT HELP: a Safari glitch changes the Auto toggle to null in HTML5 session state
after a Back from remote; see UserGuide.html#moresafarijunk for the whole story.
------------------------------------------------------------------------------------
*/

/*--DEFUNCT
try {
    // callback
    trace('run timer reschedule deferred');
    window.addEventListener('pageshow', autoContinue);    // when widgets built
}
catch (err) {
    // immediate
    trace('run timer reschedule immediately');
    autoContinue(null);    // run now in older browsers? (alt: window.onpageshow)
}
DEFUNCT--*/

// original: font-change fails here, because autoBtn is still null here
// autoContinue(null);    // run on page load immediately everywhere

// now called near end of file as soon as Auto built, so font change not visible
// document.addEventListener('DOMContentLoaded'....) not used: need to run asap 





//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// FULLSCREEN
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~



/* 
----------------------------------------------------------------------
[2.1] Update: the Full button is changed to underline font as an 
'on' indicator, whenever fullscreen has been activated by Full.  This 
is done only for parity with Auto: fullscreen is obvious, and lasts 
for just one page as currently implemented (better == major redesign).

Unlike Auto, all Full font changes happen in an event handler, because
a fullscreen request can be denied and raise an exception in an async
'promise,' and users may cancel Full without pressing Full (e.g., via 
an Escape key).  On browsers that don't support this event (e.g., some 
Safari), Full's font will simply never morph.  Users may also manually
turn a different fullscreen mode on and off outside the page's control 
at any time with shortcut keys or similar, but the page might not 
receive an event for, and be unable to cancel, that fullscreen mode.
----------------------------------------------------------------------
*/



/*
----------------------------------------------------------------------
FULLSCREEN CAVEAT

JavaScript-initiated fullscreen mode is limited to a single page, and
does nothing on some browsers - including Safari and Chrome on iOS.
It's unclear whether this is useful as a blowup/zoom feature, or just
a "stupid browser trick" in thumbspage galleries that wastes UI space.
Hence, the Full toolbar button that invokes code here can be disabled
on a per-gallery basis via a build setting in user_configs.py.

Users may be better served by browsers that have manual fullscreen
options, which persist across page changes and span Auto slideshows
(e.g., try F11 on Windows, control-command-f on Mac OS, Hide Toolbar
on iOS 13+, and Opera 63+ on Android).  Manual fullscreen doesn't 
work everywhere, but it likely beats thumbspage's Full where it does.

For more about this feature, see the User Guide's coverage:
- UserGuide.html#fullscreenmanual (manual options)
- UserGuide.html#fullscreen20 (2.0 release docs)
- UserGuide.html#_dnG (initial feature proposal)

Coding note: these alternatives were also tried (forced scrolls,
and "Add to home screen" support), but as coded did not help; 
either more is required, or they are old and fully trounced hacks:
- window.scrollTo(0,1); (this may require bogus space)
- <meta name="mobile-web-app-capable" content="yes"> 
- <meta name="apple-mobile-web-app-capable" content="yes">

[2.2] Some mobile browsers now popup a temporary but annoying message
every time JavaScript fullscreen is entered, just as many do on PCs.
Which makes it even less useful; see Opera's landscape fullscreen.
----------------------------------------------------------------------
*/



//
// The fullscreen API has been, well, fluid...
// https://developer.mozilla.org/en-US/docs/Web/API/Fullscreen_API
// Some cases below are untested; the standard calls are now widespread.
//



function openFullscreen() {
    var de = document.documentElement;           // documentElement: <html> 

    // request fullscreen
    if (de.requestFullscreen) {                  // "standard" (undefined: false)
        de.requestFullscreen();                    
    } 
    else if (de.webkitRequestFullscreen) {       // some Chrome, Safari, Opera 
             de.webkitRequestFullscreen();
    }  
    else if (de.mozRequestFullScreen) {          // some Firefox 
             de.mozRequestFullScreen();
    } 
    else if (de.msRequestFullscreen) {           // some IE/Edge
             de.msRequestFullscreen();
    }

    // if it worked: change font in fullscreen-change handler    
}



function closeFullscreen() {
    // documentElement not required here

    if (document.exitFullscreen) {               // "standard" (undefined: false) 
        document.exitFullscreen();
    } 
    else if (document.webkitExitFullscreen) {    // some Chrome, Safari, Opera 
             document.webkitExitFullscreen();
    } 
    else if (document.mozCancelFullScreen) {     // some Firefox
             document.mozCancelFullScreen();
    } 
    else if (document.msExitFullscreen) {        // some IE/Edge 
             document.msExitFullscreen();
    }

    // if it worked: change font in fullscreen-change handler    
}



function isFullscreen() {
    // any may be undefined: false in JS

    return document.fullscreenElement        ||    // "standard" 
           document.webkitFullscreenElement  ||    // some Chrome, Safari, Opera 
           document.mozFullScreenElement     ||    // some Firefox 
           document.msFullscreenElement;           // some IE/Edge
}



function onFullClick() {
    /*
    ------------------------------------------------------------------
    [2.0] On toolbar Full button taps: toggle one-page fullscreen on 
    or off.  Fullscreen persists until cancelled by a Full toggle,
    user action, or page exit, and it doesn't work in some browsers.

    This requires JavaScript (and perhaps a horizontal toolbar scroll
    on smaller displays).  It doesn't need to use cross-page state,
    because the fullscreen API has state queries.  This could track
    on/off state manually with HTML5 storage, but that might also 
    require catching (nonportable?) callbacks on user-invoked changes. 
    ------------------------------------------------------------------
    */

    if (!isFullscreen()) {
        openFullscreen();
    } 
    else {
        closeFullscreen();
    }
}



function onFullscreenChange() { 
    /* 
    ------------------------------------------------------------------
    [2.1] Change Full's font as a perhaps-redundant visual indicator,
    whenever fullscreen state is changed by the Full button itself,
    or the Full button's fullscreen is otherwise cancelled by a user. 

    The fullBtn element is already built here, because we're in a 
    user-event callback.  Run by an event handler, because fullscreen
    requests are async and can fail with an uncaught exception, and a
    Full may be cancelled without pressing Full (e.g., an Escape key).
    This won't be invoked for user-initiated fullscreen mode (which 
    weirdly differs from page/JavaScript-initiated fullscreen), but 
    it is called when users cancel a Full without pressing Full.

    Update: don't use italics - it causes buttons to shift noticeably 
    on Android, and underline is enough (and looks more like a link).
    ------------------------------------------------------------------
    */

    var fullBtn = document.getElementById("fullBtn");

    if (isFullscreen()) {
      //fullBtn.style['font-style'] = "italic";
        fullBtn.style['text-decoration'] = "underline";
    }
    else {
      //fullBtn.style['font-style'] = "normal";
        fullBtn.style['text-decoration'] = "none";
    }
}



//
// register for fullscreen on/off changes invoked by page
//
document.documentElement.onfullscreenchange = onFullscreenChange;




/*
----------------------------------------------------------------------
Caveat: we can't force fullscreen for a next page here, even in a 
deferred pageshow event, because it must be requested only from a 
short-running, user-initiated event handler (per Chrome's console 
error message, the API can be initiated only by a user gesture).
 
Hence, it lasts for one page only, where it works at all, and won't 
persist for Auto slideshows.  It's useful anyhow as a zoom, but disable 
as desired, and use browser/platform-specific fullscreen if available.

A "real" fullscreen would require morphing a single persistent page,
and more thumbspage redesign than its use cases currently warrant,
and might sacrifice linkable viewer-page URLs for individual images.
For now, users can increase page size manually in most browsers.

[2.2] JavaScript fullscreen also fails in touch events (see above).
Which would be okay if all browsers had multi-page fullscreen modes.
----------------------------------------------------------------------
*/





//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// PLATFORM
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~



/* 
These are all really bad ideas; why hasn't this been standardized? 
*/



function runningOniOS() {
    /*
    ------------------------------------------------------------------
    More heinous web hacks; MS says iPhone in IE11 too.
    Use a JS regexp on an unreliable but widely used string.
    A generally bad idea, but often the only option today. 
    ------------------------------------------------------------------
    */

    // alert(navigator.userAgent);
    return /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;  // hack!
}



function runningInSafariOniOS() {
    /*
    ------------------------------------------------------------------
    More heinous web hacks; Chrome says Safari on iOS too,
    and all iOS browsers must use the same Apple code base.
    Use a JS regexp on an unreliable but widely used string.
    These tests matter; why haven't they been standardized?
    
    Was unused; formerly employed to allow for more vertical
    cruft on landscape+iOS+Safari only (but failed: use 1.5):
    safariFudge = (runningOniOS() && runningInSafari() && 
             viewportsize.width > viewportsize.height) ? 50 : 0;

    [1.7] May-2020: Now used for Safari+iOS+landscape CSS-scaling 
    legacy option (but UNUSED by default, per user_configs.py).
    This new, renamed coding works only for Safari on iOS, not 
    desktop, but suffices...  though its !others.test() coding
    means that any not in others are Safari (where others means 
    {Chrome+iOS, Firefox+iOS, the new Edge, UC Browser}); yuck.
    ------------------------------------------------------------------
    */

    // this doesn't work today (! list incomplete?)
    // return /^((?!chrome|android).)*safari/i.test(navigator.userAgent);   // hack too!

    // close, but not quite right (see above)
    var vendor = navigator.vendor;
    var uagent = navigator.userAgent;

    return runningOniOS() &&
           vendor && vendor.indexOf('Apple') > -1 &&              // Apple code base
           uagent && !/CriOS|FxiOS|Edg|UCBrowser/.test(uagent);   // not other browsers
}



function runningInChromeOniOS() {
    /*
    ------------------------------------------------------------------
    Ditto: used for history-destacking disable on Next/Prev onclick.
    [2.0] This is now UNUSED by default too: the bug has been fixed.
    ------------------------------------------------------------------
    */

    return navigator.userAgent.match('CriOS');    // also hack!
}



function runningInSafari() {
    /*
    ------------------------------------------------------------------
    Same: add to work around Safari-only Back fails for Auto [2.0].
    UNUSED: Back fail is a session-state bug in Safari (see ahead).
    Coding note: some browsers 'spoof' a Safari string in userAgent.
    ------------------------------------------------------------------
    */

    // this doesn't work today...
    // return window.safari !== undefined;    // also also hack!

    var vendor = navigator.vendor;
    var uagent = navigator.userAgent;
    return vendor && vendor.indexOf('Apple') > -1 &&              // Apple code base
           uagent && !/CriOS|FxiOS|Edg|UCBrowser/.test(uagent);   // not other browsers
}



function runningInFirefox() {
    // [2.2] added for Chrome post-touch Back bug
    return navigator.userAgent.toLowerCase().indexOf('firefox') != -1;    
}



function runningInSamsung() {
    // [2.2] added for Chrome post-touch Back bug
    return navigator.userAgent.match(/SamsungBrowser/i);    // i=caseinsens, notfound:null
}



// to test
/*
console.log(navigator.userAgent);
console.log('runningInSafari: '      + (runningInSafari() ? 'yes' : 'no'));
console.log('runningInSafariOniOS: ' + (runningInSafariOniOS() ? 'yes' : 'no'));
*/





//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// NAVIGATION
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~



function onNavClick(page) {
    /*
    ------------------------------------------------------------------
    When opening another viewer page with the Prev or Next links,
    simply replace this page with the target, to avoid adding this
    soon-to-be-prior viewer page to the browser's history stack. 

    The net effect is that viewer pages are never added to browser
    history, except for just one when navigating away from the 
    gallery.  This way, the browser's Back skips over the entire 
    gallery navigation session, returning immediately to the gallery
    entry point: the index, or other direct viewer-page link source.

    Otherwise, N image views may require N Back clicks to return to
    pre-gallery pages (if there are any, and Index-page context is
    not enough).  This was much worse than making Back skip all nav 
    steps, given that viewer pages' Prev and Next do similar work.

    The Index link still goes to the gallery's index page regardless
    of the entry point - as it should, for direct viewer-page links.
    Moreover, after navigating away from the gallery, browser Back 
    and Forward clicks return to the last viewer page viewed (only).

    Relies on the DOM location.replace() redirect-sans-history call,
    which might fail if classified as unsecure (crossing domains?).
    This could use <button>s, but keeping original <a>s appearance.
    history.go(-N) + counter stacks may work, but can't catch Back.
    onclick() here fires before target's onload(): see "Loading...".
    Former typo: "return True;" is exception (but href used anyhow).

    CAVEAT: this history destacking feature is disabled for Chrome 
    on iOS (only), because location.replace() fails in that browser
    (only!).  If used, Back clicks redisplay the _same_ page N times 
    for N image views, which is worse than allowing pages to be  
    stacked.  thumbspage.py's version 1.6 notes have more details.
    No coding work-around could be found: go(-1), timer, paths, ...

    UPDATE [1.7]: this is still broken as of May 2020's iOS Chrome 75; 
    if enabled, the .replace() here now silently stacks all pages.
     
    UPDATE [2.0]: since iOS Chrome's current behavior is no different 
    than the work-around, the work-around bypass config switch now 
    defaults to true; set it back to false if this ever becomes worse
    again.  The new true will also pick up a future Chrome fix.

    UPDATE [2.0]: the iOS Chrome history bug has been fixed, as of
    June 2020's Chrome 83 (and perhaps earlier).  The true setting
    of the config switch adopts the fix automatically, and thumbspage
    navigation pages are no longer stacked or retraced in iOS Chrome.  
    Hence, this issue is closed; its code below is retained as info. 

    UPDATE [2.2]: see also onTouchEnd() above for a related issue, 
    plus https://developer.mozilla.org/docs/Web/API/Window/location.
    ------------------------------------------------------------------
    */

    // absolute URL not required, and URL-encoded filenames okay
    // var loc = window.location;
    // var url = loc.protocol + '//' + loc.host + ... + page;

    // no longer used: browser-bug work-around, till fixed?
    if (runningInChromeOniOS() && ! true) {
        return true;                     // use URL in <a> (same as no-JS case)
    }
    
    // all other browsers
    try {
        window.location.replace(page);   // goto this, don't add page to history
    }
    catch(err) {
        window.location.href = page;     // work on errors, add to history (=assign())
    }
    return false;                        // do not goto <a> href link (if one coded)
}





//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// INFO POPUP
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~



function imageInfoDialog()
{
    /*
    ------------------------------------------------------------------
    [1.7] Feb-2020: popup an image-info dialog when the image's
    filename at the top of the viewer page is clicked/tapped.  This 
    initially used a simple alert() call; it could instead build new
    HTML element objects, but that seems overkill in this context.
  
    The 'Image size' comes from the static .py (via Pillow); it's 
    the same as the JS DOM's theimg.natural{Width, Height}.  Display 
    size alone is from JS DOM, and reflects the size requested in 
    the last resizeImage() callback (extraneous, but interesting).
    This looks funky in generated pages, due to Py str replacements. 

    [2.0] This now uses a custom modal 'dialog' - which is really 
    a full-page overlay with opacity, plus a display box within it,
    all shown and hidden on demand.  Most implementation code (CSS,
    HTML, JavaScript) is at end of file; this formats the message.

    Why go custom? Some browser vendors discourage alert(), and even
    treat it as a threat, asking users if they wish to silence it.
    Worse, mobiles format alert() text badly with wrapping, and iOS
    13's Hide Toolbars can fully botch it with text outside the box.
    The custom dialog instead scrolls text vertically when needed.
    Bonus: the new dialog box inherits viewer-page bg/fg color 
    settings in user_configs.py; respects user font-size choices; 
    supports copy/paste; and does not kill Full fullscreen displays.

    [1.7] May-2020: now labels date as 'Digitized' for photo scans.
    [2.0] Jul-2020: now shows device|software line iff in Exif tags.
    [2.0] Jul-2020: use "Created" for unknown data, and "Nw x Mh".
    [2.0] Jul-2020: use a custom modal-dialog display (@end of file).
    [2.3] Apr-2022: bg/fg/bd colors may now be custom, as for Note.
    [2.3] Apr-2022: device text is arbitrary: now JS+HTML escaped.
    ------------------------------------------------------------------
    */

    var theimg  = document.getElementById('theimg');
    var domwide = theimg.width, domhigh = theimg.height;   // info added here

    var popup = showInfo;    // [2.0] alert() no more... see above/ahead

    // legacy: see ahead
    popup('Digitized: 2013-04-14 @15:15:55\n'  +          // Py: Exif (origin date)
          'Modified: 2020-03-05 @15:46:57\n' +                       // Py: OS
          'File size: 482,451 bytes\n'  +                  // Py: OS
          'Image size: 2,944w x 2,088h\n' +        // Py: Pillow (also in DOM)
          'Display size: ' + domwide + 'w x ' + domhigh + 'h' +  // JS: DOM (dynamic)
          '\nDevice: CanoScan 8800F (Canon)')                                   // Py: Exif (device|software?)

    return false;   // don't try to follow a fake <a> href, if coded
}



//
// [2.0] The info-popup implementation continues near the end of this file.
//





//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// IMAGE SCALING
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~



function refitAspectRatio(trueWidth, trueHeight,          // image size
                          displayWidth, displayHeight,    // display size
                          stretchBeyondActual) {          // expand if small?
    /*
    ------------------------------------------------------------------
    Constrain image size to display area, retaining original 
    aspect ratio.  Use to shrink or enlarge images as needed
    to fit a new display size while showing the entire image.
     
    The math scales both sides per the smallest display ratio:
       trueWidth, trueHeight: 
           width, height of actual/original source image (pixels)
       displayWidth, displayheight: 
           maximum available width, height for image display (pixels)

    If 'stretch' is true, images may expand beyond their actual 
    sizes to fit the display; if false, image maximum sizes are 
    limited to actual size, to avoid blurring of small images. 
    Returns an Object (see Python dict), with .width, .height.
    ------------------------------------------------------------------
    */

    var ratio;
    
    // per smallest-fit side 
    ratio = Math.min(displayWidth / trueWidth, displayHeight / trueHeight);

    if (! stretchBeyondActual) {
        ratio = Math.min(ratio, 1.0);    // don't expand beyond actual size
    }
    return {width:  (trueWidth  * ratio), 
            height: (trueHeight * ratio)};
}



function getViewportSize() {
    /*
    ------------------------------------------------------------------
    Display size: yet another browser-specific mess...
    iOS reports available size differently (of course).
    A Math.max() might work here, but seems a bit iffy.
    These are often the same, but supported unevenly;
    in theory, client* sizes discount border and margin.
    ------------------------------------------------------------------
    */

    // to watch:
    // alert('window: ' + window.innerWidth + ' x ' + window.innerHeight);
    // alert('docelmt: ' + document.documentElement.clientWidth + 
    //       ' x ' + document.documentElement.clientHeight);

    if (! runningOniOS() &&
        window.innerWidth !== undefined && window.innerHeight !== undefined) { 
        return {width:  window.innerWidth, 
                height: window.innerHeight};
    } else {
        return {width:  document.documentElement.clientWidth, 
                height: document.documentElement.clientHeight};   // portable much?
    }
}



function getUsedHeight() {
    /*
    ------------------------------------------------------------------
    [1.7] Feb-2020: Return the total pixel height of all non-image 
    elements on the page, used to deduce space left for the image.

    Though rare, the 110px fudge-factor constant formerly used 
    to calculate image-area size could result in clipping of the 
    image's bottom border and part of its content, when viewing 
    very tall images and applying unusually large font settings.

    This was first seen for mobile portrait screenshots in Firefox 
    on an S8+ smartphone, but could also happen on both desktop 
    and mobile browsers if users selected larger fonts.  This 
    page's CSS font-size boost used for toolbar buttons on small
    screens made the clipping more likely, but user font/scaling
    settings alone could trigger it for some images and browsers.

    The new scheme deduces image space from space already taken,
    rather than relying on constants, or the undrawn image's space.
    The net effect now accommodates arbitrary user font settings;
    as a bonus, it also typically yields a larger image display.  

    Caveat: this must still use a small constant size (fudgeExtra) 
    to account for margins and the image border, but this seems to 
    work universally (so far); alas, the DOM offsetHeight includes 
    content+padding+border, but not margin, which is oddly complex.

    Coding Notes: 
    - The <noscript> warning size is moot: never gets here to resize.
    - The result isn't cached, because font size can change any time.
    - This is coded defensively; browser variability is a nightmare.
    - See also elt.getBoundingClientRect(), a possible alternative.
    - Failed: elt.outerHeight(), getComputedStyle(elt)['marginTop']. 
    - TBD: cache getElementById() results in global vars for speed?
    ------------------------------------------------------------------
    */

    // locals
    var debug = false;           // show alerts?
    var defaultHeight = 110;     // prior-constant fallback, prefudge
    var fudgeExtra = 12;         // for margins + image border
    var usedHeight;

    try {
        var titleElement   = document.getElementById('thetitle');
        var toolbarElement = document.getElementById('thetoolbar');

        var titleHeight   = titleElement.offsetHeight;
        var toolbarHeight = toolbarElement.offsetHeight;

        if (titleHeight != 0 && toolbarHeight != 0) {
            usedHeight = titleHeight + toolbarHeight;
        }
        else {
            usedHeight = defaultHeight;
            if (debug) alert('usedHeight defaulted: zero element size');
        }
    }
    catch (err) {
        usedHeight = defaultHeight;
        if (debug) alert('usedHeight defaulted: size fetch failed');
    }

    usedHeight += fudgeExtra;
    if (debug) alert('getUsedHeight: ' + usedHeight);
    return usedHeight;
}



function setLoadingDisplay()
{
    /*
    ------------------------------------------------------------------
    To minimize 'flash' hide the image during initial download or
    load and post an indicator message.  The message is erased and
    the image is unhidden when the image is sized.  Otherwise, the 
    only indicator of progress is the browser's busy indicator. 

    The <P> isn't coded in HTML - else it could not be erased if 
    JavaScript is disabled.  This uses a global variable that can 
    be reference later (though it might instead set a fetchable 
    DOM id with loadingIndicator.id = 'theid').
    ------------------------------------------------------------------
    */

    // hide the image temp
    document.getElementById('theimg').style.visibility = 'hidden';

    // post a temp <P> indicator
    loadingIndicator = document.createElement('P');      // no 'var' means global
    loadingIndicator.style.fontStyle = 'italic';         // global means window
    loadingIndicator.style.textAlign = 'center';
    loadingIndicator.innerHTML = 'Loading...';

    // var text = document.createTextNode('Loading...'); // innerHTML is enough 
    // loadingIndicator.appendChild(text);
    document.getElementById('thetitle').appendChild(loadingIndicator); 
}



function resizeImage() {
    /*
    ------------------------------------------------------------------
    Run on initial page load, and again on each window resize.
    As of 1.6, viewer pages use JS here to rescale the page's 
    image to the current display area, retaining the image's 
    original aspect ratio.  CSS alone cannot preserve aspect 
    ratio on all window resizes; when used, images can overflow
    containers and run off-screen, requiring scrolls to view.
        
    This emulates native display well on all desktop browsers,
    and scales images to the available space on mobile browsers.
    Exception: iOS landscape falls back on 1.5's CSS scrolled 
    scaling, because no way could be found to do better (TBD; 
    Safari munges sizes and UI, and it is most of iOS traffic).
    1.5 CSS scaling is also used when JavaScript is disabled.

      [1.7] Update May-2020: JavaScript scaling is now also 
      used for all iOS browsers in landscape mode.  This works 
      well on all non-Safari iOS browsers (and matches their 
      Android behavior); is better on Safari in iOS < 13 (image 
      bottom is easier to view); and works perfectly in iOS 
      Safari if users enable the new toolbar-hiding option added 
      in iOS 13.  Hence, JS scaling is now used everywhere by 
      default, when JS is enabled.   As a legacy option, 1.5's
      CSS-based display can still be used for landscape in iOS 
      Safari, because its toolbars mar pages sans the iOS 13 fix. 

    Scaling to the full viewport size is partly heuristic: the 
    constants here work everywhere tested, but may need tweaking;
    alas, no universal way to get a scaling-area size was found.  
    Tried but failed: a <div> with display:flex + <canvas>/Image(). 

      [1.7] Update Feb-2020: the available image height is now 
      deduced from the sizes of other page elements instead of
      constants, to accommodate font settings; see getUsedHeight().
    
    Some Python % replacements here could be dynamic JavaScript.
    Caution: browser settings can have big side effects - Android
    Chrome's "Force enable zoom" may break same "Raw" displays.

    Note: the DOM's theimg.natural{Width, Height} here is original
    in-file image size, and the same as the {ORIGWIDE, ORIGHIGH} 
    fetched from Pillow and expanded in imageInfoDialog() by .py.
    ------------------------------------------------------------------
    */

    // erase message paragraph
    loadingIndicator.style.display = 'none';

    // HTML element: via DOM, per <img> tag
    var theimg = document.getElementById('theimg');

    // new display area size for image scaling
    var viewportsize = getViewportSize();

    // [1.7] calc size of other page elements
    var usedheight = getUsedHeight();

    // [1.7] landscape on iOS in Safari: use legacy CSS display? 
    if (false &&
        runningInSafariOniOS() && viewportsize.width > viewportsize.height) {
        // sizes and UI munged: use version 1.5 CSS scaling
        theimg.style.width  = '100%'; 
        theimg.style.height = 'auto';
    }
    else {
        // erase any iOS landscape settings
        theimg.style.width  = null; 
        theimg.style.height = null;

        // calc new scaled size (less borders, title+toolbar+cruft)
        // [1.7] usedheight was 110, now actual space used
        var refitsize = refitAspectRatio(
                            theimg.naturalWidth, theimg.naturalHeight, 
                            viewportsize.width - 18, viewportsize.height - usedheight,
                            false);
  
        // change in DOM/page: redraws 
        theimg.width  = refitsize.width;     // see also .style.cssText('width:n;')
        theimg.height = refitsize.height;    // see also .style.height=n (inline CSS)
    }

    // show now, minimizing initial flash
    theimg.style.visibility = 'visible';
}



//
// Register resize callback handler now
// Or: <body onresize="resizeImage()" onload="resizeImage()">
//
window.addEventListener('resize', resizeImage);


// There's more JS ahead (info popup, etc.): search for "<script"
</script>





<!--
====================================================================================
Back to HTML: layout, inline CSS styles, CSS/JavaScript fodder/linkage
====================================================================================
-->



<title>1998-puertorico-2.jpg</title>
</head>

<!-- initial sizing, else opens fullsize ("...code..." = func) -->

<body onload="resizeImage();">




<!--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~-->
<!-- JS NOTE: iff JavaScript not enabled, alert user                -->
<!--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~-->


<!-- [2.3] JS is now required, as hrefs for Prev/Next/Index were dropped to -->
<!-- kill URL popups; otoh, viewers were largely broken without it before;  --> 
<!-- 2.3 also omits Note/Auto/Full btns if no JS, but keeps other (layout); -->


<noscript>
<div class=overflowscroll>
<p class=nojsnote>
This page is best viewed with JavaScript enabled
</p></div>
</noscript>




<!--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~-->
<!-- FILENAME: possibly long, <h1> takes away too much image space  -->
<!--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~-->


<!-- [1.7] popup mod/taken dates + size info dialog on filename click/tap -->
<!-- use same hover style as bottom buttons for indicator and consistency -->


<div id=thetitle class=overflowscroll>     <!-- already scrolled [2.0] -->
<p class=filename>

<!-- with <p>+<span>, android chrome (only!) selects text and prompts for a copy -->
<!-- instead, use simple <a> + onclick for JS use only + manual cursor (no href) --> 

<a class="navlink cursorpointer"
   onclick="return imageInfoDialog();" title="View image info">
   1998-puertorico-2.jpg
</a>

</p></div>


<!--DEFUNCT
<span style="cursor: pointer;" onclick='alert("...")'>
1998-puertorico-2.jpg
</span>
DEFUNCT-->




<!--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~-->
<!-- IMAGE: (re)scaled to fit window/viewport, clickable for raw    -->
<!--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~-->


<!-- [1.7] make image click/tap same as Raw (view fullsize) for convenience; -->
<!-- especially useful for smaller image displays of most mobile landscapes; -->
<!-- doesn't use a simple href, because the mouseover url popup is too busy; -->
<!-- [2.2] image tap still works as before with image-swipe touch gestures;  -->


<img id=theimg src="../1998-puertorico-2.jpg"
     class=cursorpointer
     alt="Full image" title="View raw image"
     onclick='window.location.href = "../1998-puertorico-2.jpg"'>


<!-- iff JavaScript enabled, hide now to minimize initial flash, show indicator -->

<script>

//
// on page load
//
setLoadingDisplay();

// to test:
// setTimeout(setLoadingDisplay, 2000);   // func, msec
// setTimeout(resizeImage, 4000);

</script>




<!--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~-->
<!-- BUTTONS: small but scrolled to avoid mobile-viewport overflow  -->
<!--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~-->


<!-- [1.7] large user fonts could run together: avoid by dropping CSS scale-up --> 
<!-- this, plus the new used-space calcs, yield slightly larger image displays -->
<!-- [2.0] toolbar buttons now scroll, so overflow seems a largely moot point  -->
<!-- [2.2] add optional tooltip popups to Auto, Full, images, and filename     -->
<!-- [2.3] add optional Note button, drop hrefs, more tooltips, delete buttons -->


<div id=thetoolbar class=navdiv>     <!-- [1.7] id for space -->
<p>
<table class=navtable id=toolbartable>
<tr>  

<!-- hrefs used if JS is disabled (or ios-chrome browser bug work-around used);     -->
<!-- it may be nice to drop the URL popups on mouseover, but hrefs trigger them;    -->

<!-- 
[2.3] Dropped all hrefs here (e.g., href="1998-puertorico-1.jpg.html", href="#slideshow"): they are 
no longer used as fallbacks for Prev/Next if JS, Index is now a func, and tooltips are
default.  This avoids obtrusive URL popups on mouseover, which are now redundant with
tips.  It also requires JS for Prev/Next/Index, but viewer pages were already unusable
without it.  The 2.3 code renders identically to 2.2, when no Note button is present.
2.3 also changed hardcoded 'index.html' to use INDEX config: name may differ per build.
-->

<td><a class=navlink title="Go to previous image"
       onclick="return onNavClick('1998-puertorico-1.jpg.html');">Prev</a></td>

<td><a class=navlink title="Go to next image"
       onclick="return onNavClick('1998-pyref1e-book.jpg.html');">Next</a></td>

<td><a class=navlink title="Go to thumbnails page"
       onclick="window.location.href='../index.html'; return false;">Index</a></td>

<td><a class="navlink optional" id=noteBtn title="View image description"
       onclick="showNote(); return false">Note</a></td>       <!-- [2.3] new -->

<td><a class="navlink optional" id=autoBtn title="Toggle slideshow"
       onclick="onAutoClick(); return false">Auto</a></td>    <!-- [2.0] new -->

<td><a class="navlink optional" id=fullBtn title="Toggle one-page fullscreen"
       onclick="onFullClick(); return false">Full</a></td>    <!-- [2.0] new (lesser) -->

</tr></table>
</p>             <!-- the /p adds space below links bar, oddly: don't remove! -->
</div>


<script>

//
// reschedule Auto timer event on the new page, and change
// Auto's font to underline now so change is not noticeable;
//
autoContinue(null); 


//
// show Auto button now, if JS is enabled (else it's a no-op)
//
document.getElementById("autoBtn").style.display = "block";


//
// [2.3] show Note button if JS && feature enabled via config+files
//
if (true) {
    document.getElementById("noteBtn").style.display = "block";
}
else {
    // delete noteBtn, else throws off spacing, especially on mobile
    document.getElementById("toolbartable").rows[0].deleteCell(3);
}

   
//
// show optional Full button now iff JS and config enabled
//
if (true) {
    document.getElementById("fullBtn").style.display = "block";
}
else {
    // [2.3] ditto for spacing, now (4) if Note was deleted, else (5) 
    document.getElementById("toolbartable").rows[0].deleteCell(true ? 5 : 4);
}

</script>


<!--DEFUNCT 
[2.0] raw display is now image-tap or down-swipe (see also filename-tap info)
<td><a class=navlink 
       href="../1998-puertorico-2.jpg">Raw</a></td>
DEFUNCT-->





<!-- POPUP OVERLAYS -->





<!--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~-->
<!-- INFO POPUP: custom modal dialog, via div overlay+opacity [2.0] -->
<!--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~-->


<!--'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
CSS: why not in <head>? - OK per HTML 5.2, OK in all browsers tested, 
this is really a new page def, and code proximity is much better here.
Dialog-box colors are from bg/fg user settings for entire viewer page.
Page or text scrolls may be needed for mobile and/or very large fonts.
Parts of the following are also used for the Note display in [2.3].
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''-->

<style>

/* Modal-dialog display: full page, background */
.InfoDisplay {
    display: none;           /* hidden initially */
    position: fixed;         /* stay in place */
    z-index: 1;              /* open div on top: overlay */
    left: 0;                 /* fill entire page */
    top: 0;
    width: 100%;            /* full width+height */
    height: 100%;
    overflow: auto;          /* enable full-page vertical scrolls, if ever? */

    /* [2.3 now customizable */
    background-color: rgb(0, 0, 0);                      /* fallback color: blank bg */
    background-color: rgba(0, 0, 0, 0.45);   /* black, with opacity: dim bg */
}


/* Modal-dialog box: text + button content on page */
.InfoBox {
    margin: 8px auto;        /* top+bottom, right+left; offset from top, centered */
    padding: 4px;            /* top offset can't be much on mobile */
    padding-left: 20px;      /* margin=outside, padding=inside */
    border: thin solid;      /* #888 color nice, unless same as bg */
    width: 80%;             /* up to this much of the overlay/page */
    max-width: 400px;        /* but not too wide on desktop or landscape */
    border-radius: 6px;      /* rounded borders (for the kids) */

    /* [2.3] colorize dialog per configs */
    color:            wheat;       /* foreground: text (not OK) */
    background-color: black;       /* background: full widget (not OK) */
    border-color:     white;       /* dialog (and OK button) border */
}


/* Modal-dialog message: preformatted text in box */
.InfoText {
    overflow: auto;          /* enable text-area horizontal scrolls, if needed */

    /* [2.3] inherit colors from InfoBox dom parent, not page or 'initial' */
    color: inherit;
    background-color: inherit;
}


/* Modal-dialog close: OK button in box */
.InfoClose {
    display: block;
    margin: auto;            /* centered in box */
    margin-bottom: 6px;      /* but hug the box bottom */

    /* [2.2] customize to make larger on iOS */
    padding: 2px 8px 2px 8px;
    font-size: 0.8em;

    /* [2.3] use page's bg/fg colors, but inherit border from InfoBox dom parent */
    border-color: inherit;
    border-radius: 6px;      /* round corners to match popup, while we're at it */
}

.InfoClose:hover,
.InfoClose:focus {
    cursor: pointer;         /* nothing fancy: mobiles may botch hover */
}

</style>



<!--'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
HTML: make elements that use CSS and are referenced in JavaScript.
Build a div that overlays entire page, to be shown/hidden in demand.
This essentially defines a new page, but it looks like a popup box.
Fails for big fonts: <span id=infoClose class=InfoClose>&times;</span>
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''-->

<!-- Modal dialog display: full-page container -->
<div id=infoDisplay class=InfoDisplay>

    <!-- Modal dialog box: text + close content -->
    <div class=InfoBox>
        <pre    id=infoText  class=InfoText>Replace me on opens...</pre>
        <button id=infoClose class=InfoClose>&nbsp;OK&nbsp;</button>
    </div>

</div> 



<!--'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
JavaScript: must be after HTML to cache elements in globals on load
(or: run these from an onload event handler for DOM-element access).
showInfo() is called on filename tap from imageInfoDialog() above, 
which formats the message text, and documents this dialog further.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''-->

<script type="text/javascript">    /* ok anywhere in page, type optional */

// Get the full modal display <div> in a global
var infodisplay = document.getElementById('infoDisplay');

// Get the text-message element to be replaced
var infotext = document.getElementById('infoText');

// Get the OK-button element that closes the display
// or document.getElementsByClassName('InfoClose')[0]
var infoclose = document.getElementById('infoClose');


// On OK click/tap, close entire modal overlay
infoclose.onclick = function() {
    infodisplay.style.display = 'none';       // hide/close display
}


// On clicks outside the box, close entire modal overlay
window.addEventListener('click', function(event) {
    // 
    // not window.onclick = function(){}: also for Note overlay
    // but iOS still doesn't fire this with the new coding...
    //
    if (event.target == infodisplay) {        // if tap bg, not box
        infodisplay.style.display = 'none';
    }
});


// On filename clicks, open the modal display (called above, formerly alert())
function showInfo(message) {
    infotext.innerHTML = message;
    infodisplay.style.display = 'block';      // show/popup display
}

</script>





<!--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~-->
<!-- NOTE POPUP: custom modal dialog, via div overlay+opacity [2.3] -->
<!--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~-->


<!--'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
CSS: largely reused from Info popup above - see it for more intro docs.
Dialog-box colors are from bg/fg user settings for entire viewer page.
Page or text scrolls may be needed for mobile and/or very large fonts.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''-->

<style>

/* Modal-dialog display: full page, background */
    /* Reuse .InfoDisplay for overlay: identical CSS */


/* Modal-dialog box: text + button content on page */
.NoteBox {                         /* margin=outside, padding=inside */
    padding: 4px 20px 4px 20px;    /* top, right, bottom, left */
    border: thin solid;            /* #888 color nice, unless same as bg */
    border-radius: 6px;            /* rounded borders (for the kids) */
    position: absolute;            /* anchor to page bottom (for up-swipe) */
    bottom: 8px;
    left:  15%;      /* box is what's left, large=good for mobile */
    right: 15%;      /* e.g., 15 percent leaves 70 percent for box */ 
    max-height: 100%;             /* overflow works on sized block elms like <p> */
    overflow: auto;                /* vscroll notes too wordy for the page */ 

    /* [2.3] colorize dialog per configs: border doesn't work in a mixin class here */
    color:            wheat;       /* foreground: text (not OK) */
    background-color: black;       /* background: full widget (not OK) */
    border-color:     white;       /* dialog (and OK button) border */
}

    /* Punt: no longer centered on page -> [max-width: 600px;] */
    /* Punt: not needed and fails on IE<9 -> [transform: translate(-25%, 0);] */
    /* Punt: not centered -> [margin: 8px 20% 8px 20%; min-width: 53.5%;] */


/* Modal-dialog message: collapsed <p> text in box */
.NoteText {                        
    /* != InfoText: hscroll irrelevant here for <p>, and vscroll via NoteBox div */

    /* [2.3] inherit colors from NoteBox dom parent, not page or 'initial' */
    color: inherit;
    background-color: inherit;
}


/* Modal-dialog close: OK button in box */
    /* Reuse .InfoClose for OK: identical CSS, but inherits bd color from NoteBox here */

</style>



<!--'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
HTML: make elements that use CSS and are referenced in JavaScript.
Build a div that overlays entire page, to be shown/hidden in demand.
This essentially defines a new page, but it looks like a popup box.
The <p> doesn't support paragraph breaks: use <br><br> in the insert.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''-->

<!-- Modal dialog display: full-page container -->
<div id=noteDisplay class=InfoDisplay>

    <!-- Modal dialog box: text + close content -->
    <div class=NoteBox>
        <p      id=noteText  class=NoteText>Replace me on opens...</p>
        <button id=noteClose class=InfoClose>&nbsp;OK&nbsp;</button>
    </div>

</div> 



<!--'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
JavaScript: must be after HTML to cache elements in globals on load
(or: run these from an onload event handler for DOM-element access).
showNote() is called on Note tap and from up-swipe handler above.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''-->

<script type="text/javascript">    /* ok anywhere in page, type optional */

// Nit: this code is pointless but harmless for note-less galleries
// (i.e., !ENABLENOTES); adding a nesting level seems pointless too

// Get the full modal display <div> in a global
var notedisplay = document.getElementById('noteDisplay');

// Get the text-message element to be replaced
var notetext = document.getElementById('noteText');

// Get the OK-button element that closes the display
var noteclose = document.getElementById('noteClose');

// The note's file's content from Python, Unicode decoded and JS+HTML escaped
var notecontent = '';

// Distinguish if no real note: don't waste users' time; noteBtn may be deleted!
if (true && !notecontent) {
    document.getElementById("noteBtn").style["text-decoration"] = "line-through";
}


// On OK click/tap, close entire modal overlay
noteclose.onclick = function() {
    notedisplay.style.display = 'none';       // hide/close display
}


// On clicks outside the box, close entire modal overlay
window.addEventListener('click', function(event) {
    // 
    // not window.onclick = function(){}: also for Info overlay
    //
    if (event.target == notedisplay) {        // if tap bg, not box
        notedisplay.style.display = 'none';
    }
});


// On Note and up-swipe, open the modal display 
function showNote() {
    if (!notecontent)
        noteText.innerHTML = '(No note)';
    else
        notetext.innerHTML = notecontent;     // text from imagename.note file
    notedisplay.style.display = 'block';      // show/popup display (inline- same)
}

</script>


</body>
</html>



[Home page] Books Code Blog Python Author Train Find ©M.Lutz