Editing HTML5 and CSS with vim.

Simplify HTML With PHP and CSS

There's More in the HTML Than You Expect

It's not enough to simply have a web page with some text and image content. Google is the dominant search engine, and what Google does is soon copied by the others. Indexing systems within social media environments such as Facebook do similar things, but need specialized markup.. You could code all of this manually within web pages, but that could be an enormous task. Plus, what seems to be a simple addition or change would be multiplied by however many pages are in the site.

The pages need HTML metadata, microdata, structured data, schemas, and markup for social media.

Let's see how to automate this with PHP.

File Content versus Page Content

Your browser asks the web server for a specific resource. For this page, for example, the browser makes an HTTPS connection to the server and then asks for the resource:
/technical/html-php-css.html

On my sites, the server builds every page dynamically. If you right-click and select View page source you will see a lot of content that isn't in the page. If something is always included, or if it varies but can be automatically built, I don't include that literal content within the pages. PHP automatically generates and includes standard content near the beginning and end of every page.

The browser receives the content and starts rendering the page that it describes. The content will direct the browser to retrieve any further data needed. That can include literal content, such as image files or Javascript for the Google Translate gadget, but it can also include style content.

Style

On all but the very simplest of pages this further data will include at least one CSS or Cascading Style Sheet files.

The style tells the browser what colors to use for the background and the text, what fonts and sizes to use for paragraphs and various levels of headers, maximum paragraph width, and so on.

In-Line Style

Let's say that I want some of my paragraphs to have a different style, smaller text with a grey background. I could do this by starting each of those paragraphs with the specific style changes:
<p style="font-size: 85%; background: #ccc;">

If I only want that different style on one paragraph on the page, I should simply define its style as I did above. But once I decide that I want to use that category more than once, that so-called "in-line style" becomes difficult to maintain.

I may decide that 85% makes the text too small, and I want to change it to 90%. Then I would have to find and change every instance of this special style definition. That can be a lot of work, and I will probably overlook one of the changes, leading to inconsistencies that are hard to track down.

I need to define a style class, which is sort of like a subroutine in programming. Repeating the same code logic (or style markup) not only makes for more work, but it provides more opportunity for error. Get the code right once, and refer to that.

Per-Page Style With Class

If I only have one page where I want to do this, I can use a <style>...</style> block within the <head>...</head> section of the page. Let's say I include this:

<style>p.caption { font-size: 85%; background: #ccc;</style>

Then I could write something like this:

<p>
This is a normal paragraph.
There is nothing unusual here.
</p>
<p class="caption">
This paragraph will have smaller text on a grey background.
</p>
<p>
Now back to normal again.
</p>

If I try applying that to an unnumbered list with <ul class="caption"> then it won't have any effect, as I defined just the specific p.caption class. But let's say I left off the "p" in the definition, like this:

<style>.caption { font-size: 85%; background: #ccc;</style>

Now I could apply class="caption" to any element to get smaller text and a grey background — ul, ol, table, and so on.

Per-Site Style

If I find this really helpful, then I could define this style in a separate file that all the pages on my site include. This file is typically /css/style.css although you could name it and locate it wherever you want. Although, I would stick with the ".css" ending to the name. Otherwise, Windows-based browsers might get confused.

Of course, I could have multiple "style sheets" or "style files", maybe one for each section of a site. Then modify each page to load the appropriate style file.

Let's say that /css/style.css is included by every file on my site, and it includes this line:

body { color: #4d0d0c; }

Every page will use a dark brown color for the text, unless you say otherwise.

Now on one page I include the following at the end of the <head>...</head> section:

<style> body { color: #00f000; } </style>

That page-specific style overrides what was included from the site-wide file, and all the text will be bright green.

Then, for just one paragraph, I specify the following:

<p style="color: #0000f0;">
Blue can be nice.
</p>

Now the site generally has dark brown text, this one page is mostly bright green, and this one short paragraph within it is bright blue.

If I find blue paragraphs or blue text to be generally useful, then I should define a site-wide class once. I would add this to /css/style.css:

.blue { color: #0000f0; } 

And now any element of any page using that style file can use the class. If I decide to slightly modify the color of hundreds of elements using that class, I only have to change one line in that one style file.

<p class="blue">
Blue can be nice.
</p>

The "structured programming" approach makes style classes best practice for site maintenance.

So, it's good to have style files useful across an entire site. We want to specify which style files to load in the beginning of every file. But there are many more things to do before we start adding the visual content.

Header Metadata

The <head>...</head> section of the page is where you specify page-specific metadata, including the character set, the page title, and a page description that might be presented as a summary in search engine results. It's also where you tell the browser to load one or more specific style sheets. That used to be it, but the page header now contains more and more...

Responsive Web Design

Your pages need to include responsive web design. Otherwise they can be next to useless on mobile devices, and search engines will rank them much lower if at all.

The Bootstrap toolkit is, by far, the most popular RWD library.

You need to specify the viewport details. And for clients still using browsers earlier than Internet Explorer 9, you need to tell their outdated browsers how to find workarounds.

Icons and Favicons

A favicon is a small file containing graphical data. Its image can be displayed in the browser tab, in the bookmark list, and maybe in the history. The browser will ask for it, and the Safari browser will ask for its own icon. Tell them where yours are.

Search Engines and Google Ads

Google and other search engines want some extra metadata, especially a "canonical" string. If you have multiple URLs leading to the same content, which is the canonical one?

Google and Bing have webmaster dashboards, and they require your pages to contain a random looking string linked to your account. You have to sign in to an account associated with that string in order to access the dashboard for the site.

Google AdSense has "Page-level ads", which are full-screen ads that appear while loading the next page as you follow links within one site. You enable that with a small block of JavaScript.

Social Media

Twitter and Facebook insist on special code just for them, spelling out metadata in their own way. For example, they can't find this:
<title>The page title</title>
You must also include TWO copies just for them, formatted in their specific ways:
<meta property="og:title" content="The page title" />
<meta name="twitter:title" content="The page title" />

Twitter
Validation
Facebook
Validation

Then the same nonsense for the description and even the URL itself! Plus other metadata, especially for Facebook.

As their validation and debugging pages demonstrate, links to pages from your site won't appear as you would like until you support all this additional data.


Amazon
ASIN: B00QSFOSE0

Amazon
ASIN: 1536954527

Amazon Associates Shortcuts

I have Amazon Associates ads on several pages. To demonstrate, there are two just above here.

Rather than copying and pasting in the large blocks provided by Amazon, and then making numerous changes to convert them into valid HTML5 content, I include a PHP block in the standard header included on every page on my site. That defines two PHP routines.

Now I can insert the Amazon item simply:
<?php amazonImage("1536954527"); ?>

Let's Get Started! The HTML <head>...</head>

The following is all that I have in the beginning of the file making up this page. Notice the PHP line highlighted in light purple.

<!DOCTYPE html>
<html lang="en" xml:lang="en">
	<head>
		<meta charset="UTF-8" />
		<title>Simplifying HTML With PHP and CSS</title>
		<meta name="description" content="Use PHP and CSS to simplify HTML, make responsive web design easier." />
		<?php include($_SERVER['DOCUMENT_ROOT'].'/ssi/header.html'); ?>
		<meta name="twitter:image" content="https://cromwell-intl.com/open-source/pictures/motherboard-105251.jpg" />
		<meta property="og:image" content="https://cromwell-intl.com/open-source/pictures/motherboard-105251.jpg" />
		<style>	.purple	{ background: #e0e0ff; }
			.green	{ background: #d0ffc0; }
		</style>
	</head>

I tried to put some thought into the "title" and "description" fields, considering what searches people might make and what results might draw them to read this page. And, I had to manually select the respresentative image and specify that in both Twitter-specific and Facebook-specific formats.

I also specified some page-specific colored highlighting.

You do not see any other style definition above. But it's definitely there, this page doesn't appear in whatever default black-on-white style your browser uses. The CSS inclusion happens within a file named header.html stored in the top-level /ssi directory.

The resulting block of HTML code is much larger that the short passage in the file, as seen above.

Let's see what the header.html file does.

/ssi/header.html

The file that is included by PHP itself contains PHP code. Yes, I have configured my server to process every page with the PHP module regardless of file name. For a site like toilet-guru.com, hosted at GoDaddy, I have to name every file "*.php".

I know that all my file names contain nothing but letters and numbers, plus these characters:
/ . - _
All other characters are invalid file name components.

The program must automatically figure out the requested file name. The PHP function shell_exec() will be used twice, to extract the "title" and "description" fields from the requested file.

I must be extra careful to first sanitize the URI by truncating it at any invalid character, and then continuing only if the resulting name refers to a file that actually exists.

Because of the quote nesting, I cannot use literal embedded quote characters in "title" and "description" fields. Instead, use the Unicode description &#x0027;. For example the page /travel/Index.html starts like this:

<!DOCTYPE html>
<html lang="en" xml:lang="en">
	<head>
		<meta charset="UTF-8" />
		<title>Bob&#x0027;s Travel Recommendations</title>
		<meta name="description" content="Bob&#x0027;s travel suggestions through Europe, the Middle East, China and Japan, and the U.S." />
		[...] 

Amazon
ASIN: 1491918667

Amazon
ASIN: 1491927577

Once you have a safe file name string, you will need some Linux commands like grep, sed, and tr, plus regular expressions. All of that is wrapped within PHP.

Here is what I have come up with. All of the purple-highlighted PHP will be replaced with its output, if any.

<!-- start of standard header -->
<meta name="viewport" content="width=device-width, minimum-scale=1, initial-scale=1">
<!--[if lt IE 9]>
	<script src="/js/html5shiv.js"></script>
	<script src="/js/respond.min.js"></script>
<![endif]-->

<!-- style -->
<link rel="stylesheet" href="/css/bootstrap.min.css" />
<link rel="stylesheet" href="/css/bootstrap-theme.min.css" />
<link rel="stylesheet" href="/css/style.css" />

<link rel="icon" type="image/png" href="/pictures/favicon.png" />
<!-- Safari -->
<link rel="apple-touch-icon" href="/pictures/touch-icon-iphone-152x152.png" />

<!-- Facebook, Twitter -->
<?php	$file = $_SERVER['DOCUMENT_ROOT'].$_SERVER['REQUEST_URI'];
	// Sanitize $file string.
	// All valid URIs on the site contain NOTHING but
	// alphanumeric plus these:  / . - _ #
	// Only need # for id="..." links within pages.
	// Truncate the string starting at anything else.
	$file = preg_replace("@[^/a-zA-Z0-9\.\-_].*@", "", $file);
	// Add /Index.html if it is a directory
	if ( is_dir($file) ) {
		$file = $file."/Index.html";
	}
	// Fix double slashes added by confused clients
	$file = preg_replace("@//@", "/", $file);
	// For rel="canonical", drop the document root path and "Index.html"
	$canon = preg_replace("@".$_SERVER['DOCUMENT_ROOT']."@", "", $file);
	$canon = preg_replace("@/Index.html@", "/", $canon);
	echo("<link rel='canonical' href='https://cromwell-intl.com".$canon."' />\n");
	// Must avoid titles and descriptions with embedded quotes.
	if (is_file($file)) {
		$thetitle = shell_exec("grep '<title>' " . $file . " | sed -e 's/.*<title>//' -e 's@ *</title>@@' -e 's/^  *//' -e 's/^  *//' -e 's/  *$//' | tr -d '\n'");
	} else {
		$thetitle = "Bob Cromwell";
	}
	// Facebook
	echo("\t\t<meta property='og:title' content='" . $thetitle . "'/>\n");
	// Twitter
	echo("\t\t<meta name='twitter:title' content='" . $thetitle . "'/>\n");
	if (is_file($file)) {
		$description = shell_exec("grep '<meta name=.description' " . $file . " | sed -e 's/.*content=.//' -e 's@. />@@' -e 's/^  *//' -e 's/^  *//' -e 's/  *$//' | tr -d '\n' | tr -d '\"'");
	} else {
		$description = "Bob Cromwell";
	}
	echo("\t\t<meta name='twitter:description' content='" . $description . "'/>\n");
	echo("\t\t<meta property='og:description' content='" . $description . "'/>\n");
?>
<meta property="fb:admins" content="bob.cromwell.10" />
<meta property="fb:app_id" content="9869919170"/>
<meta property="og:type" content="website" />
<meta property="og:url" content="https://cromwell-intl.com<?php echo($_SERVER['REQUEST_URI']); ?>" />
<meta property="og:site_name" content="Bob Cromwell: Travel, Linux, Cybersecurity" />
<meta name="twitter:url" content="https://cromwell-intl.com<?php echo($_SERVER['REQUEST_URI']); ?>" />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:creator" content="@ToiletGuru" />

<!-- Google Page-level ads for mobile -->
<!-- Note: only need the adsbygoogle.js script this
	one time in the header, not in every ad -->
<script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>
<script>
  (adsbygoogle = window.adsbygoogle || []).push({
    google_ad_client: "ca-pub-5845932372655417",
    enable_page_level_ads: true
   });
</script>

<!-- Google webmaster tools -->
<meta name="google-site-verification" content="-QwRAzF67ZlYJ9S4v3SCsyDceuoD2J7wLepdqiSX_Q4" />
<link rel="author" href="https://plus.google.com/+BobCromwell" />
<!-- Bing webmaster tools -->
<meta name="msvalidate.01" content="3E2092BE1413B6791596BCC09A493E58" />

<?php	// Amazon Affiliate
	function amazonImage($ASIN) {
		echo '<div class="centered" style="border:1px solid #bbb;">';
		echo '<a href="https://www.amazon.com/gp/product/' . $ASIN . '?ie=UTF8&psc=1&linkCode=li3&tag=cromwelintern-20&linkId=e2542bbd54152aedf64f944c85be63bf&language=en_US&ref_=as_li_ss_il" target="_blank"><img src="//ws-na.amazon-adsystem.com/widgets/q?_encoding=UTF8&ASIN=' . $ASIN . '&Format=_SL250_&ID=AsinImage&MarketPlace=US&ServiceVersion=20070822&WS=1&tag=cromwelintern-20&language=en_US" alt=""></a><img src="https://ir-na.amazon-adsystem.com/e/ir?t=cromwelintern-20&language=en_US&l=li3&o=1&a=' . $ASIN . '" width="1" height="1" alt="" style="z-index:100; position:relative; border:none !important; margin:0px !important;">';
		echo '<br><span class="badge badge-pill badge-primary">Amazon</span>';
		echo '<br><span style="font-size:0.8rem; color:#aaa;">ASIN: ' . $ASIN . '</span></div>';
	}
?>
<!-- end of standard header -->

Starting the Body

Now we start the <body>...</body> section, the main bulk of the page.

Google says that Schema.org markup is very helpful for indexing. That project's documentation shows that it has somewhat arbitrary coverage, and is largely aimed at restaurants, theaters, and other businesses and organizations with events, menus, tickets, specific items for sale, and similar. All of my pages are coded as "Article". The file microdata.html handles the content that is either unchanging or can be automatically generated.

First, here's a little more of this page. Green marks the Schema markup, purple marks where PHP will do lots of work for me. Notice that PHP also loads a banner ad between the banner image and the H1 header.

<body>
	<article itemscope itemtype="https://schema.org/Article" class="container">
	<?php include($_SERVER['DOCUMENT_ROOT'].'/ssi/microdata.html'); ?>
	<meta itemprop="about" content="PHP" />
	<meta itemprop="about" content="HTML5" />
	<meta itemprop="about" content="CSS" />
	<meta itemprop="about" content="responsive web design" />
	<header>
	<div>
	<img src="pictures/html-banner.png"
		alt="Editing HTML5 and CSS with vim." />
	</div>
	<h1>Simplify HTML With PHP</h1>
	<?php include($_SERVER['DOCUMENT_ROOT'].'/ads/responsive-technical.html'); ?>
	</header>

Generating the Microdata with /ssi/microdata.html

This one is entirely PHP. The file name, description string, and title string are already defined. It extracts the Twitter-specific image URI from the file.

Using that, it derives the image file name, and uses jhead to get its dimensions. Note that this restricts the social media images to JPEG. More complex logic using the file command could allow for PNG and GIF social media images. However, a quick check confirmed that while I have specified social media images for most of my pages, all were JPEG.

<!-- start of schema.org microdata included in all pages -->
<?php	// Variables file, description, thetitle are already set.
	$theimage="";
	if (is_file($file)) {
		$theimage = shell_exec("grep 'twitter:image' " . $file . " | head -1 | sed 's@.*https://@https://@' | sed 's@\".*@@' | tr -d '\n'");
		// Possible that twitter:image is not yet defined.
		if ($theimage != "") {
			$imagefile = preg_replace("@https://cromwell-intl.com@", $_SERVER['DOCUMENT_ROOT'], $theimage);
			$theimagewidth = shell_exec("/usr/local/bin/jhead " . $imagefile . " | awk '/^Resolution/ {print $3}' | tr -d '\n'") . "px";
			$theimageheight = shell_exec("/usr/local/bin/jhead " . $imagefile . " | awk '/^Resolution/ {print $5}' | tr -d '\n'") . "px";
		}
		// Get timestamp in ISO yyyy-mm-dd
		$date = shell_exec("stat -x -t '%F' " . $file . " | awk -F: '/Modify/ {print $2}' | tr -d ' \n'");
	}
	// If we failed to successfully set $theimage, use defaults.
	if ($theimage == "") {
		$theimage = "https://cromwell-intl.com/pictures/headshot-2484-23pc.jpg";
		$theimagewidth = "713px";
		$theimageheight = "596px";
		$date = date("c");
	}

	// Now generate Schema output
	echo("\t<span itemprop='image' itemscope itemtype='https://schema.org/imageObject'>\n");
	echo("\t\t\t<meta itemprop='url' content='".$theimage."' />\n");
	echo("\t\t\t<meta itemprop='width' content='".$theimagewidth."' />\n");
	echo("\t\t\t<meta itemprop='height' content='".$theimageheight."' />\n");
	echo("\t\t</span>\n");
	echo("\t\t<meta itemprop='author' content='Bob Cromwell' />\n");
	echo("\t\t<span itemprop='publisher' itemscope itemtype='https://schema.org/organization'>\n");
	echo("\t\t\t<meta itemprop='name' content='Cromwell International' />\n");
	echo("\t\t\t<span itemprop='logo' itemscope itemtype='https://schema.org/imageObject'>\n");
	echo("\t\t\t\t<meta itemprop='url' content='https://cromwell-intl.com/pictures/cartoon-headshot-2484-10pc.jpg' />\n");
	echo("\t\t\t\t<meta itemprop='width' content='310px' />\n");
	echo("\t\t\t\t<meta itemprop='height' content='259px' />\n");
	echo("\t\t\t</span>\n");
	echo("\t\t</span>\n");

	echo("\t\t<meta itemprop='headline' content='".$thetitle."' />\n");
	echo("\t\t<meta itemprop='datePublished' content='".$date."' />\n");
	echo("\t\t<meta itemprop='dateModified' content='".$date."' />\n");
	echo("\t\t<meta itemprop='mainEntityOfPage' content='https://cromwell-intl.com".$_SERVER['REQUEST_URI']."' />\n");
	?>
	<!-- end of schema.org microdata -->

The Resulting Schema.org Microdata

The output of that is all Schema.org markup. The resulting page source now contains the following:

<!-- start of schema.org microdata included in all pages -->
<span itemprop='image' itemscope itemtype='https://schema.org/imageObject'>
	<meta itemprop='url' content='https://cromwell-intl.com/open-source/pictures/motherboard-105251.jpg' />
	<meta itemprop='width' content='700px' />
	<meta itemprop='height' content='544px' />
</span>
<meta itemprop='author' content='Bob Cromwell' />
<span itemprop='publisher' itemscope itemtype='https://schema.org/organization'>
	<meta itemprop='name' content='Cromwell International' />
	<span itemprop='logo' itemscope itemtype='https://schema.org/imageObject'>
		<meta itemprop='url' content='https://cromwell-intl.com/pictures/cartoon-headshot-2484-10pc.jpg' />
		<meta itemprop='width' content='310px' />
		<meta itemprop='height' content='259px' />
	</span>
</span>
<meta itemprop='headline' content='Simplifying HTML With PHP' />
<meta itemprop='datePublished' content='2018-01-13' />
<meta itemprop='dateModified' content='2018-01-13' />
<meta itemprop='mainEntityOfPage' content='https://cromwell-intl.com/technical/html-php-css.html' />
<!-- end of schema.org microdata -->

Social Media and Google Translate

My site just doesn't have much content that people are going to share on Facebook or Reddit, and who really uses Google+, but I might as well try.

Just below the <header>...</header> block the below is included. Purple PHP executes on the server, green Javascript is executed in the browser.

<!-- start of google-translate.html -->
<div style="float: right; margin-left: 1em; text-alight: right;">

<div class="fb-like" style="position:relative;top:0; vertical-align:top !important;" data-href=<?php echo $_SERVER['SERVER_NAME'] . $_SERVER['REQUEST_URI'] ; ?> data-layout="button" data-action="like" data-show-faces="false" data-share="true" data-colorscheme="light"></div>

<a href="https://twitter.com/share" class="twitter-share-button" style="position:relative; top:0; vertical-align: top !important; padding:0 3px 0 3px; margin:0;" data-lang="en" data-count="none">Tweet</a>

<br />

<div class="g-plusone"></div>

<script async src="//platform.linkedin.com/in.js"></script><script type="IN/Share"></script>

<a href="//www.reddit.com/submit" onclick="window.location = '//www.reddit.com/submit?url=' + encodeURIComponent(window.location); return false"><img src="//www.redditstatic.com/spreddit7.gif" alt="submit to reddit" style="border:none; position:relative; top:0; vertical-align:top !important; padding:0;margin:0;" /></a>

<div id="google_translate_element" style="vertical-align:top !important; padding-bottom:0; margin-bottom:0;"></div>
<script>
function googleTranslateElementInit() {
	new google.translate.TranslateElement({pageLanguage: 'en'}, 'google_translate_element');
}
</script>
<script async src="//translate.google.com/translate_a/element.js?cb=googleTranslateElementInit"></script>

</div>
<!-- end of google-translate.html -->

Despite following Google-recommended style adjustments, the various buttons never line up. And once again, Facebook has to be spoon-fed. At least the others can figure out where the click is coming from.

Finally, the Footer

The standard footer is included at the very end of the HTML body on every page.

		<?php include($_SERVER['DOCUMENT_ROOT'].'/ssi/footer.html'); ?>
	</body>
</html>

The obvious effect of the standard footer is that it includes some Amazon and Google blocks, links to other major areas on my site, and some boilerplate and disclaimers.

Amazon "Native Ads" are auto-selected if Amazon can figure out what the page is about.

Google "Matched Content" is a mix of Google's notion of some related pages on my site, plus one or more ads.

What isn't obvious is that some Javascript is included here at the very end of the file. Some of the Javascript needed for Responsive Web Design and social media buttons can be pushed down to the bottom of the page. The responsive design and buttons will still work, and the page appears to have loaded faster. Actually it just gets the top part loaded and laid out well enough for the user to start reading, while the data and Javascript loads and components further down the page are rearranged.

The user is most likely to notice that the social media buttons load a little slowly, as they're all the way at the top of the page.

/ssi/footer.html

Notice that footer.html is included, and then it includes a file. Nested inclusion is possible, within cautious reason.

The HTML5 validator now needs to have a custom URL built for it.

<!-- start of footer.html -->
<footer>
<nav class="cb centered">
<hr style="margin-bottom: 2px; padding-bottom: 0px;" />

<!-- Google matched content -->
<?php include($_SERVER['DOCUMENT_ROOT'].'/ads/matched-content.html'); ?>

<div class="row centered" style="margin-top:1px; padding-top:0; margin-bottom: 2px;">
	<div class="col-6 col-md-3 col-lg-1">
	<a href="/" class="btn btn-info btn-sm btn-block">
		Home</a>
	</div>
	<div class="col-6 col-md-3 col-lg-2">
	<a href="/travel/" class="btn btn-info btn-sm btn-block">
		Travel</a>
	</div>
	<div class="col-6 col-md-3 col-lg-2">
	<a href="/open-source/" class="btn btn-info btn-sm btn-block">
		Linux/Unix</a>
	</div>
	<div class="col-6 col-md-3 col-lg-2">
	<a href="/cybersecurity/" class="btn btn-info btn-sm btn-block">
		Cybersecurity</a>
	</div>
	<div class="col-6 col-md-3 col-lg-2">
	<a href="/networking/" class="btn btn-info btn-sm btn-block">
		Networking</a>
	</div>
	<div class="col-6 col-md-3 col-lg-1">
	<a href="/technical/" class="btn btn-info btn-sm btn-block">
		Technical</a>
	</div>
	<div class="col-6 col-md-3 col-lg-1">
	<a href="/radio/" class="btn btn-info btn-sm btn-block">
		Radio</a>
	</div>
	<div class="col-6 col-md-3 col-lg-1">
	<a href="/site-map.html" class="btn btn-info btn-sm btn-block">
		Site Map</a>
	</div>
</div>

<div class="fr hidden-sm-down">
<a href="https://jigsaw.w3.org/css-validator/check/referer">
<img src="/ssi/valid-css.png"
	alt="Valid CSS.  Validate it here."
	style="padding: 8px 0 0 0; margin: 0;" /></a>
</div>
<div class="fr hidden-sm-down">
<?php echo('<a href="https://validator.w3.org/nu/?showsource=yes&doc=https://'.$_SERVER['HTTP_HOST'].$_SERVER['PHP_SELF'].'">'); ?>
<img src="/ssi/html5-badge-h-css3-semantics.png"
	alt="Valid HTML 5.  Validate it here."
	style="padding: 0; margin: 0;" /></a>
</div>
<div class="fr hidden-sm-down">
<a href="https://www.unicode.org/">
<img src="/ssi/unicode.png"
	alt="Valid Unicode."
	style="padding: 8px 0 0 0; margin: 0;" /></a>
</div>
<div class="fr">
<a href="https://www.freebsd.org/">
<img src="/ssi/powerlogo.gif"
	alt="FreeBSD" /></a>
</div>

<!-- Javascript to display browser viewport dimensions, to help with responsive web design -->
<aside>
<p style="float: right; margin-left: 5px; font-size: 80%; text-align: right;">Viewport size:<br />
<span id="w"></span>
<span id="h"></span>
<script>
	(function() {
		if (typeof(document.documentElement.clientWidth) != 'undefined') {
			var $w = document.getElementById('w'),
			$h = document.getElementById('h'),
			$ua = document.getElementById('ua');
			$w.innerHTML = document.documentElement.clientWidth;
			$h.innerHTML = ' × ' + document.documentElement.clientHeight;
			window.onresize = function(event) {
				$w.innerHTML = document.documentElement.clientWidth;
				$h.innerHTML = ' × ' + document.documentElement.clientHeight;
			};
			$ua.innerHTML = navigator.userAgent;
	}
	})();
</script>
</aside>

<p style="clear:right; font-size:90%; text-align:center;">
© by
<a href="/contact.html">Bob Cromwell</a>
<?php echo date("M Y\. ") ; ?>
Created with
<a href="http://thomer.com/vi/vi.html"><code>vim</code></a>
and
<a href="https://www.imagemagick.org/">ImageMagick</a>,
hosted on
<a href="https://www.freebsd.org/">FreeBSD</a>
with
<a href="https://httpd.apache.org/">Apache</a>.
<br />
Root password available
<a href="/cybersecurity/root-password.html">here</a>,
privacy policy <a href="/cybersecurity/privacy-policy.html">here</a>,
contact info <a href="/contact.html">here</a>.
</p>

</nav>


</footer>
</article>

<!-- moved to footer and async load for speed -->
<script async src="/js/modernizr.min.js"></script>
<script async src="/js/jquery.min.js"></script>
<script async src="/js/bootstrap.min.js"></script>

<!-- social media button support -->

<!-- twitter support -->
<script>!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0];if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src="https://platform.twitter.com/widgets.js";fjs.parentNode.insertBefore(js,fjs);}}(document,"script","twitter-wjs");</script>

<!-- facebook support -->
<div id="fb-root"></div>
<script>(function(d, s, id) {
  var js, fjs = d.getElementsByTagName(s)[0];
  if (d.getElementById(id)) return;
  js = d.createElement(s); js.id = id;
  js.src = "https://connect.facebook.net/en_US/all.js#xfbml=1";
  fjs.parentNode.insertBefore(js, fjs);
}(document, 'script', 'facebook-jssdk'));</script>

<!-- google +1 -->
<script>
   window.___gcfg = { lang: 'en-US' };
   (function() {
      var po = document.createElement('script'); po.type = 'text/javascript'; po.async = true;
      po.src = 'https://apis.google.com/js/plusone.js';
      var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(po, s);
    })(); </script>
<!-- end of footer.html -->

Validation

So you have made many changes. Are they correct?

Validate your pages to make sure that they are correct HTML. If you used some web authoring tool, they very likely will not be correct HTML. Also check the Cascading Style Sheet markup.

Google has a Structured Data Testing Tool to see if your microcode was properly generated.

While you are improving your page, verify the links to make sure they work:
Link Valet Broken Link Checker