diff --git a/docs/assignments/fabrixiao/fabrixiao_bom.html b/docs/assignments/fabrixiao/fabrixiao_bom.html
new file mode 100644
index 0000000000000000000000000000000000000000..684991d781dc265dfb43229d7df06f89670649b7
--- /dev/null
+++ b/docs/assignments/fabrixiao/fabrixiao_bom.html
@@ -0,0 +1,4345 @@
+<!DOCTYPE html>
+<html lang="en">
+
+<head>
+  <meta charset="UTF-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1.0">
+  <title>Interactive BOM for KiCAD</title>
+  <style type="text/css">
+:root {
+  --pcb-edge-color: black;
+  --pad-color: #878787;
+  --pad-hole-color: #CCCCCC;
+  --pad-color-highlight: #D04040;
+  --pad-color-highlight-both: #D0D040;
+  --pad-color-highlight-marked: #44a344;
+  --pin1-outline-color: #ffb629;
+  --pin1-outline-color-highlight: #ffb629;
+  --pin1-outline-color-highlight-both: #fcbb39;
+  --pin1-outline-color-highlight-marked: #fdbe41;
+  --silkscreen-edge-color: #aa4;
+  --silkscreen-polygon-color: #4aa;
+  --silkscreen-text-color: #4aa;
+  --fabrication-edge-color: #907651;
+  --fabrication-polygon-color: #907651;
+  --fabrication-text-color: #a27c24;
+  --track-color: #def5f1;
+  --track-color-highlight: #D04040;
+  --zone-color: #def5f1;
+  --zone-color-highlight: #d0404080;
+}
+
+html,
+body {
+  margin: 0px;
+  height: 100%;
+  font-family: Verdana, sans-serif;
+}
+
+.dark.topmostdiv {
+  --pcb-edge-color: #eee;
+  --pad-color: #808080;
+  --pin1-outline-color: #ffa800;
+  --pin1-outline-color-highlight: #ccff00;
+  --track-color: #42524f;
+  --zone-color: #42524f;
+  background-color: #252c30;
+  color: #eee;
+}
+
+button {
+  background-color: #eee;
+  border: 1px solid #888;
+  color: black;
+  height: 44px;
+  width: 44px;
+  text-align: center;
+  text-decoration: none;
+  display: inline-block;
+  font-size: 14px;
+  font-weight: bolder;
+}
+
+.dark button {
+  /* This will be inverted */
+  background-color: #c3b7b5;
+}
+
+button.depressed {
+  background-color: #0a0;
+  color: white;
+}
+
+.dark button.depressed {
+  /* This will be inverted */
+  background-color: #b3b;
+}
+
+button:focus {
+  outline: 0;
+}
+
+button#tb-btn {
+  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8.47 8.47'%3E%3Crect transform='translate(0 -288.53)' ry='1.17' y='288.8' x='.27' height='7.94' width='7.94' fill='%23f9f9f9'/%3E%3Cg transform='translate(0 -288.53)'%3E%3Crect width='7.94' height='7.94' x='.27' y='288.8' ry='1.17' fill='none' stroke='%23000' stroke-width='.4' stroke-linejoin='round'/%3E%3Cpath d='M1.32 290.12h5.82M1.32 291.45h5.82' fill='none' stroke='%23000' stroke-width='.4'/%3E%3Cpath d='M4.37 292.5v4.23M.26 292.63H8.2' fill='none' stroke='%23000' stroke-width='.3'/%3E%3Ctext font-weight='700' font-size='3.17' font-family='sans-serif'%3E%3Ctspan x='1.35' y='295.73'%3EF%3C/tspan%3E%3Ctspan x='5.03' y='295.68'%3EB%3C/tspan%3E%3C/text%3E%3C/g%3E%3C/svg%3E%0A");
+}
+
+button#lr-btn {
+  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8.47 8.47'%3E%3Crect transform='translate(0 -288.53)' ry='1.17' y='288.8' x='.27' height='7.94' width='7.94' fill='%23f9f9f9'/%3E%3Cg transform='translate(0 -288.53)'%3E%3Crect width='7.94' height='7.94' x='.27' y='288.8' ry='1.17' fill='none' stroke='%23000' stroke-width='.4' stroke-linejoin='round'/%3E%3Cpath d='M1.06 290.12H3.7m-2.64 1.33H3.7m-2.64 1.32H3.7m-2.64 1.3H3.7m-2.64 1.33H3.7' fill='none' stroke='%23000' stroke-width='.4'/%3E%3Cpath d='M4.37 288.8v7.94m0-4.11h3.96' fill='none' stroke='%23000' stroke-width='.3'/%3E%3Ctext font-weight='700' font-size='3.17' font-family='sans-serif'%3E%3Ctspan x='5.11' y='291.96'%3EF%3C/tspan%3E%3Ctspan x='5.03' y='295.68'%3EB%3C/tspan%3E%3C/text%3E%3C/g%3E%3C/svg%3E%0A");
+}
+
+button#bom-btn {
+  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8.47 8.47'%3E%3Crect transform='translate(0 -288.53)' ry='1.17' y='288.8' x='.27' height='7.94' width='7.94' fill='%23f9f9f9'/%3E%3Cg transform='translate(0 -288.53)' fill='none' stroke='%23000' stroke-width='.4'%3E%3Crect width='7.94' height='7.94' x='.27' y='288.8' ry='1.17' stroke-linejoin='round'/%3E%3Cpath d='M1.59 290.12h5.29M1.59 291.45h5.33M1.59 292.75h5.33M1.59 294.09h5.33M1.59 295.41h5.33'/%3E%3C/g%3E%3C/svg%3E");
+}
+
+button#bom-grouped-btn {
+  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='32' height='32'%3E%3Cg stroke='%23000' stroke-linejoin='round' class='layer'%3E%3Crect width='29' height='29' x='1.5' y='1.5' stroke-width='2' fill='%23fff' rx='5' ry='5'/%3E%3Cpath stroke-linecap='square' stroke-width='2' d='M6 10h4m4 0h5m4 0h3M6.1 22h3m3.9 0h5m4 0h4m-16-8h4m4 0h4'/%3E%3Cpath stroke-linecap='null' d='M5 17.5h22M5 26.6h22M5 5.5h22'/%3E%3C/g%3E%3C/svg%3E");
+}
+
+button#bom-ungrouped-btn {
+  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='32' height='32'%3E%3Cg stroke='%23000' stroke-linejoin='round' class='layer'%3E%3Crect width='29' height='29' x='1.5' y='1.5' stroke-width='2' fill='%23fff' rx='5' ry='5'/%3E%3Cpath stroke-linecap='square' stroke-width='2' d='M6 10h4m-4 8h3m-3 8h4'/%3E%3Cpath stroke-linecap='null' d='M5 13.5h22m-22 8h22M5 5.5h22'/%3E%3C/g%3E%3C/svg%3E");
+}
+
+button#bom-netlist-btn {
+  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='32' height='32'%3E%3Cg fill='none' stroke='%23000' class='layer'%3E%3Crect width='29' height='29' x='1.5' y='1.5' stroke-width='2' fill='%23fff' rx='5' ry='5'/%3E%3Cpath stroke-width='2' d='M6 26l6-6v-8m13.8-6.3l-6 6v8'/%3E%3Ccircle cx='11.8' cy='9.5' r='2.8' stroke-width='2'/%3E%3Ccircle cx='19.8' cy='22.8' r='2.8' stroke-width='2'/%3E%3C/g%3E%3C/svg%3E");
+}
+
+button#copy {
+  background-image: url("data:image/svg+xml,%3Csvg height='48' viewBox='0 0 48 48' width='48' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M0 0h48v48h-48z' fill='none'/%3E%3Cpath d='M32 2h-24c-2.21 0-4 1.79-4 4v28h4v-28h24v-4zm6 8h-22c-2.21 0-4 1.79-4 4v28c0 2.21 1.79 4 4 4h22c2.21 0 4-1.79 4-4v-28c0-2.21-1.79-4-4-4zm0 32h-22v-28h22v28z'/%3E%3C/svg%3E");
+  background-position: 6px 6px;
+  background-repeat: no-repeat;
+  background-size: 26px 26px;
+  border-radius: 6px;
+  height: 40px;
+  width: 40px;
+  margin: 10px 5px;
+}
+
+button#copy:active {
+  box-shadow: inset 0px 0px 5px #6c6c6c;
+}
+
+textarea.clipboard-temp {
+  position: fixed;
+  top: 0;
+  left: 0;
+  width: 2em;
+  height: 2em;
+  padding: 0;
+  border: None;
+  outline: None;
+  box-shadow: None;
+  background: transparent;
+}
+
+.left-most-button {
+  border-right: 0;
+  border-top-left-radius: 6px;
+  border-bottom-left-radius: 6px;
+}
+
+.middle-button {
+  border-right: 0;
+}
+
+.right-most-button {
+  border-top-right-radius: 6px;
+  border-bottom-right-radius: 6px;
+}
+
+.button-container {
+  font-size: 0;
+  margin: 10px 10px 10px 0px;
+}
+
+.dark .button-container {
+  filter: invert(1);
+}
+
+.button-container button {
+  background-size: 32px 32px;
+  background-position: 5px 5px;
+  background-repeat: no-repeat;
+}
+
+@media print {
+  .hideonprint {
+    display: none;
+  }
+}
+
+canvas {
+  cursor: crosshair;
+}
+
+canvas:active {
+  cursor: grabbing;
+}
+
+.fileinfo {
+  width: 100%;
+  max-width: 1000px;
+  border: none;
+  padding: 5px;
+}
+
+.fileinfo .title {
+  font-size: 20pt;
+  font-weight: bold;
+}
+
+.fileinfo td {
+  overflow: hidden;
+  white-space: nowrap;
+  max-width: 1px;
+  width: 50%;
+  text-overflow: ellipsis;
+}
+
+.bom {
+  border-collapse: collapse;
+  font-family: Consolas, "DejaVu Sans Mono", Monaco, monospace;
+  font-size: 10pt;
+  table-layout: fixed;
+  width: 100%;
+  margin-top: 1px;
+  position: relative;
+}
+
+.bom th,
+.bom td {
+  border: 1px solid black;
+  padding: 5px;
+  word-wrap: break-word;
+  text-align: center;
+  position: relative;
+}
+
+.dark .bom th,
+.dark .bom td {
+  border: 1px solid #777;
+}
+
+.bom th {
+  background-color: #CCCCCC;
+  background-clip: padding-box;
+}
+
+.dark .bom th {
+  background-color: #3b4749;
+}
+
+.bom tr.highlighted:nth-child(n) {
+  background-color: #cfc;
+}
+
+.dark .bom tr.highlighted:nth-child(n) {
+  background-color: #226022;
+}
+
+.bom tr:nth-child(even) {
+  background-color: #f2f2f2;
+}
+
+.dark .bom tr:nth-child(even) {
+  background-color: #313b40;
+}
+
+.bom tr.checked {
+  color: #1cb53d;
+}
+
+.dark .bom tr.checked {
+  color: #2cce54;
+}
+
+.bom tr {
+  transition: background-color 0.2s;
+}
+
+.bom .numCol {
+  width: 30px;
+}
+
+.bom .value {
+  width: 15%;
+}
+
+.bom .quantity {
+  width: 65px;
+}
+
+.bom th .sortmark {
+  position: absolute;
+  right: 1px;
+  top: 1px;
+  margin-top: -5px;
+  border-width: 5px;
+  border-style: solid;
+  border-color: transparent transparent #221 transparent;
+  transform-origin: 50% 85%;
+  transition: opacity 0.2s, transform 0.4s;
+}
+
+.dark .bom th .sortmark {
+  filter: invert(1);
+}
+
+.bom th .sortmark.none {
+  opacity: 0;
+}
+
+.bom th .sortmark.desc {
+  transform: rotate(180deg);
+}
+
+.bom th:hover .sortmark.none {
+  opacity: 0.5;
+}
+
+.bom .bom-checkbox {
+  width: 30px;
+  position: relative;
+  user-select: none;
+  -moz-user-select: none;
+}
+
+.bom .bom-checkbox:before {
+  content: "";
+  position: absolute;
+  border-width: 15px;
+  border-style: solid;
+  border-color: #51829f transparent transparent transparent;
+  visibility: hidden;
+  top: -15px;
+}
+
+.bom .bom-checkbox:after {
+  content: "Double click to set/unset all";
+  position: absolute;
+  color: white;
+  top: -35px;
+  left: -26px;
+  background: #51829f;
+  padding: 5px 15px;
+  border-radius: 8px;
+  white-space: nowrap;
+  visibility: hidden;
+}
+
+.bom .bom-checkbox:hover:before,
+.bom .bom-checkbox:hover:after {
+  visibility: visible;
+  transition: visibility 0.2s linear 1s;
+}
+
+.split {
+  -webkit-box-sizing: border-box;
+  -moz-box-sizing: border-box;
+  box-sizing: border-box;
+  overflow-y: auto;
+  overflow-x: hidden;
+  background-color: inherit;
+}
+
+.split.split-horizontal,
+.gutter.gutter-horizontal {
+  height: 100%;
+  float: left;
+}
+
+.gutter {
+  background-color: #ddd;
+  background-repeat: no-repeat;
+  background-position: 50%;
+  transition: background-color 0.3s;
+}
+
+.dark .gutter {
+  background-color: #777;
+}
+
+.gutter.gutter-horizontal {
+  background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAeCAYAAADkftS9AAAAIklEQVQoU2M4c+bMfxAGAgYYmwGrIIiDjrELjpo5aiZeMwF+yNnOs5KSvgAAAABJRU5ErkJggg==');
+  cursor: ew-resize;
+  width: 5px;
+}
+
+.gutter.gutter-vertical {
+  background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAFAQMAAABo7865AAAABlBMVEVHcEzMzMzyAv2sAAAAAXRSTlMAQObYZgAAABBJREFUeF5jOAMEEAIEEFwAn3kMwcB6I2AAAAAASUVORK5CYII=');
+  cursor: ns-resize;
+  height: 5px;
+}
+
+.searchbox {
+  float: left;
+  height: 40px;
+  margin: 10px 5px;
+  padding: 12px 32px;
+  font-family: Consolas, "DejaVu Sans Mono", Monaco, monospace;
+  font-size: 18px;
+  box-sizing: border-box;
+  border: 1px solid #888;
+  border-radius: 6px;
+  outline: none;
+  background-color: #eee;
+  transition: background-color 0.2s, border 0.2s;
+  background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABNklEQVQ4T8XSMUvDQBQH8P/LElFa/AIZHcTBQSz0I/gFstTBRR2KUC4ldDxw7h0Bl3RRUATxi4iiODgoiLNrbQYp5J6cpJJqomkX33Z37/14d/dIa33MzDuYI4johOI4XhyNRteO46zNYjDzAxE1yBZprVeZ+QbAUhXEGJMA2Ox2u4+fQIa0mPmsCgCgJYQ4t7lfgF0opQYAdv9ABkKI/UnOFCClXKjX61cA1osQY8x9kiRNKeV7IWA3oyhaSdP0FkAtjxhj3hzH2RBCPOf3pzqYHCilfAAX+URm9oMguPzeWSGQvUcMYC8rOBJCHBRdqxTo9/vbRHRqi8bj8XKv1xvODbiuW2u32/bvf0SlDv4XYOY7z/Mavu+nM1+BmQ+NMc0wDF/LprP0DbTWW0T00ul0nn4b7Q87+X4Qmfiq2wAAAABJRU5ErkJggg==');
+  background-position: 10px 10px;
+  background-repeat: no-repeat;
+}
+
+.dark .searchbox {
+  background-color: #111;
+  color: #eee;
+}
+
+.searchbox::placeholder {
+  color: #ccc;
+}
+
+.dark .searchbox::placeholder {
+  color: #666;
+}
+
+.filter {
+  width: calc(60% - 64px);
+}
+
+.reflookup {
+  width: calc(40% - 10px);
+}
+
+input[type=text]:focus {
+  background-color: white;
+  border: 1px solid #333;
+}
+
+.dark input[type=text]:focus {
+  background-color: #333;
+  border: 1px solid #ccc;
+}
+
+mark.highlight {
+  background-color: #5050ff;
+  color: #fff;
+  padding: 2px;
+  border-radius: 6px;
+}
+
+.dark mark.highlight {
+  background-color: #76a6da;
+  color: #111;
+}
+
+.menubtn {
+  background-color: white;
+  border: none;
+  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='36' height='36' viewBox='0 0 20 20'%3E%3Cpath fill='none' d='M0 0h20v20H0V0z'/%3E%3Cpath d='M15.95 10.78c.03-.25.05-.51.05-.78s-.02-.53-.06-.78l1.69-1.32c.15-.12.19-.34.1-.51l-1.6-2.77c-.1-.18-.31-.24-.49-.18l-1.99.8c-.42-.32-.86-.58-1.35-.78L12 2.34c-.03-.2-.2-.34-.4-.34H8.4c-.2 0-.36.14-.39.34l-.3 2.12c-.49.2-.94.47-1.35.78l-1.99-.8c-.18-.07-.39 0-.49.18l-1.6 2.77c-.1.18-.06.39.1.51l1.69 1.32c-.04.25-.07.52-.07.78s.02.53.06.78L2.37 12.1c-.15.12-.19.34-.1.51l1.6 2.77c.1.18.31.24.49.18l1.99-.8c.42.32.86.58 1.35.78l.3 2.12c.04.2.2.34.4.34h3.2c.2 0 .37-.14.39-.34l.3-2.12c.49-.2.94-.47 1.35-.78l1.99.8c.18.07.39 0 .49-.18l1.6-2.77c.1-.18.06-.39-.1-.51l-1.67-1.32zM10 13c-1.65 0-3-1.35-3-3s1.35-3 3-3 3 1.35 3 3-1.35 3-3 3z'/%3E%3C/svg%3E%0A");
+  background-position: center;
+  background-repeat: no-repeat;
+}
+
+.statsbtn {
+  background-color: white;
+  border: none;
+  background-image: url("data:image/svg+xml,%3Csvg width='36' height='36' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M4 6h28v24H4V6zm0 8h28v8H4m9-16v24h10V5.8' fill='none' stroke='%23000' stroke-width='2'/%3E%3C/svg%3E");
+  background-position: center;
+  background-repeat: no-repeat;
+}
+
+.iobtn {
+  background-color: white;
+  border: none;
+  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='36' height='36'%3E%3Cpath fill='none' stroke='%23000' stroke-width='2' d='M3 33v-7l6.8-7h16.5l6.7 7v7H3zM3.2 26H33M21 9l5-5.9 5 6h-2.5V15h-5V9H21zm-4.9 0l-5 6-5-6h2.5V3h5v6h2.5z'/%3E%3Cpath fill='none' stroke='%23000' d='M6.1 29.5H10'/%3E%3C/svg%3E");
+  background-position: center;
+  background-repeat: no-repeat;
+}
+
+.visbtn {
+  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24'%3E%3Cpath fill='none' stroke='%23333' d='M2.5 4.5h5v15h-5zM9.5 4.5h5v15h-5zM16.5 4.5h5v15h-5z'/%3E%3C/svg%3E");
+  background-position: center;
+  background-repeat: no-repeat;
+  padding: 15px;
+}
+
+#vismenu-content {
+  left: 0px;
+  font-family: Verdana, sans-serif;
+}
+
+.dark .statsbtn,
+.dark .savebtn,
+.dark .menubtn,
+.dark .iobtn,
+.dark .visbtn {
+  filter: invert(1);
+}
+
+.flexbox {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  width: 100%;
+}
+
+.savebtn {
+  background-color: #d6d6d6;
+  width: auto;
+  height: 30px;
+  flex-grow: 1;
+  margin: 5px;
+  border-radius: 4px;
+}
+
+.savebtn:active {
+  background-color: #0a0;
+  color: white;
+}
+
+.dark .savebtn:active {
+  /* This will be inverted */
+  background-color: #b3b;
+}
+
+.stats {
+  border-collapse: collapse;
+  font-size: 12pt;
+  table-layout: fixed;
+  width: 100%;
+  min-width: 450px;
+}
+
+.dark .stats td {
+  border: 1px solid #bbb;
+}
+
+.stats td {
+  border: 1px solid black;
+  padding: 5px;
+  word-wrap: break-word;
+  text-align: center;
+  position: relative;
+}
+
+#checkbox-stats div {
+  position: absolute;
+  left: 0;
+  top: 0;
+  height: 100%;
+  width: 100%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+#checkbox-stats .bar {
+  background-color: rgba(28, 251, 0, 0.6);
+}
+
+.menu {
+  position: relative;
+  display: inline-block;
+  margin: 10px 10px 10px 0px;
+}
+
+.menu-content {
+  font-size: 12pt !important;
+  text-align: left !important;
+  font-weight: normal !important;
+  display: none;
+  position: absolute;
+  background-color: white;
+  right: 0;
+  min-width: 300px;
+  box-shadow: 0px 8px 16px 0px rgba(0, 0, 0, 0.2);
+  z-index: 100;
+  padding: 8px;
+}
+
+.dark .menu-content {
+  background-color: #111;
+}
+
+.menu:hover .menu-content {
+  display: block;
+}
+
+.menu:hover .menubtn,
+.menu:hover .iobtn,
+.menu:hover .statsbtn {
+  background-color: #eee;
+}
+
+.menu-label {
+  display: inline-block;
+  padding: 8px;
+  border: 1px solid #ccc;
+  border-top: 0;
+  width: calc(100% - 18px);
+}
+
+.menu-label-top {
+  border-top: 1px solid #ccc;
+}
+
+.menu-textbox {
+  float: left;
+  height: 24px;
+  margin: 10px 5px;
+  padding: 5px 5px;
+  font-family: Consolas, "DejaVu Sans Mono", Monaco, monospace;
+  font-size: 14px;
+  box-sizing: border-box;
+  border: 1px solid #888;
+  border-radius: 4px;
+  outline: none;
+  background-color: #eee;
+  transition: background-color 0.2s, border 0.2s;
+  width: calc(100% - 10px);
+}
+
+.menu-textbox.invalid,
+.dark .menu-textbox.invalid {
+  color: red;
+}
+
+.dark .menu-textbox {
+  background-color: #222;
+  color: #eee;
+}
+
+.radio-container {
+  margin: 4px;
+}
+
+.topmostdiv {
+  width: 100%;
+  height: 100%;
+  background-color: white;
+  transition: background-color 0.3s;
+}
+
+#top {
+  height: 78px;
+  border-bottom: 2px solid black;
+}
+
+.dark #top {
+  border-bottom: 2px solid #ccc;
+}
+
+#dbg {
+  display: block;
+}
+
+::-webkit-scrollbar {
+  width: 8px;
+}
+
+::-webkit-scrollbar-track {
+  background: #aaa;
+}
+
+::-webkit-scrollbar-thumb {
+  background: #666;
+  border-radius: 3px;
+}
+
+::-webkit-scrollbar-thumb:hover {
+  background: #555;
+}
+
+.slider {
+  -webkit-appearance: none;
+  width: 100%;
+  margin: 3px 0;
+  padding: 0;
+  outline: none;
+  opacity: 0.7;
+  -webkit-transition: .2s;
+  transition: opacity .2s;
+  border-radius: 3px;
+}
+
+.slider:hover {
+  opacity: 1;
+}
+
+.slider:focus {
+  outline: none;
+}
+
+.slider::-webkit-slider-runnable-track {
+  -webkit-appearance: none;
+  width: 100%;
+  height: 8px;
+  background: #d3d3d3;
+  border-radius: 3px;
+  border: none;
+}
+
+.slider::-webkit-slider-thumb {
+  -webkit-appearance: none;
+  width: 15px;
+  height: 15px;
+  border-radius: 50%;
+  background: #0a0;
+  cursor: pointer;
+  margin-top: -4px;
+}
+
+.dark .slider::-webkit-slider-thumb {
+  background: #3d3;
+}
+
+.slider::-moz-range-thumb {
+  width: 15px;
+  height: 15px;
+  border-radius: 50%;
+  background: #0a0;
+  cursor: pointer;
+}
+
+.slider::-moz-range-track {
+  height: 8px;
+  background: #d3d3d3;
+  border-radius: 3px;
+}
+
+.dark .slider::-moz-range-thumb {
+  background: #3d3;
+}
+
+.slider::-ms-track {
+  width: 100%;
+  height: 8px;
+  border-width: 3px 0;
+  background: transparent;
+  border-color: transparent;
+  color: transparent;
+  transition: opacity .2s;
+}
+
+.slider::-ms-fill-lower {
+  background: #d3d3d3;
+  border: none;
+  border-radius: 3px;
+}
+
+.slider::-ms-fill-upper {
+  background: #d3d3d3;
+  border: none;
+  border-radius: 3px;
+}
+
+.slider::-ms-thumb {
+  width: 15px;
+  height: 15px;
+  border-radius: 50%;
+  background: #0a0;
+  cursor: pointer;
+  margin: 0;
+}
+
+.shameless-plug {
+  font-size: 0.8em;
+  text-align: center;
+  display: block;
+}
+
+a {
+  color: #0278a4;
+}
+
+.dark a {
+  color: #00b9fd;
+}
+
+#frontcanvas,
+#backcanvas {
+  touch-action: none;
+}
+
+.placeholder {
+  border: 1px dashed #9f9fda !important;
+  background-color: #edf2f7 !important;
+}
+
+.dragging {
+  z-index: 999;
+}
+
+.dark .dragging>table>tbody>tr {
+  background-color: #252c30;
+}
+
+.dark .placeholder {
+  filter: invert(1);
+}
+
+.column-spacer {
+  top: 0;
+  left: 0;
+  width: calc(100% - 4px);
+  position: absolute;
+  cursor: pointer;
+  user-select: none;
+  height: 100%;
+}
+
+.column-width-handle {
+  top: 0;
+  right: 0;
+  width: 4px;
+  position: absolute;
+  cursor: col-resize;
+  user-select: none;
+  height: 100%;
+}
+
+.column-width-handle:hover {
+  background-color: #4f99bd;
+}
+
+
+  </style>
+  <script type="text/javascript" >
+///////////////////////////////////////////////
+/*
+  Split.js - v1.3.5
+  MIT License
+  https://github.com/nathancahill/Split.js
+*/
+!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):e.Split=t()}(this,function(){"use strict";var e=window,t=e.document,n="addEventListener",i="removeEventListener",r="getBoundingClientRect",s=function(){return!1},o=e.attachEvent&&!e[n],a=["","-webkit-","-moz-","-o-"].filter(function(e){var n=t.createElement("div");return n.style.cssText="width:"+e+"calc(9px)",!!n.style.length}).shift()+"calc",l=function(e){return"string"==typeof e||e instanceof String?t.querySelector(e):e};return function(u,c){function z(e,t,n){var i=A(y,t,n);Object.keys(i).forEach(function(t){return e.style[t]=i[t]})}function h(e,t){var n=B(y,t);Object.keys(n).forEach(function(t){return e.style[t]=n[t]})}function f(e){var t=E[this.a],n=E[this.b],i=t.size+n.size;t.size=e/this.size*i,n.size=i-e/this.size*i,z(t.element,t.size,this.aGutterSize),z(n.element,n.size,this.bGutterSize)}function m(e){var t;this.dragging&&((t="touches"in e?e.touches[0][b]-this.start:e[b]-this.start)<=E[this.a].minSize+M+this.aGutterSize?t=E[this.a].minSize+this.aGutterSize:t>=this.size-(E[this.b].minSize+M+this.bGutterSize)&&(t=this.size-(E[this.b].minSize+this.bGutterSize)),f.call(this,t),c.onDrag&&c.onDrag())}function g(){var e=E[this.a].element,t=E[this.b].element;this.size=e[r]()[y]+t[r]()[y]+this.aGutterSize+this.bGutterSize,this.start=e[r]()[G]}function d(){var t=this,n=E[t.a].element,r=E[t.b].element;t.dragging&&c.onDragEnd&&c.onDragEnd(),t.dragging=!1,e[i]("mouseup",t.stop),e[i]("touchend",t.stop),e[i]("touchcancel",t.stop),t.parent[i]("mousemove",t.move),t.parent[i]("touchmove",t.move),delete t.stop,delete t.move,n[i]("selectstart",s),n[i]("dragstart",s),r[i]("selectstart",s),r[i]("dragstart",s),n.style.userSelect="",n.style.webkitUserSelect="",n.style.MozUserSelect="",n.style.pointerEvents="",r.style.userSelect="",r.style.webkitUserSelect="",r.style.MozUserSelect="",r.style.pointerEvents="",t.gutter.style.cursor="",t.parent.style.cursor=""}function S(t){var i=this,r=E[i.a].element,o=E[i.b].element;!i.dragging&&c.onDragStart&&c.onDragStart(),t.preventDefault(),i.dragging=!0,i.move=m.bind(i),i.stop=d.bind(i),e[n]("mouseup",i.stop),e[n]("touchend",i.stop),e[n]("touchcancel",i.stop),i.parent[n]("mousemove",i.move),i.parent[n]("touchmove",i.move),r[n]("selectstart",s),r[n]("dragstart",s),o[n]("selectstart",s),o[n]("dragstart",s),r.style.userSelect="none",r.style.webkitUserSelect="none",r.style.MozUserSelect="none",r.style.pointerEvents="none",o.style.userSelect="none",o.style.webkitUserSelect="none",o.style.MozUserSelect="none",o.style.pointerEvents="none",i.gutter.style.cursor=j,i.parent.style.cursor=j,g.call(i)}function v(e){e.forEach(function(t,n){if(n>0){var i=F[n-1],r=E[i.a],s=E[i.b];r.size=e[n-1],s.size=t,z(r.element,r.size,i.aGutterSize),z(s.element,s.size,i.bGutterSize)}})}function p(){F.forEach(function(e){e.parent.removeChild(e.gutter),E[e.a].element.style[y]="",E[e.b].element.style[y]=""})}void 0===c&&(c={});var y,b,G,E,w=l(u[0]).parentNode,D=e.getComputedStyle(w).flexDirection,U=c.sizes||u.map(function(){return 100/u.length}),k=void 0!==c.minSize?c.minSize:100,x=Array.isArray(k)?k:u.map(function(){return k}),L=void 0!==c.gutterSize?c.gutterSize:10,M=void 0!==c.snapOffset?c.snapOffset:30,O=c.direction||"horizontal",j=c.cursor||("horizontal"===O?"ew-resize":"ns-resize"),C=c.gutter||function(e,n){var i=t.createElement("div");return i.className="gutter gutter-"+n,i},A=c.elementStyle||function(e,t,n){var i={};return"string"==typeof t||t instanceof String?i[e]=t:i[e]=o?t+"%":a+"("+t+"% - "+n+"px)",i},B=c.gutterStyle||function(e,t){return n={},n[e]=t+"px",n;var n};"horizontal"===O?(y="width","clientWidth",b="clientX",G="left","paddingLeft"):"vertical"===O&&(y="height","clientHeight",b="clientY",G="top","paddingTop");var F=[];return E=u.map(function(e,t){var i,s={element:l(e),size:U[t],minSize:x[t]};if(t>0&&(i={a:t-1,b:t,dragging:!1,isFirst:1===t,isLast:t===u.length-1,direction:O,parent:w},i.aGutterSize=L,i.bGutterSize=L,i.isFirst&&(i.aGutterSize=L/2),i.isLast&&(i.bGutterSize=L/2),"row-reverse"===D||"column-reverse"===D)){var a=i.a;i.a=i.b,i.b=a}if(!o&&t>0){var c=C(t,O);h(c,L),c[n]("mousedown",S.bind(i)),c[n]("touchstart",S.bind(i)),w.insertBefore(c,s.element),i.gutter=c}0===t||t===u.length-1?z(s.element,s.size,L/2):z(s.element,s.size,L);var f=s.element[r]()[y];return f<s.minSize&&(s.minSize=f),t>0&&F.push(i),s}),o?{setSizes:v,destroy:p}:{setSizes:v,getSizes:function(){return E.map(function(e){return e.size})},collapse:function(e){if(e===F.length){var t=F[e-1];g.call(t),o||f.call(t,t.size-t.bGutterSize)}else{var n=F[e];g.call(n),o||f.call(n,n.aGutterSize)}},destroy:p}}});
+
+///////////////////////////////////////////////
+
+///////////////////////////////////////////////
+// Copyright (c) 2013 Pieroxy <pieroxy@pieroxy.net>
+// This work is free. You can redistribute it and/or modify it
+// under the terms of the WTFPL, Version 2
+// For more information see LICENSE.txt or http://www.wtfpl.net/
+//
+// For more information, the home page:
+// http://pieroxy.net/blog/pages/lz-string/testing.html
+//
+// LZ-based compression algorithm, version 1.4.4
+var LZString=function(){var o=String.fromCharCode,i={};var n={decompressFromBase64:function(o){return null==o?"":""==o?null:n._decompress(o.length,32,function(n){return function(o,n){if(!i[o]){i[o]={};for(var t=0;t<o.length;t++)i[o][o.charAt(t)]=t}return i[o][n]}("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",o.charAt(n))})},_decompress:function(i,n,t){var r,e,a,s,p,u,l,f=[],c=4,d=4,h=3,v="",g=[],m={val:t(0),position:n,index:1};for(r=0;r<3;r+=1)f[r]=r;for(a=0,p=Math.pow(2,2),u=1;u!=p;)s=m.val&m.position,m.position>>=1,0==m.position&&(m.position=n,m.val=t(m.index++)),a|=(s>0?1:0)*u,u<<=1;switch(a){case 0:for(a=0,p=Math.pow(2,8),u=1;u!=p;)s=m.val&m.position,m.position>>=1,0==m.position&&(m.position=n,m.val=t(m.index++)),a|=(s>0?1:0)*u,u<<=1;l=o(a);break;case 1:for(a=0,p=Math.pow(2,16),u=1;u!=p;)s=m.val&m.position,m.position>>=1,0==m.position&&(m.position=n,m.val=t(m.index++)),a|=(s>0?1:0)*u,u<<=1;l=o(a);break;case 2:return""}for(f[3]=l,e=l,g.push(l);;){if(m.index>i)return"";for(a=0,p=Math.pow(2,h),u=1;u!=p;)s=m.val&m.position,m.position>>=1,0==m.position&&(m.position=n,m.val=t(m.index++)),a|=(s>0?1:0)*u,u<<=1;switch(l=a){case 0:for(a=0,p=Math.pow(2,8),u=1;u!=p;)s=m.val&m.position,m.position>>=1,0==m.position&&(m.position=n,m.val=t(m.index++)),a|=(s>0?1:0)*u,u<<=1;f[d++]=o(a),l=d-1,c--;break;case 1:for(a=0,p=Math.pow(2,16),u=1;u!=p;)s=m.val&m.position,m.position>>=1,0==m.position&&(m.position=n,m.val=t(m.index++)),a|=(s>0?1:0)*u,u<<=1;f[d++]=o(a),l=d-1,c--;break;case 2:return g.join("")}if(0==c&&(c=Math.pow(2,h),h++),f[l])v=f[l];else{if(l!==d)return null;v=e+e.charAt(0)}g.push(v),f[d++]=e+v.charAt(0),e=v,0==--c&&(c=Math.pow(2,h),h++)}}};return n}();"function"==typeof define&&define.amd?define(function(){return LZString}):"undefined"!=typeof module&&null!=module?module.exports=LZString:"undefined"!=typeof angular&&null!=angular&&angular.module("LZString",[]).factory("LZString",function(){return LZString});
+///////////////////////////////////////////////
+
+///////////////////////////////////////////////
+/*!
+ * PEP v0.4.3 | https://github.com/jquery/PEP
+ * Copyright jQuery Foundation and other contributors | http://jquery.org/license
+ */
+!function(a,b){"object"==typeof exports&&"undefined"!=typeof module?module.exports=b():"function"==typeof define&&define.amd?define(b):a.PointerEventsPolyfill=b()}(this,function(){"use strict";function a(a,b){b=b||Object.create(null);var c=document.createEvent("Event");c.initEvent(a,b.bubbles||!1,b.cancelable||!1);
+for(var d,e=2;e<m.length;e++)d=m[e],c[d]=b[d]||n[e];c.buttons=b.buttons||0;
+var f=0;return f=b.pressure&&c.buttons?b.pressure:c.buttons?.5:0,c.x=c.clientX,c.y=c.clientY,c.pointerId=b.pointerId||0,c.width=b.width||0,c.height=b.height||0,c.pressure=f,c.tiltX=b.tiltX||0,c.tiltY=b.tiltY||0,c.twist=b.twist||0,c.tangentialPressure=b.tangentialPressure||0,c.pointerType=b.pointerType||"",c.hwTimestamp=b.hwTimestamp||0,c.isPrimary=b.isPrimary||!1,c}function b(){this.array=[],this.size=0}function c(a,b,c,d){this.addCallback=a.bind(d),this.removeCallback=b.bind(d),this.changedCallback=c.bind(d),A&&(this.observer=new A(this.mutationWatcher.bind(this)))}function d(a){return"body /shadow-deep/ "+e(a)}function e(a){return'[touch-action="'+a+'"]'}function f(a){return"{ -ms-touch-action: "+a+"; touch-action: "+a+"; }"}function g(){if(F){D.forEach(function(a){String(a)===a?(E+=e(a)+f(a)+"\n",G&&(E+=d(a)+f(a)+"\n")):(E+=a.selectors.map(e)+f(a.rule)+"\n",G&&(E+=a.selectors.map(d)+f(a.rule)+"\n"))});var a=document.createElement("style");a.textContent=E,document.head.appendChild(a)}}function h(){if(!window.PointerEvent){if(window.PointerEvent=a,window.navigator.msPointerEnabled){var b=window.navigator.msMaxTouchPoints;Object.defineProperty(window.navigator,"maxTouchPoints",{value:b,enumerable:!0}),u.registerSource("ms",_)}else Object.defineProperty(window.navigator,"maxTouchPoints",{value:0,enumerable:!0}),u.registerSource("mouse",N),void 0!==window.ontouchstart&&u.registerSource("touch",V);u.register(document)}}function i(a){if(!u.pointermap.has(a)){var b=new Error("InvalidPointerId");throw b.name="InvalidPointerId",b}}function j(a){for(var b=a.parentNode;b&&b!==a.ownerDocument;)b=b.parentNode;if(!b){var c=new Error("InvalidStateError");throw c.name="InvalidStateError",c}}function k(a){var b=u.pointermap.get(a);return 0!==b.buttons}function l(){window.Element&&!Element.prototype.setPointerCapture&&Object.defineProperties(Element.prototype,{setPointerCapture:{value:W},releasePointerCapture:{value:X},hasPointerCapture:{value:Y}})}
+var m=["bubbles","cancelable","view","detail","screenX","screenY","clientX","clientY","ctrlKey","altKey","shiftKey","metaKey","button","relatedTarget","pageX","pageY"],n=[!1,!1,null,null,0,0,0,0,!1,!1,!1,!1,0,null,0,0],o=window.Map&&window.Map.prototype.forEach,p=o?Map:b;b.prototype={set:function(a,b){return void 0===b?this["delete"](a):(this.has(a)||this.size++,void(this.array[a]=b))},has:function(a){return void 0!==this.array[a]},"delete":function(a){this.has(a)&&(delete this.array[a],this.size--)},get:function(a){return this.array[a]},clear:function(){this.array.length=0,this.size=0},forEach:function(a,b){return this.array.forEach(function(c,d){a.call(b,c,d,this)},this)}};var q=["bubbles","cancelable","view","detail","screenX","screenY","clientX","clientY","ctrlKey","altKey","shiftKey","metaKey","button","relatedTarget","buttons","pointerId","width","height","pressure","tiltX","tiltY","pointerType","hwTimestamp","isPrimary","type","target","currentTarget","which","pageX","pageY","timeStamp"],r=[!1,!1,null,null,0,0,0,0,!1,!1,!1,!1,0,null,0,0,0,0,0,0,0,"",0,!1,"",null,null,0,0,0,0],s={pointerover:1,pointerout:1,pointerenter:1,pointerleave:1},t="undefined"!=typeof SVGElementInstance,u={pointermap:new p,eventMap:Object.create(null),captureInfo:Object.create(null),eventSources:Object.create(null),eventSourceList:[],registerSource:function(a,b){var c=b,d=c.events;d&&(d.forEach(function(a){c[a]&&(this.eventMap[a]=c[a].bind(c))},this),this.eventSources[a]=c,this.eventSourceList.push(c))},register:function(a){for(var b,c=this.eventSourceList.length,d=0;d<c&&(b=this.eventSourceList[d]);d++)
+b.register.call(b,a)},unregister:function(a){for(var b,c=this.eventSourceList.length,d=0;d<c&&(b=this.eventSourceList[d]);d++)
+b.unregister.call(b,a)},contains:function(a,b){try{return a.contains(b)}catch(c){return!1}},down:function(a){a.bubbles=!0,this.fireEvent("pointerdown",a)},move:function(a){a.bubbles=!0,this.fireEvent("pointermove",a)},up:function(a){a.bubbles=!0,this.fireEvent("pointerup",a)},enter:function(a){a.bubbles=!1,this.fireEvent("pointerenter",a)},leave:function(a){a.bubbles=!1,this.fireEvent("pointerleave",a)},over:function(a){a.bubbles=!0,this.fireEvent("pointerover",a)},out:function(a){a.bubbles=!0,this.fireEvent("pointerout",a)},cancel:function(a){a.bubbles=!0,this.fireEvent("pointercancel",a)},leaveOut:function(a){this.out(a),this.propagate(a,this.leave,!1)},enterOver:function(a){this.over(a),this.propagate(a,this.enter,!0)},eventHandler:function(a){if(!a._handledByPE){var b=a.type,c=this.eventMap&&this.eventMap[b];c&&c(a),a._handledByPE=!0}},listen:function(a,b){b.forEach(function(b){this.addEvent(a,b)},this)},unlisten:function(a,b){b.forEach(function(b){this.removeEvent(a,b)},this)},addEvent:function(a,b){a.addEventListener(b,this.boundHandler)},removeEvent:function(a,b){a.removeEventListener(b,this.boundHandler)},makeEvent:function(b,c){this.captureInfo[c.pointerId]&&(c.relatedTarget=null);var d=new a(b,c);return c.preventDefault&&(d.preventDefault=c.preventDefault),d._target=d._target||c.target,d},fireEvent:function(a,b){var c=this.makeEvent(a,b);return this.dispatchEvent(c)},cloneEvent:function(a){for(var b,c=Object.create(null),d=0;d<q.length;d++)b=q[d],c[b]=a[b]||r[d],!t||"target"!==b&&"relatedTarget"!==b||c[b]instanceof SVGElementInstance&&(c[b]=c[b].correspondingUseElement);return a.preventDefault&&(c.preventDefault=function(){a.preventDefault()}),c},getTarget:function(a){var b=this.captureInfo[a.pointerId];return b?a._target!==b&&a.type in s?void 0:b:a._target},propagate:function(a,b,c){for(var d=a.target,e=[];d!==document&&!d.contains(a.relatedTarget);) if(e.push(d),d=d.parentNode,!d)return;c&&e.reverse(),e.forEach(function(c){a.target=c,b.call(this,a)},this)},setCapture:function(b,c,d){this.captureInfo[b]&&this.releaseCapture(b,d),this.captureInfo[b]=c,this.implicitRelease=this.releaseCapture.bind(this,b,d),document.addEventListener("pointerup",this.implicitRelease),document.addEventListener("pointercancel",this.implicitRelease);var e=new a("gotpointercapture");e.pointerId=b,e._target=c,d||this.asyncDispatchEvent(e)},releaseCapture:function(b,c){var d=this.captureInfo[b];if(d){this.captureInfo[b]=void 0,document.removeEventListener("pointerup",this.implicitRelease),document.removeEventListener("pointercancel",this.implicitRelease);var e=new a("lostpointercapture");e.pointerId=b,e._target=d,c||this.asyncDispatchEvent(e)}},dispatchEvent:/*scope.external.dispatchEvent || */function(a){var b=this.getTarget(a);if(b)return b.dispatchEvent(a)},asyncDispatchEvent:function(a){requestAnimationFrame(this.dispatchEvent.bind(this,a))}};u.boundHandler=u.eventHandler.bind(u);var v={shadow:function(a){if(a)return a.shadowRoot||a.webkitShadowRoot},canTarget:function(a){return a&&Boolean(a.elementFromPoint)},targetingShadow:function(a){var b=this.shadow(a);if(this.canTarget(b))return b},olderShadow:function(a){var b=a.olderShadowRoot;if(!b){var c=a.querySelector("shadow");c&&(b=c.olderShadowRoot)}return b},allShadows:function(a){for(var b=[],c=this.shadow(a);c;)b.push(c),c=this.olderShadow(c);return b},searchRoot:function(a,b,c){if(a){var d,e,f=a.elementFromPoint(b,c);for(e=this.targetingShadow(f);e;){if(d=e.elementFromPoint(b,c)){var g=this.targetingShadow(d);return this.searchRoot(g,b,c)||d} e=this.olderShadow(e)} return f}},owner:function(a){
+for(var b=a;b.parentNode;)b=b.parentNode;
+return b.nodeType!==Node.DOCUMENT_NODE&&b.nodeType!==Node.DOCUMENT_FRAGMENT_NODE&&(b=document),b},findTarget:function(a){var b=a.clientX,c=a.clientY,d=this.owner(a.target);
+return d.elementFromPoint(b,c)||(d=document),this.searchRoot(d,b,c)}},w=Array.prototype.forEach.call.bind(Array.prototype.forEach),x=Array.prototype.map.call.bind(Array.prototype.map),y=Array.prototype.slice.call.bind(Array.prototype.slice),z=Array.prototype.filter.call.bind(Array.prototype.filter),A=window.MutationObserver||window.WebKitMutationObserver,B="[touch-action]",C={subtree:!0,childList:!0,attributes:!0,attributeOldValue:!0,attributeFilter:["touch-action"]};c.prototype={watchSubtree:function(a){
+//
+this.observer&&v.canTarget(a)&&this.observer.observe(a,C)},enableOnSubtree:function(a){this.watchSubtree(a),a===document&&"complete"!==document.readyState?this.installOnLoad():this.installNewSubtree(a)},installNewSubtree:function(a){w(this.findElements(a),this.addElement,this)},findElements:function(a){return a.querySelectorAll?a.querySelectorAll(B):[]},removeElement:function(a){this.removeCallback(a)},addElement:function(a){this.addCallback(a)},elementChanged:function(a,b){this.changedCallback(a,b)},concatLists:function(a,b){return a.concat(y(b))},
+installOnLoad:function(){document.addEventListener("readystatechange",function(){"complete"===document.readyState&&this.installNewSubtree(document)}.bind(this))},isElement:function(a){return a.nodeType===Node.ELEMENT_NODE},flattenMutationTree:function(a){
+var b=x(a,this.findElements,this);
+return b.push(z(a,this.isElement)),b.reduce(this.concatLists,[])},mutationWatcher:function(a){a.forEach(this.mutationHandler,this)},mutationHandler:function(a){if("childList"===a.type){var b=this.flattenMutationTree(a.addedNodes);b.forEach(this.addElement,this);var c=this.flattenMutationTree(a.removedNodes);c.forEach(this.removeElement,this)}else"attributes"===a.type&&this.elementChanged(a.target,a.oldValue)}};var D=["none","auto","pan-x","pan-y",{rule:"pan-x pan-y",selectors:["pan-x pan-y","pan-y pan-x"]}],E="",F=window.PointerEvent||window.MSPointerEvent,G=!window.ShadowDOMPolyfill&&document.head.createShadowRoot,H=u.pointermap,I=25,J=[1,4,2,8,16],K=!1;try{K=1===new MouseEvent("test",{buttons:1}).buttons}catch(L){}
+var M,N={POINTER_ID:1,POINTER_TYPE:"mouse",events:["mousedown","mousemove","mouseup","mouseover","mouseout"],register:function(a){u.listen(a,this.events)},unregister:function(a){u.unlisten(a,this.events)},lastTouches:[],
+isEventSimulatedFromTouch:function(a){for(var b,c=this.lastTouches,d=a.clientX,e=a.clientY,f=0,g=c.length;f<g&&(b=c[f]);f++){
+var h=Math.abs(d-b.x),i=Math.abs(e-b.y);if(h<=I&&i<=I)return!0}},prepareEvent:function(a){var b=u.cloneEvent(a),c=b.preventDefault;return b.preventDefault=function(){a.preventDefault(),c()},b.pointerId=this.POINTER_ID,b.isPrimary=!0,b.pointerType=this.POINTER_TYPE,b},prepareButtonsForMove:function(a,b){var c=H.get(this.POINTER_ID);
+0!==b.which&&c?a.buttons=c.buttons:a.buttons=0,b.buttons=a.buttons},mousedown:function(a){if(!this.isEventSimulatedFromTouch(a)){var b=H.get(this.POINTER_ID),c=this.prepareEvent(a);K||(c.buttons=J[c.button],b&&(c.buttons|=b.buttons),a.buttons=c.buttons),H.set(this.POINTER_ID,a),b&&0!==b.buttons?u.move(c):u.down(c)}},mousemove:function(a){if(!this.isEventSimulatedFromTouch(a)){var b=this.prepareEvent(a);K||this.prepareButtonsForMove(b,a),b.button=-1,H.set(this.POINTER_ID,a),u.move(b)}},mouseup:function(a){if(!this.isEventSimulatedFromTouch(a)){var b=H.get(this.POINTER_ID),c=this.prepareEvent(a);if(!K){var d=J[c.button];
+c.buttons=b?b.buttons&~d:0,a.buttons=c.buttons}H.set(this.POINTER_ID,a),
+c.buttons&=~J[c.button],0===c.buttons?u.up(c):u.move(c)}},mouseover:function(a){if(!this.isEventSimulatedFromTouch(a)){var b=this.prepareEvent(a);K||this.prepareButtonsForMove(b,a),b.button=-1,H.set(this.POINTER_ID,a),u.enterOver(b)}},mouseout:function(a){if(!this.isEventSimulatedFromTouch(a)){var b=this.prepareEvent(a);K||this.prepareButtonsForMove(b,a),b.button=-1,u.leaveOut(b)}},cancel:function(a){var b=this.prepareEvent(a);u.cancel(b),this.deactivateMouse()},deactivateMouse:function(){H["delete"](this.POINTER_ID)}},O=u.captureInfo,P=v.findTarget.bind(v),Q=v.allShadows.bind(v),R=u.pointermap,S=2500,T=200,U="touch-action",V={events:["touchstart","touchmove","touchend","touchcancel"],register:function(a){M.enableOnSubtree(a)},unregister:function(){},elementAdded:function(a){var b=a.getAttribute(U),c=this.touchActionToScrollType(b);c&&(a._scrollType=c,u.listen(a,this.events),
+Q(a).forEach(function(a){a._scrollType=c,u.listen(a,this.events)},this))},elementRemoved:function(a){a._scrollType=void 0,u.unlisten(a,this.events),
+Q(a).forEach(function(a){a._scrollType=void 0,u.unlisten(a,this.events)},this)},elementChanged:function(a,b){var c=a.getAttribute(U),d=this.touchActionToScrollType(c),e=this.touchActionToScrollType(b);
+d&&e?(a._scrollType=d,Q(a).forEach(function(a){a._scrollType=d},this)):e?this.elementRemoved(a):d&&this.elementAdded(a)},scrollTypes:{EMITTER:"none",XSCROLLER:"pan-x",YSCROLLER:"pan-y",SCROLLER:/^(?:pan-x pan-y)|(?:pan-y pan-x)|auto$/},touchActionToScrollType:function(a){var b=a,c=this.scrollTypes;return"none"===b?"none":b===c.XSCROLLER?"X":b===c.YSCROLLER?"Y":c.SCROLLER.exec(b)?"XY":void 0},POINTER_TYPE:"touch",firstTouch:null,isPrimaryTouch:function(a){return this.firstTouch===a.identifier},setPrimaryTouch:function(a){
+(0===R.size||1===R.size&&R.has(1))&&(this.firstTouch=a.identifier,this.firstXY={X:a.clientX,Y:a.clientY},this.scrolling=!1,this.cancelResetClickCount())},removePrimaryPointer:function(a){a.isPrimary&&(this.firstTouch=null,this.firstXY=null,this.resetClickCount())},clickCount:0,resetId:null,resetClickCount:function(){var a=function(){this.clickCount=0,this.resetId=null}.bind(this);this.resetId=setTimeout(a,T)},cancelResetClickCount:function(){this.resetId&&clearTimeout(this.resetId)},typeToButtons:function(a){var b=0;return"touchstart"!==a&&"touchmove"!==a||(b=1),b},touchToPointer:function(a){var b=this.currentTouchEvent,c=u.cloneEvent(a),d=c.pointerId=a.identifier+2;c.target=O[d]||P(c),c.bubbles=!0,c.cancelable=!0,c.detail=this.clickCount,c.button=0,c.buttons=this.typeToButtons(b.type),c.width=2*(a.radiusX||a.webkitRadiusX||0),c.height=2*(a.radiusY||a.webkitRadiusY||0),c.pressure=a.force||a.webkitForce||.5,c.isPrimary=this.isPrimaryTouch(a),c.pointerType=this.POINTER_TYPE,
+c.altKey=b.altKey,c.ctrlKey=b.ctrlKey,c.metaKey=b.metaKey,c.shiftKey=b.shiftKey;
+var e=this;return c.preventDefault=function(){e.scrolling=!1,e.firstXY=null,b.preventDefault()},c},processTouches:function(a,b){var c=a.changedTouches;this.currentTouchEvent=a;for(var d,e=0;e<c.length;e++)d=c[e],b.call(this,this.touchToPointer(d))},
+shouldScroll:function(a){if(this.firstXY){var b,c=a.currentTarget._scrollType;if("none"===c)
+b=!1;else if("XY"===c)
+b=!0;else{var d=a.changedTouches[0],e=c,f="Y"===c?"X":"Y",g=Math.abs(d["client"+e]-this.firstXY[e]),h=Math.abs(d["client"+f]-this.firstXY[f]);
+b=g>=h}return this.firstXY=null,b}},findTouch:function(a,b){for(var c,d=0,e=a.length;d<e&&(c=a[d]);d++)if(c.identifier===b)return!0},
+vacuumTouches:function(a){var b=a.touches;
+if(R.size>=b.length){var c=[];R.forEach(function(a,d){
+if(1!==d&&!this.findTouch(b,d-2)){var e=a.out;c.push(e)}},this),c.forEach(this.cancelOut,this)}},touchstart:function(a){this.vacuumTouches(a),this.setPrimaryTouch(a.changedTouches[0]),this.dedupSynthMouse(a),this.scrolling||(this.clickCount++,this.processTouches(a,this.overDown))},overDown:function(a){R.set(a.pointerId,{target:a.target,out:a,outTarget:a.target}),u.enterOver(a),u.down(a)},touchmove:function(a){this.scrolling||(this.shouldScroll(a)?(this.scrolling=!0,this.touchcancel(a)):(a.preventDefault(),this.processTouches(a,this.moveOverOut)))},moveOverOut:function(a){var b=a,c=R.get(b.pointerId);
+if(c){var d=c.out,e=c.outTarget;u.move(b),d&&e!==b.target&&(d.relatedTarget=b.target,b.relatedTarget=e,
+d.target=e,b.target?(u.leaveOut(d),u.enterOver(b)):(
+b.target=e,b.relatedTarget=null,this.cancelOut(b))),c.out=b,c.outTarget=b.target}},touchend:function(a){this.dedupSynthMouse(a),this.processTouches(a,this.upOut)},upOut:function(a){this.scrolling||(u.up(a),u.leaveOut(a)),this.cleanUpPointer(a)},touchcancel:function(a){this.processTouches(a,this.cancelOut)},cancelOut:function(a){u.cancel(a),u.leaveOut(a),this.cleanUpPointer(a)},cleanUpPointer:function(a){R["delete"](a.pointerId),this.removePrimaryPointer(a)},
+dedupSynthMouse:function(a){var b=N.lastTouches,c=a.changedTouches[0];
+if(this.isPrimaryTouch(c)){
+var d={x:c.clientX,y:c.clientY};b.push(d);var e=function(a,b){var c=a.indexOf(b);c>-1&&a.splice(c,1)}.bind(null,b,d);setTimeout(e,S)}}};M=new c(V.elementAdded,V.elementRemoved,V.elementChanged,V);var W,X,Y,Z=u.pointermap,$=window.MSPointerEvent&&"number"==typeof window.MSPointerEvent.MSPOINTER_TYPE_MOUSE,_={events:["MSPointerDown","MSPointerMove","MSPointerUp","MSPointerOut","MSPointerOver","MSPointerCancel","MSGotPointerCapture","MSLostPointerCapture"],register:function(a){u.listen(a,this.events)},unregister:function(a){u.unlisten(a,this.events)},POINTER_TYPES:["","unavailable","touch","pen","mouse"],prepareEvent:function(a){var b=a;return $&&(b=u.cloneEvent(a),b.pointerType=this.POINTER_TYPES[a.pointerType]),b},cleanup:function(a){Z["delete"](a)},MSPointerDown:function(a){Z.set(a.pointerId,a);var b=this.prepareEvent(a);u.down(b)},MSPointerMove:function(a){var b=this.prepareEvent(a);u.move(b)},MSPointerUp:function(a){var b=this.prepareEvent(a);u.up(b),this.cleanup(a.pointerId)},MSPointerOut:function(a){var b=this.prepareEvent(a);u.leaveOut(b)},MSPointerOver:function(a){var b=this.prepareEvent(a);u.enterOver(b)},MSPointerCancel:function(a){var b=this.prepareEvent(a);u.cancel(b),this.cleanup(a.pointerId)},MSLostPointerCapture:function(a){var b=u.makeEvent("lostpointercapture",a);u.dispatchEvent(b)},MSGotPointerCapture:function(a){var b=u.makeEvent("gotpointercapture",a);u.dispatchEvent(b)}},aa=window.navigator;aa.msPointerEnabled?(W=function(a){i(a),j(this),k(a)&&(u.setCapture(a,this,!0),this.msSetPointerCapture(a))},X=function(a){i(a),u.releaseCapture(a,!0),this.msReleasePointerCapture(a)}):(W=function(a){i(a),j(this),k(a)&&u.setCapture(a,this)},X=function(a){i(a),u.releaseCapture(a)}),Y=function(a){return!!u.captureInfo[a]},g(),h(),l();var ba={dispatcher:u,Installer:c,PointerEvent:a,PointerMap:p,targetFinding:v};return ba});
+
+///////////////////////////////////////////////
+
+///////////////////////////////////////////////
+var config = {"dark_mode": false, "show_pads": true, "show_fabrication": false, "show_silkscreen": true, "highlight_pin1": false, "redraw_on_drag": true, "board_rotation": 0, "checkboxes": "Sourced,Placed", "bom_view": "left-right", "layer_view": "FB", "fields": ["Value", "Footprint"]}
+///////////////////////////////////////////////
+
+///////////////////////////////////////////////
+var pcbdata = JSON.parse(LZString.decompressFromBase64("N4IgpgJg5mDOD6AjRB7AHiAXAAlAWwEsA7DHARgGYAGAOirIBpsRCiBPLbAVgHYaAOACz8ATBQCcTFgEM0pbGR60yjZnlkcc4gGw1tKnvoC+UyDFicA2qAAubAA5hOIWGCh4wRGyCmwb0gCdvHEsyQUE6bSZtCkjFfQBdUyIIKzIuXSoo7Bi4wzIk5gB3AggbAAtOZRNcEDtHZ0CAYx9mP0Dg7FCuWiymQxoM+IKpAOkIAgBXCxwRXt9/IOkiKAAbJxwAWm1+Ggp+ZIhltY3sEWE9g+LSiqqaMhrbB1OXNw8vVpdFzu7M7NyssNCuAUmkMpEmFxdvxxKIJMCSmVKjhqkwng0cK93J5vAsOmlwhDuNDYWJxMDPKkQmEIn0crFAfkETdkdhUbV6i9mp92kECQzslCBKT4aNxlMZgo6Hilit1pxNioyHoRIdjvKUdLrki7g80XVno0Ai0ZT90nw6UKYXDyWKJtNOMqqKb1acyPx5sxKa7OCJxMrtKrtbdNQ9gRAxiUVpLQLACKsANawJoBMCeTigABiVls5QITQTRDgktoFF8ADcoPZpCHmABZdKxFT8XiMGH3Kg8MIiAAy6QiIn0ghEB3bZE73ZAjzqeYLRdgJb2FarNdZIAbgl02h2rYY7ayXeHfcEfH4KmHo/EkUPIg3fHOe6vMRbXeP98Ee92gnErcEU/1FT5oWxa6lwy7VrW65hLQP5UCIjCBjQ4jIUqx7KiIYgUIICGxPs/r8BQG60Nu/YMACQjiDwXBoXoXb8EIZEiDQPBCII1FhE6ggSA+uQxOOPA8BuTFkDEXDwWRsTCNoULHkx8FnieEmDNobE/rJNCYYYUS8doyEYepGFzDwqq5DwFCBlx6mKJQXZKaIsKGOpVDDtJYG5JRZ7mTRQj5JIKn3GESi9hxegqSIAlkRE7rfieNFcFC4jfpFGlUFQGTaDR36Dn6yXuvRxk0RIMI/rl+zWYV4TmX5tLaPQGSFSOVFKT+wh+hVznhHZ9E9PwhX+mEbmxDwiX8FRcVkLC+hKSpJHsdhzFhDCPBKVw47OeI3kin5klFc5TlUBQGTLbkw7YS2Tm6fRBwnYdo0bd29yieJJ38NJ/CfKmABmjrTpyziuNiHymgSyriI10nRMqMJ5SMXqgtSEgCMIKiSPSezCIYXDMjqoYiL9hqYgD7y4m03wg0h4NgTkUP4WeFLw100E0CeMOQleZ5kGIWNSIitbKHjAEE20bw4jyZPUoItAs/RqhcOzKhc/TVKM/sez+lzkMaTsKPY3z9wCxyQtYsTYv4gj7PIxN0S4Rj0lK2kqsSJzh2a4OHO2sGrL879s7AQuoHgauzgNlhoVZA+oj3FCLE8H2ocUO6tUfmeGkTVQiVxxEXDDlhe7KjZJGZ3oYOvZIKeUIdwhFwp4gUMt5fiM5unV2DIi8HnWvmQJcdcHQcHpG2youfsPd0Kt9CD8p4dkKPE7YZPVEJbHlC92lYN1x3r0iSxs9wQnUQp7CdebtXQjmW2TGdmZ2dF9u4fJ8J0ejSHvcCT0OUp85ddt3HmQHc5qoU7aCUO6QSK9mZzGQmXWghg6Kj3COkGIHdVoil/nQMQ4V67KmoB6HQH0wDfXIPjDEwtAYky+GbFWQ1dIdSYB6PQPQ648HtgjbBogMJUxTig8c/BdZe31sQl4RNRbAwRtQxunVsD0OkgdASLCVYRBiAJJqUiGR1wmtoPhuoDboiESLIGpNKGhFDkot+dC1Fdh0PI4xckeg/myARJCa9wpaNxoI/6+jyG8jNAnS4hlOHKm4VQXhhwHa2PThkcxTi5YuJ5iybRPsgLzkXOkQOkEQ5Z3VrZLsGl4KdlnvsHgnUcn7AkHXDJzNnJpWWjktil1Z5yxwQwJQegE5+kEAUtK7pmkBgoolF+ehgnhEkC02ubd0ij23CNMsoyqnxVHm/QcH4WnQxlqPKBzSYJmQTh0ygmRjJzAODktKWRDpoLWlMnpAURLNz2QFEcXErnwSKexcydAPSDhGdgrIiVXm90mtuVUtTzj3UOhpbObcwI5K0sOZeYKCJy10lcjy/Z8GEIUO4wmnjTZ8jEQIGEtDsDGXuBIQMFBrGUECXBDh/R0JKBhCIVxbIBGCxIcbERhjcUKOYrwMxOQrxURHM5ClFAmJ1wif8aEQhOzkriTjZlnNMWkJNqIlWYqlD2OiFKyWZkRVUv8bSjS9KwZMu9qyvRZCcU+IiFRZRVNdI8qFYIEVsR6ISI/ES4SpKxCmpZRyX2ySA5tErBBNcDZpL3GMvsZpq8ikjnYv5RQYgjmxovAmqKUajkRAIvoe6/kMLNMUbXCgJa+z+QTmJaNRTBiwgiWWiI2FnKGELYMHoJ4MoiUUalH8QKs5wRfPW5iXFcEtvfgOztSEsLxtHW2xyIlV5+kraO2tal5363bYwatukS2lrXUmqt2bz55t7vulNdA40tnDSezNzTYjxSbR2iNp6W2czToIK9kbk23oEEewdz6zI/tzYOitX6AO127L1Cdjb20trgtubcg62K4KhVFeCEjw0ZtA0xcDF4/03uJTmiag6C3EqKRE4K+bgG5uaUxbdO7gOBhfDRvQbFDyDvijumZTE5bIcHcNCDzGeN1onbCN9zGVK/jLbGm84ni27qfecfIzGyM9qk5+qtTFCPHvU0c7DOyRxqc5hu79cH4Nqavt2EzF701ITPBI79r6JF8f9BeBzulK3sa4JxtzPaKAMfjTUpsaG9oTsHL5KzabiNAoZIOMzE7/16Yg2in65qPGWtVaEdyqgHX+gpYheg0Qry5blXrRVqWsXpc5WaLLhWkKwxBMrTLsR/T9EUfV3m/CyuGzZcIgxFCuWZcvtlor9XKRpHy6oLd7X4luIAgGkCoYqYuBDUHTEIc+AuR0KOXQHDzhxwtIGaKe5dBecoG1GyewE7DTAq9CmwD4L7cu1pG7ugWw6Cwo9/YqVKDHeYrmkeF2MjnGcr92qbq/MXe3HLeuJ2hCNke9s6pv25apTEI9ginYvOg9FXXR7R9hrbYEIFQMcddipRbBvW7+FRWQdVqj7chPYTZwmRdnQB1AEWgIqhC7yiwZl3vOfeqkOinmTLKNLWwTV110GcZZt4vIVwfW3oOEIlfv0HTt3SguwoTOVWsjwMVFQW7AmtQTme5e5QnOGJZLRDyvKo5f1nxO2dBeYcTt6gLFZVw0a6zv0zy6HG8xuZX1XXdFpZVVVh2zvpLmToe7zsBERUREOgnssUjTwxE3M6krnWdEGh69ijLlBk89E93QjPWEVIiv+QRLPdD2bp2eSHvPf0KsR8dw7PgfP/dSMDzHzRoSEY18rx69suCm854SXNpJC2FVLdgCt9JFch2irCM0k7WQ4uhzQ5LZZG/4PaBDrEFSw1m0DGGsmuOzXPLpxjcxdetPk9hDCuv+/yaKnxXClW1+D/R7WRYlCj/pfsXj+kIGJK/ooJxkXKJtQCMroJAfRuAp2NJIpAMBhORn/ucDcq/m7K2H/l2AAa/qLrgvgYoC2K/rVG+pgdhNuBQZRN2LPJjKgboEUhutATfnAW/gDiYj+HLFCiwb/iAbVAdO3OfuEPkEXJ/mZEcluPQcODbhinbuyn1t4g7LhJRFTFROei6mrCogJHoNNvKmat1hau3qoXihIHoXwIkIPirLQC+JodYYYaVi3kbL1l4uLFQroZobGiKvYbwD4doZPrNiYeHg7uYXYQIAEf0L4bYcYv4VYQYc3oknOLPsoPPovmGukNrlfG3MlHMDjpBrwCxooEpEoIUX2MUYYJ2IpCdJRAApUaeGZNhINLoRXJUWTiIT9rkOZBhEUh0R2LWjhHsDsDHgMX6C2DsEpMOIOGJAMTMSONpJJIlMEjPNkcpFhLnLxNQPFEUbsIGHkmWKZBNNhMvEKNJLpIGHZN+KtLsucWFmZM1PUUeOsaXsZG5AOOzvsA2HLHQBWi7LkIupRBDr8ZzMAo8bkJSvQB2qCSWo2EpHBCeAZukFeCoLwGIIxEhIisNJUVeAdDoDxJptUgVOsdaDBohFMougMRkJMSZExCeDLPdEKJuH0R+IhEiVxEybsJYTSuydaLckKDgl3JidnBTnsanG0tdExF2FhKukKHMMAklIhHZp7uMVgSxJifRMIKKuMaIEIFKUhGZAKV+ASp1ACGDNIdST+CWiZAyChO6AMXfGVAiWeDsOKUUuvNtOgqblyUjOIQaS2B6PoAMS2MEu3IhFRIoGZCGSWrCpiaMe8QMQJCeFCCKR5NuAoXqKEW3uEZ4ZlmwsIOATkP4bcSEt7mCKiS2ItGzIMFWcIMkUoe4VauNsoItEWQCKKWJHlgWS2EGLVFEaWQ2dmfbioXmeaHVqWZrCxIWYynESifcHWVcL8VCIOcEQqq4QXpVh3tSP2eeL2dbGPDORSsUf6JOdTMxPqV2WucYbmDPv7ItmklkfoEhCXJEi2MXNaXmqDD0HYuboMtFBDs+XDjUX+ePNCWWsqCAgdH+WxJuDsBBcpDuDdr3HBTqghZuKeWXBbsElRLss+QnIichSqJLHLAhfJGdH+dsjFAhWdpuG2P8mtCJDRbkW2JkCvGeDRScQfPASeFRI+vnKSK9L9uFLVMGSJHSkZmLroCWrLl+czArKNL9txAbuhZ7kUkpa9FRJBs+bpIOLHrdpSthAhdnPdlJX3EtO+uJUOiZaxcxIdBNAmmKvROcKOP8rpCzohO6KKnuHwOOG3HBGWppm3LCITiWhJvwOGtxieLCMnBtmGf6GWk2I3AJLZWFNnMvD0WZCJGZddjEolczCgjCL9g/pRPlZuN/PXBaDrq+NlczHhPRD5Y9NlRlbhF5tBeLiWmeKRbVYOJzCDuLuIXLHhU2NSvzspCOLCPlalKpGLtYWlOcI+ppkoNhGNfoJdI5XZbIjdnwEMDEItYMNFQxANfFWQJmUqsoR4UYiJOEmXr3rkv3nllFC7rHlIuzDEooEOWHjmaOVdYhP5bdfRPda7nlq6rXipHHhpB6J7p9fnqYbmb9U9f3vXlEWDB9XORGvZD3uLv6CPjDa3iOZdQNrVfRCPhDUKtDejb3JjaUennVrjdeX6reakfeXPo+cHGutNbvq/qNMARGpzSDloTzQDhGucHkYLZmmpvsF5qIf8hRihR6Hwa/nVLzRbiOPBHvgFA+mpgEQRDMtJd2nMWujrSWq/pVCQUbWrWvgMJnvGmpsIGvPwcxIumjmulLYdDUq9opmJSLcOI7ULdpavB1ALa9pmuGroFxH6GflTRLTchpL7Xfv7WWjxcZFNFoWDEnXQMhC/mnVRkRrHfzRrSoFrWuvYoQQMNQBgSXUdOQdbXZiFtuH3OEMHcxClS8Q3fNXvNzV7WsQ3YFL1dzTHQ3TJdIQPbzeHaKpRFEAMKIGwbHfbYrefq3RRidpbRrdrE5rHWJKIFbeHcOunBnfPSVOXYWTVQ3RHZPUQQbcvU9iPWgV5ogb3UianTtvffJpkGJJ3eXVfRnQXUQSfT3f8tXY7WbenGdY2YXpHjua9nBIEekHlleFOrA9zJ7FPsORdc2VAwIDA/0NYR7A1uNgg/HUSrg3jW4RA9uYzA3R8o4cXHltA3kUSiesgyAB1qg19QTRg5Q4Qww/obpPA+jDw0w3jfNizekWzZiGA2g02UXieAIFMdgD+EhNnuWRLBnlcIo3wwzaHrDWET9UTdnCMeozBMo/g9SAY+ZOo0PCY6wyEew+gzI9JfI4o4lBSpuIY0wIo+EKQ5uWYWObI69Oo73Joyo4zBcBYx40E3gzY+ueddI5A6E440Y0o644kx41Y943DXo2aOY048Y8eWKrk8k1oxuZk4TdkwU4E0UyE90BUxE7Q8U7E+QxEd0Mnk4/8iY2NmY605U14w0+A1uc0+kJpm0/U9U0M3I5Uy4301I00349ySM1M2M2JO4wo+0xk7o2UwSPM5U8E6Y6E9s3U4sywzNjE/0741dcs+E6s6M3szUxM3U7s9EzeTo99Zs2Y8M5Mx0wzC0ys849Yyc88/jfY/E6EGE4U0c50wk783k9M3Y3ExQ6C6k9cxC984FPc8i/80YYzS88wE0AQMaPKDI3wFc43HHcCGMPaIuEGMc1i9o4BMzSkhkSuEvv5IGL+HuFLBhMLbSBCuQfQjMd8ROrnPy1y9pcnssleAK2K3HcZsNMpJWtK2EL5s0k+EnHOv5GlGmiq8xMEhEh+pq9OiKyrYedOnK1Kx+my4eByxAsaxNDJnK6GT2ha23Fa2a6K+Zry1CkVrXLgpI3C7M79aDEGPQpRN2RTP0LsKG7Czi8CwixOnSCxIaY9USCG8wtG/S37Iy+I/WBdoFP6H5CdhHR2tLl5sNC1lQ9lWDCHLoI3LVHkQ3QcsArTnwIKtJCZJkIlHAhdmxPBOFGRL5ScoGCHMbhCuGTtToLpKCtYVvMdPeDsIlGQErmDP3GyRtjTsFKrN+HXAWwYeZPBVrpdqUtpFuNUTEKTilCjG5LIb8msXTj+AE2RFuARARCIH6zG/C4M9Lm6ZjNECetrBNNXoMu7AeV2M7Mw089i0Cx+2OWCmDijCBwrIdEnlHGB7+/fjDOs685w8Yu7qzDkO+KNHbHOaHAPBrDkD/hh+m2QwMzB9O4R/an++7CKrhzLNEAR5jMI3eVm8Gsy1kUKAYH0O2NlDaQMUXVMWOBkF/AMfiW3IAniTjoOI0XdlCMnJG6KqcUp8NCpEJYDUtLsUpzQhNKOJG/ibjuOT0GEAnJ+FicNF5OOUwjKY+OgrXNJEp2IBcTdqiW9C8cUVGTCAfFeG3LFOOZZ7Ik5/sIdUp5ARhAF4MApzCfeIcmxE50OB6HccSyWqtGLgKoFJQEp0VEDk52ZOrcFMUTsFGvXFeBkOlQlzWhNMhE55LF0hFeZ9QFVAwKS+kOnCzoKe9olCl3Yj16eCJMEmWKSxJjHI6dvbfp16xs/OsZq2QR106FCA6escu3MB+KS7sWt78TJ+AaS4YFxHOnt7pAgh1zBCjCFnt1hKt8t3QMON9gMRoerPd5QMEjCdCFhEd/d2FjJUmUXYpP6JdoR594MIZAcMD9xJjPMSiWDPdxIC5ncbsPGjCFt9gmFOdOsdF4OL9ypHXGD4txqcD2lGeO9KMAQiljMzRxcxtj+NWQo5BeMvWXOUKJjAz7XBTEeVRz4/Dfo3T2ecD+8YtMeU+CNEWaS2xKuSg7Y++wG/o2LzOR41LPFCL+jTBNpxL7Rp2bOTL6c9T+c/o7sOz8IB481i5VeWM/2ToErwo9r9z3r4CyI9x8trx+zcOJnfepIKS3XCWsEt5LwNhFtzAi+PQAZKpLCPd1kIwh2h7xhJMZDzBFAqNAZLKab6SzCFVPdB74dIj2BON6NO/OpOEPGsHyqCTRDh76Xo3KqON+eAdOpEOBNIwHXwJAROpKIAdIpYdxYwnB38NGlFEBn23POx3zb0JRn8lQDh7wSuOEP0n0FLH9KV2GvL9+zi7R7zsAWsD71Qyo39ZHrjv7ZxhEJFrM8vP+8uVZX7EO0g+6S0CTJA9Hwb+e2KXiOBQG+1B/L2aPNF0abwozAmkhq8xmXEDSFhH3J28/i0vGli4UaY08iaoAsQLbzBhQDgBtzEKOPwl4DhwBlvGAbnjgGG8fEAqIAf/xQGYDde6A+aOQI8bYCHeeAthnL3gE/8nQ9lUgYAOgGQtjExAwXthk14UCIOdLZ3kGld6hp2aiadhLfkPjVIq4E6MSDnGtYTs0oNmIpFpwPicQikMIQdK9HeodwOI6cR9BEFbhCBAE6EPiCpEHS1wLOagwxr8gsHP5SiXCMKA9gnTaDbUHcS6OVT4y7EkEKcLePOkHRNsIUHcWuMd0ApZwMgJ4ZOKDHVgZwoMa8TcMENGjuge6UUCQWXFBjSCWuEafqBIBuxDwNUbdFCjEFbgdwcEyBNTCPyaQpwzIYMEnK7UgLDgO4K/D0AAwgRtIxcAYSaOFDUwzQdOkFD0FkDUzGRUo4nKGLwDKhDCZyo4UGNFA8qvxaoQUPcJfCwI6AehF+JFJHEUzFoeh9AEaEsL2DwQwg2tSWC5n2GylKCdteyibUjjnw34FQlQJYX2FCop0hmMqEVU2H/xnBT6DXCOH2EcQHIn/ajoQPGwW5W4DDRxBNQ6h0NmYtUTVFIhgjUo24mHDhkXgjQxIaUqiSGu6jyxd4B+kiIQFHDtTIjY2gzM+rCMiTwinE/iPLNCF4oqJqhGqDIMSOg6/VcR7qOhFFF5RUQaRSMO1HQkgqMiB8jvSDkIIfI8dRBa2JVgdUjpT0g2nVY8FV2HAX1RkmESDN+AECGBYCVyJRHFh6ApQbeQKGCKqMqK0BIE2dOUYK1Wj6jzRFMeUV11aTb1lkFolrnqM9K/Cck68AHK6LJDuijRdovUXqSRTHJAUXomBLGXAI5JxK7aE0Q6PVpPJ04ESQEbzyyYVk46DDctHLFF5pjOE2GegMyO/4EhpSoqe1AyBhDHlcR6Y0sVEwBaQcgRfPbJhWM4QZpRsqLD3sVybGRpnC+As5vWPJgskcxTiFsT7nmj9jogJeasbSxKbOBUwTQN5qEyppiQSx7yPBpwP7BRF0xgSIjsKLpZGxuQReE8u8QKxSJcxjFcluKAdDkAtYUCVGI7h9BbBPQDWO8QAK1D0DZeQLPcSC3HKUR/K2WEvMVDwYUsJQjoK8RsiqxPjNgpLZ0N7ifEPiBBU4zEB+LjZtj1ODXHIH+KgRnjKWwEz5KBNvFyg3Q9CKCY+Pwm+gWkREuCedUQmDNkJIKVGJHEbini7QQEy8ThNQl4STgnASCWqBInkBCJ6bUUazXFGrYc25aNapEg9GGBI6g6XYXtSuQHQ74eFAcFxG/ibIsSq0U+snlrgMTVJ9PUnuxhRJBinQnYSiHmkyQ04vk7yZsBlRtQHQvKTyYdKZMdRfwrkdcTyCkJbrnBHkOScYWhQnTVFPk2o/jPRD4wHRwBVyFoSP3YwU5a44UkaOYKFZiTZRhpQMJRAwxyMj4yyGBOFGyJ20CSOgVSd+CR6XD4oLWbyS2FOJJjSm2HWOn7zhHEpOYBJL3OgJ2ALQ+RRKUwZLn4E1idxyYucZlmN5Rk9CHUhlDiKRjsicg0IMjLqh55VTURRQg1PyiHROpRpFEQlC1NGg6ompFEnsSmMwa1SKR9Uv0GSjyzzSMRDqC9MKn4lcdhBC+N3pKL+rjgMY/bJ2ppQyqbjDADXBugSRiAUZIKPQbro+ywb3ttKNMegFsRYIQoJAgVdBFxFvwNtpIHkaGZzDmBpRAZHoF9vtSFT5tAZ68M9tdT2Cy4EhwCP4tQFc74ykM5RZ6ZAmjjQztSlUZ6QnH+K0yCCIOYmeIVFQZUxUJcDUsTICI/ToZeSJ6cTOMivTIqC0bQTu3ig/TAKtGLrjFIbqrdw40M0UiMMBkBETctMxhAhBOw042ItMu1gVjPrvZgE0MiQN+CmhD0S4YfcmeTis5D0XWRw8mVxCW4N14+9AbStxgJTw9e6Sg0QNDN0rSRjomQdGZZHxmG5WCgMj7l2AoyaZA+2EQGQ1MGH4yro60BOa5GFrYZOYLEHdp8kenQza23s6Skeh7rDNWM7bOqvsBT5hzE4csNWRkBxz+zNRKgQGSgV2GNyOo5c4yGJ39n08zwaMhaiSWVLDAg5hpUnhtVPKMznp9AP0PJmwwz0p5eUAKrVXiq0EvpKxOoYCQTh3Q0ZXYFSj1TjR9sG2NQvvj1QGH5SG2WQM+PlUhTu0W57OHeLVTOyo1AZrGcyAmjURiR624dLrooHznaxo0Z9brsF2VITdy5yk/KJVI2bVS+SqeNDm3HppW8G0sxF6rxCMiJ4ZpUC1EVFGLzg18O9wPSGjSt7v1Uay0cjohWBoYKsOqI4hT3jRFQ5g86NJBUjTRiBhU8+YpgeNmwWk08F9XCfFb3pJQ0CIcCrzFnmSLAgAAQjmEYHAj3mdAIZhqUZ7K5DoxWJZqDFcGkLgeEmOYF2N1AEDexEsN6hAJQEMkE4Q4gkPLHTiowUBx8LpCHj0W7TGYVo/QRfg8aaY7oW49AU4p0AuLIBKOTVnYp2l9TFo5lMpGkw0haTCFlArvBIFTmKKfSNhbcfYqCXqiOomxVxa2mAQ/hXGeJJUCbUgHzw1aASg3voscWBIjIOURRTNGUXHkh4jcCLmEp3CR0il/rDhQYrkUvgrFMEJaOcGyVyLT8yvWzGRjLKviFUSS6qXqJUjR8NFToN6LCGPLEQpkfcxRRrklhdTaWYymRp0X2AYlFFICCeq41PDfhxwVigMNdm5FaMNln42RstRpBhLkZKUg5bRAKJBgSelBLsM0ukUlLugygYBKLjCV7KvM8yx6EaTTyHcjOnMD5V/1aWhMisDOI8SgKOlokgVIhaHOkp1S4JIVdYhxd8vB6uSPUwPZvuZC+Y+49RNxW5YosUAMkJxpWS5UhKKyZd4oAyyxGeAoGrj1RxaeKFTGB4x9hlAg2lYM0uZhlplJKP3GmyWZiohVYS4sa1ExW9Tqp1yktIlBeUBhrIkStldYX0DNEwlH9alZ1n5V+Mis1XaCpAJnozFelZ8Ntukv84ZBdVuiwJeMu17A4XlbirTsw1XFeLMlzq/FK6tlWzSrlJnNiLfmfHMrhwvS97uwiZVGdQ1Fy+1US2FCikwldUH8o8sbhLpllsy3ld1P1UXNiI/aVXMGsKT7AgVAkegJIjBUxVbVoYbNQgLeqn5VACKzblkrnLsq4V9auSDoCRUxril2KqUTas7T/KwZgK1nj8pBUDqV4W0rNbGs/HLNzgtUgZf1GGjEqwQtGDkhosu6KAjmfKqdXSrTHQk0VSg+KL0oeItZfFQOE8L6qNAmhp1pYNDHkuB4Yy4GTEi8dgGazIQkMLoHidgE2DQhqW3oT9eXCuAjL2QLSmRfsyJyKqrF9JBSqytbHG8WhkGjJddgvVULp1tSrvvit6RiQo2qiuqrAi5WQUwZQgZDSiP9VyMB4CGzmOwjFWUCSQZ0NtSSlyHgdJ13a5JZ0SjT4bBglG3gK4xHbhBs42qrjdRq3Usb5VCDZCN2nSXZy31vSzcLZMk1nZH1iS7ddRLg28VlVLGUSCuNg08pvwKiTRb2Ww1Ab7g1a7JsRFlzCqtZnYYtS5CuKKLVoAWYjSSLHJ6iRc6MsJbXCBV65FFHmrtSBq+X2ik4aaqHpPS00kriIPCbIMDxL6HQmN6y5Tc5vCTuh1Neyojaz0vg7gXYuywjZixpXxac1K+UQGEpnn+hqN7qsze/Aw17AZJOiqtXlv0ZSwvKqM58fRD3hAqpIWUJlbULSiOaqJzmm9Ulsq0vtpamE5iS+pfIrEbx3iJ8dZAXLcSOJswZ8t0iU0ia41mlPCEysLXDK2VZOLCCJCSYhrK1oyurT/yq6LiIYJqyiLLhk0fTkuJq/QFCN82fKe1EyqiIiSlXVagVMQUVDsqh4xd3QjmlkfoydDeL4evixuP4rS0MInV6SvxdnAB0FjZFzlAeJGorX5M46c64NVGsO3AantyS6JZksq2WbSt2m1KOxoE0ObHtUK0DaC2NyTsGGxik3MEh42PQ9IXKzOWgOE1+ae1/jFqJY2ZgCQbSKaruP1zs22SrNlOrFUEqtHHdgEHGlLUurMboReUucLLVrhy16rjtYIXoKvnIKQClAlaJqWysNUyJQVQVEvmsty0rbr1oUROKCpghbzsIn2s2ahNJYEkjS8O6FTTuFB0b0lFcXgr0u+ynDIBfuw9RLrlUyMquHEXBUL2y29Kd0YJMdRFw93U6pRh4edL7rBi75zVvAegukvQI3Fk9/miZbAWCSJr0gya4dSximWJqM1he7nZGweFCLIBe7KWczs8iI80V0GuvckslbuzHip6zqobtbFibeCGi7jJAXTjd7RNk6GLRxp5W9K+10eqlBYyn2bKHuZKUFfnBi5+hmd/YWCncu33Y7jNmutpe9y/zvbYUAe90DnvnXdL1ddqq3UhM6IHRHkweh/EKKiXxrIhee97lxFX2kbgE7oE5ZOhcwf7ttP6OOb7sbyGB/9SEw5f6CDWaKzlbq1sS20VWNDKlqqoTcxq51S6pYuQ5uRSsP1taaitc9NfQHeVh6/VT+hhByStUxdQ9IA7XHXTH06tt0R+rMrgflUVjZOaKwFPRBTXrzWD3EY+LAYFWOrVlMO8HXDsh1ttJDJq9xbFst1cG41YMS3JFvt2zCU1IhB8M+Ld0MLltKh6dXJGUmIGYEWOtHbOq1F6GDAm6nA7ju4PK4OYfOyFAlEeUxwVBdynGlXioOYKrlXeeapvsiAJQttqBydGtA40rK4IYhvrfihCr1qt92UULVrriOsc9DE4GIDEauoXB8oh0SLaDFaivQ29wyW7cDwiWbgsj9WgraXsgH9QWeSzGBHvT50+bDDDhiPcXANqSas8RR5tQKkSgxbJNurQzZzraPW7yi24fFZdyQ6V6SIr0O3eEbhyVHsmUMBnJMYWgxUYNJKxLYVtV1J7fDKGuNhMr6pLaoeWGzIzMd1w/bsEIWpY2CC305w116xiiDUvXRiBhVeynffsZI07r70b2QY1kGiO9HayuFL1cMPHA1ajtj+lTZEEOz4rhIekSg0wb7gv7Td+C5NB/pGNU7/N6o+Pbj12OpaQBgXAnDUf03KKITOOrE/Xqjh8a1jyVbKszpf17b51GRidXFqhMGqDCuuVE19g5kL7S43lfJbRNZPKHRjNBhFBHOD3QGwD2mreCgl91q1YkrRykz3pJTBIC0mOjYwvqj2Vb5dtxiWMwbTisGSKnK5nSgWMhwnwl6cAw0Zs4OinqJve2uJ2CtVwZxdhJtWODoQ1Sa7DbJow4cfsLWgll3KwTUCrdRpGd+ES7Az6btNzM/iNBJJnSfOMgCu8VGBqjYemNKnJdjhpaFpylV/bQjw4poknHU2HQjpwpjXeyeyMttocNNAlSoLLHNqW2zkclQSuINfGnNlZ5XJhAGUtafUDZnlF0g0Nv5aoSh8s76ftNyKHhqJgpaIGZ05Cm91iqHEyLbOA6TtDCRQHSGsVbtjIvS+7WxG5MeheTy5hHaE2G6G4EN4UHCu4aUASbm9OuV0zaZM3LqWM5p4VU/MVOeKJVTGClRPXfOYnMz7Rig5RFv1DKr9RpetV0smIYn7Dyp6faLT3V66fkooN0yJHzYIb2ErkPUzCpZ31c89jcFzkese75rjFB6kcw/rHN+NrCwwgU68ooNmLVGNurVXofzYEmHzJ+k8zpq8xeq4KI4fMwSCrN7mvVdRzNVGZgtr6tI3in/U3ID31zFIkA8/gnEwugtAu4JE9SgKlmKYCL2iyLdKUQscHHz+pv7JcVYP+dATSJs07oZQHoXGDrFis/oxXUvnE9yRxHdvS80x7KAelti6C1xGua7l2VIdUiabhdmiDSRxS/aMsLiVtVkCIFd/FahhLJYqq0K+qNUhKZIBScKWjJvXbqNuMyELqj1uNDjL+tomMJS+3CDDLAJz63CPxGyDsSNQX62ED+jm21Xv1Agaln+fD2kbK5I/dJUJeZ2dWKRCKuDCxbavUHqJ0SiRMAfiV0X2L2Z/KXErzOJXje2cDQg0oM3E7hxxvaEv1YI1q7ErRJ2tHzp2Bkmj1q0Jk5Up7Z5ijznuqURQfPi37ILs5wfsAgv2/noL/5q5YFxOtOMAw51qa0pbRN2TFFDJM1ZdZT05HKotBQG9OQwjM7pyAkdTVUszEg2i9xEG0lcYOG79PtxxvnSWfey7XgTQQvXdooutumfpRkSTRFz/1I3nt4Wj7nFZzjWWytrSAaPkf53bILdo56M/lr4h1I4rAu0VJ9vM3aqxdkZkU++Pyv7j+t+gII0NpUUgByrkoSq0MnnzfBptkY0hcRPm1nBFtgG4a34b9MwiUIwqzVQYGiv0EpodmqK1TdY06t50LN3U82u1zPJm0+J+/bVtsumbUjgZhEbvKH1haW6pa2kyydCtWiXWjFty3sZw2TKxIjxkNezbIuc2a1/Os7mzrI0Pa3TJwj3M6cHD3ndbBx6E3vv412bgzDt6k/vsUXFbIlud74wKvuNvHE9nxho3ZRMl86PjHl92ykZXJDIGlyBtrZOxrMqrQOItjm6JdQ2DBT8em/IcYM2N3HwenYCpZovKqI2Mz7VmgyhbUPdW9yvV5yv1fZ31GbL5F7I151Qv0HZiKB4cXiRkvGW2ccdt2wfYQH7F0qFl9tVaelPrXDLEluS/EAUtW35VDerXKidRouUYbUyJ6xdrrYLWLyuwmHUdCbVIn29LunS36EruvWV7Aq/0/EaK0RnrN2yzB07CHvx2R7q97OGXfDN4OGTo3GKeXawc/2JbUA1FfkoTyT7K9a8eDDDthk9KaHpG1NduGFXUBL9QJs/f3qh4AJ8Ht9mNr1vy2r5bOxVjmb2eYDy3OAitkKh+s1vNWdjGt2qwBs4dwGkIKZ+MyvBTVtxRAg58DIse0fjmJ4FcQY0g7WvmKHuXEV/WpZgekWxHhDtB60kSgu6NeX+V+ykZiAJi+dSa7reY4osajhADEQG/TbPt8Wv9fO3ioLpCcXMVjNJYsyI5eO4VS2t+78a7chN32f+3JZyrZvLXPGS7nVop9tf2WJP77AUFiHiesXGOkRJdte/Tqg2OnhLott6zuotVa9hQFxJy1haUD6BxItR3tnvarvtnE74T+K3nuURFI+Tcx88xxChB42Jo8kII0Dehsl3WIe7Xm1PcSsttRonhild4YV3sXw5/dyNFSogfwPPTcEErbOcb2omVBUIMswQ86fuPXLoMX2x3fo0ykgywdv6TJU6W2Z7rrPb61VeeuiPcnCd/J3Vj2SVaNC9kNvaNDyPubCjUFkS+89Cclqbz1iu87Y/osZPqL9Jfg+0+HtYuknHFjeHobv3pOvMyjmlyBaqc/9rC6iIi0PFou/W0WbpZsEVs5X1nl7I1jkzMSxzN6Dz75o3czC1Lwq5IsznJxSYpd2XIgFOOXbHsh3K0FFpJuWOSeP3t3FdtmQOUbZ+svGLSMSXZ8DcFd63xzEdPUlIdEzRO2l0Gc7WpfRkeLxnK5me1RCKjxmNpTOsFzyiBd3X0oALkosgUhcvHtBW/cN8y5SMsR2X+sEK5XtDOBnEjDymN2YylikHgDAJ6yKbd3ngWXyUa65yUY43lHfHBlwA0VW81OwKj6b9i3UrkHzqDc1p9VbE7L33oXH0Ltx7EfgxFXlllEVZcWuLxr4KVfl15648VernZNVnPXQpoddYXfjZBlAdefAd1u/rK/dyukqBdqrh9a5wZ1u63M32u3k7ux4HpHcoCQ987r3TkKX2GlLcbdvJ7G+HeVaVAokb5xm4vLxv7lDd/ezC5ickQw7ZSph0mdaSgcWb8V0DhA6UEksKmOxXfWQTqdOVsonbhV6g45M57vwWV/nVzi5fqjAyPTkxSoDxt5924viiff07+siv9z98/ZxqKLNSqzj776azuFkvBaPpwd5QHUkQO73eLH71zSSZ4942oQCzn/S0V48DOo9Kd1Z1nrXf2j2toOw7l1qvcBa81g56QtIQ4/Aq/lGpllb0o5WMqdPEK2TxcCMfqmBrbsU517q4og45Lm4FobR5hDpULT8EPh0x68tE4zzMzy87J5c02b1N9ml8CGYDMJHONFOy13nY5OLqBjCF3BAS6wsER3s6StKy2DytXr9bOuoSoooxmbP5H54hW+NvfVgT/1+cQDX+s1szaltv77tx2fAypRIjA70y5/su3DpfLb7zTyWubPe36Tle8Y9HqmMofdXj72RSfkUy5nRS094b6uscu0e31B4Ld+vDmcl3oPTjWD8E/C/V3YjMVnY1Fs5dtakBuunfvy4fd/u2lfEWJWQJG47AF9Z2lO9mZeuYu0P2RwPBhHjm2f+o5bsDcjN+Q/7DnOr209V+qdSyik9G9S+epLurdkq+6zlQN/+8nu2lUIbdFxaFML7F1kffJZ7iI8+epY9PMtfnDrMUe5PBBkL9++O8A/YXX30o3JFASImaNMJuMW/otJ/fKJ4t63Rl+FVDa5ET6/L6Yt5dFfNbWj6CZ+oq/q33Xx5v64vsJ3yTxP4v9yre4BXjvj3j3xO2eEsVbuyMq0XpRu9Utipt2yDh70K4ub2XRvNhjdZZ/GYjfWP+cH81C9Q8G+lXOCUj79vG9o6LfvDoLjOeM8CpAQwzsgaM+l9SizvSUWo5d7JdvOlfHtmoQTnrsE+9RndiJ0gZ/DnL1vEzllwxZV2u7mLZv2RlvzvX+nWtxng05D4QvsHmdQP17044sg2/BvJ3sDXOaecpl0Fbpyx3esQf3Osf9wLj56a1xZAgVzN7j3C7GcoO7fsLxk4UwO1b2GnKOkp8n4kdVGddVsLLxzLCAjaKr42lcio6av1X1HZXpqz+uM9VmBznW7O8p/8a/Kq3PfFF6H4nfh+7HCXpBKla/gpegTyV82ygOS8w/9LpSqvdHan/RqcNr0HYDWNY7EN1NcFPSewtdPFQJHHsONDZyPdbfK1xjM+rbSxSgLPZnVM8vVOYFQD9/Kl0EtN7Ps2/FeDYPw50h/BAI7MAmdKilUbjPsw2ktSOmy5gP/Ty3tEWHCGzUt2HM3z1F2ca/UGMDdPG1JsnTPXQps3PAPw9MU7IpG04MXDpxv8P3cK1f8k+It0r0zoHYm7MPkBX3gCIvJ71LtC7Fs0TckTAu0DsfbCBzKdktMGR/dwDVKG2UgjVu2Ds3FeCE+cbbJCw/N8UWwILdx/Av3B4oyO2zVckTCRF/IdPP/yq84fLCydcU7HXG0EZNPvVRMOMUpE09XLaUm78PvHFT78u/L7AkDyXKQM+9U1bbzkhM8Ft200k0LAjz0jlESDxtB+b7itVr7B53/turP33a8QEMtXMNUdf1yMw67PwNJ9Ag9zwICenHq3wCDkAj1JcSgy+0ks+IMNXcoEHS03wt2/HOELI+XXYhj9aAStGnIvDeFwgdrPZzzs9ibWnzThLcPPRyDUgghwElSwbNigh4oAwhkwbhF72RJwQZ5Esxzg0WhdEdsayEaEIRNtix4rgx4JMEYRLenYgTg/SiHgXgrgB+JmsftHbhngr4MqIgQgLDOELgoomvwXwC+BQ5KiKKAPh0tMELXEx0EEJRDbaNcUVJDWR+EXZGwKkSYxHEDdTwI1xGokNYb+f4MRDBgYEPrhKQ1EIMYcQvlibAzlNYgMY5cBkFWIfOG1GRDAMNjGxDrMfYShwsQgxnRD64bjFZDqQ8kL5YJQxPzZCIQokPpIfWXgEBCHudllBCRQhkFYw18FOE3Avg1UN1wrWSOAHhKiJai1ZI4aPhFDuMfTFHBMQ14IKY/hVaFNCjUc0Lkgu4R2WWY8oXcEjhJYBSFK5hIbqFsh6JaEOdDwoc0LtIpaM4gdDiQyUPGZTsVzA1D7QsJwpCtYBkK1D1QlkLlCfiIeHeC9wekJFCcws4LtCAQgeHkoiwypD9DKibBBtCnhd0LuJ0IbUIfgEQ0sOlCbsYsJjEa+RUOCN40H4lLBaQvMNTCRQvsMhCLQ/4N7CqtEcL+CGQ0sEDDJ4VXhjFqAScNaRADO4lNFcwrhCdCwrfsN1CqQrcKXC9qQ8A3AnwdcKnCsQi4FOIbg/MMvRglC8KeDTwrHnVFjbJ4KvCSwhVQExEwuaDQN3w+8NfCa2XkMjJjweAm3DpSVaDvASUd8JfDAIuLm/DPgs8P+Qawj8I3BDBWcI7g9Qs8MURGw1CN3DCQSIUNZAkTcMJBFwokKZ47g48AHB1w9sJpBU4csOFCHw5PG3DOQysKojcI5kMjQ5QsiJYwMwwcLoiQXb0MgiBoR6AHCSQmqjYhLsCCPMoGCASNvDAEWjGwiUKCiO7CHw3uDhIEwkiNwwBI1XA3C1iQkBCpvQn8LFlUcIkPgIfpLHnyxtw1phpwE0DlxHCgmb8DLR7CcyJGJLI+yKwYRw4yMsifiVEkFCDKEyO+CvI80N7h89S4OUs/ydaFxI6qd8MMFfI8KOTJDWKKOcj5yX5CNDV4b8E8jLnQ1n2RbUNkMIZ1Q9yJFCBUbyLyjXgorEwjbsK+FJD+yWyQTD/kQEDbptdd8NfhvuD0JKj1Qi3Gij5yWKL5YUKN+BqpfiW7mSjWbUkNBJvI5SPwgVQ8zlKiio74J2pSoiyJ7CJo3KPPQsozTnXCpo6Tm4pHoBKPlIEIzKKGjuSdcP+Qxos4i/Btwqmk25Lgk6JsiqtIcFK59os4JPQmo5HjAEGoh7l9JBOeKJFCW2dcI+jXgmaNajXon4j/g3IpaKGj4CQqM2iRQk7FWiIY14KfY4QtXB6i2QlghGinEG6MqJkYgKNyQAEIojhjvQlKKZIoYs4J+jvgsGPNDiY9GL7grosKPL06se6Kxj1Ik4JK0bg0aOv0ziDGMNY2ojyIJCkonUPJiCQutiMiYY74MpC/yUmUkjlmJmKeC1ouMO8jiY7MPxR4YnyK2ioYFCIJF3seaKtErob0LmjXg0GFljmYOyNLCtYoMKzhHoqsNciiQhtENjNY1WJ1jvglWMVjTYlmEsoQ7W2N0cto3NRHC5YlsIQi7Y82Kqjfhd8lrZxY4xkWjyo3qJgRwY7KiHDBkYGOjjrwjgO3DpYxOK9inI+aPVExAd8OliM4hCMCjzotUQQYDo16IVEpXR2PdizwgVFmjy4h8JGw6Y6mJTiiQh6OdiNwFgkmihYqCMljAEXaJEiLQU6NRjJI2RkMiQQu2I3AqaJOPbiBIgdwyiQYkSL+A/yFCHHQ3GAaAGjO+dSKXjSo5SNqjgoNxkE4m4/kNEip4rqOLiBIj5Etjq4uaAtwq49WKUiCZTGOpjRInizxiSUZ2OPBAovuLFiXiB+L7jV45EgfjVYw6NZjX42+Onjw47SMvjFo+OIviLYkEKmikI2OLPjr4uaAzRzQ7OKGgr43yNP5j4EBMRj1IbOAgT2omflVjUEiEDKicEyzg79/o4OM/imwDePpjf4gUFCjDYwhLLjEE3BPQSEonPhRj74uSH/i6EtUR4TFYlmMXiaE/6Prl04zj3wSOEiRKNDvYyWAoSZEtOIfDTRX2INjs+aRN5i4uF+KZh3o1ROPB1E5OD9imYZeI0SRFQ8PctIaFhPajEYR+KDDYE8xJsT6KZ+P5DrEvuOpjEYAONHA+Y1WE6ibsLxP2JXEw2NVh1JZmL4Tz2YJKlihYqcASAagEAE+hpARAACB8wGsAIAUAIgAzAQAbMBCAmaTNhulMid3hW4pfEUnu0G+JmGUQ67Tyg1QE4DcGrC6lA0hTJ3LPRIOFS4NkmX50oXZDkT7EFAjIgg2OpTAREYAiH11joS+GvgPscxNqdnZbpJfJVeUFGIFtYEyFBlVnOOAKjQybSChhk0Gqn6S/cJ02fIzIPeFpxDVC/COJAkFUjVF0iIEkmSaTTmEaSwqU3EmS64VaBNkmYBBBSk7kvghkE5E6viVJsEaOU/jc1bIiOSIEE4kr5iIIHDhlalZCEf45Eyt1+EdKeaiUSw4YvEmTADUSkaTYRZSUmTMYMkEaT7aAgkmScaQqUaT40bWExITkaPkaS/KRVUxI+qE+URhkIEBAQgvUGJBmTJ0H5CpTRaM5HMSBkpHHywRkv8E/9QWKWDogllX4g5hFYZtU3FYKD1HwoCCADlk9HYdWEy1nyN2B1hm1QVLw4RUxDgYC9XUJg5dXoGzz+ohgRr1XF+ky2FRgeiW2C1ShvFWAthgONGHAEOOYjm5IFU+1FMEmOYzwlTiHTWHURLEVxl1Sk4aIFsR7tOAOr8M2QNDFERBYSSggS0WM01RI4V7Vag44WjD5xfgonATxQUTOR4cPg4yEsFi2AUDBMmhWTlDlo0oAwClqhbenilo02LAShN4DjBJJo0gnm1J9hd2Uxwr8CBBEUxcR+Ezjc0g2MD4O09GENwN2HaDyMy4eknvpoyZfAJ5tOfYQCIEEVtKMdt0IUJoRAktRBuJ/wttFEAKkJVlusgESuA3Zk8AXXUpNhfQGcoj8e/HvpJ4W6EL5W0rqkc8/hNEjW544SekkEaYEyVOp+Us7HBQJUKJFTULSPVD8QMRLhGb4/XZPw9c8ULoT7YpEBsPpQJvblFMR6RCxA0RQrYtKjQJAOhHsJZdBIOXxwM0hRThFMNUyQzFEBzk4QEMqxGI5wkOEUcQf0+70kDh/B2DYQFpQDIQN/fZDMwg6JdDL4pIVA4KjgjgipBmJ+0VSTS5WCVtIHdOVVSRUhqfCpFhFRaVSRpJa8YTPpdSpa4x7ZhM0QDjdVJLCDVNT03fBrpgdCelBQbYMqCSlX0Q3DnSeTKFCt8ykCHGjTS1P0Asl5qV5GwxG0UqS2R1JdNLqw9KHSQ4g9ZD9PB0ebFZFG4K0vTDa5VJIVNuRrM2LA3gWkYQhcxW0w3FoQWkYKkKRW0qMhNoyJdtG0B/wbVOMR9UDERyQLzEaTIyCZL9Iml8UTaSQz1UYrPWlpUaaTGYSOe7VIUHUaWkOwkM0GnGl6pb1CEDfEd+Fyy6UTqQIymqfgMay8jeCGQ5s4ZxH6BSwG4mA8Ag9IOMQs4EilB0WkWUjsQdCN1EJR2sxqU4zrpcNNukJRESUCRnYM9GVDT6A7NAxv5Wen7J7NKtHOz8gcNAgtdwLQmHNeae7OyRAoyxGchw0U7O/x9YCRDelQvb7KezhaeYMOy78QHO0pgc0DBPQ/QJtE+z/srNB1Zi6S7JBzq0a82jERuOHJbQWSIHOUgW0BOF8wXI1yFAwBwO4ITQYEWdCnoM0A/BciJtCMVQxfshClG4R0atETlgFOlC1ZmcrSAQwrKKqCXRq0aEKsiYRWVifx8cqyiuz4crHJBkMclHOmpDAWHIWFqMMDEjCe6AMGQIrYADHiB4MWHLFzmMIPgkJRc5HLnlc0LXINyEctHKRysMU3PVYQUhVmUw5QvCjJyH0ZjC9lDafsmJMj6Q3Img7smXAVzL4Tqn3p0c8STUUgMAPORzQZVTBDzQMAMC+CMqeYKSl4oXyAJzVciyU3DXc/sNqQyUcHPvxkMK5Alzqck4QjEDs4yhNyuQ5EmfJtc9PPfwI8qtHsJlJeTAhzq8odF4wA8qehBSAsRPPCwoswI0zygoJjBaR484PP7JM+HdBkzlc8CNcw+80fNXx7MPvN4AB8wsMsxO8hVnQoT8VOlbzx0MvORzGjJvI3zQMGvKgIrKLPCUxRkJXPQpcw0ZC3g5KKfIFoWBL7DkpDKd0SdA/QzPKHzUsx/IgwXInvNEIb882n7J5ctXK6UB82PJ0kT8qvKOQ98ndC9ztch1H+CY8jHIdQz4B+iAKcsEAt/yk8siCKwL8j/PJzVJEvMzyacw0R+ym0BCivzMpQgtZyywhfN6An85fNlZtdW/KLyv0ZArhCGCgBUrjt8v4OMxoC6PNPyZMaAq7ziC4LDZJTtBPKspGcuGWELAC3iNsg+CpfPRz8C9Aqh118+wjkwjiNguExB8lQoULjsnumMZyMHAr9y80F7M3QqC9/NELn2W/BaQWcngE+BywaQFWAqeK1MGxBI9qXkoQaP7ENRHdWTx6JsgYlCXN+FHHM9RlIUK08ofCtrDyxhIHwpzDgi3gX6BZZNwpeoAMEbK8LcIHwpv4uXRCAOk0i6IoCLEi9IpiLAihJRmzaMncmoQPC9ItKKiUMIuSL3CyoqSJ0aCItaw6ipUy4zUkISSXw3kcE1A5GqGSmygFkKqD7ZxcfHnso0EEYS8xjOP4g2krMtimBxRwXEVtsRi9KjfImiUlDQQ3jQKEaphhU8jQQNMxzkGKO1MaDuR0VDYrghdcNBB/JzZDYsqh06O5DehmcRqlqFpyNBEOtA4nbREIni5MmblAaFZTM43kUSFORrOBynaJDisSHuxrOcATIJtio5UE5uSbNN/k7kNWigdAaLuGcgWuN5HdpV+cXC+9kIR7EhQTiRqkVI43NBFelGVW7DO5IhTLMcLwEFX3TgrgUkrwh/LdATeRlqfdykQWCfIODTYfWbPAQMpXBTHxG8Hdx9x44UvCb1xcOY1EU5UlgibQ6QW7AIJDIEVC7w/cGmkBp4gChRAyxfX3BIUA8SNAepiOYfDrxXqIGT4Uii0gKJohS2BVpoxSnw1qyqaNaDR86Sxz1UCQ0loqZY9sqNKGxV8DWh6Aqcj9OEAViT0s3xtwEOBmFT8KegEJgCUVHBR3uEZCAJuCT2SzwgUMMsFYIyqjW+zDk2nEkhTiGuhjL0yjsDAIEyyNH3zK0rLgsKwYwsptgzTHAmplB0tWBoJQyrWGYLl8BBDLpHGegsbLMyx2koIN6etIXZaCa2jkJqyyvAoCxCWeiLKoy1/DTLE03RxKlHaCco/TRoEQg9oh0EcqCov8GQmLgbwCkpr9jEdQj0JX4Nb0ZKdymhkKLRfK6wjLJJQInvNjUw8piIdWS1K3L3uKIkSJCi41ISILyqv05LiirwksIjyrl2oBHyn8qQzXym8svLUWM8rZZgK98pSIcknbLyTJRPqNlx62HSws4nor0qGSUoZCsdIXWWomiEZYd0gi5UkIClhA8LEMh3BuiFYxIpnuDQUNkoYZanOxQSUSCmJnyTPQZjAuHmiEomK+9l4Bwoi8AccqUnHFGTfiDGHHAjiOSEOFa4cKLEgNEOkj2B7NJkklYStCEiVDcK8KIqpEK28tLU0o61QBJIKZrzkrC3AXSRSOZa4t+IcreEjLzZia7gwK57AFLoopecKIkQLIO5MxwZBX4k1FySPH34hsoiuUK5reBWjlIEGcKDDCyILZGHAeuZSwNRref0AkxwohAietnyE5B2RwohPEo1Jk2wOykkqggi+SDhcvT0rJcF2TYQ74IolRIgba6HyETKGEgQYxss0kCQb2O4klZaoG0iRTycWIWXJ4FPuTLywaOqoMIc4PyDKVoqPyJ1ZO2Uqpkr8cGKOyJwyJXUrg9KjwUPkSvUMi8rihLSkmTLFMKE3KyfcbB7J2yEsjQFVxfsgjoSzANJpCsoPYOv9PyzLHwMuYPsm4x1oQcCzEIhWoUOqzIa+A5L30k8gHh/+HZMvIYM7oErIzyZckXJg7KrhUgHqnICLFnq8sQnIGeT6roETylPVBJFyGshXIdq1Fnwovq6IG2rB/ES2dKeM4mmHhAEbXF4BG8fKi1ITJazjNNgpWqjfUg+azjED9AGzGLx31XThkkwhesq+0aamvj2wEpH6TLhjeBkk1x/IRcVnUya4sUHk2sJQHrhHbRmWskdWRcIlr0YWaD4xMEJ60BpZ1NKD4wRuF4tzKr0idCURMA+4oVppaiIStx7ik3BdykFQi0apXoJrkUkZK0Dlip0pFFz/Q00fGv1hP5dyR4sf2FWvbRxojVnB0BinanVo7OE6DoMVamgJ7pj8T+QapMS5nHOwAQCJHuLtFMyHDQb+Qvn6pXse2jwoV1FmEqpuqoZFpkyUPIgGp7tKGXxlSkQqW6KOZUZMQhvUqaExKmcfalJRtqTOkKlM6qVytxGcaKEHkJQnit+xcEWRGhltkIzlBxa4bZGhkMcW9NJKsuUyN4EvSsuC3BSUE+EfkukH7FJLPHVdEBJU65OHTq0uNavaDiaTqrJpBFIQIxpFSnDK7wcFeVxDS96iMm/h1TQGkzxBncIoYQHS8vCHRu0K/0V8zqtdGpoz6umnFLEFfBWYUx8d6kgrGAikgShRXcXDIwaStwpJp9S27HJpG/Y0vUCiaRNGeoHEN6hIUn6p6sMgtSh+p79KdbGraKnyFgiwguaaekHpSGpumWRo6MekNIxaWhuFpfKRsBloKYMtHvBxwBejYpZC4mVM8d6TWjNziWUnlSz9aSumJkdiXWlNo96PNHvBLcfhptpTIjhodpxy52kApmG6WkXKZ6PXK+l3iUemFoqG8hpDpK8lqWuplRRhu0pGxBOkHomiFOk3RZadhpasDRO/G8UB8tA2oaICR6XVZp2QIUvoxG7xubLlcKgm3g7KdxsXobwBxtqdP6T2guybG/ugobeaMnHBJrsjUQSbHoGUS7oLslHk4aj6ZGPCa9tcFEdEKCOujwoUeORo1oQGPNCyblG4+gbKTGlKTPxRG8PJakDwW+hfp984mUOc4mtpofoDnTAL1oqtPxpCbDG9GFqb/Gmul3pb83eq5KqGbBkYYo4fhkQYbyxTWQaNvVkVw0jygnxmaeGKqjvL1qncm4YNm0aWO5DmrwvoYaGJQWhFqGJZt2br6g5uuaFmohl4ZSfIhojSl8PUQ4RLMHhDi5ZC95tlwpoL5stxx0PUVFQu+BgC+b3ifkOBaCiA4C+bO+cPN+aRKRgC+b3sc2j1EoyJdGOUx4enKYD2Ecgkxa5+OLDw98IW/HxbPWEuI0Ee0MFvThqTCuNbQfIKIHxaAy2PnqrCiKlovtsWnEymJkW58H9ycTaFqpaf1Suj5bQW8Fodl2khBi5bgkWsjbypRFomowAWks0tEb1EVs7B3Y1FtNE/mpFqla14IgvtEPmxoS+aCWrnLRbbMkHENayWqUVYgrYc1sixU9X0vAJDWplvJawZE2i+adW4LkfDS2NfH4g1W3lqfAvWj8B9a4Ww2nVE9lV1tVaIWx2VDb7W1UB9am2YTDw8EESQDja+HE7n5hxNQNtVb/HNHPebmeWNqzaWIYTARalMH1sjbVwg4RVbTwKsoXD+Wn1pRb04I8IYR1ETNvvBdha7mVbo0H1u0V0MO1sc9821tqCbQ2l1rLAfW/sFpaY8K1h9b3W5eE9bm2qlprZxKbPi98K4EdqyAWrIVr6NA5LVuRj98qFpVad2iAoD8A2+dvCU8mvds7a12yOm7BG2ilodbL2/sudaV2k9pBQPMI9rna5+Glprim2ydrXaJ4NeOXbh8zhrqxg80NpdxbIIDvICI4itovaqaHcC7BxwuVuta0oVpGLpSVRBCQ7s0ODvxDz2mFucgUOtHOFaL2wjIfQb2sDq1biOgjs3bdwR6SxaYcsK1ra8O6dpLiQWojto6TwccMCqS2g6AvYN6HDqpab+B9vo6VWySEI486Ytv+buOwFq4r7RdFoxJhK/DrnRQOrdv46FO5lvsDw2u9D1JpO2dp/aROkQp07vW7jqvbP4gDo06eOkLCU7qO7jrHav21ZBJbrOi1vZV5yxSHk7s22XKE6L29tTybLO8DrghT2geNM7V2sVHEIxKAzszb6ScVu/bDO2jFTa1OsNqC6naX8A47NWqlq86Q46Dtw6uZYAj46/KVTpjFOOiTulIxWmMVk6Euvm13QOA3FrAhcu8rsr4MC5ktS7Eu/ePpUlVTLry6pRLSVcxcukQwLjhQd0Hs60u4cHHCWO3DuuNgCQjtG61YSvPE6tWsbtDEMuqlqHhROpkg7bJu+NrlINWxFsW6W6XdtNE827buDbSudNvsxwTCZlJDE2lagO6sO8lpjaDu6ttT1h27btq6S4idsM7TlJvLC7tu22Dzoh2p9tO6eu5Lq261ob5uy7VuqlpRtUOsHuB7Du/LpS7geqKrE7lARZFXaNeVsp86tW1HvNpfuwDtShgOn7v9b323Hph632n9oh7KO9TpR6Qe6fio7fOqWGW6S4uzuTbce77qXbBlJtHB7fW8QGG6GO3oBRwPWgntJ62OsBAu6kO3no5a2CyluB6/23+MC6OuWFTR6BetfGQgzukSMV6tuAqM4wEO9Du94nwQto275KIYG94G9I3KlERujridTQe2M1Yd6rHrpK7ke83oNjYerbvqtpaS3rN76rdbu+Cwe+q1q7AepTEohOemttBaA+4nr46A+qTrOJNu/3o2x6evVv26A+8IBK7TWrbmJZBCDgMrxaCAPtt6Ous2V+EE+yLsZ6OuGPv06LYS7vD6tOmdpp6W+Ftm2Rd0aNr7ai+lXu0jZegPoR7We0SnjrW+nltZ7x4JjC76Feg3uoxW+xduY6piEPuu7Te/loD7nuyfuD6dqRVt67wmLPofxG293oHZvSibo64dZBVm57g+z2g3aFunQHXanWOT216t+x3tP7DejriCY9eksLD6UKSLvd6H+5jqn7V4Pnsr73GRKED7ZWs/q/62+kuN7724L/sg7m+qLq25MOhsvr7KWv/pH7U9Mjo654orHtL6rYRRiY7LWpNuv6DY3fujQv+xPtn6cBq2NI7lO3Aci7XVWyE54QB7Ach5XUCfs36KBrDq16r+znjc6YSBrSYGPmVsCoGOuYSCdaPOyHloxb+rgesVBCOgay7qemEVZbjFWPvVFdiGDBdcZWx8IBMSoN/0EG+B+7hn7PunfhuIxARtte70eHbsQIw+hsN8xV+qfrM0PugKuD75givtH7E+KVxL7B+lAalhIuwAeWgfeEQZb7Js2enZVc+uwbIa9ck1ozaLuKbvm79W2vmcGne/3rp6RCsPusGZW2Idogm8mbuCGZ+3NtuJwh4/sNpAh+zAz5aBlrtcxXdWAex6TaQoZA7I9SVVyHIByVl/77Ce7s5a7BjQcsGcB8wb1Yf+q/vbB621nvCYOhooaaHLwTIe3i+hpzgj7bBxrmkHqh9oclYXBnhBBCxNcbqfAlBzzhCHeu29rk4L+hIel6iiMHvbAAey/uowhONtvLbEOnmqwG1BlWsOGX+0FvOHB2oYdFK6hiVtmKmuqNomH9hyiwsHONSVUGLYul7uPavh4uigHwCQYtv7yW+Aagbiu9Acu67hg/qtaTh7to+y2h14bjoRc+ocao4Rx/v5bMS3gc37bsEAdGGZS2vrq7HBueqeHphz4ZPZ3hjvpKhSS4Ed7bKWmUvBG+O+XEE7GR8kdaGWRpEdfbPuhBu0HCIWkcBGdsHkcfbh8hBouGSenUKAiN+tXvnju+oUeuFYOqofvxlO98lD7ths6PIwiBp+OJ7ihsXFfgCRn4bnb3yT3v1GBo13vEH4umClIHQR0aLd6MR60aVaFutWN6GwBvcEMFF2m9tu7HRsoYp6XRpvuNGNExoedGCRFgdlGxcU2NkL0en0ZZ6g+6NAJEWejUZNj7Br0fNHYxyLr0GfR/wdC6pRgkTRGQxn0aNa4u0EZ5ZsWxkaLG6OtDvaHXUe7vLH9hhkHf7TBq4d4FWyxkZi7/h24cG7bwPVpS6uEGwbOH3uotu2GVjBsuSHD4GUZk77ei0PPAZexUe9ChsUwu1HghUca5HBx87qzG+x1dHnHuxhQar7kEBfoZ7iW9ISp7euhyHZ6JxiDC4Hy4LEdXGOwA/EbaYR3QUvHG8ukZW5xe70c/hJx3rrTGQ2J0c/Hah9UbFHk4MnsU6sx00R5H6xmMeiHg8tFvHGIJxHqNQtu/lnLaze+hAzG2Q7YZAmTBtQblZ/+/8e1Y2+sCaOQRsL0fCYsJxcZeG1cwifx73GB1jyGFurCadHjh3CbdHOxrbrlZNh6MYInrx3UTB65WYzqO64Jo/LmHQh/brNZy2hqWnzJWcYb9JyJxMconVh7VjwH5xuVmz7PunicE6VJ1EgfHzR1iffGuBxNhs65obcb0nHO2XsTYcxnCdMm1JqUcTYoxh7qfbE2CPr97U6XjRjize0ycFHMJk0hiGo+pyelagWpHsFQZkfYm+GcW+PihQgp1sbZ7UCZgy3GQDcfM8mvRzrvdEUedyaJb+ukZGcmE47ifn6fm7yc3RWXSHsjQAp5pDeGE2oYd4ZVB6sbVzU+y3oYnraWLscnN0XenYmcCZke4niFXVuSGc6Jsb270h5xtgHshgWnlGho7XTgop6IadV7Yp90QYovRwvq6mkBqSc4I0BiMbQIUpiXojFpKELtAGtJ6Sg8HBkckSkaABmYcXKFJsic4JlJh4b/pvBsqYFHkR66YnN/2qifGnsO7ief78Bs9DwH0+0abvx8xy4YPQDBiru4mbUAkd0mBwSydonQZ7zrKnSx/nqonmseielkjkSsYP7ZBs/CGg9RvkeiwSRvcbSnv0MtpjE1TU1jRnduwqd5ykZ+Ftynv0bCb47q0NAepmIZycCkBbC+wttwsstcRJY1mZtSRY/mRKy5mYWJZm6Z51S+o/KTS7JgFm9DdgLFnuZ4zy5mOZkAV5mqmKrxebdsyNK1yc4aYSUY58mEythNha7B0HRctWenThgQwulEYx8fRELLsg2eNC92LnMuyR0h7lMLf8gnm1nohVstdzLIrBA1m86QfKyiohT2eNn6iJ8ZfJXZp0FZj4Qps3Uj4qkiDtm9kOLEjn9hs2ZN6dKJ2YPHF4fkKTnwsENh9YG2/Wa/R6ETOI3oy8y2YcjNenOfAnONc2fmCi58uckLGlMuaGclCyBxgx6EelHULah7PPoQDzQ9sdmO8t6jEb5gkZAbx2myudAwvfbfOHmq0SVlgLa5JSZUFvaFGx7mDhPJrdnIQnid1nAKNufjrV5kXOt5GRIFACql5wJANmHWJnBOyTZjifCgMJi2dAx9iNVkfRx5o5G1w22bHOnmvwWAp1E1cr8HdZ0c4rl7yYSi7L9Fh8xNiX826fOBuiH5xeckj8KFBAFo/57RqsZ7WT+d5palWVlfnjKI+cfmeGw+evmw4M3OQWj8xa0kwD852KBRoQUKXkxsEaBeWRoQOxBdzQFgTETZrQCqIAXUskkFqawxFeeoW+50DwVzSFoeZrRmkAhbTn75gRZwX3Oq+arQNsKeaPmNVRHP7nipnlEIXu5hXNPAyF9ecbmz8U8BPmdC0eUpb9CRKE1n7KafK7w6KOSnEIF8zRdqa8F1OlPAaF36TPn5F1OcdlC50DB2on5yXJigj8yRfQpiF+RalZiCyhfkXj4OBYChb8wJdnnR892QeyNsL+Z3m9C/QmAVmFmZFcWeGxJYcX48pxa2Q4lmxbEaQ5+GP0JGF0+cb0LC4xdzQbCuwocKZwbbIVQmtV5qfJcIJlutYrk2qlJlWHehDEhk666NaWb1f2QaXkJmOTYMuly4G9KIyAketY9zB+iJID8a1naXmlhpYdZ+l0SlYd5ljpZaXeyyVn6Xa2JZYFQOxikjmX1lqaj2WWrKZq/rTIVGCAW4vJrHvx+RMe2yKF/c5bcLhoCNgJlgioaDokoAp+oX8uEUBtZnPKN5ZuX6i/BSeW64LbIZZcku6RzZpcfkpKh26YEgfTJS3tiOIzos6EPwLsWMklwyIBihxJDijaSuIFZJDGLYWCO6EUo0RGLREhq2d5BXJjoFKMKRQUE7HpczSZSJ9s+k6ShwsRacqNpxX4cqh4hMgXOXWRw4HmTco2IFnDeQu+bGWyEClOFB4pTobSEAZFSDLPfTpcPbX9S0JLBnqp5Sg4UlS0ON0hHAmfRgJI4rshjnQ4ZYQDjg4F/Y4jA4kMljiWVeGi1OQ5SORVIo5jVuVKCZViM1aGhNU9VaVX9U7NFFWP6tQNWbTS4litxJECNG1W6YYjitXssdjjddoLJWdgqc2K0AEgtJbgdrJ+4EEm1Q8clNdGIi+dbnjygeTTD8oxOvEkADUkC72nkiq/BSrIxuRzMw8ziBBhRKCsMgX2AwcbipzT+B0rNhFwozVUWIU1tyQrWtKXipUHgEEpI1SV+ateBMwCcKKrICSFNdYwVBKdadtx1gijQwYo4H340G1D4q7XUuIfmEhwoF9gkqbeDeAvdHpEdfrWBIK4gRUS1AUl71x1kvjggwEQSqqV7uWX3TWa0PVPz54TbKhirs5VXAJVvFR/kFJKNb3k6FZdF0UjZfQ8SEO5unaThUFlB9CGg2vxPNbcGnQONwFIFS/l2CHHTcTKU42cCigz46vD6bJw04bvnMM9tb4Np094fPnmDBUf3nWJ3+aAfmDZELFeZJZcJwdUSH5IUBz1SdYIcqh4rJMm65UZSXlqcwebegMrtuVDd9IVibehSHunY5ZFmwQMnEDV1KQG2QI97d1UV4jFFXmgFYaovQ02JeLTeRqfca3j4EkvHXmDtjeEgXUZzeOgXdVhuJZCw9woUvCFn304zeQF7eXsmPILNwXms2PNnzzs2p0uS1e1FvJZkU2nNuK1U2/Vp0qqWxGYhvySwBE5GA2NRRsG3ik+LAi24eE7iDmg2ESxXbWxE2Pgx48jdtcw9DhQqEnZ0+LmVWdK+JbuKgP1hcnRJ8tsewAJ0t/WEGSaIMKBr4U145T3l5oD0mb5OtkbnyQMBDjG743Qn8jmghsEih3WDhJ2Er5fcxaCm3K5AkichxkPoGMUe0LLdRjV+ZdwDsZ2qGCTQU16olfcaIQVGR1l3N+E3BqktombRuVHsY94lRO/h0qzobeMgodiGbiMl70awvfT5oTu2U2SeYxzU3WxG/hwEvVP/ki3hZlBp/4gdtzdQEAd4cVBgTNhRloFfNtUqus5EgggC2oeLrmC3KBY5LC3llf7bB2FV7gQ55eBGzdbFzDI7mxssd5zcYCqBBHa/79vXAWNTid0gVJ3kdxWei3uM2LfukdqDqGjnJKnUmCbrqDCgHDuuUQATQa2QtTFwGQYSoSpY6Tvjb59hKKseLY6TUS7DF1XeQzpIyQ6yFDwcCXY8k1TfYS3YEZDOjhangsVCGo4Sr6SFJeQjRC7AE0XuK6pxQ/WBTwMqXykXDC69LTehH0e8ChoscC0MGdsS4JrbQkoY0IF2dl3UbaomwhHx9YM6QyGUR9hD0jS4D6NrhawbhRODW4h6bdAxD+04yTj3RIOXAt21DQCkyAioVDJ1nanNTAnY+IJ4Wmog97ITKggwr1H0WE0WWjlggLC0LCohAH+hqIOhIGU7Yf6HyD1xqhLuDwoeKXgD5YCNVTLzQX6RBGsFLiThoPovXOCCaE34fRoOoqyGtMLIZ9se10ggwmYQSgP+FzfgJwcKJC9K+iE6SBlfQuhCTTWM4IrZFCUAkXNA34aETyhKHRxHP3qMtIJOXw6ckQcQERakXRpaRNqQZEJUYIuAO+UUA/sRRpEyUf3ORIkS8Kx4qTZv3i4O/fRpf94rJDZERXZrjXwVqCDu3vsNXIbQvtfhMqQJ4EZEkgSD9SCopacwqcJaBQHxoAwr+NUQYOy6Yg86oNwY/EDJUs9g+n470H9JQwDevXNAEIUZUW6YB45Yg0aW0QKo3pQBG4lvkwMJIWz470JUVRm+u8PJEOBD79G2c6+rg5ecZkV5ZXDjwSsdLhcZ7xSjbzebiERm/SRAlAF6CIctdR82ENtpBm+XsuvxLEDiN2FsCPnOzSLO5rHt3UCcQ+oTbREuExzzQULpMOgxXg5YPlcfQ8xySDuBMI4tRatGYOOIpI4sLKDjg6oj0jkZAbRwj7SJtQVfbJGiOOIlHAXoSj5iOtJRCPI9nyCjg2IF14cixgHiG0Qg9yO4J2Q5aPyDymaUOOI8QlvpHDhym3iG0Ko8CwbDuvsyRqmww/3jCjzyrMOjD7I81EMjsY95HRIuNwnA5j/eNfgijzdGiPR4mSrFopjqNuUjFxXHLmBORlClaOwj2o6AT5D6o4kHp+IJm2UraCo9WPwxXtCEPQurY9mPq0Jo6G7n8Mg/g4Ujqg/+O+HBqhSP8jqCJdY4m5PDOON+F+lsbv0Zg7AjrqT+hqPhDg7EYOXjkOn8c2j1I/+O1j4o4+PtI0htu53jkFs5GSTntkpnchWeM/d1jgDB0PK+bE+SPDj9pIxOy6Vk7fA0mz+ldRKoOvtbanGxXPYi0WM0UaEaZ8AV5bKT2g/e46O2Ria5AT8IUwLglFE4xJq0bwU9zglUE+sO8Ti4HsPAj+45a5dTgI9GOdT7khRLxchI9N6TjoE7tEcTa08yPBWUNu2OX0c9eNbTRerZ2O6DwMoD9uDmZAqOOA1w6noHTrYf2PBDpE/tEcjxE+BOnFHhxrpgz82JVODDzjTZCldBE4AxRKbLu+RwnDY49CszsE6igK6YLhDsPT048CMEz1hVVO0TsShjPMTg05jFIzm0/m6CTz05aIAh4iF9PSz19uL1Cm5nMLPI+2MStpOTgLQ7P0z8OCbPnTgDBOt6zxY4oODT+WJoP3jtUwZilQqQ5pmZ6EcFVDRDs/H9Ob+fQTFP3DjU4ljjT+I/9FsMY86YPoz1qgOO6zgkL6Oq0O9BUBwQ5mBGPv0dU6ZIVD9JvTOxi6Tq8xpRZUSHPfz249GPoYeFozKGj6k+FPfzksyhQDzpkjPP+wUY4chDz/w4QuTzr0RQuHDm8/MZ7TrC6tjwLyc8fO1xIC9fOEfMTvmzPziOsjDqQrc6DPFRyFuGPVzgY/ha8L/o5AMhokvGvOkL988NJULxQ8sQfiQwR9FrDt8+pC9T0Y6nOaYzfA5OsLzeOzOLz/0QuOujgi7ZCH+/C7vRSLpkhQoXzr86ouaY4i4AwPSIttUvWLkC6yHs0Gc7QuiiLS9XPdjxBAChQm+M7svRTzGbxPwQLU9fPCLty5wuRLuy/lO1cyi6Bbd6NS+WOKYu8/hylzy4PDpLjnw+hy7iaSmtOgj0rn1o5Lqs6RjrRMU/9P4CGkAFpMruRSiqKcok8qJ/kc88SuirkI/dEBwXw7uJo6HE9gx1zqy7o8WTkK5pi3Raw5jwgW4q94v1LpU/BAvDtw+TOKY88HIbgzn4iEa5LgC7Gv8zuc/HIpLuM8Ku7LwMTaOZTos42x4neHOWuH1ojfyuoz/0WNxsr5ZEcv5SPc8xmJL+UhLPJzjS4GJ3LnS6BbuSHC8ZOrrhK/eRSI9YmuvKr2K/GJzr0q7o3jrq44CG9r0Jv9Otr7w/9OFSqw7+vqzws2SPvr4okWvpDqq804Jz2kHqvEb7495Pa8pTjhubrn86huljny5PIhLjy7ZDgb/q+YOyl5mcUJ/UTndaLal93kVFPz3uNtP6b5UT/gmbxq5LLWpLifSv8y8wNCHfr/Ql5uXRdCH5vGb+bufA4xAW9CkXRP0VCPJb/0Rlv3RVm/m6sbnigzyYxBc5wJdc1gYHONaSER/A1q3A9dKP0ByHU4XJIQDrYehPcwFpnB78H9yMaJdPUyH6x9DYp7EEZFLBGkPGUfpmqAqWupu92OnkhE/AqTR5ri+2W6hjCqHQcoM6BzmbkWkFiHx5nbtWA5hw70TGjko7t7EizH8j0hL2DhBiSSlqZb2geC5+TdG+R94bTBNuHwEpHNu4IMOmfP70AgpXhPBIXbQxwaHLBqJDY4mW99p5pPlvq5NiHfGwa2ESnNsPRIzDN9e6GyH71vJCen+1TmkJYMqXCzi3whL9lkjOlK45aS8L4DNaUmkysoA9ak+UPLLBwTURA5hEFpc6QvBLpK3g3uQ1re5lRwD3e6GkJcArKIUKYLSEmwZhTyRBXoKwSVpv7pO9HdozSLcBuR5kZOT5xIkDGi9Lw689BEVroSJj4pH0NIvpcEIWyIwgrdm2E6SMVptvsQyqJ+hMhImbNvypRKeIHQeOYFtdqpLocwPQfUaKTlqpPcc+Q7Y+Kd+SwZiVnbAngT5U5YX3AZTLh2J8qOiBXgX5PaggeNG/+4MJFzNKS/goq9B/CBYZCB5WIASIoQLnXUYriQQI0cqnKhqHxeDiqUKQ5BdpTIYZFTII0CLlu5uHzAO6QRaKGixXTIbOwaoRaPZBDvqEM+DcgT0OHjge6sBx2qgg5oXA1YN1NfH8gtsbemklQyQ2UMExAxySS1LodB4oMu+YjArg+gPmmlomKULC+wiZJhmgziMKRypXckGghCfrQHKH0f5IMmQ1ZOSVXGUfKoa2Q1Y6oRisvj6uFqimTJ2dB9hE86+Nk24AFV+DBJ6ai8HUowHyYhZrgccMhqjuudyQ9xA5Ch4PBpWApQxIMaVeM8wZSGVZ5Q2cGzHb3hANkiCZc0O267RpaRFZrRWodyR1whwCR/VgPHhtFW4BV9GHbSGMFEpweJwiFOIxi0aFcCj6CH+HixsnvyGUi8nrgF7uA16rEyRxNPsgEJPGh5fmoRdBtizkWIF5dvL2kaIDZLMEf3xgVbqEWhEUrSlZpT9xsARXNK4XhBWakmFV3APJWFCmhR2U9duhAa0OdKFVKMXmtG+eIX9DibRgizF5QVj8NBWPqaFGmjoUdSxJQkUrAaJJiTPoFABQAbAewESSvASUGsA5bSnkxAAAQSoBPgZAHQB0k+wBQBBXoxP+Bjk8ljABVgWV8FfNgWxCYBFQFq2YY4wAAC9TgSwCzgmASseYZwJASKoAYk6sAgBBX0AFWBpANgDAAAgQV4yTPgSRWBA1XgkB+VNYZZ18ACAA16sBjXsbSoBgQcCVsCXxWAHKBpANlBQAmZz4CBZbgKQAjB4wVYEjfo3l4BQBEAVYFSSoAT4GTfVgVN/9fDXy+CYBAkYEBQBPoT6FcAfgT0HmCPX4gFOoiEcMEjBiAKAEFfgQe18deAgZwGzBpwL6GcAAAET/ApAKV/kBQAT17xQjxHWa6oKBVMFVe5XqwA1fwULV7WTdXot8DfBgE151fQ3z9U2AyJK1/GBbXkAE7enXl1+zApAd16kAJ3rwinfZXGd+BB9Xw16DeGQHd9UdociN6jeY3uN6kAE3yoCTfEkgt7TeY3rN5ze83gD8LeA3kIBLepQZhgreq3sABretQOt6veG3n6GbfpAKMDbf2XqQBPfu3zEF7f9Qft7FfX2Ed9QAx3kAGvftyklAmzkz/UmVf539V81ev1Vd4ff13kIGfft3qQHNfxwOgAPebXqRVw+z3t15AAPXhd7xQfsIlGBzooYZUfeN3qmBfeuP3d/8oP39N+cBY38pZ/ejYRN+YB831N8/eM3kD5WAwPlN9k+oPrUDLepAOD+rerAWt8GB63ogEbeMUdD8w/23nD4denXnt83KiP5gAHfxwSV7I+ZXsT8oZ8igDA2C53yj82Ab+FRG1eDHP18g+ugOlaYBsg19839egPj6PfBPqwFdfRP+V8QhZrY5HqxTProGg/IKFL9OANXsiV8B9P6cTABZxeN7cI8AVIEs/K36z5CBbPkN5Q+HPipcy+QgbL6vegvpwvy/V4Qr7Y/iv8z+YgyvhUAvMVPtlBnFyEKDka/PgKz4Q+bPpD7oBok5z9bfXP5gFw/PPvt5FfmALgAAA1fz+lecAcd4G+7kEdy+LZ3lV4i+mP7V/F3WP+L6NfN3sbSe/FP1Rw4D0vgT/c/nXrL/PfmAS9+YBKPq79HwycCgSK/Xv+T/W/PvpqwIoZvjN+/fmAX9+M/AP6r8xBM37N6M//3kz9G/LAEr7s/mv+D8Q+oJZD5B/UPpt//eMPrb+w+dvv772/CPg75ABRXj/lI+zv2oFB+IyrOU0INp+j/u/l35j84+2gfH44+Pv5gHAlIJH76yTj3v76E+L3kT/6/5X7n8c3+gPn7i+n3t74U+JfpT8q+2gDH+YB1P+ws0+2UbT5ABdPoD4M/sf3N9x/AP/H8J/YPlr5W+2vtb+YZ7ASn6c/qflz7p/Zfrt8Z/agbz5AAB38QFO/yP0H9+JztfClu+GPxd4e+WPjX7k+t38X5ABptb76vfD33767f5foH8V+Qfy74j+XUg4We/NfmH46+df1RyYCqv1T8x/kfmNjN+Lfg35AAsf0D9t+IP4t/G/Hfkn9W+yfon4p+uvqn50+W36MB9/dv/D68/mfgAHEAAOQHfQ/wL9y/aQcH6CJ5HO74G+l3hhke/GnEX5e+xfrf5T/P1NP5B+M/mX56+ugV14V+cvzhQe4tS+8yh+OPsv/3+vvh8Ut+1P2v9R/W/l/8x/DPm38H+8fl74d/y3k79SfkwByfhR8PfmGAvfrT8QgB28GfmP99vuigg/hll2fmH9Lvs01dGkShw6NH8Bfhv94/tv8S/kn89/k+Ipfun9+Pif85fgD9hPpf8dyGTh0AQMB/Anf8tfrD9y/ql9Efq/8NPij8tPn+9f/uj9q/ob9v/mj82/lYAAAcT9Wvl0BbPm79wAUYBNvsP9oAW58/fnACmfggC5/ud8KPvn9SwF9YDYvz81/kDMqYJsAs4Hv8ofqeAPGGPBJvloA0viQCMvuQDevtmAqAVCwtsGbxNAQn8QgDfxS3gIBTAXVZn/o385vvV8esIt8RAc78xAWt8Q3tOBT/pYA+vnn95Xr+dDAFZtHAXgCrAC4CpQMMpJfp4DeAcK86vib8hEH4DDfkADu/iAD1viECrAWf8bAUr87HCSx0mE4CugK/BXARQJkgawDMQN4CMgf9AsgU38cgS78e/g/93fv39Kbna9CgWEDigRECZ7GUDYgS4B8flUCpQDUDd3lxJ9fqkCGgRwDfAU19sgV382gXkDggdICsPrID6fvIDmAAR8A/sz8yAAABpZQGc/VAHYYM5ZFYajThfNf5RfXQFW+Nd4vfRL5nAVMLuAiCTmAo/6kAroA9ArP4UA2wGDfM5a4iYv5CA8b7UaMN56/FwBeA2r7zfBr4LAloFLAwIHtA+z6OfW0yhA8IGqA3L6nA65aGaKH6E/YEG6/FIGzfCEE+AzIHQg5b7AA5lCrAyAEyAroAwArYGuveAHOAUV6nUZAHz/B2Cy0NDh72S4GMfQX6b/O4H4A976EA3d6cwVeDS/D4G+/U94UAi/4lAofDhsMhR72BgGl/Z4FcwOoGG/N/6cAgQGf/PgHW/NUH2/Dv6AA2EGWAcQEIgtD4Ug9YFUguQEefBQG7AhAHT/Wf5MglQHh/QLiomWVIr/GP4hAdf43A4X4jAnf6MA5P5PiP8qWvCwGZ/MUHWAygGSgxxQOgjPQAg9j6MAh/7gSP0FV/L97sAuv5cA837gfdUFN/fgEf/bUFQSCz6LA0QH6g136Gggf4pgmn6UgywDUg80HbA8f4IA475HAi77yvb7Yw6LQGcgnAEeguUEEAs167vP0HCgoV7IgwH4gAYH6og8mAhBSMFdAe/7PAv3hKgpv4qg037Jghv6pA5v44/bgGCAsz7Zg3v4wgvMEGgzr6IgqQHGg7b6igvD6VgukGYgAd5vPW0HHA3L6JcFA4MlDkGx/LkG4Az0G8g017uA/d4BgsgFfA4MESggYE7kS8HHiOLgjg6H5bvGMG7vUEFpgo36Eg5wD1/VMGN/BcE//FMF//dv6rgzv4bggsFbgo0GD/EsEmgssFmgg8G0gxQHs0WsGDg6QKaEOl5Ngxd48fNEgrvYSDLNLEHKJKmAYQJ2gf6GCS8fV8Eig3sEhgr8GhMIsT2oTID/ggMCuAxiG7vPEEvAPFgEsJwCNAzECQQkz6N/ESFNAQlhLgqH72EPIHDKEkG5AskGFg7oH7goT4/Ah6DXwCl4P/KH4MgfiHPAoSE1fdIFzAokFLfVoFdAbV7wg1CEszT4FBgooHsQwiGOKXERq/R6C8QvQBGQuH7lfEyGYgGSFyQpMFqg6SH4sWSFiQ+SH4/RSHMoZSFWQ/MHwggoHvgpyHaQg8TuQ4oIVAywCGQqUACQ1Rx+Qlf5mQ99jNAlSHUgIIHRJfUBsQ5KFuQolAkQ9KGZQgMDGQycGzAgqHEg2KFOgFYGlQ2oBsQz8EuQ7oCVQ9M6Q/fH58QrKH1QoiRpggKHhQoKEf/EKGiQrUEvfKKH2EXUHIQ+KFlQ3oHn/HP4VQw0iGoYDIPgtIBeQoaE+QhUC5QkABjQ8CESQ2cFQQ1IHHQzMGzQgQBKQhaEBAuKFtQhKGOQvoG5/bqFfiUhTHIGT74/WqF6AYaHxgl4CNQhb7NQvUGtQtSFPQ/77Bg9aFAWErIyGOIHOA8b7ZQpqyHQwGFQgyyEgwkqHgw7P79g16Hh/SqEtSWGHbQ4qHZAOqH7Q+8STgy6HmQiCFnQqSEXQ0KGBQi36RQm6HRQu6Gkg3oDtQhyEQwpKGhgnqEbQnIA8QmqEIwv6HTA/EH5QoGFowvMGgwtmGYw8UFrQ7mHvQvSGeQ4mG/Q0mFfqQ6EUwiaHyQqaFhQmaGGvOaFuA/wGsw/IHLQxKEvQ7SHISe1Da4ExgGQwWEqwzYDIwgkHiQtoCFQ2KE2Qx6HGw56GrQ7GFmwriFaoA6iKw7yHMA3yHkwumHjQ9/6aw2mHTQq6G6wpmHzQg2GqQqWHuwzmGmwuWHmwzaFfQl74/QxGFBwkaHgg0WGowuOGugyWFGwjqErQvsEDg0H6pwlwpbQqH6DQkmGBwg6HBwyOGUw06HBQiOHawqOGrfK4Cxw3MH3Q9r7swzSHfAlOE+wqqEqgf8GZwoWFggmYEOw5uFOw4GF5g12FgwxOFYw8uGXfSuF9Q/2F7Q+uFkwnOFtwwKFhwuCE8AtlDqwhmHXQruH6wnuGGw4IFLwmWFewoeF2UXn4eQ9KG1w5WFbw1WGNw9uEzwmcCtwo+EhwnWGdw26EFwuEFuwkuEmwlEEVw4eEDANKFwwroDjw22H2wvOHzA8WH3QheEJwkBEew/oFvQteHG8MeE2w1+F2whqHTwpqFIIn4AoI4uEcw5eE4w1eEQI7BFPw3aF1wx/5Iw9+F7w1UGTQ3eHjQk+HRws+ExQvUF9w6WGQwu+G6QyDJksAWGrgrOENwneEiwyEGIIwBGWAUhFXwtBFJwz2Erw+sHDw3DJWwgaF0Il+EMI7OH/Q5wDqw/eFzgn+FNwg+HLgwIFcIlmHxwshEDwj8GywjiE8w6GFqIjeH0I8CRqw3+GOwr+GsIoxEfwkxEKQmOHnw9cG9wjGHXw/hF2I+WFCIjhzQIjKG4I7RHiI3RH1AwhFiwmRFFw+RHkIweGhIlKFEoGhGRI2BF4I+BFSIiyFJIoJEKIihFQwj6FZIwmGMwTRFiI7eFxI3FhuIz+GSQw+HCQ+pE+IxmHmImRG8ItYF7g0f6HgvCHHg6wpngusHjYC0BnLRjbSfUiGuguP6tg0X7eg/kHleHj7+gt4GWA0BFlwyhEXgzOg0fCzh0fdKFjg22FF0ScFgQ9xGNIvT7zgjMERQ//46gjpEoQvv7bgrpEj/WAG9Iy0GDvcnjMAUd7MgnciVXNkH1YG8GTIu8HTIr0Ew/H0FC+LsEsQnsGlw5yGg/fNANZeSL/g3ZF4Ih8oSIpH6JggxHnQ4D6agjuErg1wFIQwJE9/CQFdAiAHoQ734bA/cF7fYEAeAfwBHAfwDpJGwAEAGwC1WWJLxJRJJoAAgDSAFAD4IcsAEAOMCpJZwCfAJoAoAPADVgdgA8opN41gF4CYBQQB2w8QB2wigBnACgCYAc4CYAegCZZEACoAPADpJVAC1gSwBaooP5+fE14JAQoDaomsFMAZRhhAo1EKAL3BhA8V6tAMv6WoxkEKAWGCWokj5nAA1Es/Nn7cAF1FDvVoAOooP6nghQAmoxAGtAMshhAgd4DIhQBCiYNEvIwhTBokP7RAF1HWg1oBiqMIHxo0t6MoA1FhA1oDkgNNEgAA4FeokN5ZohsBeorGD6o3wAJgAgD2ARwCNYYECfQAgAqvd4GgACV69fAADCqSSIA8AAAACsQAAABJgAcYBOveABkANAD0AdtFRUPAB4AeABHfJ140opoB2FeAAAAFU7Rs6PgAvn2Zgo6PgAAAGUAAKIAAdU+AHaKIA3aN7RAQH7Rg6LIAw6IOoq6InRQQCSSqwDnRC6KXRQ8FXRm6K3R8AD5R9gA4AwIEc+YQObRRAFbRe6IPREAD7RA6KHRbaJHRY6MvRU6JnR86MXRy6MEAj6O3Ru6K7RPaIAxR6KAxp6JAx56LAxk6OvRt6OgxD6LHRT6JfRKADfRr0NfYTaJbR7aMQxh6OPRwGNAx46Kwx06JvRUGPvRK6Pwx8GKkAf6KQxgGJPRZ6OzgF6PoxkGLvRMGLgxz6NfR76KkAH/F6+PYA3RA737RCpE+AUmJkxfVAyywID/AZGJ/RFGP3RnGJQx3GPQxvGMwxV6IYxOGOYxsGNYxO6PYxlGOQx1GLQxtGPAx2GKYxQmNMxhGOIxwIDeeamN/RFmK4xNGIwxdGIMxAmNwxLGPXRbGOYAHGKoxqGJ4xJmJ8xEGMYxgmLwxgWJExRGLExzAAyybmI0x/6M8x1mO8xtmMMx9mNixT6IQxmmNCxOmJsx/GOix/mIixBGNExr0OsKKWJCxlmLCxumIixWWL8xxmOEx+WLSx2mK8xemMixdmJixAWIqxCWNeh70BqxHmI6xGWK6xTWNKxLWNMxbWK0xVmPCxfGN8xk2IcxcWKcxiWJAAIfxSx7WPgAAACk10Yui20Z2jqMSIBh0Qtioseui6wDaDmAFtjdsftjDsUBjjsSBjTsdhi10RdjXoX59evgAAlWTFZAT4BfYpTFvYz9EgAb9HuYgrF1YorGZYkrFGY5bF5Y8zGg49LHzY/TFnYnLF9Y7dGrYt7GkYs/7A41LGzY+rHFYxbFQ43LFBYkAC1Y+HENYp7HZY3rHlY1HGVYj9ESYzHHkYknGjYhHHdYinFlY1rGw4rbG44iHH445HFU4+LHOYqQBhALL5Y4xnFzYsnGI4nrFs46bEc4nHHg48bGQ43nHCYtHEfo1zFn/OsAoACACTAdYDwAAAAaAAElRXgAB5TYAfYkDFVIT4BrotMAQANdE2ASYATAFAC64g3GG4t7HJY+nHqY0XFc4+XE84ynHs44LEjYsXF44pHE+4xzE04wXHVYt3Eg4znFy4xrEK44PFxYmbGFYzrEx473FS4lbE04mJKZJLoDao3z4SvPVFZos1H+os1GUAF1FWokAEl4u1GY+M/6ivJ1G68S1Fuo5BjBo4d4KAD1G+o5fxMAYNFIAqRAeo0NFQIiNFeoxNFB/GNE5AONEz/BNEj4y7EQqLNEZo4tFZ47NGHA0t55o9vFhAgtGlvItFsvaAGcvVJI2AeACUo6QAZgGJIEAVVHwAcsCnvFJJpJTEDlgekj3AKcBAAAA="))
+///////////////////////////////////////////////
+
+///////////////////////////////////////////////
+/* Utility functions */
+
+var storagePrefix = 'KiCad_HTML_BOM__' + pcbdata.metadata.title + '__' +
+  pcbdata.metadata.revision + '__#';
+var storage;
+
+function initStorage(key) {
+  try {
+    window.localStorage.getItem("blank");
+    storage = window.localStorage;
+  } catch (e) {
+    // localStorage not available
+  }
+  if (!storage) {
+    try {
+      window.sessionStorage.getItem("blank");
+      storage = window.sessionStorage;
+    } catch (e) {
+      // sessionStorage also not available
+    }
+  }
+}
+
+function readStorage(key) {
+  if (storage) {
+    return storage.getItem(storagePrefix + key);
+  } else {
+    return null;
+  }
+}
+
+function writeStorage(key, value) {
+  if (storage) {
+    storage.setItem(storagePrefix + key, value);
+  }
+}
+
+function fancyDblClickHandler(el, onsingle, ondouble) {
+  return function() {
+    if (el.getAttribute("data-dblclick") == null) {
+      el.setAttribute("data-dblclick", 1);
+      setTimeout(function() {
+        if (el.getAttribute("data-dblclick") == 1) {
+          onsingle();
+        }
+        el.removeAttribute("data-dblclick");
+      }, 200);
+    } else {
+      el.removeAttribute("data-dblclick");
+      ondouble();
+    }
+  }
+}
+
+function smoothScrollToRow(rowid) {
+  document.getElementById(rowid).scrollIntoView({
+    behavior: "smooth",
+    block: "center",
+    inline: "nearest"
+  });
+}
+
+function focusInputField(input) {
+  input.scrollIntoView(false);
+  input.focus();
+  input.select();
+}
+
+function copyToClipboard() {
+  var text = '';
+  for (var node of bomhead.childNodes[0].childNodes) {
+    if (node.firstChild) {
+      text = text + node.firstChild.nodeValue;
+    }
+    if (node != bomhead.childNodes[0].lastChild) {
+      text += '\t';
+    }
+  }
+  text += '\n';
+  for (var row of bombody.childNodes) {
+    for (var cell of row.childNodes) {
+      for (var node of cell.childNodes) {
+        if (node.nodeName == "INPUT") {
+          if (node.checked) {
+            text = text + '✓';
+          }
+        } else if (node.nodeName == "MARK") {
+          text = text + node.firstChild.nodeValue;
+        } else {
+          text = text + node.nodeValue;
+        }
+      }
+      if (cell != row.lastChild) {
+        text += '\t';
+      }
+    }
+    text += '\n';
+  }
+  var textArea = document.createElement("textarea");
+  textArea.classList.add('clipboard-temp');
+  textArea.value = text;
+
+  document.body.appendChild(textArea);
+  textArea.focus();
+  textArea.select();
+
+  try {
+    if (document.execCommand('copy')) {
+      console.log('Bom copied to clipboard.');
+    }
+  } catch (err) {
+    console.log('Can not copy to clipboard.');
+  }
+
+  document.body.removeChild(textArea);
+}
+
+function removeGutterNode(node) {
+  for (var i = 0; i < node.childNodes.length; i++) {
+    if (node.childNodes[i].classList &&
+      node.childNodes[i].classList.contains("gutter")) {
+      node.removeChild(node.childNodes[i]);
+      break;
+    }
+  }
+}
+
+function cleanGutters() {
+  removeGutterNode(document.getElementById("bot"));
+  removeGutterNode(document.getElementById("canvasdiv"));
+}
+
+var units = {
+  prefixes: {
+    giga: ["G", "g", "giga", "Giga", "GIGA"],
+    mega: ["M", "mega", "Mega", "MEGA"],
+    kilo: ["K", "k", "kilo", "Kilo", "KILO"],
+    milli: ["m", "milli", "Milli", "MILLI"],
+    micro: ["U", "u", "micro", "Micro", "MICRO", "μ", "µ"], // different utf8 μ
+    nano: ["N", "n", "nano", "Nano", "NANO"],
+    pico: ["P", "p", "pico", "Pico", "PICO"],
+  },
+  unitsShort: ["R", "r", "Ω", "F", "f", "H", "h"],
+  unitsLong: [
+    "OHM", "Ohm", "ohm", "ohms",
+    "FARAD", "Farad", "farad",
+    "HENRY", "Henry", "henry"
+  ],
+  getMultiplier: function(s) {
+    if (this.prefixes.giga.includes(s)) return 1e9;
+    if (this.prefixes.mega.includes(s)) return 1e6;
+    if (this.prefixes.kilo.includes(s)) return 1e3;
+    if (this.prefixes.milli.includes(s)) return 1e-3;
+    if (this.prefixes.micro.includes(s)) return 1e-6;
+    if (this.prefixes.nano.includes(s)) return 1e-9;
+    if (this.prefixes.pico.includes(s)) return 1e-12;
+    return 1;
+  },
+  valueRegex: null,
+}
+
+function initUtils() {
+  var allPrefixes = units.prefixes.giga
+    .concat(units.prefixes.mega)
+    .concat(units.prefixes.kilo)
+    .concat(units.prefixes.milli)
+    .concat(units.prefixes.micro)
+    .concat(units.prefixes.nano)
+    .concat(units.prefixes.pico);
+  var allUnits = units.unitsShort.concat(units.unitsLong);
+  units.valueRegex = new RegExp("^([0-9\.]+)" +
+    "\\s*(" + allPrefixes.join("|") + ")?" +
+    "(" + allUnits.join("|") + ")?" +
+    "(\\b.*)?$", "");
+  units.valueAltRegex = new RegExp("^([0-9]*)" +
+    "(" + units.unitsShort.join("|") + ")?" +
+    "([GgMmKkUuNnPp])?" +
+    "([0-9]*)" +
+    "(\\b.*)?$", "");
+  if (config.fields.includes("Value")) {
+    var index = config.fields.indexOf("Value");
+    pcbdata.bom["parsedValues"] = {};
+    for (var id in pcbdata.bom.fields) {
+      pcbdata.bom.parsedValues[id] = parseValue(pcbdata.bom.fields[id][index])
+    }
+  }
+}
+
+function parseValue(val, ref) {
+  var inferUnit = (unit, ref) => {
+    if (unit) {
+      unit = unit.toLowerCase();
+      if (unit == 'Ω' || unit == "ohm" || unit == "ohms") {
+        unit = 'r';
+      }
+      unit = unit[0];
+    } else {
+      ref = /^([a-z]+)\d+$/i.exec(ref);
+      if (ref) {
+        ref = ref[1].toLowerCase();
+        if (ref == "c") unit = 'f';
+        else if (ref == "l") unit = 'h';
+        else if (ref == "r" || ref == "rv") unit = 'r';
+        else unit = null;
+      }
+    }
+    return unit;
+  };
+  val = val.replace(/,/g, "");
+  var match = units.valueRegex.exec(val);
+  var unit;
+  if (match) {
+    val = parseFloat(match[1]);
+    if (match[2]) {
+      val = val * units.getMultiplier(match[2]);
+    }
+    unit = inferUnit(match[3], ref);
+    if (!unit) return null;
+    else return {
+      val: val,
+      unit: unit,
+      extra: match[4],
+    }
+  }
+  match = units.valueAltRegex.exec(val);
+  if (match && (match[1] || match[4])) {
+    val = parseFloat(match[1] + "." + match[4]);
+    if (match[3]) {
+      val = val * units.getMultiplier(match[3]);
+    }
+    unit = inferUnit(match[2], ref);
+    if (!unit) return null;
+    else return {
+      val: val,
+      unit: unit,
+      extra: match[5],
+    }
+  }
+  return null;
+}
+
+function valueCompare(a, b, stra, strb) {
+  if (a === null && b === null) {
+    // Failed to parse both values, compare them as strings.
+    if (stra != strb) return stra > strb ? 1 : -1;
+    else return 0;
+  } else if (a === null) {
+    return 1;
+  } else if (b === null) {
+    return -1;
+  } else {
+    if (a.unit != b.unit) return a.unit > b.unit ? 1 : -1;
+    else if (a.val != b.val) return a.val > b.val ? 1 : -1;
+    else if (a.extra != b.extra) return a.extra > b.extra ? 1 : -1;
+    else return 0;
+  }
+}
+
+function validateSaveImgDimension(element) {
+  var valid = false;
+  var intValue = 0;
+  if (/^[1-9]\d*$/.test(element.value)) {
+    intValue = parseInt(element.value);
+    if (intValue <= 16000) {
+      valid = true;
+    }
+  }
+  if (valid) {
+    element.classList.remove("invalid");
+  } else {
+    element.classList.add("invalid");
+  }
+  return intValue;
+}
+
+function saveImage(layer) {
+  var width = validateSaveImgDimension(document.getElementById("render-save-width"));
+  var height = validateSaveImgDimension(document.getElementById("render-save-height"));
+  var bgcolor = null;
+  if (!document.getElementById("render-save-transparent").checked) {
+    var style = getComputedStyle(topmostdiv);
+    bgcolor = style.getPropertyValue("background-color");
+  }
+  if (!width || !height) return;
+
+  // Prepare image
+  var canvas = document.createElement("canvas");
+  var layerdict = {
+    transform: {
+      x: 0,
+      y: 0,
+      s: 1,
+      panx: 0,
+      pany: 0,
+      zoom: 1,
+    },
+    bg: canvas,
+    fab: canvas,
+    silk: canvas,
+    highlight: canvas,
+    layer: layer,
+  }
+  // Do the rendering
+  recalcLayerScale(layerdict, width, height);
+  prepareLayer(layerdict);
+  clearCanvas(canvas, bgcolor);
+  drawBackground(layerdict, false);
+  drawHighlightsOnLayer(layerdict, false);
+
+  // Save image
+  var imgdata = canvas.toDataURL("image/png");
+
+  var filename = pcbdata.metadata.title;
+  if (pcbdata.metadata.revision) {
+    filename += `.${pcbdata.metadata.revision}`;
+  }
+  filename += `.${layer}.png`;
+  saveFile(filename, dataURLtoBlob(imgdata));
+}
+
+function saveSettings() {
+  var data = {
+    type: "InteractiveHtmlBom settings",
+    version: 1,
+    pcbmetadata: pcbdata.metadata,
+    settings: settings,
+  }
+  var blob = new Blob([JSON.stringify(data, null, 4)], {
+    type: "application/json"
+  });
+  saveFile(`${pcbdata.metadata.title}.settings.json`, blob);
+}
+
+function loadSettings() {
+  var input = document.createElement("input");
+  input.type = "file";
+  input.accept = ".settings.json";
+  input.onchange = function(e) {
+    var file = e.target.files[0];
+    var reader = new FileReader();
+    reader.onload = readerEvent => {
+      var content = readerEvent.target.result;
+      var newSettings;
+      try {
+        newSettings = JSON.parse(content);
+      } catch (e) {
+        alert("Selected file is not InteractiveHtmlBom settings file.");
+        return;
+      }
+      if (newSettings.type != "InteractiveHtmlBom settings") {
+        alert("Selected file is not InteractiveHtmlBom settings file.");
+        return;
+      }
+      var metadataMatches = newSettings.hasOwnProperty("pcbmetadata");
+      if (metadataMatches) {
+        for (var k in pcbdata.metadata) {
+          if (!newSettings.pcbmetadata.hasOwnProperty(k) || newSettings.pcbmetadata[k] != pcbdata.metadata[k]) {
+            metadataMatches = false;
+          }
+        }
+      }
+      if (!metadataMatches) {
+        var currentMetadata = JSON.stringify(pcbdata.metadata, null, 4);
+        var fileMetadata = JSON.stringify(newSettings.pcbmetadata, null, 4);
+        if (!confirm(
+            `Settins file metadata does not match current metadata.\n\n` +
+            `Page metadata:\n${currentMetadata}\n\n` +
+            `Settings file metadata:\n${fileMetadata}\n\n` +
+            `Press OK if you would like to import settings anyway.`)) {
+          return;
+        }
+      }
+      overwriteSettings(newSettings.settings);
+    }
+    reader.readAsText(file, 'UTF-8');
+  }
+  input.click();
+}
+
+function overwriteSettings(newSettings) {
+  initDone = false;
+  Object.assign(settings, newSettings);
+  writeStorage("bomlayout", settings.bomlayout);
+  writeStorage("bommode", settings.bommode);
+  writeStorage("canvaslayout", settings.canvaslayout);
+  writeStorage("bomCheckboxes", settings.checkboxes.join(","));
+  document.getElementById("bomCheckboxes").value = settings.checkboxes.join(",");
+  for (var checkbox of settings.checkboxes) {
+    writeStorage("checkbox_" + checkbox, settings.checkboxStoredRefs[checkbox]);
+  }
+  writeStorage("markWhenChecked", settings.markWhenChecked);
+  padsVisible(settings.renderPads);
+  document.getElementById("padsCheckbox").checked = settings.renderPads;
+  fabricationVisible(settings.renderFabrication);
+  document.getElementById("fabricationCheckbox").checked = settings.renderFabrication;
+  silkscreenVisible(settings.renderSilkscreen);
+  document.getElementById("silkscreenCheckbox").checked = settings.renderSilkscreen;
+  referencesVisible(settings.renderReferences);
+  document.getElementById("referencesCheckbox").checked = settings.renderReferences;
+  valuesVisible(settings.renderValues);
+  document.getElementById("valuesCheckbox").checked = settings.renderValues;
+  tracksVisible(settings.renderTracks);
+  document.getElementById("tracksCheckbox").checked = settings.renderTracks;
+  zonesVisible(settings.renderZones);
+  document.getElementById("zonesCheckbox").checked = settings.renderZones;
+  dnpOutline(settings.renderDnpOutline);
+  document.getElementById("dnpOutlineCheckbox").checked = settings.renderDnpOutline;
+  setRedrawOnDrag(settings.redrawOnDrag);
+  document.getElementById("dragCheckbox").checked = settings.redrawOnDrag;
+  setDarkMode(settings.darkMode);
+  document.getElementById("darkmodeCheckbox").checked = settings.darkMode;
+  setHighlightPin1(settings.highlightpin1);
+  document.getElementById("highlightpin1Checkbox").checked = settings.highlightpin1;
+  showFootprints(settings.show_footprints);
+  writeStorage("boardRotation", settings.boardRotation);
+  document.getElementById("boardRotation").value = settings.boardRotation / 5;
+  document.getElementById("rotationDegree").textContent = settings.boardRotation;
+  initDone = true;
+  prepCheckboxes();
+  changeBomLayout(settings.bomlayout);
+}
+
+function saveFile(filename, blob) {
+  var link = document.createElement("a");
+  var objurl = URL.createObjectURL(blob);
+  link.download = filename;
+  link.href = objurl;
+  link.click();
+}
+
+function dataURLtoBlob(dataurl) {
+  var arr = dataurl.split(','),
+    mime = arr[0].match(/:(.*?);/)[1],
+    bstr = atob(arr[1]),
+    n = bstr.length,
+    u8arr = new Uint8Array(n);
+  while (n--) {
+    u8arr[n] = bstr.charCodeAt(n);
+  }
+  return new Blob([u8arr], {
+    type: mime
+  });
+}
+
+var settings = {
+  canvaslayout: "default",
+  bomlayout: "default",
+  bommode: "grouped",
+  checkboxes: [],
+  checkboxStoredRefs: {},
+  darkMode: false,
+  highlightpin1: false,
+  redrawOnDrag: true,
+  boardRotation: 0,
+  renderPads: true,
+  renderReferences: true,
+  renderValues: true,
+  renderSilkscreen: true,
+  renderFabrication: true,
+  renderDnpOutline: false,
+  renderTracks: true,
+  renderZones: true,
+  columnOrder: [],
+  hiddenColumns: [],
+}
+
+function initDefaults() {
+  settings.bomlayout = readStorage("bomlayout");
+  if (settings.bomlayout === null) {
+    settings.bomlayout = config.bom_view;
+  }
+  if (!['bom-only', 'left-right', 'top-bottom'].includes(settings.bomlayout)) {
+    settings.bomlayout = config.bom_view;
+  }
+  settings.bommode = readStorage("bommode");
+  if (settings.bommode === null) {
+    settings.bommode = "grouped";
+  }
+  if (!["grouped", "ungrouped", "netlist"].includes(settings.bommode)) {
+    settings.bommode = "grouped";
+  }
+  settings.canvaslayout = readStorage("canvaslayout");
+  if (settings.canvaslayout === null) {
+    settings.canvaslayout = config.layer_view;
+  }
+  var bomCheckboxes = readStorage("bomCheckboxes");
+  if (bomCheckboxes === null) {
+    bomCheckboxes = config.checkboxes;
+  }
+  settings.checkboxes = bomCheckboxes.split(",").filter((e) => e);
+  document.getElementById("bomCheckboxes").value = bomCheckboxes;
+
+  settings.markWhenChecked = readStorage("markWhenChecked") || "";
+  populateMarkWhenCheckedOptions();
+
+  function initBooleanSetting(storageString, def, elementId, func) {
+    var b = readStorage(storageString);
+    if (b === null) {
+      b = def;
+    } else {
+      b = (b == "true");
+    }
+    document.getElementById(elementId).checked = b;
+    func(b);
+  }
+
+  initBooleanSetting("padsVisible", config.show_pads, "padsCheckbox", padsVisible);
+  initBooleanSetting("fabricationVisible", config.show_fabrication, "fabricationCheckbox", fabricationVisible);
+  initBooleanSetting("silkscreenVisible", config.show_silkscreen, "silkscreenCheckbox", silkscreenVisible);
+  initBooleanSetting("referencesVisible", true, "referencesCheckbox", referencesVisible);
+  initBooleanSetting("valuesVisible", true, "valuesCheckbox", valuesVisible);
+  if ("tracks" in pcbdata) {
+    initBooleanSetting("tracksVisible", true, "tracksCheckbox", tracksVisible);
+    initBooleanSetting("zonesVisible", true, "zonesCheckbox", zonesVisible);
+  } else {
+    document.getElementById("tracksAndZonesCheckboxes").style.display = "none";
+    tracksVisible(false);
+    zonesVisible(false);
+  }
+  initBooleanSetting("dnpOutline", false, "dnpOutlineCheckbox", dnpOutline);
+  initBooleanSetting("redrawOnDrag", config.redraw_on_drag, "dragCheckbox", setRedrawOnDrag);
+  initBooleanSetting("darkmode", config.dark_mode, "darkmodeCheckbox", setDarkMode);
+  initBooleanSetting("highlightpin1", config.highlight_pin1, "highlightpin1Checkbox", setHighlightPin1);
+
+  var fields = ["checkboxes", "References"].concat(config.fields).concat(["Quantity"]);
+  var hcols = JSON.parse(readStorage("hiddenColumns"));
+  if (hcols === null) {
+    hcols = [];
+  }
+  settings.hiddenColumns = hcols.filter(e => fields.includes(e));
+
+  var cord = JSON.parse(readStorage("columnOrder"));
+  if (cord === null) {
+    cord = fields;
+  } else {
+    cord = cord.filter(e => fields.includes(e));
+    if (cord.length != fields.length)
+      cord = fields;
+  }
+  settings.columnOrder = cord;
+
+  settings.boardRotation = readStorage("boardRotation");
+  if (settings.boardRotation === null) {
+    settings.boardRotation = config.board_rotation * 5;
+  } else {
+    settings.boardRotation = parseInt(settings.boardRotation);
+  }
+  document.getElementById("boardRotation").value = settings.boardRotation / 5;
+  document.getElementById("rotationDegree").textContent = settings.boardRotation;
+}
+
+// Helper classes for user js callbacks.
+
+const IBOM_EVENT_TYPES = {
+  ALL: "all",
+  HIGHLIGHT_EVENT: "highlightEvent",
+  CHECKBOX_CHANGE_EVENT: "checkboxChangeEvent",
+  BOM_BODY_CHANGE_EVENT: "bomBodyChangeEvent",
+}
+
+const EventHandler = {
+  callbacks: {},
+  init: function() {
+    for (eventType of Object.values(IBOM_EVENT_TYPES))
+      this.callbacks[eventType] = [];
+  },
+  registerCallback: function(eventType, callback) {
+    this.callbacks[eventType].push(callback);
+  },
+  emitEvent: function(eventType, eventArgs) {
+    event = {
+      eventType: eventType,
+      args: eventArgs,
+    }
+    var callback;
+    for (callback of this.callbacks[eventType])
+      callback(event);
+    for (callback of this.callbacks[IBOM_EVENT_TYPES.ALL])
+      callback(event);
+  }
+}
+EventHandler.init();
+
+///////////////////////////////////////////////
+
+///////////////////////////////////////////////
+/* PCB rendering code */
+
+var emptyContext2d = document.createElement("canvas").getContext("2d");
+
+function deg2rad(deg) {
+  return deg * Math.PI / 180;
+}
+
+function calcFontPoint(linepoint, text, offsetx, offsety, tilt) {
+  var point = [
+    linepoint[0] * text.width + offsetx,
+    linepoint[1] * text.height + offsety
+  ];
+  // This approximates pcbnew behavior with how text tilts depending on horizontal justification
+  point[0] -= (linepoint[1] + 0.5 * (1 + text.justify[0])) * text.height * tilt;
+  return point;
+}
+
+function drawText(ctx, text, color) {
+  if ("ref" in text && !settings.renderReferences) return;
+  if ("val" in text && !settings.renderValues) return;
+  ctx.save();
+  ctx.fillStyle = color;
+  ctx.strokeStyle = color;
+  ctx.lineCap = "round";
+  ctx.lineJoin = "round";
+  ctx.lineWidth = text.thickness;
+  if ("svgpath" in text) {
+    ctx.stroke(new Path2D(text.svgpath));
+    ctx.restore();
+    return;
+  }
+  ctx.translate(...text.pos);
+  ctx.translate(text.thickness * 0.5, 0);
+  var angle = -text.angle;
+  if (text.attr.includes("mirrored")) {
+    ctx.scale(-1, 1);
+    angle = -angle;
+  }
+  var tilt = 0;
+  if (text.attr.includes("italic")) {
+    tilt = 0.125;
+  }
+  var interline = text.height * 1.5 + text.thickness;
+  var txt = text.text.split("\n");
+  // KiCad ignores last empty line.
+  if (txt[txt.length - 1] == '') txt.pop();
+  ctx.rotate(deg2rad(angle));
+  var offsety = (1 - text.justify[1]) / 2 * text.height; // One line offset
+  offsety -= (txt.length - 1) * (text.justify[1] + 1) / 2 * interline; // Multiline offset
+  for (var i in txt) {
+    var lineWidth = text.thickness + interline / 2 * tilt;
+    for (var j = 0; j < txt[i].length; j++) {
+      if (txt[i][j] == '\t') {
+        var fourSpaces = 4 * pcbdata.font_data[' '].w * text.width;
+        lineWidth += fourSpaces - lineWidth % fourSpaces;
+      } else {
+        if (txt[i][j] == '~') {
+          j++;
+          if (j == txt[i].length)
+            break;
+        }
+        lineWidth += pcbdata.font_data[txt[i][j]].w * text.width;
+      }
+    }
+    var offsetx = -lineWidth * (text.justify[0] + 1) / 2;
+    var inOverbar = false;
+    for (var j = 0; j < txt[i].length; j++) {
+      if (txt[i][j] == '\t') {
+        var fourSpaces = 4 * pcbdata.font_data[' '].w * text.width;
+        offsetx += fourSpaces - offsetx % fourSpaces;
+        continue;
+      } else if (txt[i][j] == '~') {
+        j++;
+        if (j == txt[i].length)
+          break;
+        if (txt[i][j] != '~') {
+          inOverbar = !inOverbar;
+        }
+      }
+      var glyph = pcbdata.font_data[txt[i][j]];
+      if (inOverbar) {
+        var overbarStart = [offsetx, -text.height * 1.4 + offsety];
+        var overbarEnd = [offsetx + text.width * glyph.w, overbarStart[1]];
+
+        if (!lastHadOverbar) {
+          overbarStart[0] += text.height * 1.4 * tilt;
+          lastHadOverbar = true;
+        }
+        ctx.beginPath();
+        ctx.moveTo(...overbarStart);
+        ctx.lineTo(...overbarEnd);
+        ctx.stroke();
+      } else {
+        lastHadOverbar = false;
+      }
+      for (var line of glyph.l) {
+        ctx.beginPath();
+        ctx.moveTo(...calcFontPoint(line[0], text, offsetx, offsety, tilt));
+        for (var k = 1; k < line.length; k++) {
+          ctx.lineTo(...calcFontPoint(line[k], text, offsetx, offsety, tilt));
+        }
+        ctx.stroke();
+      }
+      offsetx += glyph.w * text.width;
+    }
+    offsety += interline;
+  }
+  ctx.restore();
+}
+
+function drawedge(ctx, scalefactor, edge, color) {
+  ctx.strokeStyle = color;
+  ctx.fillStyle = color;
+  ctx.lineWidth = Math.max(1 / scalefactor, edge.width);
+  ctx.lineCap = "round";
+  ctx.lineJoin = "round";
+  if ("svgpath" in edge) {
+    ctx.stroke(new Path2D(edge.svgpath));
+  } else {
+    ctx.beginPath();
+    if (edge.type == "segment") {
+      ctx.moveTo(...edge.start);
+      ctx.lineTo(...edge.end);
+    }
+    if (edge.type == "rect") {
+      ctx.moveTo(...edge.start);
+      ctx.lineTo(edge.start[0], edge.end[1]);
+      ctx.lineTo(...edge.end);
+      ctx.lineTo(edge.end[0], edge.start[1]);
+      ctx.lineTo(...edge.start);
+    }
+    if (edge.type == "arc") {
+      ctx.arc(
+        ...edge.start,
+        edge.radius,
+        deg2rad(edge.startangle),
+        deg2rad(edge.endangle));
+    }
+    if (edge.type == "circle") {
+      ctx.arc(
+        ...edge.start,
+        edge.radius,
+        0, 2 * Math.PI);
+      ctx.closePath();
+    }
+    if (edge.type == "curve") {
+      ctx.moveTo(...edge.start);
+      ctx.bezierCurveTo(...edge.cpa, ...edge.cpb, ...edge.end);
+    }
+    if("filled" in edge && edge.filled)
+      ctx.fill();
+    else
+      ctx.stroke();
+  }
+}
+
+function getChamferedRectPath(size, radius, chamfpos, chamfratio) {
+  // chamfpos is a bitmask, left = 1, right = 2, bottom left = 4, bottom right = 8
+  var path = new Path2D();
+  var width = size[0];
+  var height = size[1];
+  var x = width * -0.5;
+  var y = height * -0.5;
+  var chamfOffset = Math.min(width, height) * chamfratio;
+  path.moveTo(x, 0);
+  if (chamfpos & 4) {
+    path.lineTo(x, y + height - chamfOffset);
+    path.lineTo(x + chamfOffset, y + height);
+    path.lineTo(0, y + height);
+  } else {
+    path.arcTo(x, y + height, x + width, y + height, radius);
+  }
+  if (chamfpos & 8) {
+    path.lineTo(x + width - chamfOffset, y + height);
+    path.lineTo(x + width, y + height - chamfOffset);
+    path.lineTo(x + width, 0);
+  } else {
+    path.arcTo(x + width, y + height, x + width, y, radius);
+  }
+  if (chamfpos & 2) {
+    path.lineTo(x + width, y + chamfOffset);
+    path.lineTo(x + width - chamfOffset, y);
+    path.lineTo(0, y);
+  } else {
+    path.arcTo(x + width, y, x, y, radius);
+  }
+  if (chamfpos & 1) {
+    path.lineTo(x + chamfOffset, y);
+    path.lineTo(x, y + chamfOffset);
+    path.lineTo(x, 0);
+  } else {
+    path.arcTo(x, y, x, y + height, radius);
+  }
+  path.closePath();
+  return path;
+}
+
+function getOblongPath(size) {
+  return getChamferedRectPath(size, Math.min(size[0], size[1]) / 2, 0, 0);
+}
+
+function getPolygonsPath(shape) {
+  if (shape.path2d) {
+    return shape.path2d;
+  }
+  if ("svgpath" in shape) {
+    shape.path2d = new Path2D(shape.svgpath);
+  } else {
+    var path = new Path2D();
+    for (var polygon of shape.polygons) {
+      path.moveTo(...polygon[0]);
+      for (var i = 1; i < polygon.length; i++) {
+        path.lineTo(...polygon[i]);
+      }
+      path.closePath();
+    }
+    shape.path2d = path;
+  }
+  return shape.path2d;
+}
+
+function drawPolygonShape(ctx, scalefactor, shape, color) {
+  ctx.save();
+  if (!("svgpath" in shape)) {
+    ctx.translate(...shape.pos);
+    ctx.rotate(deg2rad(-shape.angle));
+  }
+  if("filled" in shape && !shape.filled) {
+    ctx.strokeStyle = color;
+    ctx.lineWidth = Math.max(1 / scalefactor, shape.width);
+    ctx.lineCap = "round";
+    ctx.lineJoin = "round";
+    ctx.stroke(getPolygonsPath(shape));
+  } else {
+    ctx.fillStyle = color;
+    ctx.fill(getPolygonsPath(shape));
+  }
+  ctx.restore();
+}
+
+function drawDrawing(ctx, scalefactor, drawing, color) {
+  if (["segment", "arc", "circle", "curve", "rect"].includes(drawing.type)) {
+    drawedge(ctx, scalefactor, drawing, color);
+  } else if (drawing.type == "polygon") {
+    drawPolygonShape(ctx, scalefactor, drawing, color);
+  } else {
+    drawText(ctx, drawing, color);
+  }
+}
+
+function getCirclePath(radius) {
+  var path = new Path2D();
+  path.arc(0, 0, radius, 0, 2 * Math.PI);
+  path.closePath();
+  return path;
+}
+
+function getCachedPadPath(pad) {
+  if (!pad.path2d) {
+    // if path2d is not set, build one and cache it on pad object
+    if (pad.shape == "rect") {
+      pad.path2d = new Path2D();
+      pad.path2d.rect(...pad.size.map(c => -c * 0.5), ...pad.size);
+    } else if (pad.shape == "oval") {
+      pad.path2d = getOblongPath(pad.size);
+    } else if (pad.shape == "circle") {
+      pad.path2d = getCirclePath(pad.size[0] / 2);
+    } else if (pad.shape == "roundrect") {
+      pad.path2d = getChamferedRectPath(pad.size, pad.radius, 0, 0);
+    } else if (pad.shape == "chamfrect") {
+      pad.path2d = getChamferedRectPath(pad.size, pad.radius, pad.chamfpos, pad.chamfratio)
+    } else if (pad.shape == "custom") {
+      pad.path2d = getPolygonsPath(pad);
+    }
+  }
+  return pad.path2d;
+}
+
+function drawPad(ctx, pad, color, outline) {
+  ctx.save();
+  ctx.translate(...pad.pos);
+  ctx.rotate(deg2rad(pad.angle));
+  if (pad.offset) {
+    ctx.translate(...pad.offset);
+  }
+  ctx.fillStyle = color;
+  ctx.strokeStyle = color;
+  var path = getCachedPadPath(pad);
+  if (outline) {
+    ctx.stroke(path);
+  } else {
+    ctx.fill(path);
+  }
+  ctx.restore();
+}
+
+function drawPadHole(ctx, pad, padHoleColor) {
+  if (pad.type != "th") return;
+  ctx.save();
+  ctx.translate(...pad.pos);
+  ctx.rotate(deg2rad(pad.angle));
+  ctx.fillStyle = padHoleColor;
+  if (pad.drillshape == "oblong") {
+    ctx.fill(getOblongPath(pad.drillsize));
+  } else {
+    ctx.fill(getCirclePath(pad.drillsize[0] / 2));
+  }
+  ctx.restore();
+}
+
+function drawFootprint(ctx, layer, scalefactor, footprint, colors, highlight, outline) {
+  if (highlight) {
+    // draw bounding box
+    if (footprint.layer == layer) {
+      ctx.save();
+      ctx.globalAlpha = 0.2;
+      ctx.translate(...footprint.bbox.pos);
+      ctx.rotate(deg2rad(-footprint.bbox.angle));
+      ctx.translate(...footprint.bbox.relpos);
+      ctx.fillStyle = colors.pad;
+      ctx.fillRect(0, 0, ...footprint.bbox.size);
+      ctx.globalAlpha = 1;
+      ctx.strokeStyle = colors.pad;
+      ctx.strokeRect(0, 0, ...footprint.bbox.size);
+      ctx.restore();
+    }
+  }
+  // draw drawings
+  for (var drawing of footprint.drawings) {
+    if (drawing.layer == layer) {
+      drawDrawing(ctx, scalefactor, drawing.drawing, colors.pad);
+    }
+  }
+  // draw pads
+  if (settings.renderPads) {
+    for (var pad of footprint.pads) {
+      if (pad.layers.includes(layer)) {
+        drawPad(ctx, pad, colors.pad, outline);
+        if (pad.pin1 && settings.highlightpin1) {
+          drawPad(ctx, pad, colors.outline, true);
+        }
+      }
+    }
+    for (var pad of footprint.pads) {
+      drawPadHole(ctx, pad, colors.padHole);
+    }
+  }
+}
+
+function drawEdgeCuts(canvas, scalefactor) {
+  var ctx = canvas.getContext("2d");
+  var edgecolor = getComputedStyle(topmostdiv).getPropertyValue('--pcb-edge-color');
+  for (var edge of pcbdata.edges) {
+    drawDrawing(ctx, scalefactor, edge, edgecolor);
+  }
+}
+
+function drawFootprints(canvas, layer, scalefactor, highlight) {
+  var ctx = canvas.getContext("2d");
+  ctx.lineWidth = 3 / scalefactor;
+  var style = getComputedStyle(topmostdiv);
+
+  var colors = {
+    pad: style.getPropertyValue('--pad-color'),
+    padHole: style.getPropertyValue('--pad-hole-color'),
+    outline: style.getPropertyValue('--pin1-outline-color'),
+  }
+
+  for (var i = 0; i < pcbdata.footprints.length; i++) {
+    var mod = pcbdata.footprints[i];
+    var outline = settings.renderDnpOutline && pcbdata.bom.skipped.includes(i);
+    var h = highlightedFootprints.includes(i);
+    var d = markedFootprints.has(i);
+    if (highlight) {
+      if(h && d) {
+        colors.pad = style.getPropertyValue('--pad-color-highlight-both');
+        colors.outline = style.getPropertyValue('--pin1-outline-color-highlight-both');
+      } else if (h) {
+        colors.pad = style.getPropertyValue('--pad-color-highlight');
+        colors.outline = style.getPropertyValue('--pin1-outline-color-highlight');
+      } else if (d) {
+        colors.pad = style.getPropertyValue('--pad-color-highlight-marked');
+        colors.outline = style.getPropertyValue('--pin1-outline-color-highlight-marked');
+      }
+    }
+    if( h || d || !highlight) {
+      drawFootprint(ctx, layer, scalefactor, mod, colors, highlight, outline);
+    }
+  }
+}
+
+function drawBgLayer(layername, canvas, layer, scalefactor, edgeColor, polygonColor, textColor) {
+  var ctx = canvas.getContext("2d");
+  for (var d of pcbdata.drawings[layername][layer]) {
+    if (["segment", "arc", "circle", "curve", "rect"].includes(d.type)) {
+      drawedge(ctx, scalefactor, d, edgeColor);
+    } else if (d.type == "polygon") {
+      drawPolygonShape(ctx, scalefactor, d, polygonColor);
+    } else {
+      drawText(ctx, d, textColor);
+    }
+  }
+}
+
+function drawTracks(canvas, layer, color, highlight) {
+  ctx = canvas.getContext("2d");
+  ctx.strokeStyle = color;
+  ctx.lineCap = "round";
+  for (var track of pcbdata.tracks[layer]) {
+    if (highlight && highlightedNet != track.net) continue;
+    ctx.lineWidth = track.width;
+    ctx.beginPath();
+    if ('radius' in track) {
+      ctx.arc(
+        ...track.center,
+        track.radius,
+        deg2rad(track.startangle),
+        deg2rad(track.endangle));
+    } else {
+      ctx.moveTo(...track.start);
+      ctx.lineTo(...track.end);
+    }
+    ctx.stroke();
+  }
+}
+
+function drawZones(canvas, layer, color, highlight) {
+  ctx = canvas.getContext("2d");
+  ctx.strokeStyle = color;
+  ctx.fillStyle = color;
+  ctx.lineJoin = "round";
+  for (var zone of pcbdata.zones[layer]) {
+    if (!zone.path2d) {
+      zone.path2d = getPolygonsPath(zone);
+    }
+    if (highlight && highlightedNet != zone.net) continue;
+    ctx.fill(zone.path2d);
+    if (zone.width > 0) {
+      ctx.lineWidth = zone.width;
+      ctx.stroke(zone.path2d);
+    }
+  }
+}
+
+function clearCanvas(canvas, color = null) {
+  var ctx = canvas.getContext("2d");
+  ctx.save();
+  ctx.setTransform(1, 0, 0, 1, 0, 0);
+  if (color) {
+    ctx.fillStyle = color;
+    ctx.fillRect(0, 0, canvas.width, canvas.height);
+  } else {
+    if (!window.matchMedia("print").matches)
+      ctx.clearRect(0, 0, canvas.width, canvas.height);
+  }
+  ctx.restore();
+}
+
+function drawNets(canvas, layer, highlight) {
+  var style = getComputedStyle(topmostdiv);
+  if (settings.renderTracks) {
+    var trackColor = style.getPropertyValue(highlight ? '--track-color-highlight' : '--track-color');
+    drawTracks(canvas, layer, trackColor, highlight);
+  }
+  if (settings.renderZones) {
+    var zoneColor = style.getPropertyValue(highlight ? '--zone-color-highlight' : '--zone-color');
+    drawZones(canvas, layer, zoneColor, highlight);
+  }
+  if (highlight && settings.renderPads) {
+    var padColor = style.getPropertyValue('--pad-color-highlight');
+    var padHoleColor = style.getPropertyValue('--pad-hole-color');
+    var ctx = canvas.getContext("2d");
+    for (var footprint of pcbdata.footprints) {
+      // draw pads
+      var padDrawn = false;
+      for (var pad of footprint.pads) {
+        if (highlightedNet != pad.net) continue;
+        if (pad.layers.includes(layer)) {
+          drawPad(ctx, pad, padColor, false);
+          padDrawn = true;
+        }
+      }
+      if (padDrawn) {
+        // redraw all pad holes because some pads may overlap
+        for (var pad of footprint.pads) {
+          drawPadHole(ctx, pad, padHoleColor);
+        }
+      }
+    }
+  }
+}
+
+function drawHighlightsOnLayer(canvasdict, clear = true) {
+  if (clear) {
+    clearCanvas(canvasdict.highlight);
+  }
+  if (markedFootprints.size > 0 || highlightedFootprints.length > 0) {
+    drawFootprints(canvasdict.highlight, canvasdict.layer,
+      canvasdict.transform.s * canvasdict.transform.zoom, true);
+  }
+  if (highlightedNet !== null) {
+    drawNets(canvasdict.highlight, canvasdict.layer, true);
+  }
+}
+
+function drawHighlights() {
+  drawHighlightsOnLayer(allcanvas.front);
+  drawHighlightsOnLayer(allcanvas.back);
+}
+
+function drawBackground(canvasdict, clear = true) {
+  if (clear) {
+    clearCanvas(canvasdict.bg);
+    clearCanvas(canvasdict.fab);
+    clearCanvas(canvasdict.silk);
+  }
+
+  drawNets(canvasdict.bg, canvasdict.layer, false);
+  drawFootprints(canvasdict.bg, canvasdict.layer,
+    canvasdict.transform.s * canvasdict.transform.zoom, false);
+
+  drawEdgeCuts(canvasdict.bg, canvasdict.transform.s * canvasdict.transform.zoom);
+
+  var style = getComputedStyle(topmostdiv);
+  var edgeColor = style.getPropertyValue('--silkscreen-edge-color');
+  var polygonColor = style.getPropertyValue('--silkscreen-polygon-color');
+  var textColor = style.getPropertyValue('--silkscreen-text-color');
+  if (settings.renderSilkscreen) {
+    drawBgLayer(
+      "silkscreen", canvasdict.silk, canvasdict.layer,
+      canvasdict.transform.s * canvasdict.transform.zoom,
+      edgeColor, polygonColor, textColor);
+  }
+  edgeColor = style.getPropertyValue('--fabrication-edge-color');
+  polygonColor = style.getPropertyValue('--fabrication-polygon-color');
+  textColor = style.getPropertyValue('--fabrication-text-color');
+  if (settings.renderFabrication) {
+    drawBgLayer(
+      "fabrication", canvasdict.fab, canvasdict.layer,
+      canvasdict.transform.s * canvasdict.transform.zoom,
+      edgeColor, polygonColor, textColor);
+  }
+}
+
+function prepareCanvas(canvas, flip, transform) {
+  var ctx = canvas.getContext("2d");
+  ctx.setTransform(1, 0, 0, 1, 0, 0);
+  var fontsize = 1.55;
+  ctx.scale(transform.zoom, transform.zoom);
+  ctx.translate(transform.panx, transform.pany);
+  if (flip) {
+    ctx.scale(-1, 1);
+  }
+  ctx.translate(transform.x, transform.y);
+  ctx.rotate(deg2rad(settings.boardRotation));
+  ctx.scale(transform.s, transform.s);
+}
+
+function prepareLayer(canvasdict) {
+  var flip = (canvasdict.layer == "B");
+  for (var c of ["bg", "fab", "silk", "highlight"]) {
+    prepareCanvas(canvasdict[c], flip, canvasdict.transform);
+  }
+}
+
+function rotateVector(v, angle) {
+  angle = deg2rad(angle);
+  return [
+    v[0] * Math.cos(angle) - v[1] * Math.sin(angle),
+    v[0] * Math.sin(angle) + v[1] * Math.cos(angle)
+  ];
+}
+
+function applyRotation(bbox) {
+  var corners = [
+    [bbox.minx, bbox.miny],
+    [bbox.minx, bbox.maxy],
+    [bbox.maxx, bbox.miny],
+    [bbox.maxx, bbox.maxy],
+  ];
+  corners = corners.map((v) => rotateVector(v, settings.boardRotation));
+  return {
+    minx: corners.reduce((a, v) => Math.min(a, v[0]), Infinity),
+    miny: corners.reduce((a, v) => Math.min(a, v[1]), Infinity),
+    maxx: corners.reduce((a, v) => Math.max(a, v[0]), -Infinity),
+    maxy: corners.reduce((a, v) => Math.max(a, v[1]), -Infinity),
+  }
+}
+
+function recalcLayerScale(layerdict, width, height) {
+  var bbox = applyRotation(pcbdata.edges_bbox);
+  var scalefactor = 0.98 * Math.min(
+    width / (bbox.maxx - bbox.minx),
+    height / (bbox.maxy - bbox.miny)
+  );
+  if (scalefactor < 0.1) {
+    scalefactor = 1;
+  }
+  layerdict.transform.s = scalefactor;
+  var flip = (layerdict.layer == "B");
+  if (flip) {
+    layerdict.transform.x = -((bbox.maxx + bbox.minx) * scalefactor + width) * 0.5;
+  } else {
+    layerdict.transform.x = -((bbox.maxx + bbox.minx) * scalefactor - width) * 0.5;
+  }
+  layerdict.transform.y = -((bbox.maxy + bbox.miny) * scalefactor - height) * 0.5;
+  for (var c of ["bg", "fab", "silk", "highlight"]) {
+    canvas = layerdict[c];
+    canvas.width = width;
+    canvas.height = height;
+    canvas.style.width = (width / devicePixelRatio) + "px";
+    canvas.style.height = (height / devicePixelRatio) + "px";
+  }
+}
+
+function redrawCanvas(layerdict) {
+  prepareLayer(layerdict);
+  drawBackground(layerdict);
+  drawHighlightsOnLayer(layerdict);
+}
+
+function resizeCanvas(layerdict) {
+  var canvasdivid = {
+    "F": "frontcanvas",
+    "B": "backcanvas"
+  } [layerdict.layer];
+  var width = document.getElementById(canvasdivid).clientWidth * devicePixelRatio;
+  var height = document.getElementById(canvasdivid).clientHeight * devicePixelRatio;
+  recalcLayerScale(layerdict, width, height);
+  redrawCanvas(layerdict);
+}
+
+function resizeAll() {
+  resizeCanvas(allcanvas.front);
+  resizeCanvas(allcanvas.back);
+}
+
+function pointWithinDistanceToSegment(x, y, x1, y1, x2, y2, d) {
+  var A = x - x1;
+  var B = y - y1;
+  var C = x2 - x1;
+  var D = y2 - y1;
+
+  var dot = A * C + B * D;
+  var len_sq = C * C + D * D;
+  var dx, dy;
+  if (len_sq == 0) {
+    // start and end of the segment coincide
+    dx = x - x1;
+    dy = y - y1;
+  } else {
+    var param = dot / len_sq;
+    var xx, yy;
+    if (param < 0) {
+      xx = x1;
+      yy = y1;
+    } else if (param > 1) {
+      xx = x2;
+      yy = y2;
+    } else {
+      xx = x1 + param * C;
+      yy = y1 + param * D;
+    }
+    dx = x - xx;
+    dy = y - yy;
+  }
+  return dx * dx + dy * dy <= d * d;
+}
+
+function modulo(n, mod) {
+  return ((n % mod) + mod) % mod;
+}
+
+function pointWithinDistanceToArc(x, y, xc, yc, radius, startangle, endangle, d) {
+  var dx = x - xc;
+  var dy = y - yc;
+  var r_sq = dx * dx + dy * dy;
+  var rmin = Math.max(0, radius - d);
+  var rmax = radius + d;
+
+  if (r_sq < rmin * rmin || r_sq > rmax * rmax)
+    return false;
+
+  var angle1 = modulo(deg2rad(startangle), 2 * Math.PI);
+  var dx1 = xc + radius * Math.cos(angle1) - x;
+  var dy1 = yc + radius * Math.sin(angle1) - y;
+  if (dx1 * dx1 + dy1 * dy1 <= d * d)
+    return true;
+
+  var angle2 = modulo(deg2rad(endangle), 2 * Math.PI);
+  var dx2 = xc + radius * Math.cos(angle2) - x;
+  var dy2 = yc + radius * Math.sin(angle2) - y;
+  if (dx2 * dx2 + dy2 * dy2 <= d * d)
+    return true;
+
+  var angle = modulo(Math.atan2(dy, dx), 2 * Math.PI);
+  if (angle1 > angle2)
+    return (angle >= angle2 || angle <= angle1);
+  else
+    return (angle >= angle1 && angle <= angle2);
+}
+
+function pointWithinPad(x, y, pad) {
+  var v = [x - pad.pos[0], y - pad.pos[1]];
+  v = rotateVector(v, -pad.angle);
+  if (pad.offset) {
+    v[0] -= pad.offset[0];
+    v[1] -= pad.offset[1];
+  }
+  return emptyContext2d.isPointInPath(getCachedPadPath(pad), ...v);
+}
+
+function netHitScan(layer, x, y) {
+  // Check track segments
+  if (settings.renderTracks && pcbdata.tracks) {
+    for (var track of pcbdata.tracks[layer]) {
+      if ('radius' in track) {
+        if (pointWithinDistanceToArc(x, y, ...track.center, track.radius, track.startangle, track.endangle, track.width / 2)) {
+          return track.net;
+        }
+      } else {
+        if (pointWithinDistanceToSegment(x, y, ...track.start, ...track.end, track.width / 2)) {
+          return track.net;
+        }
+      }
+    }
+  }
+  // Check pads
+  if (settings.renderPads) {
+    for (var footprint of pcbdata.footprints) {
+      for (var pad of footprint.pads) {
+        if (pad.layers.includes(layer) && pointWithinPad(x, y, pad)) {
+          return pad.net;
+        }
+      }
+    }
+  }
+  return null;
+}
+
+function pointWithinFootprintBbox(x, y, bbox) {
+  var v = [x - bbox.pos[0], y - bbox.pos[1]];
+  v = rotateVector(v, bbox.angle);
+  return bbox.relpos[0] <= v[0] && v[0] <= bbox.relpos[0] + bbox.size[0] &&
+    bbox.relpos[1] <= v[1] && v[1] <= bbox.relpos[1] + bbox.size[1];
+}
+
+function bboxHitScan(layer, x, y) {
+  var result = [];
+  for (var i = 0; i < pcbdata.footprints.length; i++) {
+    var footprint = pcbdata.footprints[i];
+    if (footprint.layer == layer) {
+      if (pointWithinFootprintBbox(x, y, footprint.bbox)) {
+        result.push(i);
+      }
+    }
+  }
+  return result;
+}
+
+function handlePointerDown(e, layerdict) {
+  if (e.button != 0 && e.button != 1) {
+    return;
+  }
+  e.preventDefault();
+  e.stopPropagation();
+
+  if (!e.hasOwnProperty("offsetX")) {
+    // The polyfill doesn't set this properly
+    e.offsetX = e.pageX - e.currentTarget.offsetLeft;
+    e.offsetY = e.pageY - e.currentTarget.offsetTop;
+  }
+
+  layerdict.pointerStates[e.pointerId] = {
+    distanceTravelled: 0,
+    lastX: e.offsetX,
+    lastY: e.offsetY,
+    downTime: Date.now(),
+  };
+}
+
+function handleMouseClick(e, layerdict) {
+  if (!e.hasOwnProperty("offsetX")) {
+    // The polyfill doesn't set this properly
+    e.offsetX = e.pageX - e.currentTarget.offsetLeft;
+    e.offsetY = e.pageY - e.currentTarget.offsetTop;
+  }
+
+  var x = e.offsetX;
+  var y = e.offsetY;
+  var t = layerdict.transform;
+  if (layerdict.layer == "B") {
+    x = (devicePixelRatio * x / t.zoom - t.panx + t.x) / -t.s;
+  } else {
+    x = (devicePixelRatio * x / t.zoom - t.panx - t.x) / t.s;
+  }
+  y = (devicePixelRatio * y / t.zoom - t.y - t.pany) / t.s;
+  var v = rotateVector([x, y], -settings.boardRotation);
+  if ("nets" in pcbdata) {
+    var net = netHitScan(layerdict.layer, ...v);
+    if (net !== highlightedNet) {
+      netClicked(net);
+    }
+  }
+  if (highlightedNet === null) {
+    var footprints = bboxHitScan(layerdict.layer, ...v);
+    if (footprints.length > 0) {
+      footprintsClicked(footprints);
+    }
+  }
+}
+
+function handlePointerLeave(e, layerdict) {
+  e.preventDefault();
+  e.stopPropagation();
+
+  if (!settings.redrawOnDrag) {
+    redrawCanvas(layerdict);
+  }
+
+  delete layerdict.pointerStates[e.pointerId];
+}
+
+function resetTransform(layerdict) {
+  layerdict.transform.panx = 0;
+  layerdict.transform.pany = 0;
+  layerdict.transform.zoom = 1;
+  redrawCanvas(layerdict);
+}
+
+function handlePointerUp(e, layerdict) {
+  if (!e.hasOwnProperty("offsetX")) {
+    // The polyfill doesn't set this properly
+    e.offsetX = e.pageX - e.currentTarget.offsetLeft;
+    e.offsetY = e.pageY - e.currentTarget.offsetTop;
+  }
+
+  e.preventDefault();
+  e.stopPropagation();
+
+  if (e.button == 2) {
+    // Reset pan and zoom on right click.
+    resetTransform(layerdict);
+    layerdict.anotherPointerTapped = false;
+    return;
+  }
+
+  // We haven't necessarily had a pointermove event since the interaction started, so make sure we update this now
+  var ptr = layerdict.pointerStates[e.pointerId];
+  ptr.distanceTravelled += Math.abs(e.offsetX - ptr.lastX) + Math.abs(e.offsetY - ptr.lastY);
+
+  if (e.button == 0 && ptr.distanceTravelled < 10 && Date.now() - ptr.downTime <= 500) {
+    if (Object.keys(layerdict.pointerStates).length == 1) {
+      if (layerdict.anotherPointerTapped) {
+        // This is the second pointer coming off of a two-finger tap
+        resetTransform(layerdict);
+      } else {
+        // This is just a regular tap
+        handleMouseClick(e, layerdict);
+      }
+      layerdict.anotherPointerTapped = false;
+    } else {
+      // This is the first finger coming off of what could become a two-finger tap
+      layerdict.anotherPointerTapped = true;
+    }
+  } else {
+    if (!settings.redrawOnDrag) {
+      redrawCanvas(layerdict);
+    }
+    layerdict.anotherPointerTapped = false;
+  }
+
+  delete layerdict.pointerStates[e.pointerId];
+}
+
+function handlePointerMove(e, layerdict) {
+  if (!layerdict.pointerStates.hasOwnProperty(e.pointerId)) {
+    return;
+  }
+  e.preventDefault();
+  e.stopPropagation();
+
+  if (!e.hasOwnProperty("offsetX")) {
+    // The polyfill doesn't set this properly
+    e.offsetX = e.pageX - e.currentTarget.offsetLeft;
+    e.offsetY = e.pageY - e.currentTarget.offsetTop;
+  }
+
+  var thisPtr = layerdict.pointerStates[e.pointerId];
+
+  var dx = e.offsetX - thisPtr.lastX;
+  var dy = e.offsetY - thisPtr.lastY;
+
+  // If this number is low on pointer up, we count the action as a click
+  thisPtr.distanceTravelled += Math.abs(dx) + Math.abs(dy);
+
+  if (Object.keys(layerdict.pointerStates).length == 1) {
+    // This is a simple drag
+    layerdict.transform.panx += devicePixelRatio * dx / layerdict.transform.zoom;
+    layerdict.transform.pany += devicePixelRatio * dy / layerdict.transform.zoom;
+  } else if (Object.keys(layerdict.pointerStates).length == 2) {
+    var otherPtr = Object.values(layerdict.pointerStates).filter((ptr) => ptr != thisPtr)[0];
+
+    var oldDist = Math.sqrt(Math.pow(thisPtr.lastX - otherPtr.lastX, 2) + Math.pow(thisPtr.lastY - otherPtr.lastY, 2));
+    var newDist = Math.sqrt(Math.pow(e.offsetX - otherPtr.lastX, 2) + Math.pow(e.offsetY - otherPtr.lastY, 2));
+
+    var scaleFactor = newDist / oldDist;
+
+    if (scaleFactor != NaN) {
+      layerdict.transform.zoom *= scaleFactor;
+
+      var zoomd = (1 - scaleFactor) / layerdict.transform.zoom;
+      layerdict.transform.panx += devicePixelRatio * otherPtr.lastX * zoomd;
+      layerdict.transform.pany += devicePixelRatio * otherPtr.lastY * zoomd;
+    }
+  }
+
+  thisPtr.lastX = e.offsetX;
+  thisPtr.lastY = e.offsetY;
+
+  if (settings.redrawOnDrag) {
+    redrawCanvas(layerdict);
+  }
+}
+
+function handleMouseWheel(e, layerdict) {
+  e.preventDefault();
+  e.stopPropagation();
+  var t = layerdict.transform;
+  var wheeldelta = e.deltaY;
+  if (e.deltaMode == 1) {
+    // FF only, scroll by lines
+    wheeldelta *= 30;
+  } else if (e.deltaMode == 2) {
+    wheeldelta *= 300;
+  }
+  var m = Math.pow(1.1, -wheeldelta / 40);
+  // Limit amount of zoom per tick.
+  if (m > 2) {
+    m = 2;
+  } else if (m < 0.5) {
+    m = 0.5;
+  }
+  t.zoom *= m;
+  var zoomd = (1 - m) / t.zoom;
+  t.panx += devicePixelRatio * e.offsetX * zoomd;
+  t.pany += devicePixelRatio * e.offsetY * zoomd;
+  redrawCanvas(layerdict);
+}
+
+function addMouseHandlers(div, layerdict) {
+  div.addEventListener("pointerdown", function(e) {
+    handlePointerDown(e, layerdict);
+  });
+  div.addEventListener("pointermove", function(e) {
+    handlePointerMove(e, layerdict);
+  });
+  div.addEventListener("pointerup", function(e) {
+    handlePointerUp(e, layerdict);
+  });
+  var pointerleave = function(e) {
+    handlePointerLeave(e, layerdict);
+  }
+  div.addEventListener("pointercancel", pointerleave);
+  div.addEventListener("pointerleave", pointerleave);
+  div.addEventListener("pointerout", pointerleave);
+
+  div.onwheel = function(e) {
+    handleMouseWheel(e, layerdict);
+  }
+  for (var element of [div, layerdict.bg, layerdict.fab, layerdict.silk, layerdict.highlight]) {
+    element.addEventListener("contextmenu", function(e) {
+      e.preventDefault();
+    }, false);
+  }
+}
+
+function setRedrawOnDrag(value) {
+  settings.redrawOnDrag = value;
+  writeStorage("redrawOnDrag", value);
+}
+
+function setBoardRotation(value) {
+  settings.boardRotation = value * 5;
+  writeStorage("boardRotation", settings.boardRotation);
+  document.getElementById("rotationDegree").textContent = settings.boardRotation;
+  resizeAll();
+}
+
+function initRender() {
+  allcanvas = {
+    front: {
+      transform: {
+        x: 0,
+        y: 0,
+        s: 1,
+        panx: 0,
+        pany: 0,
+        zoom: 1,
+      },
+      pointerStates: {},
+      anotherPointerTapped: false,
+      bg: document.getElementById("F_bg"),
+      fab: document.getElementById("F_fab"),
+      silk: document.getElementById("F_slk"),
+      highlight: document.getElementById("F_hl"),
+      layer: "F",
+    },
+    back: {
+      transform: {
+        x: 0,
+        y: 0,
+        s: 1,
+        panx: 0,
+        pany: 0,
+        zoom: 1,
+      },
+      pointerStates: {},
+      anotherPointerTapped: false,
+      bg: document.getElementById("B_bg"),
+      fab: document.getElementById("B_fab"),
+      silk: document.getElementById("B_slk"),
+      highlight: document.getElementById("B_hl"),
+      layer: "B",
+    }
+  };
+  addMouseHandlers(document.getElementById("frontcanvas"), allcanvas.front);
+  addMouseHandlers(document.getElementById("backcanvas"), allcanvas.back);
+}
+
+///////////////////////////////////////////////
+
+///////////////////////////////////////////////
+/*
+ * Table reordering via Drag'n'Drop
+ * Inspired by: https://htmldom.dev/drag-and-drop-table-column
+ */
+
+function setBomHandlers() {
+
+  const bom = document.getElementById('bomtable');
+
+  let dragName;
+  let placeHolderElements;
+  let draggingElement;
+  let forcePopulation;
+  let xOffset;
+  let yOffset;
+  let wasDragged;
+
+  const mouseUpHandler = function(e) {
+    // Delete dragging element
+    draggingElement.remove();
+
+    // Make BOM selectable again
+    bom.style.removeProperty("userSelect");
+
+    // Remove listeners
+    document.removeEventListener('mousemove', mouseMoveHandler);
+    document.removeEventListener('mouseup', mouseUpHandler);
+
+    if (wasDragged) {
+      // Redraw whole BOM
+      populateBomTable();
+    }
+  }
+
+  const mouseMoveHandler = function(e) {
+    // Notice the dragging
+    wasDragged = true;
+
+    // Make the dragged element visible
+    draggingElement.style.removeProperty("display");
+
+    // Set elements position to mouse position
+    draggingElement.style.left = `${e.screenX - xOffset}px`;
+    draggingElement.style.top = `${e.screenY - yOffset}px`;
+
+    // Forced redrawing of BOM table
+    if (forcePopulation) {
+      forcePopulation = false;
+      // Copy array
+      phe = Array.from(placeHolderElements);
+      // populate BOM table again
+      populateBomHeader(dragName, phe);
+      populateBomBody(dragName, phe);
+    }
+
+    // Set up array of hidden columns
+    var hiddenColumns = Array.from(settings.hiddenColumns);
+    // In the ungrouped mode, quantity don't exist
+    if (settings.bommode === "ungrouped")
+      hiddenColumns.push("Quantity");
+    // If no checkbox fields can be found, we consider them hidden
+    if (settings.checkboxes.length == 0)
+      hiddenColumns.push("checkboxes");
+
+    // Get table headers and group them into checkboxes, extrafields and normal headers
+    const bh = document.getElementById("bomhead");
+    headers = Array.from(bh.querySelectorAll("th"))
+    headers.shift() // numCol is not part of the columnOrder
+    headerGroups = []
+    lastCompoundClass = null;
+    for (i = 0; i < settings.columnOrder.length; i++) {
+      cElem = settings.columnOrder[i];
+      if (hiddenColumns.includes(cElem)) {
+        // Hidden columns appear as a dummy element
+        headerGroups.push([]);
+        continue;
+      }
+      elem = headers.filter(e => getColumnOrderName(e) === cElem)[0];
+      if (elem.classList.contains("bom-checkbox")) {
+        if (lastCompoundClass === "bom-checkbox") {
+          cbGroup = headerGroups.pop();
+          cbGroup.push(elem);
+          headerGroups.push(cbGroup);
+        } else {
+          lastCompoundClass = "bom-checkbox";
+          headerGroups.push([elem])
+        }
+      } else {
+        headerGroups.push([elem])
+      }
+    }
+
+    // Copy settings.columnOrder
+    var columns = Array.from(settings.columnOrder)
+
+    // Set up array with indices of hidden columns
+    var hiddenIndices = hiddenColumns.map(e => settings.columnOrder.indexOf(e));
+    var dragIndex = columns.indexOf(dragName);
+    var swapIndex = dragIndex;
+    var swapDone = false;
+
+    // Check if the current dragged element is swapable with the left or right element
+    if (dragIndex > 0) {
+      // Get left headers boundingbox
+      swapIndex = dragIndex - 1;
+      while (hiddenIndices.includes(swapIndex) && swapIndex > 0)
+        swapIndex--;
+      if (!hiddenIndices.includes(swapIndex)) {
+        box = getBoundingClientRectFromMultiple(headerGroups[swapIndex]);
+        if (e.clientX < box.left + window.scrollX + (box.width / 2)) {
+          swapElement = columns[dragIndex];
+          columns.splice(dragIndex, 1);
+          columns.splice(swapIndex, 0, swapElement);
+          forcePopulation = true;
+          swapDone = true;
+        }
+      }
+    }
+    if ((!swapDone) && dragIndex < headerGroups.length - 1) {
+      // Get right headers boundingbox
+      swapIndex = dragIndex + 1;
+      while (hiddenIndices.includes(swapIndex))
+        swapIndex++;
+      if (swapIndex < headerGroups.length) {
+        box = getBoundingClientRectFromMultiple(headerGroups[swapIndex]);
+        if (e.clientX > box.left + window.scrollX + (box.width / 2)) {
+          swapElement = columns[dragIndex];
+          columns.splice(dragIndex, 1);
+          columns.splice(swapIndex, 0, swapElement);
+          forcePopulation = true;
+          swapDone = true;
+        }
+      }
+    }
+
+    // Write back change to storage
+    if (swapDone) {
+      settings.columnOrder = columns
+      writeStorage("columnOrder", JSON.stringify(columns));
+    }
+
+  }
+
+  const mouseDownHandler = function(e) {
+    var target = e.target;
+    if (target.tagName.toLowerCase() != "td")
+      target = target.parentElement;
+
+    // Used to check if a dragging has ever happened
+    wasDragged = false;
+
+    // Create new element which will be displayed as the dragged column
+    draggingElement = document.createElement("div")
+    draggingElement.classList.add("dragging");
+    draggingElement.style.display = "none";
+    draggingElement.style.position = "absolute";
+    draggingElement.style.overflow = "hidden";
+
+    // Get bomhead and bombody elements
+    const bh = document.getElementById("bomhead");
+    const bb = document.getElementById("bombody");
+
+    // Get all compound headers for the current column
+    var compoundHeaders;
+    if (target.classList.contains("bom-checkbox")) {
+      compoundHeaders = Array.from(bh.querySelectorAll("th.bom-checkbox"));
+    } else {
+      compoundHeaders = [target];
+    }
+
+    // Create new table which will display the column
+    var newTable = document.createElement("table");
+    newTable.classList.add("bom");
+    newTable.style.background = "white";
+    draggingElement.append(newTable);
+
+    // Create new header element
+    var newHeader = document.createElement("thead");
+    newTable.append(newHeader);
+
+    // Set up array for storing all placeholder elements
+    placeHolderElements = [];
+
+    // Add all compound headers to the new thead element and placeholders
+    compoundHeaders.forEach(function(h) {
+      clone = cloneElementWithDimensions(h);
+      newHeader.append(clone);
+      placeHolderElements.push(clone);
+    });
+
+    // Create new body element
+    var newBody = document.createElement("tbody");
+    newTable.append(newBody);
+
+    // Get indices for compound headers
+    var idxs = compoundHeaders.map(e => getBomTableHeaderIndex(e));
+
+    // For each row in the BOM body...
+    var rows = bb.querySelectorAll("tr");
+    rows.forEach(function(row) {
+      // ..get the cells for the compound column
+      const tds = row.querySelectorAll("td");
+      var copytds = idxs.map(i => tds[i]);
+      // Add them to the new element and the placeholders
+      var newRow = document.createElement("tr");
+      copytds.forEach(function(td) {
+        clone = cloneElementWithDimensions(td);
+        newRow.append(clone);
+        placeHolderElements.push(clone);
+      });
+      newBody.append(newRow);
+    });
+
+    // Compute width for compound header
+    var width = compoundHeaders.reduce((acc, x) => acc + x.clientWidth, 0);
+    draggingElement.style.width = `${width}px`;
+
+    // Insert the new dragging element and disable selection on BOM
+    bom.insertBefore(draggingElement, null);
+    bom.style.userSelect = "none";
+
+    // Determine the mouse position offset
+    xOffset = e.screenX - compoundHeaders.reduce((acc, x) => Math.min(acc, x.offsetLeft), compoundHeaders[0].offsetLeft);
+    yOffset = e.screenY - compoundHeaders[0].offsetTop;
+
+    // Get name for the column in settings.columnOrder
+    dragName = getColumnOrderName(target);
+
+    // Change text and class for placeholder elements
+    placeHolderElements = placeHolderElements.map(function(e) {
+      newElem = cloneElementWithDimensions(e);
+      newElem.textContent = "";
+      newElem.classList.add("placeholder");
+      return newElem;
+    });
+
+    // On next mouse move, the whole BOM needs to be redrawn to show the placeholders
+    forcePopulation = true;
+
+    // Add listeners for move and up on mouse
+    document.addEventListener('mousemove', mouseMoveHandler);
+    document.addEventListener('mouseup', mouseUpHandler);
+  }
+
+  // In netlist mode, there is nothing to reorder
+  if (settings.bommode === "netlist")
+    return;
+
+  // Add mouseDownHandler to every column except the numCol
+  bom.querySelectorAll("th")
+    .forEach(function(head) {
+      if (!head.classList.contains("numCol")) {
+        head.onmousedown = mouseDownHandler;
+      }
+    });
+
+}
+
+function getBoundingClientRectFromMultiple(elements) {
+  var elems = Array.from(elements);
+
+  if (elems.length == 0)
+    return null;
+
+  var box = elems.shift()
+    .getBoundingClientRect();
+
+  elems.forEach(function(elem) {
+    var elembox = elem.getBoundingClientRect();
+    box.left = Math.min(elembox.left, box.left);
+    box.top = Math.min(elembox.top, box.top);
+    box.width += elembox.width;
+    box.height = Math.max(elembox.height, box.height);
+  });
+
+  return box;
+}
+
+function cloneElementWithDimensions(elem) {
+  var newElem = elem.cloneNode(true);
+  newElem.style.height = window.getComputedStyle(elem).height;
+  newElem.style.width = window.getComputedStyle(elem).width;
+  return newElem;
+}
+
+function getBomTableHeaderIndex(elem) {
+  const bh = document.getElementById('bomhead');
+  const ths = Array.from(bh.querySelectorAll("th"));
+  return ths.indexOf(elem);
+}
+
+function getColumnOrderName(elem) {
+  var cname = elem.getAttribute("col_name");
+  if (cname === "bom-checkbox")
+    return "checkboxes";
+  else
+    return cname;
+}
+
+function resizableGrid(tablehead) {
+  var cols = tablehead.firstElementChild.children;
+  var rowWidth = tablehead.offsetWidth;
+
+  for (var i = 1; i < cols.length; i++) {
+    if (cols[i].classList.contains("bom-checkbox"))
+      continue;
+    cols[i].style.width = ((cols[i].clientWidth - paddingDiff(cols[i])) * 100 / rowWidth) + '%';
+  }
+
+  for (var i = 1; i < cols.length - 1; i++) {
+    var div = document.createElement('div');
+    div.className = "column-width-handle";
+    cols[i].appendChild(div);
+    setListeners(div);
+  }
+
+  function setListeners(div) {
+    var startX, curCol, nxtCol, curColWidth, nxtColWidth, rowWidth;
+
+    div.addEventListener('mousedown', function(e) {
+      e.preventDefault();
+      e.stopPropagation();
+
+      curCol = e.target.parentElement;
+      nxtCol = curCol.nextElementSibling;
+      startX = e.pageX;
+
+      var padding = paddingDiff(curCol);
+
+      rowWidth = curCol.parentElement.offsetWidth;
+      curColWidth = curCol.clientWidth - padding;
+      nxtColWidth = nxtCol.clientWidth - padding;
+    });
+
+    document.addEventListener('mousemove', function(e) {
+      if (startX) {
+        var diffX = e.pageX - startX;
+        diffX = -Math.min(-diffX, curColWidth - 20);
+        diffX = Math.min(diffX, nxtColWidth - 20);
+
+        curCol.style.width = ((curColWidth + diffX) * 100 / rowWidth) + '%';
+        nxtCol.style.width = ((nxtColWidth - diffX) * 100 / rowWidth) + '%';
+        console.log(`${curColWidth + nxtColWidth} ${(curColWidth + diffX) * 100 / rowWidth + (nxtColWidth - diffX) * 100 / rowWidth}`);
+      }
+    });
+
+    document.addEventListener('mouseup', function(e) {
+      curCol = undefined;
+      nxtCol = undefined;
+      startX = undefined;
+      nxtColWidth = undefined;
+      curColWidth = undefined
+    });
+  }
+
+  function paddingDiff(col) {
+
+    if (getStyleVal(col, 'box-sizing') == 'border-box') {
+      return 0;
+    }
+
+    var padLeft = getStyleVal(col, 'padding-left');
+    var padRight = getStyleVal(col, 'padding-right');
+    return (parseInt(padLeft) + parseInt(padRight));
+
+  }
+
+  function getStyleVal(elm, css) {
+    return (window.getComputedStyle(elm, null).getPropertyValue(css))
+  }
+}
+
+///////////////////////////////////////////////
+
+///////////////////////////////////////////////
+/* DOM manipulation and misc code */
+
+var bomsplit;
+var canvassplit;
+var initDone = false;
+var bomSortFunction = null;
+var currentSortColumn = null;
+var currentSortOrder = null;
+var currentHighlightedRowId;
+var highlightHandlers = [];
+var footprintIndexToHandler = {};
+var netsToHandler = {};
+var markedFootprints = new Set();
+var highlightedFootprints = [];
+var highlightedNet = null;
+var lastClicked;
+
+function dbg(html) {
+  dbgdiv.innerHTML = html;
+}
+
+function redrawIfInitDone() {
+  if (initDone) {
+    redrawCanvas(allcanvas.front);
+    redrawCanvas(allcanvas.back);
+  }
+}
+
+function padsVisible(value) {
+  writeStorage("padsVisible", value);
+  settings.renderPads = value;
+  redrawIfInitDone();
+}
+
+function referencesVisible(value) {
+  writeStorage("referencesVisible", value);
+  settings.renderReferences = value;
+  redrawIfInitDone();
+}
+
+function valuesVisible(value) {
+  writeStorage("valuesVisible", value);
+  settings.renderValues = value;
+  redrawIfInitDone();
+}
+
+function tracksVisible(value) {
+  writeStorage("tracksVisible", value);
+  settings.renderTracks = value;
+  redrawIfInitDone();
+}
+
+function zonesVisible(value) {
+  writeStorage("zonesVisible", value);
+  settings.renderZones = value;
+  redrawIfInitDone();
+}
+
+function dnpOutline(value) {
+  writeStorage("dnpOutline", value);
+  settings.renderDnpOutline = value;
+  redrawIfInitDone();
+}
+
+function setDarkMode(value) {
+  if (value) {
+    topmostdiv.classList.add("dark");
+  } else {
+    topmostdiv.classList.remove("dark");
+  }
+  writeStorage("darkmode", value);
+  settings.darkMode = value;
+  redrawIfInitDone();
+}
+
+function setShowBOMColumn(field, value) {
+  if (field === "references") {
+    var rl = document.getElementById("reflookup");
+    rl.disabled = !value;
+    if (!value) {
+      rl.value = "";
+      updateRefLookup("");
+    }
+  }
+
+  var n = settings.hiddenColumns.indexOf(field);
+  if (value) {
+    if (n != -1) {
+      settings.hiddenColumns.splice(n, 1);
+    }
+  } else {
+    if (n == -1) {
+      settings.hiddenColumns.push(field);
+    }
+  }
+
+  writeStorage("hiddenColumns", JSON.stringify(settings.hiddenColumns));
+
+  if (initDone) {
+    populateBomTable();
+  }
+
+  redrawIfInitDone();
+}
+
+
+function setFullscreen(value) {
+  if (value) {
+    document.documentElement.requestFullscreen();
+  } else {
+    document.exitFullscreen();
+  }
+}
+
+function fabricationVisible(value) {
+  writeStorage("fabricationVisible", value);
+  settings.renderFabrication = value;
+  redrawIfInitDone();
+}
+
+function silkscreenVisible(value) {
+  writeStorage("silkscreenVisible", value);
+  settings.renderSilkscreen = value;
+  redrawIfInitDone();
+}
+
+function setHighlightPin1(value) {
+  writeStorage("highlightpin1", value);
+  settings.highlightpin1 = value;
+  redrawIfInitDone();
+}
+
+function getStoredCheckboxRefs(checkbox) {
+  function convert(ref) {
+    var intref = parseInt(ref);
+    if (isNaN(intref)) {
+      for (var i = 0; i < pcbdata.footprints.length; i++) {
+        if (pcbdata.footprints[i].ref == ref) {
+          return i;
+        }
+      }
+      return -1;
+    } else {
+      return intref;
+    }
+  }
+  if (!(checkbox in settings.checkboxStoredRefs)) {
+    var val = readStorage("checkbox_" + checkbox);
+    settings.checkboxStoredRefs[checkbox] = val ? val : "";
+  }
+  if (!settings.checkboxStoredRefs[checkbox]) {
+    return new Set();
+  } else {
+    return new Set(settings.checkboxStoredRefs[checkbox].split(",").map(r => convert(r)).filter(a => a >= 0));
+  }
+}
+
+function getCheckboxState(checkbox, references) {
+  var storedRefsSet = getStoredCheckboxRefs(checkbox);
+  var currentRefsSet = new Set(references.map(r => r[1]));
+  // Get difference of current - stored
+  var difference = new Set(currentRefsSet);
+  for (ref of storedRefsSet) {
+    difference.delete(ref);
+  }
+  if (difference.size == 0) {
+    // All the current refs are stored
+    return "checked";
+  } else if (difference.size == currentRefsSet.size) {
+    // None of the current refs are stored
+    return "unchecked";
+  } else {
+    // Some of the refs are stored
+    return "indeterminate";
+  }
+}
+
+function setBomCheckboxState(checkbox, element, references) {
+  var state = getCheckboxState(checkbox, references);
+  element.checked = (state == "checked");
+  element.indeterminate = (state == "indeterminate");
+}
+
+function createCheckboxChangeHandler(checkbox, references, row) {
+  return function () {
+    refsSet = getStoredCheckboxRefs(checkbox);
+    var markWhenChecked = settings.markWhenChecked == checkbox;
+    eventArgs = {
+      checkbox: checkbox,
+      refs: references,
+    }
+    if (this.checked) {
+      // checkbox ticked
+      for (var ref of references) {
+        refsSet.add(ref[1]);
+      }
+      if (markWhenChecked) {
+        row.classList.add("checked");
+        for (var ref of references) {
+          markedFootprints.add(ref[1]);
+        }
+        drawHighlights();
+      }
+      eventArgs.state = 'checked';
+    } else {
+      // checkbox unticked
+      for (var ref of references) {
+        refsSet.delete(ref[1]);
+      }
+      if (markWhenChecked) {
+        row.classList.remove("checked");
+        for (var ref of references) {
+          markedFootprints.delete(ref[1]);
+        }
+        drawHighlights();
+      }
+      eventArgs.state = 'unchecked';
+    }
+    settings.checkboxStoredRefs[checkbox] = [...refsSet].join(",");
+    writeStorage("checkbox_" + checkbox, settings.checkboxStoredRefs[checkbox]);
+    updateCheckboxStats(checkbox);
+    EventHandler.emitEvent(IBOM_EVENT_TYPES.CHECKBOX_CHANGE_EVENT, eventArgs);
+  }
+}
+
+function clearHighlightedFootprints() {
+  if (currentHighlightedRowId) {
+    document.getElementById(currentHighlightedRowId).classList.remove("highlighted");
+    currentHighlightedRowId = null;
+    highlightedFootprints = [];
+    highlightedNet = null;
+  }
+}
+
+function createRowHighlightHandler(rowid, refs, net) {
+  return function () {
+    if (currentHighlightedRowId) {
+      if (currentHighlightedRowId == rowid) {
+        return;
+      }
+      document.getElementById(currentHighlightedRowId).classList.remove("highlighted");
+    }
+    document.getElementById(rowid).classList.add("highlighted");
+    currentHighlightedRowId = rowid;
+    highlightedFootprints = refs ? refs.map(r => r[1]) : [];
+    highlightedNet = net;
+    drawHighlights();
+    EventHandler.emitEvent(
+      IBOM_EVENT_TYPES.HIGHLIGHT_EVENT, {
+      rowid: rowid,
+      refs: refs,
+      net: net
+    });
+  }
+}
+
+function entryMatches(entry) {
+  if (settings.bommode == "netlist") {
+    // entry is just a net name
+    return entry.toLowerCase().indexOf(filter) >= 0;
+  }
+  // check refs
+  if (!settings.hiddenColumns.includes("references")) {
+    for (var ref of entry) {
+      if (ref[0].toLowerCase().indexOf(filter) >= 0) {
+        return true;
+      }
+    }
+  }
+  // check fields
+  for (var i in config.fields) {
+    var f = config.fields[i];
+    if (!settings.hiddenColumns.includes(f)) {
+      for (var ref of entry) {
+        if (pcbdata.bom.fields[ref[1]][i].toLowerCase().indexOf(filter) >= 0) {
+          return true;
+        }
+      }
+    }
+  }
+  return false;
+}
+
+function findRefInEntry(entry) {
+  return entry.filter(r => r[0].toLowerCase() == reflookup);
+}
+
+function highlightFilter(s) {
+  if (!filter) {
+    return s;
+  }
+  var parts = s.toLowerCase().split(filter);
+  if (parts.length == 1) {
+    return s;
+  }
+  var r = "";
+  var pos = 0;
+  for (var i in parts) {
+    if (i > 0) {
+      r += '<mark class="highlight">' +
+        s.substring(pos, pos + filter.length) +
+        '</mark>';
+      pos += filter.length;
+    }
+    r += s.substring(pos, pos + parts[i].length);
+    pos += parts[i].length;
+  }
+  return r;
+}
+
+function checkboxSetUnsetAllHandler(checkboxname) {
+  return function () {
+    var checkboxnum = 0;
+    while (checkboxnum < settings.checkboxes.length &&
+      settings.checkboxes[checkboxnum].toLowerCase() != checkboxname.toLowerCase()) {
+      checkboxnum++;
+    }
+    if (checkboxnum >= settings.checkboxes.length) {
+      return;
+    }
+    var allset = true;
+    var checkbox;
+    var row;
+    for (row of bombody.childNodes) {
+      checkbox = row.childNodes[checkboxnum + 1].childNodes[0];
+      if (!checkbox.checked || checkbox.indeterminate) {
+        allset = false;
+        break;
+      }
+    }
+    for (row of bombody.childNodes) {
+      checkbox = row.childNodes[checkboxnum + 1].childNodes[0];
+      checkbox.checked = !allset;
+      checkbox.indeterminate = false;
+      checkbox.onchange();
+    }
+  }
+}
+
+function createColumnHeader(name, cls, comparator, is_checkbox = false) {
+  var th = document.createElement("TH");
+  th.innerHTML = name;
+  th.classList.add(cls);
+  if (is_checkbox)
+    th.setAttribute("col_name", "bom-checkbox");
+  else
+    th.setAttribute("col_name", name);
+  var span = document.createElement("SPAN");
+  span.classList.add("sortmark");
+  span.classList.add("none");
+  th.appendChild(span);
+  var spacer = document.createElement("div");
+  spacer.className = "column-spacer";
+  th.appendChild(spacer);
+  spacer.onclick = function () {
+    if (currentSortColumn && th !== currentSortColumn) {
+      // Currently sorted by another column
+      currentSortColumn.childNodes[1].classList.remove(currentSortOrder);
+      currentSortColumn.childNodes[1].classList.add("none");
+      currentSortColumn = null;
+      currentSortOrder = null;
+    }
+    if (currentSortColumn && th === currentSortColumn) {
+      // Already sorted by this column
+      if (currentSortOrder == "asc") {
+        // Sort by this column, descending order
+        bomSortFunction = function (a, b) {
+          return -comparator(a, b);
+        }
+        currentSortColumn.childNodes[1].classList.remove("asc");
+        currentSortColumn.childNodes[1].classList.add("desc");
+        currentSortOrder = "desc";
+      } else {
+        // Unsort
+        bomSortFunction = null;
+        currentSortColumn.childNodes[1].classList.remove("desc");
+        currentSortColumn.childNodes[1].classList.add("none");
+        currentSortColumn = null;
+        currentSortOrder = null;
+      }
+    } else {
+      // Sort by this column, ascending order
+      bomSortFunction = comparator;
+      currentSortColumn = th;
+      currentSortColumn.childNodes[1].classList.remove("none");
+      currentSortColumn.childNodes[1].classList.add("asc");
+      currentSortOrder = "asc";
+    }
+    populateBomBody();
+  }
+  if (is_checkbox) {
+    spacer.onclick = fancyDblClickHandler(
+      spacer, spacer.onclick, checkboxSetUnsetAllHandler(name));
+  }
+  return th;
+}
+
+function populateBomHeader(placeHolderColumn = null, placeHolderElements = null) {
+  while (bomhead.firstChild) {
+    bomhead.removeChild(bomhead.firstChild);
+  }
+  var tr = document.createElement("TR");
+  var th = document.createElement("TH");
+  th.classList.add("numCol");
+
+  var vismenu = document.createElement("div");
+  vismenu.id = "vismenu";
+  vismenu.classList.add("menu");
+
+  var visbutton = document.createElement("div");
+  visbutton.classList.add("visbtn");
+  visbutton.classList.add("hideonprint");
+
+  var viscontent = document.createElement("div");
+  viscontent.classList.add("menu-content");
+  viscontent.id = "vismenu-content";
+
+  settings.columnOrder.forEach(column => {
+    if (typeof column !== "string")
+      return;
+
+    // Skip empty columns
+    if (column === "checkboxes" && settings.checkboxes.length == 0)
+      return;
+    else if (column === "Quantity" && settings.bommode == "ungrouped")
+      return;
+
+    var label = document.createElement("label");
+    label.classList.add("menu-label");
+
+    var input = document.createElement("input");
+    input.classList.add("visibility_checkbox");
+    input.type = "checkbox";
+    input.onchange = function (e) {
+      setShowBOMColumn(column, e.target.checked)
+    };
+    input.checked = !(settings.hiddenColumns.includes(column));
+
+    label.appendChild(input);
+    if (column.length > 0)
+      label.append(column[0].toUpperCase() + column.slice(1));
+
+    viscontent.appendChild(label);
+  });
+
+  viscontent.childNodes[0].classList.add("menu-label-top");
+
+  vismenu.appendChild(visbutton);
+  if (settings.bommode != "netlist") {
+    vismenu.appendChild(viscontent);
+    th.appendChild(vismenu);
+  }
+  tr.appendChild(th);
+
+  var checkboxCompareClosure = function (checkbox) {
+    return (a, b) => {
+      var stateA = getCheckboxState(checkbox, a);
+      var stateB = getCheckboxState(checkbox, b);
+      if (stateA > stateB) return -1;
+      if (stateA < stateB) return 1;
+      return 0;
+    }
+  }
+  var stringFieldCompareClosure = function (fieldIndex) {
+    return (a, b) => {
+      var fa = pcbdata.bom.fields[a[0][1]][fieldIndex];
+      var fb = pcbdata.bom.fields[b[0][1]][fieldIndex];
+      if (fa != fb) return fa > fb ? 1 : -1;
+      else return 0;
+    }
+  }
+  var referenceRegex = /(?<prefix>[^0-9]+)(?<number>[0-9]+)/;
+  var compareRefs = (a, b) => {
+    var ra = referenceRegex.exec(a);
+    var rb = referenceRegex.exec(b);
+    if (ra === null || rb === null) {
+      if (a != b) return a > b ? 1 : -1;
+      return 0;
+    } else {
+      if (ra.groups.prefix != rb.groups.prefix) {
+        return ra.groups.prefix > rb.groups.prefix ? 1 : -1;
+      }
+      if (ra.groups.number != rb.groups.number) {
+        return parseInt(ra.groups.number) > parseInt(rb.groups.number) ? 1 : -1;
+      }
+      return 0;
+    }
+  }
+  if (settings.bommode == "netlist") {
+    th = createColumnHeader("Net name", "bom-netname", (a, b) => {
+      if (a > b) return -1;
+      if (a < b) return 1;
+      return 0;
+    });
+    tr.appendChild(th);
+  } else {
+    // Filter hidden columns
+    var columns = settings.columnOrder.filter(e => !settings.hiddenColumns.includes(e));
+    var valueIndex = config.fields.indexOf("Value");
+    var footprintIndex = config.fields.indexOf("Footprint");
+    columns.forEach((column) => {
+      if (column === placeHolderColumn) {
+        var n = 1;
+        if (column === "checkboxes")
+          n = settings.checkboxes.length;
+        for (i = 0; i < n; i++) {
+          td = placeHolderElements.shift();
+          tr.appendChild(td);
+        }
+        return;
+      } else if (column === "checkboxes") {
+        for (var checkbox of settings.checkboxes) {
+          th = createColumnHeader(
+            checkbox, "bom-checkbox", checkboxCompareClosure(checkbox), true);
+          tr.appendChild(th);
+        }
+      } else if (column === "References") {
+        tr.appendChild(createColumnHeader("References", "references", (a, b) => {
+          var i = 0;
+          while (i < a.length && i < b.length) {
+            if (a[i] != b[i]) return compareRefs(a[i][0], b[i][0]);
+            i++;
+          }
+          return a.length - b.length;
+        }));
+      } else if (column === "Value") {
+        tr.appendChild(createColumnHeader("Value", "value", (a, b) => {
+          var ra = a[0][1], rb = b[0][1];
+          return valueCompare(
+            pcbdata.bom.parsedValues[ra], pcbdata.bom.parsedValues[rb],
+            pcbdata.bom.fields[ra][valueIndex], pcbdata.bom.fields[rb][valueIndex]);
+        }));
+        return;
+      } else if (column === "Footprint") {
+        tr.appendChild(createColumnHeader(
+          "Footprint", "footprint", stringFieldCompareClosure(footprintIndex)));
+      } else if (column === "Quantity" && settings.bommode == "grouped") {
+        tr.appendChild(createColumnHeader("Quantity", "quantity", (a, b) => {
+          return a.length - b.length;
+        }));
+      } else {
+        // Other fields
+        var i = config.fields.indexOf(column);
+        if (i < 0)
+          return;
+        tr.appendChild(createColumnHeader(
+          column, `field${i + 1}`, stringFieldCompareClosure(i)));
+      }
+    });
+  }
+  bomhead.appendChild(tr);
+}
+
+function populateBomBody(placeholderColumn = null, placeHolderElements = null) {
+  while (bom.firstChild) {
+    bom.removeChild(bom.firstChild);
+  }
+  highlightHandlers = [];
+  footprintIndexToHandler = {};
+  netsToHandler = {};
+  currentHighlightedRowId = null;
+  var first = true;
+  if (settings.bommode == "netlist") {
+    bomtable = pcbdata.nets.slice();
+  } else {
+    switch (settings.canvaslayout) {
+      case 'F':
+        bomtable = pcbdata.bom.F.slice();
+        break;
+      case 'FB':
+        bomtable = pcbdata.bom.both.slice();
+        break;
+      case 'B':
+        bomtable = pcbdata.bom.B.slice();
+        break;
+    }
+    if (settings.bommode == "ungrouped") {
+      // expand bom table
+      expandedTable = []
+      for (var bomentry of bomtable) {
+        for (var ref of bomentry) {
+          expandedTable.push([ref]);
+        }
+      }
+      bomtable = expandedTable;
+    }
+  }
+  if (bomSortFunction) {
+    bomtable = bomtable.sort(bomSortFunction);
+  }
+  for (var i in bomtable) {
+    var bomentry = bomtable[i];
+    if (filter && !entryMatches(bomentry)) {
+      continue;
+    }
+    var references = null;
+    var netname = null;
+    var tr = document.createElement("TR");
+    var td = document.createElement("TD");
+    var rownum = +i + 1;
+    tr.id = "bomrow" + rownum;
+    td.textContent = rownum;
+    tr.appendChild(td);
+    if (settings.bommode == "netlist") {
+      netname = bomentry;
+      td = document.createElement("TD");
+      td.innerHTML = highlightFilter(netname ? netname : "&lt;no net&gt;");
+      tr.appendChild(td);
+    } else {
+      if (reflookup) {
+        references = findRefInEntry(bomentry);
+        if (references.length == 0) {
+          continue;
+        }
+      } else {
+        references = bomentry;
+      }
+      // Filter hidden columns
+      var columns = settings.columnOrder.filter(e => !settings.hiddenColumns.includes(e));
+      columns.forEach((column) => {
+        if (column === placeholderColumn) {
+          var n = 1;
+          if (column === "checkboxes")
+            n = settings.checkboxes.length;
+          for (i = 0; i < n; i++) {
+            td = placeHolderElements.shift();
+            tr.appendChild(td);
+          }
+          return;
+        } else if (column === "checkboxes") {
+          for (var checkbox of settings.checkboxes) {
+            if (checkbox) {
+              td = document.createElement("TD");
+              var input = document.createElement("input");
+              input.type = "checkbox";
+              input.onchange = createCheckboxChangeHandler(checkbox, references, tr);
+              setBomCheckboxState(checkbox, input, references);
+              if (input.checked && settings.markWhenChecked == checkbox) {
+                tr.classList.add("checked");
+              }
+              td.appendChild(input);
+              tr.appendChild(td);
+            }
+          }
+        } else if (column === "References") {
+          td = document.createElement("TD");
+          td.innerHTML = highlightFilter(references.map(r => r[0]).join(", "));
+          tr.appendChild(td);
+        } else if (column === "Quantity" && settings.bommode == "grouped") {
+          // Quantity
+          td = document.createElement("TD");
+          td.textContent = references.length;
+          tr.appendChild(td);
+        } else {
+          // All the other fields
+          var field_index = config.fields.indexOf(column)
+          if (field_index < 0)
+            return;
+          var valueSet = new Set();
+          references.map(r => r[1]).forEach((id) => valueSet.add(pcbdata.bom.fields[id][field_index]));
+          td = document.createElement("TD");
+          td.innerHTML = highlightFilter(Array.from(valueSet).join(", "));
+          tr.appendChild(td);
+        }
+      });
+    }
+    bom.appendChild(tr);
+    var handler = createRowHighlightHandler(tr.id, references, netname);
+    tr.onmousemove = handler;
+    highlightHandlers.push({
+      id: tr.id,
+      handler: handler,
+    });
+    if (references !== null) {
+      for (var refIndex of references.map(r => r[1])) {
+        footprintIndexToHandler[refIndex] = handler;
+      }
+    }
+    if (netname !== null) {
+      netsToHandler[netname] = handler;
+    }
+    if ((filter || reflookup) && first) {
+      handler();
+      first = false;
+    }
+  }
+  EventHandler.emitEvent(
+    IBOM_EVENT_TYPES.BOM_BODY_CHANGE_EVENT, {
+    filter: filter,
+    reflookup: reflookup,
+    checkboxes: settings.checkboxes,
+    bommode: settings.bommode,
+  });
+}
+
+function highlightPreviousRow() {
+  if (!currentHighlightedRowId) {
+    highlightHandlers[highlightHandlers.length - 1].handler();
+  } else {
+    if (highlightHandlers.length > 1 &&
+      highlightHandlers[0].id == currentHighlightedRowId) {
+      highlightHandlers[highlightHandlers.length - 1].handler();
+    } else {
+      for (var i = 0; i < highlightHandlers.length - 1; i++) {
+        if (highlightHandlers[i + 1].id == currentHighlightedRowId) {
+          highlightHandlers[i].handler();
+          break;
+        }
+      }
+    }
+  }
+  smoothScrollToRow(currentHighlightedRowId);
+}
+
+function highlightNextRow() {
+  if (!currentHighlightedRowId) {
+    highlightHandlers[0].handler();
+  } else {
+    if (highlightHandlers.length > 1 &&
+      highlightHandlers[highlightHandlers.length - 1].id == currentHighlightedRowId) {
+      highlightHandlers[0].handler();
+    } else {
+      for (var i = 1; i < highlightHandlers.length; i++) {
+        if (highlightHandlers[i - 1].id == currentHighlightedRowId) {
+          highlightHandlers[i].handler();
+          break;
+        }
+      }
+    }
+  }
+  smoothScrollToRow(currentHighlightedRowId);
+}
+
+function populateBomTable() {
+  populateBomHeader();
+  populateBomBody();
+  setBomHandlers();
+  resizableGrid(bomhead);
+}
+
+function footprintsClicked(footprintIndexes) {
+  var lastClickedIndex = footprintIndexes.indexOf(lastClicked);
+  for (var i = 1; i <= footprintIndexes.length; i++) {
+    var refIndex = footprintIndexes[(lastClickedIndex + i) % footprintIndexes.length];
+    if (refIndex in footprintIndexToHandler) {
+      lastClicked = refIndex;
+      footprintIndexToHandler[refIndex]();
+      smoothScrollToRow(currentHighlightedRowId);
+      break;
+    }
+  }
+}
+
+function netClicked(net) {
+  if (net in netsToHandler) {
+    netsToHandler[net]();
+    smoothScrollToRow(currentHighlightedRowId);
+  } else {
+    clearHighlightedFootprints();
+    highlightedNet = net;
+    drawHighlights();
+  }
+}
+
+function updateFilter(input) {
+  filter = input.toLowerCase();
+  populateBomTable();
+}
+
+function updateRefLookup(input) {
+  reflookup = input.toLowerCase();
+  populateBomTable();
+}
+
+function changeCanvasLayout(layout) {
+  document.getElementById("fl-btn").classList.remove("depressed");
+  document.getElementById("fb-btn").classList.remove("depressed");
+  document.getElementById("bl-btn").classList.remove("depressed");
+  switch (layout) {
+    case 'F':
+      document.getElementById("fl-btn").classList.add("depressed");
+      if (settings.bomlayout != "bom-only") {
+        canvassplit.collapse(1);
+      }
+      break;
+    case 'B':
+      document.getElementById("bl-btn").classList.add("depressed");
+      if (settings.bomlayout != "bom-only") {
+        canvassplit.collapse(0);
+      }
+      break;
+    default:
+      document.getElementById("fb-btn").classList.add("depressed");
+      if (settings.bomlayout != "bom-only") {
+        canvassplit.setSizes([50, 50]);
+      }
+  }
+  settings.canvaslayout = layout;
+  writeStorage("canvaslayout", layout);
+  resizeAll();
+  changeBomMode(settings.bommode);
+}
+
+function populateMetadata() {
+  document.getElementById("title").innerHTML = pcbdata.metadata.title;
+  document.getElementById("revision").innerHTML = "Rev: " + pcbdata.metadata.revision;
+  document.getElementById("company").innerHTML = pcbdata.metadata.company;
+  document.getElementById("filedate").innerHTML = pcbdata.metadata.date;
+  if (pcbdata.metadata.title != "") {
+    document.title = pcbdata.metadata.title + " BOM";
+  }
+  // Calculate board stats
+  var fp_f = 0,
+    fp_b = 0,
+    pads_f = 0,
+    pads_b = 0,
+    pads_th = 0;
+  for (var i = 0; i < pcbdata.footprints.length; i++) {
+    if (pcbdata.bom.skipped.includes(i)) continue;
+    var mod = pcbdata.footprints[i];
+    if (mod.layer == "F") {
+      fp_f++;
+    } else {
+      fp_b++;
+    }
+    for (var pad of mod.pads) {
+      if (pad.type == "th") {
+        pads_th++;
+      } else {
+        if (pad.layers.includes("F")) {
+          pads_f++;
+        }
+        if (pad.layers.includes("B")) {
+          pads_b++;
+        }
+      }
+    }
+  }
+  document.getElementById("stats-components-front").innerHTML = fp_f;
+  document.getElementById("stats-components-back").innerHTML = fp_b;
+  document.getElementById("stats-components-total").innerHTML = fp_f + fp_b;
+  document.getElementById("stats-groups-front").innerHTML = pcbdata.bom.F.length;
+  document.getElementById("stats-groups-back").innerHTML = pcbdata.bom.B.length;
+  document.getElementById("stats-groups-total").innerHTML = pcbdata.bom.both.length;
+  document.getElementById("stats-smd-pads-front").innerHTML = pads_f;
+  document.getElementById("stats-smd-pads-back").innerHTML = pads_b;
+  document.getElementById("stats-smd-pads-total").innerHTML = pads_f + pads_b;
+  document.getElementById("stats-th-pads").innerHTML = pads_th;
+  // Update version string
+  document.getElementById("github-link").innerHTML = "InteractiveHtmlBom&nbsp;" +
+    /^v\d+\.\d+/.exec(pcbdata.ibom_version)[0];
+}
+
+function changeBomLayout(layout) {
+  document.getElementById("bom-btn").classList.remove("depressed");
+  document.getElementById("lr-btn").classList.remove("depressed");
+  document.getElementById("tb-btn").classList.remove("depressed");
+  switch (layout) {
+    case 'bom-only':
+      document.getElementById("bom-btn").classList.add("depressed");
+      if (bomsplit) {
+        bomsplit.destroy();
+        bomsplit = null;
+        canvassplit.destroy();
+        canvassplit = null;
+      }
+      document.getElementById("frontcanvas").style.display = "none";
+      document.getElementById("backcanvas").style.display = "none";
+      document.getElementById("bot").style.height = "";
+      break;
+    case 'top-bottom':
+      document.getElementById("tb-btn").classList.add("depressed");
+      document.getElementById("frontcanvas").style.display = "";
+      document.getElementById("backcanvas").style.display = "";
+      document.getElementById("bot").style.height = "calc(100% - 80px)";
+      document.getElementById("bomdiv").classList.remove("split-horizontal");
+      document.getElementById("canvasdiv").classList.remove("split-horizontal");
+      document.getElementById("frontcanvas").classList.add("split-horizontal");
+      document.getElementById("backcanvas").classList.add("split-horizontal");
+      if (bomsplit) {
+        bomsplit.destroy();
+        bomsplit = null;
+        canvassplit.destroy();
+        canvassplit = null;
+      }
+      bomsplit = Split(['#bomdiv', '#canvasdiv'], {
+        sizes: [50, 50],
+        onDragEnd: resizeAll,
+        direction: "vertical",
+        gutterSize: 5
+      });
+      canvassplit = Split(['#frontcanvas', '#backcanvas'], {
+        sizes: [50, 50],
+        gutterSize: 5,
+        onDragEnd: resizeAll
+      });
+      break;
+    case 'left-right':
+      document.getElementById("lr-btn").classList.add("depressed");
+      document.getElementById("frontcanvas").style.display = "";
+      document.getElementById("backcanvas").style.display = "";
+      document.getElementById("bot").style.height = "calc(100% - 80px)";
+      document.getElementById("bomdiv").classList.add("split-horizontal");
+      document.getElementById("canvasdiv").classList.add("split-horizontal");
+      document.getElementById("frontcanvas").classList.remove("split-horizontal");
+      document.getElementById("backcanvas").classList.remove("split-horizontal");
+      if (bomsplit) {
+        bomsplit.destroy();
+        bomsplit = null;
+        canvassplit.destroy();
+        canvassplit = null;
+      }
+      bomsplit = Split(['#bomdiv', '#canvasdiv'], {
+        sizes: [50, 50],
+        onDragEnd: resizeAll,
+        gutterSize: 5
+      });
+      canvassplit = Split(['#frontcanvas', '#backcanvas'], {
+        sizes: [50, 50],
+        gutterSize: 5,
+        direction: "vertical",
+        onDragEnd: resizeAll
+      });
+  }
+  settings.bomlayout = layout;
+  writeStorage("bomlayout", layout);
+  changeCanvasLayout(settings.canvaslayout);
+}
+
+function changeBomMode(mode) {
+  document.getElementById("bom-grouped-btn").classList.remove("depressed");
+  document.getElementById("bom-ungrouped-btn").classList.remove("depressed");
+  document.getElementById("bom-netlist-btn").classList.remove("depressed");
+  var chkbxs = document.getElementsByClassName("visibility_checkbox");
+
+  switch (mode) {
+    case 'grouped':
+      document.getElementById("bom-grouped-btn").classList.add("depressed");
+      for (var i = 0; i < chkbxs.length; i++) {
+        chkbxs[i].disabled = false;
+      }
+      break;
+    case 'ungrouped':
+      document.getElementById("bom-ungrouped-btn").classList.add("depressed");
+      for (var i = 0; i < chkbxs.length; i++) {
+        chkbxs[i].disabled = false;
+      }
+      break;
+    case 'netlist':
+      document.getElementById("bom-netlist-btn").classList.add("depressed");
+      for (var i = 0; i < chkbxs.length; i++) {
+        chkbxs[i].disabled = true;
+      }
+  }
+
+  writeStorage("bommode", mode);
+  if (mode != settings.bommode) {
+    settings.bommode = mode;
+    bomSortFunction = null;
+    currentSortColumn = null;
+    currentSortOrder = null;
+    clearHighlightedFootprints();
+  }
+  populateBomTable();
+}
+
+function focusFilterField() {
+  focusInputField(document.getElementById("filter"));
+}
+
+function focusRefLookupField() {
+  focusInputField(document.getElementById("reflookup"));
+}
+
+function toggleBomCheckbox(bomrowid, checkboxnum) {
+  if (!bomrowid || checkboxnum > settings.checkboxes.length) {
+    return;
+  }
+  var bomrow = document.getElementById(bomrowid);
+  var checkbox = bomrow.childNodes[checkboxnum].childNodes[0];
+  checkbox.checked = !checkbox.checked;
+  checkbox.indeterminate = false;
+  checkbox.onchange();
+}
+
+function checkBomCheckbox(bomrowid, checkboxname) {
+  var checkboxnum = 0;
+  while (checkboxnum < settings.checkboxes.length &&
+    settings.checkboxes[checkboxnum].toLowerCase() != checkboxname.toLowerCase()) {
+    checkboxnum++;
+  }
+  if (!bomrowid || checkboxnum >= settings.checkboxes.length) {
+    return;
+  }
+  var bomrow = document.getElementById(bomrowid);
+  var checkbox = bomrow.childNodes[checkboxnum + 1].childNodes[0];
+  checkbox.checked = true;
+  checkbox.indeterminate = false;
+  checkbox.onchange();
+}
+
+function setBomCheckboxes(value) {
+  writeStorage("bomCheckboxes", value);
+  settings.checkboxes = value.split(",").map((e) => e.trim()).filter((e) => e);
+  prepCheckboxes();
+  populateMarkWhenCheckedOptions();
+  setMarkWhenChecked(settings.markWhenChecked);
+}
+
+function setMarkWhenChecked(value) {
+  writeStorage("markWhenChecked", value);
+  settings.markWhenChecked = value;
+  markedFootprints.clear();
+  for (var ref of (value ? getStoredCheckboxRefs(value) : [])) {
+    markedFootprints.add(ref);
+  }
+  populateBomTable();
+  drawHighlights();
+}
+
+function prepCheckboxes() {
+  var table = document.getElementById("checkbox-stats");
+  while (table.childElementCount > 1) {
+    table.removeChild(table.lastChild);
+  }
+  if (settings.checkboxes.length) {
+    table.style.display = "";
+  } else {
+    table.style.display = "none";
+  }
+  for (var checkbox of settings.checkboxes) {
+    var tr = document.createElement("TR");
+    var td = document.createElement("TD");
+    td.innerHTML = checkbox;
+    tr.appendChild(td);
+    td = document.createElement("TD");
+    td.id = "checkbox-stats-" + checkbox;
+    var progressbar = document.createElement("div");
+    progressbar.classList.add("bar");
+    td.appendChild(progressbar);
+    var text = document.createElement("div");
+    text.classList.add("text");
+    td.appendChild(text);
+    tr.appendChild(td);
+    table.appendChild(tr);
+    updateCheckboxStats(checkbox);
+  }
+}
+
+function populateMarkWhenCheckedOptions() {
+  var container = document.getElementById("markWhenCheckedContainer");
+
+  if (settings.checkboxes.length == 0) {
+    container.parentElement.style.display = "none";
+    return;
+  }
+
+  container.innerHTML = '';
+  container.parentElement.style.display = "inline-block";
+
+  function createOption(name, displayName) {
+    var id = "markWhenChecked-" + name;
+
+    var div = document.createElement("div");
+    div.classList.add("radio-container");
+
+    var input = document.createElement("input");
+    input.type = "radio";
+    input.name = "markWhenChecked";
+    input.value = name;
+    input.id = id;
+    input.onchange = () => setMarkWhenChecked(name);
+    div.appendChild(input);
+
+    // Preserve the selected element when the checkboxes change
+    if (name == settings.markWhenChecked) {
+      input.checked = true;
+    }
+
+    var label = document.createElement("label");
+    label.innerHTML = displayName;
+    label.htmlFor = id;
+    div.appendChild(label);
+
+    container.appendChild(div);
+  }
+  createOption("", "None");
+  for (var checkbox of settings.checkboxes) {
+    createOption(checkbox, checkbox);
+  }
+}
+
+function updateCheckboxStats(checkbox) {
+  var checked = getStoredCheckboxRefs(checkbox).size;
+  var total = pcbdata.footprints.length - pcbdata.bom.skipped.length;
+  var percent = checked * 100.0 / total;
+  var td = document.getElementById("checkbox-stats-" + checkbox);
+  td.firstChild.style.width = percent + "%";
+  td.lastChild.innerHTML = checked + "/" + total + " (" + Math.round(percent) + "%)";
+}
+
+document.onkeydown = function (e) {
+  switch (e.key) {
+    case "n":
+      if (document.activeElement.type == "text") {
+        return;
+      }
+      if (currentHighlightedRowId !== null) {
+        checkBomCheckbox(currentHighlightedRowId, "placed");
+        highlightNextRow();
+        e.preventDefault();
+      }
+      break;
+    case "ArrowUp":
+      highlightPreviousRow();
+      e.preventDefault();
+      break;
+    case "ArrowDown":
+      highlightNextRow();
+      e.preventDefault();
+      break;
+    default:
+      break;
+  }
+  if (e.altKey) {
+    switch (e.key) {
+      case "f":
+        focusFilterField();
+        e.preventDefault();
+        break;
+      case "r":
+        focusRefLookupField();
+        e.preventDefault();
+        break;
+      case "z":
+        changeBomLayout("bom-only");
+        e.preventDefault();
+        break;
+      case "x":
+        changeBomLayout("left-right");
+        e.preventDefault();
+        break;
+      case "c":
+        changeBomLayout("top-bottom");
+        e.preventDefault();
+        break;
+      case "v":
+        changeCanvasLayout("F");
+        e.preventDefault();
+        break;
+      case "b":
+        changeCanvasLayout("FB");
+        e.preventDefault();
+        break;
+      case "n":
+        changeCanvasLayout("B");
+        e.preventDefault();
+        break;
+      default:
+        break;
+    }
+    if (e.key >= '1' && e.key <= '9') {
+      toggleBomCheckbox(currentHighlightedRowId, parseInt(e.key));
+      e.preventDefault();
+    }
+  }
+}
+
+function hideNetlistButton() {
+  document.getElementById("bom-ungrouped-btn").classList.remove("middle-button");
+  document.getElementById("bom-ungrouped-btn").classList.add("right-most-button");
+  document.getElementById("bom-netlist-btn").style.display = "none";
+}
+
+window.onload = function (e) {
+  initUtils();
+  initRender();
+  initStorage();
+  initDefaults();
+  cleanGutters();
+  populateMetadata();
+  dbgdiv = document.getElementById("dbg");
+  bom = document.getElementById("bombody");
+  bomhead = document.getElementById("bomhead");
+  filter = "";
+  reflookup = "";
+  if (!("nets" in pcbdata)) {
+    hideNetlistButton();
+  }
+  initDone = true;
+  setBomCheckboxes(document.getElementById("bomCheckboxes").value);
+  // Triggers render
+  changeBomLayout(settings.bomlayout);
+
+  // Users may leave fullscreen without touching the checkbox. Uncheck.
+  document.addEventListener('fullscreenchange', () => {
+    if (!document.fullscreenElement)
+      document.getElementById('fullscreenCheckbox').checked = false;
+  });
+}
+
+window.onresize = resizeAll;
+window.matchMedia("print").addListener(resizeAll);
+
+///////////////////////////////////////////////
+
+///////////////////////////////////////////////
+
+///////////////////////////////////////////////
+  </script>
+</head>
+
+<body>
+
+<div id="topmostdiv" class="topmostdiv">
+  <div id="top">
+    <div style="float: right; height: 100%;">
+      <div class="hideonprint menu" style="float: right; top: 8px;">
+        <button class="menubtn"></button>
+        <div class="menu-content">
+          <label class="menu-label menu-label-top" style="width: calc(50% - 18px)">
+            <input id="darkmodeCheckbox" type="checkbox" onchange="setDarkMode(this.checked)">
+            Dark mode
+          </label><!-- This comment eats space! All of it!
+          --><label class="menu-label menu-label-top" style="width: calc(50% - 17px); border-left: 0;">
+            <input id="fullscreenCheckbox" type="checkbox" onchange="setFullscreen(this.checked)">
+            Full Screen
+          </label>
+          <label class="menu-label" style="width: calc(50% - 18px)">
+            <input id="fabricationCheckbox" type="checkbox" checked onchange="fabricationVisible(this.checked)">
+            Fab layer
+          </label><!-- This comment eats space! All of it!
+          --><label class="menu-label" style="width: calc(50% - 17px); border-left: 0;">
+            <input id="silkscreenCheckbox" type="checkbox" checked onchange="silkscreenVisible(this.checked)">
+            Silkscreen
+          </label>
+          <label class="menu-label" style="width: calc(50% - 18px)">
+            <input id="referencesCheckbox" type="checkbox" checked onchange="referencesVisible(this.checked)">
+            References
+          </label><!-- This comment eats space! All of it!
+          --><label class="menu-label" style="width: calc(50% - 17px); border-left: 0;">
+            <input id="valuesCheckbox" type="checkbox" checked onchange="valuesVisible(this.checked)">
+            Values
+          </label>
+          <div id="tracksAndZonesCheckboxes">
+            <label class="menu-label" style="width: calc(50% - 18px)">
+              <input id="tracksCheckbox" type="checkbox" checked onchange="tracksVisible(this.checked)">
+              Tracks
+            </label><!-- This comment eats space! All of it!
+            --><label class="menu-label" style="width: calc(50% - 17px); border-left: 0;">
+              <input id="zonesCheckbox" type="checkbox" checked onchange="zonesVisible(this.checked)">
+              Zones
+            </label>
+          </div>
+          <label class="menu-label" style="width: calc(50% - 18px)">
+            <input id="padsCheckbox" type="checkbox" checked onchange="padsVisible(this.checked)">
+            Pads
+          </label><!-- This comment eats space! All of it!
+          --><label class="menu-label" style="width: calc(50% - 17px); border-left: 0;">
+            <input id="dnpOutlineCheckbox" type="checkbox" checked onchange="dnpOutline(this.checked)">
+            DNP outlined
+          </label>
+          <label class="menu-label">
+            <input id="highlightpin1Checkbox" type="checkbox" onchange="setHighlightPin1(this.checked)">
+            Highlight first pin
+          </label>
+          <label class="menu-label">
+            <input id="dragCheckbox" type="checkbox" checked onchange="setRedrawOnDrag(this.checked)">
+            Continuous redraw on drag
+          </label>
+          <label class="menu-label">
+            <span>Board rotation</span>
+            <span style="float: right"><span id="rotationDegree">0</span>&#176;</span>
+            <input id="boardRotation" type="range" min="-36" max="36" value="0" class="slider" oninput="setBoardRotation(this.value)">
+          </label>
+          <label class="menu-label">
+            <div style="margin-left: 5px">Bom checkboxes</div>
+            <input id="bomCheckboxes" class="menu-textbox" type=text
+                   oninput="setBomCheckboxes(this.value)">
+          </label>
+          <label class="menu-label">
+            <div style="margin-left: 5px">Mark when checked</div>
+            <div id="markWhenCheckedContainer"></div>
+          </label>
+          <label class="menu-label">
+            <span class="shameless-plug">
+              <span>Created using</span>
+              <a id="github-link" target="blank" href="https://github.com/openscopeproject/InteractiveHtmlBom">InteractiveHtmlBom</a>
+            </span>
+          </label>
+        </div>
+      </div>
+      <div class="button-container hideonprint"
+           style="float: right; position: relative; top: 8px">
+        <button id="fl-btn" class="left-most-button" onclick="changeCanvasLayout('F')"
+                title="Front only">F
+        </button>
+        <button id="fb-btn" class="middle-button" onclick="changeCanvasLayout('FB')"
+                title="Front and Back">FB
+        </button>
+        <button id="bl-btn" class="right-most-button" onclick="changeCanvasLayout('B')"
+                title="Back only">B
+        </button>
+      </div>
+      <div class="button-container hideonprint"
+           style="float: right; position: relative; top: 8px">
+        <button id="bom-btn" class="left-most-button" onclick="changeBomLayout('bom-only')"
+                title="BOM only"></button>
+        <button id="lr-btn" class="middle-button" onclick="changeBomLayout('left-right')"
+                title="BOM left, drawings right"></button>
+        <button id="tb-btn" class="right-most-button" onclick="changeBomLayout('top-bottom')"
+                title="BOM top, drawings bot"></button>
+      </div>
+      <div class="button-container hideonprint"
+           style="float: right; position: relative; top: 8px">
+        <button id="bom-grouped-btn" class="left-most-button" onclick="changeBomMode('grouped')"
+                title="Grouped BOM"></button>
+        <button id="bom-ungrouped-btn" class="middle-button" onclick="changeBomMode('ungrouped')"
+                title="Ungrouped BOM"></button>
+        <button id="bom-netlist-btn" class="right-most-button" onclick="changeBomMode('netlist')"
+                title="Netlist"></button>
+      </div>
+      <div class="hideonprint menu" style="float: right; top: 8px;">
+        <button class="statsbtn"></button>
+        <div class="menu-content">
+          <table class="stats">
+            <tbody>
+              <tr>
+                <td width="40%">Board stats</td>
+                <td>Front</td>
+                <td>Back</td>
+                <td>Total</td>
+              </tr>
+              <tr>
+                <td>Components</td>
+                <td id="stats-components-front">~</td>
+                <td id="stats-components-back">~</td>
+                <td id="stats-components-total">~</td>
+              </tr>
+              <tr>
+                <td>Groups</td>
+                <td id="stats-groups-front">~</td>
+                <td id="stats-groups-back">~</td>
+                <td id="stats-groups-total">~</td>
+              </tr>
+              <tr>
+                <td>SMD pads</td>
+                <td id="stats-smd-pads-front">~</td>
+                <td id="stats-smd-pads-back">~</td>
+                <td id="stats-smd-pads-total">~</td>
+              </tr>
+              <tr>
+                <td>TH pads</td>
+                <td colspan=3 id="stats-th-pads">~</td>
+              </tr>
+            </tbody>
+          </table>
+          <table class="stats">
+            <col width="40%"/><col />
+            <tbody id="checkbox-stats">
+              <tr>
+                <td colspan=2 style="border-top: 0">Checkboxes</td>
+              </tr>
+            </tbody>
+          </table>
+        </div>
+      </div>
+      <div class="hideonprint menu" style="float: right; top: 8px;">
+        <button class="iobtn"></button>
+        <div class="menu-content">
+          <div class="menu-label menu-label-top">
+            <div style="margin-left: 5px;">Save board image</div>
+            <div class="flexbox">
+              <input id="render-save-width" class="menu-textbox" type="text" value="1000" placeholder="Width"
+                  style="flex-grow: 1; width: 50px;" oninput="validateSaveImgDimension(this)">
+              <span>X</span>
+              <input id="render-save-height" class="menu-textbox" type="text" value="1000" placeholder="Height"
+                  style="flex-grow: 1; width: 50px;" oninput="validateSaveImgDimension(this)">
+            </div>
+            <label>
+              <input id="render-save-transparent" type="checkbox">
+              Transparent background
+            </label>
+            <div class="flexbox">
+              <button class="savebtn" onclick="saveImage('F')">Front</button>
+              <button class="savebtn" onclick="saveImage('B')">Back</button>
+            </div>
+          </div>
+          <div class="menu-label">
+            <span style="margin-left: 5px;">Config and checkbox state</span>
+            <div class="flexbox">
+              <button class="savebtn" onclick="saveSettings()">Export</button>
+              <button class="savebtn" onclick="loadSettings()">Import</button>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+    <div id="fileinfodiv" style="overflow: auto;">
+      <table class="fileinfo">
+        <tbody>
+          <tr>
+            <td id="title" class="title" style="width: 70%">
+              Title
+            </td>
+            <td id="revision" class="title" style="width: 30%">
+              Revision
+            </td>
+          </tr>
+          <tr>
+            <td id="company">
+              Company
+            </td>
+            <td id="filedate">
+              Date
+            </td>
+          </tr>
+        </tbody>
+      </table>
+    </div>
+  </div>
+  <div id="bot" class="split" style="height: calc(100% - 80px)">
+    <div id="bomdiv" class="split split-horizontal">
+      <div style="width: 100%">
+        <input id="reflookup" class="textbox searchbox reflookup hideonprint" type="text" placeholder="Ref lookup"
+               oninput="updateRefLookup(this.value)">
+        <input id="filter" class="textbox searchbox filter hideonprint" type="text" placeholder="Filter"
+               oninput="updateFilter(this.value)">
+        <div class="button-container hideonprint" style="float: left; margin: 0;">
+          <button id="copy" title="Copy bom table to clipboard"
+               onclick="copyToClipboard()"></button>
+        </div>
+      </div>
+      <div id="dbg"></div>
+      <table class="bom" id="bomtable">
+        <thead id="bomhead">
+        </thead>
+        <tbody id="bombody">
+        </tbody>
+      </table>
+    </div>
+    <div id="canvasdiv" class="split split-horizontal">
+      <div id="frontcanvas" class="split" touch-action="none" style="overflow: hidden">
+        <div style="position: relative; width: 100%; height: 100%;">
+          <canvas id="F_bg" style="position: absolute; left: 0; top: 0; z-index: 0;"></canvas>
+          <canvas id="F_fab" style="position: absolute; left: 0; top: 0; z-index: 1;"></canvas>
+          <canvas id="F_slk" style="position: absolute; left: 0; top: 0; z-index: 2;"></canvas>
+          <canvas id="F_hl" style="position: absolute; left: 0; top: 0; z-index: 3;"></canvas>
+        </div>
+      </div>
+      <div id="backcanvas" class="split" touch-action="none" style="overflow: hidden">
+        <div style="position: relative; width: 100%; height: 100%;">
+          <canvas id="B_bg" style="position: absolute; left: 0; top: 0; z-index: 0;"></canvas>
+          <canvas id="B_fab" style="position: absolute; left: 0; top: 0; z-index: 1;"></canvas>
+          <canvas id="B_slk" style="position: absolute; left: 0; top: 0; z-index: 2;"></canvas>
+          <canvas id="B_hl" style="position: absolute; left: 0; top: 0; z-index: 3;"></canvas>
+        </div>
+      </div>
+    </div>
+  </div>
+</div>
+
+</body>
+
+</html>
diff --git a/docs/fabrixiao.html b/docs/fabrixiao.html
index a76795d57b93f104849a59bd1ca56d78e2aea621..6bab62b89f6b78eb05cecda2013705c79757ce50 100644
--- a/docs/fabrixiao.html
+++ b/docs/fabrixiao.html
@@ -141,12 +141,12 @@
                             
                             
                         <h1><a id="features"></a>Features</h1>
-                            <ul><li>Identification of the power supply pins 5V in red, 3V3 in blue and GND in white.  
-                            <li>On the left there are 4 outputs or inputs with VCC and GND on each side to be able to connect different inputs or outputs. 
-                            <li>On the right there are 3 outputs or inputs at the bottom and with a GND pinout.    
-                            <li>On the bottom there is an <b>I2C</b> connection to connect an LCD, OLED or a sensor that uses this communication. 
-                            <li>There is an LED and an integrated button, which will help us to test that the XIAO RP2040 works with a simple program.
-                            <li>Possibility of connection with the <a href="adrianino.html"><b>Adrianino</b></a>  input and output modules.    
+                            <ul><li><b>Small size:</b> 40 mm.
+                                <li>Programming via <b>USB C.</b>
+                                <li>Two <b>power connections</b> and two <b>GND connections.</b>
+                                <li>4 analog inputs. 10 digital inputs/outputs.
+                                <li>Connection via a 3.7 V <b>battery.</b>
+                                <li>Includes an <b>LED</b> (D10) to test the board (assembly optional).   
                         </ul>    
                           
                             
@@ -213,12 +213,11 @@
                             </ul>        
             
                                  
-                        <h1><a id="bom"></a>BOM and Schematic for Fab-Xiao</h1>
+                        <h1><a id="bom"></a>BOM and Schematic for Fabrixiao</h1>
                             
                         <p>This is the schematic where you can see all the components.</p>
                             
-                            <p><img src="images/fabxiao/xiao_01.png" width="90%"; max-width="700" /></p>
-                            <p><img src="images/fabxiao/xiao_25.jpg" width="90%"; max-width="700" /></p>
+                            <p><img src="images/fabrixiao/schematic.png" width="90%"; max-width="700" /></p>
                             
                             <style>
                                                     table {
@@ -240,19 +239,13 @@
 
                                                 <table>
                                                     <tr>
-                                                        <td><h3><b>Fab XIAO</b></h3></td>
+                                                        <td><h3><b>Fabrixiao</b></h3></td>
                                                          <td>Where to buy?</td>
                                                         <td>Amount</td>
                                                         <td>Price</td>
                                                         <td>Total price</td>
                                                     </tr>
-                                                     <tr>
-                                                        <td><a href="https://www.digikey.es/product-detail/es/bantam-tools/MT1004/1932-1038-ND/8567212">Proto Board FR1</a></td>
-                                                        <td>Digikey</td>
-                                                        <td>1/4 board</td>
-                                                        <td>1,24 €/unit</td>
-                                                        <td>0,31 €</td>
-                                                    </tr>
+                                                    
                                                     <tr>
                                                         <td><a href="https://www.seeedstudio.com/XIAO-RP2040-v1-0-p-5026.html">Seeed Studio XIAO RP2040</a></td>
                                                         <td>Seeed Studio</td>
@@ -260,20 +253,6 @@
                                                         <td>5,00 €/unit</td>
                                                         <td>5,00 €</td>
                                                     </tr>
-                                                    <tr>
-                                                        <td><a href="https://www.digikey.es/en/products/detail/yageo/RC1206FR-070RL/5698945">0Ω resistor</a></td>
-                                                        <td>Digikey</td>
-                                                        <td>1</td>
-                                                        <td>0,09 €/unit</td>
-                                                        <td>0,09 €</td>
-                                                    </tr>
-                                                    <tr>
-                                                        <td><a href="https://www.digikey.es/product-detail/es/yageo/RC1206FR-07499RL/311-499FRCT-ND/731891">499 Ω resistor</a></td>
-                                                        <td>Digikey</td>
-                                                        <td>1</td>
-                                                        <td>0,09 €/unit</td>
-                                                        <td>0,09 €</td>
-                                                    </tr>
                                                     <tr>
                                                         <td><a href="https://www.digikey.es/products/es?keywords=311-1.00KFRCT-ND">1kΩ resistor</a></td>
                                                         <td>Digikey</td>
@@ -289,63 +268,33 @@
                                                         <td>0,35 €</td>
                                                     </tr>
                                                     <tr>
-                                                        <td><a href="https://www.digikey.com/product-detail/es/omron-electronics-inc-emc-div/B3SN-3112P/SW262CT-ND/60835">Button</a></td>
+                                                        <td><a href="https://www.digikey.es/es/products/detail/jst-sales-america-inc./B2B-PH-SM4-TBT/3906722?utm_adgroup=&utm_source=google&utm_medium=cpc&utm_campaign=PMax_Product_All%20Products&utm_term=&productid=3906722&utm_content=&utm_id=go_cmp-20199915072_adg-_ad-__dev-c_ext-_prd-3906722_sig-CjwKCAjw59q2BhBOEiwAKc0ijQlWDLuzGSrmLaOczPSHi4OsNGFO9cqlocIvBPINglJCwJndiF7vAhoC0v0QAvD_BwE&gad_source=1&gclid=CjwKCAjw59q2BhBOEiwAKc0ijQlWDLuzGSrmLaOczPSHi4OsNGFO9cqlocIvBPINglJCwJndiF7vAhoC0v0QAvD_BwE">Header JST PH 1x02 P2mm Vertical SMD</a></td>
                                                         <td>Digikey</td>
                                                         <td>1</td>
-                                                        <td>1,00 €/unit</td>
-                                                        <td>1,00 €</td>
-                                                    </tr>
-                                                     <tr>
-                                                        <td><a href="https://www.digikey.es/en/products/detail/gct/BG300-06-A-L-A/9859597">Female 1 row horizontal header</a></td>
-                                                        <td>Digikey</td>
-                                                        <td>3</td>
-                                                        <td>0,15 €/unit</td>
-                                                        <td>0,45 €</td>
-                                                    </tr>
-                                                      <tr>
-                                                        <td><a href="https://www.digikey.es/en/products/detail/sullins-connector-solutions/GBC03DABN-M30/1649558">Male 2 row vertical header</a></td>
-                                                        <td>Digikey</td>
-                                                        <td>1</td>
-                                                        <td>0,65 €/unit</td>
-                                                        <td>0,65 €</td>
+                                                        <td>0,75 €/unit</td>
+                                                        <td>0,75 €</td>
                                                     </tr>
+                                                   
                                                     
-                                                  
                                                     <tr>
                                                         <td></td>
                                                         <td></td>
                                                         <td></td>
                                                         <td><b>Total cost</b></td>
-                                                        <td><b>8,03 €</b></td>
+                                                        <td><b>6,19 €</b></td>
                                                     </tr>
                                                 </table>
                     
                             <h1><a id="board"></a>Board design</h1>
-                            <p>Here you can download the Eagle files and the PNG's. Here is a sample of the PNG's, traces and cutting lines. Here you can also find the <b>library</b> to add to your Eagle or kiCAD the traces of the Seeed Xiao RP2040 or ESP32-C3.</p>
-                            <ul><li><a href="assignments/fabxiao/fab_xiao.zip"><b>Fab-Xiao Schematic + Board</b></a>
-                                <li><a href="assignments/fabxiao/fab_xiao_traces.png"><b>Fab-Xiao Traces</b></a> 
-                                <li><a href="assignments/fabxiao/fab_xiao_interior.png"><b>Fab-Xiao Interior</b></a>
-                                <li><a href="assignments/fabxiao/Seeed%20Studio%20XIAO%20Series.lbr"><b>Seeed Xiao RP2040 and ESP32-C3 Eagle Library</b></a>
-                                <li><a href="https://gitlab.fabcloud.org/pub/libraries/electronics/kicad"><b>Fab Library for KiCAD with Seeed Xiao RP2040 and ESP32-C3</b></a>
-                                   
-                            </ul>
-                            
-                            <span class="image main"><img src="images/fabxiao/xiao_02.png" alt="" /></span>
-                            
+                        
                             <p>Here you can download the <b>KICAD 6</b> files and the SVG's. You can also see a 3D simulation of the board.</p>
-                            <ul><li><a href="assignments/fabxiao/xiao.zip"><b>Fab XIAO KICAD Schematic + Board</b></a>
-                                <li><a href="assignments/fabxiao/xiao-F_Cu.svg"><b>Fab XIAO Traces SVG</b></a> 
-                                <li><a href="assignments/fabxiao/xiao-Edge_Cuts.svg"><b>Fab XIAO Interior SVG</b></a>
-                                <li><a href="assignments/fabxiao/fab_xiao_ibom.html"><b>Fab XIAO BOM</b></a>    
+                            <ul><li><a href="assignments/fabxiao/xiao.zip"><b>Fabrixiao KICAD Schematic + Board</b></a>
+                                <li><a href="assignments/fabrixiao/fabrixiao_bom.html"><b>Fabrixiao BOM</b></a>    
                             </ul>
                             
                             <p><img src="images/fabxiao/xiao_24.png" width="80%"; max-width="700" /></p>
                             
-                            <p>As the XIAO RP2040 has connection areas below and to avoid short circuits, I cover the bottom with <b><a href="https://www.3m.com/3M/en_US/p/d/b00035183/">3Mâ„¢ Epoxy Film Electrical Tape 1.</a></b></p>
-	            
-                            <span class="image main"><img src="images/fabxiao/xiao_03.jpg" alt="" /></span>
-                            
-                            
+                          
                             
                             <h1><a id="programmingRP"></a>Programming. Seeed Studio XIAO RP2040 with Arduino</h1>
                             
@@ -448,412 +397,6 @@ void loop() {
                             
                             
                             
-                             <h2><a id="photo"></a>Phototransistor. Visible.</h2>
-                            <p>The <a href="https://www.digikey.com/product-detail/en/everlight-electronics-co-ltd/PT15-21C-TR8/1080-1380-1-ND"><b>Phototransistor</b></a> sensor is a three-layer semiconductor device which has a light-sensitive base region. The base senses the light and converts it into the current which flows between the collector and the emitter region.</p>
-                            
-                              <li><b>Connection and schematic</b></li> 
-                            <p>In this case, being a component without a module, I create my own. I design and manufacture a small board where I solder the Phototransistor sensor, a 10K resistor and with the flat connectors where there is no room for mistakes when connecting it. I use the analog input of the Seeed XIAO RP2040 GPIO 26 (Arduino pin 26).</p>  
-                            
-                            <span class="image main"><img src="images/fabxiao/xiao_09.jpg" alt="" /></span>
-                            
-                            <p>Here you can find the design in Eagle and the PNG's to create the board.</p>
-                          
-                            <p><a href="assignments/adrianino/phototransisor_eagle.zip"><b>- Phototransistor Schematic + Board</b></a>
-                            <p><a href="assignments/adrianino/phototransisor_png.zip"><b>- Phototransistor traces</b></a></p> 
-                            
-                            <p><img src="images/adrianino/a_15.jpg" width="70%"; max-width="700" /></p>
-                            
-                             
-                            <li><b>Programming</b></li>
-                                <p>Here you will find the programming to use an analog sensor such as the Phototransistor sensor. Here you can find the Arduino and Processing files to download.</p>
-                            
-                            <p><a href="assignments/fabxiao/xiao_phototransistor/xiao_phototransistor.ino"><b>- Arduino Hello Phototransistor sensor</b></a>
-                            <p><a href="assignments/fabxiao/phototransitor_processing/phototransitor_processing.pde"><b>- Processing Hello Phototransistor sensor</b></a></p>    
-                            
-                            <pre><code>//Fab Academy 2023 - Fab Lab León
-//Phototransistor
-//Fab-Xiao
-
-int sensorPin = A0;    // analog input pin RP2040 pin 26 or ESP32-C3 pin A0 
-int sensorValue = 0;  // variable to store the value coming from the sensor
- 
-void setup() {
-  Serial.begin(115200); // initialize serial communications
-}
- 
-void loop() {
-  sensorValue = analogRead(sensorPin); // read the value from the sensor
-  sensorValue = map(sensorValue, 0, 1024, 1024, 0);
-  Serial.println(sensorValue); // print value to Serial Monitor
-  //mySerial.println("x"); // print value "x" to Serial Monitor
-  delay(50); // short delay so we can actually see the numbers
-}</code></pre>
-                            
-                            <p><video controls width="100%"; max-width="800"><source src="images/fabxiao/photo.mp4" type="video/mp4"></video></p>
-                            
-                            
-                            
-                            
-                             <h2><a id="photoir"></a>Phototransistor. IR.</h2>
-                            <p>The <a href="https://www.digikey.com/product-detail/en/everlight-electronics-co-ltd/PT15-21B-TR8/1080-1379-1-ND"><b>Phototransistor IR</b></a> sensor is a three-layer semiconductor device which has a light-sensitive base region. The base senses the IR and converts it into the current which flows between the collector and the emitter region.</p>
-                            
-                              <li><b>Connection and schematic</b></li> 
-                            <p>In this case, being a component without a module, I create my own. I design and manufacture a small board where I solder the Phototransistor IR sensor, a 10K resistor, a <a href="https://www.digikey.es/product-detail/es/everlight-electronics-co-ltd/IR15-21C-TR8/1080-1352-1-ND/2676086"><b>IR LED,</b></a>  a 1K resistor and with the flat connectors where there is no room for mistakes when connecting it. I use the analog input of the Seeed XIAO RP2040 GPIO 26 (Arduino pin 26).</p>  
-                            
-                            <span class="image main"><img src="images/fabxiao/xiao_13.jpg" alt="" /></span>
-                            
-                            <p>Here you can find the design in Eagle and the PNG's to create the board.</p>
-                          
-                            <p><a href="assignments/adrianino/phototransisor_IR_eagle.zip"><b>- Phototransistor IR Schematic + Board</b></a>
-                            <p><a href="assignments/adrianino/photo_ir_png.zip"><b>- Phototransistor IR traces and interior</b></a></p> 
-                            
-                            <p><img src="images/adrianino/a_23.jpg" width="50%"; max-width="700" /></p>
-                            
-                             
-                                <li><b>Programming</b></li>
-                                <p>Here you will find the programming to use an analog sensor such as the Phototransistor sensor. Here you can find the Arduino and Processing files to download. Below you can see a video of how it works.</p>
-                            
-                            <p><a href="assignments/fabxiao/xiao_photo_ir/xiao_photo_ir.ino"><b>- Arduino Hello Phototransistor IR sensor</b></a>
-                            <p><a href="assignments/fabxiao/phototransitor_processing_ir/phototransitor_processing_ir.pde"><b>- Processing Hello Phototransistor IR sensor</b></a></p>    
-                            
-                            <pre><code>/******************************************************************************
-QRD1114_Proximity_Example.ino
-Example sketch for SparkFun's QRD1114 Reflectance Proximity Sensor
-  (https://www.sparkfun.com/products/246)
-Jim Lindblom @ SparkFun Electronics
-May 2, 2016
-
-Modified by Adrián Torres Omaña
-Fab Lab LeÓN
-Phototransistor IR
-Fab-Xiao
-
-
-******************************************************************************/
-const int Sensor = 26; // analog input pin RP2040 pin 26 or ESP32-C3 pin A0 
-
-void setup() 
-{
-  Serial.begin(115200);
-  pinMode(Sensor, INPUT);
-}
-
-void loop() 
-{
-  // Read in the ADC and convert it to a voltage:
-  int proximityADC = analogRead(Sensor);
-  float proximityV = (float)proximityADC * 1000.0 / 1023.0;
-  Serial.println(proximityV);
-  delay(100);
-}</code></pre>
-                            
-                            <p><video controls width="100%"; max-width="800"><source src="images/fabxiao/ir.mp4" type="video/mp4"></video></p>
-                            
-                            
-                            
-                           
-                     
-                            <h2><a id="step"></a>Step Response.</h2>
-                            <p>The step-response is a sensor that is made with two sheets of copper, separated from each other by a porous insulating material. It can be used to calculate the value of force, weight, resistance ... I have followed <a href="https://roberthart56.github.io/SCFAB/SC_lab/Sensors/tx_rx_sensors/index.html"><b>Robert Hart's tutorial</b></a> as well as <a href="http://academy.cba.mit.edu/classes/input_devices/step/hello.txrx.45.mp4"><b>Neil's examples.</b></a></p>
-                            
-                            <p>A series of pulses is sent through the TX electrode, which, depending on the material between the two electrodes, allows more or less that series of pulses to pass. In the following graph you can see how it works. The other electrode will be connected between two resistors, one for pull-up and the other for pull-down and to an analog input.</p>
-                            
-                            <p><img src="images/adrianino/a_38.jpg" width="70%"; max-width="700" /></p>
-                            
-                             <li><b>Connection and schematic</b></li> 
-                            <p>In this case, being a component without a module, I create my own. I design and manufacture a small board where I solder two 1M resistors and with flat connectors where there is no room for errors when connecting it. I use the analog input from the Seeed XIAO RP2040 GPIO 27 (Arduino pin 27) for the RX and a digital output from the Seeed XIAO RP2040 GPIO 28 (Arduino pin 28). Then with a connector there are two cables that connect the two electrodes (<a href="https://www.3m.com/3M/en_US/company-us/all-3m-products/~/3M-Conductive-Copper-Foil-Tape-3313/?N=5002385+3293242553&rt=rud"><b>3Mâ„¢ Copper Foil Tape</b></a>).</p>  
-                            
-                            <span class="image main"><img src="images/fabxiao/xiao_10.jpg" alt="" /></span>
-                            
-                            <p>Here you can find the design in Eagle and the PNG's to create the board.</p>
-                          
-                            <p><a href="assignments/adrianino/step_response_eagle.zip"><b>- Step Response Schematic + Board</b></a>
-                            <p><a href="assignments/adrianino/step_response_png.zip"><b>- Step Response traces</b></a></p> 
-                            
-                            <p><img src="images/adrianino/a_40.png" width="50%"; max-width="700" /></p>
-                            
-                             
-                            <li><b>Programming</b></li>
-                            <p>Here you will find the programming to use a Step Response sensor. Here you can find the Arduino and Processing files to download. Below you can see a video of how it works.</p>
-                            
-                            <p><a href="assignments/fabxiao/xiao_step_response/xiao_step_response.ino"><b>- Arduino Step Responde TX/RX</b></a>
-                            <p><a href="assignments/fabxiao/step_response/step_response.pde"><b>- Processing Step Response TX/RX</b></a></p>    
-                            
-                            <pre><code>//tx_rx03  Robert Hart Mar 2019.
-//https://roberthart56.github.io/SCFAB/SC_lab/Sensors/tx_rx_sensors/index.html
-
-//Modified by Adrián Torres Omaña
-//Fab Academy 2023 - Fab Lab León
-//Step Response TX, RX
-//Fab-Xiao
-
-//  Program to use transmit-receive across space between two conductors.
-//  One conductor attached to digital pin, another to analog pin.
-//
-//  This program has a function "tx_rx() which returns the value in a long integer.
-//
-//  Optionally, two resistors (1 MOhm or greater) can be placed between 5V and GND, with
-//  the signal connected between them so that the steady-state voltage is 2.5 Volts.
-//
-//  Signal varies with electric field coupling between conductors, and can
-//  be used to measure many things related to position, overlap, and intervening material
-//  between the two conductors.
-//
-
-
-long result;   //variable for the result of the tx_rx measurement.
-int analog_pin = 27; //  GPIO 27 of the XIA0 RP2040 or ESP32-C3 pin A1
-int tx_pin = 28;  //     GPIO 28 of the XIAO RP2040 or ESP32-C3 pin D2
-void setup() {
-pinMode(tx_pin,OUTPUT);      //Pin 2 provides the voltage step
-Serial.begin(115200);
-}
-
-
-long tx_rx(){         //Function to execute rx_tx algorithm and return a value
-                      //that depends on coupling of two electrodes.
-                      //Value returned is a long integer.
-  int read_high;
-  int read_low;
-  int diff;
-  long int sum;
-  int N_samples = 100;    //Number of samples to take.  Larger number slows it down, but reduces scatter.
-
-  sum = 0;
-
-  for (int i = 0; i < N_samples; i++){
-   digitalWrite(tx_pin,HIGH);              //Step the voltage high on conductor 1.
-   read_high = analogRead(analog_pin);        //Measure response of conductor 2.
-   delayMicroseconds(100);            //Delay to reach steady state.
-   digitalWrite(tx_pin,LOW);               //Step the voltage to zero on conductor 1.
-   read_low = analogRead(analog_pin);         //Measure response of conductor 2.
-   diff = read_high - read_low;       //desired answer is the difference between high and low.
- sum += diff;                       //Sums up N_samples of these measurements.
- }
-  return sum;
-}                         //End of tx_rx function.
-
-
-void loop() {
-
-result = tx_rx();
-result = map(result, 17000, 23000, 0, 1024);  //I recommend mapping the values of the two copper plates, it will depend on their size
-Serial.println(result);
-delay(100);
-}</code></pre>
-
-                            <p>This first video you can see how the two electrodes work with several sheets as intermediate material.</p>
-                            
-                            <p><video controls width="100%"; max-width="800"><source src="images/fabxiao/step.mp4" type="video/mp4"></video></p>
-
-                       
-                            
-                             <h2><a id="flight2"></a>Time of flight VL53L1X</h2>
-                            <p>I use a module that integrates the <b>VL53L1X sensor.</b> The module is sold by <a href="https://www.pololu.com/product/3415"> <b>Polulu,</b></a> although in my case I bought it from <a href="https://www.amazon.es/ACAMPTAR-Vl53L1X-M%C3%B3Dulo-Sensor-Tiempo/dp/B08TBZL5Q3/ref=sr_1_11?__mk_es_ES=%C3%85M%C3%85%C5%BD%C3%95%C3%91&dchild=1&keywords=VL53L1X&qid=1614460990&s=electronics&sr=1-11"> <b>Amazon.</b></a> 
-                            In this case of the pinout that the sensor brings, I will only use the VCC, GND, SDA and SCL pins.</p>
-                            
-                            <p><img src="images/adrianino/a_34.jpg" width="70%"; max-width="700" /></p>
-                            
-                            
-                              <li><b>Connection and schematic</b></li> 
-                            <p>In this case we only need four cables; one for <b>VCC</b>, one for <b>GND</b>, another cable for the SDA and SCL, the I2C connection.</p> 
-                            
-                            <p><img src="images/fabxiao/xiao_14.jpg" width="70%"; max-width="700" /></p>
-                            
-                                
-                            <li><b>Programming</b></li>
-                            <p>Here you will find the programming to use the Time of Flight VL53L1X sensor. Here you can find the Arduino and Processing files to download. Below you can see a video of how it works. <b>Recommendation:</b> Download the program from the link, in the text the symbols <b><></b> of the libraries are missing.</p>
-                            
-                            <p><a href="assignments/fabxiao/xiao_tof_rojo/xiao_tof_rojo.ino"><b>- Arduino Time of Flight VL53L1X sensor</b></a>
-                            <p><a href="assignments/fabxiao/time_flight_xiao/time_flight_xiao.pde"><b>- Processing Time of Flight VL53L1X sensor </b></a></p>     
-                            
-                            <pre><code>//Fab Academy 2023 - Fab Lab León
-//Time of Flight VL53L1X                         
-//Fab-Xiao
-
-
-/*
-This example shows how to take simple range measurements with the VL53L1X. The
-range readings are in units of mm.
-*/
-
-#include <Wire.h>
-#include <VL53L1X.h>
-
-VL53L1X sensor;
-
-void setup()
-{
-  Serial.begin(115200);
-  Wire.begin();
-  Wire.setClock(400000); // use 400 kHz I2C
-
-  sensor.setTimeout(500);
-  if (!sensor.init())
-  {
-    Serial.println("Failed to detect and initialize sensor!");
-    while (1);
-  }
-  
-  // Use long distance mode and allow up to 50000 us (50 ms) for a measurement.
-  // You can change these settings to adjust the performance of the sensor, but
-  // the minimum timing budget is 20 ms for short distance mode and 33 ms for
-  // medium and long distance modes. See the VL53L1X datasheet for more
-  // information on range and timing limits.
-  sensor.setDistanceMode(VL53L1X::Long);
-  sensor.setMeasurementTimingBudget(50000);
-
-  // Start continuous readings at a rate of one measurement every 50 ms (the
-  // inter-measurement period). This period should be at least as long as the
-  // timing budget.
-  sensor.startContinuous(50);
-}
-
-void loop()
-{
-  Serial.print(sensor.read());
-  if (sensor.timeoutOccurred()) { Serial.print(" TIMEOUT"); }
-
-  Serial.println();
-}</code></pre>
-                            
-                            <p><video controls width="100%"; max-width="800"><source src="images/fabxiao/tof.mp4" type="video/mp4"></video></p> 
-                            
-                            
-                             
-                            <h2><a id="hall"></a>Hall effect</h2>
-                            <p>The <a href="https://www.digikey.com/product-detail/en/A1324LLHLT-T/620-1402-1-ND/"><b>Hall effect</b></a> sensor or simply Hall sensor or Hall probe uses the Hall effect to measure magnetic fields or currents or to determine the position in which it is.</p>
-                            
-                              <li><b>Connection and schematic</b></li> 
-                            <p>In this case, being a component without a module, I create my own. I design and manufacture a small board where I solder the hall effect sensor and with the flat connectors where there is no room for mistakes when connecting it. I use the analog input of the Seeed XIAO RP2040 GPIO 26 (Arduino pin 26).</p>  
-                            
-                            <span class="image main"><img src="images/fabxiao/xiao_15.jpg" alt="" /></span>
-                            
-                            <p>Here you can find the design in Eagle and the PNG's to create the board.</p>
-                          
-                            <p><a href="assignments/adrianino/hall_effect_eagle.zip"><b>- Hall effect Schematic + Board</b></a>
-                            <p><a href="assignments/adrianino/hall_effect_png.zip"><b>- Hall effect traces and interior</b></a></p> 
-                            
-                            <p><img src="images/adrianino/a_10.jpg" width="50%"; max-width="700" /></p>
-                            
-                             
-                                <li><b>Programming</b></li>
-                                <p>Here you will find the programming to use an analog sensor such as the Hall effect sensor. Here you can find the Arduino and Processing files to download. Below you can see a video of how it works.</p>
-                            
-                            <p><a href="assignments/fabxiao/xiao_hall/xiao_hall.ino"><b>- Arduino Hello Hall effect sensor</b></a>
-                            <p><a href="assignments/adrianino/hall_effect_processing/hall_effect_processing.pde"><b>- Processing Hello Hall effect sensor</b></a></p>    
-                            
-                            <pre><code>//Fab Academy 2023 - Fab Lab León
-//Hall effect
-//Fab-Xiao
-
-
-int sensorPin = A0;   // analog input pin RP2040 pin 26 or ESP32-C3 pin A0 
-int sensorValue = 0;  // variable to store the value coming from the sensor
- 
-void setup() {
-  Serial.begin(115200); // initialize serial communications
-}
- 
-void loop() {
-  sensorValue = analogRead(sensorPin); // read the value from the sensor
-  sensorValue = map(sensorValue, 200, 800, 1024, 0);
-  Serial.println(sensorValue); // print value to Serial Monitor
-  //mySerial.println("x"); // print value "x" to Serial Monitor
-  delay(50); // short delay so we can actually see the numbers
-}</code></pre>
-                            
-                            <p><video controls width="100%"; max-width="800"><source src="images/fabxiao/hall.mp4" type="video/mp4"></video></p> 
-                            
-                            
-                            
-                            
-                             <h2><a id="temperature"></a>Temperature. NTC.</h2>
-                            <p>The <a href="https://www.digikey.com/product-detail/en/NHQ103B375T10/235-1109-1-ND"><b>NTC</b></a> sensor is a type of resistance whose value varies as a function of temperature in a more pronounced way than a common resistance. Its operation is based on the variation of the resistivity that a semiconductor presents with temperature.</p>
-                            
-                              <li><b>Connection and schematic</b></li> 
-                            <p>In this case, being a component without a module, I create my own. I design and manufacture a small board where I solder the NTC sensor, a 10K resistor and with the flat connectors where there is no room for mistakes when connecting it. I use the analog input of the Seeed XIAO RP2040 GPIO 26 (Arduino pin 26).</p>  
-                            
-                            <span class="image main"><img src="images/fabxiao/xiao_16.jpg" alt="" /></span>
-                            
-                            <p>Here you can find the design in Eagle and the PNG's to create the board.</p>
-                          
-                            <p><a href="assignments/adrianino/temperature_eagle.zip"><b>- NTC Schematic + Board</b></a>
-                            <p><a href="assignments/adrianino/temperature_png.zip"><b>- NTC traces and interior</b></a></p> 
-                            
-                            <p><img src="images/adrianino/a_13.jpg" width="50%"; max-width="700" /></p>
-                            
-                             
-                            <li><b>Programming</b></li>
-                            <p>Here you will find the programming to use an analog sensor such as the NTC sensor. Here you can find the Arduino and Processing files to download. Below you can see a video of how it works.</p>
-                            
-                            <p><a href="assignments/fabxiao/xiao_ntc/xiao_ntc.ino"><b>- Arduino Hello NTC Temperature sensor</b></a>
-                            <p><a href="assignments/adrianino/temperature_processing/temperature.pde"><b>- Processing Hello NTC Temperature sensor</b></a></p>    
-                            
-                            <pre><code>//Fab Academy 2023 - Fab Lab León
-//Fab-Xiao
-
-int sensorPin = 26;   // analog input pin RP2040 pin 26 or ESP32-C3 pin A0 
-int sensorValue = 0;  // variable to store the value coming from the sensor
- 
-void setup() {
-  Serial.begin(115200); // initialize serial communications
-}
- 
-void loop() {
-  sensorValue = analogRead(sensorPin); // read the value from the sensor
-  sensorValue = map(sensorValue, 482, 890, 19, 180);
-  Serial.println(sensorValue); // print value to Serial Monitor
-  //mySerial.println("x"); // print value "x" to Serial Monitor
-  delay(500); // short delay so we can actually see the numbers
-}</code></pre>
-                            
-                            <p><video controls width="100%"; max-width="800"><source src="images/fabxiao/ntc.mp4" type="video/mp4"></video></p>
-                            
-                            
-                            
-                             <h2><a id="radar"></a>Doppler radar</h2>
-                            <p>The <b>RCWL-0516</b> is a Dopler Microwave Radar Sensor.It is an alternative to traditional PIR infrared motion detectors. Faced with these, it has certain differences, which will be an advantage or disadvantage depending on the needs of our project.</p>
-
-                            <p>First of all, PIR sensors require the moving object to have a temperature difference from the environment. For this reason, they are able to detect people, but not moving objects. In contrast, the RCWL-0516 detects any object that is moving, regardless of its temperature. On the other hand, PIR sensors have sensitivity problems when the ambient temperature is high. The RCWL-0516, on the other hand, does not have this problem and works correctly between <b>-20ºC to 80ºC.</b> In terms of detection range, the RCWL-0516 has a greater range than PIR sensors, being able to easily reach <b>5-7 meters of range.</b> </p>
-
-                            <p>Finally, the RCWL-0516 is omnidirectional, that is, it detects movement in <b>360º.</b> This is a difference from PIR sensors, which have a certain "angle of view". There are even versions of PIR with a narrow beam, for example to control windows or doors.</p>    
-                            
-                            <li><b>Connection and schematic</b></li> 
-                                <p>In this case we only need three cables; one for <b>VCC</b>, another for <b>GND</b> and another for the sensor output that in our case we will connect it to Seeed XIAO RP2040 GPIO 26 (Arduino pin 26).</p>
-                            
-                            <span class="image main"><img src="images/fabxiao/xiao_17.jpg" alt="" /></span>
-                            
-                             <li><b>Programming</b></li>
-                                <p>Here you will find the programming to use a digital sensor such as the PIR. Here you can find the Arduino and Processing files to download. Below you can see a video of how it works.</p>
-                            
-                            <p><a href="assignments/fabxiao/xiao_radar/xiao_radar.ino"><b>- Arduino Hello Radar</b></a>
-                            <p><a href="assignments/adrianino/radar_pir_processing/radar_pir_processing.pde"><b>- Processing Hello Radar</b></a></p>     
-                                
-                            <pre><code>//Fab Academy 2023 - Fab Lab León
-//PIR sensor or Doppler radar
-//Fab-Xiao
-
-
-const int RadarPin = 26; // digital input pin RP2040 pin 26 or ESP32-C3 pin D0 
-int value = 6; // variable to store the value coming from the sensor
-void setup()
-{
-  Serial.begin(115200); // initialize serial communications
-  pinMode(RadarPin, INPUT); //digital input pin
-}
- 
-void loop()
-{
-  int value= digitalRead(RadarPin); // read the value from the sensor (0 or 1)
-  Serial.println(value);  // print value to Serial Monitor
-}</code></pre>
-                                
-                              
-                            <p><video controls width="100%"; max-width="800"><source src="images/fabxiao/radar.mp4" type="video/mp4"></video></p>  
-                            
-                            
-                            
                             
 
 
@@ -918,410 +461,6 @@ void loop() {
                             <p><video controls width="100%"; max-width="800"><source src="images/fabxiao/servo.mp4" type="video/mp4"></video></p>  
                             
                             
-                            <h2><a id="motor"></a>DC motor</h2>
-                            <p>To control a DC motor we will use an H bridge, such as the <a href="https://www.digikey.es/en/products/detail/TB67H451FNG%2cEL/264-TB67H451FNGELCT-ND/11568783"><b>Toshiba TB67H451FNG.</b></a> With this driver we can control all types of motors through <b>PWM signals.</b> </p>
-                            
-                            <li><b>Connection and schematic</b></li> 
-                            <p>In this case, being a component without a module, I create my own. I design and manufacture a small board where I solder the Toshiba TB67H451FNG driver, a 1uF capacitor, a 10uF capacitor and with the flat connectors where there is no room for mistakes when connecting it. It includes two traces to feed with the 5V and ground. I use two digital output of the XIAO RP2040 GPIO 26 (Arduino pin 26) and GPIO 27 (Arduino pin 27).</p>  
-                            
-                            <span class="image main"><img src="images/fabxiao/xiao_26.jpg" alt="" /></span>
-                            
-                            <p>Here you can find the design in Eagle and the PNG's to create the board.</p>
-                          
-                            <p><a href="assignments/fabxiao/dc_motor_toshiba.zip"><b>- Motor Schematic + Board</b></a>
-                            <p><a href="assignments/fabxiao/dc_motor_toshiba_traces.zip"><b>- Motor traces and interior</b></a></p> 
-                            
-                            <p><img src="images/fabxiao/xiao_27.jpg" width="50%"; max-width="700" /></p>
-                            
-                            <li><b>Programming</b></li>
-                            <p>Here you will find the programming to use a servo motor. Here you can find the Arduino file to download.</p>
-                            
-                            <p><a href="assignments/fabxiao/xiao_motor/xiao_motor.ino"><b>- Arduino Motor</b></a>
-                               
-                                
-                            <pre><code>//Fab Academy 2023 - Fab Lab León
-//Motor
-//Adrianino
-//Fab-Xiao RP2040
-const int switch1Pin = 1;     // switch 1        
-const int motor1Pin = 26;      // H-bridge pin 0 (in2) 
-const int motor2Pin = 27;      // H-bridge pin 1 (in1) 
-
-void setup() {
-    // set the switch pins as input pins and activate their internal pull up resistors
-    // so they are not in a floating state because their default state is now HIGH
-    pinMode(switch1Pin, INPUT); 
-   
-
-    // set H-bridge pins as outputs:
-    pinMode(motor1Pin, OUTPUT);
-    pinMode(motor2Pin, OUTPUT);
-  }
-
-void loop() {
-    // if switch1 is pressed, (=LOW because the unpressed 'pulled up' state is HIGH)
-   
-  if (digitalRead(switch1Pin) == HIGH) {
-      analogWrite(motor1Pin, 255); // set pin 1 of the H-bridge to 50% using PWM
-      analogWrite(motor2Pin, 0);   // set pin 2 of the H-bridge to low state
-    }
-     // if neither of the switches are pressed, the motor will stand still
-  else
-      {
-      digitalWrite(motor1Pin, 0);   // set pin 1 of the H-bridge low
-      digitalWrite(motor2Pin, 255);   // set pin 2 of the H-bridge low
-      }
-  }</code></pre>    
-                            
-                            <p><video controls width="100%"; max-width="800"><source src="images/fabxiao/motor_xiao.mp4" type="video/mp4"></video></p>  
-                            
-                            
-                            <h2><a id="speaker"></a>Speaker</h2>
-                            <p>To control a Speaker we will use an H bridge, such as the <a href="https://www.digikey.es/en/products/detail/TB67H451FNG%2cEL/264-TB67H451FNGELCT-ND/11568783"><b>Toshiba TB67H451FNG.</b></a> With this driver we can control all types of motors through <b>PWM signals.</b> </p>
-                            
-                            <li><b>Connection and schematic</b></li> 
-                            <p>In this case, being a component without a module, I create my own. I design and manufacture a small board where I solder the Toshiba TB67H451FNG driver, a 1uF capacitor, a 10uF capacitor and with the flat connectors where there is no room for mistakes when connecting it. It includes two traces to feed with the 5V and ground. I use two digital output of the XIAO RP2040 GPIO 26 (Arduino pin 26) and GPIO 27 (Arduino pin 27).</p>  
-                            
-                            <span class="image main"><img src="images/fabxiao/xiao_28.jpg" alt="" /></span>
-                            
-                            <p>Here you can find the design in Eagle and the PNG's to create the board.</p>
-                          
-                            <p><a href="assignments/fabxiao/dc_motor_toshiba.zip"><b>- Speaker Driver Schematic + Board</b></a>
-                            <p><a href="assignments/fabxiao/dc_motor_toshiba_traces.zip"><b>- Speaker Driver traces and interior</b></a></p> 
-                            
-                            <p><img src="images/fabxiao/xiao_27.jpg" width="50%"; max-width="700" /></p>
-                            
-                            <li><b>Programming</b></li>
-                            <p>Here you will find the programming to use a speaker. <b>May the force be with you</b> Here you can find the Arduino file to download.</p>
-                            
-                            <p><a href="assignments/fabxiao/music_xiao/music_xiao.ino"><b>- Arduino Speaker</b></a>
-                               
-                                
-                            <pre><code>//Fab Academy 2024 - Fab Lab León
-//Speaker
-//Fab-Xiao RP2040
-
-/* 
-  Star Wars theme  
-  Connect a piezo buzzer or speaker to pin 11 or select a new pin.
-  More songs available at https://github.com/robsoncouto/arduino-songs                                            
-                                              
-                                              Robson Couto, 2019
-*/
-
-#define NOTE_B0  31
-#define NOTE_C1  33
-#define NOTE_CS1 35
-#define NOTE_D1  37
-#define NOTE_DS1 39
-#define NOTE_E1  41
-#define NOTE_F1  44
-#define NOTE_FS1 46
-#define NOTE_G1  49
-#define NOTE_GS1 52
-#define NOTE_A1  55
-#define NOTE_AS1 58
-#define NOTE_B1  62
-#define NOTE_C2  65
-#define NOTE_CS2 69
-#define NOTE_D2  73
-#define NOTE_DS2 78
-#define NOTE_E2  82
-#define NOTE_F2  87
-#define NOTE_FS2 93
-#define NOTE_G2  98
-#define NOTE_GS2 104
-#define NOTE_A2  110
-#define NOTE_AS2 117
-#define NOTE_B2  123
-#define NOTE_C3  131
-#define NOTE_CS3 139
-#define NOTE_D3  147
-#define NOTE_DS3 156
-#define NOTE_E3  165
-#define NOTE_F3  175
-#define NOTE_FS3 185
-#define NOTE_G3  196
-#define NOTE_GS3 208
-#define NOTE_A3  220
-#define NOTE_AS3 233
-#define NOTE_B3  247
-#define NOTE_C4  262
-#define NOTE_CS4 277
-#define NOTE_D4  294
-#define NOTE_DS4 311
-#define NOTE_E4  330
-#define NOTE_F4  349
-#define NOTE_FS4 370
-#define NOTE_G4  392
-#define NOTE_GS4 415
-#define NOTE_A4  440
-#define NOTE_AS4 466
-#define NOTE_B4  494
-#define NOTE_C5  523
-#define NOTE_CS5 554
-#define NOTE_D5  587
-#define NOTE_DS5 622
-#define NOTE_E5  659
-#define NOTE_F5  698
-#define NOTE_FS5 740
-#define NOTE_G5  784
-#define NOTE_GS5 831
-#define NOTE_A5  880
-#define NOTE_AS5 932
-#define NOTE_B5  988
-#define NOTE_C6  1047
-#define NOTE_CS6 1109
-#define NOTE_D6  1175
-#define NOTE_DS6 1245
-#define NOTE_E6  1319
-#define NOTE_F6  1397
-#define NOTE_FS6 1480
-#define NOTE_G6  1568
-#define NOTE_GS6 1661
-#define NOTE_A6  1760
-#define NOTE_AS6 1865
-#define NOTE_B6  1976
-#define NOTE_C7  2093
-#define NOTE_CS7 2217
-#define NOTE_D7  2349
-#define NOTE_DS7 2489
-#define NOTE_E7  2637
-#define NOTE_F7  2794
-#define NOTE_FS7 2960
-#define NOTE_G7  3136
-#define NOTE_GS7 3322
-#define NOTE_A7  3520
-#define NOTE_AS7 3729
-#define NOTE_B7  3951
-#define NOTE_C8  4186
-#define NOTE_CS8 4435
-#define NOTE_D8  4699
-#define NOTE_DS8 4978
-#define REST      0
-
-
-// change this to make the song slower or faster
-int tempo = 108;
-
-// change this to whichever pin you want to use
-int buzzer = 26;
-
-// notes of the moledy followed by the duration.
-// a 4 means a quarter note, 8 an eighteenth , 16 sixteenth, so on
-// !!negative numbers are used to represent dotted notes,
-// so -4 means a dotted quarter note, that is, a quarter plus an eighteenth!!
-int melody[] = {
-  
-  // Dart Vader theme (Imperial March) - Star wars 
-  // Score available at https://musescore.com/user/202909/scores/1141521
-  // The tenor saxophone part was used
-  
-  NOTE_AS4,8, NOTE_AS4,8, NOTE_AS4,8,//1
-  NOTE_F5,2, NOTE_C6,2,
-  NOTE_AS5,8, NOTE_A5,8, NOTE_G5,8, NOTE_F6,2, NOTE_C6,4,  
-  NOTE_AS5,8, NOTE_A5,8, NOTE_G5,8, NOTE_F6,2, NOTE_C6,4,  
-  NOTE_AS5,8, NOTE_A5,8, NOTE_AS5,8, NOTE_G5,2, NOTE_C5,8, NOTE_C5,8, NOTE_C5,8,
-  NOTE_F5,2, NOTE_C6,2,
-  NOTE_AS5,8, NOTE_A5,8, NOTE_G5,8, NOTE_F6,2, NOTE_C6,4,  
-  
-  NOTE_AS5,8, NOTE_A5,8, NOTE_G5,8, NOTE_F6,2, NOTE_C6,4, //8  
-  NOTE_AS5,8, NOTE_A5,8, NOTE_AS5,8, NOTE_G5,2, NOTE_C5,-8, NOTE_C5,16, 
-  NOTE_D5,-4, NOTE_D5,8, NOTE_AS5,8, NOTE_A5,8, NOTE_G5,8, NOTE_F5,8,
-  NOTE_F5,8, NOTE_G5,8, NOTE_A5,8, NOTE_G5,4, NOTE_D5,8, NOTE_E5,4,NOTE_C5,-8, NOTE_C5,16,
-  NOTE_D5,-4, NOTE_D5,8, NOTE_AS5,8, NOTE_A5,8, NOTE_G5,8, NOTE_F5,8,
-  
-  NOTE_C6,-8, NOTE_G5,16, NOTE_G5,2, REST,8, NOTE_C5,8,//13
-  NOTE_D5,-4, NOTE_D5,8, NOTE_AS5,8, NOTE_A5,8, NOTE_G5,8, NOTE_F5,8,
-  NOTE_F5,8, NOTE_G5,8, NOTE_A5,8, NOTE_G5,4, NOTE_D5,8, NOTE_E5,4,NOTE_C6,-8, NOTE_C6,16,
-  NOTE_F6,4, NOTE_DS6,8, NOTE_CS6,4, NOTE_C6,8, NOTE_AS5,4, NOTE_GS5,8, NOTE_G5,4, NOTE_F5,8,
-  NOTE_C6,1
-  
-};
-
-// sizeof gives the number of bytes, each int value is composed of two bytes (16 bits)
-// there are two values per note (pitch and duration), so for each note there are four bytes
-int notes = sizeof(melody) / sizeof(melody[0]) / 2;
-
-// this calculates the duration of a whole note in ms
-int wholenote = (60000 * 4) / tempo;
-
-int divider = 0, noteDuration = 0;
-
-void setup() {
-  // iterate over the notes of the melody. 
-  // Remember, the array is twice the number of notes (notes + durations)
-  for (int thisNote = 0; thisNote < notes * 2; thisNote = thisNote + 2) {
-
-    // calculates the duration of each note
-    divider = melody[thisNote + 1];
-    if (divider > 0) {
-      // regular note, just proceed
-      noteDuration = (wholenote) / divider;
-    } else if (divider < 0) {
-      // dotted notes are represented with negative durations!!
-      noteDuration = (wholenote) / abs(divider);
-      noteDuration *= 1.5; // increases the duration in half for dotted notes
-    }
-
-    // we only play the note for 90% of the duration, leaving 10% as a pause
-    tone(buzzer, melody[thisNote], noteDuration*0.9);
-
-    // Wait for the specief duration before playing the next note.
-    delay(noteDuration);
-    
-    // stop the waveform generation before the next note.
-    noTone(buzzer);
-  }
-}
-
-void loop() {
-  // no need to repeat the melody.
-}</code></pre>    
-                            
-                            <p><video controls width="100%"; max-width="800"><source src="images/fabxiao/xiao_speaker.mp4" type="video/mp4"></video></p>  
-                            
-                            
-                             <h2><a id="oled"></a>OLED</h2>
-                            <p>For this example I am going to use an OLED with an I2C communication module. In my case, I bought it through Amazon from AZDelivery, here is the <a href="https://www.amazon.com/AZDelivery-Display-Arduino-Pixels-Module/dp/B07F5JKXD9/ref=sr_1_7?dchild=1&keywords=128+x+64+OLED&qid=1601808816&sr=8-7"><b>link.</b></a> This company has a multitude of very interesting tutorials, including that of the <a href="https://www.az-delivery.de/en/products/1-3-zoll-display-kostenfreies-e-book?_pos=3&_sid=180149b80&_ss=r"><b>OLED module with I2C.</b></a></p>
-                            
-                            <li><b>Connection and schematic</b></li> 
-                            <p>In this case we only need four cables; one for <b>VCC</b>, one for <b>GND</b>, another cable for the SDA and SCL, the I2C connection.</p>  
-                            
-                            <p><img src="images/fabxiao/xiao_12.jpg" width="70%"; max-width="700" /></p>
-                            
-                            
-                            <li><b>Programming</b></li>
-
- 
-                            <p>You will need to install the <b>Adafruit SSD1306</b> and <b>u8g2</b> libraries. If you need more information you can follow the <a href="https://randomnerdtutorials.com/guide-for-oled-display-with-arduino/"><b>following tutorial.</b></a> Below you can see a video of how it works. <b>Recommendation:</b> Download the program from the link, in the text the symbols <b><></b> of the libraries are missing.</p>
-                            
-                            <p><a href="assignments/fabxiao/xiao_oled/xiao_oled.ino"> <b>- Arduino Hello_OLED</b></a></p>
-                            
-                            <pre><code>//Original code davekw7x https://forum.seeedstudio.com/t/arduino-boards-manager/263531/6
-//Modified by Adrian Torres 
-//Fab Academy 2023 - Fab Lab León
-//OLED
-//Fab-Xiao
-
-#include <U8g2lib.h> // In U8g2 library 
-
-// Seeed Expansion board uses SSD1306
-U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset= */ U8X8_PIN_NONE);
-
-// My breadboard has an SH1106
-//U8G2_SH1106_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE);
-
-void setup(void) {
-  // Change #if 0 to #if 1 to print some stuff
-  // If you do, it will wait until you open the Serial Monitor
-#if 0
-  Serial.begin(115200);
-  while (!Serial) {}
-  delay(100);
-  Serial.println("\nOLED u8g2lib test compiled on " __DATE__ " at " __TIME__);
-#endif
-  u8g2.begin();
-}
-
-char printBuffer[30];
-uint8_t passno;
-void loop(void) {
-  snprintf(printBuffer, sizeof(printBuffer), "%3d", ++passno);
-  u8g2.clearBuffer();                   // clear the internal memory
-  u8g2.setFont(u8g2_font_ncenB08_tr);   // choose a suitable font
-  u8g2.drawStr(0, 10, "Hello World!");
-  u8g2.drawStr(0, 25, "Fab Academy 2023");
-  u8g2.drawStr(0, 40, "Fab-Xiao");
-  u8g2.drawStr(0, 55, printBuffer);
-  u8g2.sendBuffer();                    // transfer internal memory to the display
-  delay(1000);
-}</code></pre>
-                            
-                            
-                            <p><video controls width="100%"; max-width="800"><source src="images/fabxiao/oled.mp4" type="video/mp4"></video></p>
-                            
-                            
-                    
-                            
-                            <h2><a id="rgb"></a>RGB LED</h2>
-                            <p>The <a href="https://www.digikey.com/product-detail/en/cree-inc/CLV1A-FKB-CK1VW1DE1BB7C3C3/CLV1A-FKB-CK1VW1DE1BB7C3C3CT-ND"><b>RGB LED</b></a> stands for Red, Blue and Green LEDs. RGB LED products combine these three colors to produce more than 16 million shades of light. </p>
-                            
-                              <li><b>Connection and schematic</b></li> 
-                            <p>Inside the Seeed XIAO RP2040 it has an RGB LED incorporated, whose activation pins are 11 for Power and 12 for LED control.</p>  
-                            
-                            <p><img src="images/fabxiao/xiao_18.jpg" width="70%"; max-width="700" /></p>
-                   
-                                <li><b>Programming</b></li>
-                                <p>Here you will find the programming to use a RGB LED. Here you can find the Arduino file to download. Below you can see a video of how it works.</p>
-                            
-                            <p><a href="assignments/fabxiao/rgb_xiao/rgb_xiao.ino"><b>- Arduino Hello RGB LED</b></a>
-                               
-                            
-                            <pre><code>//Fab Academy 2023 - Fab Lab León
-//RGB LED
-//Fab-Xiao
-//original code from https://wiki.seeedstudio.com/XIAO-RP2040-with-Arduino/#rgb-led
-
-
-#include <Adafruit_NeoPixel.h>
- 
-int Power = 11;
-int PIN  = 12;
-#define NUMPIXELS 1
- 
-Adafruit_NeoPixel pixels(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800);
- 
-void setup() {
-  pixels.begin();
-  pinMode(Power,OUTPUT);
-  digitalWrite(Power, HIGH);
- 
-}
- 
-void loop() { 
-  pixels.clear();
-  pixels.setPixelColor(0, pixels.Color(15, 25, 205));
-  delay(400);
-  pixels.show();
-  pixels.clear();
-  pixels.setPixelColor(0, pixels.Color(103, 25, 205));
-  delay(400);
-  pixels.show();
-  pixels.clear();
-  pixels.setPixelColor(0, pixels.Color(233, 242, 205));
-  delay(400);
-  pixels.show();
-  pixels.clear();
-  pixels.setPixelColor(0, pixels.Color(233, 23, 23));
-  delay(400);
-  pixels.show();
-  pixels.clear();
-  pixels.setPixelColor(0, pixels.Color(12, 66, 101));
-  delay(400);
-  pixels.show();
-  delay(500);
- 
-}
-</code></pre>
-                            
-                            <p><video controls width="100%"; max-width="800"><source src="images/fabxiao/rgb.mp4" type="video/mp4"></video></p>
-                            
-                             
-                            <h1><a id="server"></a>ESP 32 + Web Server + LED</h1>
-                            
-                            <p>One of the features of the XIAO ESP32-C3 is that it can be connected via <b>Bluetooth</b> or <b>Wifi.</b> In this project you’ll create a standalone web server with an ESP32 that controls output (one LED) using the Arduino IDE programming environment. The web server is mobile responsive and can be accessed with any device that as a browser on the local network. We’ll show you how to create the web server and how the code works step-by-step. You can find more info <a href="https://randomnerdtutorials.com/esp32-web-server-arduino-ide/"><b>here</b></a></p>
-                            
-                             <li><b>Programming</b></li>
-                                <p>Here you will find the programming to use a Web Server with a LED. Here you can find the Arduino file to download. Below you can see a video of how it works.</p>
-                            
-                            <p><a href="assignments/fabxiao/xiao_server/xiao_server.ino"><b>- Arduino Web Server + LED</b></a>
-                               
-                            <p>When you have uploaded the code, you should open the Serial Monitor to see the IP address that you will have to copy to paste into a browser.</p>
-              
-                            <p><img src="images/fabxiao/xiao_23.jpg" width="70%"; max-width="700" /></p>
-                            
-                            <p><video controls width="100%"; max-width="800"><source src="images/fabxiao/server.mp4" type="video/mp4"></video></p>
                             
                             
                        
diff --git a/docs/images/fabrixiao/schematic.png b/docs/images/fabrixiao/schematic.png
new file mode 100644
index 0000000000000000000000000000000000000000..de5fa1917d0fcecc9154122e11e3a3764f16fd2e
Binary files /dev/null and b/docs/images/fabrixiao/schematic.png differ