<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE html>
<html b:version='2' class='v2' expr:dir='data:blog.languageDirection' lang='en-US' xmlns='http://www.w3.org/1999/xhtml' xmlns:b='http://www.google.com/2005/gml/b' xmlns:data='http://www.google.com/2005/gml/data' xmlns:expr='http://www.google.com/2005/gml/expr'>
<head>
<meta template='1.1'/>
<meta charset='UTF-8'/>
<meta content='width=device-width, initial-scale=1' name='viewport'/>
<title><data:blog.title/></title>
    
  <!--Favicon or serach results image-->
  <link href='https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjwMmqtq5dRpmvlNLPmUDGnytksf0IkGol8E0LwUplx-c0T25cwV7F2QJU5c2NtbR02Gz89UwBFE8sRz3yai5Zo30Wi6Z1Tia1C5cxL3m9NRPJxL8I_vNgfFFp3h-dQ7R3FS-uk2_MsYuQM5UXC3ZzOKGDqh54XJNOdXsF8SkdUhyphenhyphenCgyX8h20hWC4C7kxdR/s1600/android-chrome-192x192.png' rel='icon'/>
  
 <!-- external libraries -->
  
	<!--script src='https://cdn.jsdelivr.net/npm/marked/lib/marked.umd.js'/-->
    <script src='https://unpkg.com/turndown/dist/turndown.js'/>  
<script src='https://cdn.jsdelivr.net/gh/vanjs-org/van/public/van-1.6.1.nomodule.min.js' type='text/javascript'/>  
 <!-- font links -->
    <link href='https://fonts.googleapis.com' rel='preconnect'/>
    <link href='https://fonts.gstatic.com' rel='preconnect'/>
    <link href='https://fonts.googleapis.com/css2?family=Taviraj:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900' rel='stylesheet'/>
    <link href='https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900' rel='stylesheet'/>

    <!-- button icon -->
    <link href='https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@24,400,0,0' rel='stylesheet'/>
<!--  the next style tag gets filled automatically with css from availabooktools.blogspot.com-->
<style id='dynamic-css' type='text/css'/>  
  
  

  
  
<!--If we ever get page loads showing before the css loads, we can put the current copy of the css here to be sure there is always soem css, but it seems to load fast enough relying on the call above that brings in css dynamically-->  
<b:skin><![CDATA[
:root {
--font-zoom: 1;
--page-bg: #f8fafc;
--surface: #ffffff;
--surface-soft: #f1f5f9;
--surface-muted: #e2e8f0;
--text: #172033;
--text-muted: #64748b;
--heading: #0f172a;
--accent: #2563eb;
--accent-strong: #1d4ed8;
--accent-soft: #dbeafe;
--danger: #dc2626;
--warning: #fef3c7;
--border: #dbe3ee;
--shadow-sm: 0 1px 2px rgba(15, 23, 42, 0.06);
--shadow-md: 0 12px 30px rgba(15, 23, 42, 0.12);
--shadow-lg: 0 24px 60px rgba(15, 23, 42, 0.18);
--radius-sm: 0.5rem;
--radius-md: 0.85rem;
--radius-lg: 1.35rem;
}
* {
box-sizing: border-box;
}
html {
scroll-behavior: smooth;
}
body {
margin: 0;
min-height: 100vh;
font-family: "Roboto", "Helvetica Neue", Arial, sans-serif;
font-weight: 400;
font-style: normal;
font-size: calc(1rem  * var(--font-zoom));
line-height: 1.72;
color: var(--text);
background: var(--page-bg);
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
}

h1:has(span.header-section-number){
display:none;
}

a.footnote-link {
    vertical-align: super;
    font-size: smaller; /* Or use a percentage like 70% or 75% */
    line-height: normal;
    padding: 0 5px;
    background-color: bisque;
    border-radius: 3px;
}

.material-symbols-outlined{
font-size: calc(1rem  * var(--font-zoom));
cursor:pointer;
user-select:none;
}
summary::marker {
color: var(--text-muted);
}
#msg-galley{
position:fixed;
top:0;
right:0;
height:100vh;
min-width:calc(20rem  * var(--font-zoom));
max-width:50%;
z-index:12;
background-color: transparent;
pointer-events: none;
padding: 1rem 1rem 0 0;
font-family: "Roboto", sans-serif;
font-optical-sizing: auto;
font-weight: 400;
font-style: normal;
font-variation-settings: "wdth" 100;
}
.msg-close{
position:absolute;
top:calc(3px  * var(--font-zoom));
right:calc(3px  * var(--font-zoom));
display: inline-flex;
align-items: center;
justify-content: center;
width: 1.25rem;
height: 1.25rem;
padding: 0;
background-color:var(--danger);
color: white;
font-weight:700;
font-size:calc(.7rem  * var(--font-zoom));
border-radius: 999px;
cursor:pointer;
}
.msg-dialog{
position: relative;
background-color:var(--surface);
margin-bottom:.75rem;
pointer-events: auto;
border-radius:var(--radius-md);
border: 1px solid var(--border);
box-shadow: var(--shadow-md);
overflow: hidden;
}
.msg-button-{
margin: 0 .5rem .5rem 0;
}
.msg-button-bar{
background-color:var(--surface-soft);
text-align:center;
padding:.75rem;
border-top: 1px solid var(--border);
}
.msg-title{
background:var(--accent);
color:white;
font-weight:700;
padding:.55rem 2rem .55rem .85rem;
}
.msg-message{
background-color:var(--surface);
padding: .85rem;
}
#page-data{
display:none
}
.toast{
position:fixed;
bottom:2rem;
left:50%;
transform:translateX(-50%);
z-index:9999;
background-color:purple;
color:#fff;
padding:.7rem 1.25rem;
border-radius:var(--radius-md);
box-shadow:var(--shadow-lg);
}
.toast.success{
background-color:#15803d;
}
.toast.error{
background-color:var(--danger);
}
#menu{
top:0;
left:-100px;
position:fixed;
display:none;
background:var(--surface);
padding:0;
z-index:11;
transition: left .25s ease, box-shadow .25s ease;
height:100vh;
min-width:min(22rem, 88vw);
max-width:75vw;
overflow: auto;
border-right: 1px solid var(--border);
box-shadow: var(--shadow-lg);
}
#book-data{display:none}
#menu-content{
padding:1rem 1.25rem 2rem;
}
.menu-button{
display:inline-flex;
align-items:center;
justify-content:center;
min-width:2.4rem;
height:2.4rem;
padding:0 .6rem;
cursor:pointer;
color:var(--text-muted);
border-radius:999px;
transition: background .18s ease, color .18s ease, transform .18s ease;
vertical-align:middle;
}
.menu-button:hover{
background:var(--accent-soft);
color:var(--accent-strong);
transform: translateY(-1px);
}
.menu-header{
top:0;
position:sticky;
background:var(--surface);
opacity:1;
margin:0;
padding:.45rem .6rem;
border-bottom:1px solid var(--border);
box-shadow: var(--shadow-sm);
z-index:1;
}
#book-title{
font-weight:700;
letter-spacing:.02em;
}
#search-div{
width:100%;
white-space: nowrap;
margin-top: 1.5rem;
}
#search-div h6{
margin:0 0 .45rem 0;
font-size:.78rem;
font-weight:700;
letter-spacing:.08em;
text-transform:uppercase;
color:var(--text-muted);
}
#search{
width: calc(100% - 2.25rem);
border:1px solid var(--border);
border-radius:999px;
padding:.55rem .85rem;
font: inherit;
font-size:.92rem;
color:var(--text);
background:var(--surface);
box-shadow: var(--shadow-sm);
outline:none;
transition: border-color .18s ease, box-shadow .18s ease;
}
#search:focus{
border-color:var(--accent);
box-shadow:0 0 0 4px rgba(37, 99, 235, .13);
}
#search-button{
padding-left:.45rem;
color:var(--accent);
vertical-align:middle;
}
#search-results{
font-size:.86rem;
margin-top: .85rem;
}
.search-result{
margin-top:.65rem;
padding:.75rem;
cursor:pointer;
border:1px solid var(--border);
border-radius:var(--radius-sm);
background:var(--surface);
box-shadow: var(--shadow-sm);
transition: transform .18s ease, border-color .18s ease, box-shadow .18s ease;
}
.search-result:hover{
background-color:#f8fbff;
border-color:#bfdbfe;
box-shadow:0 10px 24px rgba(37, 99, 235, .1);
transform: translateY(-1px);
}
.search-result-title{
font-weight:bold;
color:var(--heading);
}
.search-result-line{
margin:.25rem 0 .35rem .75rem;
color:var(--text-muted);
}
.search-term{
background-color:var(--warning);
border-radius:.2rem;
padding:0 .15rem;
}
.chapter-result-link{
color:var(--heading);
}
.toc-section-container{
margin-left:.75rem;
}
.toc-text-container{
margin-left:1.25rem;
color:var(--text-muted);
}
.chapter-section{
margin-top: calc(3rem  * var(--font-zoom));
margin-bottom: calc(3rem  * var(--font-zoom));
}
.hide{
display:none
}
img{
max-width:100%;
height:auto;
border-radius:var(--radius-md);
}
.entry-header{
position:fixed;
top:0;
left: 0;
width: 100%;
transition:opacity 0.18s ease, transform 0.18s ease, box-shadow 0.18s ease;
padding: .35rem .75rem;
-webkit-user-select: none; /* Safari */
user-select: none; /* Standard syntax */
background: var(--surface);
border-bottom:1px solid var(--border);
box-shadow:var(--shadow-sm);
z-index:10;
}
button.nav{
border-radius:999px;
border:1px solid var(--border);
background:var(--surface);
color:var(--accent);
box-shadow: var(--shadow-md);
width:2.8rem;
height:2.8rem;
display:flex;
align-items:center;
justify-content:center;
cursor:pointer;
}
.nav{
position: fixed;
bottom: 1rem;
opacity:1.0;
transition:opacity 0.35s ease, transform 0.18s ease, box-shadow 0.18s ease;
pointer-events: all;
}
.nav:hover{
transform: translateY(-2px);
box-shadow: var(--shadow-lg);
}
.dim-button:hover{opacity: 1.0;}
.dim-button{opacity: .35;}
.dim-header:hover{opacity: 1.0;}
.dim-header{opacity: 0;}
#prior-button {
left: 1rem;
}
#next-button {
right: 1rem;
}
.span-link{
cursor:pointer;
color:var(--accent);
font-weight:500;
}
.span-link:hover{
text-decoration: underline;
color:var(--accent-strong);
}
a:link {color: var(--accent); text-decoration:none}
a:visited {color: #4f46e5;}
a:hover {background-color: var(--accent-soft); color: var(--accent-strong); border-radius:.25rem;}
a:active {color: var(--accent-strong);}
.book-title{
margin-top: 6rem;
font-family:"Taviraj", Georgia, serif;
font-size:clamp(2.4rem, 7vw, calc(4rem  * var(--font-zoom)));
line-height:1.05;
text-align: center;
font-weight: 700;
color:var(--heading);
}
.book-author{
margin-top: 1rem;
font-size:calc(1.45rem  * var(--font-zoom));
text-align: center;
color:var(--text-muted);
}
.book-toc{
text-align: center;
margin-top:2rem;
}
.book-chapters{
display: inline-block;
}
.book-chapter{
text-align:left;
padding-left: 1rem;
text-indent: -1rem;
margin-bottom: 0.55rem;
}
.tool {
cursor:pointer;
font-size:calc(18px  * var(--font-zoom));
font-weight:400;
vertical-align: middle;
padding-left:.6rem;
color:var(--text-muted);
transition: color .18s ease, transform .18s ease;
}
.tool:hover {
color:var(--accent);
transform: translateY(-1px);
}
.lead-in{
margin-top:4rem;
}
.example, .indent, .category{
margin:1rem 0 0 1.1rem;
}
.category div:first-child{
color:var(--danger);
font-weight:700;
}
.example div:first-child{
color:var(--danger);
font-style: italic;
}
h1{
font-family:"Taviraj", Georgia, serif;
font-size:clamp(1.35rem, 4vw, calc(2.1rem * var(--font-zoom)));
line-height:1.18;
color:var(--heading);
font-weight:700;
margin:0;
}
h2, h3{
font-family:"Taviraj", Georgia, serif;
color:var(--heading);
line-height:1.24;
letter-spacing:-.015em;
}
h2{
font-size:clamp(1.55rem, 4vw, calc(2.15rem * var(--font-zoom)));
margin:3rem 0 1rem;
padding-top:.5rem;
}
h3{
font-size:clamp(1.2rem, 3vw, calc(1.45rem * var(--font-zoom)));
margin:2.25rem 0 .8rem;
}
.header-section-number{
color:var(--accent);
font-weight:700;
}
p{
margin-top:0;
margin-bottom:1.15rem;
}
p.vignette{
color:var(--danger)
}
.no-dot{
list-style-type: none;
}
ul.no-dot li{
padding-left: 1rem;
text-indent: -1rem;
margin-bottom: 0.3rem;
}
audio{
height:30px;
width:100%;
max-width:34rem;
}
div.post-body{
max-width:940px;
margin:0 auto;
padding:clamp(5.2rem, 7vw, 6.25rem) clamp(1.15rem, 4vw, 3.5rem) 4rem;
background:transparent;
}
div.index-boxnbt{
background:var(--surface);
padding-bottom:2rem;
}
.material-symbols-outlined {
font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 20;
font-size: 18px;
line-height: 1;
}

			
blockquote{
margin:1.75rem 0;
padding:1rem 1.25rem 1rem 1.35rem;
background:var(--surface-soft);
border-left:.35rem solid var(--accent);
border-radius:0 var(--radius-md) var(--radius-md) 0;
color:#334155;
}
blockquote.note{
background:#E8F5E9;
border-left:.35rem solid darkgreen;
}
blockquote.note::before {
content: "Note: ";
font-weight: bold;
color:darkgreen;
}
blockquote.tip{
background:#F3E5F5;
border-left:.35rem solid purple;
}
blockquote.tip::before {
content: "Tip: ";
font-weight: bold;
color:purple;
}
blockquote.term{
background:#E3EDF7;
border-left:.35rem solid darkblue;
}
blockquote.term::before {
content: "Term: ";
font-weight: bold;
color:#2B7FFF;
}
blockquote.caution{
background:#FFE2E2;
border-left:.35rem solid red;
}
blockquote.caution::before {
content: "Caution: ";
font-weight: bold;
color:darkred;
}
blockquote.review {
background:#FFF0EC;
border-left:.35rem solid salmon;
}
blockquote.review::before {
content: "Review: ";
font-weight: bold;
color:salmon;
}

blockquote p:last-child{
margin-bottom:0;
}
div.learning {
  background-color: darkblue;
  padding-top: .4rem;
  font-family: "Taviraj", Georgia, serif;
  font-size: 1.5rem;
  border-radius: 4px;
  margin: 1rem;
  color:lightblue;
  font-weight: bold;
  text-align: center;
  margin-bottom: .5rem;
}

.learning .objectives{
  font-size: 1rem;
  margin-top:0;
  font-family: "Roboto", "Helvetica Neue", Arial, sans-serif;;
}

.objectives{
  display:block;
  background:#E3EDF7;
  border-left:.35rem solid darkblue;
  border-right:.35rem solid darkblue;
  border-bottom:.35rem solid darkblue;
  border-radius:4px;
  padding:.75rem 1rem;
}
.objectives::before {
  content: "After completing this lesson, the successful student will be able to:";
  display:block;
  font-weight: bold;
  color:darkblue;
  text-align: left;
  margin-bottom:.5rem;
}
.objectives ol{
  margin:0;
  font-weight:normal;
  text-align:left;

}



hr{
height:1px;
border:0;
background:var(--border);
margin:2.75rem 0;
}
ul, ol{
padding-left:1.45rem;
margin:0 0 1.25rem;
}
li{
margin:.45rem 0;
}
code{
font-family:"SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
font-size:.92em;
color:#0f766e;
background:#ecfdf5;
border:1px solid #bbf7d0;
border-radius:.35rem;
padding:.08rem .32rem;
}
pre code,
.sourceCode code{
color:inherit;
background:transparent;
border:0;
border-radius:0;
padding:0;
font-size:inherit;
}
pre,
pre.javascript-code,
pre.javascript-example,
pre.spreadsheet,
div.sourceCode pre{
margin:1rem 0;
padding:.75rem .85rem;
overflow:auto;
background:#0f172a;
color:#dbeafe;
border:1px solid rgba(148, 163, 184, .28);
border-radius:var(--radius-md);
box-shadow:var(--shadow-md);
font-family:"SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
font-size:.88rem;
line-height:1.38;
tab-size:2;
white-space:pre;
}
div.sourceCode{
margin:1rem 0;
}
div.sourceCode pre{
margin:0;
}
.sourceCode .kw,
.sourceCode .cf{
color:#93c5fd;
}
.sourceCode .fu,
.sourceCode .at{
color:#67e8f9;
}
.sourceCode .st{
color:#86efac;
}
.sourceCode .dv{
color:#fbbf24;
}
.sourceCode .co{
color:#94a3b8;
font-style:italic;
}
table.blog{
display:block;
width:max-content;
max-width:100%;
border-collapse:separate;
border-spacing:0;
margin:1.5rem 0 2rem;
overflow-x:auto;
-webkit-overflow-scrolling:touch;
border:1px solid var(--border);
border-radius:var(--radius-md);
box-shadow:var(--shadow-sm);
background:var(--surface);
}
th.blog,
td.blog{
padding:.72rem .85rem;
border-bottom:1px solid var(--border);
text-align:left;
vertical-align:top;
white-space:nowrap;
}
th.blog{
font-size:.86rem;
font-weight:700;
color:var(--heading);
background:var(--surface-soft);
}
tr.blog:last-child td{
border-bottom:0;
}
tr.blog:nth-child(even) td{
background:#f8fafc;
}
.monaco {
width: 100%;
margin: 1.5rem 0;
border-radius: var(--radius-md);
overflow: hidden;
box-shadow: var(--shadow-lg);
border: 1px solid rgba(148, 163, 184, 0.22);
}
.monaco-bar {
display: flex;
align-items: center;
justify-content: flex-end;
gap: 2px;
padding: 8px 10px;
background: linear-gradient(180deg, #1f2937, #111827);
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
}
.monaco-bar-label {
flex: 1;
font-family: "Roboto", sans-serif;
font-size: 11px;
font-weight: 700;
color: #94a3b8;
letter-spacing: 0.5px;
text-transform: uppercase;
padding-left: 4px;
}
.monaco-tool {
display: flex;
align-items: center;
justify-content: center;
width: 30px;
height: 30px;
border-radius: 6px;
color: #8b949e;
cursor: pointer;
transition: background 0.15s ease, color 0.15s ease;
}
.monaco-tool:hover {
background: rgba(255, 255, 255, 0.1);
color: #e6edf3;
}
.monaco-tool:active {
background: rgba(255, 255, 255, 0.06);
transform: scale(0.92);
}
.monaco-tool[data-action="run"]:hover {
background: rgba(63, 185, 80, 0.18);
color: #3fb950;
}
.editor-holder {
background-color: #1e1e1e;
padding: 8px 0;
}
.editor {
margin: 0;
padding: 0;
}
.console {
background: #161b22;
border-top: 1px solid rgba(255, 255, 255, 0.07);
padding: 8px 0 6px;
position: relative;
min-height: 36px;
font-family: 'Consolas', 'Cascadia Code', 'Courier New', monospace;
font-size: 13px;
}
.console-line {
color: #c9d1d9;
padding: 2px 40px 2px 14px;
line-height: 1.6;
white-space: pre-wrap;
word-break: break-all;
}
.console-line:first-of-type {
margin-top: 2px;
}
.console-line.error {
color: #f85149;
background: rgba(248, 81, 73, 0.06);
}
span.exitButton {
position: absolute;
top: 6px;
right: 8px;
display: flex;
align-items: center;
justify-content: center;
width: 22px;
height: 22px;
border-radius: 4px;
color: #6e7681;
cursor: pointer;
transition: background 0.15s ease, color 0.15s ease;
}
span.exitButton:hover {
background: rgba(255, 255, 255, 0.1);
color: #c9d1d9;
}
span.exitButton .material-symbols-outlined {
font-size: 15px;
}
pre.code {
color: #a8ff78;
background: #0d1117;
padding: 1rem 1.15rem;
margin: 1.35rem 0;
font-size: 14px;
border-radius: var(--radius-md);
border: 1px solid rgba(255, 255, 255, 0.07);
overflow-x: auto;
}
@media (max-width: 700px) {
body {
font-size: calc(.98rem * var(--font-zoom));
}
#msg-galley {
left: 0;
right: 0;
max-width: none;
min-width: 0;
padding: .75rem;
}
#menu {
max-width: 92vw;
}
.entry-header {
padding: .25rem .4rem;
}
div.post-body {
padding-left: 1rem;
padding-right: 1rem;
}
.nav {
bottom: .75rem;
}
#prior-button {
left: .75rem;
}
#next-button {
right: .75rem;
}
}


.entry-header .post-title{
  display:flex;
  align-items:center;
  gap:.4em;
  min-width:0;
  flex:1 1 auto;
  overflow:hidden;
}
.entry-header .post-title #title{
  overflow:hidden;
  text-overflow:ellipsis;
  white-space:nowrap;
  min-width:0;
}

#tools {
  margin-top: 12px;
  padding-top: 12px;
  border-top: 1px dashed #e2d5b8;
  font-size: 0.9rem;
  color: #4a4038;
}


]]></b:skin>  
  
  
<script type='text/javascript'>
//<![CDATA[

//////////////////////////////////////////////////////////////////////////////////////////////




  var globals = {
    bookInfo: null,
    variables: {
      fontZoom: 1
    }
  };

  function tag(id) {
    return document.getElementById(id);
  }

  // Chapters used to carry a full post URL (protocol + domain + path); the
  // "toc" post now stores just the bare filename instead (e.g.
  // "material-culture.html"). Resolving against window.location.href as a
  // base handles both: a full URL ignores the base entirely, while a bare
  // filename resolves the same way a plain <a href="material-culture.html">
  // would from the current page — which is what makes chapterHref line up
  // with window.location.pathname in buildMenu()'s isCurrent check below.
  // That reconstruction only lands correctly because every book page (each
  // chapter and the toc post itself) is published under the same
  // "/2026/09/" directory; a bare filename can't be resolved into that
  // directory from anywhere else.
  function relativePath(url) {
    if (!url) return "";
    try {
      return new URL(url, window.location.href).pathname;
    } catch (e) {
      return url;
    }
  }

  // The blog is expected to have exactly one post labeled "toc" whose body
  // is nothing but the bookInfo JSON (title, blogUrl, chapters[]) — the same
  // shape globals.bookInfo has in system.html. Same-origin, so a plain
  // fetch() works (no JSONP needed here, unlike the cross-domain loader).
  async function loadBookInfo() {
    try{
      var res = await fetch('/feeds/posts/default/-/toc?alt=json&max-results=1');
      var feed = await res.json();
      var entry = feed && feed.feed && feed.feed.entry && feed.feed.entry[0];

      if (!entry) {
        console.error('No post found with the "toc" label.');
        return null;
      }
      return JSON.parse(entry.content.$t);
    } catch (e) {
       //try to get toc from localhost in case we are running on localhost
      var res = await fetch('toc.json');
      return await res.json();
    }
  }

  // Ported from buildMenu() in system.html (https://system.availabooks.com/1970/02/system.html),
  // trimmed to just the chapter/section table of contents — the course,
  // search and AI-tools panels in that version depend on other subsystems
  // (enrollment, assessments) that aren't part of this loader.
  function buildMenu() {
    var html = [
      "<div class='menu-header'>" +
        "<span class='menu-button' onclick='hideMenu()'>&#10005;</span>" +
        "<span id='book-title'><a href='toc.html'>" + globals.bookInfo.title + "</a></span>" +
      "</div>" +
      "<div id='menu-content'>"
    ];

    html.push(
      "<div id='search-div'>" +
        "<h6>Search Book</h6>" +
        "<input id='search' placeholder='search book'/>" +
        "<span class='material-symbols-outlined tool' id='search-button'>search</span>" +
      "</div>"
    );
    html.push("<div id='search-results'></div>");

    html.push("<div id='toc'>");

    var currentPath = window.location.pathname;

    for (var i = 0; i < globals.bookInfo.chapters.length; i++) {
      var chapter = globals.bookInfo.chapters[i];
      var chapterHref = relativePath(chapter.url);
      var isCurrent = chapterHref === currentPath;

      if (isCurrent) {
        globals.bookInfo.currentChapter = chapter.id;
        // Feeds navigate(): the ordered chapters array already carries
        // everything needed for prior/next, so there's no need to re-derive
        // position from the URL a second time.
        globals.bookInfo.chapterNav = {
          prior: globals.bookInfo.chapters[i - 1] || null,
          next: globals.bookInfo.chapters[i + 1] || null
        };
      }

      if (chapter.sections) {
        html.push(isCurrent ? "<details open>" : "<details>");
        getChaptSections(chapter, html, chapterHref);
        html.push("</details>");
      } else {
        var label = "";
        if (chapter.label) {
          label = chapter.label + ": ";
        }
        html.push("<div>" + label + "<a href='" + chapterHref + "'>" + chapter.text + "</a></div>");
      }
    }

    html.push("</div>"); // close #toc

    html.push(
      "<div id='tools'>" +
        "Text Size: " +
        "<span class='material-symbols-outlined tool' onclick='fontSize(.1)'>text_increase</span>" +
        "<span class='material-symbols-outlined tool' onclick='fontSize(-.1)'>text_decrease</span>" +
        "<span class='material-symbols-outlined tool' onclick='fontSize()'>rotate_auto</span>" +
      "</div>"
    );

    html.push("</div>"); // close #menu-content

    tag("menu").innerHTML = html.join("\n");

    // The search box/button are only in the DOM after the innerHTML
    // assignment above, so binding has to happen here rather than once at
    // page load.
    tag("search").addEventListener("keydown", function (event) {
      if (event.key === "Enter") {
        event.preventDefault();
        searchBook();
      }
    });
    tag("search-button").addEventListener("click", searchBook);
  }

  // Ported from getChaptSections() in system.html — recursively renders a
  // chapter's nested sections as <details>/<summary> for sub-sections and
  // plain links for leaf sections. Sections don't have their own url (they're
  // just headings inside the chapter's post), so chapterHref — the owning
  // chapter's relative URL — is threaded through the recursion and reused
  // for every link, with "#{section.id}" appended for leaf sections.
  function getChaptSections(obj, html, chapterHref) {
    var label = "";
    if (obj.label) {
      label = obj.label + ": ";
    }

    html.push("<summary>");
    html.push("<span>" + label + "</span><span><a href='" + chapterHref + "'>" + obj.text + "</a></span>");
    html.push("</summary>");

    for (var i = 0; i < obj.sections.length; i++) {
      var child = obj.sections[i];
      if (child.sections) {
        html.push("<div class='toc-section-container'><details>");
        getChaptSections(child, html, chapterHref);
        html.push("</details></div>");
      } else {
        html.push("<div class='toc-text-container'>");
        html.push("<a href='" + chapterHref + "#" + child.id + "'>" + child.text + "</a>");
        html.push("</div>");
      }
    }
  }

  function showMenu() {
    var menuWidth = tag("menu").offsetWidth;
    if (menuWidth === 0) {
      tag("menu").style.display = "block";
      menuWidth = tag("menu").offsetWidth;
    }
    tag("menu").style.left = "0";

    // #search only exists once buildMenu() has run (bookInfo loaded
    // successfully), so guard rather than assume it's there.
    var searchInput = tag("search");
    if (searchInput) searchInput.focus();
  }

  function hideMenu() {
    var menuWidth = tag("menu").offsetWidth;
    tag("menu").style.left = "-" + (menuWidth + 10) + "px";
  }

  // Closes the menu on any click outside it, in addition to the explicit
  // "x" (menu-header's menu-button, wired to hideMenu() already). Bound on
  // document rather than e.g. a backdrop element, since the menu is an
  // off-canvas panel with no full-page overlay behind it. Checked against
  // showMenu()'s own left offset rather than a separate open/closed flag,
  // so there's one source of truth for whether the menu is open — but
  // read back through parseFloat() rather than compared directly against
  // showMenu()'s "0": the CSSOM normalizes a unitless length once it's
  // set, so el.style.left, read back after `el.style.left = "0"`, is
  // actually "0px", not "0" — a direct !== "0" check was always true and
  // silently defeated every guard below it on every single click. The
  // button that opens the menu (the hamburger in the page header) isn't a
  // fixed id in the deployed template — it's a <span onclick='showMenu()'>
  // living outside #menu — so it's matched by that onclick attribute
  // rather than an id, which keeps the click that opens the menu from
  // also immediately closing it again as that same click bubbles up to
  // document.
  //
  // A click inside #menu is otherwise left alone (typing in #search,
  // clicking the search/text-size buttons, or just clicking empty menu
  // background shouldn't dismiss it) — except a click on an <a> (a chapter
  // link, a section link, or the book-title link), which should still
  // close the menu even though it's inside #menu, since navigating away
  // (or jumping to an in-page section anchor, which doesn't reload the
  // page and so wouldn't otherwise close the menu on its own) is exactly
  // the kind of "the reader is done with the menu" action this is meant
  // to catch.
  document.addEventListener("click", function (event) {
    if(tag("menu")){ 
        if (parseFloat(tag("menu").style.left) !== 0) return;
        if (event.target.closest("[onclick*='showMenu']")) return;
        var clickedMenuLink = event.target.closest("#menu a");
        if (tag("menu").contains(event.target) && !clickedMenuLink) return;
        hideMenu();
    }
  });

  async function initMenu() {
    initFontZoom();
    highlightSearchTermFromUrl();
    globals.bookInfo = await loadBookInfo();
    if (!globals.bookInfo) return;
    buildMenu();
  }

  // A search result link (built in buildChapterSearchResult()) carries the
  // actual matched passage — the context snippet shown in the result, not
  // just the typed search term — as ?highlight=..., plus the bare term
  // itself as ?term=... as a fallback. On the landed page, try to find and
  // highlight the full snippet first (it's a much more specific match);
  // if that snippet happens to cross an inline element boundary (a <b>,
  // <a>, <em>, ...) and so isn't findable as one contiguous text-node
  // match, fall back to the shorter bare term instead of highlighting
  // nothing. Doesn't depend on bookInfo, so it runs independently of that
  // fetch.
  function highlightSearchTermFromUrl() {
    var params = new URLSearchParams(window.location.search);
    var snippet = params.get("highlight");
    var term = params.get("term");

    console.log("[highlight] params from URL:", { snippet: snippet, term: term });

    if (snippet && highlightAndScrollTo(snippet)) return;

    if (snippet) {
      console.log("[highlight] snippet did not match, falling back to term:", term);
    }

    if (term) highlightAndScrollTo(term);
  }

  // A multi-word snippet (5 words of context on each side of the search
  // term) very often spans an inline element — this content is full of
  // footnote links like <a href="...">72</a> sitting mid-sentence — so
  // testing one text node's nodeValue at a time (as an earlier version of
  // this did) almost never matched and silently fell back to the bare
  // term every time. Instead, concatenate all of the container's text
  // nodes into one string with their offsets recorded, search that, and
  // map the match back onto a Range that can span multiple nodes/elements.
  function buildTextIndex(container) {
    var walker = document.createTreeWalker(container, NodeFilter.SHOW_TEXT);
    var nodes = [];
    var fullText = "";
    var node;

    while ((node = walker.nextNode())) {
      nodes.push({ node: node, start: fullText.length });
      fullText += node.nodeValue;
    }

    console.log("[highlight] buildTextIndex:", {
      container: container,
      nodeCount: nodes.length,
      fullTextLength: fullText.length,
      fullTextPreview: fullText.slice(0, 200)
    });

    return { nodes: nodes, fullText: fullText };
  }

  // Turns `phrase` into a regex that matches it case-insensitively without
  // requiring its words to be strictly adjacent. A plain \s+ between words
  // assumes the live page's text is the same as the feed content the
  // search index reads from — but this template's own runtime sometimes
  // splices extra content between them at render time (e.g. the words
  // right after a chapter's title can get a "Learning Objectives" heading
  // glued on with zero separating whitespace, built from the post's own
  // intro list, that simply isn't present in the raw post body). So each
  // word is joined with "up to ~100 characters of anything" rather than
  // just whitespace, bridging over that kind of injected content instead
  // of silently failing to match altogether.
  function phraseToFlexibleRegex(phrase) {
    var words = phrase.trim().split(/\s+/).map(escapeRegExp);
    return new RegExp(words.join("[\\s\\S]{0,100}?"), "i");
  }

  // A single <mark> can't wrap a [matchStart, matchEnd) span that crosses
  // mismatched ancestor depths — e.g. from a shallow <h1> into a deeply
  // nested <li> inside <blockquote><ol> (surroundContents throws
  // "partially selected a non-Text node" for exactly that case, and this
  // template's injected content routinely creates spans like that). So
  // instead of one Range covering the whole match, wrap each text node's
  // own overlapping portion in its own separate <mark> — every such
  // sub-range lies entirely inside one text node, which surroundContents
  // always accepts. Returns the list of <mark> elements created, in order.
  function wrapMatchAcrossNodes(index, matchStart, matchEnd) {
    var marks = [];

    for (var i = 0; i < index.nodes.length; i++) {
      var entry = index.nodes[i];
      var nodeStart = entry.start;
      var nodeEnd = entry.start + entry.node.nodeValue.length;

      var overlapStart = Math.max(matchStart, nodeStart);
      var overlapEnd = Math.min(matchEnd, nodeEnd);

      if (overlapStart >= overlapEnd) continue; // this node isn't part of the match

      var range = document.createRange();
      range.setStart(entry.node, overlapStart - nodeStart);
      range.setEnd(entry.node, overlapEnd - nodeStart);

      var mark = document.createElement("mark");
      mark.className = "search-highlight";
      range.surroundContents(mark);
      marks.push(mark);
    }

    return marks;
  }

  // Finds the first case-insensitive match of `phrase` anywhere in the
  // chapter content (even split across inline elements), wraps it in a
  // <mark>, and scrolls it into view. Returns true if a match was found,
  // false otherwise, so the caller can fall back to a different phrase.
  function highlightAndScrollTo(phrase) {
    if (!phrase) return false;

    console.log("[highlight] highlightAndScrollTo called with phrase:", phrase);

    var container = document.querySelector(".entry-content") || document.body;
    console.log("[highlight] using container:", container, "(.entry-content found:", !!document.querySelector(".entry-content"), ")");

    var index = buildTextIndex(container);
    var regex = phraseToFlexibleRegex(phrase);
    console.log("[highlight] regex used for search:", regex);

    var match = regex.exec(index.fullText);
    console.log("[highlight] regex match result:", match);

    if (!match) {
      console.log("[highlight] no match found in container's full text for phrase:", phrase);

      // Debug aid: locate the phrase's first word on its own and dump the
      // actual surrounding characters (plus their char codes) — this
      // reveals mismatches you can't see by eye, like a curly '’'
      // apostrophe in the page vs. a straight "'" in the phrase (common
      // with Pandoc-generated content, which "smartens" quotes), or
      // whitespace the flexible regex still didn't account for.
      var firstWord = phrase.trim().split(/\s+/)[0];
      var probeIndex = index.fullText.toLowerCase().indexOf(firstWord.toLowerCase());

      if (probeIndex === -1) {
        console.log("[highlight] probe: first word of phrase (\"" + firstWord + "\") not found anywhere in fullText either.");
      } else {
        var probeSlice = index.fullText.slice(probeIndex, probeIndex + 60);
        console.log("[highlight] probe: first word (\"" + firstWord + "\") found at index " + probeIndex + ". Actual text there:", JSON.stringify(probeSlice));
        console.log("[highlight] probe: char codes for that slice:", Array.prototype.map.call(probeSlice, function (c) { return c.charCodeAt(0); }));
      }

      return false;
    }

    var marks = wrapMatchAcrossNodes(index, match.index, match.index + match[0].length);
    console.log("[highlight] wrapped", marks.length, "text-node segment(s) for match:", match[0]);

    if (marks.length === 0) {
      console.log("[highlight] wrapMatchAcrossNodes produced no marks for match indices:", match.index, match.index + match[0].length);
      return false;
    }

    marks[0].scrollIntoView({ behavior: "smooth", block: "center" });
    return true;
  }

  function escapeRegExp(text) {
    return text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
  }

  // Restores any text-size zoom the reader picked on a prior visit (saved
  // under the same "book-settings" localStorage key system.js uses) and
  // applies it via the --font-zoom custom property the template's
  // font-size rules are already written in terms of. Call this once on
  // page load so returning readers see their saved size immediately.
  function initFontZoom() {
    var stored = localStorage.getItem("book-settings");
    if (stored) {
      try {
        globals.variables = JSON.parse(stored);
      } catch (e) {
        // ignore corrupt/old data and fall back to the default below
      }
    }
    if (typeof globals.variables.fontZoom !== "number") {
      globals.variables.fontZoom = 1;
    }
    document.body.style.setProperty("--font-zoom", globals.variables.fontZoom);
  }

  // Ported from fontSize() in availabooks.com's system.js. Called with no
  // argument (the "rotate_auto" reset icon) it snaps back to 1x; otherwise
  // it nudges by the given amount (+/-0.1 from the increase/decrease
  // icons), rounded to avoid floating-point drift, then persists the
  // choice and re-applies --font-zoom.
  function fontSize(adjustment) {
    if (!adjustment) {
      globals.variables.fontZoom = 1;
    } else {
      globals.variables.fontZoom = Math.round((globals.variables.fontZoom + adjustment) * 10) / 10;
    }
    document.body.style.setProperty("--font-zoom", globals.variables.fontZoom);
    localStorage.setItem("book-settings", JSON.stringify(globals.variables));
  }

  // Moves to the prior/next chapter using globals.bookInfo.chapterNav,
  // computed in buildMenu() from the chapter's position in
  // globals.bookInfo.chapters — same approach as navigate() in
  // availabooks.com's system.js, adapted since our chapters carry a full
  // chapter.url rather than a ready-made relative href. #prior-button and
  // #next-button already exist in the template's markup with
  // onclick="navigate('prior'|'next')".
  function navigate(direction) {
    var nav = globals.bookInfo.chapterNav;
    if (!nav) return;

    if (direction === "prior") {
      window.location.href = nav.prior ? relativePath(nav.prior.url) : "toc.html";
    } else if (nav.next) {
      window.location.href = relativePath(nav.next.url);
    } else {
      alert("You have reached the end of this book.");
    }
  }

  // The header (hamburger + chapter title) and the prior/next nav buttons
  // already have the "dim-header"/"dim-button" CSS in place — opacity: 0 /
  // .35 by default, opacity: 1 on :hover, both with a transition. What was
  // missing is anything to actually add/remove those classes as the reader
  // scrolls. Bright near the top or bottom of the page, dimmed everywhere
  // in between; hovering restores full opacity via the CSS alone.
  function dimButtons(brightOrDim) {
    document.querySelectorAll(".nav").forEach(function (button) {
      if (brightOrDim === "bright") {
        button.classList.remove("dim-button");
      } else {
        button.classList.add("dim-button");
      }
    });
  }

  function dimHeader(brightOrDim) {
    var headers = document.getElementsByTagName("header");
    for (var i = 0; i < headers.length; i++) {
      if (brightOrDim === "bright") {
        headers[i].classList.remove("dim-header");
      } else {
        headers[i].classList.add("dim-header");
      }
    }
  }

  function setDimness() {
    var scrollTop = document.documentElement.scrollTop;
    var scrollHeight = document.documentElement.scrollHeight;
    var clientHeight = document.documentElement.clientHeight;

    if (scrollTop < 20 || Math.abs((scrollTop + clientHeight) - scrollHeight) < 5) {
      dimButtons("bright");
      dimHeader("bright");
    } else {
      dimButtons("dim");
      dimHeader("dim");
    }
  }

  window.onscroll = setDimness;

  // Ported from searchBook() in system.html
  // (https://system.availabooks.com/1970/02/system.html). Re-queries the
  // blog's own feed for posts labeled "chapter" that also match the typed
  // phrase, then renders each match with a bit of surrounding context.
  function searchBook() {
    var query = tag("search").value;

    fetch(
      "/feeds/posts/default?alt=json&label=chapter&v=2&orderby=relevance&max-results=100&q=label%3Achapter+" +
      encodeURIComponent(query) +
      "&start-index=1&rewriteforssl=true"
    )
      .then(function (response) {
        if (!response.ok) {
          throw new Error("Network response was not ok");
        }
        return response.json();
      })
      .then(function (data) {
        tag("search-results").replaceChildren();

        if (data.feed.entry) {
          data.feed.entry.forEach(function (entry) {
            buildChapterSearchResult(entry, query);
          });
        } else {
          var noResultsDiv = document.createElement("div");
          noResultsDiv.appendChild(document.createTextNode("No Results Found"));
          tag("search-results").appendChild(noResultsDiv);
        }
      })
      .catch(function (err) {
        console.error(err);
      });
  }

  // Takes a Blogger feed entry's <link> array and returns the "alternate"
  // one — the actual post URL, as opposed to the feed's "self"/"edit" links.
  function findLink(links) {
    for (var i = 0; i < links.length; i++) {
      if (links[i].rel === "alternate") return links[i];
    }
    return null;
  }

  function buildChapterSearchResult(entry, query) {
    var tempDiv = document.createElement("div");
    tempDiv.innerHTML = entry.content.$t;

    var link = findLink(entry.link);
    // textContent, not innerText: tempDiv is never attached to the
    // document, and innerText's result depends on layout — on a detached
    // element it can silently come back empty in some browsers, which
    // made `snippets` empty and highlightSnippet quietly fall back to the
    // bare query below.
    var snippets = findPhraseWithContext(tempDiv.textContent, query, 5);
    // The actual passage the search feature found — not just the typed
    // term — so the target page can highlight/scroll to that specific
    // occurrence rather than merely the first place the bare term appears.
    var highlightSnippet = snippets[0] || query;

    console.log("[highlight] buildChapterSearchResult:", {
      title: entry.title.$t,
      query: query,
      textContentLength: tempDiv.textContent.length,
      snippets: snippets,
      highlightSnippet: highlightSnippet
    });

    var resultLink = document.createElement("a");
    resultLink.className = "chapter-result-link";
    resultLink.href = link
      ? relativePath(link.href) +
        "?highlight=" + encodeURIComponent(highlightSnippet) +
        "&term=" + encodeURIComponent(query)
      : "#";
    resultLink.style.color = "black";

    console.log("[highlight] result link href:", resultLink.href);

    var resultDiv = document.createElement("div");
    resultDiv.className = "search-result";

    var titleDiv = document.createElement("div");
    titleDiv.className = "search-result-title";
    titleDiv.appendChild(document.createTextNode(entry.title.$t));
    resultDiv.appendChild(titleDiv);

    snippets.forEach(function (snippet) {
      var regex = new RegExp(query, "gi");
      var markedSnippet = snippet.replace(regex, "<span class='search-term'>" + query + "</span>");
      var lineDiv = document.createElement("div");
      lineDiv.className = "search-result-line";
      lineDiv.innerHTML = markedSnippet;
      resultDiv.appendChild(lineDiv);
    });

    resultLink.appendChild(resultDiv);
    tag("search-results").appendChild(resultLink);
  }

  // Finds every occurrence of `phrase` (whole words, case-insensitive)
  // inside `text` and returns each as a snippet with `contextCount` words
  // of context on either side, for a search-result preview.
  function findPhraseWithContext(text, phrase, contextCount) {
    contextCount = contextCount || 10;

    var words = text.split(/\s+/);
    var phraseWords = phrase.toLowerCase().split(/\s+/);
    var results = [];

    for (var i = 0; i <= words.length - phraseWords.length; i++) {
      var match = true;
      for (var j = 0; j < phraseWords.length; j++) {
        var cleanWord = words[i + j].replace(/[^\w\s]/g, "").toLowerCase();
        if (cleanWord !== phraseWords[j]) {
          match = false;
          break;
        }
      }

      if (match) {
        var start = Math.max(0, i - contextCount);
        var end = i + phraseWords.length + contextCount;
        results.push(words.slice(start, end).join(" "));
        i += phraseWords.length - 1;
      }
    }

    return results;
  }



//////////////////////////////////////////////////////////////////////////////////////////////
//]]>
  </script>

  
</head>
<body>
<div class='sitenbt' id='pagenbt'>
<div class='site-contentnbt' id='contentnbt'>
<div class='testing' expr:id='data:blog.pageType'/>
  
  
  <b:if cond='data:blog.pageType == &quot;error_page&quot;'>
    <div expr:data-blog='data:blog.pageType' style='padding:1rem'>
      <h1>Page Not Found</h1>
      title: <data:blog.title/><br/>
      pageType: <data:blog.pageType/><br/>
      pageTitle: <data:blog.pageTitle/><br/>
      pageName: <data:blog.pageName/><br/>
      searchLabel: <data:blog.searchLabel/><br/>
      searchQuery: <data:blog.searchQuery/><br/>
      url: <data:blog.url/><br/>
      homepageUrl: <data:blog.homepageUrl/><br/>
      canonicalHomepageUrl: <data:blog.canonicalHomepageUrl/><br/>
    </div>
 <b:else/>
    
    
    
<div class='content-areanbt' id='primarynbt'>
<div class='site-mainnbt' id='mainnbt' role='main'>
<b:section id='mainblogsec' showaddelement='no'>
  <b:widget id='Blog1' locked='true' title='Blog Posts' type='Blog' version='1'>
    <b:widget-settings>
      <b:widget-setting name='showDateHeader'>true</b:widget-setting>
      <b:widget-setting name='style.textcolor'>#ffffff</b:widget-setting>
      <b:widget-setting name='showShareButtons'>true</b:widget-setting>
      <b:widget-setting name='authorLabel'>By</b:widget-setting>
      <b:widget-setting name='showCommentLink'>true</b:widget-setting>
      <b:widget-setting name='style.urlcolor'>#ffffff</b:widget-setting>
      <b:widget-setting name='showAuthor'>false</b:widget-setting>
      <b:widget-setting name='style.linkcolor'>#ffffff</b:widget-setting>
      <b:widget-setting name='style.unittype'>TextAndImage</b:widget-setting>
      <b:widget-setting name='style.bgcolor'>#ffffff</b:widget-setting>
      <b:widget-setting name='reactionsLabel'/>
      <b:widget-setting name='showAuthorProfile'>false</b:widget-setting>
      <b:widget-setting name='style.layout'>1x1</b:widget-setting>
      <b:widget-setting name='showLabels'>true</b:widget-setting>
      <b:widget-setting name='showLocation'>true</b:widget-setting>
      <b:widget-setting name='showTimestamp'>true</b:widget-setting>
      <b:widget-setting name='postsPerAd'>3</b:widget-setting>
      <b:widget-setting name='showBacklinks'>false</b:widget-setting>
      <b:widget-setting name='style.bordercolor'>#ffffff</b:widget-setting>
      <b:widget-setting name='showInlineAds'>true</b:widget-setting>
      <b:widget-setting name='showReactions'>false</b:widget-setting>
    </b:widget-settings>
    <b:includable id='main' var='top'>

    <!-- posts -->
    <div class='blog-posts hfeed'>
      <b:loop values='data:posts[0]' var='post'>
        <div class='post-outer'>
        <b:include data='post' name='post'/>
        </div>
      </b:loop>
    </div>

    </b:includable>
    <b:includable id='backlinkDeleteIcon' var='backlink'/>
    <b:includable id='backlinks' var='post'/>
    <b:includable id='comment-form' var='post'/>
    <b:includable id='commentDeleteIcon' var='comment'/>
    <b:includable id='comment_count_picker' var='post'/>
    <b:includable id='comment_picker' var='post'/>
    <b:includable id='comments' var='post'/>
    <b:includable id='feedLinks'/>
    <b:includable id='feedLinksBody' var='links'/>
    <b:includable id='iframe_comments' var='post'/>
    <b:includable id='mobile-index-post' var='post'/>
    <b:includable id='mobile-main' var='top'/>
    <b:includable id='mobile-nextprev'/>
    <b:includable id='mobile-post' var='post'/>
    <b:includable id='nextprev'/>
    <b:includable id='post' var='post'>

      
      
<b:if cond='data:post.title.length==0'><!--post has not title-->
  <!-- Title page in the most recently published post. no title -->
<data:post.body/>
<b:else/><!--post has a title-->

<article class='post'>
      

<div id='msg-galley'/>  
  
<b:if cond='data:post.labels any (label =&gt; label.name == &quot;toc&quot;)'>
            <!-- Table of contents-->
            <div class='post-body entry-content' data-label='toc' expr:data-blog-id='data:blog.blogId' expr:data-post-id='data:post.id' style='display:none'>
              &lt;!--postBegin--&gt;<data:post.body/>&lt;!--postEnd--&gt;
            </div>
       <script type='text/javascript'>
            //<![CDATA[

            function buildTocContent(bookInfo) {
              console.log("building")
              var authors = bookInfo.authors || [];
              var html = [
                "<div class='book-title'>" + bookInfo.title + "</div>",
                "<div class='book-author'>" + authors.join(" and ") + "</div>",
                "<div class='book-toc'>",
                "<div class='book-chapters'>"
              ];

              for (var i = 0; i < bookInfo.chapters.length; i++) {
                var chapter = bookInfo.chapters[i];
                var href = relativePath(chapter.url);
                var text = stripChapterPrefix(chapter.text);
                html.push(
                  "<div class='book-chapter'>Chapter " + chapter.label + ": " +
                    "<a href='" + href + "'>" + text + "</a></div>"
                );
              }

              html.push("</div></div>");
              return html.join("\n");
            }

            function stripChapterPrefix(text) {
              return text.replace(/^\s*(?:chapter|chatper)\s+\S+[.:]?\s*/i, "").trim();
            }

            function initToc() {
            	const bookInfo=JSON.parse(document.querySelector(".post-body").innerHTML)
				console.log("bookInfo",bookInfo)
				const container=document.querySelector(".post-body")
				container.innerHTML = buildTocContent(bookInfo);
				container.style.display=""

            }
            initToc()

            //]]>
        </script>  
<b:elseif cond='data:post.labels any (label =&gt; label.name == &quot;redirect&quot;)'/>
            <!-- redirect-->
       <div id='post-json' style='display:none'><data:post.body/></div>
<b:elseif cond='data:post.labels any (label =&gt; label.name == &quot;chapter&quot;)'/>
          <!-- A chapter post x-->
            <div class='index-boxnbt'>

              <header class='entry-header' style='display:flex;align-items:center;justify-content:space-between;'>
<h1 class='post-title entry-title' itemprop='headline'><span class='material-symbols-outlined menu-button' onclick='showMenu()'>menu</span><span id='title'><data:post.title/></span></h1>
</header>
              
            <div id='menu'/>
              
            <div class='post-body entry-content' expr:data-blog-id='data:blog.blogId' expr:data-post-id='data:post.id'>
              <button class='nav' id='prior-button' onclick='navigate(&apos;prior&apos;)'><span class='material-symbols-outlined'>arrow_back</span></button>
              <button class='nav' id='next-button' onclick='navigate(&apos;next&apos;)'><span class='material-symbols-outlined'>arrow_forward</span></button>
              
              &lt;!--postBegin--&gt;<data:post.body/>&lt;!--postEnd--&gt;
              </div>
            </div>
  
            <script type='text/javascript'>
            //<![CDATA[
				document.addEventListener("DOMContentLoaded", initMenu);
 
                function initializeChatper(){

					// find footnotes and style them correctly
             		for(const a of document.querySelectorAll("a")){
                        const text = a.innerHTML.trim()
           				if(!isNaN(text) && parseInt(text)===parseFloat(text)){
							// looks like a link to a foot note
							a.classList.add("footnote-link")
                            a.target=("endnotes")
                            a.href+="#fn"+text 
                            a.setAttribute('aria-label', 'Link to endnote number ' + text);
						}
                    }
					// find block quotes and style them correctly
                    for(const elem of document.querySelectorAll("blockquote")){
                        const label = elem.innerText.split(":").shift().trim().toLowerCase()
                        if(["caution","term","note","tip","review"].includes(label)){
                            elem.classList.add(label)
                            const firstNode = elem.childNodes[0]
                            if(firstNode && firstNode.nodeType === Node.TEXT_NODE){
                                firstNode.textContent = firstNode.textContent.replace(/^\s*\w+:\s*/, "")
                            }
                        }
                    }
					// find learning objectives and style them correctly
					const heading=document.querySelector(".post-body h1")
					if(heading){
  						const nextElem = heading.nextElementSibling
						if(nextElem.tagName==="BLOCKQUOTE"){
							nextElem.classList.add("objectives")
							console.log("We have the learning Objectives" , nextElem)
                            const newDiv = document.createElement('div');
                            newDiv.textContent = 'Learning Objectives';
							newDiv.classList.add("learning")
							newDiv.appendChild(nextElem)
                            heading.insertAdjacentElement('afterend', newDiv);
						}
					}
					assignHeadingIds(document.querySelector('.post-body'))
                }

                
				function assignHeadingIds(root) {
                  root = root || document;

                  var headings = root.querySelectorAll("h1, h2, h3, h4");
                  var stack = []; // depths of the currently open path
                  var counters = [];

                  headings.forEach(function (heading) {
                    var depth = parseInt(heading.tagName.substring(1), 10) - 1;

                    while (stack.length && stack[stack.length - 1] >= depth) stack.pop();
                    while (counters.length > depth + 1) counters.pop();

                    counters[depth] = (counters[depth] || 0) + 1;

                    heading.id = "l" + counters.slice(0, depth + 1).join("-");

                    stack.push(depth);
                  });
                }
                initializeChatper()
				
                
            //]]>
            </script>  

  
  
<b:else/>
            <!-- post not tagged with one of hte above tags-->
            <div class='post-body entry-content' data-label='not-recognized' expr:data-blog-id='data:blog.blogId' expr:data-post-id='data:post.id'>
              <h3><data:post.title/></h3>
              <blockquote>&lt;!--postBegin--&gt;<data:post.body/>&lt;!--postEnd--&gt;</blockquote>
            </div>

</b:if>
  
  

  
  
</article>
      
      
      
      
</b:if><!--end of post has a title or not-->

</b:includable>
    <b:includable id='postQuickEdit' var='post'/>
    <b:includable id='shareButtons' var='post'/>
    <b:includable id='status-message'/>
    <b:includable id='threaded-comment-form' var='post'/>
    <b:includable id='threaded_comment_js' var='post'/>
    <b:includable id='threaded_comments' var='post'/>
  </b:widget>
</b:section>
</div><!-- #main -->
</div><!-- #primary -->
</b:if><!--end of error vs non error block-->
  
</div><!-- #content -->
<footer class='site-footernbt' id='colophonnbt' role='contentinfo'>
<div id='supplementarynbt'>
<div class='footer-widgetsnbt widget-areanbt clearnbt' id='footer-widgetsnbt' role='complementary'>
<b:section class='sidebarwidgetsarea' id='sidebarwidgetsarea' preferred='yes'/>
</div><!-- #footer-sidebar -->
</div><!-- #supplementary -->
</footer><!-- #colophon -->
</div><!-- #page -->

</body>
</html>