mirror of
https://github.com/UzixLS/zx-sizif-xxs.git
synced 2025-07-18 23:01:40 +03:00
3429 lines
198 KiB
HTML
Vendored
3429 lines
198 KiB
HTML
Vendored
<!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-color-highlight: #D04040;
|
|
--pin1-outline-color: #ffb629;
|
|
--pin1-outline-color-highlight: #b4ff03;
|
|
--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;
|
|
}
|
|
|
|
.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 {
|
|
transition: background-color 0.2s;
|
|
}
|
|
|
|
.bom .numCol {
|
|
width: 25px;
|
|
}
|
|
|
|
.bom .Description {
|
|
width: 10%;
|
|
}
|
|
|
|
.bom .Part {
|
|
width: 10%;
|
|
}
|
|
|
|
.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;
|
|
}
|
|
|
|
.dark .statsbtn, .dark .savebtn, .dark .menubtn, .dark .iobtn {
|
|
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 {
|
|
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;
|
|
}
|
|
|
|
.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;
|
|
}
|
|
|
|
</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 = {"show_fabrication": false, "redraw_on_drag": true, "highlight_pin1": false, "extra_fields": [], "dark_mode": false, "bom_view": "left-right", "board_rotation": 0, "checkboxes": "Sourced,Placed", "show_silkscreen": true, "show_pads": true, "layer_view": "FB"}
|
|
///////////////////////////////////////////////
|
|
|
|
///////////////////////////////////////////////
|
|
var pcbdata = JSON.parse(LZString.decompressFromBase64("N4IgpgJg5mDOIC4AEBtUsAuBDAThxqAjAAwBMAdAKyUA0SAnMeQCwDsAuneAHYQEokK1OoULNy9ZvWkzZ0zkhAYAngAcwBELDBQAtmG74uAdwCWEDAAsCTYnPtyAvnXTY8/UmSpSHMgBwiYhI+vvJcBnzIAl7CSKLikqEyCkpqGshaOvqGICbmVjbkdklOLlpu+FGeQiG+AXFBiSUpER4xtQ71jCwcXCrqmtp6BkaKZhbWyLYlpUiuuJVEMbQMTGwtvG01JV1rvYr96YpD2aMg4wVTRTOyjikARgD2ugSgT5eoKADMXCQArgB6QgANgAarlFABhAAKAH0AKIASQAggBaL6kEGowh+WEAaTA+gwqORsOhWAghCofgAHlSvpRdLpYQAJLC8ADKjwANhAwDgISgUCBISCIYQFMLIZ5xfspaRSOLgexJSq6ChSL9iMRASRwVwYQiUajWF9mF90YR8YSwMSACJkikUUiUGlCRnMtmcnl8gXq+UQ9bqkVfQPK1WSwgrEAkYjcCEi2HEPzESiwzyEUgAWVtOFMAGNHZTyFG6SxiEzWeyIFzefzBVLCBC/HKRcwIcDSJKRZQIaxlcHIcDmwPUCLWM3KN3IX5m8xp/Rm19pyRm13B6JmxKN4q6CmV6G6Kw/Cv20f6Cve3RgcuNxOj63pU3r8Q1ag3xquOJWPGDUmU2mGbZrmBZFlSpZUswFaetWta+g2IqkGeSCsFOg4un284RuqhBfqwxCqAmkL/qm6bEJmOYYHmhbksWEHlpWXo1j69b+iKOLisu2FEFqBFESRgHkcBVGgbR4GupB0FVt6dZ+p87GLnQzBTtxAhar+UICWRFEgTRFLiWWUGMbBLFyUKiGHkglAXqp9QxtqCYAEpaUBlHUWBJYSQxMEyfBbGOfeSBfLeY6OYpQXbqFm50KQF7Bo5hCWaQrYJchzDrlFw4xWhUWzjFIXCgl4UKh+H7AlqADWTkuUJbmifpnmGVJTFwax8mObuQU5YVllfKOPWdTi3aOcF4pxaFXyBdQw1fOFlBYeqZVcJ4xDVcmpGubpHn0UZPnMbJCGOVlSDmsNeVBRlhWrnQXyvvFmYQqQJ73VeSCkP1IAJYFSGlZKzBfuQrBVVwznrYJOkiXpdFebt0n7X57XJYGw1PRCBWfbFfZ3RNq3XuGi2Sj8ihsLjiigwB2nCe5YmNZJxm+W15mOchrCRYV70QpQ6MjcdlD4++kqaooVKkMDZM1RD1MNTtzUmQd/mYoGbMY71J6qULICi2tFObZD20w7LDNmT1z5IMCz0TedwI2QTOHLeQWsgxLVP1dDTX0/DjNXR2HCqbhihIaTn3O3VUMGXTe2tcbn2vWav124owIO2Lwdg5Tof6+7kemYdMpKa+fu/F8KfkxttVbTTMse1HuedbN8c8YofidanOvl3rlcG9XOcK8hk0N2pAeO+Lae61LbsR3DNcK69mID/7IAkyXIcV9LXfZ/L7Vmpzvu243MbL6P7fj+H3lTz3W+vZm89cAAQgAMlmmLIgA4sC5EcgAcgm98rx3a9Z3Ppvcy99TYFz3oPGMn9mBiD8AAdQTA6DkAB5O02JSCWXMnaQa4CBYJxAPfeEdof5ENhHacQ0EEJ2lNvQLiECF7wmhJCDkyEQAoMRJCVEuIvgSBpAkSs0IRasCZAhAAqpZUQN9haiFYOicglk2HIIACqogVBaL4sIlFYHuNCUw3BOrmVETQhaeD96iEILIqkr1FEqLUeiTR2jdH6LEZ1YEKl6FcGRByYEkIoLJlRNQDkiJv5cA5Cg6E6JSCwlEPI1Mbo1j0A9GSIRIi2KiOOlGKRIBGGEEhF8JRsYZwhMUEogAigAMQibGaJzA6TMAEUwD0YjXrAhMSgD8C8ABafhmCQm1J4aE8JISKC4GUypqJmDMBqXUhp5A/CpPkqI5CYgsmUGIEgLMLIABeCZkGwHzKYbk3IsAYEeDgWEHIswOnhH8HAjxSl/AWFs2EAANZB9BCATKcbCVgRQaSUCKN3WSeioAIReabLsqlowk2qsiHAOAsDKFhJCR43AABuYAaSwlqcQd+mDCqf1NpQZWjlP6dTWcNT+llKCXU+tAwMFsCWvSkAPDWt8ZwWITCg2x+KQClNNpM7spTkKUDVhAtlYTErnRsao3lpTOqmiFZZVgkLxVcGhAAKUicQYhXANVYHzBVWEPCkmaoxDq1kZzTBbNRdgbkCENWWXGsKDVyExWmMgU4pAlgwAUn5HEPhSBdEYHzJYAAvDUBZIAnEsl9b6aJNJiBTOhJG5koJ+QYALFge1bENWvT8DbD1C8vU+r9TgANpAg2mBDeG1NCYY1xv5OmRNVoU3eErOmvAWac3yQ1abYEEpC6KFERyW+sIx1ZgLHchMI6J1TseKiCdPJMXYlTORWR2pyIOtcVku0WYvhoI5OUhMuh50cgdCyYiLJTB3O0GQ/dh7j25sCowX6KRb78BQP9YW2oNKJiPpLV2p9YYtQvuZXxHZaWilJs3A8fZGXsRZoW9pkY8J8T/ABl2YdaZn1A8Axs51EpZNjIRDDbdAPYarhvBG4HCDhWUgPOysZtZlwo5nSeeGaOFUCsFYa4Uvgkuim9Z1n1EqPRSmIQMtKErHRdMNDi+V5PFVVR68qAcHJO0wxnTugDONe0+sdU68VzoYnk6TW68nOpPXk7PD6X1HptI/BrEWh9yNYfY7huWXGMYCpJRgtG7qUP4MDix8G7mdMca8/pxysdHOocTsnUL6dV4T080bXOpMoLEeLklseQGcMgai9HDqddkMdK4M3XLx98tUaAd5jqfdd5FuWsPVurHwsAMi+lmej1B0eJ/a59r2nOtpc9sV0aIgyvxeyUwlhXLkEcK4ca3h/DmSCIdsIl4aSJF9ea4oLxPi/F+ACZQIJxS2HhMidE+kcTPAsESQIlJW3FkZPcXtmbuT8mFL8OdsZVTtTTLELMppaSWlxfwWsjZ2zdn7MOcc055zLnXNufcx5eBnlvI+V8vRPy/kAsNmNkFYKIVZPZX4TloTlGyoQvywMcWUiwAqqYVQ6hIioBWKwGKxA9xdH9vQDKIBj1RG+FqXUYIiJwiRGiDEWIcTWiJCSDyoqywMiBd50UGTpx5ziI+BUSpWW8V1MQfUUJJfGlNOaS08vbSogdLRZ0rp3Rq/09KQMj5LLMH5kFogC9Yx/uIlplLwGCfT3kqKZsj5Xr9mnMdPwH1ISBT8N1EU50ekLiXCuU25OVydX3BuFp6NRSBVYLr/tuDvefmJoDf3f8T4FZD2B+ULNk/Sij+DsxcZ+KB//qlwr3Ww/+boNZAe0YSCDbC8N3vDf8MYzRsnkaaM7MYnFAhkaU15+zU5u3r9AMgZVbYxF0boemZIyUijEzKNwr4RmqTYEXuPxE0XvhffHWp/O+KyzPzvNuZ9U5vfwWy04+yWPewe7+h0AmHYq+XwVsU2wWiWmmbmk+oB1G0W/aTWFeC8S8L+SB9eYB/k28Q+6B5WwsQBeWlG68dW0WDI4oKmGBd8j8z8b8H852v83edetWem0coCEI5exBUCMCzA8CiCFyqC6CvK2C4ovB02hCuqigMhZCFCCyWCNCdC725isiPCCi3KqiGC9iWiOieiBiwoRiEIkgxGMi2IVA82tiuhGi+hTiRhIAoirib2dBig3SvS/SxAgywyCYf2EyUyYgMya2TA8yz2hiyy2+0Kz+TscKCKSKKK6KmK2KiaeKh0hKnMJKZKnM2MBK1K0m9KSkq+n8zKsBqAEqHIUq1h1ObE8qfY6MpSyqtBfBZOFOig2hg+8kwqnMgWTm6qWq5qshIA+qhqy2pqjqngF6VqNqhg2aDqTq3YrqzYWSJaja5ahAgawaoaEa7az20aeisaZaCaSaZIqasInama+Ycxua+aZRnqei3qaxFaVaNaOx80UaDaRxpALapxux5xGa3aDq/au2bhTho646sIk6+Y06XAs6kJ86i6kJy6NIq6ayFiqIm6ps5kGqO6Q6IAe6B6qIR6J6Z6F6V6N6jwd6BJj6DqL6Uh7AzgxwhyFU+yOAYABgrwIAH6UQoAFwkwcQgMNw9Adk7IUA3IRwtgXAJyVE/AIAp68KZykAIAKQlg0xsIAAVn8JgKYAAGbKA2B9CYqLBsLWq6mogvIvIcgJhWAFgVTcBwDwBXAMhcCqCUn8A4jiBqYkD0DyIqlgCmBQCWCLAUDMCMm8n5D8lUjFA3BSncBikSlFBSkYAylRByk3p3Jsl8Aqlqmanal6kGkHBGmaCORgBorkDIjWmWC2n2mwCOlIBMAKhCl2Sul1kCA9LkBemJQdl+kBlBkED0hhnnARn9nXBCmxnxmFDc6KDSkCipnykZlKnZl5jPK5mZr5lTCGk0jGk3IwD2lID3yPA2poqPDKBvRkBBw2mGo1l1kNkawtnuntmdlUjWwzA9mBmLADllB8mFCsBCn0DjnimTlJkpljjzmKlZlcCqnLkalalrn6kbmFlbmaDgqVnVkOmFCZhcBsk6n9kululRCJQAr+wkAAovnNCQX+nvk/l/mDnflXC/ljnTlxmAVXBTkgAzmylgWZnKmQU5mwW6nwX1mbnGmQ6bI7J9BVlXnoVXCYWKD3kEUMgOwiC4pzJvl9n0U0VlCYALDumxQSArDelzI3AbBs4CACYsAGV2BGWvl9BpCDBZAjAJh0X1klikCDnaXuAKWQSWU+kFo2WKCtBeUWWBDPnGW2UDAZAnCOV5ATAYVuVaUVDunQFzIGUxJkVJAmVJV+ApXKW+VhUHB2WRUOU5AxUfBMCZjuWJUKU8JJ6BAmqZUKVJzKR1VUApCHD2XDAlVjDDkyXxVzDlA6WNXBVxDJglgNVEDBQ5UjXZWRSpARXHDFVnDOXlV9XzCeUTU1WWUzXjVmWbXKW/I5RzVHCZCdVLU9UuUVVlCiksWoC2BtWFVyU8jKBQCooJjyU+7gQxnTXWXkWPXcjPWoqtlCgNlJpSB0ahD1DiDmjJixShCShMAYjJTBRKTyIFrBQFRrBJr9Io1mg0oMjw09AYj/knQOySDWwMUODdSNIKhuJCziBIQwJ9Rw3qiNJQS00o2YjBQDoE3zS3RRgo04jJR2A82zTWzfriCZhJoFoi12CUAU0OAo1kCJLem+A80WLEC/k3QSCZiirzgs33Z+DI1BSAwqrErOoJKoRrJJCc7G39jWxuIE2TLkQ0pJBqaaGioWJ623X3aCF9Ra0Cac1yis3K3E08LvQDrkQ82ipIT+wUCsCCG0IY1UB370CtIxQOwCb9gdCyCMpMAR0YJEzOiCAggE2dh+Dl3g2+Dp0rSTJe0oB52TJuL1AUA4iJ250dly0pix0ljx3JhJ135J7zTp1Rg4h36l0l5RhqYt10Zx7W2l2G21VvRFCoRPRsx53l0tg20UDO1PR115385sDE1Ui0L4StKl2SAFpelzKTQpjy32Dn03jpUK0Cmsye5hDe3k1iBExWKtL86w2dDn3x18wiCo18wO363k3zQGUlgfL0D9gQ2ANd1JB0ANnk7NxU0SAYgtgrANn9jmJz04RFBPRp0uVI2TQFRRmp3ahEwNnWwFpszgQCE0ooMOytLWT/0OByjgSv2mgsMuitKpgENEAwMuhD2kOTIuhcMOwa03g4MZ24oqqRgOxsAy4sN0Z2AMaEMuhISdh0AYmAwFpx510ixcwujE2WHkSZjGMOymPx16MiwggrJaM0pczWShD2PlgenOomMkAl4eN8wBOV32CMoiwJ3vQeP4S+39ShMDpiDINICWHk4D3RPKPvS8MJNUiGOCEpMYKJK6MZOYNwPUAINaPkStJBP2B6Mt3pQSNKOJR9SCFVPyLBTR1KPEA33SBNNW2TJJ0fLAisyzTuMJMUB35l13050E0l4x1CyqJzLUOwYQPUA4gVNyBNPl12D9z63FOb1NMn2SCXSNLzR0N6OaHHjx1CP11UBcxK0nNUDvTTRbNrL4QrByKtKI1J1QOSA21yKmguhEZbNM2a0JM8K/lJMYM0rVD+xyI9KzSNl1CO3SCxTfM1WmimOhBB0sD9NkBP1yDItGUa31z63pRmjOnAvWXEME19T9MwK3OMCxQWLouUtS0Yi0trpxO+AYuJR5O0uiC4oE1cvAMJNQ1uK0J136MCsDp6PCvWRxzqjiuzR8xQs8IfIkD9T6O3S+0zPKtsvZ0yByjqvx3misvvSZJytrCxTQF+Ucu3OGPsOSj6NsCwKDO+BqbQsFqeDdT6NrJIQtjxPQv4Ssxsxesx2oQ2uzRM2MtmtUDQECbmMgsfLmgXNeue7WxEw/MYIMv2uHO+O3M3jJie5ZtXOI0vMmq+OFvUAkzfpyKTS/n/OoBeuioCZVs8Ia3tNPShBqu4MfKYi3OttYMduFvJTdtKsdlPRy1r0bbdu5tjuswE1DuyVyJ9vtsuuDu1s9tktLtWv2CdtXOG1iC9v4T9srtRvbO+tDOzO0Kdj9ORv1t5182z3nsUCX2oTjN6uFsl4piSBNO/mCHl2FsfJ83fMjNuKe32tRm3TWT1CzPEoMjHhgeuWtIl5+sUBmjvw3g3soCWF0adip1NMghJ60IYeWGYgDpAuzOy1DRysiw00MgQ1NO4rErvTwcKjk6TL0eI0fLMcYgl5uNV0FPSA31btyCMrEdUsa0eNk2mj6siyzSBseMb1IS8cODMfpQggzOZNRjmgcP31UfKNQTBR+tUim0wJBuhPzTtMeMDou1isxNmhQuQTNwz0qfWxIQeMYjudKc6f1uhMqrhMFOEUQslPefKO/NQtMB9MWLadyAqe/MzOhFJ6G0FTEcqMKgsO5H6NwMufOvP13VRuZf87ZeVMJPhfk7Cn/v9jl1Vsixtup1BeYfhf9iiquv0j85nPldy2SsFPUAMhcvwt5fx1cuWfl283ldiDMMFMpgbOevhfUtRerMTcn3zQZV5cDopi6syASf4QKP/u80a3IdFBE2yu3vBApvmPb1J7pT/sYidjRnP2zOJSph/t5cWJo14esxrJJfhf9JRj7eYiZhMdRu+vudNPaPNyfdzKdgu0g89KMA7sl5mgJ2PssDYfR5RtuKmjNxdMwKQdEfU2wJQcjOTLCmvvSD6sI0qqtffsx07tmPEpAeAyP0tiFsggl537fspiYgie2DFPQElCutx3l0CYYO4qYjHjs+/lcz8sqpIN0fDMm0fLC363aNJrftsOaPe0wLvxnsuvftJq3T9SNJYuphNMDrKQCaTPagDo4uyBNM+Bs0YehGZihty99QB0E1+UYIg92AXcO/6UaPredNy+4pM1736Up388HduIa0E2p2T0vPb3ajHhzfSDt2p18ypf8dcyv3Lfe2p1o0rM28LdIS1fM25/vckOJNf0lT61wMMg9IRMgjQEB+p+oS4r2dUCJKb0x/9gi8eNE/SCh+ZduJptCLsMYv5cWcFOtstgHMSDwOe/FcSClct9ZdpfldZfxO5fBdISxdr+6c7+I1qOMCMBJfVf9h+2kP6/Erwet1JrE0Nme6G2XRYcD0Z8Nke3x038I/9N8Po+etRk+8bauDYPjZyKCJR0o1vDbqQwx5lc8uWDVCJvw2yxMC+b7NHnAxTA0MHYaDOTGgJ74oDA+4XSQEzULYwI48mPFygP2IFRtSB5sIWF20zATsaBn7dFnwzXaMDR6zA61hQKIHWwSBHAugRIB4HOp9G5Nd+PgPv6CDzQvA3AVAxAZ0Y6M8DPrsd3JoSN4mkkZMMIP3qswlaQzKxCXml6Ft7au9EBqbX16GCvmibXQXMk9zmgueHZP+ulDUHBBw2QnaLmgIsRuIQGiSJvmT2R5kChYx9LmD0l8FMCAhNjd+IKiJYcDbu9gG2iYwiGh8mBMQ3Fl4KCFBhjuoQ1IT4PMEeDoG3g4IYYOG7QF0655TsIVwmZo9Eu5dEob+Vui+D+mbiLmMPRY5JpDB/TIFi3S5i0JDBfMAjsPVQhSASecDQweaDp7D0T+OA5QclBpTfpnQQkH6Gj0EAGchmRdMgCXRPbh8A+xNZ0JnV/pKD6uu7JCELDjoJ1eMJ7GlI53iYttD24Peduu2uHtNbhbA6ukTX6Zzs12Lw2KG8KV4fDjafbJ4cOy1r/Dy2FwzEFcOXqPDy2UgcbmHTvwkAg2CSMJtbX9oMDNmGQqCO9C2Fu15EH/MVgkk7BPQtaqEZuAsOO7Eo6MRtFFspAZYcty22jbEcKVVaecKhZIhkJ1woQqpBm5baApiBWAUJpAvQ1doCJJrFBBRUbZKP0wv4cjoCfMUvgcKFoz1kRIo6oHA1CDTdWGeveoMq2Uh8xbhvtMXsbSTw3hq+x3DBFICkBEj2GykAduKMxAyMtacIy3oOxkba9OgWtXoUjVx4wNx2WtQQhPU0Elg48GtIWDwggFCQ6u4rZuKaBmC+i2+6Qg4ZFxbD+1zRVtWkVG0T60IQx8iFMckNQHe1g+inZMcT3Nolg6MJHEoP7WJQ6C0x3tKMBglc7G0GQQtJOlGAwGVjoC18fWiCHWg21TmUYVMZw35bmxnavonECmAN491y6wpX0YwFFQliyxW3JIFqOpDyCMGngOPObAdGb1EkNo72iRzYDfoQWotZKO8N57iCbaEtO0e3WqB9Q+RnkNkaHylSi0BaFuQ2gTTb7h0Sgd4nEB2LsFrDZ6AtJPFQOO4kBoC+TCWseDMJRsLEltC8SWGH6xtVa4ouvuRCXEC0ze8Y/RslDEDNUSaogdAb4LuH+x+ReTOwUEN5GK0BR5sKERgkXoS1oaNKKEVIDjyATLaZE2up4LwnClzYzImQOqJpT4R92JNM0ULxIH85naSQO8clEEkkCBuuE+mpIFTDqi+oJrcWvIhfIwJB2mdUQCjXShIRze6Y8unJxJrKQGOAfd8dZHiC6Ss+6vS5p4CJ7iDIaLAW6M3AyoqhaK51JgEMKK7sVmKCZNihxTnLplwKPFRQFBWtQwU8yglNihgCLIZAuQfwXgPuTAA6kzgl5O0tJQup3l8KPuJOOtG8GBARm+VEAD6l7KLAvJmlfqstUFKMVfJE5VisBVnKgVgp3FJchFNXICUCySgOKe0UeCJSIASARyGVNQpSVaycVPCq2VEBJxUqz5LYe/RKmUV1KLlbybMHDKxUNKtU66v5MamcUWpi5XitBQ6nrkhKiFY0i/DuT9SRpGUsab1QmnukggpXKWoEDzpqVypNUm4B5PWnLS/yAFbadOWTJNThQXFfaWFL4pRSupsUpChkFBDmAwAjwK6deXGmPVJpQQdzuTlyqtUKKw0jaR9K/KeT3pMYpivVJcoBSAZu0hUq1IOntT+Kx0mKT1JADIgTcSgSStdJvKuU7pBFGJOIKgG+N7B/lBadjO+lClByW0h8lvyOqaBXS/1F6n+neoCB+mpNHYMpRbrFSpZANbgEDQ1ASABGdGf2rjTBH7ChAtCMYY2P1n4DGURsnrlvQ75WzBYDsDBORFmE2y6edstRI7JAaCEVozqS2SbIc5ey7ZMrX2aWKjDR91QFAVOlBF1kClzETzO2RHNQksMBu2oLYYTGabKQDZbo5aTAmTk59vgacmlObL0bk810yfUngHONkWIi58iZ5tWMHFhznZlcsljXMmFaz45Ucn5iXO6jhydZ5jcQGC1MxytNCZs2XhMjmQghTG9rIeenMLlNzO59rbuZHKVEdzvWnrBeahJYETcXGmYXOdW2Hl1B5OW80ucIKnkFyR54EUQGshE5rzzxHjGOcmHnnazF5G8xJofJ3nOgHZ7LThgfL+ZHyH5bsz+bEJB4fyA++rH2Y3Nmb/yQFD8wOeAo5HUATxcra+UvLgUCSH5bc5BSwE9y1z7AGDJOXf3PZQ0sFR83BdnPwV8dR5GteBVI09nahe5y9KhUoxoV2ApWmC9+MQvLlWyWFjdbBXIC7kNzvmKChBeUXtl80q2giuUO/NEW3Nm53UTJofOkVzzCGSeH+bsx7kYdJF7suXugvUUiLNFWHC+ffKUXyL/OBikJilRUWL88FKc+uZAqrlWLc5GioSVv1bk9y9+wi9BSw0mglyOFJs4ud6x8WNyvJpC6xcItsVZzYwISrWWEvPkhypGyixjnIIMVxLjFfs2hYwubi0KPZGSxXsIyYVH1zFjHJRvEtkpyKf5RS4xUgtdnAL066CqpVIttoly66YC3sYDEaUBK4J3C2KOUosWEK2F3jApbJUEUTj7FQzIZZM2CXgjm5ofLxd6yJFtL9aMysgHrOnkRi/FPC2QC0uCgrL9hBrEuUqMqW6c75zdR+ahJv6mL06MC0BScpvlL1LlaCtRVwMcVitml1dYBdAorlxCBlz/VJbmP8AgNil3ylgNkrQkCk8lhbRZUfM+Vgqo2EKpUTEtjn60RlfHeFaHO9pIqcugMCZXKJ+USD0VXnAQECq9luKCVAK4laUoSWL9YVXoqlVwN2Xesk2ayyFXYqxVITgueSkeUEoiU7ycVHK6uXsqI5PLn5jKpNoKq4HOLoOHy4lRKs4WWKWVynRBbooAUpDZVXKuURAuAXPyUVV865UvK1X3Kn5YqooO8plXiqXlLlCFX/I1WGqLVCqyBRvPFXkrZKDqr5Z4ubmWrRF9qvlf4ttVWqv50cpJfB3ZX7z/VsSwNcCtl6MqzlIc+oJGsOXnLwlOctVcKs9V4q3Bx3GlX6ucWSkSZ74xOXKvxV0r1lerPNaqtZWYctVJaxNWWv0XRrEloa3TkGszkoqrldqrgc2vdWOyN5OK/VevLbWEq0lCq7RX2oBWTz85Gc4Jv8sPmjqtlp84NQcrZXhq51OqsNV7Nl4nzx1wnd9vmvm7rrzZW60tc/RBbzL8xcy71qHynJjKFlzcxWpbQB7e0IVukohf0ryWPq+l6SolSTX7m/LEkcc1xZ+vHm3Q1Rv6xecstnUAN3JV1PyWLKNXhVjqasmWW9RynyzfkR8lPsrJgb8z4NgNT9AcIzWALZ5K88Fc3IwWULUFca2tUK3oVkbguRyhRYRphXEbn5R6wjRBv6qiyCKKYGDQVXmogAsNsspDSz0VkgrBAGG36rxqeoyzNZ6qj1bSqQUCrFVICoBTJrrnBdSVcvVtSpvLUuqtFDyzTdJs7WyadVjLVjaAHY1EBON2MCWRkD42IbJpCs1DdOJGoqzMNEm7DcLi01qbd52ygBrp081yaaxWmxtROp00Gq9NPK4NV5rA3BN4Onm3dQgxM11SbqbZcWe1Ws2ub+NdmlDTMHqAibuZ80vjVJvC2Zzl5ZAf/v2u/WObItG6lkVpto1ksZ11W/wFGsvl0bStK62hSPOY1taEtZm5LVxqs1/V1Ztm90vZuy3oa8t40cTdLLc2fBLmqawviVpbmcqq1d3cRZS2vWUbSNQiy5g+oI1tbEV26hbV1rkw9aoNHGlLQ9Sm1DaOZRAUbUrKc2iac+V2yTThuzX6N5tyQQhnfO+bLayF8q4Rt9tvmmLc1Ji6NSDve2Hbkgp24mX1ss2pbBtCGm7chqE2y9ctqs9LYVuDktaQtvasLVjuTBdMPl7WirQT34XE6QVL8n+dyvx1QdHVv88jdjulUuyGN/KwzXcpZ30rNVXy6nXVsp2Mced8aywkFs3VRsPtgfIXYup83Hcxd5jPVfrSnI1rL54O/zXjt50q7YhKoBkpBph04gLtPGmzUjsE0OactXgCbSkAK2vbEye2pbVbova6aNdV6kuaotC0O771G2xbcL1a0natdbGs7eZr11wb0tw2ginduE2m70d02jWThrp0sKtt1C4FbHtvXx6P1Hu7pRStT1GKLFFCpPZrpFl+7Yd91fXUHsN2h7Ud4elzZHsx2ea6db8hTVzoBW16NN+GxXYYtU3GKW9InGPZvKp1w1odSW3Xf1vh3PbXqJerLfdrR0V71Zmsp5WSu03OKu9sa0JdUvNVuqbFy+u6gluqkrTC+iWv6exTJlBSKZIMkqWDLgoQz6Z4iBGZlJWpYUUpuFZGe6XmgEzg1vMyzaVKoo4yZglVQakQCf3b6ctQQD1jtRHo4jAg9NVMIXuOpRUuqQ5L6StW/3rUBAf+sbQ0HAOHVAqv+pOM73iDSNDqQ+6A2dTgOuUEDiwJA9+BQOGVkw6BzYARRpRTVKDSeSAx1VOBOV8Zl1fqh5VIOadn9TaqylQeAN8xAYPlIoIwdg3MHoq3VIg+wbWllUeDQzXfUBX+kgUgZe0iCqDMOk0zopwlTQIwmYSsJ0piM26Q/toPkHx9o1M3VjI/1CzcZVU/GdvqgEKGGpShwGWmSP1qGT9Gh8GQhW6lQyoQCmA4CzMMNZTb9OFZAAvDlmiAZqX1ETa4P+0CyrDFU4WXjKIP2HA+jhkmTtMP0Ll3D4UlcpofP2+Gn86GAI2hRunBHjDPubKlORIAUJXp1FJIxwaqpEBMQDsJVUV0SQHcWwghYAy0cxBzS6AHRxVtoyYNFVTqrBqQ6tQGqIH/uHZM0BWIYD45oC8dC2DwFMozGbw/RhY8nR1oC58Di1cY7IekOwHZDqR4mukezX77lDrh7I6FI8PUyvDJ0nw8aXSRX6yjN+xQNhXv3ibJpcDFHcGo+S+lLDS0xIzYbWqkGMw5YF0AMZ9KjhVjbQSSFCZGq/ILEeBy7QQYOP8l4DCVH/RqHIiQmVg0gbsuEBoPlE8TGIgk9qPOal8BtJ1Fg6VUxPEHsT0xzjebGd6EnYTGBvraycsoAptGqJnjeifpNxUSD0G7k9CaJMBUSTXJ7BgMcpMhKaTgpyQ4ccmNb6fpRMm6hccCnNS3Dtx3I5FLP3eHIZxpA7L4m1DHZAkwSV42zNkrfGHyeVe7QCZCjxHgTch3wJ9JONqn0jykK3ZcZcPAycjp+zqYafpkjQFEBh6/ezI+N36wjhu5Aw6aIp1HP9JQd0wydOO/SCA3p0mVcf9O6nAztM7QxkDHxWmkZtp2g6RQGMApYjXnZ029NOMinaDvyCRnYBtq0IigA6AUQId+Tk5J6RMfnPYO0aTa9jYxoU71XrOYHnJk3Ak9vTlon8BDScW+o6wGPPlKuaIhU/sZHNZSUz9Rr6ucZ9NamVDOptqXkYeN0zCjHUaxOGbeORmQAnxmMxUYEDJQOy6Gqs2mprPbmv9TJrg7dGR4Vntjwx4k2se/M0stj7TLoysaHN0mlTDJo45wd0rZVhS8Zv84xIAtwWl+iF0C8sZGMLVhzUF4U8kY9ObS/dmpg/dqZuNHn9TQZx40ac0AKgU4l5609lMmmPmvSSafre/pdN1n8LqZz02ZuIvZnVDuZzwwaaoshm6MxZowzeejNxBYzAA2WgEK8AQGgTtZyqWCfdJsAx5Ax5E6IH/OSm1j6l9GQwGmlGTujYh0Y5BeOPQXJjsFgip7nkTE1fj5iHS3CZstJxZoAxoy2Bawu0mJDFlvC7YZSM8WiLe5kiwebItUzjzwl088aWYwSVSjDFmSyWGUo8ILDYUxacpYaMyHuLhF4mXxb9MCXyLR0rQ6dOLL+HmZcVks7eekv3mQD+U8boSYeapXBZIJj840ZxNiAuzfOUMdpbNpxHOT7VjSwwCfbGWSeXlxU75dHOfm1Lrl+y11Z1ooDgDtlty4NbmTDXWVa5nC+Nc3NcX3z8x3c1mbyuHnwrFF/M8VcLMaYSjo0+K9VeJSJWRqhzRM9YZauZWdr8TPa5kdIshSCr+R4M2ebaJlXLrFVqS+EYE2CEnzD2xS41YSOumHAY5gQN6Y+SaXg5Tlvq/Db5weXMLpl7C+ZeqkwWmjcNqGi+bQ0MAtLOxhawTYdPo2TL3GqA+udwsTX/LBFnc7xeCv8XDr6h+45FYLPqYLygRiMzaYiOg32zT0kasleKnsX0roJza81d2vM39r5MsK+zYiuUWorJV46P9dZmA3QjVV0s0QEFvimGrb5pM0kFhtjdtasp5HnNbWso2AUCN5axvXjojXMb3lmAzjast42cJQKh07Nb+ZW2pTHtxHrLz7P23D6cRiCz5ddtbmjb8h2W+9dCufWjrhVgo9FfOvq2gj7xnW3DeypC3Gmd1q5g9elvG3trUdvjm9ecPy347it460VaeMlWLzvNq8zacqvA3Jptl2q/JenP53ob9gE25MjAGI3HLyF3S2pYoRo3qQnlp22NYjuTWbLRs/u91YFwo3Z7hlsexjepviGXbbBlU3YcCs5WWbB1hW3caVsnWa7GQFaOJfKMZ2xAScYiu00e3UnxbL10IJHcesy2grctrIxXcPtV2k7JVsM/XauuSWtbzdqayIbkv63DqD94uzDenu62R75tkzj7d6t+3e75EAY0Na6OO217Zl8O5vZ7tL3WzCDnq9WcXsd80HK1jB2tbDsb2Jjz9gu9Hbfux3rjn9vU4nZ+vJ2eb5ViSwLevvKV7rSlx+26aLsv3XrMdsux/cpmV3WHIl36y3Houa2vjAt0MZ+3G4iaIbht4R0/Zgdw2W6FWwPg5fnsLWdHhM1OivapvrXsbuDrR2IH3pz3SbKFmyzY+Xuipx7WDrGzg5odCO6HJd0R76fLsSOv7UjlW6fZTtyOuHINk1ElbvtrXIHGjwRwzaytM2GHYjj6/45YffXpHxpBKKbFTt82NYTdw3eaCBVgOGAPpA2zE68fQPWr0x9KCWF0czWLbiD4h8g6MfzGg7q10O2idptS3GTVTrg1BHsHwPur81+x7rccdtOKHHTgU106nvxOBHz9Uu74/EfH60nJ5rm/ZA4cA2wnLdiJ7nbKdpW5n3dzx13fm4LP9zTD1J3mervUWMgCUDZxrYkv5Pqr/To+gpYgf7OoHhz3p2pZBoOmSbyN5Bz85BUmPnHq98x+4+VMm3kqQLfR3Y6HsKVsq0Lym+Bc6cbWZnz1j5yc58dnOczX11Z6de5vn307At8LuoxzugSoncR8p8c9uBHO0z6pvfdi/ysJ30nQTsmOFBycN28nQNgp+F3Aed3OLXzhx9Iy9sNOiHr5lG4C8DvoOHblDlFxY48eCuJqCL+p4Q+GdwulXgpMh8HcwdgvqHEL2lzvY1N72/Hyzy5z/bOt3O07154l9rUYBkvtQed/hxi5pezPnXDhrFyFfOemuhLyttZ45GlShOL7jzy+8lWN3oa1HVLgV6pfhfyI+cfzwe85Y1cCZ3Lpj5F1M9ReWPFXZlec/ZfjcL2/bfUIyim5BdmOqHhB/V669ifzOPXrNg+ys85v4vNYIT/+yWYiPJVY2SaV9ibtFv8zI3Kl7p3S9OeeucXzLvFyfbJiBQOXAD4N22+yphuSnjryGxxf7fWWk3nV0V2q8TdmVsqybu2+0+rNluMTfl6NxNRzeDPLbSDwC2e73cTOD3cr8F5ZdofUv3XSTxZyk+9cc3fXjbmKxdfucX3Z3sbu11294eLv1HFTz5+i6rc+Sa3+95h2a7YfFk67nDoN9y+quFv+aC7vZ01effSBIXTVdd6q99tXufzN7mV5M5psZuFXJ7sygkEJuOaCHQzoj0lVo8OnpXIdu9+m/lcVvIP4HzF6+8Zds2AnLLtZyFliubP/3AmxSseE/YsW+HS7iW09YHeGuGXw7pl5I+E+NuGshL68zO4E13YWLT7MW+86g+rSpjXBpCNGwJOLGXHW7qxpZ9/NDGE3h7jc1iazeZhkrGHjoxhbMd9WMEpYqz0hd2P3u9Xj7g19laNfv333AZn18feudNxZHLbrZ7pSjLKVajTrkzy65484ezjMHk19F8/exeQznRKd/I7vOX2LP1R1MHZd7fGfePmXsz7pWRPinHO/ztY4+b5w+lYot718857ptbW3PT0KgNzK6C+VMQrX1C2K8zmEmuvZHjjxR64+hfK3dXl97vci9x2LnMXq5/TJczaf+benihHU+UrTT+X/b1U+F5U+1u4Pm3812TD1xie/36d3T5NO/OVfDPNX7D1G4a8KUUOfOAFI5/zeAWfvv57z2m/m8Pvj3X3iaqEXst/eB043hStD+B9LHS3wX8t4t6y+DvcvSz/L0fa2+FHEodFxLxJ+e/ZqajbF2r9l6feY/+PqnwT/W6/fjuMYlr3JyEYUeSe46FBrwHR/ml9uMrkPmj96Ic9w+E3fVs0IL5AvI/Qf69tHxD9XdmU46bJ2H7C9s8YgTaAXkH6NemeZuMfynxQ2+/W8fvcfN3kAEnFayBuiX7PsG2T7f0U/PvZ3xJ6t8Ycjv1PY7uLz5l29cugHhu/T8pQBTlDKXtvld+7feigGF3M39j+K79sh/SWhJlr059R9Hv6b1Huz/zmMedfieEf2QD0YSAObmvY3+P5x/B9J+lP53vXwJ7rfweMnmgHb/d6td7emLedI75jPk8HPTP9vwmUO8u8beCvePzJ5Jlr8s+ozXv6q1GApdFcYjRnj70H7at3ZUWQv5X7568kx/rPoL3r909xttW6MsbgL/9+ANb+heSPmz2v7Rcl+HfEXp32p6E+u/tvzb5Dxb5RlEVojrFm31P75/t/X7jv5Jwb5x/f2EPNztW+b46eqHiG7X2llB3bpey3rh5WOhbsBYwu8Pqe4kewLsZZS+2DiF6y+7tmL4IWQLnm7AGmAXR5dASLpr6Ue3Hqf4d+WPlF6CWPfsb5n2A/py6G6Unusy4cItmP6vmvPpLbv+IjjT5d+hvr/5V+GQLTi0B07sAERGFnhNrPmk/lDafecvjrTBAKbk9zquD5r8j++7RvOYoBbjmgHF+0gS6CyBDACCwA+qFuu6KME9lr5Ue/PjIFfsugYDD6BBFNoEWBkgB2RzSg5gn4uePTsn62B36CY7yBtnu4G/mTQE9rH+2vqQEf+5/l/5euP/oE5rOrRJO6AB9frpQS0T/ml4t+brlAFLe2XumZOG+vmEGUBRvn/5+G7LjEGe+bPkxZKBT/n74SBy7nz7SB35tbAEm6ft15Z+IzmZT70tQXMj5+QXoX4aB/Xm4GdebJnUGzekfm149BLQXH7tBYPp0GueOvqX4ZB5fld5UBuQes6kYv7nX6MWjXtV7zGqrM35gelPmF5n+F3rB7d+OQXwFQg+hkT6PewgZJ7ZqAJm86v+ktloFNUyPiTxLmdlj0ZJwsfI07zcjpk4EdBMvpoHB+9wfUFQCQdrNS+erwcL4oCWrrNQBBpgRwH0On/pkHO+V/g26M+fuB770BDZGQ77AWwXb7b2UwRkahBCIfT6FehRqIhIe4nmcHD+l9grIeBRnCd6VB7tnfhzMK8oVIaifdEx4h6lZvzgJcLVJFwugvykQELe6AW1al6/xoTzmi1gbdq/IP7EUzm2vXOsKuOztj8FdBZgQyEAQpWjKGDMcobZ7ChU3qKH7M/IUX5Kh0gXLR6ckgAXwm6RUvzJ9WxodGJ9MkTnRiDC/JmMGKhEwWYHWhGoQZ64GZAOtydmJoTAi8GFoWJpQhJAdIEl4OImsgzmz9CJqQCAwe6T9gEPGabfoImh6xehsrt8GJ+hoe7ZxhQYvmx2hiQKhDAGoYQyDhhjwQ9rRhDQfKGT2gQdIHahwWtpZCGxYWyG3a85p3LMh70GabKB+oeMGuByoZKF5hqVM6DvwrIZe4jaY+sJqZg9YamA6uQYej5BBnAXCEzBBwbwGsuH2HkgFI2oEUioh1VjWHCcYBmNQQB2wakHU+84bT4V+13vMGigf9nf5ABFISIFJwewmoJWU3PpNpsBinloFRGz0trIL+Ufu+ENAawA8GphToemEuhWgTCaHev4Z+HwBD5qBGEyvLBOb9B5Ybq7Oh3YTCHeOXAfsE8BEQd+6/om4eV6m+M0nzJiaL4cmY7BZAX7qZmF/nT6V+y4aKAnBV4Y3bnBk0k/qTcm6FN4+ks1ERGF2bnu/CzG7QjD7qSpoHWxahtDAJG9mAKKaC8RnYUhEb+0xtZCzG70EmIMA4gK7wKgwIX7ayR70IjR8434NdxM8xgcQEzhKEdW5kR+OBRGnhcwUcHrOf6AUGxmAKH4jagN9gjS0h7ATiG7BGZiZH4hl/oSG9+mgNBg4RT3o/ooacgeUEKexEVxFh0ceEhwYhFiAHSNh8suHLRR28sFqtmyYO0JVmkkUBHdh1Yc6DNw5sNby84gMIlAKgfIY0GOM2tJpFkOMNLlEAR0vhlFHGhkdB7GRxrtj7ZBS4Ws4ohgga24Ca1oSga/GT4U5Ff6KQELioAyfpNC3WJFP1qi+PDiNR5S6US4FRgkLk14jU+OLgGLR3QH1F6RAoTJSUAJtjiBW6GwZZqTRY0SpRw6zgX17lU20SRHBBewfwC4uSIW77cEHUQ84MRI2mxEYyZYZ9pJBGXjIAm2DIYQ7yWPpCyglRbiOQ7LGGMoDEVhJgcGH0h+HjsYfhnHEDGVmyAXDGQhp0ev5b2KRukF4h+vrdEM+bvg/BPwhAK/BiCX8DhERGKqFQAfh60Z9GQB9AFT66+0wSFY4xRIcaQa4fkc9G0GnXruHsRgfnSFChPCLIwhU6ktQZrGskUtxjh9IOAwQx+kYKHTGN4LMapUYdPjRAx/MaSzmIQsbNFnRmUe7aixc0gAaKxwsSNoqxCsRnSOhNUXNHoxjNqRHHhyhkzHeRZ1mLhMy1kVuGhiXMf1GhRkwa5EMxNsaO53RRXgG6nB14UUHfOSlCNRsRwUa371eW+pjG5WN0T7G4x9Mt+CE+dESsEKUvyEfQkALAQhEcRmjm56FOWAajoZxjjO8EIRKNsqz4Bb0f+HkeZsZrHSRfTrZHEUhcWCGxRptsBaGUN4ACHzS04RD71RfHtbGAytscb4dQ0QQHH0RN4V1Et0ylNlQrG2cXE4Y+0cU1HvgccczGaAicaTHjxR0b5RuxnEcn7qWQkrBFFxk3h9G2eu8YmFWU7cfBFHxXcb8FtWoNoKz7xTccOE2W2VHfFnxlcXN7VxaMXTG4hMcVEADx8wTiRsxY8ZNLmGX1CY7dWW8TnHJ+j4aAl++OtMAbQJxjn3KQ8GsZ/FWOCCa06wJkwrZ5WU/fPEwdGAdKbGoBUkW7ZtWrFr2bfgnYPKZ9WrFmyYUJxfNSZXxGYaQlKRZDt2wGxBFGQkDGdCfKaMJwEe7bjhfOAjTIJJUQIkDGedHAkbRBoXwnMJ8iGInqSWCdQksJqwB2QSJUsZtFMJ0xqInLWbCXv4TxikQzz0J1UUQm1RJCZol6J9gf2CGJj8R9QOBiCQYk8JqMSf49xO+uQHtIS8XbGKASxI9EoeQCUlQhk5trCYzxlTh7FWxN1N0Bref8RZFaIAwN4n3+E3im73h99jzHORAVt/ELxbiS76+xhRpqjaoQxE7Ehu/iSBbXBkgdP7TGilC0HvR80odH2WMJsVK8JWsW1blJylCYy4Brwc0k2MKCSf4gRfxrwahEPRun4OmuDHNKdJVYRgF/e7SQokFu4yQ9qTQhCeoHEJkLtMm/GTpqL5LJtSfzL1JtcUlRLJSkbgFLJpFI4EjJpgVUFLJQyWJqrJFMctaJJRifMkmJJtnpTzuImrMl7JlyU8n5hkiV2FbJClKckbYwySVGYBciclB/JaiVIkNJ0xnpT2WZyU9q+eAyUC5QpDCY4mjJjSQkA84vyeclR+PpMTTl0aKf4GIpxyRgEopyiUCnopgFoSnYpxKbilph5sfcmYpgKVSbUmMKRICopFKQilUpNcRbEJOoSddG/x7icb6OogCUHE2BQgAEkQJs8bOGwhYST/GLxmSfHGFGjMmvEoyWdqAl3hhyfuHYhqSZ7FYx+5pEnLhOSYMQKpulMKlFJoqcElmB2gWpjsmxUr56/IU5JakbJeKVDEz+5ZiNQtJJUY+aVerqSCmfJpiVwb7+T/n0kiJPpMoEfB8KTckKhdyVY7mpEyewnNGzqW8lzJ4adSmRpzqcsk9GKaesmBhDqTOFaBKabslupKaQcn2pbKagkDeKaaGnWJD5immE8RaYBFJpbnn6kwRXgM8n5pVvk2nvJXqQsnJpYNt0AspzcdoG2p5ae/HGJdaW4GXBg6TGGcyQaaAnjpCEZsk+pulO5Hkp9KX2nZqS6cCmIREaaWlW6PacukVpMgVORrpmacWldJwfmOk4pDKVH6rpc7rulDptySOnipqEX3GxxMqcvEZAEhLEmBxZXtw6AwKbi/4lJvMdU51eXQH95k2P6VsYJplYfik3xQGYjYxp+NmBm/GEGZDHZp7tv07/6DngtYwZxNpsFzpX8ZqlSpGSYiGyp0VtAiwICCB+mxBT8Qhk2pJqRB4PpRkU+k8pL6R4mfQ1BBRmFBX6V1HKs42uHHJBtMZdFzhIQdjG8p8wYQBJxZIda5dREtJE5OmQSXRlGhm1Gn4FRX4SLGKZrTmxH7uPXlmkyxXBnQZscC7oGyQRdYpgrimY4hfGdx2mcX7OJK3kJnapImRZFLIAqZxmTSLYNS5dAILLRmme0gRYmz01sO0mrcAhMAYdG5sEBIgerMDxKbumyRdFuermTKahx2YkWFwZY4jiKNkcQqNQbiLoGoGJpNcdFnJ+nGtQCtIuEqo5WJe6eTgHck0OlDtJ80CXjtMRyYca5ZZgbFm0JrSoVlJZrmbJz4Qc3MBmBiFWaMEfxONg1neZj/olnhuJWU05rGFiWpwRhO4Q9resSLHVnQWg2e7ZNZBJt+C4okyG1k7ugXC2ZEUhtPHR9Zw6Tlkm2+WXzAbZo2eul9WZWY8L7Z6GnNnDCHyVJFLZbVitkYyJLNQBtZJQTVldZk8RDyQ8WWZBn1ZJth0YUi72edkkp7pD5n20Lzs+QekTjB2m1RDWdZlpGriTqlrOHhH0jnkPhCMhLBg/pfauZ6cdvSeZkcS5FcpZfozH2Z1EXd445dAUP6CpPuD6SVe2ou97/ptwahnncWwvlEMcuAVUZPBXOQ9mbpO8ezlAu4ckrEKBYgELmB2UZJLEbp96VUE852iaLnHxEuSKGYMSGdLFWZJOVdFk53scxnG+/wECDi47Gd75h0T0J+yROfgUkk3Binkjk5eaEc+lEZr6VCDa4JXk9G+Jk6XtFVewaVnHJJr4WzkiGHOeba3i3OdumaE0ubhlWONTvmxAuvJorkkOUeYHah5aueonSJZSfLkdGGCElmR5MPIHYx5SeaCl1RmuYJncp0qQ7ksZ+uSCCOxI8SnHNGJuYwG++awU9pyZbfoXkSpxeYRleRg8cvhG5tOc5mxhYdAkm8ZX0SkEhJWuV7H9xFOW1HEA4mQ96SZk0qaAhxvUYPk0xP0b5RTmQKhVk6ufVtbBTUyUSLyHxFmcelIp0xqzCsMrHuvnmZk2n1Yn570CK6W8xcZfGWZSoTbnzxESRPmNuGqAAFV5rPr3kcaUNE8GYhTecTkappOcgDhJsdqjmNuSiJCB4gBqQRRxhtqTVRL5B4SPlF5BAGAVf+EBYz5KIdoMgiwFRAKGHzurZlTFYhp3i3mPpkqekmYFbvkohZgVpN3mX2x4N2mIFzORUEpJlsaPnKJr+brnzBL8J/B5JX+dVa/kTKZYHEFgBd9ECZreWgUEZVBfTLYFiIHgVtk4EFwl7h1McgX0ZDUcTLoFwmdwUWRAANSXhEmZRnmahSfYHcxVue7HqFvcbZnk5OhcuEdQTmdrZkxyrhTYERjeT7kWFIYfjgtmIZGcw+eftqhDL05tvZFOW4eW57z5nEn2ZsANnlfn8xFJkUBCQBfrWnspeGSAVapNhaXl65U+QoUvcbmYjaiF7hdvGWFLiXblMZGRfMEL49Bf5FwFeUgPksFIUYUUhhyVt4U9A0Rf4VNFQRQkUHZd6ckVWOJ+WyY+FrRWsZ9FcRcEWJF/WYEHP59Ltrnj5thZPnT5ywYbrwFeRUgXqp7BagVj59uR3nnhBhTPmjxdOfL5HRSgcUmsFvuY0l3hi9IZSEsYuYN5FZVlFcUy5PRW56FuPEi1TwxYuc8UXFdxV8FJFJaW4HZURWV2RvFKvucXGxQJaEUoFkhRsWlFWxRZGxgRuJXnJx9AXokkA/xaqmqFqxZykcF0hW/mM+ruJUXsxzRihxP+RxUTniFA3q8GfFU6Xv4IuwhvcXglZqRSWglqkW16Mlr2d8XjFUGZok0lrxcyW6UrJQ0DKsKMYfnQhZBQxnWFOuWUWwl2oPCXZFCoA3m6Co1LgmW5LOdbmilGhTdQTI7kdoWSldhYUTU5QgW7nmaMJv/mklw+WYHdAnfE8xkO9zKbxBZz5G4im8TwdQBJ4uKGGkA5KGW1atmWymBLKF5TFIArJftvYFJocTL2bKsJLARxVxh2b8VFFNmUcCallBTiVu+MKPQURG2KehnLWxxfUWQJMZcjl+68ZVwU6lfriUQOFwDlUAOuFqdtRqppSVwa/GGxjKJTeU8QITdCJUcKTZicTMnwDGM1OGW8S7JVGUnpnpbyaioayHlEpunuIqxe0tnh0Zp8jpVsbEiCKvDn3pkxekb5l4BYmUJxMRPqWdRPxsaUMAU8SsWkFwBRwXkRGBWuVnm2RPiWGlAgMFmCx+ReYUNF7tpEUtM4nA0AmMtCBGXjZEOclZs0e8TEip0v9E6b0l3mX3IbMLEt9SC885ZOV/eIIP6UYy1kG3wdh/OYuVqlVhUcDHl2pTCXLhyZZuVJeBFCY5e5RNnWG3lKpRYVLlZmiuUnlsxZp4ZEF5fsU5F6ZYZREVJxR4WZhAMeOJ7xKlG+UQVV+Qi6osz5bzJsMtpYhWPFeWXIr/lr2V/Rx4wBmVkhloFbBFrCUgMnwLZ3cchXFFxMuRXoVVEWs5YVLucT6xhvYf4DIqGcZUnPhBRdmWkVjUQWUYVRZTsULFPeY4UCa7JvtQ/UbhXeXZlQ2RnQMgfFYqUysklS2U2235f74m6bEXfgKVbpchk6ZEOb8jvwb5SxZ5ShtFxWBlO7tBWp+tYSpS/k8FWFXq5T+SpWxlbkQmWUVjPtpX5JcsitDylvBnuV1FEcWSUQl5BXGVoVdmQVVu+HUJ/mIldlaWX+6YGXWH4VPPqZVip0gc3CYqGbN/QxI2TEhxSVHnjBKxGABufIiRSldfHMmRnDyKeATahnGeyrJlJU6OFXHEKFxhUXPBCV0ZeZVqV9VekVWVjbkVUCFDBT6T0VXZClYkFb/jlW5ljGSXmnVjPiND5BF1VUUTUr0SNTiAmZVVVmlh1eKUzFhZVhHzFuOW27fV40YEm9VpqXLkQ8FWSbqvl4OQpRBpcePfmOa8IjYw1pHJY6mp58NaDELuhtJaEFuqNZDxmhzXt1VzV2VYeXrFaRRKUvVTVZNAllBTlgaWUv1aaX8Zh4fTF01wNQzU3+YNTTmX2tltgZVe0Na5V9VqGU1RIxhNZTUlR6lpYzo1I3tfTY1fZUfl9OUtWBbRpxNXpY2pjHqlVI1lKT8VOJD1bblPV7eZpWaev+DRU/5X1UdHOahETDV0ZgNW3kyFhRudWtVIbpDW32smY7VeZGAVPE3gBNdN49lJcSTWo0ZNaxFY1R6UbVq1WVPjU52vUbLXvFpNYrV5FSdQ8UHVJtS/mrljVSGYEE2FT4m0VwtWAGZxR8WIUA1WdVMVQlz1RbWFV+EALUAO36fRXe1HNT3a61F7jNnB12tSA6/sQdZ14h1D+cKW41fTu3Wp12GUTXI1uthrVB1XZunX0lztdMWbFtdW77dE1tfZWMRawDxmVVfGSbY3W9Hgi7c+AhsfQm67/FTUp5fTlPFCwrZZBKT1mdq0EgeFuZlXJ5YKRfX31u5QYzd1NlldUEmB9SrXdF0ZUaHH1nZR/W31NVrrHANN9YbU41BkZXWd+vNcvX0yUQdkVrILADJmt1EhbVWL10JQg2FG0oDZW45n1XDYtsqXpsHl1nNYLkdk0tbH7p1pca0awxMtX/XZZADX7khZBNZjVmMoDYU5/c2jFrXR10DcpU01kJTzVL1Z4VKVWRF1Yo4e5pFOg1c1aSZZU4NLMYrBr17Vdo76UW9Q7Xi1sNahlh089uKYcN0Kc06UNmtWH4D1B+THWclfTjo30NidZ/W62IzFQ2z1jDe6UCNaxUI3Yludfj7YRKZeE4e5mhDI01VYpS7WnlxpH2jM11VqNS9mU6dvVD55DWYGsWbAHIGVJ8CVDRpZ29JiGAV7tqxZNCy9g+wGNaxvE0tm85kk37V/ZdMYa0JYN/QKWmIYok9A4buk2P559e6RZNvZkU22N0QL9WRNPQGfUv1TTR03im6QtgnImnTQM0ZNpCUM39N1TX7blNiUHU3dNXyUsCVmiTW028yMzTk3FNC5cJVxN4zWs3LN0zb2ZRGTjeFUa5gjZg3V15taI3URUYGE0MFFCE35/pTFfeVtWJ+QrX75JusiYcm/hWsDS1KzbCajNx+VGR61M2eNGf8JUfhBAqxjcC0Tl89bA0o5wTdX5eNBdXElwFf+Uial1PVZo1O1MLSUU11FzWs7OE1zREYggraY0j+NcTUGkgeztHv5++YEbGBgCeed6k7RViJZTzmseX7aj+gGusEqU8VXM3zpnMtS2NptgKaxi57LTS14mvaZGX/1pTb6n8tnLSy1JZIrY2l/eN6VplD1MDSc2BNUhflUg1yIRYRWI2RV2TdVLzqGJYJZDSkVYlWrXzVnmt0AS2SeacRjK3VprbS7Z1FFdq1JlgMA3VblwcY8mFx+5QBl9OViC84wmb8ROm62AbR+EHxkWQ009NNli2w32QbR3GX5yDrG3htD8belMNxteq3qlQTR419+Nra2SlVaWfSCkt3mTNSnxRnMgFBZpbehoCYyvn801lpTixbltgxRDkNt1bQY4lNExVi1m1rtcaTOgHrThWkmIsOIHRNy+Rg0atZzT23FkEBMo3e+jZh1w4g9eYa3Ft2jdfQdxXQAkC8lNlkpHC+V9Ru08tPdjVTBtfypYHgxx8du3WNoYr2WStnbZm0oVWDTi3mRy4Wb4SNenldWjCaWWUEjtahQvUTtcLTc7QEebasHKQeDN27BAX7VIErtC9OZldAT7B816W57Tw122vzVG3zNRDau3QdTwZEJi5vdn1DWNkEFC0odHKf9Wm1QNSI2PtInv226VNgW+2wc5uWiV3VbBZiW017ja61Fe71R7WEN3BltSqUVZfdUYxVdcI3YNuLWdXutyDSi0MVy7TfFhxFcQm0LW0neBERtT9fnm8tv+nnSit8bRfkCGanTBGNxHdSq3mNJAT+2CdD7YcF2FtEYYUcZ69fdIjMKbj7UYtftZv422O7bBl1OVLXBGZ+gIQdSudHbRY33STndY1xVyzaP62CF+b+mH1PnYZ1dtpHUJ3kdZ1RuU6VSLT7g2d2GX9U71Y7Vm33t5zbF2M+jRIB2pxnVVy2+trOdBnyIqGtNUwMuAQDGCxdGPu3QByJttWfUweQ3GNdEXR6VlJ9XdV2JtgFh10ClOIrV25xCNGV2ddmGaV3j644dckSt6bTe2uNpzcZ1ZdpnZEGSoAHd43PeUVdx33NWZWKlGdLHZa3GkdRDO3XWWBk35i1xFY80yRXhWN0tdYuU/oYeasTV2tdEVbQbH0AQlzJddj+k+z4R93Rs3MNbVnvUvdV3bZ5/dLVIrl1tj+hd3ixFXSVHUAy9GN3VpfDarUilt7apXRdJna1GNut8Et3+xHtREaCGaWVFVFdqpUj25Vv7Tm0+RGEAd0FJRDIkgZ840Uu28dxXdU5RVgdQnWXtC1jNRo1dVqz0Pd81SPUdkzPeu3a0bPYGKQ8u7YL3c91NTN3jtc3ZO0ZA34OI3Y9knjn4B5S0WVUB+9nUAWS9GXST2sduDUhB5dE1NvSxQELHR3gd1ZWpZM9CNZh0TlKNuz0i9Vvf107xFvUHWwdQvaPSIdQdsh2qtLjUx1uNFrQo0rx1eNkWYBY2TnQm9Gjad1mVUXdm069LMaSG7FlnSo0ggbEdx1mFEfRLVChMMYh0olBUcAbAxwdiboIuQpQZ1tdXBr9G6NqLcux5NI2ojEQt5TZX2spxfd73EdzrRpXCdyIQi0Jds+bGHPkP2an0PNkfUT2PVKPfN1o9uJX9b5JnHQOghxvMkqXROvtRr0+9s3Tt3+9FrvL0WdJekO3fUWHmn1aNQoeFyS+sFTQ1+2U/Z7j0NhlF0Lw917b50h6+/RC0ZxqdLn2QQzneBEP94vS6FGd6lQ1Ux9PkQ9AU9ERiY7plAJsxGR09PacXTG9gbRLagq2UIbpVAZRNmekGtIub6JlrOaD1NXvTz0Q5vJulDNmv5ngwdmflSok9mv5rCwu0DvTmUkddVVqVf9u3fC2d4K3RDk1FdtqS3bdfvW31u+ooNk4T9BJbiaXBzBeH3996fYgalVxDO71WIR/RNlXV/PU6Vz1hHbDbCDHPVfXdyQWZIOW92iZ72N9xzZr13t2vTQOr9ChcIO/mffZt2mpLA/I1sD9MpCDsd6/W1Ul6XkiQ3GVknbLGaE3zfE3W9x/YKVn9rg2QPVhzg7X2VmcHYbHa0ng/4NkDpgznXf9eg/QM39Ujc5XKlAgyYNR9mXTL1QgWPdYOAOtFQEWPJt9ql0xNJtuTHdmWfaxbZ4oLdlG19fckX38NGA3AVPs5feS73FV+aUNsN3tVe1TdiPVoPI90fboM/odA4i1d91RUdGekBPSRWJDOgyv1Qgw8Rx3cDUYO7SuxoA8xW/drwaY3ld58R51VJakYsOw98Enp2zpsg1Y7Q9YjGrErDm+WpF/eiYQImpt+nZUMS9i/VL3L95g5409DnfUYVIGNtrMPolB5e0PE90vX+1QgLVWkOcdYLc3XlUjg1wZgtKvNhlwi6NW91wFtgB4FDNsnW/3Rt+Bd5S/OfPdsNHxV+SiPYBFTQiPfdGbZ8ND9nQ2MOWR2RWC3Q5gJu8N8dBIxQNJDPw+xD690QK5aLtQw4UVhDLrV0MkjUQ0sBMj00RKYMdYA1wbHRB/cwEaCoDSpT/ekTqKNQNCPcPVNNeUsKNk+A4hen5N00s/2Kj3nXiOx1T3RnSCx7ZqA03WC/HWGP1k3c41VDutrSkvlKics0Gjw1TDmHNWVY01UZN4LqN5aQvc6OoGWAt4OoZWdsNX00KxhK5qNHo9ZiIjqHbAizGLo8s3PO+EXqPSjV/bKNCuiSBGOcN4XKlR+joQ+dQpeAnXcPZdbvtCAcgAIAl50Rf5M2Qg2eUixbIm9HTE5RkZrcx2sDOY/TK6FcfXX7RgOPZvUV9OQ6O2yN+GXWMLdjbvwV0RLYyDbwWSw05Umt8/dVXkDLfdQPEjkILt6Dj2zvBKjjHY9+0jD3w6T0ZAL8HOMFOcio2koay4xiXN9WYz2Oj9bvhqgTD/w1MOdg3aSBlzDZ3bpmm+ItXHSstIsdfacSImqixejQoQzmqxeJnLQXZx/d+Ml1H4yGMqd8soBPoawE9d2Shj44DAg9uw256CG48hBPPjj+q+OWU4XFgmg9IeuBPv1GIP+NrG2+e0xr5ptJ+MyRaE+KYphVfbQbkTy1iRMgTNY771mD9Y4UbUkRJE+i9Dzw2pzdp+Pab1UjNw1r1rjEQ4oDUIDI5EayJDAEW23jA/cqYAWZmjUxIC/TNZBlCdomphmBo/nOKtOotikAIoEAKYBakBACGRPhdkJwZmaGrD3R34CAspM1GTtrgD5gIAE+42mEQGZoZ5rQcFCBwk0P0yX0XANIHqT3PjKGTaOk3pN1khkzcDGTFQGZoXyVgW5P6c/YJBKqTQ+rZP2TRzo5O8AckxYhYCrvMbINCowt5P8Jf3n5OWBX3TeYUgQUwZM+mJk37q3Q9jZlNsiWdEbQ0miUw5MawTk37oYISgY3xocWnLVO5TjndSDGOWk1hQlT+k8gDb0PU3gARTcpeFl9Q62R5xs88oY1NWOQQEqN8cAJoJF9WQQOoytO4EABXwTyfkS3CkdHk8FrTbLXeEtm20+mOD9NI6AXZjvY4z66F+rfEH9TPHZSOMdh43A1kdt0274mEf/QJpEtLFpWUvTAo003ktqLdILCtMrQ+H49pE76lMtu4ctN7pCresFLTGoxnVStfnbEi7j9gm50ctD4fBb0dWEz7gQzugsjNBdRM4ZVh09LZ2ldjqRVoXTj9w9FYWEmhPq2aE0RlzKt1PuuADQAcALCD3ATwDSCckp6NwD8zYRl4C1AVZlwCCzglN0B+hKfCXa6AWADSDCzZ5CMyboqDooDyzNIIJSRTqs+RCMkIADqS2osIBAAnIWAJySKAyAKAD2opRcYA/k2dngOdu7ZnrP3TFsyABWzs2g2TQEclp7PU9HYlWzmsSeK/Qzs4uYPRtMVculABzwc+Ozhz00JKCXMds/HPyCVcg7Oku9s2wBW8pdCnMJzttvozKOkAz7PKOmuiYAjkec3jS5zHYnrOognJG7NCgdkt7OlzXs6xxhzQc9HOBzIc4QwK6/s1HPNzY7CpApANs2EYZ0Zc0POEAeswCBVzluvnMNzXsx4xTzk88rTvic8/XPK0MajAzJzQHnbOFzYwCORrzO8weJ6z5AOPPuaHs0PNTzUHOVQtzkc63NriEcxfMtzSc4vMlzGCHOx1zj857NnzRQGnOZzX8xOJfz68ynP3zS84AtPzSvC/OgLKYO/OXzkC9HPPzJ85PPvzu86nN4M4YH3OFAn83/N/zwIHrNNgLs9XPA0DPL/NrzSc2gsIL6c12LoLCC0QuILCc17iXMxC3QtILlC1nPJzE4vQvULM86wspz0yrAtLzabF9wcLQHnOw3z3cwHNVy3CyXP8s5C/Qt2KTC2gubz5wIUAPzoC3rOrQOC5bq3zwi7EAWM/C526h8QC6AvfMUZNosHiGc5It4D7fLkR50ozFYvWLozFXKKLZc5MxQL3c6ItdzQixHOTMMi3gNVyNiz4ujMHiwQtoLTc24uuLDVnNpOLQi1XKvzJ8+3S+Lvi1XLhLISyYskLic4vyzzeizzRRLXsz9ofzVC9/OO0Gi1fMvMsI7ksYLlLOjTlLFS+jQALmSz7OCLIS1fPZLCS1fMwLaS5APvzNS/XPt0AS4gtBL9S5HMYM3S3bPeLsSzYstL9iwTqL8wSw0sTsBS5fN2Leiw4sLKlS8stG0FjPktTL/S+wslLOi3Iv9zFAgstezes6GCqLR8yWDqLwS1sueLofNsuBLU/EYsgtGvLMvRzRS3z0jLVizzTTLt88MtvLfixAw/Lti4vyeL7ZhOyfLzi4vx9Lt8/4umL7ZmHM3LSC1CvJL5jMfOtLb844vnLXc/EvorzS38v/LrrNMAHLtS1sygrN84wuDLcoXZIbLd86kv3LpC7WLQr/86kvjLhK7WJYrkc5itPLY7LssKLBK3jR6zioCcvuzq8/cvzLHS8At7iYC6fMzzZSysuVLly90u6LxK1HNyrpi6Hy4rc02suIqTS+LkirEq8ytzaQK+3KhEEK0HMIr/C9ItkrJYoquBz3ixasTsWq9HOkr9K8MoGrSKzksGryC0XNXATK7ytcAvYAKs1zXkvavqcbq/KtjLBy7TohrKqxIuIrnblXLGrN82GuZL78y6sTsMq8svfL/yx8v2rGaz8ul0aqzav0rbxWEusrlWeCtWrcOfqsWrsK+6umrJS5Eu6rnS2iscr7K5SucrOK/8vVLDa2KuXM8a0qvUrcK7Ss7aaaxUuOrySxOKirb852tiLLYoWuxri/EGuMGb4PIterPKxgh6z7YP6t4Laq3isFRJa7Pzbro6xws0Lnc4qsvMlDDyt2rs6wk2Uq065+xJ0t67LRVyXa97NcQKCyusTresxOCbrlzAutnrkawguh8D64mN3LcK1wsord64eu5LHq1vPvrz63rPDg365Yu4ryqwBuO0A6yCAob9C2BtMrI/FxpDrw61UuL83q92vIr4y+/NAbAy1evZLKa9GvCri/BOvgLiaxKsQLe66IZ7irayIuMrq60nTPrEG/2s1rRK9mv8bZKxOIHrXGxOtNrray2u9rta1nP1rD6+3TobrqzSvkrga3utxr5a6qv5rgK7atZrJazmtvLlLEBu8Lry5mtK8HG3LQFriKzMu8bEy16yabtG3CvVrIm1yuwb063rOLgiG1gK9r2SzGufzLC+hs7rfm/CuCb+m+JvPrSS/wthcZyxytsbxa82v0bFmxixKbzm06uybv8y4vlrkm8as6r063mvIbU/HptPLmG4gvYbBK7hvBbofLFuWbhWyAsKbuWyiuObni5ltarTW90tWbUW0xvRLcaxOsYs1W9ksqbE4rZu0IvS6etFblK6NsJLGDGJsOsgW9lugrPW8+vJbLqx1slLg6G+v7LH6xViHzgq8NsmbNG+Zs+bBmyMtdbkq5YpBrbW6YuiL4G7LSXbFC5Ms+bNm/VtEb3G3UtWrUqwsrGbJW3bMKrQm1ougb+Wx2u1baKkGsNbii+lvoLLW6ysQ7hC5Ms3bP6pqvqb523uuA7Py8duxL42xCurbWc6mtfbOm4WszLSWxpv2rp23AuLb065yxUbk2+iuUbwW/Js3b14rZssb1W29tNLU641ufb8OyZvBbg640iabkG9Quo7by+zvg7iOwlvisrG7gorb9G0zsw7tyw6zVbuCqDvgrvW0LsjLxOyWuY7ny9ju/z6256ubbcG1wBhoO2wGuDz4G0JL2bJO+3Ma7Tyx+AEqHc95tWrpO6XMW7qDD5uubApAxsjzniCbt4LFq3OvqsBG+jRq7sS0+tB7vGLHP4rE6wLt5LRLIrvfbnC3LteLwm2ltLrey0avlres7ONebFG9JuKrSezCvhbeWxAy2rMe6UtbMvW2XvMLRm1zsi76S4dvvb861RvO7bS5EtJbd21IuL8g2x3s9L+O9Zst7k63DverrO1it2LwWzXs4bH297TcbBi/+tYbkW2tsJ7QHtcvS7Gq97S57L2xJtp7283rPck/VLgs9r/O2WtW7IO0jvBsS2wXuGrPQAJvr78O37Oo0XO+7x379O2RuX7rq7+vzbTi3XuvzC+3Jsp7Y6wPtti/+0euAHk/P9tXLWu1AtL7Oy+2tvLf6xYu7r4uymMSbiKqvsZ7rW6gdVrlii6uf76i+ju+LIez4svLB29PtH7lu5rt1bN2yZuPbWEBtvVjXAPCC+7tc89vkHtu6FvFbx+5ruR7iB1JvAHUG6AfwL3e5Qdkb0B8Yti7vB+Aehr2+16vBbes8QhebDG2AfFLN+8wdUHohw8u0L6G+oeqrWB2vvxbEh3wtwrb+9dvD7+tEBv37JB5czmHqWwAeYHha23vu74hzltN74+yXv2HRexzsa8Wh3weC7whwcvc73e9IcCkxAHrObjXmylvA7cc9puSHUaxXtLb2hxPvlbU+xStHbRG64firLB6ETw7tO3RsiCum2Qt07Xe3Nt+HSaw4dO7De2zsuH9y4kc1L3O8RsfMlez4ex7H9KXtNH5e2ipoH1wK9tmH2R3nsJLT+96s2HIB24fWbQx/wdBH9B4oBDRls5bo0HNu5SsCH8xxCt27pG+GtV7/m4scgbEB04cLbWx1Icbb6B6yt6z8hQodM76xyFsZHDOwkdBHewO6t6zLIEwerHmSwEfFHlx+MsVb9y20iqH7x0sefLAxwSv37NBysdzMTK/fs87qmyCcAn1x3Qdz7pW3rMwFpxw+svHKh08cSrHxwDvcH1h20fV7nO0ys676CyCRWHz+3sexHbx/4djHguxMdFAesxqiPHdzPEcknqG3EcU7Ye+Htob7q/EvN7uJ0kceHou2SelH2Jxsddiku+cfpzNx/guFres1mB0nih4IevH3x2seMn8++wcTblKuHu9MhRzEdMnufJqfKHLm1ScLres/fB0nmm3Fywnvh/yfMbopxCeyn0J/rtZH3q3rO4FXmyKdKnpW7/sZbbpz9u4HYK3ocOnUJ2kfVHZh6kfvaF28GeN76rMZv/HNS/fv+78YmptIHXRygctH7h/ozgnLC9VvWnQ2+Ht+caZ/UegHJm3GeU7fm0+tRnBR0Yt2KYZ5acnzJm1yf3qOZ81x4bJ6wkt2nMG8EefHes9/CInah4KcXHCp88cJHPR6CdZn0Z7qtVc5p4ntUnlW3rOlIdJ1TskyQh7fvDnOajsdf7vJ/XukHf26ifRL7J2StlHTSzUe6r+51ivAnrp2meLn+GwRu5nUexFslHaJ5yfpHP61eulnj++Wc3LlZyjt3nNZ2PtBn9Z0Hu5nUZOsu9rg5ymeIrIF/ofOHfp5CcxnP50YfhnlR6Gefn6+yGcI0ZZ0ucBn4rHWcQXux3mfdHoF2au9nvOywCZnhFyXRTnHZ+qgyntm8icubX51PPon2x/hd1rXp4nurneB5EdeSnR/mdDnGF4cezLo57Av37pp8Yd97nW2xe+nfOyftxzuh9TQX7dF5PPUHqu1Od6zVpF5tFn3+6AvSrQeyOcqnWO6Reib0RzefF7p+4mcf7dh2Bcy7CmyJeB7bJ+JcRLXe4ruEHPixSfNHh+1uf0nFO4eewL1Bw5sVHo+6JdrbmxyIJqrPe0MvYHFq6FcMrOc49tBXyB7efsbIZ4BfcntR+BfiJBO6lcSnFl2svin9R3rOOQdJ1ifnn8p7cd7nKuxfvcHihzRdpb8l/XMMXoayMcEXWp8qcmXEh0lcdHMl/pTJnRJ8ufRXWWzxcwXyOxyvWXaV/3t2XGK5wdsHTF3/vn7nlzVeQDil+VcwnIR7CRUXSJxleVXL++Gsj7GixXznzLO0sv/nQW9xePLFmzpdTXnp0VconPB5BdGXnh5Wupne1xZsCXs8/fuynz13PPjnlWx7uGLoG3rNKIdJ9kcL8TV+6ewHwuyxfL7wJ3ttnXO2njvA33p+KdKbeswghqXZ5z9eMXMN7Xv6XkB18vhXha8NcTn7R/butny69HKsbes+CBebgN3VekntC/HtY35l41do3+x/acpUme1wAvIdJ7+sZXG+5df6n/oP6cdLGV1zdY3OV9xt6znSJzeo3BNzAfoXgt+Dcy3P62ed6n1V7LcNrI56LdbbigLCAA3aXBeecXWB8rcAHCNy6t6zps15szbht8Mcf0hl1QC+X51wSf4HPixksMniF0NcHX2l4NetrsV6Zu5r3BxbdJn8V25dhbvNyreXnBG9DfbnZ27hddX5VPOeYXj57He6njuwedzXg+5Gevnf5x7cK7Fm+CxkHzZzTtOXNiy5elLOV7IdcAdk+bc23VN9jfNzEdw/uT79NwlcRnt13yf9nVp/OuS7kV0B6bXSa13cB7sl7NfVnUdyNdiXTdwhdIbZm/eqw3CZ17e6X2u57fGrhd9YuO3oy+KdJb3uwFR0nKaxpeLL0+40ch3th5nfh3jd23fdb65z/tvnzW1UdwXdK0ndAnqd+Asr31i1pfH3rt7Pd73LtzPeL3DV4vuArIV6Dfq7E1wscAPoe0UdXX6lzeuP7a949t6zbODMenL/t+mdL3Vi9cf83Pt2DfR3gd5JfB3lt/wfJXuq1VeH3KR83cB3xl6ffD3MW09eX37W5ZcM7fd9eszXKK31tnHAVzjvbXUy0/fvL+D95cfnbt5uc4P116CvIPAK8Fe4rHu4YeeLes6CiV3BW2/ff31twVst3G59JfpXF1FhfYPHB5HdwLu506t8M7e+7fH3bu+UdD3ZO5fxWXft1XfHXQdxwcH3VtxjcN3tj3g9j3/l5g9kPWj50t9H6K/Q/ZLb19Q9XbZV4PfkPpj3kcE77D/UucPvy1PeY3cj0I9z3UBwvexPCj0DuiPma6Xf3Les1rbwPgq1Te8P795occnCT04vAntpyffuPre4Gc33xDwhdtX9jzydw3rF14cFP9TxDfinNB3rOmAq11celPZuyIeFP6i8U8sPzTwreJ3uRzU9lPad3FduPydy49jPPT4qdDPYhyY8ePm+xFving23rP8kWT6bslPjjxadBPyzws+f8Fj7I+4PezxPdo7QD8sc/3OOxE9j0zJ0w+5P8j7U8pXfT93NhPDS7c/jrgzyPeBXaT3Ct6zVUN2e9Puz65fjPj9/LfGL3B+qcmbku9NutHILyXdoP29689CL3t9C/iP0Fw2t6zLwEC/zPpz6C9zPApzE9FPFV989Wjo1848FLnz15ezzJm8Jf3Pii9kv0v9t2vPRb268I/qrGj8A9JPFzwi/V7xz8k9cvVzy1c3X6Dydv9Xatyi9dzMfNxuPPiT/bufH1L+3Per0W/ffCM2R9FtIPVJ9C96zbs1s94LUN90/9bO917Pe3RZ0FdM3Ua60+9bes/DJebhr3y9CnlLxw/n3mlw/d2bj1zJt+P921hKy73r53uMPjL97eWHRj+PdivGO9w+0vcr0U8Mvei0y9VnKj6Md/3YjyA9xLrD7rs+n9l4G9xvmb+Ne+vVl6m/OXHd/tcsvaC5q9WPA9w8/dPDr/i8l3BxxIBl3igPGC4vRL1/fyvYLx6/S3Gx6S+ZH5L6PdVPLj+c+Gbkb3PN0vCb5W+Mv0b/0/XPuu0q88vGD0K9/Hhb0Xci39b1Rt6zhEC2/t3xL9O9LP5T6G8nnPb108pPuawY/DrU728+xvr8/G9IXib1FtzvURyc99vgV2NfWrAT0w+5vb7/m90Py78vfFvVD6W9mLJr3qsTvcb3XfQvddzs9KPP+38+SP3k5zdx30H26/Ov4T90+Dbdd3Meuvu93e+/38d7+c4fNz7Q+v7cTzjc2XQezo9jrL5zhsv3575A/Uf7r+/Owv3j+Tsfv/r73usH3L/s/lPJ78O9H3tHzu+XvjTyJsXvqLyR+13yb6k/1vFG3rNNS+r1x8TPXb32cdv788JdHvwL6B/Xv7z3MvpvBJzR9prD7/ncaLLy8y89r+9228kv9bymt6zfwFvc23Jn0O+APjr/CtqfeL4I8xve7wp/mvfl1S9YfpryO88LGH2Z+KfRF8i+uPd1w5+gPEuyzuwf3S3rOLAcn0h9lzIn9K9EsgW8l+BzAzyweWv2p/J/gveH5U8HvPn2F+t3qF9E+1vOJ0J+6Pq7yzdtPXAGiiS3JFwJ+ifUTw4/lfTrwR+zvUrxl/G3Fq3rOCUCX5Q/AXXX+Lk0vo7+rdIv8L+Z+7vzzwQ+QfRO2o8J3hL+3fKf/LK6eFfUyxi+I3XAErMDfwt058hfdnxdvcHe31N+Cfj74K/BfZF1J/ZHeszsgo3jX6d/Nf537y+PfKXyysPfl3wZdPvWr9d+On2L48AQAfwOKStkoAKoAUgIP67OIo/IK2SC4txmRUD6bFBEbAxOtOG3FSsAJYBYAPGhdK8AbJPmBnAgU8NPiMO+o8A6kOpNoCkG2agHqDAugHwDeT1qEcAEqt3SwA+6ls1D84AMP8egpA8PxZqj6PdMr37xaPxj9Y/fUjj9gAePwmAE/bMlYlcAJP2T+2g/AJT/9aqgHojYL2tvgY0/CYLAD0/7pJ5AeyDJNpNSWBCNk68zjwDt+SW3IHLKJMrfGrMZc1v7NQRTPP1uGgBqP/zJa/WyAz8gsayNjQCkiSJuhdgur2z+aA7EyAAQACKGYBxkrZCz+8a4P/wCs/ygND+yknP5jHYgjv5fayRrMIiZdVDOEL/HU2P6H9i/+P0NNS/tQDL+k/5Pwr9W6VP5FQa/dP+786/ctNAxzGxlIOTHI8f+z+J/cP3mUI/JekIBxZWf95M5/mgHn+4/hf7pOE/D/MT9l/8v1ECK/lmsr/cAqvwvDq/tP0yR1/BFLbeN/jgfIB3A3+X4Zq2Jv2b9skFv0hrQcpkjb9Rk1vxVpc/fut39bh4kMyFOmbvwz/44Xv0spL0IcvZGMsAf239B/CYKH9YA4fygAkf0HIYPwgAEP1b+Cf1TIH6Gv+alQuMbbmPoP2QcGA/0x+ufwL+CYFl+5fxn+lfyV+Kvy+My/01+2vyiAOzkJyLfzZ+MP2gByfzgBknn+KiAMF+KAKH+aANL+cvwp+2AJOiAphr+q/w9+lyRIBZQAgB7fygBnf1gBPpgA8Zwze8YmnR+9AIyAI/3QBU/xYBU5Cr+xwA4BWgEIBqAGIBRqlIBbf3IBggI1KVAOe8CAIaAYgKe0EgKx+jAMUAGAOn+J6hXOFYSUBT/34AagNfAO/zsqIABQoXAAP+nJCP+lvz98vIQ60d3GNaXv3t+r7jbcRFFSoAaU4BTTQ7omJCGY85lf+/vy4AfAM0AH6C4A//0ABwALKAoAPABZAI7+MAI1KWhQFs34DhiWCWMBufxF++f3F+g0zH+xf0n+zAIr+8gJwBC/zwBaJhsBKgMZ+0DHnAGgMgBY4CT+AnVRAuQJBs+QLAq7aS0Ag/ykBJQOkB5QNKmVwGl+ZgNkBNQLcUCpiaBa/wB0rQP1+u/wxg0qDcBLsw8BJ/0yYdvzsWdv2yBRwF6BLdn6BDFTR+zQM9+fv1SEfvw5m8QIyAwf2SBIKFSB/VHSBsf0h+mgKyByfyOB5vXoM9/UKBwwI+MowNMBxUwqBhQCmBIAHMBcgLmB8/0X+E9gWBDP3v+oKij+fAI5+2gLjKXwJssXZm462DGz+kgIBB/UjGBHxiL+oIJL+0wOqBWANqBbAKgMcIJ1+TPzaBBvy1sGMEncGwP6oWwNbIVv38BewP8BBwM1aiPxBsmIIxkxBVsBRAKEMlwJ9+MQJuBgfzuBf/zD+jwJuiIAJj+pRUqsBCGQQL8GQQLcGZBoAFZB/AGI4eNDhY5ChSaU3G5BGQUJan1EbSFVXEBzQKEAJFEc08Fi+E2eG/+9YAyAiQMUADwIj+coLSBCoOGibwI6BwoC6By5Vv+l9i5k/+WESxwH+BkljKBpIMwBlgIUBWgGpB6/zCEF4HaB/AM6BqIIIAKf10BTTT98wYL+BuIPDBZwAhBswKsB8wJX+ygMWBBKgTBSIMyBqZGD+FAO6BAYIiMrFmMq2YMOoofzhwQoI/oIDEf+YYMeAaKGuIkYIsBlzApBKQFbBRyCKBmgEeA9wG5AqKFBQTtgKAtf3hB0/QkAlYPeB1YITAtYOXKGYM5koVGMcRnBDBIfzzAo4OaBjfgFIXYNzBPYL7B4IJmB5ILmBI4O5AY4IyAE4KnBcZGtIl2jnBYQPjBXgmXBPoNh+RoIyYqfwbBYcR3BBiUOo94KjMEYMvBZIOjBg+kaBJYPbBBKjyEX4OTBvoLXBqYOQA6YOEBAmlFqoCWFYIfSPit4LghRqxYYbGyGBuYIOQOAHzAgFCYBUYMHBN4IPBd4LDBZEIohGgFnB1gHnBOvyKyFlEQhKIN/B6EN5BwCQOarThwh8plAheYJkBkEJohRYPwBbEKwBhrVJ4nYK3+iYN4BVYJTBPEPrBmEIOo0A09wuEPmkIkIJBEEOohs/1GscYPvUf5E+UTf1fISYO4hyfzUhwCRzcdiS0hwkLDBekILB14MkhMEIIBZYIRopkPkhQpC4hHwLrB/4MwhpTmUKDkKMBTkKBBLkKghc/1wBZXikh74JMhQpDMhCkL8hq4NUhgUP4hO+SEhYUNzBzkKvBUUKMhsEKPBqvT7U5kOaAlkI7+d8FQhf4M3BPuH3o9kMoST2nwhRUMXoRqxxBPGkYhlEP7BkIKLB+EIYhN6CYhL4J40b4NLBC4I4hDGHKhqUOsh6UKaayJlASu4JzBJgPAhkUIkhMYNgAxkPghn4McB6QwyAACVcBfM3cBYAGP+bIKho3gKhYhky5BmMRshTTQEheCRbYDOGaBU8RiB14E8g1wIdBTUlh+SQJlBboN/i8oLAB9uSVB98BVByCGN++0M2Bh0Mt+Q8h9mLCkc4XMCdMDvxqhyOiyG9tSMBzQNOmdrgtSDgSv4EoJ/+ToOlBAANlBP0I9Bf0J5I3oKQhH0MUA64LIqQtAwhz3hMKOwJbBdELghViBAYIEL6h5EM6h+kIHBhkM+hbYLZhA0JYhHkIXB0DCnAE0JTBlUJ4h1ML4hfiVQa79WxBPMMPBZYOZhApFZhpEP6hHMOWh3MJdBjML5hHMKH0w0KZhryVaoosOQhEsLBG9AVIoBJiaogwN0hEULyhK0LqBMIOsBhUMVhIcSEAKUJUhyf0lh5sI7o8Dmth4UKWhdsM1hxYMFhOv2OERsKUhK4I9h3QK9h1Vj0ot3V/GyrQQiNsIDh4kKDhcUJGhocIuU7sJNhnsLNhaHgthIHjYAKsMWh+YMDhrAIKhIcI/BtyhWBTgK8SigA1B5v0t+NJQ5BCTHxwp0N/BMcMp6HgRVSrv2aBD0iehI1CKA4oLehv/0+h+MO+h74F+hGQMjhyEPFhucIRhMxgJM9MOHBjML7hhsOLhx1A6hzEK6hhYPFkvUNVh7MO3hNJn1ha8OFh2cPJhXJCqhqiDzh5XiNSrZTlhWsLbBp8JZhbUM3hasKPhGsPLh8sPohB8P5h8oRPhLsLPhxsJ/B88JphC6SEMo5T9hOUNthqcK/hclBihav3ch0kOEYYcJFhEcO/BfoKphN8JECYkTiKRcNfhDAJThBkLgRwcOQR5YKzhwCMwReZU7hhLX7qVWUThR8WThpcNgRQ4NhBzsIXBqCPPhVCLUqNCL08uCMLhUCJLhYkOIRrCKdhlcJQRFCPpBmgH5Se0NN+B0KOh2oObh9kT0YbcIuhAnV4RTFjvhPcItBisL7kA8Izia6H6Q2MMdBUxzxhKQPdBzwM9BaAFJhVkO6BC8N7shTU2CTCOERXMJIR6cIIhsQycEsQP32ykJzhdiLARNlkggxbgIRUgJgRIiKhBCCKX+SCPihVhzVMIsBrh20OEm6oNBhLIPBhJ/xQ4l/y50KJnsiv4PsRLbHAaTjnuhZYO/Ar/3wE6dE8qRiJHhUoLHh5iMJhliOJhXoORBspEpheZQXhxoWmELVANsukMBB4EMl+GFGjAn8NERpCJiRgQhYYyoGARLSKEBUsIbMG2H7Cno2QBwv3xBQIL6RW0SohriKGR7iLXhwGxekEyKvhbSJQ02BhiQPgOpM3SKWRvSKJBqyJ3hrkNWh60NGRLlHGR6CLJhkyJ0BASN/0ByOEMDyxIhiyNF+5yJBBlyM5h3UJuR7CJ1+2yO7IuyJ4hbSJQ4mfysonyNORPyNH+EwIuoAyLLhGyOiRGcPX+oKIeRPiJnhl8IhRryKQMUKPwixyLWscKNKBCKPH+uvyuR+ULYR4iIJUmKPPhzyLjKkKNK6cyODGoYJyhPSPJR1pmRRLCLmBmyJdh9KPBRlAPxRxmQxAxsS6RTkM5REvwuRSKLWRgKIdhDQPYBwKIxRcjHGRUiIyAl+lkRh/zSRbIL0CLcNmYptByRLfRx6phgh6bDCKRDPznaooNsiw8LiBkoIphZiIJhk8KJh08IwRV8PRBv+mSsP9WIuG8KH+UqPGBFKLBBgyL5RaKINhtIK4R7qLaRXqIERvqJGBZyK5RxIKqB4SKLB0IMVRVIOVRSwL1+W0KVBI0GsQDcK1BUQHZByiKI2+wMxiHqKQM0aIXcs/TiMcEIuB3v0CEr0LtROMNMRNSKdR7SCnhrwKaRAgNNhC8JaM/OGgG4rWrMpKL0hKyKJ+DhmDRKaMiRNKLIRCIMggDKKvhGiOS8EgERMeFW0hOkMlR8aOlRfyLHRaRgnRQKNpRs6OZ+2aMN+ooHWBKSM1BOqO1B9MOLRtvzUR6RkXRNgWe6AXkf+5wJFB9aL94lSKbRJiMvhraInh7aJdRnaN8RICO6BQvBFRXnjAsRrWpAwfFjRYEOYRyaP3RZCKNSwdGb+jyNsRy5VAx0yKIA9gQLka3BIaV9kQkNaP9hcGPWRIaKVRtKKQx3gF8hlCKvhGGMN0rZjyYkRhIaGfydECyNQBRCJIxbkLIxiGMuSyGIshqGP8h6GNH8mGKvKLMz14lTV5MrPGP4hGOgR7GPlRlIOp+GaK1kcjDFifGOxRbqJ4htGOqsQdh34163GiMLA8mJKKIxLiLkxFcO4xymOSh1GI0xQmLoxDuFh48liaodgDb4hmJkxxGJMx06JiRFGJUxZUP4xk0JAx1mK0xoVGCgfjF5GcDAPEmIWcRcqN3h0EK4xHmJ4xlGJQxamLJh3CI1KmmMvsq0wlEttl5k5nHNAp4KERkWOuR0WPTR5GLixXmIyolmOT+qWP/6hvBWg/01qcsTBWMEWKpR9sPkx1f0UxnmIsxPmKjhgmK1KqZUxSqdBZY31H6YuugnKjWIBRUWJaxigLaxJWI6xiWLQxZFUqxAmgLQzkmgxGMjamCdBCRsGOMx42NMxsWPMxVGM6xfiOXKCoB6xi2NcsFIjfGVlDWx0tFYxhCNcx22Pcx6KOEUe2ISxcf3UxnsP7ShuiTwQhj6gBfQ7IrMDW4G2NEh+WOpRYiLMxLDFKxcNHKx0cI+x1Vi+xOIAYop8RmobfEo47KLyxTWLThoaMtB02P2xs2IExVMJhxeOU9IbshecB1CggOWMBxuUN5RnGKKxYOJcoEONVoUOKOxBONTKFCDAYhow4qDtgGao2L3RhWIUxxWOexqmNexSWIXRzOMWxl7RAqTfiggsPE3c3OJRRpGJpxu2PBxM2KFxc2OoRouJcy4cj5M4mOsEQWMgEFOLCRHGIQxiuLpxyuJsReOLVxJ2I1xOIjyYH7Q2w8DGKiqOLYxd2IKxE2NjBU2IFx3mNxxvmKZxluIfIRnDfK5AjJ8zGL5YN2NCRsmPuxoOONxvGM9xKuPNxPCPVxfuPtkpXBYs9EhvA+An1x4eJdxO2MexSmKVxOONjx3uPxxvuPO0fPT+YN9hNQ44kVysuKpxRuJzx7WPzxZuMLxeZQWxLmUsWZeMLhqbGly1ePgxvONax/OLzxL2MbxXWPmx/mLxy1HGTx4phP41ElDxm2OBxzWOzxcEPrxg+K7Rw+Obxo+NTK/uMtYV9SDSk0AH4GeOdxIOOGRdeOxxy+KAxyWLjKLeIfIKHGsgfMBbMPFQdkaURnxQOPRxbiMxxZYKXxguKHxh2JHxxeOMKy6JcY+zWkYd+DFWXyKdxW2KzxD2MXxJ+M/xK+O/xa+N/xbZHFxb5URsAjD2Y0mLRxY2IgJkeOPxHuLKxB2OAx3WJFR7ZFTAwBN7MNqRXoLtHQJYBLnxGOJixOBIHxMBLPxNGPXxi2KJxDsnss00lloKdCoJt2PAJh+P5RDPw/xMeK/xBBJ/xRBN2yCON8CtggWYjuN4JNBNfxdBKgJuBMhx+BPPxaYMvxHGlwRP2OIGx4Gw4++L4J8+MgJWOOUJDONUJzBIQJo9EA8t4mUK5ODLx+hPkJqKMUJxhIYJwhNgJohItxRBIBi+vBj85QzsJT+MpxPeNdxa0PdxLhLwJXuNXx8eIsJS2OkAxoh0JyePsJL+McJCuPoJJuIbxbhLUJaEOOxYGOqxlvCkJW+ISJmBP4Jb+MEJ0BNcJTBIlhCeNwq4EAyxo9itoYDGcxGBJ5xQRPWhQhLCJBeIiJGpSyJwmPkE9giCxLZnIJLjEUq/hINxbmOwJShNCJKhPCJcBMiJYGNsxvfAhGUuN3xPBLDxB+MMJYxOcJqRNPxOKIyJwzEqJWGKfYOmLvxQBLKEBRKaJC+I2J0eLaJIhJ2JOhAsJXpTtcRtGvqnIilGr5m7xhuN7xk2P7xmxMYJ2xJFxdxOVYirAXaC7iCxe+OGJmeKKJThPfxpRKuJ6RL+JYGN+qnPHBGhJinxDWKMxDhPlxfONpxlxMmJ7ROmJnRL2JV5XxwEGI7xl7Bgxz+MKJaxN40U6PWJUJJMJynGPRDIMcyWqPkRlv0bMW/z0YbJOKkzkwJJ8OLmQME0omJyL7hfckYoD0hxxpMNHhWsPHhQAIsRoPysRuJNxRnwIRhpVRq2yUTJJw/2WRMqPOi6JOpxmJNixxvHuR86J4h5aPBoPSSSiLbDVJ/qMJB26K1JiRIxJfeO4x+pJ2R+BMZRaYONJrZUn4qpIpxlpOBBiKJtJFJNoJyRKgJjpLBRzpMjRYGKwMFqXNJXpM3RAaO5R2pNrxQZNVRhpMVJYGNsi5cUsCajmHRGpOtJlKP9JChMDJWOODJWKPlJLpLQhbpPpoV9SjJ/hO9Jo6L9JZxKMJUJKLJyZO6BbpPDkIxQtJMZKtJvpNzJ9ZJpJJRKbJQqJbJC8IBM17CBcVZNkJcaPhRW6O7JGHjzJSRN1Jx+IHJoZKNJw5LWA67kzJG6KnJsZP6R8ZI+JbuOKxS5KmJCpKHJnhLOWQRQ7JW5K7JFKNnJvZKPxiZLGRzZOXKxpKay55OjJl5J9J15J5RgRPOJjZKTJg5KfJC8LjwTBSHhb5LJR05M/Ju5OaJU2MPJJZLDJ3RK+xRNDHJIFOrJnZI/JcZNtJOpPtJepL/Jy5JTJ8FIoQkZOQpE5LxB75NrJPZLlxmFM+JDpJwpR5NLJCTGfJHPlfJKFNIpmpPIpNeL3JwRIPJNFNgpK5KIJViGG8TFOIpN5hrJrFJvJFFITJhZO4pIhLopPQMAp/FLMM19no6WZN+RM5K/J7xKgpXFIfJ/5LIqDFM1cvIzaMQ6M3JYFO3J/yNvJAhI8AQ8K0puFJPJ+FLbMTfgMprxKMpI6NEpalNGJd5MkpVlNopcFM+xXgIUpZy1d+TlOzJqlMgpP5P7JUlLcJMlOfJd4XspSlMCpKlIgpGFIkpv5M8pPFLwpn2K7MaWUUpAVI5RqFLIpYlPYpGlOopKVOkp3lNhxbEVPiWVPEBcVITRplPEpHFJaJllINJ2lLzKbpLXJMVOyp3yOMpV5PQpc5LtJVFOwpxVIippVLSxz5BWkJukqp2UM6pzlJzJ+VO/JDZLCpg1KAxkVOHJbZPapVVJypLFJmprlIjx7lOSpTVOspAFPhJ9sjWpk1OKBuVJcpIVPmpFlJgpJVN4p3RI6MAdhf0KlAcpScOqp4FJ6pZlOKJ11PCpS1OGp//QjJJ1JORb1JMpsqMSp9VOgpP1JxRy1LAxVRkqaE1KBpG1K6paFJ3JYNMKpA1P2pXlLupdGNhSZenhpDRLOpm1OCpqNNCp31MWpUNL+pSGmVJmVP8p61KmpQVISpvVMnR9QNihX1KqAjVJ2R6qOHQ+/3PRjcJP+mIP1RGcRcYRqIE6bpMMWFSQtRj+gKiooJaMhiLIAxiPehzoP3BUpKeBspIaR1iNhJaUIXhyVCmyFUVAp01O7JQaLqpaNJzxegk3+aRPKJU0PsR4HH6K/WmUpNVJ3RZxlvJqaJZpkJKFh2BmzEVGM5piEGSRciLBhCiMLRNQAFp9ChYi1JnhhIqNmg3aUJyZCJf+0tJehn/1pEVSJbRkpNqRzqPqRrqOFxmtPDpM1Eh4yhVyxBNKRpZFMNpBVJJp6/wb+PkK2Jb2IChVtIO4zWTzpfqPOpOZKLpc1PgRzNMQRrtPr+7tNKhZWK9p0oCDg+aMvRAdO8AQdMoUIdLWsYdO6JEdItSeaRiRMdO9+nQkiBCdK/R70PuBX0OlJdSNVp6dNVxPCOwRAmjBagrFbMJeD1p9NMqB46KNpJdMzRiIMZxWCIXhe9ItSRnAFwdtPepiaNPpxdJdI1JN2pC4PDRDJKnaPtO1R/tNU0uwJLRd6OcmO9LnyhvAxC1aNeJr6Mv+CYNtRigFuBSdKVpKdP/RadMAx5NJ4haFRx6TVBlCR9PipJ9N3RZ9LfprdKiR7dPX+X9IOpZFUwZXUS7M0Ik3EEkwiBWnFwZ9tIn+L9ObpH9JpBywO/p/7QUQ/dP/pHmkAZt6OFpXpgsJxoSsYx4A8CGSILksJlrRb6Lf+DaM/R8DPtRP6OTpbaKj+LwJJhGtLSpsOLphgMEBx54J7QjNKSpC4OqM6gLMJWNNhx20xNKT+P0ZBhIDJC5INhJjIcBZjK0Zl1RSoQEPCx3YN7BBjM+ppDPVeIDCcZR5JuJxpMYK1kCsZQlJsZl1L7JOv0cZEaPMZDBSUCV9RpC1jK8ZtjPzJ9jLXhMTKvpLVJvpeUgKRAJg8ZLmNSZQyOdpbdILJLsMyZXtI/yCYF4Zlvy8kr/wq0dinqZT2jM0wTNyZDpiSZIyKoAqsxnkXZm6ZkxPFJ1SJUZf6LUZcpNupLjJECV1Vs6a6Mm0j9JBpLDIIZr9JbpjsPYZZDM4ZFDOyZIqL0oLZnpoDCPXRiNP1pgaJJBhjPBpB6MpRc6K4Zt3jzRPNILRADP1RDXGAZfumNJWzOIGEtOFBMDKuBCjIGZDqN/Ra9NTpG9LQZ34Ohp3RN3ieRUER+dIOZ+DMdphDKWZaaPSZLsPIZmNPGZfINusHRmbg66VmZ3VOfpCzLYZ5lNWZWaK9pRUGqZ1zIHptzJvR9zKEZLTPsRq0WiBrzNUBsjNgZjaMUZzaOUZSDNUZHaI0Zv1LiZAtlWiEEgvJBdNYpTdPUp5unfpuLIvp5zPWZalWNJILOWsqiVAJ9dMJphzKTRQrKupeLMvpBLNKsNTO2BfJLuZc/AeZmhSpZKLK/KvcLLBdaLkZH6LlpidJZZroN+ZKDP+ZHLPQZSLJbsvJlBZfLIhZWLKhZizKpJxDJVZYrKPRErJyB9iOdZWxjRZtNPBZx9PdZkTJWZvrLpBqwPswzJL9plv2vRNv3JZIAxFpgbIXyNLLIRprIZZnzIQZVrNXpKtOj+atNSpNlIKcQbNMKYLPlZ/LMbpRzKdpIrNZp0bMfJOlPTZig1corrPDZkwNrZ0LKjZLQPxZsbLFA8bNSRfDKLRybN1ZFLMeZLbJlCSlOgZ+iPNZ3iK+Z+bOVpMpKLZm9OaRFNIXGhBQyp7bLwZEbOJpRDOWZorN7Zl9P9ZaILyRppM7qlZmmZTDKfpnbKVZblMPZh6JjZTgISgVzN9pQ7MTZ2rLJZY7NTZ6RilZYdB6imbJiR2bI+ZFrKXpCQMdRwzPZZjSM5ZjrLUs/7IdMvLOvZczOUYd7J2pJTJIZZTM/pazMRZpbKec8HOFybbKQ5mLNvZrDOVZUTNVZ5zPVZPDOJZw7KTZ0WyKY47P1ZIqK4aPUSNZOiK4B7zLFBjLIXZitOtZhbPUZ0HIdZuHKFqQSPHqlbMnJ1bINpXbM9Z6HJ9ZR7PFZOHMOpwLNE5qLKvZzFMk5irNI597IbZ8nKPR6rN/pLJK1Z2DC/ZDHJ/ZlLOY5KnMA5OeOA5XHNzZSjN45BbOXZAnPVpMHOE5AthU5iHPU5brJI52LLI5XrIPZOnMfZTbI2ZynJlhfZllZGLORpPnI9ZOLMC5ZzL05/bJBhb7IvRtHM/Zo7NM5AQKY5oXI8CbHJRhJrPpZIHPnZebIc5S7PXpK7IBZTyPXZSVGChYnO3ZzDJQ5WnLQ59bN8ZunLaBJ7NdJWtJq5qnPRZwNOI5DtMjZD7Pi5T7MSRomD7pNHI/ZxnPS5paLTZ4dK65VnJkZnHPkZoHKZZ36JK5yDJGZxbLGZbnMk8XXM85QlPVJO7Oi5kbNk55HMbZzVMlZnXMuS4XIWhYbMO5/XL3Zp3Na5CSJzR7Lk1ZbILo5nIMY5FBQnpXXNy5gpPy5i3LnZ8tPA5PzP45ozKGpXLNta3E21ZRHKi593OOZ0UO9Zj3KC553IDZ4dKiqv5hDZp1KrZ3nPh5PjMw5HDL7Zz7LPRyXN5p73LS59HOm5v7K1pGPJyaIEJnZooKW5RXPs5EHJtZG3NXZ3aNg5+XSnpPdDq5N7Lx53bJO5PbJR57XLLJNPMjphHK85HbIF5nrMG5X9IJZTIPG5RnIEZKbMy533PoCtPNbML1MYRDPPfRvvzs5zLLW5bLIAx9rMBZVXO+SCGT25crIk5uPPmZMXL85QvLl52HJLZSnO9hzRQi5vXLh5dvIG5cXPl5sbNfZf9Im5KvO/ZavMOBWtLEiU7ONZHHNnZ+vOW5PHNZ5YPM25mjOjhoDNjCSkVMa9liM42vJmZnvMLp0nLYZjvN95zvOuJC6NT5yLWtxmkwZ4fPOQ5grO05LXJR5BLIM5CbOV5OrIy5HcLL5+BXT5xjhOEtLLzkgPNj5zPOZZK9NK5fzPK5pvIzpoCO6J5MWtEQLiz5PXP2Z0vO95D3P85sLKwpJtKG5sTMn5ixRDII40sCjXFh5efNQ5WBOF5G/IuZrGSJZpPJuZ/DNb5VPJAZN9J35Z+VhRuvLNZA/OB5gzNZZkHJN5gnLN5PaM2ZNVGmZUzPn5dNLu5S/IR559Ke5qPLjKD6JryrQU2MEA12ZOfIX5IAoa5vnLcphfPr5p/Mb5rCDe5V6Ip5n3LM5rUw75D5n/5cAr98vfJs5TPLf53zKGZbPKg5LnKE5rvNjhvTOMcz8SrxufIFZ+fL85TvKzRovPopvaOYFnLUaQxEMi5h/Ma5x/PQFBPIo5CXOfZxXhwFhaI+5QDK+5YfM2ZAgrwSDbXIFBXNs5cfOK5CfKc54PNc5jApDcPfWXseE1DZOPMX5KAvt5aAua5kgrO5vAtkp4dOMF8AqAFt3Pq5tfJ2p3ArVZsbKpyIADkFpLKm5erPV5aHicFZAqzZmgsoFlrKN5n/NQZ4/K0BkPOe8SjjgFrljU5+3JEpNbKP5h+IkFcLKw5PAsU5zbPDpiQsEh56Xxp5guQF7guP5ngso53grG5l/JJZ1/JM5t/InZBQqBUpAopG1nPCFQPMiFugrK5znJd519M2ZByRA8Q5QP5HAoyFlJMqFfrLyF1CKIFMgRIYrZVcYIwvSFYgsyFNguyFhPK8FMgqS5gfJb5DQsCFRwGgFlaQcC+tmkZz/JzZ2gpZ5oPL0FSfIMF+Qu6Jd2AFJK02SFLgtKFbgs4F1gqR5J/IRZfQpC53vlsAAAv0Sg6McpSAteFYwrsZa/LDRnDPVZ2AqV55PMm5lPL2FPIN+FRDFaFL6IB5MfLgZ8fMuFPQv0FDAtuFbvJdZiwqk5oIpIRWQvBFa8K+FW3MMFQQIQyl7OeFNvIsF5QohJtgogFBLO5ptQtS5cIvwFofMRF+cIQy83NOFhXKoFi7PW5dAu+FalSuhNgVIoMCTaFbxLr5zIuh8jFBpiwXPFF00JsCPE1aceEVOJ3bMPZCoq+oSosgFaYIlFzRnVFeCXT5Wopk5qwrJFnkM8RQzH1FXtIEC9cJhFuAsNR5/zV8o9Oi0l0NVFzRjvC0otRFXALP+b/xNQpSK/+YHNxhWItH5vQspF/QruFmKWzyCpSEF1fL65oAvx5awqkFbXKmF29N7RsYrgFrAsTFXvMsFx3MtF/VPX5fvKcBooFe5TovkFeAsUFBAuJkBwu3kS/DgF6grCF/fIxFOgvDFtrLH53/In5VdM2Z8Fl1GsVOBF/POTF2op05DuAuUaCMCZV8KNFZlF5MFBjSog4uAFIIuWFlJNJFxYqgJiJndAz3JPR1HPZFlv09IZ/zOhPqKUF/ZC9FBxWa6tiXY5/AH7qooL0R3HLzZw/JFFX/PoFldP9BZ4vwkaLXTis0g6prguHFBYuX5h7PHFtyknF8pJuJM4tmFA4p/FLwr/FjItXFRYv3J3GM3FedjP5eJUdFe4pP+B4v1RIZHbhnot7RRJR0634qvFUQBvF3vzvFBvO/Rj4uN5MQu7FW9PxJ/xMbFBXCm8mhFREMuLRJy/LXFCEpiRNMXBxm/OhxdxI+6RYV7MLdFvo0VWWJs+IAlOnO4l5qlMhvEp9xYGK6sjEogZhWT1xYJNWJYIvXFRULpcXkMShskqLxsxKwEx4DoZI5NiU0jLYlYArk5UkpJaWTJmJ91OqYhWW2ZYAkYl5oti5LXMslxUMHE1krol+koz+hUWUKutAPozkq4FkkrSCdON0lHhPupViBElBHlcYkvFUlRTL6pnEpzxbktkhwwk8lUAp5JAJmdo9oj3chktRJhTJ95rkpClVkoJZepV8FlYuC4Df3dFRXBHZ3IpGmGUrvhyVg0FH/1x0yqmzZQosol0QrtZNErjxXkvupsCSeYFqRtSLYDIEgUrlFqYrRUapislzjL4lqZJWsMjFzEXQDW6gktGlHguClWkvclGujSlaYK6JdGN1qnNHJq79XEZjErEl5JJTFVooZ+bkpAlJfIqJdxKaoNgjfoSUTncZYnuKsotWlhUvWlKUo4AW0syJGUqMsuUsx5sSh7KK0oqFa0sml4cKnFN0rAxkoWelHBOCAykpKFKxPillFMSlHiI+lMku+luxLuJyJhilLTQf2PfARl4kvMlj3OSl6Mumlckvupc7hh4DYkQyqIgfpZkrrZHwp1FRUqNhJUoD5hnPJ5lUuJR83BqlHcN+luRWwyforsBcdJalGyktE94qUZHUtoFz4tAl04sUo91IloLYF8l31EUklCgJlp0tHF70rBlV0uT5/oLlldGP5ifRl0xVlGLCoksMp+UoklmssVFG0txYYUvFFesq0xIZEYA2UtgigdUKiwMqZF40rm0WsttlGpUIo8kqHh87Q/CGtBwkKkv25IxLel8ouZln0p9lcZT9lfUosoiLDOGDZEzAI0rilBUsjl60u1lFtLrB9srSxSkR7mto2zExLEQFjRI1lGcrBl0coxl2IFzl//WFJxco/CH7HgUasoCJQUstleoutlGyhjlhoprlDlRBYZdDoZasSkxuTBOlLcveFAXLblhMmKlsbPPKaEu2FHMsyR57Do5v4Ljl+st1ZH4XSg9PPy5zUvwELShgZ7Uu6FEYpxFr4soZURP9xictoBnNDpl5sqJlPbMulXcrQhVDJcyMnHrl31EzA9kvdl4wtBlVssrlZMpPlRBPKoZADIETfgFEMjA/l6kpRlmku9lVcsfloph4k2TSyx78DwYYCrSZ50or+aMp0l0CqiJ1NAGY/0UfkAUrTlFsvLlVsqzlvxIwZFhKEKCrDwYP2US43XGQV85NQVWAPQVNwC+lv8rzKMCrgKmKVN4RsqiauTDoVCUs4pZCJJlGCtYVR1XIVU8VugzstGoaTAiizcvDl4gvglAiq4lzMpFhJUq2F7MtwFnMoaZE3EaFxMnYVHVQP831FWGUDK3lwYq4EbUstZkssT5HPI6Jscp7lPxgpKOCpipifFMl18rOlGkutFmcvvlGTDsVDA2IuOEmppzzCQVBCpvlTMqYVMwBYVEMushPitwqc4ucVTlT2ybsuCVbiogVHiqgVIit9l0SqwxlZmoAVCor6r9C5x9MrLlnsqEVzCq8V1cruJc7jcYw8qBa5TRgQxDB1cr0pBlE8vmMU0siVOcqxlrlEHo32S36EjF54I8rkVHsoYVE0u/lpMraVuso6VYtFokvBjTix4Cbl/SvBJn8uaViAh/lYyvh+mSqvKqo3qVvBjYixQGqVQItLlFosZlX8vblKiunlu4rnlGioXl5CiXll0PWV2HBNo/P3Kae4JkZ28o3k5itDFiDL45VwusVeJMoGRBKNZx4ECqQcpXomoREFowpXF4CsUVJYuL5Osr/l8FKM433F4k5XRmE4jMJFmnNQFTXKOVGAtLFI3OaqF/IuVVYs5FNYtqlJ0CiJ2Ev5+GcQaxAoq0Fg/IolB8s7FkYthVrSOY5NpNpaEqKHFNfLeFEcs9lIvNWVzKonpM1CGFeYtEFGKvkVWKuZFDfNjZm+EHZKXKD5N/IRFxoJ25bbKFVGgtbF4sqH59KvZ5FXNolTKOY5YkWhRcnmt5JFI05kLMLF4qp5Vp/PSVuquBZ0E3QmoHjBVSwtFVgyvcVOQo2FuKqZqMqrJ5uAqJVgjNrFQmSbqdqq6R1KoiFHyovhXyuxF1wtIVwqOBZNpPmFwqvBVTqsWVEqstVfKqmR9AUFV79XZVS4pglXKrFV48uTVOKpzRVtVnl6isJVwfLb5xqKVVKP0zVm8uj5jPM6FoassV3yu1VPUtjl74vUsPHGAaWat/FnKuJFxTIUVtyJTVMsszpwLP1V3qO7V0Et7VEKpQVLqvWFVQufZ+dTKl6EthF5ap0VN1HAlxdS7VtaqFlaqvIly9M1VoouulXPNU6YXOfiapIGVSauKVNor44+oqtVHXJFRN1h/GhqsaVzqpSVF0uvVGKlOMZSqlZAMUDV56oWVQcI4lUKo8RT4QIE2Xm3FDINXqJaub55PJdF7fB44VUpZE1PIfVbY3qs26uFBAYqzE1AAXpHksbVB6ullR6v8RoXIPoYemfV7AsdVVgsxV+aotVFIqZVaarw5a8t2c8aoo16cpo1kItWBeDXxVpav8F8IpPFiqpbsq1KY1qqvRF6qrpVHYq1VsQtbVhovbVyrDZMZ6rRVpqvYlA6sUxvKuHVltIs5y6OV6caoU1u7JCVRfKJ5I3KfAXGpg13qtXVCqriA7asE12mpbFImr3VEpI/5UsuolL4uFxc8JT5C8Kq8XQBuh1Jiah5TP8ZfCp6h2sN/hHMI4lACIXBnygiVMstc1R2JmFHmrCyE5R81xjL81yTIvBt5P3h7UPfhg0OOooWp1+4Wog10iLUVJmsJV3gK0ViTGK1zTMIF7msWa79VuqcENN8qsyzEUfHjpuGpW5+6vE1h6oh50atnaVgTXys1AdVRIunV9CtnVaYuVFLyLuFpFBtpvWvI1/WsTVgGuU1pzMLVJ6OhFy6tM18qr41WMVvCrhSGY4XOE19atf5XQra1BGro1vsvfFfRRFqd0qgl9IrKFuapWF5qqGVEArvVaEPAl2+VNokuMXFPaqTF/4r012KvY1ZYquanqqv5NUq5Fy8pO1dTNe1UfJ3VtmvOFGqoO1TmrFFGpT0V8sn+KltDfG44WmmPSBcV2auQ5lClY1d2oRoHsgQF2/3sFCOvw4rQRVYABgT4FPCGJqQobp3ZOx1hCqvV39B6AW/xG1fyu6J2+UFo/TH+6HdAqyOmtYozvC+1kcsZ1B4grplXLIVIqO3yXMAJE+EQkVo9F51JMn51ySuA1RUKF1BOsUhGYvh1FhO3yU4kiiL/RTYiFLNlmOo+1dOoF1DOvx1zOoNFD8vIVD/HRoB0tgiMrA1oj+Op1CrLZkxusV160Lx1oKlV1LOrTBxOr3pefAuKGcVyYNKAx172vzFruqKVuOvEmkEC91FuqFYVur94ibAFBBHAp4I8rSFtOoV1EeqG196jN1IuriFyf191N2EGYuCtMYpAmblaeopR4etl5wUpV15uqJ15CufIDf09w9rWk8dzyd1JqsnIGeqr1hUpr1uerXZYuqn529F5YgfGz66DDd4UvOQFlepclgupz1PxJ/5+evIVKHFaQIHHKqPQBRMjuqNVwlJp1Fes71U+tN1nutr16utZ12/IKiGSiRVvfUFoIeLb1tvPoUOOqz1O2hn1ZRNxFbCvIV1whRMJOIZ4Y5SvlhurD1O+tbl0+v31ves553QN91fgIDoTlRM4xQDl1tgF/1Y0sj1Petn1ouvn1IqPnyH7A3ETlUnCRzCgNN+vp1cBof1MJJuFz+uQNEtDwmUcg2Ch7ERYWBsn1f+r310eoP1cOqP1ghQJs1kHSYGwU8qibEoNMBu5VuBoANCBrz1wBvIVfcgVY+jVD6KvRiJlXDL1W+pd1nBqaV/+toNgBrHAMlN91w2Qd1vBgOS6NC/1oerIpVBtgNd+o91cht4NfeqQNU/NOGd+H1183HGi7bDGq4+vq52hq4Nuhqj1TOvkNwoEUN5Cpts9kVkqrFkvYx2I4N21JkNNBscNBhqANy5V91eEQqyJDWjonOp8Nt+rfVFf3gNj+rn1/BuQNd4WDEI2x+q9sjJ1URpwN9hriN+Bqf1oiqSN2snzYLFhRYBWWY16et8Nr6qV11opyNOJKjFhBqn5oISKNVWQZobAo5VRuukNlRsHVwusnlvpFj16lTJiyRop4lTW4YyYFK4qeskNHeoqNl6ru1+hpaVvRrr1BRpdAYRtzs5OF+YDSqm12+qmNkKq6Nqus8U3ust1yBrcN5EA8NawEDq4nONV1+tsNfhpmNARrmNy4D6NShu8Apho7Kudl5YKkUyNJupuN3RruN+xrj1hxpgYdQimq4plQgozHWNbRp/1WxpnVMRvX+3xuWVvxv6Nu9MEN1PTP1VaPaYF8g+NbupU1txrhNDxoENQKmYNnTTv4irAxNmeuhNuSl2N5qnhNvuuINMdHssSqUHCYJu/1Who6N0xrv1sxpxNCxqn5ILBb1FZWzEinAQqV+osFVxs6NWJthNtovmNh+p91L+p9RYBuwyB00YAk2vBNzJshNg2rJNBKjFNN6olN9BqlNyBtf1CAhQJDIDNAeUqZNrFOFNrJrVN7JvFN9xs5Nx+v+xHrCm8KGlWNhHHGNzusmN0RqqNo0IpNjkVxNyBsX1Y5QOlJjk5CMdBJNXeuTVGpoxU1pslNBxoH1YAhVoxbnjoIWRDNu+q+NXpq1NdRvyNU/Ib1AwgtSpvj+4ywgN1mhtNNLJu2NoptTNkZu1N0ZsWKhesRYmPMUmxQ0FNE+uLNUJo9N7ELLNVJvj1vv0T14GWscQig31B3JsNTZtVNLZphNbZp9NU/MaQ/uoC83YkWefZvL1UhpVN/Cp2N7JMpNY5sWK1uuCgAZuAqLnEVNJppzJZppLNpzPDNRXG9NNpq3CvlALQOuvsCZvD6gBTN3N5RvdNS5vbl5ZvTNGuvF1pThUkK6JIkrHCTN1BpTNy5pPNUZr+NbOo0yaTErJmKkh4CNLvNmxofNpZv/NaZo61iRrZ1l9TJ1geVa4qKlnNExr51C5uRlw5vJNcFufNCFuCNmuqR1pRqCKs0E04P5p0NFpuxNVpvbN4uuvsLRpGK5FvmglFrsNapr0NNFtMJgFoRNdmjEi+EHLoYFthYAbFYt1xuyNeBtqNhFrhVJenrivzHcy4FuEN+ysLNe5sHNi5sUxHFqPN0XFXNW4XxwZxugGxQG/N1hr/F+5ubN7uocNGlqz8WlspC34EEA67CvNsMO+EDZoHN2FqMZsRvEtXForNQFpL06fNBNU3i3N/OB3NSlvvNWRvYtZlpj1p5qst8RRraurG6y05thRGxvnNMFtpR6lrCt3FuJ1csV/oirAc8PZtvNgVugtwVtwt9+p4N8RsQNiFpL0/7MHC3WQiiHtBEtIpqStoVroNL5oYNlIXDkNTDEYJjhUi9TAkNrpqwtiVsEV9VqcNx5KIt4ut7+BaDVmwLgTNXgT61eVs+NYlqKtuRoSNQ1rZ1Owi5gCdVeCT0GDNhlqx1KlpwtplpqN7lsatOprZ1mTCdNygURsq9FqEXVvb1PVvytu1rct9JPCtSPysQ61qlxSUSiqhprMcU1oStN1rUt/VsCNChqvhaVu4Yj/EWlS/FGtAVsnV7RuctJzL6te1vutqVs11oRDzNHgR3crVoFNGFu6t8uqhtxtI8RsNvvollqR+pxuJEfOHpNGYhdNV1sxtvVqUVuNs0tD1oE03EXiAZABbMDOVoZ4Nsu1TlsptSUt+txVr4Ni1tjMkgx0W/TTRNQrXRt5NugNWNvAFyVoatklvqNfNvLALnAWlwJrAYo5ILNENohNHNpxtd1rxttNsYiDOXstI5VeNNltZtFxqFN21pctMkM1tNNvhtD6qVSjIlPipxprYZRumtmJrqt1Nost2tsf0KNqvYIHlWN3HEutlxtNt0NqptFtrdtVtu6JxoURYAbHaS6tAroftpNt4toslXNvmtJVt5t11jetHyAOGv40YtNVvNNBVsltA1pcND6rTtmnEic2DACY2doPNM6M4tEZrotYdrTiaFrSyFZIyNm1sht6trXh5lqgEAFo8tPFo9t2Yi9taRoiNRgUctRloDt2Nrbto5vdttBhttjczSNXhpu5qtuVNrdpdh7drA1BFoINGZtltqYC5o+tosNF5t/I5dpMtsFqfNNdtlt9vGWqqVTUNbk33tQ5sfNPRtXteRtfNYdv5tB4nryYhrjFilvntRZvjtyPKrtx5vgta9ofttg1LEGImppbBpel8VrdN31sPN49tDtgDs9wRNqb85Bv5wV9tUtUDvwtx9q3CiNtEAZoHQNjepD1H9uUtX9pP5y9okEd9oWtUlrv+fPxCy4BqH1JctytX1pmt1FuIdexvxtdNqetaLIVtqLSzotNGQdO1sPtt9vQdlIWOtDLFOtr8ov1eDrZtw9sIdg3KYdK5ontt2mWtRpp+yjrGJEsdsbNUjri5Mjs7tB1srNW4RGtCpuoVSxs7EQ9q2t6jowFmjr/t99qatSPxatinFPibER5NPDrNteFqPtLDrs05VrNMh/Rjo6+s+tEDoYdBVstNmptIdydvIdlIX5iLNqT1zBqp1Itv9tpjrDN0Dq7taVoO8UVpWqHs2p64juNtajsXtnprQdrjpG03losmKTptkKhrJtMTqydrZpydcjvlk1lrIA67FgiZNGNEqjvZtkDsrt5jqCdPNpCdSPx0tDtsFiMut8qxjpbtzTs6ZrToEdnTuR4slt1GNkkcdgdvX5wztyd7IRNooxpe6FOtigUTp8d11r8dN9p+NczqbCrlB6YcyLR1BbGbtatqU1t2rZNP9vm43pq9pJIWM177JP+WdgEZ9zvM1aVtHCZegDCeXPhBRxUZ5nzrs1YYpoFVipbVTePo1gYPf4UdrntiMrNV1Gru1o02VhZSoXhgep5M3Wv81Tjq1kJjJIVx8v5VhunhdkTg3lSLumdQZJfhVcrhd00ksofgNxdo9sbJWtBPAZ/Pxa/2rqFo8g3lN6INlr/xvYCzkJaLM0bSpLrIRj0NFBe4xw1Wtpa19mvDVh8sjV6LszFzHOdA84owmjtsU1GzqxNtGuzl0WvsRErrMMU8XONm+oxtI4sOVkLrOdC2oZBI0BqFBKp41QOsxi9YpqcWQ3tMRErpZu6qh1Ymr+dzask1gLs6JMwvvhJLuXodIoydy4pm1B9vm1MKoVd0Yroxj0PaSDwpKdDIuu1cEtOd1Ft1dJVhudsqp2FAQrW19Ypdd4bnQ1Vrsh1tKta1drojVPyvcJQLoFsRFE/NDsCwNsEortnTPldUaqI1BTmuEA6KLdYbtm1Ebv8dmAqlVBWtudK6tW1fqr30eQJRZ9NB21evLbFEsvw1sOsI1G4L1VyqplZb2vwd02so1olsjdvrvLdw7uBZVbrIc2fNodE7vRVU7pu12rpndBmqLVJPMNd9Qvjd7br18nbr5w1THB1bzLTd+8ph1XUuc1Oqu2lMwqkAwhTqdYLo9dOar7VKDsrtZbtFdTrvsRFoxH1arv7Nr7oG1ESPrdg6qjdNzkxgtLo5FZmoTd97t/d6CXedEOt21fbuh1mbuFd2brAl74qJaL2pyaNbrfdTNM3dDbs/dPYrfFcLs9IbJnnMuHqA9vDp9d27sN+zMBjdXqrLVbbpJV4EtH8TYJw9NmqQ9omozdDmv+dDrpsV96onp+lTHCLVou1hMoZlBHtMtoGtxUf5G/Vl3O2qonrMF4LsGdnNuk9iclk9VcuNJULhCBYHSU94ntJNudo/Vv9q/VZ/Ny6kHo/ZcGoiYVyo9FM3IFVECMtGxoyHRr6Mw17olMVzWoXZTaqzdALoE9YvIfV2wBE9unux5ynpvlQGqk9YMpM9D2r4FD6qhocyMc979uC9ztr6tanpiacnofVOzjqd47oS9BnrC9VspM99oqb5LbudFp0Ks9zLqUEyGrDt6Xt/K07Py5LnuNo2Gqa1m0rw1V7q7FN7qk1aEPsRbSQBFlHq9d/apA9crtndX7utVBTmHGyhQ0Nq7pldiXtLdP2sM15PWg1hXqY9uwrW1BrJI1gdm7dnHt7d3HsFdjnK89/Ht+VaYI69whXC5mXpfdU6p69wHsk9/XtyFamordTzhG9Y7rE96rtFtn2sm90Kto9DIOlAS2r3dgOuJVuSOY5acTXRMHRppCHvPdXHp+dnyu29aHu89e3se174oVkntDD0VRm6967vDdF3po9x7NTVx2oXhyP2hRiPqOdIquR9Jbte9bqqVBkIDZlhWqNdP3twl9FrOZo1Es0C3IvdFioHd17uu987sAd8PoLip9Tx9CaoJ9JIrm1H7oG9xHrIqWPvfkOnufdj3uv1xbu9d/Pre9P/UV5y2oW9B7pJVWPs36sEUKBwaobVArvf5QroZVR8uCdPwtjh/YviyikyR9ZTuG1fRqeZhvrVidE36d+Ysl9vXtR90vuJ9J6LEw5nrjdvGsPd10z7Fj7qMqZwLRFoPptdCtKZ9LXq7tO0uusnXm0YGG1nKDtjTwXPpY1KnohFV3u0dmMvF1edGj9HgXzlUjJN9Jzod9U3pl9GQBoiDHoB1Cgt9VJKtD9af0xS6vhTdffIZ9oaqiFjmuZ9Q7o6dDlXpomPKz9IXr59ufvR9LPqb9PxkLSvQlzpayEYZsfsndpvrO5PdN/6c3tjdrbsW9HvtJVR1IPo4jOB8IKpOFfvo29YPrDVEPp19IrsF9Xf3fFhJltSDuDb9L3oT9Xfsb9u/qVJeJj+0OoQ6Sw/rXdELtX5Orum9JPqEwS6q+9xftV5wOov9fwq01h/vW9L/OQ9trt499ru6lQRr0lR1u1ZJHBlCO/AnqKtokdp3p5919su9p/ultNkrKtpXSiK1IT+xBciBlt/om9gvI79RPvnVhmuugk/sY9FPpL9vMs11yXT7MdPSA5HQr21tfqD9jKr9dGLt0do7ARdy7qP9+Ab69aPoU53ftYDEVupYYeiIox3vF9obrw9yLslVZYorF8vvIDH/srVbjp9hKvU4Df/rOF6bq29I/K396HunFJ2pNQItQloXAa1dD/q3dyAZYDKopvpT1rZqKhWid4gao9kgcbdZYt3d3Gv3d7vpY9J2v89ZegMDqgcFFjPua9zAbnd/rsO6NjEpi/7rnNumok9xgcI9AvtvdmRJmFghkz+44X7RhgeTNj/rz94w0L9dLvf9IfPb5bSLwiN5V99darX9Afo0DT4sHdR2tPZyBtB179RxduAfCD3AZz9hAcmF/AYu5lQfs9eFVCDmFpl5KQZMDRAZJ9bIrf91YooDZaJvpVQeWSBQcQ9RQfUDWvs39EmpADPntSWhuiq8gIsL45LmNNdDrqDRgZdpBauiDbXoWD4TQSZmxlWDyQd/NqQad973ubdU/pW1M/qV9IqJUolTRqoPbv/9m3umDmgdmDrXtXBUWqF9fno3+5tmIKCWqFlU5DuhT+K3hSMr3hgWvS1h8Leo8Euy1woMBD6gK9pLIAK9lwcLRYdAEZqIfM1zKLLpIhXGDUQGJdooLxD6/s89kPt29F8JkppruyizITi9CEX+D6/xiZyWu8Z3bLS1b8MhDAsMrtuWuoxnwemF9iL7kSzsC93mtXhvmuCOejJSZkbKZDmgBBDrIc6Z7IbMJnIbFdNqor5agkSD9HRpDfjKFD9IdBD/WjFDGQAlD/8NYhUoZMEEaNlD37uY5v7qVD/MhVDRNzVDYcoA1JCK1DigB1DK/K2DmgBhDwjHZDlTLJ983tU0ZWsXlrQVK9Y9Iq1Joe99inuB9dLMa1IsuLUDQBsYzwfB9rwfa1Zgcx9hdqIYJdVoDfZovVhPocZLMK3+VLsi9vENjMrwTZM99P/Vakql9nTKAROYfAlskRqYs/L5GL6pR9kQdMt4WrRdO/vMDKfruYS8JrDhStDNV6sbDZSqe1z5BXRz5DJdEts6qQhl7DsPrjoA4Y7Driuy9P1p7DVcqe1//PbD6TvVlXYcj1c4fLDsPqV61YeXDo8rYthnvXDGPrbVWPrykk4Z3DaYZLDnNoPDzQYTDYdrvCK6MxS/7vPDiAbqtdLj4DZ/pbDSFuCDKxpSFqYZtDz4Zad0DACZ14aPD4uufivQXQDenpXD3Qf3DcSPBlwEek1WPp3ck4et9v4eLD/4c6ZjOqbDMQeqh4uuRMp4aHDCdrnDlTLl9X3teCAYrVEHJKTDa2qe11AbIjZ7omoKVC+dwsqmDMYdKDDfthJRoaG9WmM5ilowv6jUIFDXALhDdPp1hH8MZD4IeZDf8OPheofaFQkby1GQBZA5yucDciGmEjLtmRS3rAxPEbViQarLBBIbf+ukZYjG/tjDh2qYJnEf29twf8qBEtA8FoeIafwkBxDodS14kfFDGWv3ZxgZdDeclkjZ/JZAFwbIDmHHRDqkZUjs/sq1fIcMqTLV75+keeh9XuKDLwbYjwfpL5pkdiDcLvymQEKQd38JA1YMuk9BEcfho4JEjUIYjdbkbPlFBhw82YanF8UeGYMwt8m9UJXhT8OtFyXt1FD3vsjYkd5hQWqPhesOkjBsNSMOWiKjckeEm3kaL9PQCwlbotn99YoqjW02XRvfL+8fTL44f3gADPHu19bwfgj7XpFRRLViAd3UyjPbOhdDwcJdS0ZBdloyNtUEZODapuhdViFhd20a/DWDo7lK7qy9q4bv1R0bgj74dG1mLp2jI+rWjoXugpBLqudvUbpdJ0P1RTLsQ1eYlZdv03ZdSM3AgvfO5d3v15dDXsttHnoPVKQB1I2iGogJyFMAr1Bdme+2sR0gSq8XzF54gsQNso6NCoGPBfMdkCH0DoYGyQOTDiMAmxjh1Fxj9gnxjsvBpMxMbYMT2RkiNQBpjZpKM9R8Spj5NEtYtMaJjzkaOyQjkzGIABSZZXlcSWQWSGIAFEo0OAoy8422SNPtmh0TUmOkHgFjIsYJCxIxcBHE2ljFvNu6lKoesCsbMC5lEORfjX+Sz8QKR50Z2m6AxJkf5FiacNXpxIhtNjweRR1Pbkv6rQ1kMdTnq8cNQdjn4vtj3HRTD9SVdjE43dj3satSBbmNjKrraFvsctjkLhDjCPraFoviM43se6afsbNKVQTjjP2TgGSVAb1qcbqSUbUTjVsYwCGce+oKYdjjSgfOjt1XDjZvSCoHsduqRcYNjYcezjEcegC+ccuKuARGY8ccRGOcefsAsaFj2tmVjnkWJGuhjmwUsdjMpqLL0c7kgZ3uUFkuseqkjmnemzUTFjmqPVjQ8f5lhw3V4WwWFIu9Q+63IVdGUPUXDu4Vu2bcfrjCEw3j8WTowSWToMAeo889o2fq7cb2GrNXtaCO2u6R8cMoJ8YTjB8eT8uPVTGXRznM9nqfj98dRm+MmvjCEx3jHoz3j13VvjwCd/jZcb4m28zq+cxGFj2LW/8LUUwiddWKMTww1jhMzmQ6GiQBn0V1jWgSEA/wvsCaccfREEdNFtcfNj0wHLjzRnpA+Cenp3gSoTSQtITGgwtjFCYfMdCY1FMcaj8eCfoTZscYT5Cb9ahqTuYXCZ6MnCaKF3CauGvCdemVwGjA5nLW8osbpGp6O08Ey3K8T6JeZ+4URyGYx9MXcYXgPccoi9M00AJpiOwJ2DOwW4wCxHuVCB6jknj6iYCk8CdkT64y5pxiZGpH829R08WM8OCbxswg37R9eT5MO1AhMKzqsDXif3jzCYH0v5F2ZniZcY3iYdcvidCTmEzrjgSc40wSa2E5tgsEVEw6q8SYfCg5WiTZCdHIfCbLKIsA8TlgSSTyo2g0qSa21cpljGzsYZMACeo8PibyTZPn8TCgWqTK6NKTDfTETWSYkTwRxgTbsy0TFlVMiswU+m/NXsTOPS2y5tm36S0lcTv3RtsorFWafZibogjC/jPSHtC67mHKoqDQGPCdaTQM2om1ggWTwyYY4ukWu6lSqs4a+TsAbU1ETMowqTr8ddC+yf7EFUQK4iqCh6jZhkYyUCeC+kh6QlMwyilSddC9ycsSZ02UYTfFPjEybs4Qko7oAEBaGpoyYTUCckTmMWOq3AXCCGnleqE2F6GiiZx6eEYwDZDlmSJHE7saia+kncdgT3cesTKsd0Tp9mfaA4298c7kwTg+TGT4KW+qeFVwCDZAA5DCZaT7yaqCtKZcKhCah857JENBCZfjgSe/MhBRoTjKS0i9KdOTk5HOT3SQFTrKagiKLNcsXKfBTdOOGD6SRsTQkwxgTYwjMiKdfaNIraFVLkxTHwGxTnSanGJ4V6TJ4xv8zPgbsaCYrRHfBkAsnhSoOsepOEeWmkeRUzykoXtT0qYZ6vPX6JVqblqjqeDZryZcCjKdQynqdRZmeTtTXqedT6yanqV3OyVC1iDTvUW9TmsXeT1UikTTQpkT+KeYmffisGM+VVTLdmpZ7qdUKWqcjIGiZxTXSbNqCqY5GP7lQTsZhNQsgDUwS2JGTH5BtTucSjT9EkjG/qcbTTsdBT4idDTcNmbTQPsKTGIIQyp7tbTRzTBTLqe+B7vPo6KNijT/abKTbabWT8w2qcE6e7TzcVss0AxUDmoyIMcafxkCaay5PScXCSCaaqpVkACGae+B67hXTWwVzT0CcUAmib1T0KcQTsKbd8NAQXj11jU6iXG9tOQwpTfTk9ITqblqYkR6imIJDTs6bri/MujTC1m/TiFmIhkCeHTNlhAz0eWzTZ7WvGMGfAzHaY9sFqQjTctQ/T49RjT3TnXTRBk3TQQvhCvcYJTZMHH6AcUPTkGYzZMGc1THcfzTuqaPGSafwzKadoGAya6iT6aNo1adfTdaZ3iaGdMKY6eQcUGdW9C6ZNGg6fbT/6bUsvGf+MLaeSTcNlEz1/pPTCGeEzpGcAGk6Z7TsDgl5MmZiT2SeUzFqUUzi6c4z4meaTQqdYo5yfjTcqdozOifozNzgHZCKZLGTrIXyf3Ln62HjPTA8zYol6Zoz26Ywit6fpk96bLTTziiqIIBCqL6etTy11zicdE/T2HW4y6Gb/Td4zg5h3t/TctTCzAaYizblRXafEWIhJDh2y8GbUzEGbsa6qZSzhjTdTYGYyzhPVkMOGeUFJmbMifSd+syqavMJGd1s+HMDsVnIoz/MaozXxm0TZWcNTDw0YzmaZUSozG/QrGYCzeDkt5/GY/KMbUO9umaU6XYV9TN8Tizo2b3SecSXd+MwKzcmZqzh3q0zM2eCz93oHTDoxnTkWZnsfacGzIbVUaDktUzmSawzRWeMzrmZhT1/l+s+DSqzVmaizhBVszavShsDmfaTF6YLTV6fQiF2aySvbVv8hhVNTSGfoSJ9XIzLifYzZgTQyP6ZgzceRCzf8bXTIqb9ytqRiz2HWhd8WYCT6mdUatqRQz2HSwyyOdXTLsdhzJXUAGGOePiWOYRz0OdxzHwwZMxWZ5FeGdMz5Wb78hYwky1WbhslnKBz9mcozTmbezLmY8iNObaz7Dg6zw9lHYPgHqAvWfS8b6eDigA2mzQ2ayztqQlzlw30zQ6cQzkeWUKJ6chz62anTgma2ziWZviSOZWzkubhsWOZ1zsubjGZycCTYObPyyuYBcgGZlzOw2OzhmY3TZ2a5zrWd3Tolk4GxGduzgSLC5D2erMDWcrcOqeazeKboztOZosP2d2Kf2ZgQsxnUN/mZFzIObhqubghzFubjc6WZtzJufKoUOYDGv3kTzqyYmzgGRRZhOdF8hvuxzpOeNzqOalQh3tzzwceizGeYZTtuewz9uepzjufczv1mNTLurdzttUthLOaezbOY6TfuaLTyacDzFrj5z7uZvAEeaQAwuewT0ef9qI2b2zodT0sKef0SR2czzeOezzAqfNzM+ZRZBuetzC+eTza+anzGI3Lz9lnXzg9U3zxeeSo++Z3zaw0As+eatzh+arz5OcKAlOc99pWYNTTubPMaaatcjOa5YV3M9zrAQp8z2d9zcCZ7zAeZ5zQeabzKNJDcWdk14c01paxEKrG4+caSNqVTz5eavqJOdkz22Y1cSBbjzF+YXyhOZQLmubxqV9TLz3XW3S2BYWzqBbMo8BeDT/yXILQGZRzbScaQteYXCbmcuzmTlSG6aZbzZBe7S9WZ/znedez1GZnjFATFjpaZiCf2eSo5TABWo+fMTsBfa626Svz5+bjqovXnzN+ePzpKbnz3GcwLrbIULcuaEzpBZLz6hdULSVCoLB+bMaihcyz7BYP9Z+ehGE1CoLMhZBT6uZOzFOfoL+qZ3TDecyc541YLGvOApJ6e9zisaaz/+eH6xaeJGongfTIbgLhI+dKcbGcCzI0SIoSub0LQ1F5T5hYSzggy/Mpvh6i1hdaS7KePaR3svjoKSzzSRfSLRNkyLoDUUoJ7viLNBcQzRRbmzyzXKLKhayL42erzp2ds952ZvTTBcQ8CibYLfspbMX+fHjHecaz7Od4LLWafzzhc0AlWEHjI/kAhnLTedcQ1rTERe6c9+c4KjRbFjWng/S7+b88WPK21oQpzT9yW+jtnWz8PqNzpCRd364KRNybefFTdng3l2xdKLi2eIF9smgGJxb88x2POLOOaLzJhdOLBbtuLRxb2LFxdZGlia7zvhbby/hYIzMYHgIQRY21YgTSNWCYkL0xaMzDRYdzAxeaL4Huuzzea61nAi21zYo2LkaRQ0xNuToPRgxLQwvUGxhcQzNxUxLfMGxLrRi7VeJc0LGucSLqFlbiDrmJLbqX+KRJfJLRueFTgScfMtTtpLAQxsCDJdxL+xcxaWKZ8LuKYAL3Oefz0VhywIxaMFMsKjC5KckLX5kzG1Kf+SzoAeLheZZLx+cVLJgubjC+RoTOBapL33k1L7CcAscpa1LJBdwLspekL+pfTj26SlTnxekm9hehLdedhLX2eLIPgoPTbRfMoyGY1TXBZ6LPxcFLfhd7zQBYyApvko6fpLbcw8aepExbszVhlFzupeG4SpdF8FCEXo8pceLKpeeLYvioMsZYLc8ZZuLvJYc6ZSUzLHxfeKQ1izL1pZ1LBvRWsRZYLLZZaVL2pYSGNebtLDBc+zxGSdLIBdYorpcKFeCXWLp6e4LgsY5zfBYQTAhaBLXmdvhF0fsG0pchLdubrLjhcYLjpfA9LuavCyxcGFmHhO6LpkZj5nlN8dtqxLbqTXLZJezLbseD8ixgFaG5euKW5d2cTJfKTyZYJL+5dacz8U5LsaQO4B5bpLSZYMzrJePL8wpvLhwvaYLAsPLypafLMqb/zPpb+LfpZFLDGfFLhLWRhCpXDLj2dGTMpfukAMRXRVpeuKedDgrgqeZLP5eeL+niQrtxcQr5Ze/L8ucuLktHYD+Zds8W/jOLyAx3L/sf4SsFewr600orVZeNLW3XHL5XvmLdI1SgrRaRFwbvaMHZa8Lm1j/Lhad9LgBaArwTmbLoNOCLxcYdc0BeBz0xZOSKLJJzh0U3ZleYpLORaSoyRdRGOWeI8hBWILSeePzylegzqlZlj6efyzmlZTLURYoL7xWMr1BcfLuFa+LtZcYrMJacLcJbJgfwzcLvIscR4ldZzXpZ4L3eb4rwpcGLToI5QrhebGxuWHLpYVHL9yRKCxjnnM4oQfMjA08CXRTPLqFYJL0VZBY/oyj80VYirZFaTjwfjCrIiaSyIfgTLSVfSrucZn80QPNscHE3LIeQMYBVdCrQVYITOVayreCQo9xZZrL9RZsr9pbsr05cUADos76xuo217HvwSGKa7Lzmd7L/xbMz3Q0WCg5bbcbYzJ84JZgLY5esr0iaYrtiYUgrFdjh2YvUyX5a4rTKepAgarfLD3C2r25car8mWD8qNW2rNKb2rJ5cqrkaWOr+1euKV1fOrB1ZzLX5gTF11e8Ct1dfLF1Z9zApd4rAFf4rPle6Ga/RDz3vh9FxjjWA4RafcsxYIyw1b7zxwWWruEU21K0xbo/VfRLcNefoVwTTSyNfaMx0furu5Zn8Joq21cdBJLvVcxrFla0LJpfARvVfxrbqVxr8Nd0ZWNfIrTqXRrHwSJr3gSlF3fJprxNcpLTVbzTvRc8r31e8r9lZXCX2HXCP2AHzEoTyLGNVvsd9D6z7laGrgFd+rThEqzPVKR+LzqeppoP4GUxZ+i00k5CqRrVi7rEtYufSqMwcr4qMSF1rXgWrLh1d+69ybXQ0VrJTyzQZC7DFe4loxNr/2VsLi+dL6ytev9k5jUcVoUtrg/v9CDNY3z+JbwroYXBo5oj9r4Ja3y7taSiT7CgGajjNrD1ZG0Bte0EDXSMotEmSrQxTljoddSqEFbTa06bsL56e7LfRf9zfNfarnI2BLfCJ+BdULVrO+yOcsxdpmk5YbLjuXYgCJcnIbRcG86cUrrLlW6LA3jncnPgzSEmZTlYteWGWccMrBJZq5bcSDjbXh7rZhj7remZQrlldJr1HVeSaVAnrulDHrZ8WHrR+bQrU9bNRK9cXr0Yw3rgdasr2qc+rkKaoG9daaLJdfaiZdbs0WOeO8UedmrzVbkmZ9evTYsYL9SxbaLskWA2XnmXGK5cf0MJiGxKpOvs5OEmgm7Vu0AAkSgrMGUKsUyjAKycPrC9d/0U8Td6fETcmMLAEM/9fe4jiOWYIDferyfm4i9YhhYiSaQbAhkQbaNWQbWDCMw7NcUrIenAbIDagbADcmaBEw9mu9FDK2tFIbODe4rJ9YE6UKY+zF9cbLkQ2vreTvKrEiqEZM1bBrp9flTstf5rvkXfrPfwl5KYY2r9IR+85fCYl0bAgboDflkR4jUbS7osQk9DGzSESobYDcxYpDbIctAh6QUTq3ySjd0brMa5gajfYbWURWsu9G6VAJnkiZdGbiJ/VyisUBMbzcDMbejbeTrtcCGIkUgbMrJ0bsoj3SV4wEtPjaSiIsA8b6+rjrC/UKAqk36LbVb4bigERDCictjsZiEAbJj4GndZdMmCyOciTaLr9ef5rCkfSbnpn/6mkZiQNaYSbT7kKbQpeKbJda8jZTdqkDYIsjwMdA8VLkwWg0VeBRoWoDOTepMHMdr4U4mDUdMd5j6/l/ruFUqboUZBpeMa5jIzZ5jLIb5jbngxjjgQpj2kxlRszeGbmclGbizfGbA1Z7LsLUWr+MUYIxMXOwQhbXNS8eNrIVZrr72Y+m/pbkIs5YZzbRYZCWsdjuX7Qmbt2mCBb0Vz62Mu+btNYyrQoS+b4ETTr1fRp9MJjsb9IV+bwLdz6QLdgiztc2zBjeR08cPBbQMShbcLfYbzlB4rtzZi6UNfsgDsRFr8shV9QQA261depm5rSYmuLdZiXI0JbZzNR1IIz/r8WILiTNatCpThNjN1QhbQoTjo+EVPjrLcu6OGTorBxdL6XLeZC4deP6wrd4jHLdli1+O5CufXFbdYXBLcTbprMkV5bY4UalQMWlbvXQqGClbqLXNe9LX1dpGi1dXiIFb6BBXQBmEJZ7sc4rG6dUaUz8GQbi1rd8bPqf8bW7TGilKt3rymZvs5oLVzCLadb7reoVbrckzg9b9bNRf0bPrYDbWQ09bNrdNs3rXtbAme9bt+YhTE5fpqxIw6gLBbfzbRYcRAoKQKv+c4bvZbFjRrYEbvaebqZrdEbEeX+KOnuIKKNj3GlMQPr2rZNzVbeBb/rf6sOPrZjRhdrbxefUszbYrbyDjLbQbY2zV8dDbYY1uKEbcXTPbYbbwbb8b8bdlTibfgaAJaHiMNe5Z9Bng9kxbvz+zcLr3bTpGcJDnQ0JHhkxreASXZi+o2KWAJoNbQSIM2By3hpKidxWUKOiX+bhVbKaM9ZWm+bsmS+TXvbKNY3aGSc3riGfXrxjkfbSWUvbWxn+4GGeqkiLcuKV7dlZ1CV25BUTXRkrcFGEHYz+VCSmaL7faMb7cA7/8YHbVXsJkS2OF8e/mPoqzf0SWHZvbjLWCjr7ZUST7d6amCjsS+HcobaHZw7GHZhMvjFf6YuVcGtHZUSe3FPLudeo7n4rkSAmFY78CUf8wNdjcPHYI7aCUIUzHco72CRE7RQrE7irYrq/JasT67cWru0ILbSwC811NfAS99dXbPNYNbiqb1SkxAJbhUTC5B1ClrH1bk7w/TFjdcPGrlvg8CNqOPbA3jXrIvv6SV3PNYKHZhzrJa65TndOrgiR9R0HaSos+elmpHYR8t1mxSNQao73KdnzQXf87E1H6B4Xec7ZOePzUXf+KEXYF8LZnc7QnaeK/QL87SWUUohBRS7IXePzlrbhSXnf+S+Xded1xe87FvKRhpXbdSdncq7uXZTLJSJVVNfKW4xKCds9MbaGurY8rvxa07HIx07OqD07cpQJMdVeXbjma7LhzcVTMiKU7D5jvh1naM7e0x3KSYUS7H4otSOXZwrJNZLLAgC38S3cK7CFc47yiWC7K3Y5r5tfBSbzdacy3cX8O3ei7ZXeaMx3fiYF3bdSfclRSe3ek7t7fM893ZHzCXZyrr3Yy7l3YfMn3dO7Ufk/aJ3a27+3eA72gUNaciT27vngB7D4Xs7qXbcCUPfArNXaIrc3a8A57dq7BJb0RDXY+1LoGhEGHKcjuzfxG7XYLrmndGGAJcIApGUEI5GQm7YYyvqd9bHzkldQy+ceCyoGVwkTPdh7oOZi9v5h2rvdhZ7g3YdbsaYHb3PYG7F0YWsjPd57sbf7bJudF7wvblqjPekabPekCZrrpTmeTWzwLm+74uT5FmwVSzUhPHbjrZNzKvdp7SuWoyRFOB7Avf17mvaTagbeXsWrbnrq3cFbd2bpT6ja/olvc8CNbZt7B3fjrw2cIKdkM4aZvZi7TxYVztWf+MBvdoagAwN7T3ZG78CbFj76Sp7/Ti97fIxLbxnb1b2LdR6AlZIIeneh6tqVKcM3ZmLDhaTbs7bYylmfLTlKMJMY8a6Ly5d3qnv2Aapfd3zqmW/jDrmr7rbbd7wHboMNsYyLqJWtGlfeqD82ZHreFZb7Jsfr7MRd/0Hgf+M7fb7b2RYHbXMDr7awEH7BKMZbT1On7Ovf57k7axbnOdb6I1ZAA6OS8IWOT075KWra2faAqg81j47SUzoaNCCy9NF54nXHfGMuHQtUWWOyW7KvbXQgHkYuQgGF/Zvs9IBe4zZXZr80SscnGgwEmYjBy/dZey+iW8EmYjmaHzbbI9/fiydgDQ4xwzWMv/Y/LA2OKy66Vv7Vjhf7l9BvsT4ztEDDYfIkA8oMVDGVthubirSKIj78ncVTTJKp7rmSBGHpYnj0FZLxYpgXcO1ZZMcWXZMF02Pr3Nc67JPfX75eUNyVPcnpe4EWG2fbZGa/cpbzpZfaz3h3KfZhJbaXSeKaeXiRRse3ST7DV7YecSZhbvkHtqTkHaPbwryg9RTjbe0H9DNir7Ha3zCmeemx8U36NAdd7RA9t7h3a/MaeUUHctX1zJg/D70tcGro3ZLTOoANyCJV+z9AR3KS2LHGNA4fr/E20Ggkw5GvdKD6Eg5oTjrRkH5Vcy7aeQTM8vYZ7NmfN7mBYQK/LZ772hb0H9gVFbeljMH9VHiH+OZ6icQ+w6Zg8GGi/cwzA7eSo6OesDAYwJz1gacHifY67/5a67xIyLMu7djCqGuFJ8sZBzUJZardM3X7I0HpzTlYYK4DNomDfcRrXEQr9IrkNYoDW3yO4mrDUw69bEveLzJ+Uj9B9NGHQMQmHSFPmHs9csH7vexr/zUXG9DK2HzcWWHhFKOH4vfH7gSZmHu/PyZaw7FyJw//ytw5N7y/Zzbrg+JGUcCQArpD0QGAHgArQ7gKhvt8Hgg++Lq/d6HuLaqZvw/wK4fvbDABQkrx2XyRZ+ULj/hT98a+R9jAresHD5DhH1YYOiftkNoH83hHFg8MHSw5CLmUvGquI8xHavYCKMfkkmdw6JHbVyeHhI9D85g+WaFI/bDJyab7OrfzrLg8j7dIxaHVPaWK+iUYq6td6KfcoeHBYQSA1w6M4BHTSH8DYEA8dH0o7TMxUoo7lHmw8lHH7aDrYo8mHbgyGKwo735Ko7gba3eiiDPBKrmo77yho8OHuo7bbtBcuSPQ5OqybYGHabcWKXfNacPfNUTGnY4HwQ+aHWRXBHMo/7y/I+ubYRR3Go0c+RMRTspZo/JHLbDvpCo9Ba4Y6NHYY5DHqw4LCAY9uhkY80H2hZPy75RKTyY9s8aY4z5Io7Z73Q/mr7I2TbzdZbLx+pn5UrkzHXFcxbLw65Hi1bhKQIGNwendV8HrY6bMI+lrrw4BLeSEbHyJQXFnQ/p7M/lHb5/R6MA467I8LcWHRlbr7WffpLw1EHHeQ7KS/CIMBJg4uSItUnHKY+lH4Am/jI45eShyLsHq4/1HNxWNiILYUo9Xd66o44uHv5erHpA7cH7g71AfXfwlnLXk16nbbHNY8VTqEos7miKCry8d7HO0W5K8WXlaP4/Ojp49qLz5capM4+Fa/4+NlavZD8+pMIqLwRAnEE9nH5nhf+LVEPH3opAn44UAnIbcCTu0RrjKE/fLB44xbQI6J7bo8/6No4BL7tS8HJielzpDVbHqQVmLJE7z7fQ9KlB6f51//Qal1gYUbnpXiCzzEIpd+F6ESWWSiDpQTL5Bm4navYEnfE6gbcDBIJQWSyOsUH0yfZhWtTbD57ZQ8CTa0SVock6EAJLAcpdpUwY5oE88CJiIESk6A7A7YBMorAMVAJgxEBk73SphUkn+9P5igk79755eGGsnaT7pVsLHZE/i65za0x5bOHbkFcFHbnnatmk+trWxj6UWHUnK/xRsng0oh4fE9EnRFDUnQKuXsFuFEkLZVN8gU6m8pFDUn5o7ZHKk6UCgk6CnrZVJ4Uk4IGggEsnndSwMmk4cn8VbwrrZUGEJBLyK9k6CyOU96EeU/gsEU4Inc1Zltbk8YnCtd8d/1MAzSgTGH3hfYHjQ+QA3DbubqfaKMenemT9rX37D5XG1HrBYsq+VoQwtr6sBDg2YWsZboRUQfLdI+eL2GLZonxQRcQ7FCnK07mntU/iyBHCSnu47t7uFWdASHH0ydTth4uldwqv1T2n3HRVQP7EynOw+A7q06WncyP+4hU+f7N0/encMQenFU/nr9FfanuiufrM7cYndo5VTLE97ljGtgie3crHhE85H69s6nuLfInANcEKNALOnfo7yy8XFxQAevKoNWS2n3FRUSQM9RaCbAun208QzA1SEgp08oMykHNg3oRKiv/fHExM57oIk4QnsYTfaP04QdrkiOn2I8H1804xkSaCanhk9Q7gScYKpHEkAp8TTiX9GWn/hT5nBirINgs4+nBI8tHdE9GnOLfubdKEeb6afhnreO38Sju30A044bQ0/1bI06hnY07lrWM4CrXk47l41OoHkZdoHWGKR1HrBRNr+hlYSs4mydWv5n8WURoSkklnLneLzyyT2nQU/GirXDlnlbR6Ans+mVQ8J+nMU+XolM+z66jB4kDU+zERM8jnGMcOnGs5drKk/9nqs7uKm05wDz/a8Kqc5ASRM9BnVg+byEM5uoOs5T7ctdJQxY4ptWmKwMuzLea7AfNnVY8tnRhvCGHI3tnfNj+zLJgf+gI/rnqFRtnus/GneKpkb2jOIuFKrMTHE+ZMQYsDqHDrt1ITewHHGmqYvImTrS0+wbPM+3nrSnEZlTX/rbDbZnq868UhTrp4MDbV7ZWQtYrjdrCRyO8bFDds8A1QDYBDaN9HjZsLcbeLzA1XFyMDcFi+89nYbM9yTKkiBNloxhoqDbzHqM4Obz47cHQZdzJENVHdU1fxnOfeBHpE76H0qsL7aHjYiBqpbHblYG6KC7xMK+bjq1asxqGhayn7bZWoLQSpDNfeDi5C5IX3fdVH2he1p94aI7uuesco7uRJzC71HV041cjC8YYlRZmo7C7oXjfc+n7I8czLk9zb3I89HMfbATqC6/HEeXnHFC5n7ptnAjYi5/nY44Vz8454XTabaDmKV4XFo4VzkoVoXai47VllCEXY/aAn7bdMXUdvMXKi6YXpQ6Mnk7boLGC4YnuLbXw87dLGU/YIXXdcGn0i/bH6/aHnJqe8HxC8MBQ3eCOYjfcX0M88Xxaq6rrpbwX46p/rPdhoXpmTUXbC5aClC4kXW+erVvxk0XI3TyX5VOcXUs6UL3C7Ti5i7SXcpqMXVC5TLIi6yXGS/qXiNgKXMC+cnDQ6tn7o/cnci7fHIDmbqES4jLUFb7HjPTaDFS/0X4EeyXms5MXBi5KL2HV0XxS+sXmE/bbcy5mXx8V0Xoy4WXE7aWXIy44X+2f6sbJnWXCw7PHWs+T7I/Rnni6pdLLNTaDD47RL9Q6Inw086X6/aQaXo5AM8lkyYM09+6z3RA8C3aB6ssJrnuw6Vbr9WqEvy6Pqy6OAaBlZYXa44fdtxU592HSniStQVbqI497utjhX4piyHalhRXwK8Pn5o2nHESdPj+gNpa77b4XfJcfr8C+JGUGq6rRs8f0qGrCLPc9gXa7dM7si/+rDs6FqgmsUXVdeG7EeTuRqi8jGdyL0XGy9177bd5Xhi8jGAJMnxNS5yX7bdFXC7haXoWcDG3K/5XS/clXcq6cXnDS5XKq8OXNi+OXMS9tnUjeK8Fy6ecrK9pLKS+cHcC8vHHo8eGnk5ZXyq/6Xvk9JbO8SlX+S/MXDq/mXGq8WXzxbziDS55XgY0JMEy4Lngq+9XLq8jbYea/rBy+2Hky60HvK9DXi6YdXUa/OHmq8KztpZkXi1aM1c86tXX9euXnZZNX9K6JGAJdWIZaGeIWxFrQuxAJbfZm3x1E/8HJtnKaz5VbKVcama5BPDcKMzqHUCXrX4NhRmNTXgVClkbXiK72Hgoz6ara92afa8DNDmjV7zTRTcKVX7rsWuXs467DXfq+eLk66HXuzRbXNa/xHs64TX+daCXoI4NnzK9abGCarR0I4rXWa+J7Dy9xbV9Z6X5fMq8BvYT7yfmGK9eUenyIzr7NqT+XwHZvXqLS9rAEwfXxvabXyoW/qODtz6P65V64K6JXSK7AmdfYIL3fVA38ldqXa64TbSa5fHf2pwXQw7r752vZXdONdH9y5umes/UIlhG6n/yLZdH46KGaC58mg5QpaCArc60KI7nyA+7XAK/uksM15GJeHwm6M3I3DyoVXyk+Lz44RWjDrkQGoDVH8+C643rq82Xzxaqbli7dd3G9o3ZBtI3WK/W7Ym8v9Em+Fa7oBI3lG6lH4M7YHJy7njsM9CXI/iBjD4U5ddPbQ3HS4w340/zbZ66yz4bfYnNE6y8tdYM3zc+taqa4FsyrrD0Frtybb0nAHYgCM41ba9XaWR8nOddXXWg/Dk7m9VXnVVdbJS9Dn7q783YFV0HYW7hbK69/n7q7c34W8jGkW/Sy0W+0XTk5U37S9U3dIz7ak0+cKYegiH5m/QX1o48Xes4Sg3i5+MgbvBsxq/8nlSslG/E+q3LqU/XVG4Bb4A1/VNW9jn0KPHJtM6qnLW/q3b64kGOK463X65LaZ0cv9/E+63mNVjrjW9iavc8CXpK4BLRKYonQtTQzvg5s7Fm4y3i1ZGgW65VT6bekyXXpdHyi67di44t7AqYwnAm50XhrL5G1tgO3J24FX7q+SsIqS/T527Y7Pm/SHd2/5HC1le3rZie3MW+g3L2buX+m+PG40/m32M8W3YNiApv8avXBW43XxW+W6CG+PdFRZQ3jSAtb2+cGBtDWKLgwMG3qGTMr0zdmXyO6fXAvY+32O9WXKLMJ3GO8mzuO/e3xO9A8X6+m36W+1X087tnonWeXD7pecxbfy3+Y8TTA89tHr+a23BTjg9jm8iXiO4jyONJf0QW84awu5X1xBVJ3MkTcXUW8jG4u6fnMbe8332977Mu6S31oxl3ou/43N24Vz8u5qVXm92XKDSxByW6OXP25X7sG8HnHk5Hif2bY9oCR3chG/4SQNbYTta7WMf01ASYvaV3KW+0Lru+Mc7u92XQQF6rvu4Drxi7wr/u7d30veFaJQ6d3xu/jXIe8j39Vc9jIiUd38e9Ljk2+iX5u+Tb5nUGHhLRZrbCecThC4CXtO/T3AJYx6lRBh3xm+3cS8fgnum5ubdO6bn/NbM9CKcpXMbkORNHYR3VAAWilzbh6/ddj7cyIm6Hu5N3eFdGihowESK0R1Glo0JXwe9YXwnoLiXe8jbQ++GqCNck3EDbH3asQW7sffei5XQn3UG60HtDE2Gfe92X6+75bW+4lXbSbN3UO/GnJe6qIzy8n7C0/LXrs4CHd+bW3iqf26je7+zk/fwiMq8zXh8bn7K1Vn3zcQz7IrfFX4a+0Le9XJ1VQ7UiQDXH3eO8CTffb33ai9APzIWt7J+8QzCB6gPAhnxwAlLQPS+4z7mB/lb8B4wPR++gP5477nte9OXDO5/AenfUsWKX7q7y7nTmmqBcfckjT9B4TyfIyl36tWYP/xn5iQvRW9nB9YPqe9LbHB5UbXPdtBWmsYPS+9BsPB98th270sa1tEPjg/4PtE6f3IQ9m9CS4Kc/3rkPAuBRnbS7+3Sh+JGcvUoPdrRHzAg8fH9acEPUddHDctVkP1YYNsbB5Ackh6iboHht6Zh87qJO4UPO8REP1h8zyHh/LH6O7cPoOe8PKuRR3yDisPPh6IPWq6L36/Y+9pW++B9h87qhhZMq+e4tnM27NXpPY76lq7Jis+ez6Ug6iXVjjh9llED39C5D0aLdGoX2893a47yPiAJhbNPqzsaveR+3HR2rdR6cqKI6U3/C/lksLfKaCI4ImxR4KP4i+APNpcf3pB9frOG5Jkzzed+31GyPgu9uXaM4ZXtY7SPVu4360/RKP9u7360/UpVSWTwbb0SAPz2/KP6ISN9ai6vGuCv33Qe+332hYOPmx9trapbhbYR8QzZx+Bb6x92PVx7anJK5SPkR6Izc5eebRLZhXX+4L3Oh9cnwg8w3cx+JT7c6dnJVdoPNZQPFWyeXsngCboW86wx8bABTxA1FQsyaX3B9MmTokRxEwKaCyvJgeTHRecELyeTnngC+TiSdsEK8ZWneUkVYsQAsSNydZHyB6qn5J4OTEk+OTZ/YcCkJ/atOydgbk+/6PMG6frAO7lrooHU3iJa0xSvUX9IFmX9tK+0P0x5zXXA8BPC25KqnG6Vz48+ePMx7g3m25uzSOlKq7FY5Tv/puXyfn39APqeCRx8KPg7WRF24a2Pyu+0Lep5/9GBZbaJp8lyl4q13iq5MLEJk1PGRY+6yg1tPKuUA3nJ7W7zp/1P9DMNPshZyTHp49r1x9S3hPclPTQ9SPFq/mPGDsWPhQ/NbuR5bj9eSCPBEyTP/dtDPpx7TPio3WPnpCsDNh78P1YVzP4RpzPoldwRtR6LPKvRTPI2izPVXl8PLR+JXia/P3fJ+53ap+dipZ5WXWh7S3vx4iPJ65lPwO4GNHuS8D1e7CKJqH77uQ7uHo57MMce5nX5p7XH8+Vb7BFUbBjbZPyC5/Fr055DnsXeeLK5/776573S257MM45863qY6sQY56SHJo9XPoHQzPXJ6nbPZ71nM4GiP1Qx/3qVSHP3x6SPhe6bP/NdPX6R66icaWJbYJ7B6xcc13kbcEMnM8l3BZ+1ixVbHb+o0gvhFWj3bq5QPMF/Qnjbeh6KOptX/e5j3IB5CLsF+tGWF6AvG5/97YZ4GPhW9iXd5/8rPO6CDCQcyYWbb03uh6jPTK+HnixXjYqI3kb+W5DC9IG9RnR7aHxfbt3wW83PiGbBannh4voDQEvxxbgvp26DrsaumsTI/YvtnTEv2u6DrMl5MFy58UvrZWaPEK+U3jZ453GM7vPjlftHghVGDPR4SP/i7fP3Z4/Pl9b7P268whPI3GiDfaMvgy8rXWYLSNnF44Sjl/JcKe/rPwG9p6uYUXXUetvs7l/UvrR95k9wYT3jHaUCJDTUvQG57XM0N8vSkUbbnuXCvcl8dPP27cXt5/Gn4eFs3Vl98vDwb23qQTqbyp45GaTaWLGTeusIzE7nvwaQK+TamPpq/yvxIzzXvoADQzABeI2xDrQTO6wM84uRM/59oMjSFViaVDNnUPW4YD/2Ftth/dzas0OGfV+u63V+jG416PPkK4Bau4RNEpg77slowIH+F8cnIB4GvQY0irrm6WvOBgWvw19/0G166qvs8f0h18AMBg+2PGl/XXs2/X7eaEoPeUgJyOy7L7fk5+PEZ84HuLdYmxJGeXV4xzpoRd1ZnV/HM9xf0H3y9zNJdWN9S+/NgjGucbC3Yhv0oVLCdZ4CvaI5D08FhgmYN9AT1xcwT8N8iv1G6RvxC+VY3y4S7jyrxvV5/1HoqAXnYelKvp8YJvYeiJvtR6aXcN/WPdN/JcSQfBvSgWybwZ73SxoWYHLbB/D+18R1kN+o4DN+4XAt9qPkA/MnVZ6KP2rLIcMwhY3Li+LzxoVmSmHXFvbyOrka+WlvDp9Y3zxdEZk4dR7gPRBvTyeP3fR/1HEN7yXwt6BidN/aymN+9PgV4VkP16ApD8MB6UVURMdt8tvJx7XHWt5WAFt9PjeEfbDOt+p3pS53MLU2Jk003Km2sW9vit8pjArOjAFUzrFN8ISm5EKSmqQRzjskxv+qfyNCot+ETT2jIpUd/CmWl4am8d6fcSd90sd/JDvQoTpvYt4jvjdOzvCwHHpNk3zvRzkLvqxgLHPYRVvUt5/DWd7Gm2AFfccd7smBd+Kvjd8qm3ETYo1YUdvHt9ZvFd+7JVd/GmAYZikl2gWmid77vgd5uoncLTvBFZHzY9/WbOZMnvnd+JkAYO7vCd5+PZl5SbwxDIvVWb7v1jtXvD1MGBEO+qkeV6lPuLdKbRV/KbDlV13tsaE3ndkqvWXlvvkZ/X7TTcfvLTcyv0E7fv+4Q/v3TlUmr1+PXes5kIzTZ3M2e+7SsV6/aID5vvtF/X7Ikz/vMD9+mVaTM32HkQf/t478i96OASkwsoAkS/oawgsCakySjlfKKmo6MKSZnlMm2QzloUVqqm9oVrvPd8WmrwV8zAfCOmFhfW79NCHznD/oZRU15vkUw0mSY6ofMqOhdOzckjst9P3F45qvpPcZm8iD07BrVZmVF6UX9aWI3nDvlaGj5wMtl7V7Qm93Cuj5ES2j6CAhj8uniN6S6VzCcqSt/W72j/KaLt9pPXu+k3NJWw7lj49Gpj5mv+o/0f31GsfdYQ43pi6ePhPcPvjde+mVPa7IK0cLiV95hHHMyJAFIBNmnJGNmsUiDzngAxIlABSfCYHzAzwDB+3AEEoIAAl+pZFMAWv2Rj+2GtI1aBuoG/ZeQSAA5AppB1I5pEtI9k0cAQAAA=="))
|
|
///////////////////////////////////////////////
|
|
|
|
///////////////////////////////////////////////
|
|
/* 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.*)?$", "");
|
|
for (var bom_type of ["both", "F", "B"]) {
|
|
for (var row of pcbdata.bom[bom_type]) {
|
|
row.push(parseValue(row[1], row[3][0][0]));
|
|
}
|
|
}
|
|
}
|
|
|
|
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]);
|
|
}
|
|
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;
|
|
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,
|
|
}
|
|
|
|
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;
|
|
|
|
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);
|
|
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;
|
|
}
|
|
|
|
///////////////////////////////////////////////
|
|
|
|
///////////////////////////////////////////////
|
|
/* 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
|
|
];
|
|
// Adding half a line height here is technically a bug
|
|
// but pcbnew currently does the same, text is slightly shifted.
|
|
point[0] -= (point[1] + text.height * 0.5) * tilt;
|
|
return point;
|
|
}
|
|
|
|
function drawtext(ctx, text, color, flip) {
|
|
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 (text.svgpath) {
|
|
ctx.stroke(new Path2D(text.svgpath));
|
|
ctx.restore();
|
|
return;
|
|
}
|
|
ctx.translate(...text.pos);
|
|
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) / 2;
|
|
var txt = text.text.split("\n");
|
|
// KiCad ignores last empty line.
|
|
if (txt[txt.length - 1] == '') txt.pop();
|
|
ctx.rotate(deg2rad(angle));
|
|
for (var i in txt) {
|
|
var offsety = (-(txt.length - 1) + i * 2) * interline + text.height / 2;
|
|
var lineWidth = 0;
|
|
for (var c of txt[i]) {
|
|
if (c == '\t') {
|
|
var fourSpaces = 4 * pcbdata.font_data[' '].w * text.width;
|
|
lineWidth += fourSpaces - lineWidth % fourSpaces;
|
|
} else {
|
|
lineWidth += pcbdata.font_data[c].w * text.width;
|
|
}
|
|
}
|
|
var offsetx = 0;
|
|
switch (text.horiz_justify) {
|
|
case -1:
|
|
// Justify left, do nothing
|
|
break;
|
|
case 0:
|
|
// Justify center
|
|
offsetx -= lineWidth / 2;
|
|
break;
|
|
case 1:
|
|
// Justify right
|
|
offsetx -= lineWidth;
|
|
break;
|
|
}
|
|
for (var c of txt[i]) {
|
|
if (c == '\t') {
|
|
var fourSpaces = 4 * pcbdata.font_data[' '].w * text.width;
|
|
offsetx += fourSpaces - offsetx % fourSpaces;
|
|
continue;
|
|
}
|
|
for (var line of pcbdata.font_data[c].l) {
|
|
ctx.beginPath();
|
|
ctx.moveTo(...calcFontPoint(line[0], text, offsetx, offsety, tilt));
|
|
for (var i = 1; i < line.length; i++) {
|
|
ctx.lineTo(...calcFontPoint(line[i], text, offsetx, offsety, tilt));
|
|
}
|
|
ctx.stroke();
|
|
}
|
|
offsetx += pcbdata.font_data[c].w * text.width;
|
|
}
|
|
}
|
|
ctx.restore();
|
|
}
|
|
|
|
function drawedge(ctx, scalefactor, edge, color) {
|
|
ctx.strokeStyle = color;
|
|
ctx.lineWidth = Math.max(1 / scalefactor, edge.width);
|
|
ctx.lineCap = "round";
|
|
if (edge.svgpath) {
|
|
ctx.stroke(new Path2D(edge.svgpath));
|
|
} else {
|
|
ctx.beginPath();
|
|
if (edge.type == "segment") {
|
|
ctx.moveTo(...edge.start);
|
|
ctx.lineTo(...edge.end);
|
|
}
|
|
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);
|
|
}
|
|
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 (shape.svgpath) {
|
|
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, shape, color) {
|
|
ctx.save();
|
|
ctx.fillStyle = color;
|
|
if (!shape.svgpath) {
|
|
ctx.translate(...shape.pos);
|
|
ctx.rotate(deg2rad(-shape.angle));
|
|
}
|
|
ctx.fill(getPolygonsPath(shape));
|
|
ctx.restore();
|
|
}
|
|
|
|
function drawDrawing(ctx, layer, scalefactor, drawing, color) {
|
|
if (["segment", "arc", "circle", "curve"].includes(drawing.type)) {
|
|
drawedge(ctx, scalefactor, drawing, color);
|
|
} else if (drawing.type == "polygon") {
|
|
drawPolygonShape(ctx, drawing, color);
|
|
} else {
|
|
drawtext(ctx, drawing, color, layer == "B");
|
|
}
|
|
}
|
|
|
|
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, hole) {
|
|
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);
|
|
}
|
|
if (pad.type == "th" && hole) {
|
|
if (pad.offset) {
|
|
ctx.translate(-pad.offset[0], -pad.offset[1]);
|
|
}
|
|
ctx.fillStyle = "#CCCCCC";
|
|
if (pad.drillshape == "oblong") {
|
|
ctx.fill(getOblongPath(pad.drillsize));
|
|
} else {
|
|
ctx.fill(getCirclePath(pad.drillsize[0] / 2));
|
|
}
|
|
}
|
|
ctx.restore();
|
|
}
|
|
|
|
function drawModule(ctx, layer, scalefactor, module, padcolor, outlinecolor, highlight, outline) {
|
|
if (highlight) {
|
|
// draw bounding box
|
|
if (module.layer == layer) {
|
|
ctx.save();
|
|
ctx.globalAlpha = 0.2;
|
|
ctx.translate(...module.bbox.pos);
|
|
ctx.rotate(deg2rad(-module.bbox.angle));
|
|
ctx.translate(...module.bbox.relpos);
|
|
ctx.fillStyle = padcolor;
|
|
ctx.fillRect(0, 0, ...module.bbox.size);
|
|
ctx.globalAlpha = 1;
|
|
ctx.strokeStyle = padcolor;
|
|
ctx.strokeRect(0, 0, ...module.bbox.size);
|
|
ctx.restore();
|
|
}
|
|
}
|
|
// draw drawings
|
|
for (var drawing of module.drawings) {
|
|
if (drawing.layer == layer) {
|
|
drawDrawing(ctx, layer, scalefactor, drawing.drawing, padcolor);
|
|
}
|
|
}
|
|
// draw pads
|
|
if (settings.renderPads) {
|
|
for (var pad of module.pads) {
|
|
if (pad.layers.includes(layer)) {
|
|
drawPad(ctx, pad, padcolor, outline, true);
|
|
if (pad.pin1 && settings.highlightpin1) {
|
|
drawPad(ctx, pad, outlinecolor, true, false);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function drawEdgeCuts(canvas, scalefactor) {
|
|
var ctx = canvas.getContext("2d");
|
|
var edgecolor = getComputedStyle(topmostdiv).getPropertyValue('--pcb-edge-color');
|
|
for (var edge of pcbdata.edges) {
|
|
drawedge(ctx, scalefactor, edge, edgecolor);
|
|
}
|
|
}
|
|
|
|
function drawModules(canvas, layer, scalefactor, highlight) {
|
|
var ctx = canvas.getContext("2d");
|
|
ctx.lineWidth = 3 / scalefactor;
|
|
var style = getComputedStyle(topmostdiv);
|
|
var padcolor = style.getPropertyValue('--pad-color');
|
|
var outlinecolor = style.getPropertyValue('--pin1-outline-color');
|
|
if (highlight) {
|
|
padcolor = style.getPropertyValue('--pad-color-highlight');
|
|
outlinecolor = style.getPropertyValue('--pin1-outline-color-highlight');
|
|
}
|
|
for (var i = 0; i < pcbdata.modules.length; i++) {
|
|
var mod = pcbdata.modules[i];
|
|
var outline = settings.renderDnpOutline && pcbdata.bom.skipped.includes(i);
|
|
if (!highlight || highlightedModules.includes(i)) {
|
|
drawModule(ctx, layer, scalefactor, mod, padcolor, outlinecolor, highlight, outline);
|
|
}
|
|
}
|
|
}
|
|
|
|
function drawBgLayer(layername, canvas, layer, scalefactor, edgeColor, polygonColor, textColor) {
|
|
var ctx = canvas.getContext("2d");
|
|
for (var d of pcbdata[layername][layer]) {
|
|
if (["segment", "arc", "circle", "curve"].includes(d.type)) {
|
|
drawedge(ctx, scalefactor, d, edgeColor);
|
|
} else if (d.type == "polygon") {
|
|
drawPolygonShape(ctx, d, polygonColor);
|
|
} else {
|
|
drawtext(ctx, d, textColor, layer == "B");
|
|
}
|
|
}
|
|
}
|
|
|
|
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();
|
|
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.lineWidth = zone.width ? zone.width : 0;
|
|
ctx.fill(zone.path2d);
|
|
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 {
|
|
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 ctx = canvas.getContext("2d");
|
|
for (var mod of pcbdata.modules) {
|
|
// draw pads
|
|
for (var pad of mod.pads) {
|
|
if (highlightedNet != pad.net) continue;
|
|
if (pad.layers.includes(layer)) {
|
|
drawPad(ctx, pad, padColor, false, true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function drawHighlightsOnLayer(canvasdict, clear = true) {
|
|
if (clear) {
|
|
clearCanvas(canvasdict.highlight);
|
|
}
|
|
if (highlightedModules.length > 0) {
|
|
drawModules(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);
|
|
drawModules(canvasdict.bg, canvasdict.layer,
|
|
canvasdict.transform.s * canvasdict.transform.zoom, false);
|
|
|
|
drawEdgeCuts(canvasdict.bg, canvasdict.transform.s);
|
|
|
|
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 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 (pointWithinDistanceToSegment(x, y, ...track.start, ...track.end, track.width / 2)) {
|
|
return track.net;
|
|
}
|
|
}
|
|
}
|
|
// Check pads
|
|
if (settings.renderPads) {
|
|
for (var mod of pcbdata.modules) {
|
|
for(var pad of mod.pads) {
|
|
if (pad.layers.includes(layer) && pointWithinPad(x, y, pad)) {
|
|
return pad.net;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function pointWithinModuleBbox(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.modules.length; i++) {
|
|
var module = pcbdata.modules[i];
|
|
if (module.layer == layer) {
|
|
if (pointWithinModuleBbox(x, y, module.bbox)) {
|
|
result.push(i);
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
function handlePointerDown(e, layerdict) {
|
|
if (e.button != 0) {
|
|
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 modules = bboxHitScan(layerdict.layer, ...v);
|
|
if (modules.length > 0) {
|
|
modulesClicked(modules);
|
|
}
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
///////////////////////////////////////////////
|
|
|
|
///////////////////////////////////////////////
|
|
/* 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 moduleIndexToHandler = {};
|
|
var netsToHandler = {};
|
|
var highlightedModules = [];
|
|
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 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.modules.length; i++) {
|
|
if (pcbdata.modules[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) {
|
|
return function() {
|
|
refsSet = getStoredCheckboxRefs(checkbox);
|
|
if (this.checked) {
|
|
// checkbox ticked
|
|
for (var ref of references) {
|
|
refsSet.add(ref[1]);
|
|
}
|
|
} else {
|
|
// checkbox unticked
|
|
for (var ref of references) {
|
|
refsSet.delete(ref[1]);
|
|
}
|
|
}
|
|
settings.checkboxStoredRefs[checkbox] = [...refsSet].join(",");
|
|
writeStorage("checkbox_" + checkbox, settings.checkboxStoredRefs[checkbox]);
|
|
updateCheckboxStats(checkbox);
|
|
}
|
|
}
|
|
|
|
function clearHighlightedModules() {
|
|
if (currentHighlightedRowId) {
|
|
document.getElementById(currentHighlightedRowId).classList.remove("highlighted");
|
|
currentHighlightedRowId = null;
|
|
highlightedModules = [];
|
|
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;
|
|
highlightedModules = refs ? refs.map(r => r[1]) : [];
|
|
highlightedNet = net;
|
|
drawHighlights();
|
|
}
|
|
}
|
|
|
|
function entryMatches(entry) {
|
|
if (settings.bommode == "netlist") {
|
|
// entry is just a net name
|
|
return entry.toLowerCase().indexOf(filter) >= 0;
|
|
}
|
|
// check refs
|
|
for (var ref of entry[3]) {
|
|
if (ref[0].toLowerCase().indexOf(filter) >= 0) {
|
|
return true;
|
|
}
|
|
}
|
|
// check extra fields
|
|
for (var i in config.extra_fields) {
|
|
if (entry[4][i].toLowerCase().indexOf(filter) >= 0) {
|
|
return true;
|
|
}
|
|
}
|
|
// check value
|
|
if (entry[1].toLowerCase().indexOf(filter) >= 0) {
|
|
return true;
|
|
}
|
|
// check footprint
|
|
if (entry[2].toLowerCase().indexOf(filter) >= 0) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function findRefInEntry(entry) {
|
|
return entry[3].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) {
|
|
var th = document.createElement("TH");
|
|
th.innerHTML = name;
|
|
th.classList.add(cls);
|
|
th.style.cursor = "pointer";
|
|
var span = document.createElement("SPAN");
|
|
span.classList.add("sortmark");
|
|
span.classList.add("none");
|
|
th.appendChild(span);
|
|
th.onclick = function() {
|
|
if (currentSortColumn && this !== currentSortColumn) {
|
|
// Currently sorted by another column
|
|
currentSortColumn.childNodes[1].classList.remove(currentSortOrder);
|
|
currentSortColumn.childNodes[1].classList.add("none");
|
|
currentSortColumn = null;
|
|
currentSortOrder = null;
|
|
}
|
|
if (currentSortColumn && this === 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 = this;
|
|
currentSortColumn.childNodes[1].classList.remove("none");
|
|
currentSortColumn.childNodes[1].classList.add("asc");
|
|
currentSortOrder = "asc";
|
|
}
|
|
populateBomBody();
|
|
}
|
|
return th;
|
|
}
|
|
|
|
function populateBomHeader() {
|
|
while (bomhead.firstChild) {
|
|
bomhead.removeChild(bomhead.firstChild);
|
|
}
|
|
var tr = document.createElement("TR");
|
|
var th = document.createElement("TH");
|
|
th.classList.add("numCol");
|
|
tr.appendChild(th);
|
|
var checkboxCompareClosure = function(checkbox) {
|
|
return (a, b) => {
|
|
var stateA = getCheckboxState(checkbox, a[3]);
|
|
var stateB = getCheckboxState(checkbox, b[3]);
|
|
if (stateA > stateB) return -1;
|
|
if (stateA < stateB) return 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 {
|
|
for (var checkbox of settings.checkboxes) {
|
|
th = createColumnHeader(
|
|
checkbox, "bom-checkbox", checkboxCompareClosure(checkbox));
|
|
th.onclick = fancyDblClickHandler(
|
|
th, th.onclick.bind(th), checkboxSetUnsetAllHandler(checkbox));
|
|
tr.appendChild(th);
|
|
}
|
|
tr.appendChild(createColumnHeader("References", "References", (a, b) => {
|
|
var i = 0;
|
|
while (i < a[3].length && i < b[3].length) {
|
|
if (a[3][i] != b[3][i]) return a[3][i] > b[3][i] ? 1 : -1;
|
|
i++;
|
|
}
|
|
return a[3].length - b[3].length;
|
|
}));
|
|
// Extra fields
|
|
if (config.extra_fields.length > 0) {
|
|
var extraFieldCompareClosure = function(fieldIndex) {
|
|
return (a, b) => {
|
|
var fa = a[4][fieldIndex];
|
|
var fb = b[4][fieldIndex];
|
|
if (fa != fb) return fa > fb ? 1 : -1;
|
|
else return 0;
|
|
}
|
|
}
|
|
for (var i in config.extra_fields) {
|
|
tr.appendChild(createColumnHeader(
|
|
config.extra_fields[i], "extra", extraFieldCompareClosure(i)));
|
|
}
|
|
}
|
|
tr.appendChild(createColumnHeader("Value", "Value", (a, b) => {
|
|
return valueCompare(a[5], b[5], a[1], b[1]);
|
|
}));
|
|
tr.appendChild(createColumnHeader("Footprint", "Footprint", (a, b) => {
|
|
if (a[2] != b[2]) return a[2] > b[2] ? 1 : -1;
|
|
else return 0;
|
|
}));
|
|
if (settings.bommode == "grouped") {
|
|
tr.appendChild(createColumnHeader("Quantity", "Quantity", (a, b) => {
|
|
return a[3].length - b[3].length;
|
|
}));
|
|
}
|
|
}
|
|
bomhead.appendChild(tr);
|
|
}
|
|
|
|
function populateBomBody() {
|
|
while (bom.firstChild) {
|
|
bom.removeChild(bom.firstChild);
|
|
}
|
|
highlightHandlers = [];
|
|
moduleIndexToHandler = {};
|
|
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[3]) {
|
|
expandedTable.push([1, bomentry[1], bomentry[2], [ref], bomentry[4], bomentry[5]]);
|
|
}
|
|
}
|
|
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 : "<no net>");
|
|
tr.appendChild(td);
|
|
} else {
|
|
if (reflookup) {
|
|
references = findRefInEntry(bomentry);
|
|
if (references.length == 0) {
|
|
continue;
|
|
}
|
|
} else {
|
|
references = bomentry[3];
|
|
}
|
|
// 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);
|
|
setBomCheckboxState(checkbox, input, references);
|
|
td.appendChild(input);
|
|
tr.appendChild(td);
|
|
}
|
|
}
|
|
// References
|
|
td = document.createElement("TD");
|
|
td.innerHTML = highlightFilter(references.map(r => r[0]).join(", "));
|
|
tr.appendChild(td);
|
|
// Extra fields
|
|
for (var i in config.extra_fields) {
|
|
td = document.createElement("TD");
|
|
td.innerHTML = highlightFilter(bomentry[4][i]);
|
|
tr.appendChild(td);
|
|
}
|
|
// Value
|
|
td = document.createElement("TD");
|
|
td.innerHTML = highlightFilter(bomentry[1]);
|
|
tr.appendChild(td);
|
|
// Footprint
|
|
td = document.createElement("TD");
|
|
td.innerHTML = highlightFilter(bomentry[2]);
|
|
tr.appendChild(td);
|
|
if (settings.bommode == "grouped") {
|
|
// Quantity
|
|
td = document.createElement("TD");
|
|
td.textContent = bomentry[3].length;
|
|
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])) {
|
|
moduleIndexToHandler[refIndex] = handler;
|
|
}
|
|
}
|
|
if (netname !== null) {
|
|
netsToHandler[netname] = handler;
|
|
}
|
|
if ((filter || reflookup) && first) {
|
|
handler();
|
|
first = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
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();
|
|
}
|
|
|
|
function modulesClicked(moduleIndexes) {
|
|
var lastClickedIndex = moduleIndexes.indexOf(lastClicked);
|
|
for (var i = 1; i <= moduleIndexes.length; i++) {
|
|
var refIndex = moduleIndexes[(lastClickedIndex + i) % moduleIndexes.length];
|
|
if (refIndex in moduleIndexToHandler) {
|
|
lastClicked = refIndex;
|
|
moduleIndexToHandler[refIndex]();
|
|
smoothScrollToRow(currentHighlightedRowId);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
function netClicked(net) {
|
|
if (net in netsToHandler) {
|
|
netsToHandler[net]();
|
|
smoothScrollToRow(currentHighlightedRowId);
|
|
} else {
|
|
clearHighlightedModules();
|
|
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";
|
|
}
|
|
var fp_f = 0, fp_b = 0, pads_f = 0, pads_b = 0, pads_th = 0;
|
|
for (var i = 0; i < pcbdata.modules.length; i++) {
|
|
if (pcbdata.bom.skipped.includes(i)) continue;
|
|
var mod = pcbdata.modules[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;
|
|
}
|
|
|
|
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");
|
|
switch (mode) {
|
|
case 'grouped':
|
|
document.getElementById("bom-grouped-btn").classList.add("depressed");
|
|
break;
|
|
case 'ungrouped':
|
|
document.getElementById("bom-ungrouped-btn").classList.add("depressed");
|
|
break;
|
|
case 'netlist':
|
|
document.getElementById("bom-netlist-btn").classList.add("depressed");
|
|
}
|
|
writeStorage("bommode", mode);
|
|
if (mode != settings.bommode) {
|
|
settings.bommode = mode;
|
|
bomSortFunction = null;
|
|
currentSortColumn = null;
|
|
currentSortOrder = null;
|
|
clearHighlightedModules();
|
|
}
|
|
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(",").filter((e) => e);
|
|
prepCheckboxes();
|
|
populateBomTable();
|
|
}
|
|
|
|
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 updateCheckboxStats(checkbox) {
|
|
var checked = getStoredCheckboxRefs(checkbox).size;
|
|
var total = pcbdata.modules.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));
|
|
}
|
|
}
|
|
}
|
|
|
|
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;
|
|
prepCheckboxes();
|
|
// Triggers render
|
|
changeBomLayout(settings.bomlayout);
|
|
}
|
|
|
|
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">
|
|
<input id="darkmodeCheckbox" type="checkbox" onchange="setDarkMode(this.checked)">
|
|
Dark mode
|
|
</label>
|
|
<label class="menu-label">
|
|
<input id="padsCheckbox" type="checkbox" checked onchange="padsVisible(this.checked)">
|
|
Show footprint pads
|
|
</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">
|
|
<input id="dnpOutlineCheckbox" type="checkbox" checked onchange="dnpOutline(this.checked)">
|
|
DNP components 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>°</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">
|
|
<span class="shameless-plug">
|
|
<span>Created using</span>
|
|
<a 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">
|
|
<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>
|