Storybook V2 Comic Book Builder: Part 2, The Document Generator

Written by bobnoxious | Published 2023/03/23
Tech Story Tags: web-development | php-web-development | javascript | responsive-web-design | scifi | web-accessibility | fiction | comic-novel

TLDRA sequentially paginated, accessible, performant, responsive design, illustrated serial content document generator, aka “my comic book builder”. The basic concept for the comic builder is to first gather the materials which will constitute the content of the comic from the nominal author by either form text entry or file selection and upload. Then these components, the comic content, is assembled to generate the comic book document.via the TL;DR App

A sequentially paginated, accessible, performant, responsive design, illustrated serial content document generator, aka “my comic book builder.”

As we said in Part 1, the basic concept for the comic builder is to first gather the materials which will constitute the content of the comic from the nominal author by either form text entry or file selection and upload. Then these components, the comic content, is assembled to generate the comic book document, and this is the stage of the Builder process we will discuss in this article.

Let’s begin by looking at what the content-gathering stage has prepared for the generator stage.

The Content Repository or “Comic Folder”

The comic book contents have been collated and stored in a folder named after the comic. The folder also contains a number of subfolders that also contain content, and it also contains some configuration files, and some image content. Here next are the folder structure and main folder content.

In addition to the subfolders above that the upload functions have created, the main comic panel images, that is the page images, are stored in this folder. Our image uploading functions only upload one pilot image, a JPG or PNG for static images, and each pilot image is then duplicated into fifteen comic page images of three formats (JPG, WEBP, and AVIF). The first two panels or page images of our comic result in the thirty files we have saved above. Of note here is that there are two mySQL databases involved in all of this. The first one stores some particular data about the comic itself, its title, for example, and it is intended as a permanent database for use by the comics gallery. The second mySQL database contains an entry for each of the pilot images. The builder code in the makecomic.php program, which we will discuss here later, will use the pilot file entry from the database to parse and assemble the other format image files into the comic HTML.

Shown here next is the bottom of the comic folder contents, which we can see includes the webcomic page background image and the web gallery card image as well as three PHP configuration files.

We saw the contents of the main configuration file (Hyenas2.php) in the first article as an un-minified listing. Here next is the contents of our Hyenas2FONTS.php file which specifies our choice of caption fonts and caption panel colors. A similar file was generated for the alternate panel captions and saved as Hyenas2AFONTS.php.

<?php $_SESSION["hfontinfo"] = '@font-face {font-family: "MerriweatherSans-ExtraBold"; src: url("./Fonts/MerriweatherSans-ExtraBold.ttf") format("truetype");} @media screen and (max-width: 576px) {   h1 { font: 5vw MerriweatherSans-ExtraBold; color: #000000;} h2 { font: 4vw MerriweatherSans-ExtraBold; color: #000000;} h3 { font: 3.5vw MerriweatherSans-ExtraBold; color: #000000;}} @media screen and (min-width: 577px) and (max-width: 1079px) {h1 { font: 4vw MerriweatherSans-ExtraBold; color: #000000;} h2 { font: 3.5vw MerriweatherSans-ExtraBold; color: #000000;} h3 { font: 3vw MerriweatherSans-ExtraBold; color: #000000;}}  @media screen and (min-width: 1080px) {h1 { font: 3vw MerriweatherSans-ExtraBold; color: #000000;} h2 { font: 2.5vw MerriweatherSans-ExtraBold; color: #000000;} h3 { font: 2.2vw MerriweatherSans-ExtraBold; color: #000000;}}';$_SESSION["pfontinfo"] = '@font-face {font-family: "Merriweather-Regular"; src: url("./Fonts/Merriweather-Regular.ttf") format("truetype");} @media screen and (max-width: 576px) {   .card-body, p { font: 3.5vw Merriweather-Regular; color: #000000;}} @media screen and (min-width: 577px) and (max-width: 1079px) { .card-body, p { font: 2.5vw Merriweather-Regular; color: #000000;}}  @media screen and (min-width: 1080px) { .card-body, p { font: 2vw Merriweather-Regular; color: #000000;}}';$_SESSION["cbinfo"] = '{background: #FFC0CB;}';?>

Besides the files we have discussed above, there are a number of subfolders that also contain content files. One of these folders is named captions and this screen next below shows a part of its directory or folder listing.

The folder contents shown here are text files, each of which corresponds to a particular page image panel. Here for example is the content of the first caption file in our folder.

<h1>Hyenas 2</h1>
<p>"¡Hola chicas, y buenas noches!"<br>
(Hello girls, and good evening!)<br>
"I am really so glad you could all be here this evening. Thank you all for being here."</p>

As shown in this example above, some support is provided for a few rudimentary HTML text format tags, h1, h2, h3, p, br. b, and I.

In a similar fashion, we have an altText folder which contains an alt text file for each page image named to correspond with each image as was shown with the captions files above. So this main comic folder contains the main sequence page images, some config files, and two folders, captions and altText, that contain those two additional main image values for each panel image.

We know that each panel or page image may also have alternate content to display, and the data for that content is contained in three additional comic subfolders, which are the altImgs folder along with its associated altImgText and altCaptions folders. These last two subfolders serve the altImgs image folder content in the same fashion that the altText and captions folders serve the main content images. Hence the altCaptions folder contains a set of captions that correspond to the altImgs files, and similarly, each altImgs file has a corresponding altImgText file for its alt text attribute.

For our Hyenas2 example comic we have this altImgs folder content.

Our example comic has an alternate image for two of its page image panels. The Builder stores animations in one unscaled size and in three file formats, GIF, WEBP, and AVIF as shown above for an animation panel that is displayed as an alternate image for our 014_wormhole main panel or page image. Similarly we also have three image format files at one size for our other comic animation, 101_FeralPencil as shown in the directory above. Our example comic also has an MP3 audio track which plays as alternate content for this last panel, 101_FeralPencil.mp3, and there is also an audio transcript text file, 101_FeralPencil.mpt, present. In addition to these altImgs files there is a folder with two caption text files, one for each alternate image, and two altImgText files for the altImgs alt text attribute.

All of this preceding content was uploaded and collated or generated by the Builder first stage processes. Now let’s use that data to create a comic.

The makecomic.php Document Generator

The last program in the data gathering stage is also the first program in the document generator stage of our Builder. This is a PHP program name Yield.php which also handles such tasks as creating and perhaps downloading a ZIP archive of the comic for the user, maintaining the mySQL databases used by the Builder and by the comic gallery pages, and finally closing the PHP $_SESSION itself. Yield invokes a program named getConfigValues.php to retrieve the comic configuration data and it also invokes a program named downloadZip.php to download the ZIP archive. But what is of most import here is the makecomic.php program invoked by Yield to actually generate our comic HTML from the data we have gathered. Here next below is the makecomic.php program code.

<?php
/*
* makeComic.php actually builds and then saves the web comic
* using _SESSION data values and database values
*/
// disable error reporting for production code
error_reporting(E_ALL);
ini_set('display_errors', TRUE);

// be sure we are here by a POST request
if (($_SERVER["REQUEST_METHOD"] == "POST") && (isset($_POST['Comicname'])) && ($_POST['Comicname'] != '')) {
// Start session
//if ((session_status() == PHP_SESSION_NONE) || (session_status() !== PHP_SESSION_ACTIVE)) {
	session_name("Storybook");
	//if(isset($_COOKIE['Storybook'])) {
	//	session_id($_COOKIE['Storybook']);}
	require_once("/var/www/session2DB/Zebra.php");
//}
$rindex = 0;
$showFigCntr = false;
$Comicname = $_SESSION['Comicname'];
$siteurl = $_SESSION['siteurl'];
$Comics = '/var/www/Comics/htdocs/';
if(is_file($Comics.$Comicname.'/'.$Comicname.'FONTS.php')) {
	include($Comics.$Comicname.'/'.$Comicname.'FONTS.php');
}
if(is_file($Comics.$Comicname.'/'.$Comicname.'AFONTS.php')) {
	include($Comics.$Comicname.'/'.$Comicname.'AFONTS.php');
}
// reinsert head here
//require("./headClip.php");

// set up to buffer output
ob_start();
// begin generated web page content
$head1 = <<< EOT1
<!DOCTYPE html>
<html class="no-js" lang="en">
<head>
	<meta charset="utf-8">
    <meta http-equiv="x-ua-compatible" content="ie=edge">
	<meta name="viewport" content="width=device-width, initial-scale=1.0">
EOT1;
echo $head1;
echo '<title>'.$_SESSION['cardTitle'].'</title>';
echo '<script> //if we have javascript then remove no-js class from html\n'.
	'document.documentElement.classList.remove("no-js")\n'.
	'</script>';
echo '<meta NAME="Last-Modified" CONTENT="'. date ("F d Y H:i.", getlastmod()).'">';
echo '<meta name="copyright" content="Site design and code Copyright 2021, 2023 by IsoBlock.com, Artistic Content Copyright 2021, 2023 by '.$_SESSION['artistname'].'">';
$head3 = <<< EOT3
	<meta name="description" content="A Storybook Web Comic">
	<meta name="generator" content ="IsoBlock Synthetic Reality Storybook Comic Book Builder">
	<meta name="author" content="Bob Wright">
	<meta name="keywords" content="comics,images,art,graphics,illustration">
	<meta name="rating" content="general">
	<meta name="robots" content="index, follow"/> 
EOT3;
echo $head3;
if((is_file($Comics.$Comicname.'OGIMG.jpg')) || (is_file($Comics.$Comicname.'OGIMG.webp'))) {
	echo '<meta property="og:url" content="'.$_SESSION['siteurl'].$Comicname.'.html" >';
	echo '<meta property="og:type" content="website" >';
	echo '<meta property="og:title" content= "'.$_SESSION['cardTitle'].'" >';
	echo '<meta property="og:description" content="A comic by '.$_SESSION['artistname'].'">';
	if(is_file($Comics.$Comicname.'OGIMG.webp')) {
		echo '<meta property="og:image" content="'.$siteurl.$Comicname.'OGIMG.webp" >';
		echo '<meta property="og:image:type"       content="image/webp" >';
	} else {
		echo '<meta property="og:image" content="'.$siteurl.$Comicname.'OGIMG.jpg" >';
		echo '<meta property="og:image:type"       content="image/jpg" >';
	}	
	echo 
		 '<meta property="og:image:width"      content="1800" >'.
		 '<meta property="og:image:height"     content="960" >';
	echo
		'<!-- <meta property="fb:app_id" content="1297101973789783" > -->';
}
echo '<base href="'.$_SESSION['siteurl'].'">';
echo '<link href="'.$_SESSION['pageURL'].'" rel="canonical" Content-Type="text/html">';
$head4 = <<< EOT4
	<!-- Bootstrap -->
	<link rel="stylesheet" href="./css/bootstrap51.min.css">
    <!--    <link rel="manifest" href="site.webmanifest"> -->
	<link rel="icon" href="./favicon.ico" type="image/ico"/>
	<link rel="shortcut icon" href="./favicon.ico" type="image/x-icon"/>
<style>
* {
 box-sizing: border-box;
}
EOT4;
echo $head4;
if(isset($_SESSION['pfontinfo'])) {
	$pfontinfo = $_SESSION['pfontinfo'];
	echo $pfontinfo;
} else {
$pfinfo = <<< PF
@media screen and (max-width: 576px) {
@font-face {font-family: "RobotoSlab-Regular"; src: url("./Fonts/RobotoSlab-Regular.ttf") format("truetype");} .card-body, p { font: 3.5vw RobotoSlab-Regular; color: #000000;}}
@media screen and (min-width: 577px) and (max-width: 1079px)  {
@font-face {font-family: "RobotoSlab-Regular"; src: url("./Fonts/RobotoSlab-Regular.ttf") format("truetype");} .card-body, p { font: 2.5vw RobotoSlab-Regular; color: #000000;}}
@media (min-width: 1080px) {
@font-face {font-family: "RobotoSlab-Regular"; src: url("./Fonts/RobotoSlab-Regular.ttf") format("truetype");} .card-body, p { font: 2vw RobotoSlab-Regular; color: #000000;}
PF;
echo $pfinfo;
}
if(isset($_SESSION['hfontinfo'])) {
	$hfontinfo = $_SESSION['hfontinfo'];
	echo $hfontinfo;
} else {
$hfinfo = <<< HF
@media screen and (max-width: 576px) {
@font-face {font-family: "Montserrat-Bold"; src: url("./Fonts/Montserrat-Bold.ttf") format("truetype");} h1 { font: 5vw Montserrat-Bold; color: #000000;} h2 { font: 4vw Montserrat-Bold; color: #000000;} h3 { font: 3.5vw Montserrat-Bold; color: #000000;}.xmplc {background: #FFE4E1;}
}
@media screen and (min-width: 577px) and (max-width: 1079px)  {
@font-face {font-family: "Montserrat-Bold"; src: url("./Fonts/Montserrat-Bold.ttf") format("truetype");} h1 { font: 4vw Montserrat-Bold; color: #000000;} h2 { font: 3.5vw Montserrat-Bold; color: #000000;} h3 { font: 3vw Montserrat-Bold; color: #000000;}.xmplc {background: #FFE4E1;}
}
@media (min-width: 1080px) {
@font-face {font-family: "Montserrat-Bold"; src: url("./Fonts/Montserrat-Bold.ttf") format("truetype");} h1 { font: 3vw Montserrat-Bold; color: #000000;} h2 { font: 2.5vw Montserrat-Bold; color: #000000;} h3 { font: 2.2vw Montserrat-Bold; color: #000000;}.xmplc {background: #FFE4E1;}
}
HF;
echo $hfinfo;
}
if(isset($_SESSION['cbinfo'])) {
	$cbinfo = $_SESSION['cbinfo'];
	echo '.xmplc ' . $cbinfo;
} else {
$cbinfo = <<< CB
.xmplc {background: #FFE4E1;}
CB;
echo $cbinfo;
}
if(isset($_SESSION['apfontinfo'])) {
	$apfontinfo = $_SESSION['apfontinfo'];
	echo '.xmpla { ' . $apfontinfo . ' }';
}
if(isset($_SESSION['ahfontinfo'])) {
	$ahfontinfo = $_SESSION['ahfontinfo'];
	echo '.xmpla { ' . $ahfontinfo . ' }';
}
if(isset($_SESSION['acbinfo'])) {
	$acbinfo = $_SESSION['acbinfo'];
	echo '.xmpla ' . $acbinfo;
}
$head4a = <<< EOT4a
.playButton {
 display: block;
 background-color: rgba(128, 128, 128, .8);
 z-index: 30;
 opacity: 1;
 border: .3vw solid cyan;
}
.MP3Overlay {
 display: block;
 background-color: rgba(128, 128, 128, .8);
 z-index: 30;
 opacity: 1;
 border: .3vw solid cyan;
}
.transcriptControl {
 display: block;
 background-color: rgba(128, 128, 128, .8);
 z-index: 30;
 opacity: 1;
 border: .3vw solid cyan;
}
.imgblock img {
  position: relative;
  left: 50%;
  transform: translateX(-50%);
}
a:focus, div:focus, img:focus {
  border: .05vw solid black;
  outline-style: solid;
  outline-color: GreenYellow;
  outline-width: .5vw;
}
.avif {
 display: block;
}
.no-avif {
display: block;
}
EOT4a;
echo $head4a;
if(is_file($Comics.$Comicname.'/'.$Comicname.'BKGND-s.jpg')) {
	$_SESSION['bkgndImage'] = $Comicname.'BKGND-s.jpg';
	$bkgndImage = $Comicname.'BKGND';
/*
* These values are based upon the Bootstrap breakpoint choices
* breakpoint names	X- Small Small   Medium  Large   X- Large XX- Large
* breakpoints       <576px	≥576px	≥768px	≥992px	≥1200px	≥1400px
* .container		100%	540px	720px	960px	1140px	1320px
* image sizes				576px	768px	992px	1200px	1400px
*/
$headC1 = <<< EOTC1
@media screen and (max-width: 576px) {
#container:before {background-image: url("./$Comicname/$bkgndImage-s.webp");}
}
@media screen and (min-width: 577px) and (max-width: 768px)  {
#container:before {background-image: url("./$Comicname/$bkgndImage-m.webp");}
}
@media screen and (min-width: 769px) and (max-width: 992px)  {
#container:before {background-image: url("./$Comicname/$bkgndImage-l.webp");}
}
@media screen and (min-width: 993px) and (max-width: 1200px)  {
#container:before {background-image: url("./$Comicname/$bkgndImage-x.webp");}
}
@media screen and (min-width: 1201px) {
#container:before {background-image: url("./$Comicname/$bkgndImage-X.webp");}
}
#container:before {
	content: ' ';
	display: block;
	position: fixed;
	left: 0;
	top: 0;
	width: 100%;
	height: 100%;
	z-index: -1;
	opacity: 0.6;
	background-position: top center;
	background-repeat: no-repeat;
	-ms-background-size: 100% 100%;
	-o-background-size: 100% 100%;
	-moz-background-size: 100% 100%;
	-webkit-background-size: 100% 100%;
	background-size: 100% 100%;
}
EOTC1;
echo $headC1;
} else {
$headC1 = <<< EOTC1
#container:before {
	background-color: #b0bec5;
}
EOTC1;
echo $headC1;
}
$head55 = <<< EOT55
</style>
</head>
<!-- End of the HTML head section-->
<!-- =========================== -->
//<!-- +++++++++++++++++++++++ -->
//<!-- Build out the page -->
<body class="container">
EOT55;
echo $head55;
echo
	'<h1 class="visually-hidden">'.$_SESSION['cardTitle'].'</h1>';
$head6 = <<<EOT6
<!--#include file="./includes/browserupgrade.ssi" -->
<main  id="container" class="row d-flex align-items-start">
<article class="imgblock col-12 justify-content-center">
   <nav tabindex="-1" class="row fixed-top col-2 d-flex justify-content-center" style="background-color: rgba(255,80,0,.4);"><a tabindex="0" class="d-flex justify-content-center" title="jump to the Comics gallery" href="./Comics.php">
		<svg version="1.0" xmlns="http://www.w3.org/2000/svg"  id="comicsHome" class="bi-layout-wtf"
		 width="4vw" height="4vw" viewBox="0 0 60.000000 42.000000" stroke="blue" stroke-width="10">
		<g transform="translate(0.000000,42.000000) scale(0.100000,-0.100000)">
		<path d="M467 413 c-2 -2 -32 -4 -67 -4 -53 -1 -67 -5 -87 -26 l-25 -24 -43
		21 c-36 17 -58 20 -136 18 l-93 -3 3 -150 c1 -82 6 -153 10 -158 4 -4 24 -1
		44 8 41 17 85 19 107 5 11 -7 6 -10 -22 -10 -40 0 -121 -31 -133 -51 -9 -13
		-1 -12 75 16 51 19 97 14 149 -16 l34 -20 22 27 c20 25 25 26 92 22 48 -2 78
		-9 90 -20 22 -20 73 -34 73 -20 0 5 -24 21 -53 36 -43 21 -69 26 -127 26 -65
		0 -71 2 -59 16 13 16 35 16 197 -2 l52 -6 0 79 c0 43 5 109 10 147 6 38 7 73
		3 77 -9 7 -110 18 -116 12z m88 -70 c-4 -26 -9 -86 -11 -133 l-3 -85 -28 1
		c-15 1 -59 7 -97 14 -64 11 -70 11 -97 -9 -26 -20 -29 -20 -38 -3 -9 15 -10
		15 -11 -5 0 -27 2 -27 -61 -7 -48 16 -112 15 -152 -1 -16 -7 -17 3 -17 129 l0
		136 69 0 c70 0 149 -21 163 -43 4 -7 8 -40 8 -74 0 -35 4 -63 10 -63 6 0 10
		30 10 69 0 52 5 74 19 92 17 23 26 24 131 27 l112 3 -7 -48z"/></a>
		</g></svg>
    </nav>

<!-- ++++++++++++++++++++ -->
<!--  build comic pages -->
<!-- ++++++++++++++++++++ -->
EOT6;
echo $head6;
// Include ComicImages class
	/*	TABLE `comicimagedata`
	 `comic_id`
	 `comic_name`
	 `oauth_id`
	 `image_hash`
	 `image_key`
	 `filename`
	 `filetype`
	 `width`
	 `height`
	 `created`
	*/
require("/var/www/includes/ComicImages.class.php");
    $comic = new ComicImages();
$imageList = $comic->listComicImages($Comicname);
$_SESSION['imageList'] = count($imageList);
//echo '<p>'; echo var_dump($imageList); echo '</p>';
/*
$constring = print_r($imageList);
echo '<script>console.info('.$constring.')</script>';
*/
for ($i = 0; $i <  count($imageList); $i++) {
	$imageIndex=key($imageList);
	$imageKey=$imageList[$imageIndex];
	if ($imageKey<> ' ') {
	   //echo $imageIndex ." = ".  $imageKey ." <br> ";
	   $imageIndex = $imageIndex + 1;
		//echo $val .".jpg<br> ";
		//$imageKey = $val;
 	// get image data from the database
    $imageData = $comic->returnComicRecord($imageKey);
    // Store image data in the session
    $_SESSION['imageData'] = $imageData;
	//echo '<p>'; echo var_dump($imageData); echo '</p>';
	$imageKey = $_SESSION['imageData']['image_key'];
	$filename = $_SESSION['imageData']['filename'];
	$xmlFile = pathinfo($filename);
	$filenameNoExt = $xmlFile['filename'];
	$filetype = $xmlFile['extension'];
	$_SESSION['filenameNoExt'] = $filenameNoExt;
	$width = $_SESSION['imageData']['width'];
	$height = $_SESSION['imageData']['height'];
	$created = $_SESSION['imageData']['created'];
	$FigDesc = 'This is '.$filenameNoExt.'.';
	//$FigCntr = '[ '.$imageIndex.' of '.count($imageList).' ]';
	$FigCntr = '[&nbsp;p&emsp;'.$imageIndex.'&nbsp;]';
	// now have an array of values for this image as large jpg
	// we generate the filenames for our other sizes and formats

	// ----------------------------------
	// process the source image
	
	// see if we have alt text for this image
	// -------------------------------------------------------
	// !!! every image should have an alt text description !!!
	// -------------------------------------------------------
	$altText = $FigDesc; // fallback to file name as alt text
	if(is_dir($Comics.$Comicname.'/altText/')) {
		$altTextDir = $Comics.$Comicname.'/altText/';
		$altTextFile = $filenameNoExt.'.txt';
		// check for txt file
		if(is_file($altTextDir.$altTextFile)) {
			$altText = (file_get_contents($altTextDir.$altTextFile));
			$_SESSION['altText'] = $altText;
			$altDesc = 'There is an alt text file named '.$altTextDir.$altTextFile.'.';
			$_SESSION['altDesc'] = $altDesc;
		}
	}
	// see if we have alt content for this image
	$_SESSION["playGIF"] = '';
	$playGIF = $_SESSION["playGIF"];
	$_SESSION["playMP3"] = '';
	$playMP3 = $_SESSION["playMP3"];
	$_SESSION["playJPG"] = '';
	$playJPG = $_SESSION["playJPG"];

	if(is_dir($Comics.$Comicname.'/altImgs/')) { // if alternate content set image classes
		$altImgsDir = $Comics.$Comicname.'/altImgs/';
		$_SESSION["altImgsDir"] = $altImgsDir;

	$altImgsDirFiles = dir($altImgsDir) or die; // loop thru directory
	while (false !== ($f = $altImgsDirFiles->read())) { // get each filetype for this image
	if (strpos($f, $filenameNoExt) !== false) {
		if (strpos($f, '.gif') !== false) {
			$_SESSION["playGIF"] = 'playGIF';
			$_SESSION["hitGIF"] = $imageIndex;
		}
		if (strpos($f, '.jpg') !== false) {
			$_SESSION["playJPG"] = 'playJPG';
			$_SESSION["hitJPG"] = $imageIndex;
			$_SESSION["playGIF"] = 'playGIF';
		}
		if (strpos($f, '.mp3') !== false) {
			$_SESSION["playMP3"] = 'playMP3';
			$_SESSION["hitMP3"] = $imageIndex;
			$_SESSION["playGIF"] = 'playGIF';
		}
	}
	}
$altImgsDirFiles->close();
	}	
	 // image class variables
	if(($_SESSION["playGIF"]) != '') {
	$playGIF = $_SESSION["playGIF"].' ';}
	if(($_SESSION["playMP3"]) != '') {
	$playMP3 = $_SESSION["playMP3"].' ';}

if ($_SESSION['playGIF'] != '') { // there is alternate content
$clickMe = '<span id="c'.$imageIndex.'" class="clickMeOverlay">';
echo $clickMe;}

// process the image
if(!(($width == 1) && ($height == 1))) { // display image if not the 1px by 1px "no image image" used for caption only
// $imgcode1 = '';
if($filetype == 'gif') {
$srcset = array();
$ComicsDirFiles = dir($Comics.$Comicname) or die; // loop thru directory
while (false !== ($f = $ComicsDirFiles->read())) { // get each filetype for this image
  if (strpos($f, $filenameNoExt) !== false) {
	if (strpos($f, '.avif') !== false) {
	 $srcset[0] = $f;}
	if (strpos($f, '.webp') !== false) {
	 $srcset[1] = $f;}
	if (strpos($f, '.gif') !== false) {
	 $srcset[2] = $f;}
  }
}
$ComicsDirFiles->close();
$imgcode1 = '<picture class="'.$playGIF.$playMP3.'src avif" id="pic'.$imageIndex.'">';
$imgcode1 .= '	<source srcset = "./'.$Comicname.'/'.$srcset[0].'" media = "(min-width: 320px)" type = "image/avif">';
$imgcode1 .= '	<source srcset = "./'.$Comicname.'/'.$srcset[1].'" media = "(min-width: 320px)" type = "image/webp">';
$imgcode1 .= '	<source srcset = "./'.$Comicname.'/'.$srcset[2].'" media = "(min-width: 320px)" type = "image/gif">';
$imgcode1 .= '<img tabindex="0" id="s'.$imageIndex.'" class="'.$playGIF.$playMP3.'src" src = "./'.$Comicname.'/'.$srcset[1].'" alt = "'.$altText.'">';
$imgcode1 .= '</picture>';
$_SESSION["imgcode1"] = 'got $imgcode1';

$imgcode1na = '<picture class="'.$playGIF.$playMP3.'src no-avif" id="pic'.$imageIndex.'">';
// $imgcode1na .= '	<source srcset = "./'.$Comicname.'/'.$srcset[0].'" media = "(min-width: 320px)" type = "image/avif">';
$imgcode1na .= '	<source srcset = "./'.$Comicname.'/'.$srcset[1].'" media = "(min-width: 320px)" type = "image/webp">';
$imgcode1na .= '	<source srcset = "./'.$Comicname.'/'.$srcset[2].'" media = "(min-width: 320px)" type = "image/gif">';
$imgcode1na .= '<img tabindex="0" id="s'.$imageIndex.'" class="'.$playGIF.$playMP3.'src" src = "./'.$Comicname.'/'.$srcset[1].'" alt = "'.$altText.'">';
$imgcode1na .= '</picture>';
$_SESSION["imgcode1na"] = 'got imgcode1na';
} // else not a GIF
/*
* These values are based upon the Bootstrap breakpoint choices
* breakpoint names	X- Small Small   Medium  Large   X- Large XX- Large
* breakpoints       <576px	≥576px	≥768px	≥992px	≥1200px	≥1400px
* .container		100%	540px	720px	960px	1140px	1320px
* image sizes				576px	768px	992px	1200px	1400px
*/
if($filetype == 'jpg') {
$srcset = array();
$ComicsDirFiles = dir($Comics.$Comicname) or die; // loop thru directory
while (false !== ($f = $ComicsDirFiles->read())) { // get each filetype for this image
  if (strpos($f, $filenameNoExt) !== false) {
	if (strpos($f, '.avif') !== false) {
	 if (strpos($f, '-s-') !== false) {
	 $srcset[0] = $f;}
	 if (strpos($f, '-m-') !== false) {
	 $srcset[1] = $f;}
	 if (strpos($f, '-l-') !== false) {
	 $srcset[2] = $f;}
	 if (strpos($f, '-x-') !== false) {
	 $srcset[3] = $f;}
	 if (strpos($f, '-X-') !== false) {
	 $srcset[4] = $f;}
	}
	if (strpos($f, '.webp') !== false) {
	 if (strpos($f, '-s-') !== false) {
	 $srcset[5] = $f;}
	 if (strpos($f, '-m-') !== false) {
	 $srcset[6] = $f;}
	 if (strpos($f, '-l-') !== false) {
	 $srcset[7] = $f;}
	 if (strpos($f, '-x-') !== false) {
	 $srcset[8] = $f;}
	 if (strpos($f, '-X-') !== false) {
	 $srcset[9] = $f;}
	}
	if (strpos($f, '.jpg') !== false) {
	 if (strpos($f, '-s-') !== false) {
	 $srcset[10] = $f;}
	 if (strpos($f, '-m-') !== false) {
	 $srcset[11] = $f;}
	 if (strpos($f, '-l-') !== false) {
	 $srcset[12] = $f;}
	 if (strpos($f, '-x-') !== false) {
	 $srcset[13] = $f;}
	 if (strpos($f, '-X-') !== false) {
	 $srcset[14] = $f;}
	}
  }
}
$ComicsDirFiles->close();
//$srcImgHdr = '<img class="src" id="" style="display: block;"';
// <picture class="src" id="s$imageIndex" style="display: block;">
$imgcode1 = '<picture class="'.$playGIF.$playMP3.'src avif" id="pic'.$imageIndex.'">';
$imgcode1 .= '	<source srcset = "./'.$Comicname.'/'.$srcset[0].'" media = "(max-width: 576px)" type = "image/avif">';
$imgcode1 .= '	<source srcset = "./'.$Comicname.'/'.$srcset[1].'" media = "((min-width: 577px) and (max-width: 768px))" type = "image/avif">';
$imgcode1 .= '	<source srcset = "./'.$Comicname.'/'.$srcset[2].'" media = "((min-width: 769px) and (max-width: 992px))" type = "image/avif">';
$imgcode1 .= '	<source srcset = "./'.$Comicname.'/'.$srcset[3].'" media = "((min-width: 993px) and (max-width: 1200px))" type = "image/avif">';
$imgcode1 .= '	<source srcset = "./'.$Comicname.'/'.$srcset[4].'" media = "(min-width: 1201px)" type = "image/avif">';
$imgcode1 .= '	<source srcset = "./'.$Comicname.'/'.$srcset[5].'" media = "(max-width: 576px)" type = "image/webp">';
$imgcode1 .= '	<source srcset = "./'.$Comicname.'/'.$srcset[6].'" media = "((min-width: 577px) and (max-width: 768px))" type = "image/webp">';
$imgcode1 .= '	<source srcset = "./'.$Comicname.'/'.$srcset[7].'" media = "((min-width: 769px) and (max-width: 992px))" type = "image/webp">';
$imgcode1 .= '	<source srcset = "./'.$Comicname.'/'.$srcset[8].'" media = "((min-width: 993px) and (max-width: 1200px))" type = "image/webp">';
$imgcode1 .= '	<source srcset = "./'.$Comicname.'/'.$srcset[9].'" media = "(min-width: 1201px)" type = "image/webp">';
$imgcode1 .= '	<source srcset = "./'.$Comicname.'/'.$srcset[10].'" media = "(max-width: 576px)" type = "image/jpg">';
$imgcode1 .= '	<source srcset = "./'.$Comicname.'/'.$srcset[11].'" media = "((min-width: 577px) and (max-width: 768px))" type = "image/jpg">';
$imgcode1 .= '	<source srcset = "./'.$Comicname.'/'.$srcset[12].'" media = "((min-width: 769px) and (max-width: 992px))" type = "image/jpg">';
$imgcode1 .= '	<source srcset = "./'.$Comicname.'/'.$srcset[13].'" media = "((min-width: 993px) and (max-width: 1200px))" type = "image/jpg">';
$imgcode1 .= '	<source srcset = "./'.$Comicname.'/'.$srcset[14].'" media = "(min-width: 1201px)" type = "image/jpg">';
$imgcode1 .= '<img tabindex="0" id="s'.$imageIndex.'" class="'.$playGIF.$playMP3.'src" src="./'.$Comicname.'/'.$srcset[9].'" alt = "'.$altText.'">';
$imgcode1 .= '</picture>';
$_SESSION["imgcode1"] = 'got imgcode1';

$imgcode1na = '<picture class="'.$playGIF.$playMP3.'src no-avif" id="pic'.$imageIndex.'">';
//$imgcode1na .= '	<source srcset = "./'.$Comicname.'/'.$srcset[0].'" media = "(max-width: 576px)" type = "image/avif">';
//$imgcode1na .= '	<source srcset = "./'.$Comicname.'/'.$srcset[1].'" media = "((min-width: 577px) and (max-width: 768px))" type = "image/avif">';
//$imgcode1na .= '	<source srcset = "./'.$Comicname.'/'.$srcset[2].'" media = "((min-width: 769px) and (max-width: 992px))" type = "image/avif">';
//$imgcode1na .= '	<source srcset = "./'.$Comicname.'/'.$srcset[3].'" media = "((min-width: 993px) and (max-width: 1200px))" type = "image/avif">';
//$imgcode1na .= '	<source srcset = "./'.$Comicname.'/'.$srcset[4].'" media = "(min-width: 1201px)" type = "image/avif">';
$imgcode1na .= '	<source srcset = "./'.$Comicname.'/'.$srcset[5].'" media = "(max-width: 576px)" type = "image/webp">';
$imgcode1na .= '	<source srcset = "./'.$Comicname.'/'.$srcset[6].'" media = "((min-width: 577px) and (max-width: 768px))" type = "image/webp">';
$imgcode1na .= '	<source srcset = "./'.$Comicname.'/'.$srcset[7].'" media = "((min-width: 769px) and (max-width: 992px))" type = "image/webp">';
$imgcode1na .= '	<source srcset = "./'.$Comicname.'/'.$srcset[8].'" media = "((min-width: 993px) and (max-width: 1200px))" type = "image/webp">';
$imgcode1na .= '	<source srcset = "./'.$Comicname.'/'.$srcset[9].'" media = "(min-width: 1201px)" type = "image/webp">';
$imgcode1na .= '	<source srcset = "./'.$Comicname.'/'.$srcset[10].'" media = "(max-width: 576px)" type = "image/jpg">';
$imgcode1na .= '	<source srcset = "./'.$Comicname.'/'.$srcset[11].'" media = "((min-width: 577px) and (max-width: 768px))" type = "image/jpg">';
$imgcode1na .= '	<source srcset = "./'.$Comicname.'/'.$srcset[12].'" media = "((min-width: 769px) and (max-width: 992px))" type = "image/jpg">';
$imgcode1na .= '	<source srcset = "./'.$Comicname.'/'.$srcset[13].'" media = "((min-width: 993px) and (max-width: 1200px))" type = "image/jpg">';
$imgcode1na .= '	<source srcset = "./'.$Comicname.'/'.$srcset[14].'" media = "(min-width: 1201px)" type = "image/jpg">';
$imgcode1na .= '<img tabindex="0" id="s'.$imageIndex.'" class="'.$playGIF.$playMP3.'src" src="./'.$Comicname.'/'.$srcset[9].'" alt = "'.$altText.'">';
$imgcode1na .= '</picture>';
$_SESSION["imgcode1na"] = 'got imgcode1';

}
}
$_SESSION["scounter"] = $imageIndex;
echo $imgcode1;
echo $imgcode1na;
	// see if we have an altImg or a soundtrack for this image
	// this alternate image is displayed on a click
	// if present the audio will also play, it is muted by default
if($playGIF != '') {
$imgcode2 = '';
if(is_dir($Comics.$Comicname.'/altImgs/')) {
		$altimgmp3 = $filenameNoExt.'.mp3';
	if(file_exists($Comics.$Comicname.'/altImgs/'.$altimgmp3)) {
		$altImgMP3Desc = 'There is an audio alternate image file named '.$Comicname.'/altImgs/'.$altimgmp3.'.';
	} else { $altImgMP3Desc = 'There is no audio alternate image.';}
	$_SESSION['altImgMP3Desc'] = $altImgMP3Desc;
// transcript text file
if(file_exists($Comics.$Comicname.'/altImgs/'.$filenameNoExt.'.mpt')) { // all audio needs a transcript
	$altMP3Text = (file_get_contents($Comics.$Comicname.'/altImgs/'.$filenameNoExt.'.mpt'));
	$altMP3TextDesc = 'There is an audio transcript file named '.$Comicname.'/altImgs/'.$filenameNoExt.'.mpt';
	$_SESSION['altImgMP3TextDesc'] = $altImgMP3TextDesc;
	} else {
	$_SESSION['altImgMP3TextDesc'] = 'no audio transcript';
	}


// see if we have alt text for this image
	// -------------------------------------------------------
	// !!! every image should have an alt text description !!!
	// -------------------------------------------------------
	$altText = $FigDesc; // fallback to file name as alt text
	if(is_dir($Comics.$Comicname.'/altImgText/')) {
		$altImgTextDir = $Comics.$Comicname.'/altImgText/';
		$altImgTextFile = $filenameNoExt.'.txt';
		// check for txt file
		if(is_file($altImgTextDir.$altImgTextFile)) {
			$altImgText = (file_get_contents($altImgTextDir.$altImgTextFile));
			$_SESSION['altImgText'] = $altImgText;
			$altImgTextDesc = 'There is an alt image text file named '.$altImgTextDir.$altImgTextFile.'.';
			$_SESSION['altImgTextDesc'] = $altImgTextDesc;
		}
	}

// reinsert	<img class="playGIF src" id="apic$imageIndex" style="display: block;"
//$imgcode2 = '';
if(($_SESSION["playGIF"] != '') && ($_SESSION["playJPG"] == '')) { 
// see if we have a GIF alternate image
$asrcset = array();
$altImgsDirFiles = dir($altImgsDir) or die;
while (false !== ($f = $altImgsDirFiles->read())) { // loop thru directory
 if (strpos($f, $filenameNoExt) !== false) {
	if (strpos($f, '.avif') !== false) {
	 $asrcset[0] = $f;}
	if (strpos($f, '.webp') !== false) {
	 $asrcset[1] = $f;}
	if (strpos($f, '.gif') !== false) {
	 $asrcset[2] = $f;}
  }
}
$altImgsDirFiles->close();
// <picture class="playGIF alt" id="apic$imageIndex">
$imgcode2 = '<picture class="'.$playGIF.$playMP3.'alt avif" id="apic'.$imageIndex.'">';
$imgcode2 .= '	<source srcset = "./'.$Comicname.'/altImgs/'.$asrcset[0].'" media = "(min-width: 320px)" type = "image/avif">';
$imgcode2 .= '	<source srcset = "./'.$Comicname.'/altImgs/'.$asrcset[1].'" media = "(min-width: 320px)" type = "image/webp">';
$imgcode2 .= '	<source srcset = "./'.$Comicname.'/altImgs/'.$asrcset[2].'" media = "(min-width: 320px)" type = "image/gif">';
$imgcode2 .= '<img tabindex="0" id="a'.$imageIndex.'" class="'.$playGIF.$playMP3.'alt" src="./'.$Comicname.'/altImgs/'.$asrcset[1].'" alt = "'.$altImgText.'" style="display: none;">';
$imgcode2 .= '</picture>';
$_SESSION["imgcode2"] = 'got imgcode2';

$imgcode2na = '<picture class="'.$playGIF.$playMP3.'alt no-avif" id="apic'.$imageIndex.'">';
// $imgcode2na .= '	<source srcset = "./'.$Comicname.'/altImgs/'.$asrcset[0].'" media = "(min-width: 320px)" type = "image/avif">';
$imgcode2na .= '	<source srcset = "./'.$Comicname.'/altImgs/'.$asrcset[1].'" media = "(min-width: 320px)" type = "image/webp">';
$imgcode2na .= '	<source srcset = "./'.$Comicname.'/altImgs/'.$asrcset[2].'" media = "(min-width: 320px)" type = "image/gif">';
$imgcode2na .= '<img tabindex="0" id="a'.$imageIndex.'" class="'.$playGIF.$playMP3.'alt" src="./'.$Comicname.'/altImgs/'.$asrcset[1].'" alt = "'.$altImgText.'" style="display: none;">';
$imgcode2na .= '</picture>';
$_SESSION["imgcode2na"] = 'got imgcode2na';
}
// else check for images that are not a gif
// see if we have  alternate image
if(($_SESSION["playGIF"] != '') && ($_SESSION["playJPG"] != '')) { 
$asrcset = array();
$altImgsDirFiles = dir($altImgsDir) or die;
while (false !== ($f = $altImgsDirFiles->read())) { // loop thru directory
  if (strpos($f, $filenameNoExt) !== false) {
	if (strpos($f, '.avif') !== false) {
	 if (strpos($f, '-s-') !== false) {
	 $asrcset[0] = $f;}
	 if (strpos($f, '-m-') !== false) {
	 $asrcset[1] = $f;}
	 if (strpos($f, '-l-') !== false) {
	 $asrcset[2] = $f;}
	 if (strpos($f, '-x-') !== false) {
	 $asrcset[3] = $f;}
	 if (strpos($f, '-X-') !== false) {
	 $asrcset[4] = $f;}
	}
	if (strpos($f, '.webp') !== false) {
	 if (strpos($f, '-s-') !== false) {
	 $asrcset[5] = $f;}
	 if (strpos($f, '-m-') !== false) {
	 $asrcset[6] = $f;}
	 if (strpos($f, '-l-') !== false) {
	 $asrcset[7] = $f;}
	 if (strpos($f, '-x-') !== false) {
	 $asrcset[8] = $f;}
	 if (strpos($f, '-X-') !== false) {
	 $asrcset[9] = $f;}
	}
	if (strpos($f, '.jpg') !== false) {
	 if (strpos($f, '-s-') !== false) {
	 $asrcset[10] = $f;}
	 if (strpos($f, '-m-') !== false) {
	 $asrcset[11] = $f;}
	 if (strpos($f, '-l-') !== false) {
	 $asrcset[12] = $f;}
	 if (strpos($f, '-x-') !== false) {
	 $asrcset[13] = $f;}
	 if (strpos($f, '-X-') !== false) {
	 $asrcset[14] = $f;}
	}
  }
}
$altImgsDirFiles->close();
$imgcode2 = '<picture class="'.$playGIF.$playMP3.'alt avif" id="apic'.$imageIndex.'">';
$imgcode2 .= '	<source srcset = "./'.$Comicname.'/altImgs/'.$asrcset[0].'" media = "(max-width: 576px)" type = "image/avif">';
$imgcode2 .= '	<source srcset = "./'.$Comicname.'/altImgs/'.$asrcset[1].'" media = "((min-width: 577px) and (max-width: 768px))" type = "image/avif">';
$imgcode2 .= '	<source srcset = "./'.$Comicname.'/altImgs/'.$asrcset[2].'" media = "((min-width: 769px) and (max-width: 992px))" type = "image/avif">';
$imgcode2 .= '	<source srcset = "./'.$Comicname.'/altImgs/'.$asrcset[3].'" media = "((min-width: 993px) and (max-width: 1200px))" type = "image/avif">';
$imgcode2 .= '	<source srcset = "./'.$Comicname.'/altImgs/'.$asrcset[4].'" media = "(min-width: 1201px)" type = "image/avif">';
$imgcode2 .= '	<source srcset = "./'.$Comicname.'/altImgs/'.$asrcset[5].'" media = "(max-width: 576px)" type = "image/webp">';
$imgcode2 .= '	<source srcset = "./'.$Comicname.'/altImgs/'.$asrcset[6].'" media = "((min-width: 577px) and (max-width: 768px))" type = "image/webp">';
$imgcode2 .= '	<source srcset = "./'.$Comicname.'/altImgs/'.$asrcset[7].'" media = "((min-width: 769px) and (max-width: 992px))" type = "image/webp">';
$imgcode2 .= '	<source srcset = "./'.$Comicname.'/altImgs/'.$asrcset[8].'" media = "((min-width: 993px) and (max-width: 1200px))" type = "image/webp">';
$imgcode2 .= '	<source srcset = "./'.$Comicname.'/altImgs/'.$asrcset[9].'" media = "(min-width: 1201px)" type = "image/webp">';
$imgcode2 .= '	<source srcset = "./'.$Comicname.'/altImgs/'.$asrcset[10].'" media = "(max-width: 576px)" type = "image/jpg">';
$imgcode2 .= '	<source srcset = "./'.$Comicname.'/altImgs/'.$asrcset[11].'" media = "((min-width: 577px) and (max-width: 768px))" type = "image/jpg">';
$imgcode2 .= '	<source srcset = "./'.$Comicname.'/altImgs/'.$asrcset[12].'" media = "((min-width: 769px) and (max-width: 992px))" type = "image/jpg">';
$imgcode2 .= '	<source srcset = "./'.$Comicname.'/altImgs/'.$asrcset[13].'" media = "((min-width: 993px) and (max-width: 1200px))" type = "image/jpg">';
$imgcode2 .= '	<source srcset = "./'.$Comicname.'/altImgs/'.$asrcset[14].'" media = "(min-width: 1201px)" type = "image/jpg">';
$imgcode2 .= '<img tabindex="0" id="a'.$imageIndex.'" class="'.$playGIF.$playMP3.'alt" src = "./'.$Comicname.'/altImgs/'.$asrcset[9].'" alt = "'.$altImgText.'">';
$imgcode2 .= '</picture>';
$_SESSION["imgcode2"] = 'got imgcode2';

$imgcode2na = '<picture class="'.$playGIF.$playMP3.'alt no-avif" id="apic'.$imageIndex.'">';
//$imgcode2na .= '	<source srcset = "./'.$Comicname.'/altImgs/'.$asrcset[0].'" media = "(max-width: 576px)" type = "image/avif">';
//$imgcode2na .= '	<source srcset = "./'.$Comicname.'/altImgs/'.$asrcset[1].'" media = "((min-width: 577px) and (max-width: 768px))" type = "image/avif">';
//$imgcode2na .= '	<source srcset = "./'.$Comicname.'/altImgs/'.$asrcset[2].'" media = "((min-width: 769px) and (max-width: 992px))" type = "image/avif">';
//$imgcode2na .= '	<source srcset = "./'.$Comicname.'/altImgs/'.$asrcset[3].'" media = "((min-width: 993px) and (max-width: 1200px))" type = "image/avif">';
//$imgcode2na .= '	<source srcset = "./'.$Comicname.'/altImgs/'.$asrcset[4].'" media = "(min-width: 1201px)" type = "image/avif">';
$imgcode2na .= '	<source srcset = "./'.$Comicname.'/altImgs/'.$asrcset[5].'" media = "(max-width: 576px)" type = "image/webp">';
$imgcode2na .= '	<source srcset = "./'.$Comicname.'/altImgs/'.$asrcset[6].'" media = "((min-width: 577px) and (max-width: 768px))" type = "image/webp">';
$imgcode2na .= '	<source srcset = "./'.$Comicname.'/altImgs/'.$asrcset[7].'" media = "((min-width: 769px) and (max-width: 992px))" type = "image/webp">';
$imgcode2na .= '	<source srcset = "./'.$Comicname.'/altImgs/'.$asrcset[8].'" media = "((min-width: 993px) and (max-width: 1200px))" type = "image/webp">';
$imgcode2na .= '	<source srcset = "./'.$Comicname.'/altImgs/'.$asrcset[9].'" media = "(min-width: 1201px)" type = "image/webp">';
$imgcode2na .= '	<source srcset = "./'.$Comicname.'/altImgs/'.$asrcset[10].'" media = "(max-width: 576px)" type = "image/jpg">';
$imgcode2na .= '	<source srcset = "./'.$Comicname.'/altImgs/'.$asrcset[11].'" media = "((min-width: 577px) and (max-width: 768px))" type = "image/jpg">';
$imgcode2na .= '	<source srcset = "./'.$Comicname.'/altImgs/'.$asrcset[12].'" media = "((min-width: 769px) and (max-width: 992px))" type = "image/jpg">';
$imgcode2na .= '	<source srcset = "./'.$Comicname.'/altImgs/'.$asrcset[13].'" media = "((min-width: 993px) and (max-width: 1200px))" type = "image/jpg">';
$imgcode2na .= '	<source srcset = "./'.$Comicname.'/altImgs/'.$asrcset[14].'" media = "(min-width: 1201px)" type = "image/jpg">';
$imgcode2na .= '<img tabindex="0" id="a'.$imageIndex.'" class="'.$playGIF.$playMP3.'alt" src = "./'.$Comicname.'/altImgs/'.$asrcset[9].'" alt = "'.$altImgText.'">';
$imgcode2na .= '</picture>';
$_SESSION["imgcode2na"] = 'got imgcode2na';

}
	// if there is an alt image we have details
if ($imgcode2 != '') {
	echo $imgcode2;
	echo $imgcode2na;
	$_SESSION["acounter"] = $imageIndex;
	echo '</span>'; // end of clickmeoverlay

if (($playMP3 != '') && ($playGIF != '')) { // we have audio
// if we have audio leave space for mute/unmute and CC buttons
// need an audio instance to preset mute/unmute

	echo '<div class="col-12 d-flex px-sm-0 transcript"><div class="card-body" id="t'.$imageIndex.'" style="display: none; background-color: #b0eec0">'.$altMP3Text.'</div></div>'.
	'<audio id="audio'.$imageIndex.'" src="./'.$Comicname.'/altImgs/'.$altimgmp3.'" type="audio/mpeg" alt="'.$altimgmp3.'">No Audio Support</audio>';
	$imgcode3 = '<div class="row">
	<div tabindex="0" title="toggle audio mute" class="mute-audio card col-2 d-flex flex-column px-sm-0 MP3Overlay align-items-center">
	<svg title="mute-audio" width="8vw" height="8vw" viewBox="0 0 16 16" class="bi bi-volume-mute" fill="white" stroke="red" stroke-width=".5" xmlns="http://www.w3.org/2000/svg">
	  <path fill-rule="evenodd" d="M6.717 3.55A.5.5 0 0 1 7 4v8a.5.5 0 0 1-.812.39L3.825 10.5H1.5A.5.5 0 0 1 1 10V6a.5.5 0 0 1 .5-.5h2.325l2.363-1.89a.5.5 0 0 1 .529-.06zM6 5.04L4.312 6.39A.5.5 0 0 1 4 6.5H2v3h2a.5.5 0 0 1 .312.11L6 10.96V5.04zm7.854.606a.5.5 0 0 1 0 .708l-4 4a.5.5 0 0 1-.708-.708l4-4a.5.5 0 0 1 .708 0z"/>
	  <path fill-rule="evenodd" d="M9.146 5.646a.5.5 0 0 0 0 .708l4 4a.5.5 0 0 0 .708-.708l-4-4a.5.5 0 0 0-.708 0z"/>
	</svg>
	<svg title="enable-audio" width="8vw" height="8vw" viewBox="0 0 16 16" class="bi bi-volume-up" fill="white" stroke="green" stroke-width=".5" xmlns="http://www.w3.org/2000/svg">
	  <path fill-rule="evenodd" d="M6.717 3.55A.5.5 0 0 1 7 4v8a.5.5 0 0 1-.812.39L3.825 10.5H1.5A.5.5 0 0 1 1 10V6a.5.5 0 0 1 .5-.5h2.325l2.363-1.89a.5.5 0 0 1 .529-.06zM6 5.04L4.312 6.39A.5.5 0 0 1 4 6.5H2v3h2a.5.5 0 0 1 .312.11L6 10.96V5.04z"/>
	  <path d="M11.536 14.01A8.473 8.473 0 0 0 14.026 8a8.473 8.473 0 0 0-2.49-6.01l-.708.707A7.476 7.476 0 0 1 13.025 8c0 2.071-.84 3.946-2.197 5.303l.708.707z"/>
	  <path d="M10.121 12.596A6.48 6.48 0 0 0 12.025 8a6.48 6.48 0 0 0-1.904-4.596l-.707.707A5.483 5.483 0 0 1 11.025 8a5.483 5.483 0 0 1-1.61 3.89l.706.706z"/>
	  <path d="M8.707 11.182A4.486 4.486 0 0 0 10.025 8a4.486 4.486 0 0 0-1.318-3.182L8 5.525A3.489 3.489 0 0 1 9.025 8 3.49 3.49 0 0 1 8 10.475l.707.707z"/>
	</svg></div>

	<div tabindex="0" title="toggle transcript display" id="transcriptButton" class="card col-2 d-flex flex-column px-sm-0 transcriptControl align-items-center">
	<svg style="padding-top: 1vw;" title="no-cc" xmlns="http://www.w3.org/2000/svg" width="7vw" height="7vw" stroke="red" stroke-width=".5" fill="white" class="bi bi-x-box" viewBox="0 0 16 16">
	<path d="M5.18 4.616a.5.5 0 0 1 .704.064L8 7.219l2.116-2.54a.5.5 0 1 1 .768.641L8.651 8l2.233 2.68a.5.5 0 0 1-.768.64L8 8.781l-2.116 2.54a.5.5 0 0 1-.768-.641L7.349 8 5.116 5.32a.5.5 0 0 1 .064-.704z"/>
	<path d="M4 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H4zm0 1h8a1 1 0 0 1 1 1v12a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1z"/>
	</svg>
	<svg title="show-cc" xmlns="http://www.w3.org/2000/svg" width="8vw" height="8vw"  stroke="blue" stroke-width=".5" fill="white" class="bi bi-badge-cc" viewBox="0 0 16 16">
	  <path d="M3.708 7.755c0-1.111.488-1.753 1.319-1.753.681 0 1.138.47 1.186 1.107H7.36V7c-.052-1.186-1.024-2-2.342-2C3.414 5 2.5 6.05 2.5 7.751v.747c0 1.7.905 2.73 2.518 2.73 1.314 0 2.285-.792 2.342-1.939v-.114H6.213c-.048.615-.496 1.05-1.186 1.05-.84 0-1.319-.62-1.319-1.727v-.743zm6.14 0c0-1.111.488-1.753 1.318-1.753.682 0 1.139.47 1.187 1.107H13.5V7c-.053-1.186-1.024-2-2.342-2C9.554 5 8.64 6.05 8.64 7.751v.747c0 1.7.905 2.73 2.518 2.73 1.314 0 2.285-.792 2.342-1.939v-.114h-1.147c-.048.615-.497 1.05-1.187 1.05-.839 0-1.318-.62-1.318-1.727v-.743z"/>
	  <path d="M14 3a1 1 0 0 1 1 1v8a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h12zM2 2a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V4a2 2 0 0 0-2-2H2z"/>
	</svg></div>

	<div id="b'.$imageIndex.'" tabindex="0" title="show alternate content" class="playButton clickMeOverlay card col-8 d-flex flex-column px-sm-0 align-items-center">
	<svg id="p'.$imageIndex.'" title="Play Button" width="8vw" height="8vw" viewBox="0 0 16 16" class="bi bi-play" fill="white" stroke="blue" stroke-width=".5" xmlns="http://www.w3.org/2000/svg">
	<path fill-rule="evenodd" d="M10.804 8L5 4.633v6.734L10.804 8zm.792-.696a.802.802 0 0 1 0 1.392l-6.363 3.692C4.713 12.69 4 12.345 4 11.692V4.308c0-.653.713-.998 1.233-.696l6.363 3.692z"/>
	</svg>
	<svg id="r'.$imageIndex.'" title="Reload Button" width="7vw" height="7vw"  viewBox="0 -1 16 16" class="bi bi-arrow-counterclockwise" fill="white" stroke="blue" stroke-width=".5" xmlns="http://www.w3.org/2000/svg">
	<path fill-rule="evenodd" d="M12.83 6.706a5 5 0 0 0-7.103-3.16.5.5 0 1 1-.454-.892A6 6 0 1 1 2.545 5.5a.5.5 0 1 1 .91.417 5 5 0 1 0 9.375.789z"/>
	<path fill-rule="evenodd" d="M7.854.146a.5.5 0 0 0-.708 0l-2.5 2.5a.5.5 0 0 0 0 .708l2.5 2.5a.5.5 0 1 0 .708-.708L5.707 3 7.854.854a.5.5 0 0 0 0-.708z"/>
	</svg>
</div></div>
';
echo $imgcode3;
}

if (($playMP3 == '') && ($playGIF != '')) { // no audio
$imgcode3 = '
	<div id="b'.$imageIndex.'" tabindex="0" title="show alternate content" class="playButton clickMeOverlay card col-12 d-flex flex-column px-sm-0 align-items-center">
	<svg id="p'.$imageIndex.'" title="Play Button" width="8vw" height="8vw" viewBox="0 0 16 16" class="bi bi-play" fill="white" stroke="blue" stroke-width=".5" xmlns="http://www.w3.org/2000/svg">
	<path fill-rule="evenodd" d="M10.804 8L5 4.633v6.734L10.804 8zm.792-.696a.802.802 0 0 1 0 1.392l-6.363 3.692C4.713 12.69 4 12.345 4 11.692V4.308c0-.653.713-.998 1.233-.696l6.363 3.692z"/>
	</svg>
	<svg id="r'.$imageIndex.'" title="Reload Button" width="7vw" height="7vw"  viewBox="0 -1 16 16" class="bi bi-arrow-counterclockwise" fill="white" stroke="blue" stroke-width=".5" xmlns="http://www.w3.org/2000/svg">
	<path fill-rule="evenodd" d="M12.83 6.706a5 5 0 0 0-7.103-3.16.5.5 0 1 1-.454-.892A6 6 0 1 1 2.545 5.5a.5.5 0 1 1 .91.417 5 5 0 1 0 9.375.789z"/>
	<path fill-rule="evenodd" d="M7.854.146a.5.5 0 0 0-.708 0l-2.5 2.5a.5.5 0 0 0 0 .708l2.5 2.5a.5.5 0 1 0 .708-.708L5.707 3 7.854.854a.5.5 0 0 0 0-.708z"/>
	</svg>
</div>
';
echo $imgcode3;
}
}
}
//*
}
// ----------------------
//from 1x1 image test
	// see if we have an optional caption for this image
	// we can have captions with no images for text only content
	$caption = '';
	$captionsDir = $Comics.$Comicname.'/captions/';
	if(is_dir($captionsDir)) {
		$captionTextFile = $filenameNoExt.'.txt';
		// check for txt file
		if(is_file($captionsDir.$captionTextFile)) {
			$caption = (file_get_contents($captionsDir.$captionTextFile));
			$_SESSION['imgcaption'] = 'img has caption';
		} else {
			$_SESSION['imgcaption'] = 'no img caption';
		}
	}
	$altCaption = '';
	$altCaptionsDir = $Comics.$Comicname.'/altCaptions/';
	if(is_dir($altCaptionsDir)) {
		$altCaptionTextFile = $filenameNoExt.'.txt';
		// check for txt file
		if(is_file($altCaptionsDir.$altCaptionTextFile)) {
			$altCaption = (file_get_contents($altCaptionsDir.$altCaptionTextFile));
			$_SESSION['altcaption'] = 'has alt caption';
		} else {
			$_SESSION['altcaption'] = 'no alt caption';
		}
	}
	
	// display caption
	if($caption != '') {
		if(preg_match('/To\sbe\scontinued\./', $caption)) {
			echo
				'<div class="card col-12 px-sm-0" style="opacity: 0;"><br></div>'.
				'<div class="card col-12 d-flex flex-column shadow-md #ef9a9a 	danger-color-lite lighten-3 px-sm-0">'.
				'<div class="card-body"><h2 style="text-align: center;"><b>To be continued...</b></h2></div>'.
				'</div>'.
				'<div class="card col-12 px-sm-0" style="opacity: 0;"><br></div>';
		} else {
			echo
				'<div class="card col-12 px-sm-0" style="opacity: 0;"><br></div>'.
				'<div class="xmplc card col-12 d-flex shadow-md px-sm-0">';
			echo
				'<div tabindex="0" class="card-body" id="caption'.$imageIndex.'" style="display: block;">'.$caption.'</div>';
			if($altCaption != '') {
			echo
				'<div tabindex="0" class="card-body xmpla" id="altcap'.$imageIndex.'" style="display: none;">'.$altCaption.'</div>';
			}
			echo
				'</div>'.
				'<div class="card col-12 px-sm-0" style="opacity: 0;"><br></div>';
		}
	}
	if($caption == '') {
		echo
			'<div class="card col-12 px-sm-0" style="opacity: 0;"><br></div>';
	}
$_SESSION['figCntr'] = $FigCntr;
	if($showFigCntr == true) {
			echo
			'<div class="card col-12 px-sm-0" style="opacity: 0;"><br></div>'.
			'<div class="xmplc card col-3 d-flex shadow-md px-sm-0">'.
			'<div class="card-body"><h2>'.$FigCntr.'</h2></div>'.
			'</div>'.
			'<div class="card col-12 px-sm-0" style="opacity: 0;"><br></div>';
	}

	}
	next($imageList);
}

$head9 = <<< EOT9
<!-- +++++++++++++++++++++++ -->
<!-- =========================== -->
<!--#include file="./includes/footer.shtml" -->
</article>
</main>
<!-- End of the web page display -->
<!-- ====================== -->
<!-- ++++++++++++++++++++ -->
<!-- Java script section -->
  <!-- jQuery -->
  <script defer type="text/javascript" src="./js/jquery.min.js"></script>
  <!-- core JavaScript -->
  <script defer type="text/javascript" src="./js/bootstrap51.min.js"></script>
<script defer type="text/javascript" src="./js/Reader.js"></script>
<!-- <script >
  //conditionally enable/disable right mouse click //
$(document).ready( function() {
		//Disable cut copy paste
		$('body').bind('cut copy paste', function (e) {
        e.preventDefault();
		});
		//Disable mouse right click
		$("body").on("contextmenu",function(e){
			return false;
		});
		console.info("no context");
})
</script> -->
<!-- End of the Java script section-->
<!-- ======================= -->
<!-- +++++++++++++++++++++++ -->
<!-- End of the web page -->
</body>
</html>
EOT9;
echo $head9;
//nominal end of the generated web page
$page = ob_get_contents();
ob_end_clean();

// strip off the ISP inserted script footer at end of the page
//$page = substr($page, 0, strpos($page, '<!-- End of the web page -->'));
//$page = $page.'<!-- End of the web page --></body></html>';

if(is_file('/var/www/Comics/htdocs/'.$Comicname.'.html')) {
	unlink('/var/www/Comics/htdocs/'.$Comicname.'.html');}
$file = fopen('/var/www/Comics/htdocs/'.$Comicname.'.html', "w");
fwrite($file, $page);
fclose($file);

$_SESSION['Comicsaved'] = 1;
echo
	'<script>window.location.replace("https://syntheticreality.net/Storybook/Yield.php?saveDB=1");</script>';
}
?>

The makecomic program does much of the comic builder’s work. The first thing makecomic does is to initialize or join a PHP $_SESSION and to include the font specification PHP files. Then it creates a buffer object ($page) which will contain the generated HTML page code it will create. It begins this by writing out the contents of the HTML <head> element based on the various data we have gathered. For example it writes out the page Title meta tag and generates the Open Graph meta tags including the OG Image we uploaded previously, specifies the favicon image files and the the base href address, and uploads our Bootstrap CSS style sheet.

The next thing makecomic writes out is a CSS <style> section using the font file include contents and a few other style considerations like the web comic page background image and highlights for hover or select functions. This <style> section could be written as a separate CSS file but inasmuch as it is fairly short I chose to insert it here into the HTML file.

After it has generated the <head> section and the embedded <style> sheet it begins to process the “actual content” for the comic. The process begins by opening the ComicImages.class.php mySQL file which contains the list of pilot images for the comic panels or pages for this comic. Each entry in the database reflects the base image name for one main image panel or page in the comic. Hence a ten page comic database would have ten pilot image name entries. makecomic parses the actual image file names for each of the pilot images in three image formats and at five different dimensional sizes giving us a list of fifteen files for each panel or page main image shown as a static non-animated image. For animated image panels the pilot image will be used to parse three image format files but each format here is presented in only one size or dimension for a total of three image files**.**

The comic employs an image naming scheme which reflects the image dimensions as a part of the name. Our static images are in five sizes arranged by width, and each width is encoded by one of five letter codes. The five widths were selected based upon these Bootstrap CSS styles documentation choices for our breakpoint names, x- Small, Small, Medium, Large, X- Large, and XX- Large, which correlate to five image width sizes at 576px, 768px, 992px,1200px, and 1400px while the images height in pixels is encoded as a numeric field in the image file name as may be seen in the example comic HTML code segment seen here below.

This HTML code is typical of the Builder output for the image content. You may note that we leverage the <picture> element along with the <source> element and srcset attribute to present a list of image choices to the browser. Each image choice is shown along with a media attribute which specifies the range of viewport sizes that are intended for each image. Each <source> element also contains a Mime type attribute tag. The browser will select the first image file in the source images list which it deems appropriate and it will replace the <img> element’s default src attribute with that value, which represents the image the browser will ultimately display. Ideally this will also be the optimum image. To sum this up, each entry in the database will end up creating a <picture> element like the first of the pair above. Note that a page or panel may contain only a caption with no image if desired.

Note that in the example above there are actually two separate <picture> elements both wrapped in a <span> element with the class value of “clickMeOverlay”. This particular panel or comic page, that contained in the first <picture> element of the wrapped pair, is the main page image. In this case we also have alternate content, an animation image, that is displayed instead of the main image when the page or actually the <span> is clicked, and one of the three animation file formats is selected from the list of <source> elements contained in the second <picture> element. So after makecomic has processed the main page image and created the first picture element it proceeds in a similar fashion to parse the image folder contents to construct a second or alternate image <picture> element containing the alternate image values if they are present. This alternate content may be a static or animated image as desired.

If our main panel has alternate content, that may be displayed by clicking the image itself via its clickMeOverlay <span> wrapper. Images with alternate content will also have some buttons relative to the Audio Animation Play functions including a Mute Audio, Closed Caption, and Play/Reload button for alternate content with audio, and simply a Play/Reload button alone for just an alternate image with no audio This button panel is placed immediately below the image panel.

Each image panel has an optional caption panel which is placed below the Audio Animation Buttons panel. When a main image is replaced by its alternate content, then the caption panels are also changed so both the main and alternate images have their own caption panels.

Now in the ordinary course of events we could have this as we present it above build some really nice comics. However our description has omitted an important detail which has to do with our picture element source attributes. As it turns out the Firefox browser does not display AVIF animations, instead it will display a static preview image. So we might want to address that issue. A second issue has to do with our commitment to mobile first, and that issue is that iPhones will not display the AVIF animations either. Now both Firefox and iPhone will nonetheless select an AVIF file if it is in the <source> elements set. Now I see three possibilities to contend with this image compatibility issue. One, we could just ignore it. In this case I imagine Firefox and iPhone Safari will both work, eventually. A second ploy, which I actually tested and found to have some benefits in speed, is to create two different HTML file versions and use a simple redirect based on browser detection. Now that is fairly quick, but it is UGLY. So the solution I chose is to actually generate two separate HTML files except that they are initially combined such that the final HTML file contains two picture elements for each image; one of the elements contains AVIF format file images and the other does not. The distinction is established by setting each of the two types with a class value of avif or no-avif and then, upon load, delete one or the other class based upon the browser determination. Here again from makecomic.php is the code used to do this.

$imgcode2 = '<picture class="'.$playGIF.$playMP3.'alt avif" id="apic'.$imageIndex.'">';
$imgcode2 .= '	<source srcset = "./'.$Comicname.'/altImgs/'.$asrcset[0].'" media = "(max-width: 576px)" type = "image/avif">';
$imgcode2 .= '	<source srcset = "./'.$Comicname.'/altImgs/'.$asrcset[1].'" media = "((min-width: 577px) and (max-width: 768px))" type = "image/avif">';
$imgcode2 .= '	<source srcset = "./'.$Comicname.'/altImgs/'.$asrcset[2].'" media = "((min-width: 769px) and (max-width: 992px))" type = "image/avif">';
$imgcode2 .= '	<source srcset = "./'.$Comicname.'/altImgs/'.$asrcset[3].'" media = "((min-width: 993px) and (max-width: 1200px))" type = "image/avif">';
$imgcode2 .= '	<source srcset = "./'.$Comicname.'/altImgs/'.$asrcset[4].'" media = "(min-width: 1201px)" type = "image/avif">';
$imgcode2 .= '	<source srcset = "./'.$Comicname.'/altImgs/'.$asrcset[5].'" media = "(max-width: 576px)" type = "image/webp">';
$imgcode2 .= '	<source srcset = "./'.$Comicname.'/altImgs/'.$asrcset[6].'" media = "((min-width: 577px) and (max-width: 768px))" type = "image/webp">';
$imgcode2 .= '	<source srcset = "./'.$Comicname.'/altImgs/'.$asrcset[7].'" media = "((min-width: 769px) and (max-width: 992px))" type = "image/webp">';
$imgcode2 .= '	<source srcset = "./'.$Comicname.'/altImgs/'.$asrcset[8].'" media = "((min-width: 993px) and (max-width: 1200px))" type = "image/webp">';
$imgcode2 .= '	<source srcset = "./'.$Comicname.'/altImgs/'.$asrcset[9].'" media = "(min-width: 1201px)" type = "image/webp">';
$imgcode2 .= '	<source srcset = "./'.$Comicname.'/altImgs/'.$asrcset[10].'" media = "(max-width: 576px)" type = "image/jpg">';
$imgcode2 .= '	<source srcset = "./'.$Comicname.'/altImgs/'.$asrcset[11].'" media = "((min-width: 577px) and (max-width: 768px))" type = "image/jpg">';
$imgcode2 .= '	<source srcset = "./'.$Comicname.'/altImgs/'.$asrcset[12].'" media = "((min-width: 769px) and (max-width: 992px))" type = "image/jpg">';
$imgcode2 .= '	<source srcset = "./'.$Comicname.'/altImgs/'.$asrcset[13].'" media = "((min-width: 993px) and (max-width: 1200px))" type = "image/jpg">';
$imgcode2 .= '	<source srcset = "./'.$Comicname.'/altImgs/'.$asrcset[14].'" media = "(min-width: 1201px)" type = "image/jpg">';
$imgcode2 .= '<img tabindex="0" id="a'.$imageIndex.'" class="'.$playGIF.$playMP3.'alt" src = "./'.$Comicname.'/altImgs/'.$asrcset[9].'" alt = "'.$altImgText.'">';
$imgcode2 .= '</picture>';
$_SESSION["imgcode2"] = 'got imgcode2';

$imgcode2na = '<picture class="'.$playGIF.$playMP3.'alt no-avif" id="apic'.$imageIndex.'">';
//$imgcode2na .= '	<source srcset = "./'.$Comicname.'/altImgs/'.$asrcset[0].'" media = "(max-width: 576px)" type = "image/avif">';
//$imgcode2na .= '	<source srcset = "./'.$Comicname.'/altImgs/'.$asrcset[1].'" media = "((min-width: 577px) and (max-width: 768px))" type = "image/avif">';
//$imgcode2na .= '	<source srcset = "./'.$Comicname.'/altImgs/'.$asrcset[2].'" media = "((min-width: 769px) and (max-width: 992px))" type = "image/avif">';
//$imgcode2na .= '	<source srcset = "./'.$Comicname.'/altImgs/'.$asrcset[3].'" media = "((min-width: 993px) and (max-width: 1200px))" type = "image/avif">';
//$imgcode2na .= '	<source srcset = "./'.$Comicname.'/altImgs/'.$asrcset[4].'" media = "(min-width: 1201px)" type = "image/avif">';
$imgcode2na .= '	<source srcset = "./'.$Comicname.'/altImgs/'.$asrcset[5].'" media = "(max-width: 576px)" type = "image/webp">';
$imgcode2na .= '	<source srcset = "./'.$Comicname.'/altImgs/'.$asrcset[6].'" media = "((min-width: 577px) and (max-width: 768px))" type = "image/webp">';
$imgcode2na .= '	<source srcset = "./'.$Comicname.'/altImgs/'.$asrcset[7].'" media = "((min-width: 769px) and (max-width: 992px))" type = "image/webp">';
$imgcode2na .= '	<source srcset = "./'.$Comicname.'/altImgs/'.$asrcset[8].'" media = "((min-width: 993px) and (max-width: 1200px))" type = "image/webp">';
$imgcode2na .= '	<source srcset = "./'.$Comicname.'/altImgs/'.$asrcset[9].'" media = "(min-width: 1201px)" type = "image/webp">';
$imgcode2na .= '	<source srcset = "./'.$Comicname.'/altImgs/'.$asrcset[10].'" media = "(max-width: 576px)" type = "image/jpg">';
$imgcode2na .= '	<source srcset = "./'.$Comicname.'/altImgs/'.$asrcset[11].'" media = "((min-width: 577px) and (max-width: 768px))" type = "image/jpg">';
$imgcode2na .= '	<source srcset = "./'.$Comicname.'/altImgs/'.$asrcset[12].'" media = "((min-width: 769px) and (max-width: 992px))" type = "image/jpg">';
$imgcode2na .= '	<source srcset = "./'.$Comicname.'/altImgs/'.$asrcset[13].'" media = "((min-width: 993px) and (max-width: 1200px))" type = "image/jpg">';
$imgcode2na .= '	<source srcset = "./'.$Comicname.'/altImgs/'.$asrcset[14].'" media = "(min-width: 1201px)" type = "image/jpg">';
$imgcode2na .= '<img tabindex="0" id="a'.$imageIndex.'" class="'.$playGIF.$playMP3.'alt" src = "./'.$Comicname.'/altImgs/'.$asrcset[9].'" alt = "'.$altImgText.'">';
$imgcode2na .= '</picture>';
$_SESSION["imgcode2na"] = 'got imgcode2na';

When the comic HTML is loaded into the browser, one of these two <picture> elements will be deleted as you can see from the debug console display shown here.

The code shown above is modified on load by our reader.js program shown below which also does some display scaling and handles our alternate image and caption exchanges and plays any audio as desired.

// Reader.js
// scale the images to fill the viewport and keep aspect
// this is for demo and has extra tagging/labeling features
// comment out the console.info messages once you figure it out
 var showDetails = 0;
 var showVPDetails = 0;
 var clicked = 2;
 var currentImgID = 0;
 var viewportWidth = $(window).width();
 var viewportHeight = $(window).height();
 var elWidth = 0;
 var elHeight = 0;
 var scale = 1;
 var mp3Count = 0;
 var AltDataMsg = "";
 //var audio;
 var currentImgFolder = "";
 var currentBase = "";
 var currentMP3 = "";
	
 // browser needs to decide which source image to load for each image element
 // before it can tell us which it is, use window onload instead of jquery ready
/*$(window).bind('resize', function(e)
{
  if (window.RT) clearTimeout(window.RT);
  window.RT = setTimeout(function()
  {
    this.location.reload(true); //* false to get page from cache
  }, 100);
}); */

window.onload = function() {
//window.addEventListener("load", () => {
let searchParams = new URLSearchParams(window.location.search);
if(searchParams.has('info')) { // show some details about the images on query
 var showDetails = 1;
	console.info("show details true");
} // true

// safari mobile does not display AVIF animation files
//window.addEventListener("load", () => {
    //console.info("index "+navigator.userAgent.indexOf("Edg"));
    console.info("user agent "+navigator.userAgent);
  if ((navigator.userAgent.indexOf("iPhone") != -1 ) || (navigator.userAgent.indexOf("Firefox") != -1 )) {
	  console.info("its an iPhone or Firefox");
      // echo 'window.location.replace("https://syntheticreality.net/Comics/'.$Comicname.'s.html")';
	  $(".avif").remove();
  	} else {
      console.info("not an iPhone or Firefox");
	  $(".no-avif").remove();
  }
//});

$(window).resize(function() {
	if(showVPDetails == 1) {
		$("body").append('<div id="viewport-size" style="display:block;color:#fff;background:#08F;position:fixed;top:0;left:0;font-size:2vw;z-index:5;"></div>');}
	var viewportWidth = $(window).width();
	var viewportHeight = $(window).height();
	var VPaspectRatio = viewportWidth / viewportHeight;
	var VPaspectRounded = (Math.round(VPaspectRatio * 100)) / 100;
	  // console.info("rounded VP aspect " + VPaspectRounded);
 	if(showVPDetails == 1) {
	$("#viewport-size").html('<div class="dimensions">' + viewportWidth + ' &times; ' + viewportHeight + ' px &amp; w/h = ' + VPaspectRounded + ' </div>');}

	// delete old info cards on resize
	$(".info").each(function() {
	  this.remove();
	});
	// get total src image count not including alt images
	var pnlmatched = $(".imgblock img.src");
	var pnlimgCount = pnlmatched.length;
	console.info("Number of src panels = " + pnlimgCount);
	// get total alt image count
	var altmatched = $(".imgblock img.alt");
	var altimgCount = altmatched.length;
	console.info("Number of alt panels = " + altimgCount);
	// get total alt audio count
	var mp3matched = $(".imgblock .playMP3");
	var mp3Count = mp3matched.length / 2;
	console.info("Number of alt panels with audio = " + mp3Count);

	var matched = $(".imgblock img");
	var imgCount = matched.length;
	console.info("Total Number of images/panels = " + imgCount);

	// loop through each image and tag it with an "id"
	matched.each(function() {
		console.info("================");
		console.info("currentSource "+ this.currentSrc);
		currentImgID = (this.getAttribute("id"));
			console.info("current img ID = "+ currentImgID);
		//});
		//console.info("next index "+ imgcounter);

	if (this.currentSrc.endsWith(".gif")) {
	  // get the image dimensions, faster to have sizes already specified
		if (this.currentSrc.includes("-w") && this.currentSrc.includes("-h")) {
	    var fnameLen = this.currentSrc.indexOf(".gif");
	    var elWidth = this.currentSrc.substr((this.currentSrc.indexOf("-w") + 2), 3 );
	    var elHeight = this.currentSrc.substr((this.currentSrc.indexOf("-h") + 2), 3 );
		}
	}
	if (this.currentSrc.endsWith(".avif")) {
	  // get the image dimensions, faster to have sizes already specified
		if (this.currentSrc.includes("-w") && this.currentSrc.includes("-h")) {
	    var fnameLen = this.currentSrc.indexOf(".avif");
	    var elWidth = this.currentSrc.substr((this.currentSrc.indexOf("-w") + 2), 3 );
	    var elHeight = this.currentSrc.substr((this.currentSrc.indexOf("-h") + 2), 3 );
		}
	}
	if (this.currentSrc.endsWith(".webp")) {
	  // get the image dimensions, faster to have sizes already specified
		if (this.currentSrc.includes("-w") && this.currentSrc.includes("-h")) {
	    var fnameLen = this.currentSrc.indexOf(".webp");
	    var elWidth = this.currentSrc.substr((this.currentSrc.indexOf("-w") + 2), 3 );
	    var elHeight = this.currentSrc.substr((this.currentSrc.indexOf("-h") + 2), 3 );
		}
	}
/*if (document.body.classList.includes("no-avif")) {	avifSpt = 'no-avif';
	console.info('avifSpt ' + avifSpt);}
	if (this.currentSrc.endsWith(".avif") && avifSpt == 'no-avif') {
	nameLength = (this.currentSrc.length ) - 4;
	nameString = this.currentSrc.substr(0, nameLength) + 'webp';
	console.info('nameString ' + nameString);
	this.currentSrc = nameString;
	}
*/

	if ((this.currentSrc.endsWith(".webp")) || (this.currentSrc.endsWith(".jpg")) || this.currentSrc.endsWith(".avif")) {

		if (this.currentSrc.includes("-s-")) {
	  // get the image dimensions, faster to have sizes already specified
	    var elWidth = 576;
	    var elHeight = this.currentSrc.substr((this.currentSrc.indexOf("-s-") + 3 ), 4 );
		}
		if (this.currentSrc.includes("-m-")) {
	  // get the image dimensions, faster to have sizes already specified
	    var elWidth = 768;
	    var elHeight = this.currentSrc.substr((this.currentSrc.indexOf("-m-") + 3 ), 4 );
		}
		if (this.currentSrc.includes("-l-")) {
	  // get the image dimensions, faster to have sizes already specified
	    var elWidth = 992;
	    var elHeight = this.currentSrc.substr((this.currentSrc.indexOf("-l-") + 3 ), 4 );
		}
		if (this.currentSrc.includes("-x-")) {
	  // get the image dimensions, faster to have sizes already specified
	    var elWidth = 1200;
	    var elHeight = this.currentSrc.substr((this.currentSrc.indexOf("-x-") + 3 ), 4 );
		}
		if (this.currentSrc.includes("-X-")) {
	  // get the image dimensions, faster to have sizes already specified
	    var elWidth = 1400;
	    var elHeight = this.currentSrc.substr((this.currentSrc.indexOf("-X-") + 3 ), 4 );
		}
	}
		console.info("nW "+elWidth);
		console.info("nH "+ elHeight);
		 elWidth = parseInt(elWidth);
		 elHeight = parseInt(elHeight);
		 viewportWidth = parseInt(viewportWidth);
		 viewportHeight = parseInt(viewportHeight);
		  //console.info("eW "+elWidth);
		  //console.info("eH "+elHeight);
		 // console.info("vW "+viewportWidth);
		 // console.info("vH "+viewportHeight);
		var aspect = elWidth/elHeight;
		var aspectRounded = (Math.round(aspect * 100)) / 100;
		 // console.info("rounded img aspect " + aspectRounded);
		var widthRatio = viewportWidth / elWidth;
		var heightRatio = viewportHeight / elHeight;
		 // console.info("wR "+widthRatio);
		 // console.info("hR "+heightRatio);
		 // default to the width ratio until proven wrong
		var scale = widthRatio;
		if (widthRatio * elHeight > viewportHeight) {
			scale = heightRatio;};
		var scaleRounded = (Math.round(scale * 100)) / 100;
		//  console.info("rounded scale " + scaleRounded);
		//  fit the content into the window
	// checkpoint for 1x1 image
	if ((elHeight == 1) && (elHeight == 1)) {
		hsize = elWidth;
		vsize = elHeight;
	} else {
		var hsize  = Math.round(elWidth * scale);
		var vsize = Math.round(elHeight * scale);
	}
		 console.info ("hsize "+hsize);
		 console.info ("vsize "+vsize);
	  // finally set the scaled image width and height attributes
		this.setAttribute("width", hsize);
		this.setAttribute("height", vsize);
		this.setAttribute("src", this.currentSrc);
	
	  // for the demo show a bunch of info about the image as displayed
		// parse out the source name and folder for messages and to see if we have audio
		var currentImg = this.currentSrc;
		var currentImgSource = [];
		currentImgSource = this.currentSrc.split('/');
		var currentImgFilename = currentImgSource[currentImgSource.length - 1];
		console.info("currentImgFilename "+ currentImgFilename);
		var currentImgFolder = currentImgSource[currentImgSource.length - 2];
		console.info("currentImgFolder "+ currentImgFolder);
		//currentImgPath = currentImgSource.pop();
		//console.info("currentImgPath array "+ currentImgSource);
		var currentImgName = [];
		currentImgName = currentImgFilename.split('.');
		var currentImgNoExt = currentImgName[0];
		//console.info("current Img name no extension"+ currentImgNoExt);
		var currentBasePlus = [];
		currentBasePlus = currentImgNoExt.split('-');
		var currentBase = currentBasePlus[0];
		console.info("current Img basename "+ currentBase);
		var currentMP3 = "";
		currentImg = document.getElementById(currentImgID);
		if((currentImg.classList.contains("playMP3")) && (currentImg.classList.contains("playGIF"))) {
			currentMP3 = currentBase + '.mp3';
			console.info('audio file exists = ' + currentMP3);
			AltDataMsg = "<br>There is an audio file named <span  style=\"color: darkBlue;\"><b><i>"+currentMP3+"</i></b></span> that will play if you click the panel or the play button with audio unmuted. By default the audio is muted. Click the audio icon to toggle audio muting.";
            //alert('file exists');
        } else {
			console.info('there is no audio file');
			AltDataMsg = "";
		}

		if((showDetails == 1) && (this.classList.contains("src")) && !(this.classList.contains("playGIF"))) {
			console.info("imageInfo");
			//this.setAttribute("id", imgcounter);
		// create info msg about the image
			srcid = "srcinfo"+currentImgID;
			console.info("srcinfo "+srcid);
			imageInfo = "<div id="+srcid+" class=\"info card imginfo col-12 shadow-md px-sm-0\" style=\"background-color: #b0d0ec;\"><p style=\"margin: 1vw;\">This image above is named <span style=\"color: darkBlue;\"><b><i>"+currentImgFilename+"</i></b></span> and it is panel number "+currentImgID+" of "+pnlimgCount+" total panels.<br>The source image size is "+elWidth+" X "+elHeight+" pixels for an aspect ratio of "+aspectRounded+". A scale multiplier of "+scaleRounded+" was then applied to fit the image to the viewport, resulting in the Image Display Size of "+hsize+" X "+vsize+" pixels seen here. There is no alternate image or audio for this panel.</p></div>";
		// display the info for this image
				$(this).after(imageInfo); 
				document.getElementById(srcid).style.display = "block";
		}
		if((showDetails == 1) && (currentImg.classList.contains("src")) && (currentImg.classList.contains("playGIF"))) {
		console.info("srcimginfo")
			//this.setAttribute("id", imgcounter);
		// create info msg about the image
			srcid = "srcinfo"+currentImgID;
			console.info("srcinfo "+srcid);
			srcimageInfo = "<div id="+srcid+" class=\"info card srcinfo col-12 shadow-md px-sm-0\" style=\"background-color: #b0d0ec;\"><p style=\"margin: 1vw;\">This image above is named <span style=\"color: darkBlue;\"><b><i>"+currentImgFilename+"</i></b></span> and it is panel number "+currentImgID+" of "+pnlimgCount+" total panels.<br>The source image size is "+elWidth+" X "+elHeight+" pixels for an aspect ratio of "+aspectRounded+". A scale multiplier of "+scaleRounded+" was then applied to fit the image to the viewport, resulting in the Image Display Size of "+hsize+" X "+vsize+" pixels seen here. This panel has an alternate image that will display if you click the panel or the play button."+AltDataMsg+"</p></div>";
		// display the info for this image
			$(this).after(srcimageInfo); 
			document.getElementById(srcid).style.display = "block";
		}
	
 		if ((showDetails == 1) && (currentImg.classList.contains("alt")) && (currentImg.classList.contains("playGIF"))) {
		console.info("altimginfo")
			//this.setAttribute("id", imgcounter);
			altid = "altinfo"+currentImgID;
			console.info("altinfo "+altid);
			altimageInfo = "<div id="+altid+" class=\"info card altinfo col-12 shadow-md px-sm-0\" style=\"background-color: #b0d0ec;\"><p style=\"margin: 1vw;\">This image above is named <span style=\"color: darkBlue;\"><b><i>"+currentImgFolder+'/'+currentImgFilename+"</i></b></span> and it is panel number "+currentImgID+" of "+pnlimgCount+" total panels.<br>The source image size is "+elWidth+" X "+elHeight+" pixels for an aspect ratio of "+aspectRounded+". A scale multiplier of "+scaleRounded+" was then applied to fit the image to the viewport, resulting in the Image Display Size of "+hsize+" X "+vsize+" pixels seen here. This panel is an alternate image that displays from a click on the panel or the play button."+AltDataMsg+"</p></div>";
		// display the info for this image
			$(this).after(altimageInfo); 
			document.getElementById(altid).style.display = "none";
		}
	}) // processed each matched image
}).trigger('resize'); //rescale images on viewport resize

/* ----------------------- */
// audio mute unmute toggle
$("audio").prop('muted', true); // muted by default
$(".bi-volume-up").hide(0);
$(".bi-volume-mute").show(0);
// toggle on click
  $(".mute-audio").click( function (){
	console.info("------ mute audio clicked -------");
    if( $("audio").prop('muted') ) {
          $("audio").prop('muted', false);
		  $(".bi-volume-mute").hide(0);
		  $(".bi-volume-up").show(0);
    } else {
      $("audio").prop('muted', true);
	  $(".bi-volume-up").hide(0);
	  $(".bi-volume-mute").show(0);                                 
    }
  });
// toggle on enter key
$(".mute-audio").keyup(function(event) {
  if (event.keyCode === 13) {
   event.preventDefault();
   $(".mute-audio").click();
  }
});

/* ----------------------- */
// Control transcript display
	var transtext = "active"; // active by default
	  $(".transcript").hide(0);
	  $(".bi-badge-cc").show(0);
	  $(".bi-x-box").hide(0);
// toggle display on click
$('.transcriptControl').click( function() {
	console.info('----- transcriptControl clicked -----');
	console.info("currentImgID "+ currentImgID);
	if(transtext == "notactive") {
		transtext = "active";
	  $(".bi-x-box").hide(0);
	  $(".bi-badge-cc").show(0);
	} else {
		transtext = "notactive";
	  $(".bi-badge-cc").hide(0);
	  $(".bi-x-box").show(0);
	}
	console.info("transtext "+ transtext);
});
// toggle on enter key
$(".transcriptControl").keyup(function(event) {
  if (event.keyCode === 13) {
   event.preventDefault();
   $(".transcriptControl").click();
  }
});

/* ----------------------- */
// control alternate img display
	  $(".bi-arrow-counterclockwise").hide(0);
	  $(".bi-play").show(0);
// toggle image to alternate img on click
// if it is a GIF it plays GIF each time clicked
$('.clickMeOverlay').click( function(event) {
	imgid = $(this).attr('id');
	imgindex = imgid.substring(1);
 	//clickaltinfo = $('.clickMeOverlay').children('.altinfo');
 	//clicksrcinfo = $('.clickMeOverlay').children('.srcinfo');
	console.info('----- play has been clicked -----');
	console.info("transtext "+ transtext);
	console.info("imgid " + imgid);
	console.info("imgindex " + imgindex);
	//console.info("mp3ID "+ mp3ID);
	source = ("s"+imgindex);
	altsource = ("a"+imgindex);
	pbutton = ("p"+imgindex);
	rbutton = ("r"+imgindex);
	tbutton = ("t"+imgindex);
	console.info("sourcetag " + source);
	console.info("altsourcetag " + altsource);
	console.info("Pbuttontag " + pbutton);
	console.info("Rbuttontag " + rbutton);
	console.info("transcripttag " + tbutton);
	aimage = document.getElementById(altsource);
	simage = document.getElementById(source);
	play = document.getElementById(pbutton);
	reload = document.getElementById(rbutton);
	tscript = document.getElementById(tbutton);

	//console.info(image);
	if(aimage.style.display == "none") {
		simage.style.display = "none";
		aimage.style.display = "block";
		play.style.display = "none";
		reload.style.display = "block";
		if(transtext == "active") {
		if(typeof(tscript) != 'undefined' && tscript != null) {
		tscript.style.display = "block";}}
		if(showDetails == 1) {
			src = document.getElementById("srcinfo"+source);
			src.style.display = "none";
			alt = document.getElementById("altinfo"+altsource);
			alt.style.display = "block";}
		cap = document.getElementById("caption" + imgindex);
		$(cap).css("display", "none");
		acap = document.getElementById("altcap" + imgindex);
		$(acap).css("display", "block");
		clicked = 1;

		console.info('----- image and caption changed -----');
		console.info("altimgID "+ imgindex);
		if ( $("audio").prop('muted') == false ) {
			console.info("audio not muted. play the audio.");
			var audio_element = document.getElementById("audio" + imgindex);
			if(typeof(audio_element) != 'undefined' && audio_element != null) {
    		audio_element.load();
    		audio_element.playclip = function(){
        		audio_element.pause();
        		audio_element.currentTime=0;
        		audio_element.play();}
			audio_element.playclip();
		}}
		} else {
		console.info('----- back to original image and caption -----');
		aimage.style.display = "none";
		simage.style.display = "block";
		reload.style.display = "none";
		play.style.display = "block";
		if(transtext == "active") {
		if(typeof(tscript) != 'undefined' && tscript != null) {
		tscript.style.display = "none";}}
		if(showDetails == 1) {
			src.style.display = "block";;
			alt.style.display = "none";}
		acap = document.getElementById("altcap" + imgindex);
		$(acap).css("display", "none");
		cap = document.getElementById("caption" + imgindex);
		$(cap).css("display", "block");
		clicked = 0;
		}
console.info("Clicked " + clicked);
});

/*
$('.clickMeOverlay').keyup(function(event) {
  if (event.keyCode === 13) {
	event.preventDefault();
$('.clickMeOverlay').click();
*/
}

If you look through the JavaScript code above you may see a lot of console.info() functions that I use to debug my scripts. Here is a partial dump of the debug console resulting from viewing the comic. These breakpoint messages let me know what my code is doing.

Recall that I also referred to a dumpSession PHP debug program, and this is the result from that program after running the makecomic.php code.

dumpSession
Options are available by query strings.
Use dumpSession.php?help to display the options.

There is one $_COOKIE key named Storybook
No Requested Session Name query, using the COOKIE key value.
Current Session Name is Storybook
Current Session ID is g39c7v3qr5qfhg038halln65kt
SESSION Array
(
    [Comicsaved] => 1
    [usedSavedConfig] => 1
    [skip] => ./Yield.php?saveDB=1
    [landingpage] => 
    [panelcount] => 48
    [oauth_id] => 
    [email_id] => [email protected]
    [ComicFoldername] => Hyenas2
    [cardTitle] => Hyenas2
    [siteurl] => https://syntheticreality.net/Comics/
    [Comicname] => Hyenas2
    [pageURL] => https://syntheticreality.net/Comics/Hyenas2.html
    [bkgndImage] => Hyenas2BKGND-s.jpg
    [cardImage] => Hyenas2CARD.webp
    [cardAlt] => aerial view of High City urban buildings
    [cardSubtitle] => "Through Nanzinger Gate Wormhole with SheLa"
    [cardText] => In this episode we meet one of "the girls", SheLa, who is a member of an urban social club aptly named "The Catty Chatters".
SheLa is telling the ladies about her just completed jump through the Nanzinger Gate Wormhole and her adventures beyond. She is greeted with interest, such response being reflective of the group. Let's see how this plays out then.
    [category] => Adult SciFi Sitcom
    [authorname] => Bob Wright
    [scriptname] => 
    [pencilsname] => 
    [inksname] => 
    [colorsname] => 
    [lettersname] => 
    [publisher] => Raw Material Comics
    [audience] => General Adult
    [artistname] => Bob Wright
    [cardemail] => [email protected]
    [pageimage] => CARD
    [hfontinfo] => @font-face {font-family: "MerriweatherSans-ExtraBold"; src: url("./Fonts/MerriweatherSans-ExtraBold.ttf") format("truetype");} @media screen and (max-width: 576px) {   h1 { font: 5vw MerriweatherSans-ExtraBold; color: #000000;} h2 { font: 4vw MerriweatherSans-ExtraBold; color: #000000;} h3 { font: 3.5vw MerriweatherSans-ExtraBold; color: #000000;}} @media screen and (min-width: 577px) and (max-width: 1079px) {h1 { font: 4vw MerriweatherSans-ExtraBold; color: #000000;} h2 { font: 3.5vw MerriweatherSans-ExtraBold; color: #000000;} h3 { font: 3vw MerriweatherSans-ExtraBold; color: #000000;}}  @media screen and (min-width: 1080px) {h1 { font: 3vw MerriweatherSans-ExtraBold; color: #000000;} h2 { font: 2.5vw MerriweatherSans-ExtraBold; color: #000000;} h3 { font: 2.2vw MerriweatherSans-ExtraBold; color: #000000;}}
    [pfontinfo] => @font-face {font-family: "Merriweather-Regular"; src: url("./Fonts/Merriweather-Regular.ttf") format("truetype");} @media screen and (max-width: 576px) {   .card-body, p { font: 3.5vw Merriweather-Regular; color: #000000;}} @media screen and (min-width: 577px) and (max-width: 1079px) { .card-body, p { font: 2.5vw Merriweather-Regular; color: #000000;}}  @media screen and (min-width: 1080px) { .card-body, p { font: 2vw Merriweather-Regular; color: #000000;}}
    [cbinfo] => {background: #FFC0CB;}
    [ahfontinfo] => @font-face {font-family: "RobotoSlab-Bold"; src: url("./Fonts/RobotoSlab-Bold.ttf") format("truetype");} @media screen and (max-width: 576px) {   h1 { font: 5vw RobotoSlab-Bold; color: #000000;} h2 { font: 4vw RobotoSlab-Bold; color: #000000;} h3 { font: 3.5vw RobotoSlab-Bold; color: #000000;}} @media screen and (min-width: 577px) and (max-width: 1079px) {h1 { font: 4vw RobotoSlab-Bold; color: #000000;} h2 { font: 3.5vw RobotoSlab-Bold; color: #000000;} h3 { font: 3vw RobotoSlab-Bold; color: #000000;}}  @media screen and (min-width: 1080px) {h1 { font: 3vw RobotoSlab-Bold; color: #000000;} h2 { font: 2.5vw RobotoSlab-Bold; color: #000000;} h3 { font: 2.2vw RobotoSlab-Bold; color: #000000;}}
    [apfontinfo] => @font-face {font-family: "Roboto-Regular"; src: url("./Fonts/Roboto-Regular.ttf") format("truetype");} @media screen and (max-width: 576px) {   .card-body, p { font: 3.5vw Roboto-Regular; color: #000000;}} @media screen and (min-width: 577px) and (max-width: 1079px) { .card-body, p { font: 2.5vw Roboto-Regular; color: #000000;}}  @media screen and (min-width: 1080px) { .card-body, p { font: 2vw Roboto-Regular; color: #000000;}}
    [acbinfo] => {background: #DB7093;}
    [imageList] => 42
    [imageData] => Array
        (
            [comic_id] => 97
            [comic_name] => Hyenas2
            [oauth_id] => 
            [image_hash] => 9017431349375fe85098a023a6f18be41bb98769370fda0faf3c6364691b1fa9
            [image_key] => 1c14c0ea65692c7871f6576aadaa3040168043880fb4924cf585375c17bd9f1c
            [filename] => 101_FeralPencil.jpg
            [filetype] => jpg
            [width] => 1600
            [height] => 1600
            [created] => 2023-01-27 15:25:33
        )

    [filenameNoExt] => 101_FeralPencil
    [altText] => 101 FeralPencil
    [altDesc] => There is an alt text file named /var/www/Comics/htdocs/Hyenas2/altText/101_FeralPencil.txt.
    [playGIF] => playGIF
    [playMP3] => playMP3
    [playJPG] => 
    [altImgsDir] => /var/www/Comics/htdocs/Hyenas2/altImgs/
    [imgcode1] => got imgcode1
    [imgcode1na] => got imgcode1
    [scounter] => 42
    [imgcaption] => img has caption
    [altcaption] => has alt caption
    [figCntr] => [ p 42 ]
    [hitGIF] => 42
    [altImgMP3Desc] => There is an audio alternate image file named Hyenas2/altImgs/101_FeralPencil.mp3.
    [altImgMP3TextDesc] => There is an audio transcript file named Hyenas2/altImgs/101_FeralPencil.mpt
    [altImgText] => Animation of a fly feeding itself.

    [altImgTextDesc] => There is an alt image text file named /var/www/Comics/htdocs/Hyenas2/altImgText/101_FeralPencil.txt.
    [imgcode2] => got imgcode2
    [imgcode2na] => got imgcode2na
    [acounter] => 42
    [hitMP3] => 42
    [postcount] => 
User posts count changed

    [zip exists] => /var/www/Comics/htdocs/Hyenas2.zip
    [contentLength] => 235775843
    [Started] => True
    [SessionName] => Storybook
    [SessionID] => g39c7v3qr5qfhg038halln65kt
)

This screen next below is from the Yield.php program, which in turn invokes the makecomic program.

When makecomic completes its run, it again returns control to the Yield program which announces the builder’s completion and offers the user an opportunity to download a ZIP archive of the comic, which it has placed on our comics gallery.

Conclusion of the Generator stage

So we now have a comic that was created by our Builder. The comic, Hyenas 2, is shown in the next article, Storybook V2 Comic Book Builder: Part 3, A Comic Book Result.

As always, Comments, Criticisms, and Suggestions are welcome.


Written by bobnoxious | Bob has been designing hardware and coding software for decades. He likes to draw and write. He’s a web cadet wannabe.
Published by HackerNoon on 2023/03/23