const char HTMLLogger_html[] = 
  R"x(<!doctype html>)x" "\n"
  R"x(<html>)x" "\n"
  R"x(<!-- HTMLLogger.cpp ----------------------------------------------------)x" "\n"
  R"x()x" "\n"
  R"x( Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.)x" "\n"
  R"x( See https://llvm.org/LICENSE.txt for license information.)x" "\n"
  R"x( SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception)x" "\n"
  R"x()x" "\n"
  R"x(//===------------------------------------------------------------------------>)x" "\n"
  R"x()x" "\n"
  R"x(<head>)x" "\n"
  R"x(<?INJECT?>)x" "\n"
  R"x()x" "\n"
  R"x(<template id="value-template">)x" "\n"
  R"x(  <details class="value" open>)x" "\n"
  R"x(    <summary>)x" "\n"
  R"x(      <span>{{v.kind}})x" "\n"
  R"x(        <template data-if="v.value_id"><span class="address">#{{v.value_id}}</span></template>)x" "\n"
  R"x(      </span>)x" "\n"
  R"x(      <template data-if="v.location">)x" "\n"
  R"x(        <span class="location">{{v.type}} <span class="address">@{{v.location}}</span></span>)x" "\n"
  R"x(      </template>)x" "\n"
  R"x(    </summary>)x" "\n"
  R"x(    <template)x" "\n"
  R"x(        data-for="kv in Object.entries(v)")x" "\n"
  R"x(        data-if="['kind', 'value_id', 'type', 'location'].indexOf(kv[0]) < 0">)x" "\n"
  R"x(      <div class="property"><span class="key">{{kv[0]}}</span>)x" "\n"
  R"x(        <template data-if="typeof(kv[1]) != 'object'">{{kv[1]}}</template>)x" "\n"
  R"x(        <template data-if="typeof(kv[1]) == 'object'" data-let="v = kv[1]">)x" "\n"
  R"x(          <template data-use="value-template"></template>)x" "\n"
  R"x(        </template>)x" "\n"
  R"x(      </div>)x" "\n"
  R"x(    </template>)x" "\n"
  R"x(  </details>)x" "\n"
  R"x(</template>)x" "\n"
  R"x()x" "\n"
  R"x(</head>)x" "\n"
  R"x()x" "\n"
  R"x(<body>)x" "\n"
  R"x()x" "\n"
  R"x(<section id="timeline" data-selection="">)x" "\n"
  R"x(<header>Timeline</header>)x" "\n"
  R"x(<template data-for="entry in timeline">)x" "\n"
  R"x(  <div id="{{entry.block}}:{{entry.iter}}" data-bb="{{entry.block}}" class="entry">)x" "\n"
  R"x(    <span class="counter"></span>)x" "\n"
  R"x(    {{entry.block}})x" "\n"
  R"x(    <template data-if="entry.post_visit">(post-visit)</template>)x" "\n"
  R"x(    <template data-if="!entry.post_visit">({{entry.iter}})</template>)x" "\n"
  R"x(    <template data-if="entry.converged"> &#x2192;&#x7c;<!--Rightwards arrow, vertical line--></template>)x" "\n"
  R"x(  </div>)x" "\n"
  R"x(</template>)x" "\n"
  R"x(</section>)x" "\n"
  R"x()x" "\n"
  R"x(<section id="function" data-selection="">)x" "\n"
  R"x(<header>Function</header>)x" "\n"
  R"x(<div id="code"></div>)x" "\n"
  R"x(<div id="cfg"></div>)x" "\n"
  R"x(</section>)x" "\n"
  R"x()x" "\n"
  R"x(<section id="block" data-selection="bb">)x" "\n"
  R"x(<header><template>Block {{selection.bb}}</template></header>)x" "\n"
  R"x(<div id="iterations">)x" "\n"
  R"x(  <template data-for="iter in cfg[selection.bb].iters">)x" "\n"
  R"x(    <a class="chooser {{selection.bb}}:{{iter.iter}}" data-iter="{{selection.bb}}:{{iter.iter}}">)x" "\n"
  R"x(      <template data-if="iter.post_visit">Post-visit</template>)x" "\n"
  R"x(      <template data-if="!iter.post_visit">{{iter.iter}}</template>)x" "\n"
  R"x(      <template data-if="iter.converged"> &#x2192;&#x7c;<!--Rightwards arrow, vertical line--></template>)x" "\n"
  R"x(    </a>)x" "\n"
  R"x(  </template>)x" "\n"
  R"x(</div>)x" "\n"
  R"x(<table id="bb-elements">)x" "\n"
  R"x(<template>)x" "\n"
  R"x(  <tr id="{{selection.bb}}.0">)x" "\n"
  R"x(    <td class="{{selection.bb}}">{{selection.bb}}.0</td>)x" "\n"
  R"x(    <td>(initial state)</td>)x" "\n"
  R"x(  </tr>)x" "\n"
  R"x(</template>)x" "\n"
  R"x(<template data-for="elt in cfg[selection.bb].elements">)x" "\n"
  R"x(  <tr id="{{selection.bb}}.{{elt_index+1}}">)x" "\n"
  R"x(    <td class="{{selection.bb}}">{{selection.bb}}.{{elt_index+1}}</td>)x" "\n"
  R"x(    <td>{{elt}}</td>)x" "\n"
  R"x(  </tr>)x" "\n"
  R"x(</template>)x" "\n"
  R"x(</table>)x" "\n"
  R"x(</section>)x" "\n"
  R"x()x" "\n"
  R"x(<section id="element" data-selection="iter,elt">)x" "\n"
  R"x(<template data-let="state = states[selection.iter + '_' + selection.elt]">)x" "\n"
  R"x(<header>)x" "\n"
  R"x(  <template data-if="state.element == 0">{{state.block}} initial state</template>)x" "\n"
  R"x(  <template data-if="state.element != 0">Element {{selection.elt}}</template>)x" "\n"
  R"x(  <template data-if="state.post_visit"> (post-visit)</template>)x" "\n"
  R"x(  <template data-if="!state.post_visit"> (iteration {{state.iter}})</template>)x" "\n"
  R"x(</header>)x" "\n"
  R"x(<template data-if="state.value" data-let="v = state.value">)x" "\n"
  R"x(  <h2>Value</h2>)x" "\n"
  R"x(  <template data-use="value-template"></template>)x" "\n"
  R"x(</template>)x" "\n"
  R"x(<template data-if="state.logs">)x" "\n"
  R"x(  <h2>Logs</h2>)x" "\n"
  R"x(  <pre>{{state.logs}}</pre>)x" "\n"
  R"x(</template>)x" "\n"
  R"x(<h2>Built-in lattice</h2>)x" "\n"
  R"x(<pre>{{state.builtinLattice}}</pre>)x" "\n"
  R"x(</template>)x" "\n"
  R"x(</section>)x" "\n"
  R"x()x" "\n"
  R"x(<script>)x" "\n"
  R"x(addBBColors(Object.keys(HTMLLoggerData.cfg).length);)x" "\n"
  R"x(watchSelection(HTMLLoggerData);)x" "\n"
  R"x(updateSelection({}, HTMLLoggerData);)x" "\n"
  R"x(// Copy code and cfg from <template>s into the body.)x" "\n"
  R"x(for (tmpl of document.querySelectorAll('template[data-copy]')))x" "\n"
  R"x(  document.getElementById(tmpl.dataset.copy).replaceChildren()x" "\n"
  R"x(      ...tmpl.content.cloneNode(/*deep=*/true).childNodes);)x" "\n"
  R"x(</script>)x" "\n"
  R"x()x" "\n"
  R"x(</body>)x" "\n"
  R"x(</html>)x" "\n"
  R"x()x" "\n"
  ;
const char HTMLLogger_css[] = 
  R"x(/*===-- HTMLLogger.css ----------------------------------------------------===)x" "\n"
  R"x(*)x" "\n"
  R"x(* Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.)x" "\n"
  R"x(* See https://llvm.org/LICENSE.txt for license information.)x" "\n"
  R"x(* SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception)x" "\n"
  R"x(*)x" "\n"
  R"x(*===----------------------------------------------------------------------===*/)x" "\n"
  R"x(html { font-family: sans-serif; })x" "\n"
  R"x(body { margin: 0; display: flex; justify-content: left; })x" "\n"
  R"x(body > * { box-sizing: border-box; })x" "\n"
  R"x(body > section {)x" "\n"
  R"x(  border: 1px solid black;)x" "\n"
  R"x(  min-width: 20em;)x" "\n"
  R"x(  overflow: auto;)x" "\n"
  R"x(  max-height: 100vh;)x" "\n"
  R"x(})x" "\n"
  R"x(section header {)x" "\n"
  R"x(  background-color: #008;)x" "\n"
  R"x(  color: white;)x" "\n"
  R"x(  font-weight: bold;)x" "\n"
  R"x(  font-size: large;)x" "\n"
  R"x(  padding-right: 0.5em;)x" "\n"
  R"x(})x" "\n"
  R"x(section h2 {)x" "\n"
  R"x(  font-size: medium;)x" "\n"
  R"x(  margin-bottom: 0.5em;)x" "\n"
  R"x(  padding-top: 0.5em;)x" "\n"
  R"x(  border-top: 1px solid #aaa;)x" "\n"
  R"x(})x" "\n"
  R"x(#timeline {)x" "\n"
  R"x(  min-width: max-content;)x" "\n"
  R"x(  counter-reset: entry_counter;)x" "\n"
  R"x(})x" "\n"
  R"x(#timeline .entry .counter::before {)x" "\n"
  R"x(  counter-increment: entry_counter;)x" "\n"
  R"x(  content: counter(entry_counter) ":";)x" "\n"
  R"x(})x" "\n"
  R"x(#timeline .entry .counter {)x" "\n"
  R"x(  display: inline-block;)x" "\n"
  R"x(  min-width: 2em; /* Enough space for two digits and a colon */)x" "\n"
  R"x(  text-align: right;)x" "\n"
  R"x(})x" "\n"
  R"x(#timeline .entry.hover {)x" "\n"
  R"x(  background-color: #aaa;)x" "\n"
  R"x(})x" "\n"
  R"x(#timeline .entry.iter-select {)x" "\n"
  R"x(  background-color: #aac;)x" "\n"
  R"x(})x" "\n"
  R"x()x" "\n"
  R"x(#bb-elements {)x" "\n"
  R"x(  font-family: monospace;)x" "\n"
  R"x(  font-size: x-small;)x" "\n"
  R"x(  border-collapse: collapse;)x" "\n"
  R"x(})x" "\n"
  R"x(#bb-elements td:nth-child(1) {)x" "\n"
  R"x(  text-align: right;)x" "\n"
  R"x(  width: 4em;)x" "\n"
  R"x(  border-right: 1px solid #008;)x" "\n"
  R"x(  padding: 0.3em 0.5em;)x" "\n"
  R"x()x" "\n"
  R"x(  font-weight: bold;)x" "\n"
  R"x(  color: #888;)x" "\n"
  R"x(};)x" "\n"
  R"x(#bb-elements tr.hover {)x" "\n"
  R"x(  background-color: #abc;)x" "\n"
  R"x(})x" "\n"
  R"x(#bb-elements tr.elt-select {)x" "\n"
  R"x(  background-color: #acf;)x" "\n"
  R"x(})x" "\n"
  R"x(#iterations {)x" "\n"
  R"x(  display: flex;)x" "\n"
  R"x(})x" "\n"
  R"x(#iterations .chooser {)x" "\n"
  R"x(  flex-grow: 1;)x" "\n"
  R"x(  text-align: center;)x" "\n"
  R"x(  padding-left: 0.2em;)x" "\n"
  R"x(})x" "\n"
  R"x(#iterations .chooser :last-child {)x" "\n"
  R"x(  padding-right: 0.2em;)x" "\n"
  R"x(})x" "\n"
  R"x(#iterations .chooser:not(.iter-select).hover {)x" "\n"
  R"x(  background-color: #ddd;)x" "\n"
  R"x(})x" "\n"
  R"x(#iterations .iter-select {)x" "\n"
  R"x(  font-weight: bold;)x" "\n"
  R"x(})x" "\n"
  R"x(#iterations .chooser:not(.iter-select) {)x" "\n"
  R"x(  text-decoration: underline;)x" "\n"
  R"x(  color: blue;)x" "\n"
  R"x(  cursor: pointer;)x" "\n"
  R"x(  background-color: #ccc;)x" "\n"
  R"x(})x" "\n"
  R"x()x" "\n"
  R"x(code.filename {)x" "\n"
  R"x(  font-weight: bold;)x" "\n"
  R"x(  color: black;)x" "\n"
  R"x(  background-color: #ccc;)x" "\n"
  R"x(  display: block;)x" "\n"
  R"x(  text-align: center;)x" "\n"
  R"x(})x" "\n"
  R"x(code.line {)x" "\n"
  R"x(  display: block;)x" "\n"
  R"x(  white-space: pre;)x" "\n"
  R"x(})x" "\n"
  R"x(code.line:before { /* line numbers */)x" "\n"
  R"x(  content: attr(data-line);)x" "\n"
  R"x(  display: inline-block;)x" "\n"
  R"x(  width: 2em;)x" "\n"
  R"x(  text-align: right;)x" "\n"
  R"x(  padding-right: 2px;)x" "\n"
  R"x(  background-color: #ccc;)x" "\n"
  R"x(  border-right: 1px solid #888;)x" "\n"
  R"x(  margin-right: 8px;)x" "\n"
  R"x(})x" "\n"
  R"x(code.line:has(.bb-select):before {)x" "\n"
  R"x(  border-right: 4px solid black;)x" "\n"
  R"x(  margin-right: 5px;)x" "\n"
  R"x(})x" "\n"
  R"x(.c.hover, .bb.hover {)x" "\n"
  R"x(  filter: saturate(200%) brightness(90%);)x" "\n"
  R"x(})x" "\n"
  R"x(.c.elt-select {)x" "\n"
  R"x(  box-shadow: inset 0 -4px 2px -2px #a00;)x" "\n"
  R"x(})x" "\n"
  R"x(.bb.bb-select polygon {)x" "\n"
  R"x(  stroke-width: 4px;)x" "\n"
  R"x(  filter: brightness(70%) saturate(150%);)x" "\n"
  R"x(})x" "\n"
  R"x(.bb { user-select: none; })x" "\n"
  R"x(.bb polygon { fill: white; })x" "\n"
  R"x(#cfg {)x" "\n"
  R"x(  position: relative;)x" "\n"
  R"x(  margin-left: 0.5em;)x" "\n"
  R"x(})x" "\n"
  R"x()x" "\n"
  R"x(.value {)x" "\n"
  R"x(  border: 1px solid #888;)x" "\n"
  R"x(  font-size: x-small;)x" "\n"
  R"x(  flex-grow: 1;)x" "\n"
  R"x(})x" "\n"
  R"x(.value > summary {)x" "\n"
  R"x(  background-color: #ace;)x" "\n"
  R"x(  display: flex;)x" "\n"
  R"x(  cursor: pointer;)x" "\n"
  R"x(})x" "\n"
  R"x(.value > summary::before {)x" "\n"
  R"x(  content: '\25ba';  /* Black Right-Pointing Pointer */)x" "\n"
  R"x(  margin-right: 0.5em;)x" "\n"
  R"x(  font-size: 0.9em;)x" "\n"
  R"x(})x" "\n"
  R"x(.value[open] > summary::before {)x" "\n"
  R"x(  content: '\25bc';  /* Black Down-Pointing Triangle */)x" "\n"
  R"x(})x" "\n"
  R"x(.value > summary > .location {)x" "\n"
  R"x(  margin-left: auto;)x" "\n"
  R"x(})x" "\n"
  R"x(.value .address {)x" "\n"
  R"x(  font-size: xx-small;)x" "\n"
  R"x(  font-family: monospace;)x" "\n"
  R"x(  color: #888;)x" "\n"
  R"x(})x" "\n"
  R"x(.value .property {)x" "\n"
  R"x(  display: flex;)x" "\n"
  R"x(  margin-top: 0.5em;)x" "\n"
  R"x(})x" "\n"
  R"x(.value .property .key {)x" "\n"
  R"x(  font-weight: bold;)x" "\n"
  R"x(  min-width: 5em;)x" "\n"
  R"x(})x" "\n"
  R"x()x" "\n"
  ;
const char HTMLLogger_js[] = 
  R"x(//===-- HTMLLogger.js -----------------------------------------------------===//)x" "\n"
  R"x(//)x" "\n"
  R"x(// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.)x" "\n"
  R"x(// See https://llvm.org/LICENSE.txt for license information.)x" "\n"
  R"x(// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception)x" "\n"
  R"x(//)x" "\n"
  R"x(//===----------------------------------------------------------------------===//)x" "\n"
  R"x()x" "\n"
  R"x(// Based on selected objects, hide/show sections & populate data from templates.)x" "\n"
  R"x(//)x" "\n"
  R"x(// For example, if the selection is {bb="BB4", elt="BB4.6" iter="BB4:2"}:)x" "\n"
  R"x(//   - show the "block" and "element" sections)x" "\n"
  R"x(//   - re-render templates within these sections (if selection changed))x" "\n"
  R"x(//   - apply "bb-select" to items with class class "BB4", etc)x" "\n"
  R"x(let selection = {};)x" "\n"
  R"x(function updateSelection(changes, data) {)x" "\n"
  R"x(  Object.assign(selection, changes);)x" "\n"
  R"x()x" "\n"
  R"x(  data = Object.create(data);)x" "\n"
  R"x(  data.selection = selection;)x" "\n"
  R"x(  for (root of document.querySelectorAll('[data-selection]')))x" "\n"
  R"x(    updateSection(root, data);)x" "\n"
  R"x()x" "\n"
  R"x(  for (var k in changes))x" "\n"
  R"x(    applyClassIf(k + '-select', classSelector(changes[k]));)x" "\n"
  R"x(})x" "\n"
  R"x()x" "\n"
  R"x(// Given <section data-selection="x,y">:)x" "\n"
  R"x(//  - hide section if selections x or y are null)x" "\n"
  R"x(//  - re-render templates if x or y have changed)x" "\n"
  R"x(function updateSection(root, data) {)x" "\n"
  R"x(  let changed = root.selection == null;)x" "\n"
  R"x(  root.selection ||= {};)x" "\n"
  R"x(  for (key of root.dataset.selection.split(',')) {)x" "\n"
  R"x(    if (!key) continue;)x" "\n"
  R"x(    if (data.selection[key] != root.selection[key]) {)x" "\n"
  R"x(      root.selection[key] = data.selection[key];)x" "\n"
  R"x(      changed = true;)x" "\n"
  R"x(    })x" "\n"
  R"x(    if (data.selection[key] == null) {)x" "\n"
  R"x(      root.hidden = true;)x" "\n"
  R"x(      return;)x" "\n"
  R"x(    })x" "\n"
  R"x(  })x" "\n"
  R"x(  if (changed) {)x" "\n"
  R"x(    root.hidden = false;)x" "\n"
  R"x(    for (tmpl of root.getElementsByTagName('template')))x" "\n"
  R"x(      reinflate(tmpl, data);)x" "\n"
  R"x(  })x" "\n"
  R"x(})x" "\n"
  R"x()x" "\n"
  R"x(// Expands template `tmpl` based on input `data`:)x" "\n"
  R"x(//  - interpolates {{expressions}} in text and attributes)x" "\n"
  R"x(//  - <template> tags can modify expansion: if, for etc)x" "\n"
  R"x(// Outputs to `parent` element, inserting before `next`.)x" "\n"
  R"x(function inflate(tmpl, data, parent, next) {)x" "\n"
  R"x(  // We use eval() as our expression language in templates!)x" "\n"
  R"x(  // The templates are static and trusted.)x" "\n"
  R"x(  let evalExpr = (expr, data) => eval('with (data) { ' + expr + ' }');)x" "\n"
  R"x(  let interpolate = (str, data) =>)x" "\n"
  R"x(      str.replace(/\{\{(.*?)\}\}/g, (_, expr) => evalExpr(expr, data)))x" "\n"
  R"x(  // Anything other than <template> tag: copy, interpolate, recursively inflate.)x" "\n"
  R"x(  if (tmpl.nodeName != 'TEMPLATE') {)x" "\n"
  R"x(    let clone = tmpl.cloneNode();)x" "\n"
  R"x(    clone.inflated = true;)x" "\n"
  R"x(    if (clone instanceof Text))x" "\n"
  R"x(      clone.textContent = interpolate(clone.textContent, data);)x" "\n"
  R"x(    if (clone instanceof Element) {)x" "\n"
  R"x(      for (attr of clone.attributes))x" "\n"
  R"x(        attr.value = interpolate(attr.value, data);)x" "\n"
  R"x(      for (c of tmpl.childNodes))x" "\n"
  R"x(        inflate(c, data, clone, /*next=*/null);)x" "\n"
  R"x(    })x" "\n"
  R"x(    return parent.insertBefore(clone, next);)x" "\n"
  R"x(  })x" "\n"
  R"x(  // data-use="xyz": use <template id="xyz"> instead. (Allows recursion.))x" "\n"
  R"x(  if ('use' in tmpl.dataset))x" "\n"
  R"x(    return inflate(document.getElementById(tmpl.dataset.use), data, parent, next);)x" "\n"
  R"x(  // <template> tag handling. Base case: recursively inflate.)x" "\n"
  R"x(  function handle(data) {)x" "\n"
  R"x(    for (c of tmpl.content.childNodes))x" "\n"
  R"x(      inflate(c, data, parent, next);)x" "\n"
  R"x(  })x" "\n"
  R"x(  // Directives on <template> tags modify behavior.)x" "\n"
  R"x(  const directives = {)x" "\n"
  R"x(    // data-for="x in expr": expr is enumerable, bind x to each in turn)x" "\n"
  R"x(    'for': (nameInExpr, data, proceed) => {)x" "\n"
  R"x(      let [name, expr] = nameInExpr.split(' in ');)x" "\n"
  R"x(      let newData = Object.create(data);)x" "\n"
  R"x(      let index = 0;)x" "\n"
  R"x(      for (val of evalExpr(expr, data) || []) {)x" "\n"
  R"x(        newData[name] = val;)x" "\n"
  R"x(        newData[name + '_index'] = index++;)x" "\n"
  R"x(        proceed(newData);)x" "\n"
  R"x(      })x" "\n"
  R"x(    },)x" "\n"
  R"x(    // data-if="expr": only include contents if expression is truthy)x" "\n"
  R"x(    'if': (expr, data, proceed) => { if (evalExpr(expr, data)) proceed(data); },)x" "\n"
  R"x(    // data-let="x = expr": bind x to value of expr)x" "\n"
  R"x(    'let': (nameEqExpr, data, proceed) => {)x" "\n"
  R"x(      let [name, expr] = nameEqExpr.split(' = ');)x" "\n"
  R"x(      let newData = Object.create(data);)x" "\n"
  R"x(      newData[name] = evalExpr(expr, data);)x" "\n"
  R"x(      proceed(newData);)x" "\n"
  R"x(    },)x" "\n"
  R"x(  })x" "\n"
  R"x(  // Compose directive handlers on top of the base handler.)x" "\n"
  R"x(  for (let [dir, value] of Object.entries(tmpl.dataset).reverse()) {)x" "\n"
  R"x(    if (dir in directives) {)x" "\n"
  R"x(      let proceed = handle;)x" "\n"
  R"x(      handle = (data) => directives[dir](value, data, proceed);)x" "\n"
  R"x(    })x" "\n"
  R"x(  })x" "\n"
  R"x(  handle(data);)x" "\n"
  R"x(})x" "\n"
  R"x(// Expand a template, after first removing any prior expansion of it.)x" "\n"
  R"x(function reinflate(tmpl, data) {)x" "\n"
  R"x(  // Clear previously rendered template contents.)x" "\n"
  R"x(  while (tmpl.nextSibling && tmpl.nextSibling.inflated))x" "\n"
  R"x(    tmpl.parentNode.removeChild(tmpl.nextSibling);)x" "\n"
  R"x(  inflate(tmpl, data, tmpl.parentNode, tmpl.nextSibling);)x" "\n"
  R"x(})x" "\n"
  R"x()x" "\n"
  R"x(// Handle a mouse event on a region containing selectable items.)x" "\n"
  R"x(// This might end up changing the hover state or the selection state.)x" "\n"
  R"x(//)x" "\n"
  R"x(// targetSelector describes what target HTML element is selectable.)x" "\n"
  R"x(// targetToID specifies how to determine the selection from it:)x" "\n"
  R"x(//   hover: a function from target to the class name to highlight)x" "\n"
  R"x(//   bb: a function from target to the basic-block name to select (BB4))x" "\n"
  R"x(//   elt: a function from target to the CFG element name to select (BB4.5))x" "\n"
  R"x(//   iter: a function from target to the BB iteration to select (BB4:2))x" "\n"
  R"x(// If an entry is missing, the selection is unmodified.)x" "\n"
  R"x(// If an entry is null, the selection is always cleared.)x" "\n"
  R"x(function mouseEventHandler(event, targetSelector, targetToID, data) {)x" "\n"
  R"x(  var target = event.type == "mouseout" ? null : event.target.closest(targetSelector);)x" "\n"
  R"x(  let selTarget = k => (target && targetToID[k]) ? targetToID[k](target) : null;)x" "\n"
  R"x(  if (event.type == "click") {)x" "\n"
  R"x(    let newSel = {};)x" "\n"
  R"x(    for (var k in targetToID) {)x" "\n"
  R"x(      if (k == 'hover') continue;)x" "\n"
  R"x(      let t = selTarget(k);)x" "\n"
  R"x(      newSel[k] = t;)x" "\n"
  R"x(    })x" "\n"
  R"x(    updateSelection(newSel, data);)x" "\n"
  R"x(  } else if ("hover" in targetToID) {)x" "\n"
  R"x(    applyClassIf("hover", classSelector(selTarget("hover")));)x" "\n"
  R"x(  })x" "\n"
  R"x(})x" "\n"
  R"x(function watch(rootSelector, targetSelector, targetToID, data) {)x" "\n"
  R"x(  var root = document.querySelector(rootSelector);)x" "\n"
  R"x(  for (event of ['mouseout', 'mousemove', 'click']))x" "\n"
  R"x(    root.addEventListener(event, e => mouseEventHandler(e, targetSelector, targetToID, data));)x" "\n"
  R"x(})x" "\n"
  R"x(function watchSelection(data) {)x" "\n"
  R"x(  let lastIter = (bb) => `${bb}:${data.cfg[bb].iters}`;)x" "\n"
  R"x(  watch('#code', '.c', {)x" "\n"
  R"x(    hover: e => e.dataset.elt,)x" "\n"
  R"x(    bb: e => e.dataset.bb,)x" "\n"
  R"x(    elt: e => e.dataset.elt,)x" "\n"
  R"x(    // If we're already viewing an iteration of this BB, stick with the same.)x" "\n"
  R"x(    iter: e => (selection.iter && selection.bb == e.dataset.bb) ? selection.iter : lastIter(e.dataset.bb),)x" "\n"
  R"x(  }, data);)x" "\n"
  R"x(  watch('#cfg', '.bb', {)x" "\n"
  R"x(    hover: e => e.id,)x" "\n"
  R"x(    bb: e => e.id,)x" "\n"
  R"x(    elt: e => e.id + ".0",)x" "\n"
  R"x(    iter: e => lastIter(e.id),)x" "\n"
  R"x(  }, data);)x" "\n"
  R"x(  watch('#timeline', '.entry', {)x" "\n"
  R"x(    hover: e => [e.id, e.dataset.bb],)x" "\n"
  R"x(    bb: e => e.dataset.bb,)x" "\n"
  R"x(    elt: e => e.dataset.bb + ".0",)x" "\n"
  R"x(    iter: e => e.id,)x" "\n"
  R"x(  }, data);)x" "\n"
  R"x(  watch('#bb-elements', 'tr', {)x" "\n"
  R"x(    hover: e => e.id,)x" "\n"
  R"x(    elt: e => e.id,)x" "\n"
  R"x(  }, data);)x" "\n"
  R"x(  watch('#iterations', '.chooser', {)x" "\n"
  R"x(    hover: e => e.dataset.iter,)x" "\n"
  R"x(    iter: e => e.dataset.iter,)x" "\n"
  R"x(  }, data);)x" "\n"
  R"x(  updateSelection({}, data);)x" "\n"
  R"x(})x" "\n"
  R"x(function applyClassIf(cls, query) {)x" "\n"
  R"x(  document.querySelectorAll('.' + cls).forEach(elt => elt.classList.remove(cls));)x" "\n"
  R"x(  document.querySelectorAll(query).forEach(elt => elt.classList.add(cls));)x" "\n"
  R"x(})x" "\n"
  R"x(// Turns a class name into a CSS selector matching it, with some wrinkles:)x" "\n"
  R"x(// - we treat id="foo" just like class="foo" to avoid repetition in the HTML)x" "\n"
  R"x(// - cls can be an array of strings, we match them all)x" "\n"
  R"x(function classSelector(cls) {)x" "\n"
  R"x(  if (cls == null) return null;)x" "\n"
  R"x(  if (Array.isArray(cls)) return cls.map(classSelector).join(', ');)x" "\n"
  R"x(  var escaped = cls.replace('.', '\\.').replace(':', '\\:');)x" "\n"
  R"x(  // don't require id="foo" class="foo")x" "\n"
  R"x(  return '.' + escaped + ", #" + escaped;)x" "\n"
  R"x(})x" "\n"
  R"x()x" "\n"
  R"x(// Add a stylesheet defining colors for n basic blocks.)x" "\n"
  R"x(function addBBColors(n) {)x" "\n"
  R"x(  let sheet = new CSSStyleSheet();)x" "\n"
  R"x(  // hex values to subtract from fff to get a base color)x" "\n"
  R"x(  options = [0x001, 0x010, 0x011, 0x100, 0x101, 0x110, 0x111];)x" "\n"
  R"x(  function color(hex) {)x" "\n"
  R"x(    return "#" + hex.toString(16).padStart(3, "0");)x" "\n"
  R"x(  })x" "\n"
  R"x(  function add(selector, property, hex) {)x" "\n"
  R"x(    sheet.insertRule(`${selector} { ${property}: ${color(hex)}; }`))x" "\n"
  R"x(  })x" "\n"
  R"x(  for (var i = 0; i < n; ++i) {)x" "\n"
  R"x(    let opt = options[i%options.length];)x" "\n"
  R"x(    add(`.B${i}`, 'background-color', 0xfff - 2*opt);)x" "\n"
  R"x(    add(`#B${i} polygon`, 'fill', 0xfff - 2*opt);)x" "\n"
  R"x(    add(`#B${i} polygon`, 'stroke', 0x888 - 4*opt);)x" "\n"
  R"x(  })x" "\n"
  R"x(  document.adoptedStyleSheets.push(sheet);)x" "\n"
  R"x(})x" "\n"
  R"x()x" "\n"
  ;
