@jax-data-science/sci-image-visualizer 0.2.16 → 0.2.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -70,6 +70,9 @@ export class ToolbarComponent {
70
70
  /** YOLO checkpoints offered by the host's registry, and the active one. */
71
71
  this.yoloModels = [];
72
72
  this.yoloModelId = '';
73
+ /** Retinal-layer checkpoints offered by the host's registry, and the active one. */
74
+ this.retinalModels = [];
75
+ this.retinalModelId = '';
73
76
  this.selectPlotType = new EventEmitter();
74
77
  /** Intensity (LINE) mode: add another colored line ROI + inset trace. */
75
78
  this.addProfileLine = new EventEmitter();
@@ -114,6 +117,12 @@ export class ToolbarComponent {
114
117
  this.yoloModelChange = new EventEmitter();
115
118
  /** Open the YOLO parameter dialog. */
116
119
  this.openYoloParams = new EventEmitter();
120
+ /** Run retinal-layer segmentation over the current view. */
121
+ this.segmentRetinal = new EventEmitter();
122
+ /** Pick the retinal-layer checkpoint. */
123
+ this.retinalModelChange = new EventEmitter();
124
+ /** Open the retinal-layer parameter dialog. */
125
+ this.openRetinalParams = new EventEmitter();
117
126
  /** SAM model picker (jit-ui#90 P1). */
118
127
  this.samModelChange = new EventEmitter();
119
128
  this.wandSensitivityChange = new EventEmitter();
@@ -144,6 +153,8 @@ export class ToolbarComponent {
144
153
  this.samMenuItems = [];
145
154
  /** Same stable-array reasoning as {@link samMenuItems}. */
146
155
  this.yoloMenuItems = [];
156
+ /** Same stable-array reasoning as {@link samMenuItems}. */
157
+ this.retinalMenuItems = [];
147
158
  }
148
159
  /** Rebuild the SAM model menu when the model list or active selection
149
160
  * changes (keeps the array reference stable across other CD ticks). */
@@ -162,6 +173,13 @@ export class ToolbarComponent {
162
173
  command: () => this.yoloModelChange.emit(m.id),
163
174
  }));
164
175
  }
176
+ if (changes['retinalModels'] || changes['retinalModelId']) {
177
+ this.retinalMenuItems = this.retinalModels.map((m) => ({
178
+ label: m.label,
179
+ icon: m.id === this.retinalModelId ? 'pi pi-check' : 'pi pi-fw',
180
+ command: () => this.retinalModelChange.emit(m.id),
181
+ }));
182
+ }
165
183
  }
166
184
  /** The Image plot type renders as a natively pan/zoom-able raster, so the
167
185
  * generic zoom/pan tools are hidden. */
@@ -216,11 +234,11 @@ export class ToolbarComponent {
216
234
  this.displayHelpDialog = true;
217
235
  }
218
236
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.9", ngImport: i0, type: ToolbarComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
219
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "17.3.9", type: ToolbarComponent, selector: "plotting-toolbar", inputs: { imageInfo: "imageInfo", plotTypeOptions: "plotTypeOptions", selectedPlotType: "selectedPlotType", isoRange: "isoRange", maxIndex: "maxIndex", zIndex: "zIndex", isHeatmap: "isHeatmap", activeDragMode: "activeDragMode", activeSurface3dMode: "activeSurface3dMode", axesVisible: "axesVisible", wireframeActive: "wireframeActive", resolutionScale: "resolutionScale", wandSensitivity: "wandSensitivity", brushSize: "brushSize", vertexEraserRadius: "vertexEraserRadius", canUndo: "canUndo", canRedo: "canRedo", stackOptions: "stackOptions", tools: "tools", imageSmoothingEnabled: "imageSmoothingEnabled", samModels: "samModels", samModelId: "samModelId", yoloModels: "yoloModels", yoloModelId: "yoloModelId" }, outputs: { selectPlotType: "selectPlotType", addProfileLine: "addProfileLine", toggleImageSmoothing: "toggleImageSmoothing", isoRangeChange: "isoRangeChange", openChannelHistogram: "openChannelHistogram", openRegionEditor: "openRegionEditor", selectStackOption: "selectStackOption", zScrub: "zScrub", zSlide: "zSlide", zIndexInput: "zIndexInput", reloadAndPlot: "reloadAndPlot", downloadImage: "downloadImage", autoscaleImage: "autoscaleImage", toggleDragMode: "toggleDragMode", zoomIn: "zoomIn", zoomOut: "zoomOut", toggleSurface3dMode: "toggleSurface3dMode", resetSurfaceCamera: "resetSurfaceCamera", toggleAxes: "toggleAxes", toggleWireframe: "toggleWireframe", selectResolution: "selectResolution", deleteRegion: "deleteRegion", undoRegion: "undoRegion", redoRegion: "redoRegion", segmentRegions: "segmentRegions", segmentCellpose: "segmentCellpose", detectYolo: "detectYolo", yoloModelChange: "yoloModelChange", openYoloParams: "openYoloParams", samModelChange: "samModelChange", wandSensitivityChange: "wandSensitivityChange", brushSizeChange: "brushSizeChange", vertexEraserRadiusChange: "vertexEraserRadiusChange" }, usesOnChanges: true, ngImport: i0, template: "<p-toolbar>\n <div class=\"p-toolbar-group-start flex-wrap align-items-center gap-1\">\n\n <!-- Host-provided custom toolbar buttons, shown first (e.g. app-specific\n actions the visualization view itself doesn't own). -->\n <ng-content></ng-content>\n\n <!-- Plot type: available for any image (grayscale or RGB). -->\n <ng-container *ngIf=\"imageInfo && tools.specialTools\">\n <span class=\"toolbar-separator\"></span>\n <p-dropdown [options]=\"plotTypeOptions\" optionLabel=\"label\" optionValue=\"type\"\n [(ngModel)]=\"selectedPlotType\" appendTo=\"body\"\n pTooltip=\"Select plot type\" tooltipPosition=\"bottom\"\n [style]=\"{'min-width': '11rem'}\"\n styleClass=\"plotmode-dropdown\" panelStyleClass=\"plotmode-panel\"\n (onChange)=\"selectPlotType.emit($event.value)\">\n <ng-template let-opt pTemplate=\"selectedItem\">\n <span class=\"plotmode-item\" *ngIf=\"opt\">\n <i *ngIf=\"isPiIcon(opt.icon)\" [class]=\"opt.icon\"></i>\n <img *ngIf=\"opt.icon && !isPiIcon(opt.icon)\" class=\"plotmode-icon-img\" [src]=\"opt.icon\" alt=\"\" />\n <span>{{ opt.label }}</span>\n </span>\n </ng-template>\n <ng-template let-opt pTemplate=\"item\">\n <span class=\"plotmode-item\">\n <i *ngIf=\"isPiIcon(opt.icon)\" [class]=\"opt.icon\"></i>\n <img *ngIf=\"opt.icon && !isPiIcon(opt.icon)\" class=\"plotmode-icon-img\" [src]=\"opt.icon\" alt=\"\" />\n <span>{{ opt.label }}</span>\n </span>\n </ng-template>\n </p-dropdown>\n <ng-container *ngIf=\"isIsosurfaceMode\">\n <span class=\"flex align-items-center mx-1 toolbar-text\">Iso</span>\n <p-slider [(ngModel)]=\"isoRange\" [range]=\"true\"\n [min]=\"isoValueMin\" [max]=\"isoValueMax\" [step]=\"isoValueStep\"\n (onChange)=\"isoRangeChange.emit($event.values)\"\n pTooltip=\"Isosurface band (0\u2013255), mapped onto the volume's\n actual intensity range. Surfaces are drawn for voxels whose\n intensity falls in the selected band \u2014 narrow it to isolate\n features, widen it to see the whole structure.\"\n [escape]=\"false\" tooltipPosition=\"bottom\"\n styleClass=\"wand-sensitivity-slider\"></p-slider>\n <span class=\"flex align-items-center mx-1 toolbar-text wand-sensitivity-value\">\n {{ isoRange[0] }}\u2013{{ isoRange[1] }}</span>\n </ng-container>\n <!-- Intensity tools (Heatmap / Image mode): add another colored line ROI.\n Each added line gets the next bright palette colour and its own inset\n trace; select a line on the image and use the trash tool to remove it\n (and its trace). The lines are Region-based but excluded from the\n Region Editor and exports. -->\n <ng-container *ngIf=\"isIntensityCapable\">\n <span class=\"toolbar-separator\"></span>\n <p-button size=\"small\" [text]=\"true\"\n pTooltip=\"Add an intensity line (new colour). Click a line to select it,\n then use the trash tool to remove it.\"\n [escape]=\"false\" tooltipPosition=\"bottom\"\n (onClick)=\"addProfileLine.emit()\">\n <img src=\"assets/plotting/spline-chart.svg\" class=\"toolbar-icon-dark\" style=\"width: 1rem\"/>\n </p-button>\n </ng-container>\n </ng-container>\n <!-- Channels & Histogram: brightness/contrast, gamma, per-channel display,\n colormap/reverse/invert \u2014 opens the floating dialog. -->\n <p-button *ngIf=\"tools.specialTools\" pRipple size=\"small\" [text]=\"true\"\n pTooltip=\"Channels &amp; Histogram (brightness/contrast, colormap)\"\n tooltipPosition=\"bottom\"\n (onClick)=\"openChannelHistogram.emit()\">\n <img src=\"assets/plotting/invert.svg\" class=\"toolbar-icon-dark\" style=\"width: 1rem\"/>\n </p-button>\n\n <!-- Smoothen toggle: bilinear smoothing vs nearest-neighbour raw pixels (Image\n view only \u2014 it's an OpenSeadragon drawer setting). Highlighted when smoothing on. -->\n <p-button *ngIf=\"tools.specialTools && isImageView\" size=\"small\"\n [text]=\"!imageSmoothingEnabled\"\n pTooltip=\"Smoothen (turn on image smoothing)\" tooltipPosition=\"bottom\"\n (onClick)=\"toggleImageSmoothing.emit()\">\n <img src=\"assets/plotting/smooth.svg\" style=\"width: 1rem\"\n [class]=\"imageSmoothingEnabled ? 'toolbar-icon-active' : 'toolbar-icon-dark'\"/>\n </p-button>\n\n <!-- Stack controls -->\n <ng-container *ngIf=\"imageInfo?.isStack\">\n <span class=\"toolbar-separator\"></span>\n <!-- Image view: scrub z-slices live (the renderer swaps slices in place). -->\n <ng-container *ngIf=\"showsLiveSliceScrubber\">\n <span class=\"flex align-items-center mx-1 toolbar-text\">Slice</span>\n <p-slider [(ngModel)]=\"zIndex\" [min]=\"0\" [max]=\"maxIndex\"\n (onChange)=\"zScrub.emit($event.value)\" (onSlideEnd)=\"zSlide.emit($event.value)\"\n pTooltip=\"Slice through the stack (\u2190 / \u2192 keys)\" tooltipPosition=\"bottom\"\n [style]=\"{'width':'16rem'}\" styleClass=\"wand-sensitivity-slider\"></p-slider>\n <span class=\"flex align-items-center mx-1 toolbar-text\">{{zIndex}} of {{maxIndex}}</span>\n </ng-container>\n <!-- Heatmap view: single-image (high-def zoom) vs stack (frame slider). Plotly-only \u2014\n hidden for napari, which has no single/stack toggle or per-frame fetch. -->\n <ng-container *ngIf=\"!showsLiveSliceScrubber && !isNapariMode\">\n <p-button icon=\"pi pi-image\" size=\"small\"\n [text]=\"imageInfo?.showStack\"\n pTooltip=\"Single image mode: enables high-def zoom\" tooltipPosition=\"bottom\"\n (onClick)=\"selectStackOption.emit(stackOptions[0])\">\n </p-button>\n <p-button icon=\"pi pi-images\" size=\"small\"\n [text]=\"!imageInfo?.showStack\"\n pTooltip=\"Stack mode, enables slicing through image frames\" tooltipPosition=\"bottom\"\n (onClick)=\"selectStackOption.emit(stackOptions[1])\">\n </p-button>\n <ng-container *ngIf=\"!imageInfo?.showStack\">\n <span class=\"flex align-items-center mx-1 toolbar-text\">Slice</span>\n <div class=\"p-inputgroup\" style=\"width: auto\">\n <p-inputNumber styleClass=\"w-3rem toolbar-input\"\n\t\t\t\t\tpTooltip=\"View image in stack (&#x23CE;)\"\n\t\t\t\t\t[(ngModel)]=\"zIndex\" [min]=\"0\" [max]=\"maxIndex\"\n (onInput)=\"zIndexInput.emit(zIndex)\" (keydown.enter)=\"reloadAndPlot.emit()\"/>\n <p-button icon=\"pi pi-refresh\" size=\"small\" [text]=\"true\" (onClick)=\"reloadAndPlot.emit()\"\n pTooltip=\"Plot this slice\" tooltipPosition=\"bottom\">\n </p-button>\n </div>\n <span class=\"flex align-items-center mx-1 toolbar-text\">of {{maxIndex}}</span>\n </ng-container>\n </ng-container>\n </ng-container>\n\n <!-- Image actions (special tools) -->\n <ng-container *ngIf=\"tools.specialTools\">\n <span class=\"toolbar-separator\"></span>\n <p-button icon=\"pi pi-download\" size=\"small\" [text]=\"true\"\n pTooltip=\"Download image as PNG\" tooltipPosition=\"bottom\"\n (onClick)=\"downloadImage.emit()\">\n </p-button>\n <p-button *ngIf=\"isHeatmap\" icon=\"pi pi-window-maximize\" size=\"small\" [text]=\"true\"\n pTooltip=\"Fit to view\" tooltipPosition=\"bottom\"\n (onClick)=\"autoscaleImage.emit()\">\n </p-button>\n </ng-container>\n\n <!-- Zoom tools. Plotly rubber-band drag-zoom: on OpenSeadragon the box-zoom\n covers this and a bare drag pans natively, so it's hidden in image view. -->\n <ng-container *ngIf=\"isHeatmap && tools.zoomTools\">\n <span class=\"toolbar-separator\"></span>\n <p-button *ngIf=\"!isImageView\" icon=\"pi pi-search\" size=\"small\"\n [text]=\"activeDragMode !== 'zoom'\"\n pTooltip=\"Zoom drag\" tooltipPosition=\"bottom\"\n (onClick)=\"toggleDragMode.emit('zoom')\">\n </p-button>\n <p-button size=\"small\"\n [text]=\"activeDragMode !== 'zoomToBox'\"\n pTooltip=\"Zoom box (<kbd>b</kbd>)\" [escape]=\"false\" tooltipPosition=\"bottom\"\n (onClick)=\"toggleDragMode.emit('zoomToBox')\">\n <img src=\"assets/plotting/zoom-box.svg\"\n [class]=\"activeDragMode === 'zoomToBox' ? 'toolbar-icon-active' : 'toolbar-icon-dark'\"\n style=\"width: 1rem\"/>\n </p-button>\n <p-button icon=\"pi pi-arrows-alt\" size=\"small\"\n [text]=\"activeDragMode !== 'pan'\"\n pTooltip=\"Pan the image (<kbd>p</kbd>)\" [escape]=\"false\" tooltipPosition=\"bottom\"\n (onClick)=\"toggleDragMode.emit('pan')\">\n </p-button>\n <p-button icon=\"pi pi-search-plus\" size=\"small\" [text]=\"true\"\n pTooltip=\"Zoom in (<kbd>+</kbd>)\" [escape]=\"false\" tooltipPosition=\"bottom\"\n (onClick)=\"zoomIn.emit()\">\n </p-button>\n <p-button icon=\"pi pi-search-minus\" size=\"small\" [text]=\"true\"\n pTooltip=\"Zoom out (<kbd>-</kbd>)\" [escape]=\"false\" tooltipPosition=\"bottom\"\n (onClick)=\"zoomOut.emit()\">\n </p-button>\n </ng-container>\n\n <!-- Surface 3D controls -->\n <ng-container *ngIf=\"!isHeatmap\">\n <span class=\"toolbar-separator\"></span>\n <p-button icon=\"pi pi-search\" size=\"small\"\n [text]=\"activeSurface3dMode !== 'zoom'\"\n pTooltip=\"Zoom\" tooltipPosition=\"bottom\"\n (onClick)=\"toggleSurface3dMode.emit('zoom')\">\n </p-button>\n <p-button icon=\"pi pi-arrows-alt\" size=\"small\"\n [text]=\"activeSurface3dMode !== 'pan'\"\n pTooltip=\"Pan\" tooltipPosition=\"bottom\"\n (onClick)=\"toggleSurface3dMode.emit('pan')\">\n </p-button>\n <p-button icon=\"pi pi-globe\" size=\"small\"\n [text]=\"activeSurface3dMode !== 'orbit'\"\n pTooltip=\"Orbital rotation\" tooltipPosition=\"bottom\"\n (onClick)=\"toggleSurface3dMode.emit('orbit')\">\n </p-button>\n <p-button icon=\"pi pi-sync\" size=\"small\"\n [text]=\"activeSurface3dMode !== 'turntable'\"\n pTooltip=\"Turntable rotation\" tooltipPosition=\"bottom\"\n (onClick)=\"toggleSurface3dMode.emit('turntable')\">\n </p-button>\n <p-button icon=\"pi pi-home\" size=\"small\" [text]=\"true\"\n pTooltip=\"Reset camera to default\" tooltipPosition=\"bottom\"\n (onClick)=\"resetSurfaceCamera.emit()\">\n </p-button>\n <!-- 3D coordinate-axes / scale gizmo toggle (napari-js WebGPU volume/isosurface only).\n `p-button-icon-only` gives the same square sizing as the neighbouring pi-icon buttons\n (the projected <img> doesn't trigger PrimeNG's automatic icon-only sizing). -->\n <p-button size=\"small\" *ngIf=\"isNapariMode\" styleClass=\"p-button-icon-only\"\n [text]=\"!axesVisible\"\n pTooltip=\"Toggle 3D axes / scale\" tooltipPosition=\"bottom\"\n (onClick)=\"toggleAxes.emit()\">\n <img src=\"assets/plotting/3d-coordinates.svg\"\n [class]=\"axesVisible ? 'toolbar-icon-active' : 'toolbar-icon-dark'\"\n style=\"width: 1rem\"/>\n </p-button>\n <!-- Surface wireframe toggle (napari-js WebGPU surface only). -->\n <p-button icon=\"pi pi-share-alt\" size=\"small\" *ngIf=\"isNapariSurfaceMode\"\n [text]=\"!wireframeActive\"\n pTooltip=\"Toggle wireframe\" tooltipPosition=\"bottom\"\n (onClick)=\"toggleWireframe.emit()\">\n </p-button>\n <!-- Resolution (decimate factor) for the napari 3D types \u2014 reloads at coarser/finer sampling. -->\n <ng-container *ngIf=\"isNapari3dMode\">\n <span class=\"flex align-items-center mx-1 toolbar-text\">Res</span>\n <p-dropdown [options]=\"decimateOptions\" optionLabel=\"label\" optionValue=\"value\"\n [ngModel]=\"resolutionScale\" appendTo=\"body\"\n pTooltip=\"Resolution (decimate factor)\" tooltipPosition=\"bottom\"\n [style]=\"{'min-width': '6rem'}\"\n (onChange)=\"selectResolution.emit($event.value)\">\n </p-dropdown>\n </ng-container>\n </ng-container>\n\n <ng-container *ngIf=\"isHeatmap && tools.regionTools\">\n <span class=\"toolbar-separator\"></span>\n <!-- Region Editor: opens the editor as a floating dialog (formerly the\n right-panel Regions tab). Sits with the region drawing tools. -->\n <p-button size=\"small\" [text]=\"true\"\n pTooltip=\"Open the Region Editor\" tooltipPosition=\"bottom\"\n (onClick)=\"openRegionEditor.emit()\">\n <img src=\"assets/plotting/region-editor.svg\" class=\"toolbar-icon-dark\" style=\"width: 1rem\"/>\n </p-button>\n <!-- Region drawing tools -->\n <p-button icon=\"pi pi-arrow-up-right\" size=\"small\"\n [text]=\"activeDragMode !== 'select'\"\n pTooltip=\"Selection mode (<kbd>s</kbd>)\"\n [escape]=\"false\" tooltipPosition=\"bottom\"\n (onClick)=\"toggleDragMode.emit('select')\">\n </p-button>\n <p-button icon=\"pi pi-stop\" size=\"small\"\n [text]=\"activeDragMode !== 'drawrect'\"\n pTooltip=\"Draw a rectangular region (<kbd>r</kbd>)\" [escape]=\"false\"\n tooltipPosition=\"bottom\"\n (onClick)=\"toggleDragMode.emit('drawrect')\">\n </p-button>\n <p-button size=\"small\"\n [text]=\"activeDragMode !== 'drawopenpath'\"\n pTooltip=\"Draw a polyline (<kbd>l</kbd>)\" [escape]=\"false\"\n tooltipPosition=\"bottom\"\n (onClick)=\"toggleDragMode.emit('drawopenpath')\">\n <img src=\"assets/plotting/polyline.svg\"\n [class]=\"activeDragMode === 'drawopenpath' ? 'toolbar-icon-active' : 'toolbar-icon-dark'\"\n style=\"width: 1rem\"/>\n </p-button>\n <p-button icon=\"pi pi-pencil\" size=\"small\"\n [text]=\"activeDragMode !== 'drawclosedpath'\"\n pTooltip=\"Draw a freeform region (<kbd>f</kbd>)\" [escape]=\"false\"\n tooltipPosition=\"bottom\"\n (onClick)=\"toggleDragMode.emit('drawclosedpath')\">\n </p-button>\n <!-- Brush: paint a region by dragging a disc of the given size; the Size\n slider appears while it's active. Hold Shift to erase (subtract). -->\n <p-button size=\"small\"\n [text]=\"activeDragMode !== 'brush'\"\n pTooltip=\"Brush: click and drag to paint a region.\n <br>Hold <kbd>Shift</kbd> while painting to erase from a region.\"\n [escape]=\"false\" tooltipPosition=\"bottom\"\n (onClick)=\"toggleDragMode.emit('brush')\">\n <img src=\"assets/plotting/paintbrush.svg\"\n [class]=\"activeDragMode === 'brush' ? 'toolbar-icon-active' : 'toolbar-icon-dark'\"\n style=\"width: 1rem\"/>\n </p-button>\n <ng-container *ngIf=\"activeDragMode === 'brush'\">\n <span class=\"flex align-items-center mx-1 toolbar-text\">Size</span>\n <p-slider [(ngModel)]=\"brushSize\"\n [min]=\"brushSizeMin\" [max]=\"brushSizeMax\" [step]=\"brushSizeStep\"\n (onChange)=\"brushSizeChange.emit($event.value)\"\n pTooltip=\"Brush diameter in image pixels.\"\n [escape]=\"false\" tooltipPosition=\"bottom\"\n styleClass=\"wand-sensitivity-slider\"></p-slider>\n <span class=\"flex align-items-center mx-1 toolbar-text wand-sensitivity-value\">\n {{ brushSize }}</span>\n </ng-container>\n\n <!-- Vertex editing \u2014 backends with a vertex-editing overlay (OSD + napari-js image). -->\n <ng-container *ngIf=\"supportsRegionVertexTools\">\n <p-button size=\"small\"\n [text]=\"activeDragMode !== 'drawpolygon'\"\n pTooltip=\"Polygon: click to place each vertex; click the first point to close\"\n [escape]=\"false\" tooltipPosition=\"bottom\"\n (onClick)=\"toggleDragMode.emit('drawpolygon')\">\n <img src=\"assets/plotting/polygon-vertices.svg\"\n [class]=\"activeDragMode === 'drawpolygon' ? 'toolbar-icon-active' : 'toolbar-icon-dark'\"\n style=\"width: 1rem\"/>\n </p-button>\n <p-button size=\"small\"\n [text]=\"activeDragMode !== 'addpoint'\"\n pTooltip=\"Add a vertex: click an edge of the selected polygon\"\n [escape]=\"false\" tooltipPosition=\"bottom\"\n (onClick)=\"toggleDragMode.emit('addpoint')\">\n <img src=\"assets/plotting/vertex-add.svg\"\n [class]=\"activeDragMode === 'addpoint' ? 'toolbar-icon-active' : 'toolbar-icon-dark'\"\n style=\"width: 1rem\"/>\n </p-button>\n <p-button size=\"small\"\n [text]=\"activeDragMode !== 'deletepoint'\"\n pTooltip=\"Delete a vertex: click a vertex of the selected polygon\"\n [escape]=\"false\" tooltipPosition=\"bottom\"\n (onClick)=\"toggleDragMode.emit('deletepoint')\">\n <img src=\"assets/plotting/vertex-delete.svg\"\n [class]=\"activeDragMode === 'deletepoint' ? 'toolbar-icon-active' : 'toolbar-icon-dark'\"\n style=\"width: 1rem\"/>\n </p-button>\n </ng-container>\n\n <p-button size=\"small\"\n [text]=\"activeDragMode !== 'wand'\"\n pTooltip=\"Wand: click and drag to grow a region by similar pixel values (<kbd>w</kbd>).\n <br>Hold <kbd>Shift</kbd> while clicking or dragging on a region to erase pixels from it.\"\n [escape]=\"false\" tooltipPosition=\"bottom\"\n (onClick)=\"toggleDragMode.emit('wand')\">\n <img src=\"assets/plotting/wand.svg\"\n [class]=\"activeDragMode === 'wand' ? 'toolbar-icon-active' : 'toolbar-icon-dark'\"\n style=\"width: 1rem\"/>\n </p-button>\n <ng-container *ngIf=\"activeDragMode === 'wand'\">\n <span class=\"flex align-items-center mx-1 toolbar-text\">Sens.</span>\n <p-slider [(ngModel)]=\"wandSensitivity\"\n [min]=\"wandSensitivityMin\" [max]=\"wandSensitivityMax\" [step]=\"wandSensitivityStep\"\n (onChange)=\"wandSensitivityChange.emit($event.value)\"\n pTooltip=\"Higher = stricter (smaller selection); lower = looser.\n Takes effect from the next click/drag tick.\"\n [escape]=\"false\" tooltipPosition=\"bottom\"\n styleClass=\"wand-sensitivity-slider\"></p-slider>\n <span class=\"flex align-items-center mx-1 toolbar-text wand-sensitivity-value\">\n {{ wandSensitivity.toFixed(1) }}</span>\n </ng-container>\n <p-button icon=\"pi pi-eraser\" size=\"small\"\n [text]=\"activeDragMode !== 'eraseVertex'\"\n pTooltip=\"Vertex eraser (<kbd>e</kbd>): click or drag to remove polygon vertices inside\n the cursor circle from any region.\"\n [escape]=\"false\" tooltipPosition=\"bottom\"\n (onClick)=\"toggleDragMode.emit('eraseVertex')\">\n </p-button>\n <ng-container *ngIf=\"activeDragMode === 'eraseVertex'\">\n <span class=\"flex align-items-center mx-1 toolbar-text\">Radius</span>\n <p-slider [(ngModel)]=\"vertexEraserRadius\"\n [min]=\"vertexEraserRadiusMin\" [max]=\"vertexEraserRadiusMax\" [step]=\"vertexEraserRadiusStep\"\n (onChange)=\"vertexEraserRadiusChange.emit($event.value)\"\n pTooltip=\"Eraser radius in image pixels.\n Vertices within this distance of the cursor are removed.\"\n [escape]=\"false\" tooltipPosition=\"bottom\"\n styleClass=\"wand-sensitivity-slider\"></p-slider>\n <span class=\"flex align-items-center mx-1 toolbar-text wand-sensitivity-value\">\n {{ vertexEraserRadius }}</span>\n </ng-container>\n <p-button size=\"small\" [text]=\"true\"\n [disabled]=\"!canUndo\"\n pTooltip=\"Undo the last region action (<kbd>Ctrl</kbd>+<kbd>Z</kbd>).\n Up to 10 actions can be undone.\"\n [escape]=\"false\" tooltipPosition=\"bottom\"\n (onClick)=\"undoRegion.emit()\">\n <img src=\"assets/plotting/undo.svg\" class=\"toolbar-icon-dark\" style=\"width: 1rem\"/>\n </p-button>\n <p-button size=\"small\" [text]=\"true\"\n [disabled]=\"!canRedo\"\n pTooltip=\"Redo the last undone region action\n (<kbd>Ctrl</kbd>+<kbd>Shift</kbd>+<kbd>Z</kbd> / <kbd>Ctrl</kbd>+<kbd>Y</kbd>).\"\n [escape]=\"false\" tooltipPosition=\"bottom\"\n (onClick)=\"redoRegion.emit()\">\n <img src=\"assets/plotting/redo.svg\" class=\"toolbar-icon-dark\" style=\"width: 1rem\"/>\n </p-button>\n <p-button icon=\"pi pi-trash\" size=\"small\" [text]=\"true\"\n pTooltip=\"Delete selected region (<kbd>Del</kbd>)\"\n [escape]=\"false\" tooltipPosition=\"bottom\"\n (onClick)=\"deleteRegion.emit()\">\n </p-button>\n <!-- SAM (jit-ui#90): box-prompt segment, interactive point prompts, and a\n model picker. -->\n <span class=\"toolbar-separator\"></span>\n <!-- SAM controls grouped: box-prompt Segment, then the point-prompt tool,\n then the model-picker dropdown (the chevron) which governs BOTH tools.\n Falls back to Segment + point with no model picker when none are set. -->\n <span *ngIf=\"samModels.length; else segmentPlain\" class=\"p-buttonset segment-split\">\n <p-button size=\"small\" [text]=\"true\"\n pTooltip=\"Segment the drawn rectangles with SAM (box prompt)\"\n tooltipPosition=\"bottom\"\n (onClick)=\"segmentRegions.emit()\">\n <img src=\"assets/plotting/picture-segmentation.svg\"\n class=\"toolbar-icon-dark\" style=\"width: 1rem\"/>\n </p-button>\n <ng-container [ngTemplateOutlet]=\"samPointBtn\"></ng-container>\n <p-button icon=\"pi pi-chevron-down\" size=\"small\" [text]=\"true\"\n pTooltip=\"Pick the SAM model (used by both segment tools)\"\n tooltipPosition=\"bottom\"\n (onClick)=\"samModelMenu.toggle($event)\">\n </p-button>\n </span>\n <ng-template #segmentPlain>\n <span class=\"p-buttonset segment-split\">\n <p-button size=\"small\" [text]=\"true\"\n pTooltip=\"Segment the drawn rectangles with SAM (box prompt)\"\n tooltipPosition=\"bottom\"\n (onClick)=\"segmentRegions.emit()\">\n <img src=\"assets/plotting/picture-segmentation.svg\"\n class=\"toolbar-icon-dark\" style=\"width: 1rem\"/>\n </p-button>\n <ng-container [ngTemplateOutlet]=\"samPointBtn\"></ng-container>\n </span>\n </ng-template>\n <!-- Point-prompt tool \u2014 shared between the models / no-models layouts so it\n always sits just left of the model dropdown. -->\n <ng-template #samPointBtn>\n <p-button size=\"small\"\n [text]=\"activeDragMode !== 'samPoint'\"\n pTooltip=\"SAM point prompts: click a fiber to segment it as a new region;\n Shift/Alt-click to refine (exclude) the current one.<br>Esc undoes it.\"\n [escape]=\"false\" tooltipPosition=\"bottom\"\n (onClick)=\"toggleDragMode.emit('samPoint')\">\n <img src=\"assets/plotting/click.svg\"\n [class]=\"activeDragMode === 'samPoint' ? 'toolbar-icon-active' : 'toolbar-icon-dark'\"\n style=\"width: 1rem\"/>\n </p-button>\n </ng-template>\n <p-menu #samModelMenu [model]=\"samMenuItems\" [popup]=\"true\" appendTo=\"body\"></p-menu>\n <!-- Help: explains the ViT-T vs ViT-B trade-off behind the model picker. -->\n <p-button *ngIf=\"samModels.length\" icon=\"pi pi-info-circle\" size=\"small\" [text]=\"true\"\n pTooltip=\"About the SAM models\" tooltipPosition=\"bottom\"\n (onClick)=\"samModelInfo.toggle($event)\">\n </p-button>\n <p-overlayPanel #samModelInfo appendTo=\"body\" styleClass=\"sam-model-info\">\n <div style=\"max-width: 24rem; line-height: 1.4\">\n <div class=\"font-bold mb-1\">SAM models</div>\n <p class=\"mt-0 mb-2\">\n All are promptable Segment Anything models finetuned for biomedical images. They differ\n in the <i>image encoder</i> (the heavy once-per-image step) and the training domain \u2014\n box and point prompting behave the same.\n </p>\n <div class=\"mb-2\">\n <div class=\"font-bold\">micro-sam ViT-T <span class=\"font-normal\">(default)</span></div>\n <div>Light-microscopy finetuned. TinyViT encoder, ~14&nbsp;MB, very fast, runs on WASM.\n Good mask quality \u2014 best for quick interactive work and many/large objects.</div>\n </div>\n <div class=\"mb-2\">\n <div class=\"font-bold\">micro-sam ViT-B</div>\n <div>Light-microscopy finetuned. ViT-Base encoder, ~172&nbsp;MB, slower, GPU-accelerated\n (WebGPU). Highest accuracy \u2014 when ViT-T misses subtle or overlapping boundaries.</div>\n </div>\n <div class=\"mb-2\">\n <div class=\"font-bold\">patho-sam ViT-B</div>\n <div>Histopathology finetuned (H&amp;E / stained tissue). ViT-Base encoder, GPU-accelerated.\n Use it for pathology slides \u2014 it segments tissue/nuclei better than the microscopy models.</div>\n </div>\n <div class=\"mb-2\">\n <div class=\"font-bold\">patho-sam ViT-B (int8)</div>\n <div>Same model, int8-quantized \u2014 ~100&nbsp;MB vs ~180&nbsp;MB (smaller download). Runs on\n WASM (slower encode than the fp16 version); masks are near-identical. Pick it on slow\n connections.</div>\n </div>\n <div class=\"text-sm\" style=\"opacity: 0.75\">\n The choice applies to both the box and point tools. Each tool loads its own encoder,\n so the first click after switching re-encodes once.\n </div>\n </div>\n </p-overlayPanel>\n <p-button size=\"small\" [text]=\"true\"\n pTooltip=\"Auto-segment cells inside each drawn rectangle (cellpose-SAM, client-side)\"\n tooltipPosition=\"bottom\"\n (onClick)=\"segmentCellpose.emit()\">\n <img src=\"assets/plotting/cells.svg\" class=\"toolbar-icon-dark\" style=\"width: 1rem\"/>\n </p-button>\n\n <!-- YOLO: detect objects across the current view. Unlike the SAM and\n cellpose tools it takes no prompt \u2014 a detector finds objects in a\n field rather than being pointed at one \u2014 so there is nothing to draw\n first. Split button: run, pick checkpoint, open parameters. -->\n <span *ngIf=\"yoloModels.length\" class=\"p-buttonset segment-split\">\n <p-button icon=\"pi pi-microchip-ai\" size=\"small\" [text]=\"true\"\n pTooltip=\"Detect objects in the current view with YOLO (client-side)\"\n tooltipPosition=\"bottom\"\n (onClick)=\"detectYolo.emit()\">\n </p-button>\n <p-button icon=\"pi pi-chevron-down\" size=\"small\" [text]=\"true\"\n pTooltip=\"Pick the YOLO model\"\n tooltipPosition=\"bottom\"\n (onClick)=\"yoloModelMenu.toggle($event)\">\n </p-button>\n <p-button icon=\"pi pi-sliders-h\" size=\"small\" [text]=\"true\"\n pTooltip=\"YOLO parameters\"\n tooltipPosition=\"bottom\"\n (onClick)=\"openYoloParams.emit()\">\n </p-button>\n </span>\n <p-menu #yoloModelMenu [model]=\"yoloMenuItems\" [popup]=\"true\" appendTo=\"body\"></p-menu>\n </ng-container>\n\n <ng-container *ngIf=\"tools.help\">\n <span class=\"toolbar-separator\"></span>\n\n <!-- Help -->\n <p-button icon=\"pi pi-question-circle\" size=\"small\" [text]=\"true\"\n pTooltip=\"Help\" tooltipPosition=\"bottom\"\n (onClick)=\"showHelp()\">\n </p-button>\n </ng-container>\n\n </div>\n</p-toolbar>\n<p-dialog header=\"Plotting help\" [(visible)]=\"displayHelpDialog\" [modal]=\"true\" [style]=\"{width: '50vw'}\"\n [maximizable]=\"true\" [draggable]=\"false\" [resizable]=\"false\">\n <p>Click a file in the left-hand tree to display it. By default it opens in the\n <b>Image</b> view \u2014 a natively tiled, deeply zoomable raster (OpenSeadragon) with a\n navigator minimap and a physical-units scale bar. <b>Click the image to zoom</b> toward\n the clicked point; scroll to zoom and drag to pan. When a stack is open, a slice slider\n (Image view) or image-mode toggle (other views) appears. Regions can also be edited in the\n <b>Region Editor</b>, opened with the\n <img src=\"assets/plotting/region-editor.svg\" width=\"14\" height=\"14\" alt=\"Region Editor\"/>\n toolbar button as a floating dialog. Drag the <i class=\"pi pi-bars\"></i> handle to detach\n this toolbar into a floating window.\n </p>\n\n <h3>Plot-type dropdown</h3>\n <p>\n The <b>plot-type dropdown</b> switches how the file is visualized:\n <b>Image</b>, <b>Contour</b>, <b>Scatter (regions)</b>, <b>Surface (3D)</b>,\n <b>Scatter 3D</b>, and <b>Isosurface (3D)</b>. Scalar and 3D types require a grayscale\n image (the 3D volume types also require a z-stack). The non-image views render with\n Plotly and support \"real zooming\" (a downscaled overview that re-fetches higher\n resolution as you zoom in).\n </p>\n <ul>\n <li>\"<b>Isosurface band</b>\" slider (Isosurface): sets the intensity band the surfaces are\n drawn for, mapped onto the volume's real intensity range.</li>\n <li>\"<b>3D camera</b>\" (Surface / Scatter 3D / Isosurface): zoom <i class=\"pi pi-search\"></i>,\n pan <i class=\"pi pi-arrows-alt\"></i>, orbit <i class=\"pi pi-globe\"></i>,\n turntable <i class=\"pi pi-sync\"></i>, and reset camera <i class=\"pi pi-home\"></i>;\n scroll to zoom the 3D scene.</li>\n </ul>\n\n <h3>Image manipulation tools</h3>\n <ul>\n <li>\"<b>Region Editor</b>\" <img src=\"assets/plotting/region-editor.svg\" width=\"15\" height=\"15\">:\n opens the Region Editor as a floating, draggable dialog \u2014 a per-image table of every region\n (class, colour, area) with bulk select/label/colour/delete and GeoJSON import/export. It stays\n in sync with the image, so you can keep it open while drawing.</li>\n <li>\"<b>Intensity profile lines</b>\" <img src=\"assets/plotting/spline-chart.svg\" width=\"15\" height=\"15\">\n (<i>work in progress \u2014 not yet stable</i>): adds a coloured line ROI; a floating inset\n chart plots intensity along each line, updates live as you drag it, and re-samples at the\n current zoom resolution. Each added line gets its own colour; select a line and use the\n trash tool <i class=\"pi pi-trash\"></i> to remove it (and its trace).</li>\n <li>\"<b>Channels &amp; Histogram</b>\" <img src=\"assets/plotting/invert.svg\" width=\"15\" height=\"15\">:\n opens a floating dialog for brightness/contrast, gamma, and per-channel display, plus the\n <b>colormap / LUT</b> controls (grayscale only) \u2014 a <b>reverse</b> toggle and a colormap\n dropdown (default: inverted greys).</li>\n <li>\"<b>Smoothen</b>\" <img src=\"assets/plotting/smooth.svg\" width=\"15\" height=\"15\">\n (Image view only): the image opens showing <b>raw pixels</b> (nearest-neighbour), so\n zooming in renders crisp blocks for pixel inspection. Toggle this on for bilinear\n smoothing; the icon turns white while smoothing is active.</li>\n </ul>\n\n <h3>Download &amp; view tools</h3>\n <ul>\n <li>\"<b>Download</b>\" <i class=\"pi pi-download\"></i>: download the current image as a PNG.</li>\n <li>\"<b>Fit to view</b>\" / autoscale <i class=\"pi pi-window-maximize\"></i>: resets the image\n to its original dimensions. In stack mode it also resets to single-image mode.</li>\n </ul>\n\n <h3>Zoom tools</h3>\n <ul>\n <li>\"<b>Zoom box</b>\" <img src=\"assets/plotting/zoom-box.svg\" width=\"15\" height=\"15\">\n (<kbd>b</kbd>): drag a box to select an area to zoom into.</li>\n <li>\"<b>Pan</b>\" <i class=\"pi pi-arrows-alt\"></i> (<kbd>p</kbd>): drag to pan the image.</li>\n <li>\"<b>Zoom in</b>\" <i class=\"pi pi-search-plus\"></i> (<kbd>+</kbd>): zoom in one step.</li>\n <li>\"<b>Zoom out</b>\" <i class=\"pi pi-search-minus\"></i> (<kbd>-</kbd>): zoom out one step,\n stopping at the original image dimensions.</li>\n </ul>\n\n <h3>Region tools</h3>\n <p>By default no tool is selected (all greyed-out); selecting a tool highlights it. The\n vertex-editing tools (polygon, add/delete vertex, move, B\u00E9zier conversions) are available\n in the <b>Image</b> view only. On a <b>donut</b> (a region with a hole), the inner-ring\n vertices are editable too \u2014 drag, add, and delete them just like the outer ring.</p>\n <ul>\n <li>\"<b>Selection</b>\" <i class=\"pi pi-arrow-up-right\"></i> (<kbd>s</kbd>): a neutral mode\n that turns off whatever drawing tool was active (wand, vertex eraser, rectangle, etc.).\n Use it to step out of editing without picking another tool.</li>\n <li>\"<b>Rectangular region</b>\" <i class=\"pi pi-stop\"></i> (<kbd>r</kbd>): click and drag to\n create a rectangular region.</li>\n <li>\"<b>Polyline</b>\" <img src=\"assets/plotting/polyline.svg\" width=\"14\" height=\"14\">\n (<kbd>l</kbd>): draw an open (non-closed) polyline by clicking and dragging. Polylines can\n be edited and exported to GeoJSON as a <code>LineString</code> feature.</li>\n <li>\"<b>Freeform region</b>\" <i class=\"pi pi-pencil\"></i> (<kbd>f</kbd>): click and drag to\n draw a closed freeform polygon.</li>\n <li>\"<b>Brush</b>\" <img src=\"assets/plotting/paintbrush.svg\" width=\"15\" height=\"15\">: click\n and drag to paint a region with a circular brush \u2014 the painted stroke is the region.\n A <b>Size</b> slider (brush diameter, in image pixels) appears in the toolbar while the\n brush is active. Hold <kbd>Shift</kbd> while painting to <b>erase</b> (subtract) from a\n region instead. Painting over an existing region extends it; brushing across two regions\n merges them. Painting a closed ring (or shift-erasing an interior pocket) leaves the\n enclosed area empty \u2014 a <b>donut</b> (Image view).</li>\n <li>\"<b>Polygon</b>\" <img src=\"assets/plotting/polygon-vertices.svg\" width=\"15\" height=\"15\">:\n click to place each vertex; click the first point to close the polygon.</li>\n <li>\"<b>Add vertex</b>\" <img src=\"assets/plotting/vertex-add.svg\" width=\"15\" height=\"15\">:\n click an edge of the selected polygon to insert a new vertex.</li>\n <li>\"<b>Delete vertex</b>\" <img src=\"assets/plotting/vertex-delete.svg\" width=\"15\" height=\"15\">:\n click a vertex of the selected polygon to remove it.</li>\n <li>\"<b>Move region</b>\" <img src=\"assets/plotting/region-move.svg\" width=\"15\" height=\"15\">:\n drag to move the selected region.</li>\n <li>\"<b>To B\u00E9zier</b>\" <img src=\"assets/plotting/to-bezier.svg\" width=\"15\" height=\"15\"> /\n \"<b>To polygon</b>\" <img src=\"assets/plotting/to-polygon.svg\" width=\"15\" height=\"15\">:\n convert the selected region to a smooth B\u00E9zier curve, or back to a straight-edged polygon.</li>\n <li>\"<b>Wand</b>\" <img src=\"assets/plotting/wand.svg\" width=\"15\" height=\"15\"> (<kbd>w</kbd>):\n click to grow a region from pixels with similar values around the click point. Hold\n <kbd>Ctrl</kbd>/<kbd>Cmd</kbd> for an exact-match flood fill; drag to keep extending the\n selection while moving; hold <kbd>Shift</kbd> while clicking or dragging on an existing\n region to <b>erase</b> pixels from it. Use the <b>Sens.</b> slider that appears to make the\n wand stricter (higher = smaller selection) or looser. Subsequent clicks keep extending the\n same region; clicking inside an existing region adopts it, and growing into another region\n merges them.</li>\n <li>\"<b>Vertex eraser</b>\" <i class=\"pi pi-eraser\"></i> (<kbd>e</kbd>): click or click-and-drag\n to remove polygon vertices that fall inside the cursor circle from any region, including a\n region's inner rings (donut holes). Use the <b>Radius</b> slider to set the eraser size in\n image pixels. Polygons reduced below 3 vertices (or polylines below 2) are deleted entirely;\n a hole reduced below 3 vertices is removed.</li>\n <li>\"<b>Undo</b>\" <img src=\"assets/plotting/undo.svg\" width=\"15\" height=\"15\">\n (<kbd>Ctrl</kbd>/<kbd>Cmd</kbd>+<kbd>Z</kbd>): reverts the\n last region action (draw, delete, edit, wand/brush stroke, segmentation, \u2026). Up to the last\n <b>10</b> actions can be undone; the button greys out when there's nothing left to undo.</li>\n <li>\"<b>Redo</b>\" <img src=\"assets/plotting/redo.svg\" width=\"15\" height=\"15\">\n (<kbd>Ctrl</kbd>/<kbd>Cmd</kbd>+<kbd>Shift</kbd>+<kbd>Z</kbd> or <kbd>Ctrl</kbd>/<kbd>Cmd</kbd>+<kbd>Y</kbd>):\n re-applies the most recently undone action. Performing a new region action clears what can be redone.</li>\n <li>\"<b>Delete region</b>\" <i class=\"pi pi-trash\"></i> (<kbd>Del</kbd>): deletes the selected\n region. This can also be done from the Region Editor.</li>\n </ul>\n\n <h3>Region actions (right-click the image)</h3>\n <p>Right-click a region \u2014 or the current multi-selection \u2014 to open the\n \"<b>Selected region(s)</b>\" group at the top of the context menu. These act immediately on\n the selection (they're not drawing modes) and are fully undo/redo-able:</p>\n <ul>\n <li>\"<b>Select all regions</b>\" <i class=\"pi pi-check-square\"></i>: select every region on\n the image (also in the Region Editor toolbar).</li>\n <li>\"<b>Merge / group</b>\": combine 2+ selected regions into one. If the pieces don't touch,\n the result is a single multi-part region (sparse \"bits\").</li>\n <li>\"<b>Ungroup</b>\": split a multi-part region back into one region per piece.</li>\n <li>\"<b>Inverse</b>\": replace the selection with everything <i>outside</i> it \u2014 the image\n rectangle with the selected regions punched out as holes. Works at any image size.</li>\n <li>\"<b>Simplify</b>\": reduce a region's vertex count (Douglas\u2013Peucker) by an altitude\n threshold in pixels \u2014 pick <b>Light</b> (1&nbsp;px), <b>Medium</b> (3&nbsp;px),\n <b>Strong</b> (8&nbsp;px), or <b>Custom\u2026</b> for a slider. Applies to the exterior and any\n holes (and every part of a multi-part region).</li>\n <li>The B\u00E9zier conversions and Delete also live in this group (the right-click equivalents of\n the toolbar / vertex tools above).</li>\n </ul>\n\n <h3>Segmentation tools (SAM &amp; cellpose)</h3>\n <p>AI-assisted segmentation that runs entirely in the browser. The box and point\n tools use a promptable <b>micro-sam</b> model; the cellpose tool auto-detects cells.\n Generated regions inherit the color of the rectangle they came from. The first run\n downloads the model (a progress toast is shown); it's then cached.</p>\n <ul>\n <li>\"<b>Segment</b>\" <img src=\"assets/plotting/picture-segmentation.svg\" width=\"15\" height=\"15\">\n (box prompt): draw one or more <b>Rectangular regions</b> around the objects of interest,\n then click Segment. Each rectangle is replaced by the SAM mask of the object inside it.</li>\n <li>\"<b>Point prompts</b>\" <img src=\"assets/plotting/click.svg\" width=\"15\" height=\"15\">: click directly on an object to\n segment it as a <b>new</b> region. Each click starts a fresh object \u2014 clicking another\n object won't grow the previous one. <kbd>Shift</kbd>/<kbd>Alt</kbd>-click adds an\n <i>exclude</i> point that refines the current object; <kbd>Esc</kbd> undoes it,\n <kbd>Enter</kbd> commits it.</li>\n <li>\"<b>Model picker</b>\" <i class=\"pi pi-chevron-down\"></i>: the dropdown attached to the\n Segment button chooses the SAM model \u2014 <b>micro-sam ViT-T</b> (default; small, fast) or\n <b>micro-sam ViT-B</b> (larger, highest accuracy) for light microscopy, or\n <b>patho-sam ViT-B</b> for histopathology (H&amp;E / stained tissue). The choice applies to\n <i>both</i> the box and point tools. The <i class=\"pi pi-info-circle\"></i> info button next\n to it explains the trade-off.</li>\n <li>\"<b>Cellpose</b>\" <img src=\"assets/plotting/cells.svg\" width=\"15\" height=\"15\">: draw rectangles, then click Cellpose to\n automatically segment every cell inside each rectangle (client-side cellpose-SAM) \u2014 one\n region per detected cell. No clicking on individual cells needed.</li>\n </ul>\n\n <h3>Help</h3>\n <p>\"<b>Help</b>\" <i class=\"pi pi-question-circle\"></i>: opens this dialog.</p>\n\n <h3>Keyboard shortcuts</h3>\n <table class=\"shortcuts-table\">\n <tr><td><kbd>+</kbd> / <kbd>=</kbd></td><td>Zoom in</td></tr>\n <tr><td><kbd>-</kbd></td><td>Zoom out</td></tr>\n <tr><td><kbd>b</kbd></td><td>Toggle zoom box mode</td></tr>\n <tr><td><kbd>p</kbd></td><td>Toggle pan mode</td></tr>\n <tr><td><kbd>f</kbd></td><td>Toggle freeform region drawing</td></tr>\n <tr><td><kbd>l</kbd></td><td>Toggle polyline drawing</td></tr>\n <tr><td><kbd>r</kbd></td><td>Toggle rectangular region drawing</td></tr>\n <tr><td><kbd>w</kbd></td><td>Toggle wand tool</td></tr>\n <tr><td>Wand + <kbd>Shift</kbd></td><td>Erase pixels from the active region</td></tr>\n <tr><td>Wand + <kbd>Ctrl</kbd>/<kbd>Cmd</kbd></td><td>Exact-match flood fill (no smoothing)</td></tr>\n <tr><td><kbd>e</kbd></td><td>Toggle vertex eraser</td></tr>\n <tr><td><kbd>s</kbd></td><td>Selection mode (deactivates drawing tools)</td></tr>\n <tr><td>Point prompt + <kbd>Shift</kbd>/<kbd>Alt</kbd></td><td>Add an exclude point (refine the current object)</td></tr>\n <tr><td><kbd>Enter</kbd></td><td>Commit the current SAM point object</td></tr>\n <tr><td><kbd>Esc</kbd></td><td>Undo the current SAM point object</td></tr>\n <tr><td><kbd>Del</kbd> / <kbd>\u232B</kbd></td><td>Delete selected region</td></tr>\n <tr><td><kbd>Ctrl</kbd>/<kbd>Cmd</kbd>+<kbd>Z</kbd></td><td>Undo the last region action (up to 10)</td></tr>\n <tr><td><kbd>Ctrl</kbd>/<kbd>Cmd</kbd>+<kbd>Shift</kbd>+<kbd>Z</kbd> / <kbd>Ctrl</kbd>+<kbd>Y</kbd></td><td>Redo the last undone region action</td></tr>\n </table>\n\n <ng-template pTemplate=\"footer\">\n <p-button icon=\"pi pi-check\" (click)=\"displayHelpDialog=false\" label=\"Ok\" styleClass=\"p-button-text\">\n </p-button>\n </ng-template>\n</p-dialog>\n", styles: [":host{display:block}:host ::ng-deep .p-toolbar{padding:.25rem .5rem}.toolbar-text{color:var(--primary-color);font-size:.875rem}::ng-deep .toolbar-input .p-inputtext{color:var(--primary-color);border-color:var(--primary-color)}.toolbar-separator{border-left:1px solid var(--surface-border);height:1.5rem;margin:0 .25rem;align-self:center}.toolbar-icon-dark{filter:brightness(0) invert(47%) sepia(98%) saturate(567%) hue-rotate(181deg) brightness(103%) contrast(98%)}.toolbar-icon-active{filter:brightness(0) invert(1)}::ng-deep .plotmode-item{display:inline-flex;align-items:center;gap:.5rem;color:#000}::ng-deep .plotmode-item i{font-size:1rem;color:var(--primary-color)}::ng-deep .plotmode-item .plotmode-icon-img{width:1.1rem;height:1.1rem;object-fit:contain;filter:brightness(0) invert(47%) sepia(98%) saturate(567%) hue-rotate(181deg) brightness(103%) contrast(98%)}::ng-deep .wand-sensitivity-slider{width:8rem;align-self:center;margin:0 .25rem}.wand-sensitivity-value{min-width:2rem;text-align:right}span.colormap{width:50px;height:auto;display:inline-block}img.colormap{width:50px;height:auto}.colormap{background:no-repeat;background-size:100%;vertical-align:middle}kbd{background:#e8e8e8;border:1px solid #bbb;border-radius:3px;padding:1px 5px;font-family:monospace;font-size:.85em}.shortcuts-table{border-collapse:collapse}.shortcuts-table td{padding:.25rem .75rem .25rem 0;vertical-align:middle}.shortcuts-table td:first-child{white-space:nowrap}\n"], dependencies: [{ kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: i3.Toolbar, selector: "p-toolbar", inputs: ["style", "styleClass", "ariaLabelledBy"] }, { kind: "directive", type: i4.PrimeTemplate, selector: "[pTemplate]", inputs: ["type", "pTemplate"] }, { kind: "component", type: i5.Button, selector: "p-button", inputs: ["type", "iconPos", "icon", "badge", "label", "disabled", "loading", "loadingIcon", "raised", "rounded", "text", "plain", "severity", "outlined", "link", "tabindex", "size", "style", "styleClass", "badgeClass", "ariaLabel", "autofocus"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: i6.Menu, selector: "p-menu", inputs: ["model", "popup", "style", "styleClass", "appendTo", "autoZIndex", "baseZIndex", "showTransitionOptions", "hideTransitionOptions", "ariaLabel", "ariaLabelledBy", "id", "tabindex"], outputs: ["onShow", "onHide", "onBlur", "onFocus"] }, { kind: "directive", type: i7.Tooltip, selector: "[pTooltip]", inputs: ["tooltipPosition", "tooltipEvent", "appendTo", "positionStyle", "tooltipStyleClass", "tooltipZIndex", "escape", "showDelay", "hideDelay", "life", "positionTop", "positionLeft", "autoHide", "fitContent", "hideOnEscape", "pTooltip", "tooltipDisabled", "tooltipOptions"] }, { kind: "component", type: i8.Dropdown, selector: "p-dropdown", inputs: ["id", "scrollHeight", "filter", "name", "style", "panelStyle", "styleClass", "panelStyleClass", "readonly", "required", "editable", "appendTo", "tabindex", "placeholder", "loadingIcon", "filterPlaceholder", "filterLocale", "variant", "inputId", "dataKey", "filterBy", "filterFields", "autofocus", "resetFilterOnHide", "checkmark", "dropdownIcon", "loading", "optionLabel", "optionValue", "optionDisabled", "optionGroupLabel", "optionGroupChildren", "autoDisplayFirst", "group", "showClear", "emptyFilterMessage", "emptyMessage", "lazy", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "overlayOptions", "ariaFilterLabel", "ariaLabel", "ariaLabelledBy", "filterMatchMode", "maxlength", "tooltip", "tooltipPosition", "tooltipPositionStyle", "tooltipStyleClass", "focusOnHover", "selectOnFocus", "autoOptionFocus", "autofocusFilter", "autoShowPanelOnPrintableCharacterKeyDown", "disabled", "itemSize", "autoZIndex", "baseZIndex", "showTransitionOptions", "hideTransitionOptions", "filterValue", "options"], outputs: ["onChange", "onFilter", "onFocus", "onBlur", "onClick", "onShow", "onHide", "onClear", "onLazyLoad"] }, { kind: "component", type: i9.Slider, selector: "p-slider", inputs: ["animate", "disabled", "min", "max", "orientation", "step", "range", "style", "styleClass", "ariaLabel", "ariaLabelledBy", "tabindex", "autofocus"], outputs: ["onChange", "onSlideEnd"] }, { kind: "component", type: i10.InputNumber, selector: "p-inputNumber", inputs: ["showButtons", "format", "buttonLayout", "inputId", "styleClass", "style", "placeholder", "size", "maxlength", "tabindex", "title", "ariaLabelledBy", "ariaLabel", "ariaRequired", "name", "required", "autocomplete", "min", "max", "incrementButtonClass", "decrementButtonClass", "incrementButtonIcon", "decrementButtonIcon", "readonly", "step", "allowEmpty", "locale", "localeMatcher", "mode", "currency", "currencyDisplay", "useGrouping", "variant", "minFractionDigits", "maxFractionDigits", "prefix", "suffix", "inputStyle", "inputStyleClass", "showClear", "autofocus", "disabled"], outputs: ["onInput", "onFocus", "onBlur", "onKeyDown", "onClear"] }, { kind: "component", type: i11.Dialog, selector: "p-dialog", inputs: ["header", "draggable", "resizable", "positionLeft", "positionTop", "contentStyle", "contentStyleClass", "modal", "closeOnEscape", "dismissableMask", "rtl", "closable", "responsive", "appendTo", "breakpoints", "styleClass", "maskStyleClass", "maskStyle", "showHeader", "breakpoint", "blockScroll", "autoZIndex", "baseZIndex", "minX", "minY", "focusOnShow", "maximizable", "keepInViewport", "focusTrap", "transitionOptions", "closeIcon", "closeAriaLabel", "closeTabindex", "minimizeIcon", "maximizeIcon", "visible", "style", "position"], outputs: ["onShow", "onHide", "visibleChange", "onResizeInit", "onResizeEnd", "onDragEnd", "onMaximize"] }, { kind: "directive", type: i12.Ripple, selector: "[pRipple]" }, { kind: "component", type: i13.OverlayPanel, selector: "p-overlayPanel", inputs: ["ariaLabel", "ariaLabelledBy", "dismissable", "showCloseIcon", "style", "styleClass", "appendTo", "autoZIndex", "ariaCloseLabel", "baseZIndex", "focusOnShow", "showTransitionOptions", "hideTransitionOptions"], outputs: ["onShow", "onHide"] }] }); }
237
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "17.3.9", type: ToolbarComponent, selector: "plotting-toolbar", inputs: { imageInfo: "imageInfo", plotTypeOptions: "plotTypeOptions", selectedPlotType: "selectedPlotType", isoRange: "isoRange", maxIndex: "maxIndex", zIndex: "zIndex", isHeatmap: "isHeatmap", activeDragMode: "activeDragMode", activeSurface3dMode: "activeSurface3dMode", axesVisible: "axesVisible", wireframeActive: "wireframeActive", resolutionScale: "resolutionScale", wandSensitivity: "wandSensitivity", brushSize: "brushSize", vertexEraserRadius: "vertexEraserRadius", canUndo: "canUndo", canRedo: "canRedo", stackOptions: "stackOptions", tools: "tools", imageSmoothingEnabled: "imageSmoothingEnabled", samModels: "samModels", samModelId: "samModelId", yoloModels: "yoloModels", yoloModelId: "yoloModelId", retinalModels: "retinalModels", retinalModelId: "retinalModelId" }, outputs: { selectPlotType: "selectPlotType", addProfileLine: "addProfileLine", toggleImageSmoothing: "toggleImageSmoothing", isoRangeChange: "isoRangeChange", openChannelHistogram: "openChannelHistogram", openRegionEditor: "openRegionEditor", selectStackOption: "selectStackOption", zScrub: "zScrub", zSlide: "zSlide", zIndexInput: "zIndexInput", reloadAndPlot: "reloadAndPlot", downloadImage: "downloadImage", autoscaleImage: "autoscaleImage", toggleDragMode: "toggleDragMode", zoomIn: "zoomIn", zoomOut: "zoomOut", toggleSurface3dMode: "toggleSurface3dMode", resetSurfaceCamera: "resetSurfaceCamera", toggleAxes: "toggleAxes", toggleWireframe: "toggleWireframe", selectResolution: "selectResolution", deleteRegion: "deleteRegion", undoRegion: "undoRegion", redoRegion: "redoRegion", segmentRegions: "segmentRegions", segmentCellpose: "segmentCellpose", detectYolo: "detectYolo", yoloModelChange: "yoloModelChange", openYoloParams: "openYoloParams", segmentRetinal: "segmentRetinal", retinalModelChange: "retinalModelChange", openRetinalParams: "openRetinalParams", samModelChange: "samModelChange", wandSensitivityChange: "wandSensitivityChange", brushSizeChange: "brushSizeChange", vertexEraserRadiusChange: "vertexEraserRadiusChange" }, usesOnChanges: true, ngImport: i0, template: "<p-toolbar>\n <div class=\"p-toolbar-group-start flex-wrap align-items-center gap-1\">\n\n <!-- Host-provided custom toolbar buttons, shown first (e.g. app-specific\n actions the visualization view itself doesn't own). -->\n <ng-content></ng-content>\n\n <!-- Plot type: available for any image (grayscale or RGB). -->\n <ng-container *ngIf=\"imageInfo && tools.specialTools\">\n <span class=\"toolbar-separator\"></span>\n <p-dropdown [options]=\"plotTypeOptions\" optionLabel=\"label\" optionValue=\"type\"\n [(ngModel)]=\"selectedPlotType\" appendTo=\"body\"\n pTooltip=\"Select plot type\" tooltipPosition=\"bottom\"\n [style]=\"{'min-width': '11rem'}\"\n styleClass=\"plotmode-dropdown\" panelStyleClass=\"plotmode-panel\"\n (onChange)=\"selectPlotType.emit($event.value)\">\n <ng-template let-opt pTemplate=\"selectedItem\">\n <span class=\"plotmode-item\" *ngIf=\"opt\">\n <i *ngIf=\"isPiIcon(opt.icon)\" [class]=\"opt.icon\"></i>\n <img *ngIf=\"opt.icon && !isPiIcon(opt.icon)\" class=\"plotmode-icon-img\" [src]=\"opt.icon\" alt=\"\" />\n <span>{{ opt.label }}</span>\n </span>\n </ng-template>\n <ng-template let-opt pTemplate=\"item\">\n <span class=\"plotmode-item\">\n <i *ngIf=\"isPiIcon(opt.icon)\" [class]=\"opt.icon\"></i>\n <img *ngIf=\"opt.icon && !isPiIcon(opt.icon)\" class=\"plotmode-icon-img\" [src]=\"opt.icon\" alt=\"\" />\n <span>{{ opt.label }}</span>\n </span>\n </ng-template>\n </p-dropdown>\n <ng-container *ngIf=\"isIsosurfaceMode\">\n <span class=\"flex align-items-center mx-1 toolbar-text\">Iso</span>\n <p-slider [(ngModel)]=\"isoRange\" [range]=\"true\"\n [min]=\"isoValueMin\" [max]=\"isoValueMax\" [step]=\"isoValueStep\"\n (onChange)=\"isoRangeChange.emit($event.values)\"\n pTooltip=\"Isosurface band (0\u2013255), mapped onto the volume's\n actual intensity range. Surfaces are drawn for voxels whose\n intensity falls in the selected band \u2014 narrow it to isolate\n features, widen it to see the whole structure.\"\n [escape]=\"false\" tooltipPosition=\"bottom\"\n styleClass=\"wand-sensitivity-slider\"></p-slider>\n <span class=\"flex align-items-center mx-1 toolbar-text wand-sensitivity-value\">\n {{ isoRange[0] }}\u2013{{ isoRange[1] }}</span>\n </ng-container>\n <!-- Intensity tools (Heatmap / Image mode): add another colored line ROI.\n Each added line gets the next bright palette colour and its own inset\n trace; select a line on the image and use the trash tool to remove it\n (and its trace). The lines are Region-based but excluded from the\n Region Editor and exports. -->\n <ng-container *ngIf=\"isIntensityCapable\">\n <span class=\"toolbar-separator\"></span>\n <p-button size=\"small\" [text]=\"true\"\n pTooltip=\"Add an intensity line (new colour). Click a line to select it,\n then use the trash tool to remove it.\"\n [escape]=\"false\" tooltipPosition=\"bottom\"\n (onClick)=\"addProfileLine.emit()\">\n <img src=\"assets/plotting/spline-chart.svg\" class=\"toolbar-icon-dark\" style=\"width: 1rem\"/>\n </p-button>\n </ng-container>\n </ng-container>\n <!-- Channels & Histogram: brightness/contrast, gamma, per-channel display,\n colormap/reverse/invert \u2014 opens the floating dialog. -->\n <p-button *ngIf=\"tools.specialTools\" pRipple size=\"small\" [text]=\"true\"\n pTooltip=\"Channels &amp; Histogram (brightness/contrast, colormap)\"\n tooltipPosition=\"bottom\"\n (onClick)=\"openChannelHistogram.emit()\">\n <img src=\"assets/plotting/invert.svg\" class=\"toolbar-icon-dark\" style=\"width: 1rem\"/>\n </p-button>\n\n <!-- Smoothen toggle: bilinear smoothing vs nearest-neighbour raw pixels (Image\n view only \u2014 it's an OpenSeadragon drawer setting). Highlighted when smoothing on. -->\n <p-button *ngIf=\"tools.specialTools && isImageView\" size=\"small\"\n [text]=\"!imageSmoothingEnabled\"\n pTooltip=\"Smoothen (turn on image smoothing)\" tooltipPosition=\"bottom\"\n (onClick)=\"toggleImageSmoothing.emit()\">\n <img src=\"assets/plotting/smooth.svg\" style=\"width: 1rem\"\n [class]=\"imageSmoothingEnabled ? 'toolbar-icon-active' : 'toolbar-icon-dark'\"/>\n </p-button>\n\n <!-- Stack controls -->\n <ng-container *ngIf=\"imageInfo?.isStack\">\n <span class=\"toolbar-separator\"></span>\n <!-- Image view: scrub z-slices live (the renderer swaps slices in place). -->\n <ng-container *ngIf=\"showsLiveSliceScrubber\">\n <span class=\"flex align-items-center mx-1 toolbar-text\">Slice</span>\n <p-slider [(ngModel)]=\"zIndex\" [min]=\"0\" [max]=\"maxIndex\"\n (onChange)=\"zScrub.emit($event.value)\" (onSlideEnd)=\"zSlide.emit($event.value)\"\n pTooltip=\"Slice through the stack (\u2190 / \u2192 keys)\" tooltipPosition=\"bottom\"\n [style]=\"{'width':'16rem'}\" styleClass=\"wand-sensitivity-slider\"></p-slider>\n <span class=\"flex align-items-center mx-1 toolbar-text\">{{zIndex}} of {{maxIndex}}</span>\n </ng-container>\n <!-- Heatmap view: single-image (high-def zoom) vs stack (frame slider). Plotly-only \u2014\n hidden for napari, which has no single/stack toggle or per-frame fetch. -->\n <ng-container *ngIf=\"!showsLiveSliceScrubber && !isNapariMode\">\n <p-button icon=\"pi pi-image\" size=\"small\"\n [text]=\"imageInfo?.showStack\"\n pTooltip=\"Single image mode: enables high-def zoom\" tooltipPosition=\"bottom\"\n (onClick)=\"selectStackOption.emit(stackOptions[0])\">\n </p-button>\n <p-button icon=\"pi pi-images\" size=\"small\"\n [text]=\"!imageInfo?.showStack\"\n pTooltip=\"Stack mode, enables slicing through image frames\" tooltipPosition=\"bottom\"\n (onClick)=\"selectStackOption.emit(stackOptions[1])\">\n </p-button>\n <ng-container *ngIf=\"!imageInfo?.showStack\">\n <span class=\"flex align-items-center mx-1 toolbar-text\">Slice</span>\n <div class=\"p-inputgroup\" style=\"width: auto\">\n <p-inputNumber styleClass=\"w-3rem toolbar-input\"\n\t\t\t\t\tpTooltip=\"View image in stack (&#x23CE;)\"\n\t\t\t\t\t[(ngModel)]=\"zIndex\" [min]=\"0\" [max]=\"maxIndex\"\n (onInput)=\"zIndexInput.emit(zIndex)\" (keydown.enter)=\"reloadAndPlot.emit()\"/>\n <p-button icon=\"pi pi-refresh\" size=\"small\" [text]=\"true\" (onClick)=\"reloadAndPlot.emit()\"\n pTooltip=\"Plot this slice\" tooltipPosition=\"bottom\">\n </p-button>\n </div>\n <span class=\"flex align-items-center mx-1 toolbar-text\">of {{maxIndex}}</span>\n </ng-container>\n </ng-container>\n </ng-container>\n\n <!-- Image actions (special tools) -->\n <ng-container *ngIf=\"tools.specialTools\">\n <span class=\"toolbar-separator\"></span>\n <p-button icon=\"pi pi-download\" size=\"small\" [text]=\"true\"\n pTooltip=\"Download image as PNG\" tooltipPosition=\"bottom\"\n (onClick)=\"downloadImage.emit()\">\n </p-button>\n <p-button *ngIf=\"isHeatmap\" icon=\"pi pi-window-maximize\" size=\"small\" [text]=\"true\"\n pTooltip=\"Fit to view\" tooltipPosition=\"bottom\"\n (onClick)=\"autoscaleImage.emit()\">\n </p-button>\n </ng-container>\n\n <!-- Zoom tools. Plotly rubber-band drag-zoom: on OpenSeadragon the box-zoom\n covers this and a bare drag pans natively, so it's hidden in image view. -->\n <ng-container *ngIf=\"isHeatmap && tools.zoomTools\">\n <span class=\"toolbar-separator\"></span>\n <p-button *ngIf=\"!isImageView\" icon=\"pi pi-search\" size=\"small\"\n [text]=\"activeDragMode !== 'zoom'\"\n pTooltip=\"Zoom drag\" tooltipPosition=\"bottom\"\n (onClick)=\"toggleDragMode.emit('zoom')\">\n </p-button>\n <p-button size=\"small\"\n [text]=\"activeDragMode !== 'zoomToBox'\"\n pTooltip=\"Zoom box (<kbd>b</kbd>)\" [escape]=\"false\" tooltipPosition=\"bottom\"\n (onClick)=\"toggleDragMode.emit('zoomToBox')\">\n <img src=\"assets/plotting/zoom-box.svg\"\n [class]=\"activeDragMode === 'zoomToBox' ? 'toolbar-icon-active' : 'toolbar-icon-dark'\"\n style=\"width: 1rem\"/>\n </p-button>\n <p-button icon=\"pi pi-arrows-alt\" size=\"small\"\n [text]=\"activeDragMode !== 'pan'\"\n pTooltip=\"Pan the image (<kbd>p</kbd>)\" [escape]=\"false\" tooltipPosition=\"bottom\"\n (onClick)=\"toggleDragMode.emit('pan')\">\n </p-button>\n <p-button icon=\"pi pi-search-plus\" size=\"small\" [text]=\"true\"\n pTooltip=\"Zoom in (<kbd>+</kbd>)\" [escape]=\"false\" tooltipPosition=\"bottom\"\n (onClick)=\"zoomIn.emit()\">\n </p-button>\n <p-button icon=\"pi pi-search-minus\" size=\"small\" [text]=\"true\"\n pTooltip=\"Zoom out (<kbd>-</kbd>)\" [escape]=\"false\" tooltipPosition=\"bottom\"\n (onClick)=\"zoomOut.emit()\">\n </p-button>\n </ng-container>\n\n <!-- Surface 3D controls -->\n <ng-container *ngIf=\"!isHeatmap\">\n <span class=\"toolbar-separator\"></span>\n <p-button icon=\"pi pi-search\" size=\"small\"\n [text]=\"activeSurface3dMode !== 'zoom'\"\n pTooltip=\"Zoom\" tooltipPosition=\"bottom\"\n (onClick)=\"toggleSurface3dMode.emit('zoom')\">\n </p-button>\n <p-button icon=\"pi pi-arrows-alt\" size=\"small\"\n [text]=\"activeSurface3dMode !== 'pan'\"\n pTooltip=\"Pan\" tooltipPosition=\"bottom\"\n (onClick)=\"toggleSurface3dMode.emit('pan')\">\n </p-button>\n <p-button icon=\"pi pi-globe\" size=\"small\"\n [text]=\"activeSurface3dMode !== 'orbit'\"\n pTooltip=\"Orbital rotation\" tooltipPosition=\"bottom\"\n (onClick)=\"toggleSurface3dMode.emit('orbit')\">\n </p-button>\n <p-button icon=\"pi pi-sync\" size=\"small\"\n [text]=\"activeSurface3dMode !== 'turntable'\"\n pTooltip=\"Turntable rotation\" tooltipPosition=\"bottom\"\n (onClick)=\"toggleSurface3dMode.emit('turntable')\">\n </p-button>\n <p-button icon=\"pi pi-home\" size=\"small\" [text]=\"true\"\n pTooltip=\"Reset camera to default\" tooltipPosition=\"bottom\"\n (onClick)=\"resetSurfaceCamera.emit()\">\n </p-button>\n <!-- 3D coordinate-axes / scale gizmo toggle (napari-js WebGPU volume/isosurface only).\n `p-button-icon-only` gives the same square sizing as the neighbouring pi-icon buttons\n (the projected <img> doesn't trigger PrimeNG's automatic icon-only sizing). -->\n <p-button size=\"small\" *ngIf=\"isNapariMode\" styleClass=\"p-button-icon-only\"\n [text]=\"!axesVisible\"\n pTooltip=\"Toggle 3D axes / scale\" tooltipPosition=\"bottom\"\n (onClick)=\"toggleAxes.emit()\">\n <img src=\"assets/plotting/3d-coordinates.svg\"\n [class]=\"axesVisible ? 'toolbar-icon-active' : 'toolbar-icon-dark'\"\n style=\"width: 1rem\"/>\n </p-button>\n <!-- Surface wireframe toggle (napari-js WebGPU surface only). -->\n <p-button icon=\"pi pi-share-alt\" size=\"small\" *ngIf=\"isNapariSurfaceMode\"\n [text]=\"!wireframeActive\"\n pTooltip=\"Toggle wireframe\" tooltipPosition=\"bottom\"\n (onClick)=\"toggleWireframe.emit()\">\n </p-button>\n <!-- Resolution (decimate factor) for the napari 3D types \u2014 reloads at coarser/finer sampling. -->\n <ng-container *ngIf=\"isNapari3dMode\">\n <span class=\"flex align-items-center mx-1 toolbar-text\">Res</span>\n <p-dropdown [options]=\"decimateOptions\" optionLabel=\"label\" optionValue=\"value\"\n [ngModel]=\"resolutionScale\" appendTo=\"body\"\n pTooltip=\"Resolution (decimate factor)\" tooltipPosition=\"bottom\"\n [style]=\"{'min-width': '6rem'}\"\n (onChange)=\"selectResolution.emit($event.value)\">\n </p-dropdown>\n </ng-container>\n </ng-container>\n\n <ng-container *ngIf=\"isHeatmap && tools.regionTools\">\n <span class=\"toolbar-separator\"></span>\n <!-- Region Editor: opens the editor as a floating dialog (formerly the\n right-panel Regions tab). Sits with the region drawing tools. -->\n <p-button size=\"small\" [text]=\"true\"\n pTooltip=\"Open the Region Editor\" tooltipPosition=\"bottom\"\n (onClick)=\"openRegionEditor.emit()\">\n <img src=\"assets/plotting/region-editor.svg\" class=\"toolbar-icon-dark\" style=\"width: 1rem\"/>\n </p-button>\n <!-- Region drawing tools -->\n <p-button icon=\"pi pi-arrow-up-right\" size=\"small\"\n [text]=\"activeDragMode !== 'select'\"\n pTooltip=\"Selection mode (<kbd>s</kbd>)\"\n [escape]=\"false\" tooltipPosition=\"bottom\"\n (onClick)=\"toggleDragMode.emit('select')\">\n </p-button>\n <p-button icon=\"pi pi-stop\" size=\"small\"\n [text]=\"activeDragMode !== 'drawrect'\"\n pTooltip=\"Draw a rectangular region (<kbd>r</kbd>)\" [escape]=\"false\"\n tooltipPosition=\"bottom\"\n (onClick)=\"toggleDragMode.emit('drawrect')\">\n </p-button>\n <p-button size=\"small\"\n [text]=\"activeDragMode !== 'drawopenpath'\"\n pTooltip=\"Draw a polyline (<kbd>l</kbd>)\" [escape]=\"false\"\n tooltipPosition=\"bottom\"\n (onClick)=\"toggleDragMode.emit('drawopenpath')\">\n <img src=\"assets/plotting/polyline.svg\"\n [class]=\"activeDragMode === 'drawopenpath' ? 'toolbar-icon-active' : 'toolbar-icon-dark'\"\n style=\"width: 1rem\"/>\n </p-button>\n <p-button icon=\"pi pi-pencil\" size=\"small\"\n [text]=\"activeDragMode !== 'drawclosedpath'\"\n pTooltip=\"Draw a freeform region (<kbd>f</kbd>)\" [escape]=\"false\"\n tooltipPosition=\"bottom\"\n (onClick)=\"toggleDragMode.emit('drawclosedpath')\">\n </p-button>\n <!-- Brush: paint a region by dragging a disc of the given size; the Size\n slider appears while it's active. Hold Shift to erase (subtract). -->\n <p-button size=\"small\"\n [text]=\"activeDragMode !== 'brush'\"\n pTooltip=\"Brush: click and drag to paint a region.\n <br>Hold <kbd>Shift</kbd> while painting to erase from a region.\"\n [escape]=\"false\" tooltipPosition=\"bottom\"\n (onClick)=\"toggleDragMode.emit('brush')\">\n <img src=\"assets/plotting/paintbrush.svg\"\n [class]=\"activeDragMode === 'brush' ? 'toolbar-icon-active' : 'toolbar-icon-dark'\"\n style=\"width: 1rem\"/>\n </p-button>\n <ng-container *ngIf=\"activeDragMode === 'brush'\">\n <span class=\"flex align-items-center mx-1 toolbar-text\">Size</span>\n <p-slider [(ngModel)]=\"brushSize\"\n [min]=\"brushSizeMin\" [max]=\"brushSizeMax\" [step]=\"brushSizeStep\"\n (onChange)=\"brushSizeChange.emit($event.value)\"\n pTooltip=\"Brush diameter in image pixels.\"\n [escape]=\"false\" tooltipPosition=\"bottom\"\n styleClass=\"wand-sensitivity-slider\"></p-slider>\n <span class=\"flex align-items-center mx-1 toolbar-text wand-sensitivity-value\">\n {{ brushSize }}</span>\n </ng-container>\n\n <!-- Vertex editing \u2014 backends with a vertex-editing overlay (OSD + napari-js image). -->\n <ng-container *ngIf=\"supportsRegionVertexTools\">\n <p-button size=\"small\"\n [text]=\"activeDragMode !== 'drawpolygon'\"\n pTooltip=\"Polygon: click to place each vertex; click the first point to close\"\n [escape]=\"false\" tooltipPosition=\"bottom\"\n (onClick)=\"toggleDragMode.emit('drawpolygon')\">\n <img src=\"assets/plotting/polygon-vertices.svg\"\n [class]=\"activeDragMode === 'drawpolygon' ? 'toolbar-icon-active' : 'toolbar-icon-dark'\"\n style=\"width: 1rem\"/>\n </p-button>\n <p-button size=\"small\"\n [text]=\"activeDragMode !== 'addpoint'\"\n pTooltip=\"Add a vertex: click an edge of the selected polygon\"\n [escape]=\"false\" tooltipPosition=\"bottom\"\n (onClick)=\"toggleDragMode.emit('addpoint')\">\n <img src=\"assets/plotting/vertex-add.svg\"\n [class]=\"activeDragMode === 'addpoint' ? 'toolbar-icon-active' : 'toolbar-icon-dark'\"\n style=\"width: 1rem\"/>\n </p-button>\n <p-button size=\"small\"\n [text]=\"activeDragMode !== 'deletepoint'\"\n pTooltip=\"Delete a vertex: click a vertex of the selected polygon\"\n [escape]=\"false\" tooltipPosition=\"bottom\"\n (onClick)=\"toggleDragMode.emit('deletepoint')\">\n <img src=\"assets/plotting/vertex-delete.svg\"\n [class]=\"activeDragMode === 'deletepoint' ? 'toolbar-icon-active' : 'toolbar-icon-dark'\"\n style=\"width: 1rem\"/>\n </p-button>\n </ng-container>\n\n <p-button size=\"small\"\n [text]=\"activeDragMode !== 'wand'\"\n pTooltip=\"Wand: click and drag to grow a region by similar pixel values (<kbd>w</kbd>).\n <br>Hold <kbd>Shift</kbd> while clicking or dragging on a region to erase pixels from it.\"\n [escape]=\"false\" tooltipPosition=\"bottom\"\n (onClick)=\"toggleDragMode.emit('wand')\">\n <img src=\"assets/plotting/wand.svg\"\n [class]=\"activeDragMode === 'wand' ? 'toolbar-icon-active' : 'toolbar-icon-dark'\"\n style=\"width: 1rem\"/>\n </p-button>\n <ng-container *ngIf=\"activeDragMode === 'wand'\">\n <span class=\"flex align-items-center mx-1 toolbar-text\">Sens.</span>\n <p-slider [(ngModel)]=\"wandSensitivity\"\n [min]=\"wandSensitivityMin\" [max]=\"wandSensitivityMax\" [step]=\"wandSensitivityStep\"\n (onChange)=\"wandSensitivityChange.emit($event.value)\"\n pTooltip=\"Higher = stricter (smaller selection); lower = looser.\n Takes effect from the next click/drag tick.\"\n [escape]=\"false\" tooltipPosition=\"bottom\"\n styleClass=\"wand-sensitivity-slider\"></p-slider>\n <span class=\"flex align-items-center mx-1 toolbar-text wand-sensitivity-value\">\n {{ wandSensitivity.toFixed(1) }}</span>\n </ng-container>\n <p-button icon=\"pi pi-eraser\" size=\"small\"\n [text]=\"activeDragMode !== 'eraseVertex'\"\n pTooltip=\"Vertex eraser (<kbd>e</kbd>): click or drag to remove polygon vertices inside\n the cursor circle from any region.\"\n [escape]=\"false\" tooltipPosition=\"bottom\"\n (onClick)=\"toggleDragMode.emit('eraseVertex')\">\n </p-button>\n <ng-container *ngIf=\"activeDragMode === 'eraseVertex'\">\n <span class=\"flex align-items-center mx-1 toolbar-text\">Radius</span>\n <p-slider [(ngModel)]=\"vertexEraserRadius\"\n [min]=\"vertexEraserRadiusMin\" [max]=\"vertexEraserRadiusMax\" [step]=\"vertexEraserRadiusStep\"\n (onChange)=\"vertexEraserRadiusChange.emit($event.value)\"\n pTooltip=\"Eraser radius in image pixels.\n Vertices within this distance of the cursor are removed.\"\n [escape]=\"false\" tooltipPosition=\"bottom\"\n styleClass=\"wand-sensitivity-slider\"></p-slider>\n <span class=\"flex align-items-center mx-1 toolbar-text wand-sensitivity-value\">\n {{ vertexEraserRadius }}</span>\n </ng-container>\n <p-button size=\"small\" [text]=\"true\"\n [disabled]=\"!canUndo\"\n pTooltip=\"Undo the last region action (<kbd>Ctrl</kbd>+<kbd>Z</kbd>).\n Up to 10 actions can be undone.\"\n [escape]=\"false\" tooltipPosition=\"bottom\"\n (onClick)=\"undoRegion.emit()\">\n <img src=\"assets/plotting/undo.svg\" class=\"toolbar-icon-dark\" style=\"width: 1rem\"/>\n </p-button>\n <p-button size=\"small\" [text]=\"true\"\n [disabled]=\"!canRedo\"\n pTooltip=\"Redo the last undone region action\n (<kbd>Ctrl</kbd>+<kbd>Shift</kbd>+<kbd>Z</kbd> / <kbd>Ctrl</kbd>+<kbd>Y</kbd>).\"\n [escape]=\"false\" tooltipPosition=\"bottom\"\n (onClick)=\"redoRegion.emit()\">\n <img src=\"assets/plotting/redo.svg\" class=\"toolbar-icon-dark\" style=\"width: 1rem\"/>\n </p-button>\n <p-button icon=\"pi pi-trash\" size=\"small\" [text]=\"true\"\n pTooltip=\"Delete selected region (<kbd>Del</kbd>)\"\n [escape]=\"false\" tooltipPosition=\"bottom\"\n (onClick)=\"deleteRegion.emit()\">\n </p-button>\n <!-- SAM (jit-ui#90): box-prompt segment, interactive point prompts, and a\n model picker. -->\n <span class=\"toolbar-separator\"></span>\n <!-- SAM controls grouped: box-prompt Segment, then the point-prompt tool,\n then the model-picker dropdown (the chevron) which governs BOTH tools.\n Falls back to Segment + point with no model picker when none are set. -->\n <span *ngIf=\"samModels.length; else segmentPlain\" class=\"p-buttonset segment-split\">\n <p-button size=\"small\" [text]=\"true\"\n pTooltip=\"Segment the drawn rectangles with SAM (box prompt)\"\n tooltipPosition=\"bottom\"\n (onClick)=\"segmentRegions.emit()\">\n <img src=\"assets/plotting/picture-segmentation.svg\"\n class=\"toolbar-icon-dark\" style=\"width: 1rem\"/>\n </p-button>\n <ng-container [ngTemplateOutlet]=\"samPointBtn\"></ng-container>\n <p-button icon=\"pi pi-chevron-down\" size=\"small\" [text]=\"true\"\n pTooltip=\"Pick the SAM model (used by both segment tools)\"\n tooltipPosition=\"bottom\"\n (onClick)=\"samModelMenu.toggle($event)\">\n </p-button>\n </span>\n <ng-template #segmentPlain>\n <span class=\"p-buttonset segment-split\">\n <p-button size=\"small\" [text]=\"true\"\n pTooltip=\"Segment the drawn rectangles with SAM (box prompt)\"\n tooltipPosition=\"bottom\"\n (onClick)=\"segmentRegions.emit()\">\n <img src=\"assets/plotting/picture-segmentation.svg\"\n class=\"toolbar-icon-dark\" style=\"width: 1rem\"/>\n </p-button>\n <ng-container [ngTemplateOutlet]=\"samPointBtn\"></ng-container>\n </span>\n </ng-template>\n <!-- Point-prompt tool \u2014 shared between the models / no-models layouts so it\n always sits just left of the model dropdown. -->\n <ng-template #samPointBtn>\n <p-button size=\"small\"\n [text]=\"activeDragMode !== 'samPoint'\"\n pTooltip=\"SAM point prompts: click a fiber to segment it as a new region;\n Shift/Alt-click to refine (exclude) the current one.<br>Esc undoes it.\"\n [escape]=\"false\" tooltipPosition=\"bottom\"\n (onClick)=\"toggleDragMode.emit('samPoint')\">\n <img src=\"assets/plotting/click.svg\"\n [class]=\"activeDragMode === 'samPoint' ? 'toolbar-icon-active' : 'toolbar-icon-dark'\"\n style=\"width: 1rem\"/>\n </p-button>\n </ng-template>\n <p-menu #samModelMenu [model]=\"samMenuItems\" [popup]=\"true\" appendTo=\"body\"></p-menu>\n <!-- Help: explains the ViT-T vs ViT-B trade-off behind the model picker. -->\n <p-button *ngIf=\"samModels.length\" icon=\"pi pi-info-circle\" size=\"small\" [text]=\"true\"\n pTooltip=\"About the SAM models\" tooltipPosition=\"bottom\"\n (onClick)=\"samModelInfo.toggle($event)\">\n </p-button>\n <p-overlayPanel #samModelInfo appendTo=\"body\" styleClass=\"sam-model-info\">\n <div style=\"max-width: 24rem; line-height: 1.4\">\n <div class=\"font-bold mb-1\">SAM models</div>\n <p class=\"mt-0 mb-2\">\n All are promptable Segment Anything models finetuned for biomedical images. They differ\n in the <i>image encoder</i> (the heavy once-per-image step) and the training domain \u2014\n box and point prompting behave the same.\n </p>\n <div class=\"mb-2\">\n <div class=\"font-bold\">micro-sam ViT-T <span class=\"font-normal\">(default)</span></div>\n <div>Light-microscopy finetuned. TinyViT encoder, ~14&nbsp;MB, very fast, runs on WASM.\n Good mask quality \u2014 best for quick interactive work and many/large objects.</div>\n </div>\n <div class=\"mb-2\">\n <div class=\"font-bold\">micro-sam ViT-B</div>\n <div>Light-microscopy finetuned. ViT-Base encoder, ~172&nbsp;MB, slower, GPU-accelerated\n (WebGPU). Highest accuracy \u2014 when ViT-T misses subtle or overlapping boundaries.</div>\n </div>\n <div class=\"mb-2\">\n <div class=\"font-bold\">patho-sam ViT-B</div>\n <div>Histopathology finetuned (H&amp;E / stained tissue). ViT-Base encoder, GPU-accelerated.\n Use it for pathology slides \u2014 it segments tissue/nuclei better than the microscopy models.</div>\n </div>\n <div class=\"mb-2\">\n <div class=\"font-bold\">patho-sam ViT-B (int8)</div>\n <div>Same model, int8-quantized \u2014 ~100&nbsp;MB vs ~180&nbsp;MB (smaller download). Runs on\n WASM (slower encode than the fp16 version); masks are near-identical. Pick it on slow\n connections.</div>\n </div>\n <div class=\"text-sm\" style=\"opacity: 0.75\">\n The choice applies to both the box and point tools. Each tool loads its own encoder,\n so the first click after switching re-encodes once.\n </div>\n </div>\n </p-overlayPanel>\n <p-button size=\"small\" [text]=\"true\"\n pTooltip=\"Auto-segment cells inside each drawn rectangle (cellpose-SAM, client-side)\"\n tooltipPosition=\"bottom\"\n (onClick)=\"segmentCellpose.emit()\">\n <img src=\"assets/plotting/cells.svg\" class=\"toolbar-icon-dark\" style=\"width: 1rem\"/>\n </p-button>\n\n <!-- YOLO: detect objects across the current view. Unlike the SAM and\n cellpose tools it takes no prompt \u2014 a detector finds objects in a\n field rather than being pointed at one \u2014 so there is nothing to draw\n first. Split button: run, pick checkpoint, open parameters. -->\n <span *ngIf=\"yoloModels.length\" class=\"p-buttonset segment-split\">\n <p-button icon=\"pi pi-microchip-ai\" size=\"small\" [text]=\"true\"\n pTooltip=\"Detect objects in the current view with YOLO (client-side)\"\n tooltipPosition=\"bottom\"\n (onClick)=\"detectYolo.emit()\">\n </p-button>\n <p-button icon=\"pi pi-chevron-down\" size=\"small\" [text]=\"true\"\n pTooltip=\"Pick the YOLO model\"\n tooltipPosition=\"bottom\"\n (onClick)=\"yoloModelMenu.toggle($event)\">\n </p-button>\n <p-button icon=\"pi pi-sliders-h\" size=\"small\" [text]=\"true\"\n pTooltip=\"YOLO parameters\"\n tooltipPosition=\"bottom\"\n (onClick)=\"openYoloParams.emit()\">\n </p-button>\n </span>\n <p-menu #yoloModelMenu [model]=\"yoloMenuItems\" [popup]=\"true\" appendTo=\"body\"></p-menu>\n\n <!-- Retinal layers: semantic segmentation of the current view. Same\n no-prompt shape as YOLO \u2014 it labels every pixel rather than being\n pointed at an object \u2014 so the same split button: run, pick\n checkpoint, open parameters. -->\n <span *ngIf=\"retinalModels.length\" class=\"p-buttonset segment-split\">\n <p-button icon=\"pi pi-align-left\" size=\"small\" [text]=\"true\"\n pTooltip=\"Segment retinal layers in the current view (client-side)\"\n tooltipPosition=\"bottom\"\n (onClick)=\"segmentRetinal.emit()\">\n </p-button>\n <p-button icon=\"pi pi-chevron-down\" size=\"small\" [text]=\"true\"\n pTooltip=\"Pick the retinal layer model\"\n tooltipPosition=\"bottom\"\n (onClick)=\"retinalModelMenu.toggle($event)\">\n </p-button>\n <p-button icon=\"pi pi-sliders-h\" size=\"small\" [text]=\"true\"\n pTooltip=\"Retinal layer parameters\"\n tooltipPosition=\"bottom\"\n (onClick)=\"openRetinalParams.emit()\">\n </p-button>\n </span>\n <p-menu #retinalModelMenu [model]=\"retinalMenuItems\" [popup]=\"true\" appendTo=\"body\"></p-menu>\n </ng-container>\n\n <ng-container *ngIf=\"tools.help\">\n <span class=\"toolbar-separator\"></span>\n\n <!-- Help -->\n <p-button icon=\"pi pi-question-circle\" size=\"small\" [text]=\"true\"\n pTooltip=\"Help\" tooltipPosition=\"bottom\"\n (onClick)=\"showHelp()\">\n </p-button>\n </ng-container>\n\n </div>\n</p-toolbar>\n<p-dialog header=\"Plotting help\" [(visible)]=\"displayHelpDialog\" [modal]=\"true\" [style]=\"{width: '50vw'}\"\n [maximizable]=\"true\" [draggable]=\"false\" [resizable]=\"false\">\n <p>Click a file in the left-hand tree to display it. By default it opens in the\n <b>Image</b> view \u2014 a natively tiled, deeply zoomable raster (OpenSeadragon) with a\n navigator minimap and a physical-units scale bar. <b>Click the image to zoom</b> toward\n the clicked point; scroll to zoom and drag to pan. When a stack is open, a slice slider\n (Image view) or image-mode toggle (other views) appears. Regions can also be edited in the\n <b>Region Editor</b>, opened with the\n <img src=\"assets/plotting/region-editor.svg\" width=\"14\" height=\"14\" alt=\"Region Editor\"/>\n toolbar button as a floating dialog. Drag the <i class=\"pi pi-bars\"></i> handle to detach\n this toolbar into a floating window.\n </p>\n\n <h3>Plot-type dropdown</h3>\n <p>\n The <b>plot-type dropdown</b> switches how the file is visualized:\n <b>Image</b>, <b>Contour</b>, <b>Scatter (regions)</b>, <b>Surface (3D)</b>,\n <b>Scatter 3D</b>, and <b>Isosurface (3D)</b>. Scalar and 3D types require a grayscale\n image (the 3D volume types also require a z-stack). The non-image views render with\n Plotly and support \"real zooming\" (a downscaled overview that re-fetches higher\n resolution as you zoom in).\n </p>\n <ul>\n <li>\"<b>Isosurface band</b>\" slider (Isosurface): sets the intensity band the surfaces are\n drawn for, mapped onto the volume's real intensity range.</li>\n <li>\"<b>3D camera</b>\" (Surface / Scatter 3D / Isosurface): zoom <i class=\"pi pi-search\"></i>,\n pan <i class=\"pi pi-arrows-alt\"></i>, orbit <i class=\"pi pi-globe\"></i>,\n turntable <i class=\"pi pi-sync\"></i>, and reset camera <i class=\"pi pi-home\"></i>;\n scroll to zoom the 3D scene.</li>\n </ul>\n\n <h3>Image manipulation tools</h3>\n <ul>\n <li>\"<b>Region Editor</b>\" <img src=\"assets/plotting/region-editor.svg\" width=\"15\" height=\"15\">:\n opens the Region Editor as a floating, draggable dialog \u2014 a per-image table of every region\n (class, colour, area) with bulk select/label/colour/delete and GeoJSON import/export. It stays\n in sync with the image, so you can keep it open while drawing.</li>\n <li>\"<b>Intensity profile lines</b>\" <img src=\"assets/plotting/spline-chart.svg\" width=\"15\" height=\"15\">\n (<i>work in progress \u2014 not yet stable</i>): adds a coloured line ROI; a floating inset\n chart plots intensity along each line, updates live as you drag it, and re-samples at the\n current zoom resolution. Each added line gets its own colour; select a line and use the\n trash tool <i class=\"pi pi-trash\"></i> to remove it (and its trace).</li>\n <li>\"<b>Channels &amp; Histogram</b>\" <img src=\"assets/plotting/invert.svg\" width=\"15\" height=\"15\">:\n opens a floating dialog for brightness/contrast, gamma, and per-channel display, plus the\n <b>colormap / LUT</b> controls (grayscale only) \u2014 a <b>reverse</b> toggle and a colormap\n dropdown (default: inverted greys).</li>\n <li>\"<b>Smoothen</b>\" <img src=\"assets/plotting/smooth.svg\" width=\"15\" height=\"15\">\n (Image view only): the image opens showing <b>raw pixels</b> (nearest-neighbour), so\n zooming in renders crisp blocks for pixel inspection. Toggle this on for bilinear\n smoothing; the icon turns white while smoothing is active.</li>\n </ul>\n\n <h3>Download &amp; view tools</h3>\n <ul>\n <li>\"<b>Download</b>\" <i class=\"pi pi-download\"></i>: download the current image as a PNG.</li>\n <li>\"<b>Fit to view</b>\" / autoscale <i class=\"pi pi-window-maximize\"></i>: resets the image\n to its original dimensions. In stack mode it also resets to single-image mode.</li>\n </ul>\n\n <h3>Zoom tools</h3>\n <ul>\n <li>\"<b>Zoom box</b>\" <img src=\"assets/plotting/zoom-box.svg\" width=\"15\" height=\"15\">\n (<kbd>b</kbd>): drag a box to select an area to zoom into.</li>\n <li>\"<b>Pan</b>\" <i class=\"pi pi-arrows-alt\"></i> (<kbd>p</kbd>): drag to pan the image.</li>\n <li>\"<b>Zoom in</b>\" <i class=\"pi pi-search-plus\"></i> (<kbd>+</kbd>): zoom in one step.</li>\n <li>\"<b>Zoom out</b>\" <i class=\"pi pi-search-minus\"></i> (<kbd>-</kbd>): zoom out one step,\n stopping at the original image dimensions.</li>\n </ul>\n\n <h3>Region tools</h3>\n <p>By default no tool is selected (all greyed-out); selecting a tool highlights it. The\n vertex-editing tools (polygon, add/delete vertex, move, B\u00E9zier conversions) are available\n in the <b>Image</b> view only. On a <b>donut</b> (a region with a hole), the inner-ring\n vertices are editable too \u2014 drag, add, and delete them just like the outer ring.</p>\n <ul>\n <li>\"<b>Selection</b>\" <i class=\"pi pi-arrow-up-right\"></i> (<kbd>s</kbd>): a neutral mode\n that turns off whatever drawing tool was active (wand, vertex eraser, rectangle, etc.).\n Use it to step out of editing without picking another tool.</li>\n <li>\"<b>Rectangular region</b>\" <i class=\"pi pi-stop\"></i> (<kbd>r</kbd>): click and drag to\n create a rectangular region.</li>\n <li>\"<b>Polyline</b>\" <img src=\"assets/plotting/polyline.svg\" width=\"14\" height=\"14\">\n (<kbd>l</kbd>): draw an open (non-closed) polyline by clicking and dragging. Polylines can\n be edited and exported to GeoJSON as a <code>LineString</code> feature.</li>\n <li>\"<b>Freeform region</b>\" <i class=\"pi pi-pencil\"></i> (<kbd>f</kbd>): click and drag to\n draw a closed freeform polygon.</li>\n <li>\"<b>Brush</b>\" <img src=\"assets/plotting/paintbrush.svg\" width=\"15\" height=\"15\">: click\n and drag to paint a region with a circular brush \u2014 the painted stroke is the region.\n A <b>Size</b> slider (brush diameter, in image pixels) appears in the toolbar while the\n brush is active. Hold <kbd>Shift</kbd> while painting to <b>erase</b> (subtract) from a\n region instead. Painting over an existing region extends it; brushing across two regions\n merges them. Painting a closed ring (or shift-erasing an interior pocket) leaves the\n enclosed area empty \u2014 a <b>donut</b> (Image view).</li>\n <li>\"<b>Polygon</b>\" <img src=\"assets/plotting/polygon-vertices.svg\" width=\"15\" height=\"15\">:\n click to place each vertex; click the first point to close the polygon.</li>\n <li>\"<b>Add vertex</b>\" <img src=\"assets/plotting/vertex-add.svg\" width=\"15\" height=\"15\">:\n click an edge of the selected polygon to insert a new vertex.</li>\n <li>\"<b>Delete vertex</b>\" <img src=\"assets/plotting/vertex-delete.svg\" width=\"15\" height=\"15\">:\n click a vertex of the selected polygon to remove it.</li>\n <li>\"<b>Move region</b>\" <img src=\"assets/plotting/region-move.svg\" width=\"15\" height=\"15\">:\n drag to move the selected region.</li>\n <li>\"<b>To B\u00E9zier</b>\" <img src=\"assets/plotting/to-bezier.svg\" width=\"15\" height=\"15\"> /\n \"<b>To polygon</b>\" <img src=\"assets/plotting/to-polygon.svg\" width=\"15\" height=\"15\">:\n convert the selected region to a smooth B\u00E9zier curve, or back to a straight-edged polygon.</li>\n <li>\"<b>Wand</b>\" <img src=\"assets/plotting/wand.svg\" width=\"15\" height=\"15\"> (<kbd>w</kbd>):\n click to grow a region from pixels with similar values around the click point. Hold\n <kbd>Ctrl</kbd>/<kbd>Cmd</kbd> for an exact-match flood fill; drag to keep extending the\n selection while moving; hold <kbd>Shift</kbd> while clicking or dragging on an existing\n region to <b>erase</b> pixels from it. Use the <b>Sens.</b> slider that appears to make the\n wand stricter (higher = smaller selection) or looser. Subsequent clicks keep extending the\n same region; clicking inside an existing region adopts it, and growing into another region\n merges them.</li>\n <li>\"<b>Vertex eraser</b>\" <i class=\"pi pi-eraser\"></i> (<kbd>e</kbd>): click or click-and-drag\n to remove polygon vertices that fall inside the cursor circle from any region, including a\n region's inner rings (donut holes). Use the <b>Radius</b> slider to set the eraser size in\n image pixels. Polygons reduced below 3 vertices (or polylines below 2) are deleted entirely;\n a hole reduced below 3 vertices is removed.</li>\n <li>\"<b>Undo</b>\" <img src=\"assets/plotting/undo.svg\" width=\"15\" height=\"15\">\n (<kbd>Ctrl</kbd>/<kbd>Cmd</kbd>+<kbd>Z</kbd>): reverts the\n last region action (draw, delete, edit, wand/brush stroke, segmentation, \u2026). Up to the last\n <b>10</b> actions can be undone; the button greys out when there's nothing left to undo.</li>\n <li>\"<b>Redo</b>\" <img src=\"assets/plotting/redo.svg\" width=\"15\" height=\"15\">\n (<kbd>Ctrl</kbd>/<kbd>Cmd</kbd>+<kbd>Shift</kbd>+<kbd>Z</kbd> or <kbd>Ctrl</kbd>/<kbd>Cmd</kbd>+<kbd>Y</kbd>):\n re-applies the most recently undone action. Performing a new region action clears what can be redone.</li>\n <li>\"<b>Delete region</b>\" <i class=\"pi pi-trash\"></i> (<kbd>Del</kbd>): deletes the selected\n region. This can also be done from the Region Editor.</li>\n </ul>\n\n <h3>Region actions (right-click the image)</h3>\n <p>Right-click a region \u2014 or the current multi-selection \u2014 to open the\n \"<b>Selected region(s)</b>\" group at the top of the context menu. These act immediately on\n the selection (they're not drawing modes) and are fully undo/redo-able:</p>\n <ul>\n <li>\"<b>Select all regions</b>\" <i class=\"pi pi-check-square\"></i>: select every region on\n the image (also in the Region Editor toolbar).</li>\n <li>\"<b>Merge / group</b>\": combine 2+ selected regions into one. If the pieces don't touch,\n the result is a single multi-part region (sparse \"bits\").</li>\n <li>\"<b>Ungroup</b>\": split a multi-part region back into one region per piece.</li>\n <li>\"<b>Inverse</b>\": replace the selection with everything <i>outside</i> it \u2014 the image\n rectangle with the selected regions punched out as holes. Works at any image size.</li>\n <li>\"<b>Simplify</b>\": reduce a region's vertex count (Douglas\u2013Peucker) by an altitude\n threshold in pixels \u2014 pick <b>Light</b> (1&nbsp;px), <b>Medium</b> (3&nbsp;px),\n <b>Strong</b> (8&nbsp;px), or <b>Custom\u2026</b> for a slider. Applies to the exterior and any\n holes (and every part of a multi-part region).</li>\n <li>The B\u00E9zier conversions and Delete also live in this group (the right-click equivalents of\n the toolbar / vertex tools above).</li>\n </ul>\n\n <h3>Segmentation tools (SAM &amp; cellpose)</h3>\n <p>AI-assisted segmentation that runs entirely in the browser. The box and point\n tools use a promptable <b>micro-sam</b> model; the cellpose tool auto-detects cells.\n Generated regions inherit the color of the rectangle they came from. The first run\n downloads the model (a progress toast is shown); it's then cached.</p>\n <ul>\n <li>\"<b>Segment</b>\" <img src=\"assets/plotting/picture-segmentation.svg\" width=\"15\" height=\"15\">\n (box prompt): draw one or more <b>Rectangular regions</b> around the objects of interest,\n then click Segment. Each rectangle is replaced by the SAM mask of the object inside it.</li>\n <li>\"<b>Point prompts</b>\" <img src=\"assets/plotting/click.svg\" width=\"15\" height=\"15\">: click directly on an object to\n segment it as a <b>new</b> region. Each click starts a fresh object \u2014 clicking another\n object won't grow the previous one. <kbd>Shift</kbd>/<kbd>Alt</kbd>-click adds an\n <i>exclude</i> point that refines the current object; <kbd>Esc</kbd> undoes it,\n <kbd>Enter</kbd> commits it.</li>\n <li>\"<b>Model picker</b>\" <i class=\"pi pi-chevron-down\"></i>: the dropdown attached to the\n Segment button chooses the SAM model \u2014 <b>micro-sam ViT-T</b> (default; small, fast) or\n <b>micro-sam ViT-B</b> (larger, highest accuracy) for light microscopy, or\n <b>patho-sam ViT-B</b> for histopathology (H&amp;E / stained tissue). The choice applies to\n <i>both</i> the box and point tools. The <i class=\"pi pi-info-circle\"></i> info button next\n to it explains the trade-off.</li>\n <li>\"<b>Cellpose</b>\" <img src=\"assets/plotting/cells.svg\" width=\"15\" height=\"15\">: draw rectangles, then click Cellpose to\n automatically segment every cell inside each rectangle (client-side cellpose-SAM) \u2014 one\n region per detected cell. No clicking on individual cells needed.</li>\n </ul>\n\n <h3>Help</h3>\n <p>\"<b>Help</b>\" <i class=\"pi pi-question-circle\"></i>: opens this dialog.</p>\n\n <h3>Keyboard shortcuts</h3>\n <table class=\"shortcuts-table\">\n <tr><td><kbd>+</kbd> / <kbd>=</kbd></td><td>Zoom in</td></tr>\n <tr><td><kbd>-</kbd></td><td>Zoom out</td></tr>\n <tr><td><kbd>b</kbd></td><td>Toggle zoom box mode</td></tr>\n <tr><td><kbd>p</kbd></td><td>Toggle pan mode</td></tr>\n <tr><td><kbd>f</kbd></td><td>Toggle freeform region drawing</td></tr>\n <tr><td><kbd>l</kbd></td><td>Toggle polyline drawing</td></tr>\n <tr><td><kbd>r</kbd></td><td>Toggle rectangular region drawing</td></tr>\n <tr><td><kbd>w</kbd></td><td>Toggle wand tool</td></tr>\n <tr><td>Wand + <kbd>Shift</kbd></td><td>Erase pixels from the active region</td></tr>\n <tr><td>Wand + <kbd>Ctrl</kbd>/<kbd>Cmd</kbd></td><td>Exact-match flood fill (no smoothing)</td></tr>\n <tr><td><kbd>e</kbd></td><td>Toggle vertex eraser</td></tr>\n <tr><td><kbd>s</kbd></td><td>Selection mode (deactivates drawing tools)</td></tr>\n <tr><td>Point prompt + <kbd>Shift</kbd>/<kbd>Alt</kbd></td><td>Add an exclude point (refine the current object)</td></tr>\n <tr><td><kbd>Enter</kbd></td><td>Commit the current SAM point object</td></tr>\n <tr><td><kbd>Esc</kbd></td><td>Undo the current SAM point object</td></tr>\n <tr><td><kbd>Del</kbd> / <kbd>\u232B</kbd></td><td>Delete selected region</td></tr>\n <tr><td><kbd>Ctrl</kbd>/<kbd>Cmd</kbd>+<kbd>Z</kbd></td><td>Undo the last region action (up to 10)</td></tr>\n <tr><td><kbd>Ctrl</kbd>/<kbd>Cmd</kbd>+<kbd>Shift</kbd>+<kbd>Z</kbd> / <kbd>Ctrl</kbd>+<kbd>Y</kbd></td><td>Redo the last undone region action</td></tr>\n </table>\n\n <ng-template pTemplate=\"footer\">\n <p-button icon=\"pi pi-check\" (click)=\"displayHelpDialog=false\" label=\"Ok\" styleClass=\"p-button-text\">\n </p-button>\n </ng-template>\n</p-dialog>\n", styles: [":host{display:block}:host ::ng-deep .p-toolbar{padding:.25rem .5rem}.toolbar-text{color:var(--primary-color);font-size:.875rem}::ng-deep .toolbar-input .p-inputtext{color:var(--primary-color);border-color:var(--primary-color)}.toolbar-separator{border-left:1px solid var(--surface-border);height:1.5rem;margin:0 .25rem;align-self:center}.toolbar-icon-dark{filter:brightness(0) invert(47%) sepia(98%) saturate(567%) hue-rotate(181deg) brightness(103%) contrast(98%)}.toolbar-icon-active{filter:brightness(0) invert(1)}::ng-deep .plotmode-item{display:inline-flex;align-items:center;gap:.5rem;color:#000}::ng-deep .plotmode-item i{font-size:1rem;color:var(--primary-color)}::ng-deep .plotmode-item .plotmode-icon-img{width:1.1rem;height:1.1rem;object-fit:contain;filter:brightness(0) invert(47%) sepia(98%) saturate(567%) hue-rotate(181deg) brightness(103%) contrast(98%)}::ng-deep .wand-sensitivity-slider{width:8rem;align-self:center;margin:0 .25rem}.wand-sensitivity-value{min-width:2rem;text-align:right}span.colormap{width:50px;height:auto;display:inline-block}img.colormap{width:50px;height:auto}.colormap{background:no-repeat;background-size:100%;vertical-align:middle}kbd{background:#e8e8e8;border:1px solid #bbb;border-radius:3px;padding:1px 5px;font-family:monospace;font-size:.85em}.shortcuts-table{border-collapse:collapse}.shortcuts-table td{padding:.25rem .75rem .25rem 0;vertical-align:middle}.shortcuts-table td:first-child{white-space:nowrap}\n"], dependencies: [{ kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: i3.Toolbar, selector: "p-toolbar", inputs: ["style", "styleClass", "ariaLabelledBy"] }, { kind: "directive", type: i4.PrimeTemplate, selector: "[pTemplate]", inputs: ["type", "pTemplate"] }, { kind: "component", type: i5.Button, selector: "p-button", inputs: ["type", "iconPos", "icon", "badge", "label", "disabled", "loading", "loadingIcon", "raised", "rounded", "text", "plain", "severity", "outlined", "link", "tabindex", "size", "style", "styleClass", "badgeClass", "ariaLabel", "autofocus"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: i6.Menu, selector: "p-menu", inputs: ["model", "popup", "style", "styleClass", "appendTo", "autoZIndex", "baseZIndex", "showTransitionOptions", "hideTransitionOptions", "ariaLabel", "ariaLabelledBy", "id", "tabindex"], outputs: ["onShow", "onHide", "onBlur", "onFocus"] }, { kind: "directive", type: i7.Tooltip, selector: "[pTooltip]", inputs: ["tooltipPosition", "tooltipEvent", "appendTo", "positionStyle", "tooltipStyleClass", "tooltipZIndex", "escape", "showDelay", "hideDelay", "life", "positionTop", "positionLeft", "autoHide", "fitContent", "hideOnEscape", "pTooltip", "tooltipDisabled", "tooltipOptions"] }, { kind: "component", type: i8.Dropdown, selector: "p-dropdown", inputs: ["id", "scrollHeight", "filter", "name", "style", "panelStyle", "styleClass", "panelStyleClass", "readonly", "required", "editable", "appendTo", "tabindex", "placeholder", "loadingIcon", "filterPlaceholder", "filterLocale", "variant", "inputId", "dataKey", "filterBy", "filterFields", "autofocus", "resetFilterOnHide", "checkmark", "dropdownIcon", "loading", "optionLabel", "optionValue", "optionDisabled", "optionGroupLabel", "optionGroupChildren", "autoDisplayFirst", "group", "showClear", "emptyFilterMessage", "emptyMessage", "lazy", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "overlayOptions", "ariaFilterLabel", "ariaLabel", "ariaLabelledBy", "filterMatchMode", "maxlength", "tooltip", "tooltipPosition", "tooltipPositionStyle", "tooltipStyleClass", "focusOnHover", "selectOnFocus", "autoOptionFocus", "autofocusFilter", "autoShowPanelOnPrintableCharacterKeyDown", "disabled", "itemSize", "autoZIndex", "baseZIndex", "showTransitionOptions", "hideTransitionOptions", "filterValue", "options"], outputs: ["onChange", "onFilter", "onFocus", "onBlur", "onClick", "onShow", "onHide", "onClear", "onLazyLoad"] }, { kind: "component", type: i9.Slider, selector: "p-slider", inputs: ["animate", "disabled", "min", "max", "orientation", "step", "range", "style", "styleClass", "ariaLabel", "ariaLabelledBy", "tabindex", "autofocus"], outputs: ["onChange", "onSlideEnd"] }, { kind: "component", type: i10.InputNumber, selector: "p-inputNumber", inputs: ["showButtons", "format", "buttonLayout", "inputId", "styleClass", "style", "placeholder", "size", "maxlength", "tabindex", "title", "ariaLabelledBy", "ariaLabel", "ariaRequired", "name", "required", "autocomplete", "min", "max", "incrementButtonClass", "decrementButtonClass", "incrementButtonIcon", "decrementButtonIcon", "readonly", "step", "allowEmpty", "locale", "localeMatcher", "mode", "currency", "currencyDisplay", "useGrouping", "variant", "minFractionDigits", "maxFractionDigits", "prefix", "suffix", "inputStyle", "inputStyleClass", "showClear", "autofocus", "disabled"], outputs: ["onInput", "onFocus", "onBlur", "onKeyDown", "onClear"] }, { kind: "component", type: i11.Dialog, selector: "p-dialog", inputs: ["header", "draggable", "resizable", "positionLeft", "positionTop", "contentStyle", "contentStyleClass", "modal", "closeOnEscape", "dismissableMask", "rtl", "closable", "responsive", "appendTo", "breakpoints", "styleClass", "maskStyleClass", "maskStyle", "showHeader", "breakpoint", "blockScroll", "autoZIndex", "baseZIndex", "minX", "minY", "focusOnShow", "maximizable", "keepInViewport", "focusTrap", "transitionOptions", "closeIcon", "closeAriaLabel", "closeTabindex", "minimizeIcon", "maximizeIcon", "visible", "style", "position"], outputs: ["onShow", "onHide", "visibleChange", "onResizeInit", "onResizeEnd", "onDragEnd", "onMaximize"] }, { kind: "directive", type: i12.Ripple, selector: "[pRipple]" }, { kind: "component", type: i13.OverlayPanel, selector: "p-overlayPanel", inputs: ["ariaLabel", "ariaLabelledBy", "dismissable", "showCloseIcon", "style", "styleClass", "appendTo", "autoZIndex", "ariaCloseLabel", "baseZIndex", "focusOnShow", "showTransitionOptions", "hideTransitionOptions"], outputs: ["onShow", "onHide"] }] }); }
220
238
  }
221
239
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.9", ngImport: i0, type: ToolbarComponent, decorators: [{
222
240
  type: Component,
223
- args: [{ selector: 'plotting-toolbar', template: "<p-toolbar>\n <div class=\"p-toolbar-group-start flex-wrap align-items-center gap-1\">\n\n <!-- Host-provided custom toolbar buttons, shown first (e.g. app-specific\n actions the visualization view itself doesn't own). -->\n <ng-content></ng-content>\n\n <!-- Plot type: available for any image (grayscale or RGB). -->\n <ng-container *ngIf=\"imageInfo && tools.specialTools\">\n <span class=\"toolbar-separator\"></span>\n <p-dropdown [options]=\"plotTypeOptions\" optionLabel=\"label\" optionValue=\"type\"\n [(ngModel)]=\"selectedPlotType\" appendTo=\"body\"\n pTooltip=\"Select plot type\" tooltipPosition=\"bottom\"\n [style]=\"{'min-width': '11rem'}\"\n styleClass=\"plotmode-dropdown\" panelStyleClass=\"plotmode-panel\"\n (onChange)=\"selectPlotType.emit($event.value)\">\n <ng-template let-opt pTemplate=\"selectedItem\">\n <span class=\"plotmode-item\" *ngIf=\"opt\">\n <i *ngIf=\"isPiIcon(opt.icon)\" [class]=\"opt.icon\"></i>\n <img *ngIf=\"opt.icon && !isPiIcon(opt.icon)\" class=\"plotmode-icon-img\" [src]=\"opt.icon\" alt=\"\" />\n <span>{{ opt.label }}</span>\n </span>\n </ng-template>\n <ng-template let-opt pTemplate=\"item\">\n <span class=\"plotmode-item\">\n <i *ngIf=\"isPiIcon(opt.icon)\" [class]=\"opt.icon\"></i>\n <img *ngIf=\"opt.icon && !isPiIcon(opt.icon)\" class=\"plotmode-icon-img\" [src]=\"opt.icon\" alt=\"\" />\n <span>{{ opt.label }}</span>\n </span>\n </ng-template>\n </p-dropdown>\n <ng-container *ngIf=\"isIsosurfaceMode\">\n <span class=\"flex align-items-center mx-1 toolbar-text\">Iso</span>\n <p-slider [(ngModel)]=\"isoRange\" [range]=\"true\"\n [min]=\"isoValueMin\" [max]=\"isoValueMax\" [step]=\"isoValueStep\"\n (onChange)=\"isoRangeChange.emit($event.values)\"\n pTooltip=\"Isosurface band (0\u2013255), mapped onto the volume's\n actual intensity range. Surfaces are drawn for voxels whose\n intensity falls in the selected band \u2014 narrow it to isolate\n features, widen it to see the whole structure.\"\n [escape]=\"false\" tooltipPosition=\"bottom\"\n styleClass=\"wand-sensitivity-slider\"></p-slider>\n <span class=\"flex align-items-center mx-1 toolbar-text wand-sensitivity-value\">\n {{ isoRange[0] }}\u2013{{ isoRange[1] }}</span>\n </ng-container>\n <!-- Intensity tools (Heatmap / Image mode): add another colored line ROI.\n Each added line gets the next bright palette colour and its own inset\n trace; select a line on the image and use the trash tool to remove it\n (and its trace). The lines are Region-based but excluded from the\n Region Editor and exports. -->\n <ng-container *ngIf=\"isIntensityCapable\">\n <span class=\"toolbar-separator\"></span>\n <p-button size=\"small\" [text]=\"true\"\n pTooltip=\"Add an intensity line (new colour). Click a line to select it,\n then use the trash tool to remove it.\"\n [escape]=\"false\" tooltipPosition=\"bottom\"\n (onClick)=\"addProfileLine.emit()\">\n <img src=\"assets/plotting/spline-chart.svg\" class=\"toolbar-icon-dark\" style=\"width: 1rem\"/>\n </p-button>\n </ng-container>\n </ng-container>\n <!-- Channels & Histogram: brightness/contrast, gamma, per-channel display,\n colormap/reverse/invert \u2014 opens the floating dialog. -->\n <p-button *ngIf=\"tools.specialTools\" pRipple size=\"small\" [text]=\"true\"\n pTooltip=\"Channels &amp; Histogram (brightness/contrast, colormap)\"\n tooltipPosition=\"bottom\"\n (onClick)=\"openChannelHistogram.emit()\">\n <img src=\"assets/plotting/invert.svg\" class=\"toolbar-icon-dark\" style=\"width: 1rem\"/>\n </p-button>\n\n <!-- Smoothen toggle: bilinear smoothing vs nearest-neighbour raw pixels (Image\n view only \u2014 it's an OpenSeadragon drawer setting). Highlighted when smoothing on. -->\n <p-button *ngIf=\"tools.specialTools && isImageView\" size=\"small\"\n [text]=\"!imageSmoothingEnabled\"\n pTooltip=\"Smoothen (turn on image smoothing)\" tooltipPosition=\"bottom\"\n (onClick)=\"toggleImageSmoothing.emit()\">\n <img src=\"assets/plotting/smooth.svg\" style=\"width: 1rem\"\n [class]=\"imageSmoothingEnabled ? 'toolbar-icon-active' : 'toolbar-icon-dark'\"/>\n </p-button>\n\n <!-- Stack controls -->\n <ng-container *ngIf=\"imageInfo?.isStack\">\n <span class=\"toolbar-separator\"></span>\n <!-- Image view: scrub z-slices live (the renderer swaps slices in place). -->\n <ng-container *ngIf=\"showsLiveSliceScrubber\">\n <span class=\"flex align-items-center mx-1 toolbar-text\">Slice</span>\n <p-slider [(ngModel)]=\"zIndex\" [min]=\"0\" [max]=\"maxIndex\"\n (onChange)=\"zScrub.emit($event.value)\" (onSlideEnd)=\"zSlide.emit($event.value)\"\n pTooltip=\"Slice through the stack (\u2190 / \u2192 keys)\" tooltipPosition=\"bottom\"\n [style]=\"{'width':'16rem'}\" styleClass=\"wand-sensitivity-slider\"></p-slider>\n <span class=\"flex align-items-center mx-1 toolbar-text\">{{zIndex}} of {{maxIndex}}</span>\n </ng-container>\n <!-- Heatmap view: single-image (high-def zoom) vs stack (frame slider). Plotly-only \u2014\n hidden for napari, which has no single/stack toggle or per-frame fetch. -->\n <ng-container *ngIf=\"!showsLiveSliceScrubber && !isNapariMode\">\n <p-button icon=\"pi pi-image\" size=\"small\"\n [text]=\"imageInfo?.showStack\"\n pTooltip=\"Single image mode: enables high-def zoom\" tooltipPosition=\"bottom\"\n (onClick)=\"selectStackOption.emit(stackOptions[0])\">\n </p-button>\n <p-button icon=\"pi pi-images\" size=\"small\"\n [text]=\"!imageInfo?.showStack\"\n pTooltip=\"Stack mode, enables slicing through image frames\" tooltipPosition=\"bottom\"\n (onClick)=\"selectStackOption.emit(stackOptions[1])\">\n </p-button>\n <ng-container *ngIf=\"!imageInfo?.showStack\">\n <span class=\"flex align-items-center mx-1 toolbar-text\">Slice</span>\n <div class=\"p-inputgroup\" style=\"width: auto\">\n <p-inputNumber styleClass=\"w-3rem toolbar-input\"\n\t\t\t\t\tpTooltip=\"View image in stack (&#x23CE;)\"\n\t\t\t\t\t[(ngModel)]=\"zIndex\" [min]=\"0\" [max]=\"maxIndex\"\n (onInput)=\"zIndexInput.emit(zIndex)\" (keydown.enter)=\"reloadAndPlot.emit()\"/>\n <p-button icon=\"pi pi-refresh\" size=\"small\" [text]=\"true\" (onClick)=\"reloadAndPlot.emit()\"\n pTooltip=\"Plot this slice\" tooltipPosition=\"bottom\">\n </p-button>\n </div>\n <span class=\"flex align-items-center mx-1 toolbar-text\">of {{maxIndex}}</span>\n </ng-container>\n </ng-container>\n </ng-container>\n\n <!-- Image actions (special tools) -->\n <ng-container *ngIf=\"tools.specialTools\">\n <span class=\"toolbar-separator\"></span>\n <p-button icon=\"pi pi-download\" size=\"small\" [text]=\"true\"\n pTooltip=\"Download image as PNG\" tooltipPosition=\"bottom\"\n (onClick)=\"downloadImage.emit()\">\n </p-button>\n <p-button *ngIf=\"isHeatmap\" icon=\"pi pi-window-maximize\" size=\"small\" [text]=\"true\"\n pTooltip=\"Fit to view\" tooltipPosition=\"bottom\"\n (onClick)=\"autoscaleImage.emit()\">\n </p-button>\n </ng-container>\n\n <!-- Zoom tools. Plotly rubber-band drag-zoom: on OpenSeadragon the box-zoom\n covers this and a bare drag pans natively, so it's hidden in image view. -->\n <ng-container *ngIf=\"isHeatmap && tools.zoomTools\">\n <span class=\"toolbar-separator\"></span>\n <p-button *ngIf=\"!isImageView\" icon=\"pi pi-search\" size=\"small\"\n [text]=\"activeDragMode !== 'zoom'\"\n pTooltip=\"Zoom drag\" tooltipPosition=\"bottom\"\n (onClick)=\"toggleDragMode.emit('zoom')\">\n </p-button>\n <p-button size=\"small\"\n [text]=\"activeDragMode !== 'zoomToBox'\"\n pTooltip=\"Zoom box (<kbd>b</kbd>)\" [escape]=\"false\" tooltipPosition=\"bottom\"\n (onClick)=\"toggleDragMode.emit('zoomToBox')\">\n <img src=\"assets/plotting/zoom-box.svg\"\n [class]=\"activeDragMode === 'zoomToBox' ? 'toolbar-icon-active' : 'toolbar-icon-dark'\"\n style=\"width: 1rem\"/>\n </p-button>\n <p-button icon=\"pi pi-arrows-alt\" size=\"small\"\n [text]=\"activeDragMode !== 'pan'\"\n pTooltip=\"Pan the image (<kbd>p</kbd>)\" [escape]=\"false\" tooltipPosition=\"bottom\"\n (onClick)=\"toggleDragMode.emit('pan')\">\n </p-button>\n <p-button icon=\"pi pi-search-plus\" size=\"small\" [text]=\"true\"\n pTooltip=\"Zoom in (<kbd>+</kbd>)\" [escape]=\"false\" tooltipPosition=\"bottom\"\n (onClick)=\"zoomIn.emit()\">\n </p-button>\n <p-button icon=\"pi pi-search-minus\" size=\"small\" [text]=\"true\"\n pTooltip=\"Zoom out (<kbd>-</kbd>)\" [escape]=\"false\" tooltipPosition=\"bottom\"\n (onClick)=\"zoomOut.emit()\">\n </p-button>\n </ng-container>\n\n <!-- Surface 3D controls -->\n <ng-container *ngIf=\"!isHeatmap\">\n <span class=\"toolbar-separator\"></span>\n <p-button icon=\"pi pi-search\" size=\"small\"\n [text]=\"activeSurface3dMode !== 'zoom'\"\n pTooltip=\"Zoom\" tooltipPosition=\"bottom\"\n (onClick)=\"toggleSurface3dMode.emit('zoom')\">\n </p-button>\n <p-button icon=\"pi pi-arrows-alt\" size=\"small\"\n [text]=\"activeSurface3dMode !== 'pan'\"\n pTooltip=\"Pan\" tooltipPosition=\"bottom\"\n (onClick)=\"toggleSurface3dMode.emit('pan')\">\n </p-button>\n <p-button icon=\"pi pi-globe\" size=\"small\"\n [text]=\"activeSurface3dMode !== 'orbit'\"\n pTooltip=\"Orbital rotation\" tooltipPosition=\"bottom\"\n (onClick)=\"toggleSurface3dMode.emit('orbit')\">\n </p-button>\n <p-button icon=\"pi pi-sync\" size=\"small\"\n [text]=\"activeSurface3dMode !== 'turntable'\"\n pTooltip=\"Turntable rotation\" tooltipPosition=\"bottom\"\n (onClick)=\"toggleSurface3dMode.emit('turntable')\">\n </p-button>\n <p-button icon=\"pi pi-home\" size=\"small\" [text]=\"true\"\n pTooltip=\"Reset camera to default\" tooltipPosition=\"bottom\"\n (onClick)=\"resetSurfaceCamera.emit()\">\n </p-button>\n <!-- 3D coordinate-axes / scale gizmo toggle (napari-js WebGPU volume/isosurface only).\n `p-button-icon-only` gives the same square sizing as the neighbouring pi-icon buttons\n (the projected <img> doesn't trigger PrimeNG's automatic icon-only sizing). -->\n <p-button size=\"small\" *ngIf=\"isNapariMode\" styleClass=\"p-button-icon-only\"\n [text]=\"!axesVisible\"\n pTooltip=\"Toggle 3D axes / scale\" tooltipPosition=\"bottom\"\n (onClick)=\"toggleAxes.emit()\">\n <img src=\"assets/plotting/3d-coordinates.svg\"\n [class]=\"axesVisible ? 'toolbar-icon-active' : 'toolbar-icon-dark'\"\n style=\"width: 1rem\"/>\n </p-button>\n <!-- Surface wireframe toggle (napari-js WebGPU surface only). -->\n <p-button icon=\"pi pi-share-alt\" size=\"small\" *ngIf=\"isNapariSurfaceMode\"\n [text]=\"!wireframeActive\"\n pTooltip=\"Toggle wireframe\" tooltipPosition=\"bottom\"\n (onClick)=\"toggleWireframe.emit()\">\n </p-button>\n <!-- Resolution (decimate factor) for the napari 3D types \u2014 reloads at coarser/finer sampling. -->\n <ng-container *ngIf=\"isNapari3dMode\">\n <span class=\"flex align-items-center mx-1 toolbar-text\">Res</span>\n <p-dropdown [options]=\"decimateOptions\" optionLabel=\"label\" optionValue=\"value\"\n [ngModel]=\"resolutionScale\" appendTo=\"body\"\n pTooltip=\"Resolution (decimate factor)\" tooltipPosition=\"bottom\"\n [style]=\"{'min-width': '6rem'}\"\n (onChange)=\"selectResolution.emit($event.value)\">\n </p-dropdown>\n </ng-container>\n </ng-container>\n\n <ng-container *ngIf=\"isHeatmap && tools.regionTools\">\n <span class=\"toolbar-separator\"></span>\n <!-- Region Editor: opens the editor as a floating dialog (formerly the\n right-panel Regions tab). Sits with the region drawing tools. -->\n <p-button size=\"small\" [text]=\"true\"\n pTooltip=\"Open the Region Editor\" tooltipPosition=\"bottom\"\n (onClick)=\"openRegionEditor.emit()\">\n <img src=\"assets/plotting/region-editor.svg\" class=\"toolbar-icon-dark\" style=\"width: 1rem\"/>\n </p-button>\n <!-- Region drawing tools -->\n <p-button icon=\"pi pi-arrow-up-right\" size=\"small\"\n [text]=\"activeDragMode !== 'select'\"\n pTooltip=\"Selection mode (<kbd>s</kbd>)\"\n [escape]=\"false\" tooltipPosition=\"bottom\"\n (onClick)=\"toggleDragMode.emit('select')\">\n </p-button>\n <p-button icon=\"pi pi-stop\" size=\"small\"\n [text]=\"activeDragMode !== 'drawrect'\"\n pTooltip=\"Draw a rectangular region (<kbd>r</kbd>)\" [escape]=\"false\"\n tooltipPosition=\"bottom\"\n (onClick)=\"toggleDragMode.emit('drawrect')\">\n </p-button>\n <p-button size=\"small\"\n [text]=\"activeDragMode !== 'drawopenpath'\"\n pTooltip=\"Draw a polyline (<kbd>l</kbd>)\" [escape]=\"false\"\n tooltipPosition=\"bottom\"\n (onClick)=\"toggleDragMode.emit('drawopenpath')\">\n <img src=\"assets/plotting/polyline.svg\"\n [class]=\"activeDragMode === 'drawopenpath' ? 'toolbar-icon-active' : 'toolbar-icon-dark'\"\n style=\"width: 1rem\"/>\n </p-button>\n <p-button icon=\"pi pi-pencil\" size=\"small\"\n [text]=\"activeDragMode !== 'drawclosedpath'\"\n pTooltip=\"Draw a freeform region (<kbd>f</kbd>)\" [escape]=\"false\"\n tooltipPosition=\"bottom\"\n (onClick)=\"toggleDragMode.emit('drawclosedpath')\">\n </p-button>\n <!-- Brush: paint a region by dragging a disc of the given size; the Size\n slider appears while it's active. Hold Shift to erase (subtract). -->\n <p-button size=\"small\"\n [text]=\"activeDragMode !== 'brush'\"\n pTooltip=\"Brush: click and drag to paint a region.\n <br>Hold <kbd>Shift</kbd> while painting to erase from a region.\"\n [escape]=\"false\" tooltipPosition=\"bottom\"\n (onClick)=\"toggleDragMode.emit('brush')\">\n <img src=\"assets/plotting/paintbrush.svg\"\n [class]=\"activeDragMode === 'brush' ? 'toolbar-icon-active' : 'toolbar-icon-dark'\"\n style=\"width: 1rem\"/>\n </p-button>\n <ng-container *ngIf=\"activeDragMode === 'brush'\">\n <span class=\"flex align-items-center mx-1 toolbar-text\">Size</span>\n <p-slider [(ngModel)]=\"brushSize\"\n [min]=\"brushSizeMin\" [max]=\"brushSizeMax\" [step]=\"brushSizeStep\"\n (onChange)=\"brushSizeChange.emit($event.value)\"\n pTooltip=\"Brush diameter in image pixels.\"\n [escape]=\"false\" tooltipPosition=\"bottom\"\n styleClass=\"wand-sensitivity-slider\"></p-slider>\n <span class=\"flex align-items-center mx-1 toolbar-text wand-sensitivity-value\">\n {{ brushSize }}</span>\n </ng-container>\n\n <!-- Vertex editing \u2014 backends with a vertex-editing overlay (OSD + napari-js image). -->\n <ng-container *ngIf=\"supportsRegionVertexTools\">\n <p-button size=\"small\"\n [text]=\"activeDragMode !== 'drawpolygon'\"\n pTooltip=\"Polygon: click to place each vertex; click the first point to close\"\n [escape]=\"false\" tooltipPosition=\"bottom\"\n (onClick)=\"toggleDragMode.emit('drawpolygon')\">\n <img src=\"assets/plotting/polygon-vertices.svg\"\n [class]=\"activeDragMode === 'drawpolygon' ? 'toolbar-icon-active' : 'toolbar-icon-dark'\"\n style=\"width: 1rem\"/>\n </p-button>\n <p-button size=\"small\"\n [text]=\"activeDragMode !== 'addpoint'\"\n pTooltip=\"Add a vertex: click an edge of the selected polygon\"\n [escape]=\"false\" tooltipPosition=\"bottom\"\n (onClick)=\"toggleDragMode.emit('addpoint')\">\n <img src=\"assets/plotting/vertex-add.svg\"\n [class]=\"activeDragMode === 'addpoint' ? 'toolbar-icon-active' : 'toolbar-icon-dark'\"\n style=\"width: 1rem\"/>\n </p-button>\n <p-button size=\"small\"\n [text]=\"activeDragMode !== 'deletepoint'\"\n pTooltip=\"Delete a vertex: click a vertex of the selected polygon\"\n [escape]=\"false\" tooltipPosition=\"bottom\"\n (onClick)=\"toggleDragMode.emit('deletepoint')\">\n <img src=\"assets/plotting/vertex-delete.svg\"\n [class]=\"activeDragMode === 'deletepoint' ? 'toolbar-icon-active' : 'toolbar-icon-dark'\"\n style=\"width: 1rem\"/>\n </p-button>\n </ng-container>\n\n <p-button size=\"small\"\n [text]=\"activeDragMode !== 'wand'\"\n pTooltip=\"Wand: click and drag to grow a region by similar pixel values (<kbd>w</kbd>).\n <br>Hold <kbd>Shift</kbd> while clicking or dragging on a region to erase pixels from it.\"\n [escape]=\"false\" tooltipPosition=\"bottom\"\n (onClick)=\"toggleDragMode.emit('wand')\">\n <img src=\"assets/plotting/wand.svg\"\n [class]=\"activeDragMode === 'wand' ? 'toolbar-icon-active' : 'toolbar-icon-dark'\"\n style=\"width: 1rem\"/>\n </p-button>\n <ng-container *ngIf=\"activeDragMode === 'wand'\">\n <span class=\"flex align-items-center mx-1 toolbar-text\">Sens.</span>\n <p-slider [(ngModel)]=\"wandSensitivity\"\n [min]=\"wandSensitivityMin\" [max]=\"wandSensitivityMax\" [step]=\"wandSensitivityStep\"\n (onChange)=\"wandSensitivityChange.emit($event.value)\"\n pTooltip=\"Higher = stricter (smaller selection); lower = looser.\n Takes effect from the next click/drag tick.\"\n [escape]=\"false\" tooltipPosition=\"bottom\"\n styleClass=\"wand-sensitivity-slider\"></p-slider>\n <span class=\"flex align-items-center mx-1 toolbar-text wand-sensitivity-value\">\n {{ wandSensitivity.toFixed(1) }}</span>\n </ng-container>\n <p-button icon=\"pi pi-eraser\" size=\"small\"\n [text]=\"activeDragMode !== 'eraseVertex'\"\n pTooltip=\"Vertex eraser (<kbd>e</kbd>): click or drag to remove polygon vertices inside\n the cursor circle from any region.\"\n [escape]=\"false\" tooltipPosition=\"bottom\"\n (onClick)=\"toggleDragMode.emit('eraseVertex')\">\n </p-button>\n <ng-container *ngIf=\"activeDragMode === 'eraseVertex'\">\n <span class=\"flex align-items-center mx-1 toolbar-text\">Radius</span>\n <p-slider [(ngModel)]=\"vertexEraserRadius\"\n [min]=\"vertexEraserRadiusMin\" [max]=\"vertexEraserRadiusMax\" [step]=\"vertexEraserRadiusStep\"\n (onChange)=\"vertexEraserRadiusChange.emit($event.value)\"\n pTooltip=\"Eraser radius in image pixels.\n Vertices within this distance of the cursor are removed.\"\n [escape]=\"false\" tooltipPosition=\"bottom\"\n styleClass=\"wand-sensitivity-slider\"></p-slider>\n <span class=\"flex align-items-center mx-1 toolbar-text wand-sensitivity-value\">\n {{ vertexEraserRadius }}</span>\n </ng-container>\n <p-button size=\"small\" [text]=\"true\"\n [disabled]=\"!canUndo\"\n pTooltip=\"Undo the last region action (<kbd>Ctrl</kbd>+<kbd>Z</kbd>).\n Up to 10 actions can be undone.\"\n [escape]=\"false\" tooltipPosition=\"bottom\"\n (onClick)=\"undoRegion.emit()\">\n <img src=\"assets/plotting/undo.svg\" class=\"toolbar-icon-dark\" style=\"width: 1rem\"/>\n </p-button>\n <p-button size=\"small\" [text]=\"true\"\n [disabled]=\"!canRedo\"\n pTooltip=\"Redo the last undone region action\n (<kbd>Ctrl</kbd>+<kbd>Shift</kbd>+<kbd>Z</kbd> / <kbd>Ctrl</kbd>+<kbd>Y</kbd>).\"\n [escape]=\"false\" tooltipPosition=\"bottom\"\n (onClick)=\"redoRegion.emit()\">\n <img src=\"assets/plotting/redo.svg\" class=\"toolbar-icon-dark\" style=\"width: 1rem\"/>\n </p-button>\n <p-button icon=\"pi pi-trash\" size=\"small\" [text]=\"true\"\n pTooltip=\"Delete selected region (<kbd>Del</kbd>)\"\n [escape]=\"false\" tooltipPosition=\"bottom\"\n (onClick)=\"deleteRegion.emit()\">\n </p-button>\n <!-- SAM (jit-ui#90): box-prompt segment, interactive point prompts, and a\n model picker. -->\n <span class=\"toolbar-separator\"></span>\n <!-- SAM controls grouped: box-prompt Segment, then the point-prompt tool,\n then the model-picker dropdown (the chevron) which governs BOTH tools.\n Falls back to Segment + point with no model picker when none are set. -->\n <span *ngIf=\"samModels.length; else segmentPlain\" class=\"p-buttonset segment-split\">\n <p-button size=\"small\" [text]=\"true\"\n pTooltip=\"Segment the drawn rectangles with SAM (box prompt)\"\n tooltipPosition=\"bottom\"\n (onClick)=\"segmentRegions.emit()\">\n <img src=\"assets/plotting/picture-segmentation.svg\"\n class=\"toolbar-icon-dark\" style=\"width: 1rem\"/>\n </p-button>\n <ng-container [ngTemplateOutlet]=\"samPointBtn\"></ng-container>\n <p-button icon=\"pi pi-chevron-down\" size=\"small\" [text]=\"true\"\n pTooltip=\"Pick the SAM model (used by both segment tools)\"\n tooltipPosition=\"bottom\"\n (onClick)=\"samModelMenu.toggle($event)\">\n </p-button>\n </span>\n <ng-template #segmentPlain>\n <span class=\"p-buttonset segment-split\">\n <p-button size=\"small\" [text]=\"true\"\n pTooltip=\"Segment the drawn rectangles with SAM (box prompt)\"\n tooltipPosition=\"bottom\"\n (onClick)=\"segmentRegions.emit()\">\n <img src=\"assets/plotting/picture-segmentation.svg\"\n class=\"toolbar-icon-dark\" style=\"width: 1rem\"/>\n </p-button>\n <ng-container [ngTemplateOutlet]=\"samPointBtn\"></ng-container>\n </span>\n </ng-template>\n <!-- Point-prompt tool \u2014 shared between the models / no-models layouts so it\n always sits just left of the model dropdown. -->\n <ng-template #samPointBtn>\n <p-button size=\"small\"\n [text]=\"activeDragMode !== 'samPoint'\"\n pTooltip=\"SAM point prompts: click a fiber to segment it as a new region;\n Shift/Alt-click to refine (exclude) the current one.<br>Esc undoes it.\"\n [escape]=\"false\" tooltipPosition=\"bottom\"\n (onClick)=\"toggleDragMode.emit('samPoint')\">\n <img src=\"assets/plotting/click.svg\"\n [class]=\"activeDragMode === 'samPoint' ? 'toolbar-icon-active' : 'toolbar-icon-dark'\"\n style=\"width: 1rem\"/>\n </p-button>\n </ng-template>\n <p-menu #samModelMenu [model]=\"samMenuItems\" [popup]=\"true\" appendTo=\"body\"></p-menu>\n <!-- Help: explains the ViT-T vs ViT-B trade-off behind the model picker. -->\n <p-button *ngIf=\"samModels.length\" icon=\"pi pi-info-circle\" size=\"small\" [text]=\"true\"\n pTooltip=\"About the SAM models\" tooltipPosition=\"bottom\"\n (onClick)=\"samModelInfo.toggle($event)\">\n </p-button>\n <p-overlayPanel #samModelInfo appendTo=\"body\" styleClass=\"sam-model-info\">\n <div style=\"max-width: 24rem; line-height: 1.4\">\n <div class=\"font-bold mb-1\">SAM models</div>\n <p class=\"mt-0 mb-2\">\n All are promptable Segment Anything models finetuned for biomedical images. They differ\n in the <i>image encoder</i> (the heavy once-per-image step) and the training domain \u2014\n box and point prompting behave the same.\n </p>\n <div class=\"mb-2\">\n <div class=\"font-bold\">micro-sam ViT-T <span class=\"font-normal\">(default)</span></div>\n <div>Light-microscopy finetuned. TinyViT encoder, ~14&nbsp;MB, very fast, runs on WASM.\n Good mask quality \u2014 best for quick interactive work and many/large objects.</div>\n </div>\n <div class=\"mb-2\">\n <div class=\"font-bold\">micro-sam ViT-B</div>\n <div>Light-microscopy finetuned. ViT-Base encoder, ~172&nbsp;MB, slower, GPU-accelerated\n (WebGPU). Highest accuracy \u2014 when ViT-T misses subtle or overlapping boundaries.</div>\n </div>\n <div class=\"mb-2\">\n <div class=\"font-bold\">patho-sam ViT-B</div>\n <div>Histopathology finetuned (H&amp;E / stained tissue). ViT-Base encoder, GPU-accelerated.\n Use it for pathology slides \u2014 it segments tissue/nuclei better than the microscopy models.</div>\n </div>\n <div class=\"mb-2\">\n <div class=\"font-bold\">patho-sam ViT-B (int8)</div>\n <div>Same model, int8-quantized \u2014 ~100&nbsp;MB vs ~180&nbsp;MB (smaller download). Runs on\n WASM (slower encode than the fp16 version); masks are near-identical. Pick it on slow\n connections.</div>\n </div>\n <div class=\"text-sm\" style=\"opacity: 0.75\">\n The choice applies to both the box and point tools. Each tool loads its own encoder,\n so the first click after switching re-encodes once.\n </div>\n </div>\n </p-overlayPanel>\n <p-button size=\"small\" [text]=\"true\"\n pTooltip=\"Auto-segment cells inside each drawn rectangle (cellpose-SAM, client-side)\"\n tooltipPosition=\"bottom\"\n (onClick)=\"segmentCellpose.emit()\">\n <img src=\"assets/plotting/cells.svg\" class=\"toolbar-icon-dark\" style=\"width: 1rem\"/>\n </p-button>\n\n <!-- YOLO: detect objects across the current view. Unlike the SAM and\n cellpose tools it takes no prompt \u2014 a detector finds objects in a\n field rather than being pointed at one \u2014 so there is nothing to draw\n first. Split button: run, pick checkpoint, open parameters. -->\n <span *ngIf=\"yoloModels.length\" class=\"p-buttonset segment-split\">\n <p-button icon=\"pi pi-microchip-ai\" size=\"small\" [text]=\"true\"\n pTooltip=\"Detect objects in the current view with YOLO (client-side)\"\n tooltipPosition=\"bottom\"\n (onClick)=\"detectYolo.emit()\">\n </p-button>\n <p-button icon=\"pi pi-chevron-down\" size=\"small\" [text]=\"true\"\n pTooltip=\"Pick the YOLO model\"\n tooltipPosition=\"bottom\"\n (onClick)=\"yoloModelMenu.toggle($event)\">\n </p-button>\n <p-button icon=\"pi pi-sliders-h\" size=\"small\" [text]=\"true\"\n pTooltip=\"YOLO parameters\"\n tooltipPosition=\"bottom\"\n (onClick)=\"openYoloParams.emit()\">\n </p-button>\n </span>\n <p-menu #yoloModelMenu [model]=\"yoloMenuItems\" [popup]=\"true\" appendTo=\"body\"></p-menu>\n </ng-container>\n\n <ng-container *ngIf=\"tools.help\">\n <span class=\"toolbar-separator\"></span>\n\n <!-- Help -->\n <p-button icon=\"pi pi-question-circle\" size=\"small\" [text]=\"true\"\n pTooltip=\"Help\" tooltipPosition=\"bottom\"\n (onClick)=\"showHelp()\">\n </p-button>\n </ng-container>\n\n </div>\n</p-toolbar>\n<p-dialog header=\"Plotting help\" [(visible)]=\"displayHelpDialog\" [modal]=\"true\" [style]=\"{width: '50vw'}\"\n [maximizable]=\"true\" [draggable]=\"false\" [resizable]=\"false\">\n <p>Click a file in the left-hand tree to display it. By default it opens in the\n <b>Image</b> view \u2014 a natively tiled, deeply zoomable raster (OpenSeadragon) with a\n navigator minimap and a physical-units scale bar. <b>Click the image to zoom</b> toward\n the clicked point; scroll to zoom and drag to pan. When a stack is open, a slice slider\n (Image view) or image-mode toggle (other views) appears. Regions can also be edited in the\n <b>Region Editor</b>, opened with the\n <img src=\"assets/plotting/region-editor.svg\" width=\"14\" height=\"14\" alt=\"Region Editor\"/>\n toolbar button as a floating dialog. Drag the <i class=\"pi pi-bars\"></i> handle to detach\n this toolbar into a floating window.\n </p>\n\n <h3>Plot-type dropdown</h3>\n <p>\n The <b>plot-type dropdown</b> switches how the file is visualized:\n <b>Image</b>, <b>Contour</b>, <b>Scatter (regions)</b>, <b>Surface (3D)</b>,\n <b>Scatter 3D</b>, and <b>Isosurface (3D)</b>. Scalar and 3D types require a grayscale\n image (the 3D volume types also require a z-stack). The non-image views render with\n Plotly and support \"real zooming\" (a downscaled overview that re-fetches higher\n resolution as you zoom in).\n </p>\n <ul>\n <li>\"<b>Isosurface band</b>\" slider (Isosurface): sets the intensity band the surfaces are\n drawn for, mapped onto the volume's real intensity range.</li>\n <li>\"<b>3D camera</b>\" (Surface / Scatter 3D / Isosurface): zoom <i class=\"pi pi-search\"></i>,\n pan <i class=\"pi pi-arrows-alt\"></i>, orbit <i class=\"pi pi-globe\"></i>,\n turntable <i class=\"pi pi-sync\"></i>, and reset camera <i class=\"pi pi-home\"></i>;\n scroll to zoom the 3D scene.</li>\n </ul>\n\n <h3>Image manipulation tools</h3>\n <ul>\n <li>\"<b>Region Editor</b>\" <img src=\"assets/plotting/region-editor.svg\" width=\"15\" height=\"15\">:\n opens the Region Editor as a floating, draggable dialog \u2014 a per-image table of every region\n (class, colour, area) with bulk select/label/colour/delete and GeoJSON import/export. It stays\n in sync with the image, so you can keep it open while drawing.</li>\n <li>\"<b>Intensity profile lines</b>\" <img src=\"assets/plotting/spline-chart.svg\" width=\"15\" height=\"15\">\n (<i>work in progress \u2014 not yet stable</i>): adds a coloured line ROI; a floating inset\n chart plots intensity along each line, updates live as you drag it, and re-samples at the\n current zoom resolution. Each added line gets its own colour; select a line and use the\n trash tool <i class=\"pi pi-trash\"></i> to remove it (and its trace).</li>\n <li>\"<b>Channels &amp; Histogram</b>\" <img src=\"assets/plotting/invert.svg\" width=\"15\" height=\"15\">:\n opens a floating dialog for brightness/contrast, gamma, and per-channel display, plus the\n <b>colormap / LUT</b> controls (grayscale only) \u2014 a <b>reverse</b> toggle and a colormap\n dropdown (default: inverted greys).</li>\n <li>\"<b>Smoothen</b>\" <img src=\"assets/plotting/smooth.svg\" width=\"15\" height=\"15\">\n (Image view only): the image opens showing <b>raw pixels</b> (nearest-neighbour), so\n zooming in renders crisp blocks for pixel inspection. Toggle this on for bilinear\n smoothing; the icon turns white while smoothing is active.</li>\n </ul>\n\n <h3>Download &amp; view tools</h3>\n <ul>\n <li>\"<b>Download</b>\" <i class=\"pi pi-download\"></i>: download the current image as a PNG.</li>\n <li>\"<b>Fit to view</b>\" / autoscale <i class=\"pi pi-window-maximize\"></i>: resets the image\n to its original dimensions. In stack mode it also resets to single-image mode.</li>\n </ul>\n\n <h3>Zoom tools</h3>\n <ul>\n <li>\"<b>Zoom box</b>\" <img src=\"assets/plotting/zoom-box.svg\" width=\"15\" height=\"15\">\n (<kbd>b</kbd>): drag a box to select an area to zoom into.</li>\n <li>\"<b>Pan</b>\" <i class=\"pi pi-arrows-alt\"></i> (<kbd>p</kbd>): drag to pan the image.</li>\n <li>\"<b>Zoom in</b>\" <i class=\"pi pi-search-plus\"></i> (<kbd>+</kbd>): zoom in one step.</li>\n <li>\"<b>Zoom out</b>\" <i class=\"pi pi-search-minus\"></i> (<kbd>-</kbd>): zoom out one step,\n stopping at the original image dimensions.</li>\n </ul>\n\n <h3>Region tools</h3>\n <p>By default no tool is selected (all greyed-out); selecting a tool highlights it. The\n vertex-editing tools (polygon, add/delete vertex, move, B\u00E9zier conversions) are available\n in the <b>Image</b> view only. On a <b>donut</b> (a region with a hole), the inner-ring\n vertices are editable too \u2014 drag, add, and delete them just like the outer ring.</p>\n <ul>\n <li>\"<b>Selection</b>\" <i class=\"pi pi-arrow-up-right\"></i> (<kbd>s</kbd>): a neutral mode\n that turns off whatever drawing tool was active (wand, vertex eraser, rectangle, etc.).\n Use it to step out of editing without picking another tool.</li>\n <li>\"<b>Rectangular region</b>\" <i class=\"pi pi-stop\"></i> (<kbd>r</kbd>): click and drag to\n create a rectangular region.</li>\n <li>\"<b>Polyline</b>\" <img src=\"assets/plotting/polyline.svg\" width=\"14\" height=\"14\">\n (<kbd>l</kbd>): draw an open (non-closed) polyline by clicking and dragging. Polylines can\n be edited and exported to GeoJSON as a <code>LineString</code> feature.</li>\n <li>\"<b>Freeform region</b>\" <i class=\"pi pi-pencil\"></i> (<kbd>f</kbd>): click and drag to\n draw a closed freeform polygon.</li>\n <li>\"<b>Brush</b>\" <img src=\"assets/plotting/paintbrush.svg\" width=\"15\" height=\"15\">: click\n and drag to paint a region with a circular brush \u2014 the painted stroke is the region.\n A <b>Size</b> slider (brush diameter, in image pixels) appears in the toolbar while the\n brush is active. Hold <kbd>Shift</kbd> while painting to <b>erase</b> (subtract) from a\n region instead. Painting over an existing region extends it; brushing across two regions\n merges them. Painting a closed ring (or shift-erasing an interior pocket) leaves the\n enclosed area empty \u2014 a <b>donut</b> (Image view).</li>\n <li>\"<b>Polygon</b>\" <img src=\"assets/plotting/polygon-vertices.svg\" width=\"15\" height=\"15\">:\n click to place each vertex; click the first point to close the polygon.</li>\n <li>\"<b>Add vertex</b>\" <img src=\"assets/plotting/vertex-add.svg\" width=\"15\" height=\"15\">:\n click an edge of the selected polygon to insert a new vertex.</li>\n <li>\"<b>Delete vertex</b>\" <img src=\"assets/plotting/vertex-delete.svg\" width=\"15\" height=\"15\">:\n click a vertex of the selected polygon to remove it.</li>\n <li>\"<b>Move region</b>\" <img src=\"assets/plotting/region-move.svg\" width=\"15\" height=\"15\">:\n drag to move the selected region.</li>\n <li>\"<b>To B\u00E9zier</b>\" <img src=\"assets/plotting/to-bezier.svg\" width=\"15\" height=\"15\"> /\n \"<b>To polygon</b>\" <img src=\"assets/plotting/to-polygon.svg\" width=\"15\" height=\"15\">:\n convert the selected region to a smooth B\u00E9zier curve, or back to a straight-edged polygon.</li>\n <li>\"<b>Wand</b>\" <img src=\"assets/plotting/wand.svg\" width=\"15\" height=\"15\"> (<kbd>w</kbd>):\n click to grow a region from pixels with similar values around the click point. Hold\n <kbd>Ctrl</kbd>/<kbd>Cmd</kbd> for an exact-match flood fill; drag to keep extending the\n selection while moving; hold <kbd>Shift</kbd> while clicking or dragging on an existing\n region to <b>erase</b> pixels from it. Use the <b>Sens.</b> slider that appears to make the\n wand stricter (higher = smaller selection) or looser. Subsequent clicks keep extending the\n same region; clicking inside an existing region adopts it, and growing into another region\n merges them.</li>\n <li>\"<b>Vertex eraser</b>\" <i class=\"pi pi-eraser\"></i> (<kbd>e</kbd>): click or click-and-drag\n to remove polygon vertices that fall inside the cursor circle from any region, including a\n region's inner rings (donut holes). Use the <b>Radius</b> slider to set the eraser size in\n image pixels. Polygons reduced below 3 vertices (or polylines below 2) are deleted entirely;\n a hole reduced below 3 vertices is removed.</li>\n <li>\"<b>Undo</b>\" <img src=\"assets/plotting/undo.svg\" width=\"15\" height=\"15\">\n (<kbd>Ctrl</kbd>/<kbd>Cmd</kbd>+<kbd>Z</kbd>): reverts the\n last region action (draw, delete, edit, wand/brush stroke, segmentation, \u2026). Up to the last\n <b>10</b> actions can be undone; the button greys out when there's nothing left to undo.</li>\n <li>\"<b>Redo</b>\" <img src=\"assets/plotting/redo.svg\" width=\"15\" height=\"15\">\n (<kbd>Ctrl</kbd>/<kbd>Cmd</kbd>+<kbd>Shift</kbd>+<kbd>Z</kbd> or <kbd>Ctrl</kbd>/<kbd>Cmd</kbd>+<kbd>Y</kbd>):\n re-applies the most recently undone action. Performing a new region action clears what can be redone.</li>\n <li>\"<b>Delete region</b>\" <i class=\"pi pi-trash\"></i> (<kbd>Del</kbd>): deletes the selected\n region. This can also be done from the Region Editor.</li>\n </ul>\n\n <h3>Region actions (right-click the image)</h3>\n <p>Right-click a region \u2014 or the current multi-selection \u2014 to open the\n \"<b>Selected region(s)</b>\" group at the top of the context menu. These act immediately on\n the selection (they're not drawing modes) and are fully undo/redo-able:</p>\n <ul>\n <li>\"<b>Select all regions</b>\" <i class=\"pi pi-check-square\"></i>: select every region on\n the image (also in the Region Editor toolbar).</li>\n <li>\"<b>Merge / group</b>\": combine 2+ selected regions into one. If the pieces don't touch,\n the result is a single multi-part region (sparse \"bits\").</li>\n <li>\"<b>Ungroup</b>\": split a multi-part region back into one region per piece.</li>\n <li>\"<b>Inverse</b>\": replace the selection with everything <i>outside</i> it \u2014 the image\n rectangle with the selected regions punched out as holes. Works at any image size.</li>\n <li>\"<b>Simplify</b>\": reduce a region's vertex count (Douglas\u2013Peucker) by an altitude\n threshold in pixels \u2014 pick <b>Light</b> (1&nbsp;px), <b>Medium</b> (3&nbsp;px),\n <b>Strong</b> (8&nbsp;px), or <b>Custom\u2026</b> for a slider. Applies to the exterior and any\n holes (and every part of a multi-part region).</li>\n <li>The B\u00E9zier conversions and Delete also live in this group (the right-click equivalents of\n the toolbar / vertex tools above).</li>\n </ul>\n\n <h3>Segmentation tools (SAM &amp; cellpose)</h3>\n <p>AI-assisted segmentation that runs entirely in the browser. The box and point\n tools use a promptable <b>micro-sam</b> model; the cellpose tool auto-detects cells.\n Generated regions inherit the color of the rectangle they came from. The first run\n downloads the model (a progress toast is shown); it's then cached.</p>\n <ul>\n <li>\"<b>Segment</b>\" <img src=\"assets/plotting/picture-segmentation.svg\" width=\"15\" height=\"15\">\n (box prompt): draw one or more <b>Rectangular regions</b> around the objects of interest,\n then click Segment. Each rectangle is replaced by the SAM mask of the object inside it.</li>\n <li>\"<b>Point prompts</b>\" <img src=\"assets/plotting/click.svg\" width=\"15\" height=\"15\">: click directly on an object to\n segment it as a <b>new</b> region. Each click starts a fresh object \u2014 clicking another\n object won't grow the previous one. <kbd>Shift</kbd>/<kbd>Alt</kbd>-click adds an\n <i>exclude</i> point that refines the current object; <kbd>Esc</kbd> undoes it,\n <kbd>Enter</kbd> commits it.</li>\n <li>\"<b>Model picker</b>\" <i class=\"pi pi-chevron-down\"></i>: the dropdown attached to the\n Segment button chooses the SAM model \u2014 <b>micro-sam ViT-T</b> (default; small, fast) or\n <b>micro-sam ViT-B</b> (larger, highest accuracy) for light microscopy, or\n <b>patho-sam ViT-B</b> for histopathology (H&amp;E / stained tissue). The choice applies to\n <i>both</i> the box and point tools. The <i class=\"pi pi-info-circle\"></i> info button next\n to it explains the trade-off.</li>\n <li>\"<b>Cellpose</b>\" <img src=\"assets/plotting/cells.svg\" width=\"15\" height=\"15\">: draw rectangles, then click Cellpose to\n automatically segment every cell inside each rectangle (client-side cellpose-SAM) \u2014 one\n region per detected cell. No clicking on individual cells needed.</li>\n </ul>\n\n <h3>Help</h3>\n <p>\"<b>Help</b>\" <i class=\"pi pi-question-circle\"></i>: opens this dialog.</p>\n\n <h3>Keyboard shortcuts</h3>\n <table class=\"shortcuts-table\">\n <tr><td><kbd>+</kbd> / <kbd>=</kbd></td><td>Zoom in</td></tr>\n <tr><td><kbd>-</kbd></td><td>Zoom out</td></tr>\n <tr><td><kbd>b</kbd></td><td>Toggle zoom box mode</td></tr>\n <tr><td><kbd>p</kbd></td><td>Toggle pan mode</td></tr>\n <tr><td><kbd>f</kbd></td><td>Toggle freeform region drawing</td></tr>\n <tr><td><kbd>l</kbd></td><td>Toggle polyline drawing</td></tr>\n <tr><td><kbd>r</kbd></td><td>Toggle rectangular region drawing</td></tr>\n <tr><td><kbd>w</kbd></td><td>Toggle wand tool</td></tr>\n <tr><td>Wand + <kbd>Shift</kbd></td><td>Erase pixels from the active region</td></tr>\n <tr><td>Wand + <kbd>Ctrl</kbd>/<kbd>Cmd</kbd></td><td>Exact-match flood fill (no smoothing)</td></tr>\n <tr><td><kbd>e</kbd></td><td>Toggle vertex eraser</td></tr>\n <tr><td><kbd>s</kbd></td><td>Selection mode (deactivates drawing tools)</td></tr>\n <tr><td>Point prompt + <kbd>Shift</kbd>/<kbd>Alt</kbd></td><td>Add an exclude point (refine the current object)</td></tr>\n <tr><td><kbd>Enter</kbd></td><td>Commit the current SAM point object</td></tr>\n <tr><td><kbd>Esc</kbd></td><td>Undo the current SAM point object</td></tr>\n <tr><td><kbd>Del</kbd> / <kbd>\u232B</kbd></td><td>Delete selected region</td></tr>\n <tr><td><kbd>Ctrl</kbd>/<kbd>Cmd</kbd>+<kbd>Z</kbd></td><td>Undo the last region action (up to 10)</td></tr>\n <tr><td><kbd>Ctrl</kbd>/<kbd>Cmd</kbd>+<kbd>Shift</kbd>+<kbd>Z</kbd> / <kbd>Ctrl</kbd>+<kbd>Y</kbd></td><td>Redo the last undone region action</td></tr>\n </table>\n\n <ng-template pTemplate=\"footer\">\n <p-button icon=\"pi pi-check\" (click)=\"displayHelpDialog=false\" label=\"Ok\" styleClass=\"p-button-text\">\n </p-button>\n </ng-template>\n</p-dialog>\n", styles: [":host{display:block}:host ::ng-deep .p-toolbar{padding:.25rem .5rem}.toolbar-text{color:var(--primary-color);font-size:.875rem}::ng-deep .toolbar-input .p-inputtext{color:var(--primary-color);border-color:var(--primary-color)}.toolbar-separator{border-left:1px solid var(--surface-border);height:1.5rem;margin:0 .25rem;align-self:center}.toolbar-icon-dark{filter:brightness(0) invert(47%) sepia(98%) saturate(567%) hue-rotate(181deg) brightness(103%) contrast(98%)}.toolbar-icon-active{filter:brightness(0) invert(1)}::ng-deep .plotmode-item{display:inline-flex;align-items:center;gap:.5rem;color:#000}::ng-deep .plotmode-item i{font-size:1rem;color:var(--primary-color)}::ng-deep .plotmode-item .plotmode-icon-img{width:1.1rem;height:1.1rem;object-fit:contain;filter:brightness(0) invert(47%) sepia(98%) saturate(567%) hue-rotate(181deg) brightness(103%) contrast(98%)}::ng-deep .wand-sensitivity-slider{width:8rem;align-self:center;margin:0 .25rem}.wand-sensitivity-value{min-width:2rem;text-align:right}span.colormap{width:50px;height:auto;display:inline-block}img.colormap{width:50px;height:auto}.colormap{background:no-repeat;background-size:100%;vertical-align:middle}kbd{background:#e8e8e8;border:1px solid #bbb;border-radius:3px;padding:1px 5px;font-family:monospace;font-size:.85em}.shortcuts-table{border-collapse:collapse}.shortcuts-table td{padding:.25rem .75rem .25rem 0;vertical-align:middle}.shortcuts-table td:first-child{white-space:nowrap}\n"] }]
241
+ args: [{ selector: 'plotting-toolbar', template: "<p-toolbar>\n <div class=\"p-toolbar-group-start flex-wrap align-items-center gap-1\">\n\n <!-- Host-provided custom toolbar buttons, shown first (e.g. app-specific\n actions the visualization view itself doesn't own). -->\n <ng-content></ng-content>\n\n <!-- Plot type: available for any image (grayscale or RGB). -->\n <ng-container *ngIf=\"imageInfo && tools.specialTools\">\n <span class=\"toolbar-separator\"></span>\n <p-dropdown [options]=\"plotTypeOptions\" optionLabel=\"label\" optionValue=\"type\"\n [(ngModel)]=\"selectedPlotType\" appendTo=\"body\"\n pTooltip=\"Select plot type\" tooltipPosition=\"bottom\"\n [style]=\"{'min-width': '11rem'}\"\n styleClass=\"plotmode-dropdown\" panelStyleClass=\"plotmode-panel\"\n (onChange)=\"selectPlotType.emit($event.value)\">\n <ng-template let-opt pTemplate=\"selectedItem\">\n <span class=\"plotmode-item\" *ngIf=\"opt\">\n <i *ngIf=\"isPiIcon(opt.icon)\" [class]=\"opt.icon\"></i>\n <img *ngIf=\"opt.icon && !isPiIcon(opt.icon)\" class=\"plotmode-icon-img\" [src]=\"opt.icon\" alt=\"\" />\n <span>{{ opt.label }}</span>\n </span>\n </ng-template>\n <ng-template let-opt pTemplate=\"item\">\n <span class=\"plotmode-item\">\n <i *ngIf=\"isPiIcon(opt.icon)\" [class]=\"opt.icon\"></i>\n <img *ngIf=\"opt.icon && !isPiIcon(opt.icon)\" class=\"plotmode-icon-img\" [src]=\"opt.icon\" alt=\"\" />\n <span>{{ opt.label }}</span>\n </span>\n </ng-template>\n </p-dropdown>\n <ng-container *ngIf=\"isIsosurfaceMode\">\n <span class=\"flex align-items-center mx-1 toolbar-text\">Iso</span>\n <p-slider [(ngModel)]=\"isoRange\" [range]=\"true\"\n [min]=\"isoValueMin\" [max]=\"isoValueMax\" [step]=\"isoValueStep\"\n (onChange)=\"isoRangeChange.emit($event.values)\"\n pTooltip=\"Isosurface band (0\u2013255), mapped onto the volume's\n actual intensity range. Surfaces are drawn for voxels whose\n intensity falls in the selected band \u2014 narrow it to isolate\n features, widen it to see the whole structure.\"\n [escape]=\"false\" tooltipPosition=\"bottom\"\n styleClass=\"wand-sensitivity-slider\"></p-slider>\n <span class=\"flex align-items-center mx-1 toolbar-text wand-sensitivity-value\">\n {{ isoRange[0] }}\u2013{{ isoRange[1] }}</span>\n </ng-container>\n <!-- Intensity tools (Heatmap / Image mode): add another colored line ROI.\n Each added line gets the next bright palette colour and its own inset\n trace; select a line on the image and use the trash tool to remove it\n (and its trace). The lines are Region-based but excluded from the\n Region Editor and exports. -->\n <ng-container *ngIf=\"isIntensityCapable\">\n <span class=\"toolbar-separator\"></span>\n <p-button size=\"small\" [text]=\"true\"\n pTooltip=\"Add an intensity line (new colour). Click a line to select it,\n then use the trash tool to remove it.\"\n [escape]=\"false\" tooltipPosition=\"bottom\"\n (onClick)=\"addProfileLine.emit()\">\n <img src=\"assets/plotting/spline-chart.svg\" class=\"toolbar-icon-dark\" style=\"width: 1rem\"/>\n </p-button>\n </ng-container>\n </ng-container>\n <!-- Channels & Histogram: brightness/contrast, gamma, per-channel display,\n colormap/reverse/invert \u2014 opens the floating dialog. -->\n <p-button *ngIf=\"tools.specialTools\" pRipple size=\"small\" [text]=\"true\"\n pTooltip=\"Channels &amp; Histogram (brightness/contrast, colormap)\"\n tooltipPosition=\"bottom\"\n (onClick)=\"openChannelHistogram.emit()\">\n <img src=\"assets/plotting/invert.svg\" class=\"toolbar-icon-dark\" style=\"width: 1rem\"/>\n </p-button>\n\n <!-- Smoothen toggle: bilinear smoothing vs nearest-neighbour raw pixels (Image\n view only \u2014 it's an OpenSeadragon drawer setting). Highlighted when smoothing on. -->\n <p-button *ngIf=\"tools.specialTools && isImageView\" size=\"small\"\n [text]=\"!imageSmoothingEnabled\"\n pTooltip=\"Smoothen (turn on image smoothing)\" tooltipPosition=\"bottom\"\n (onClick)=\"toggleImageSmoothing.emit()\">\n <img src=\"assets/plotting/smooth.svg\" style=\"width: 1rem\"\n [class]=\"imageSmoothingEnabled ? 'toolbar-icon-active' : 'toolbar-icon-dark'\"/>\n </p-button>\n\n <!-- Stack controls -->\n <ng-container *ngIf=\"imageInfo?.isStack\">\n <span class=\"toolbar-separator\"></span>\n <!-- Image view: scrub z-slices live (the renderer swaps slices in place). -->\n <ng-container *ngIf=\"showsLiveSliceScrubber\">\n <span class=\"flex align-items-center mx-1 toolbar-text\">Slice</span>\n <p-slider [(ngModel)]=\"zIndex\" [min]=\"0\" [max]=\"maxIndex\"\n (onChange)=\"zScrub.emit($event.value)\" (onSlideEnd)=\"zSlide.emit($event.value)\"\n pTooltip=\"Slice through the stack (\u2190 / \u2192 keys)\" tooltipPosition=\"bottom\"\n [style]=\"{'width':'16rem'}\" styleClass=\"wand-sensitivity-slider\"></p-slider>\n <span class=\"flex align-items-center mx-1 toolbar-text\">{{zIndex}} of {{maxIndex}}</span>\n </ng-container>\n <!-- Heatmap view: single-image (high-def zoom) vs stack (frame slider). Plotly-only \u2014\n hidden for napari, which has no single/stack toggle or per-frame fetch. -->\n <ng-container *ngIf=\"!showsLiveSliceScrubber && !isNapariMode\">\n <p-button icon=\"pi pi-image\" size=\"small\"\n [text]=\"imageInfo?.showStack\"\n pTooltip=\"Single image mode: enables high-def zoom\" tooltipPosition=\"bottom\"\n (onClick)=\"selectStackOption.emit(stackOptions[0])\">\n </p-button>\n <p-button icon=\"pi pi-images\" size=\"small\"\n [text]=\"!imageInfo?.showStack\"\n pTooltip=\"Stack mode, enables slicing through image frames\" tooltipPosition=\"bottom\"\n (onClick)=\"selectStackOption.emit(stackOptions[1])\">\n </p-button>\n <ng-container *ngIf=\"!imageInfo?.showStack\">\n <span class=\"flex align-items-center mx-1 toolbar-text\">Slice</span>\n <div class=\"p-inputgroup\" style=\"width: auto\">\n <p-inputNumber styleClass=\"w-3rem toolbar-input\"\n\t\t\t\t\tpTooltip=\"View image in stack (&#x23CE;)\"\n\t\t\t\t\t[(ngModel)]=\"zIndex\" [min]=\"0\" [max]=\"maxIndex\"\n (onInput)=\"zIndexInput.emit(zIndex)\" (keydown.enter)=\"reloadAndPlot.emit()\"/>\n <p-button icon=\"pi pi-refresh\" size=\"small\" [text]=\"true\" (onClick)=\"reloadAndPlot.emit()\"\n pTooltip=\"Plot this slice\" tooltipPosition=\"bottom\">\n </p-button>\n </div>\n <span class=\"flex align-items-center mx-1 toolbar-text\">of {{maxIndex}}</span>\n </ng-container>\n </ng-container>\n </ng-container>\n\n <!-- Image actions (special tools) -->\n <ng-container *ngIf=\"tools.specialTools\">\n <span class=\"toolbar-separator\"></span>\n <p-button icon=\"pi pi-download\" size=\"small\" [text]=\"true\"\n pTooltip=\"Download image as PNG\" tooltipPosition=\"bottom\"\n (onClick)=\"downloadImage.emit()\">\n </p-button>\n <p-button *ngIf=\"isHeatmap\" icon=\"pi pi-window-maximize\" size=\"small\" [text]=\"true\"\n pTooltip=\"Fit to view\" tooltipPosition=\"bottom\"\n (onClick)=\"autoscaleImage.emit()\">\n </p-button>\n </ng-container>\n\n <!-- Zoom tools. Plotly rubber-band drag-zoom: on OpenSeadragon the box-zoom\n covers this and a bare drag pans natively, so it's hidden in image view. -->\n <ng-container *ngIf=\"isHeatmap && tools.zoomTools\">\n <span class=\"toolbar-separator\"></span>\n <p-button *ngIf=\"!isImageView\" icon=\"pi pi-search\" size=\"small\"\n [text]=\"activeDragMode !== 'zoom'\"\n pTooltip=\"Zoom drag\" tooltipPosition=\"bottom\"\n (onClick)=\"toggleDragMode.emit('zoom')\">\n </p-button>\n <p-button size=\"small\"\n [text]=\"activeDragMode !== 'zoomToBox'\"\n pTooltip=\"Zoom box (<kbd>b</kbd>)\" [escape]=\"false\" tooltipPosition=\"bottom\"\n (onClick)=\"toggleDragMode.emit('zoomToBox')\">\n <img src=\"assets/plotting/zoom-box.svg\"\n [class]=\"activeDragMode === 'zoomToBox' ? 'toolbar-icon-active' : 'toolbar-icon-dark'\"\n style=\"width: 1rem\"/>\n </p-button>\n <p-button icon=\"pi pi-arrows-alt\" size=\"small\"\n [text]=\"activeDragMode !== 'pan'\"\n pTooltip=\"Pan the image (<kbd>p</kbd>)\" [escape]=\"false\" tooltipPosition=\"bottom\"\n (onClick)=\"toggleDragMode.emit('pan')\">\n </p-button>\n <p-button icon=\"pi pi-search-plus\" size=\"small\" [text]=\"true\"\n pTooltip=\"Zoom in (<kbd>+</kbd>)\" [escape]=\"false\" tooltipPosition=\"bottom\"\n (onClick)=\"zoomIn.emit()\">\n </p-button>\n <p-button icon=\"pi pi-search-minus\" size=\"small\" [text]=\"true\"\n pTooltip=\"Zoom out (<kbd>-</kbd>)\" [escape]=\"false\" tooltipPosition=\"bottom\"\n (onClick)=\"zoomOut.emit()\">\n </p-button>\n </ng-container>\n\n <!-- Surface 3D controls -->\n <ng-container *ngIf=\"!isHeatmap\">\n <span class=\"toolbar-separator\"></span>\n <p-button icon=\"pi pi-search\" size=\"small\"\n [text]=\"activeSurface3dMode !== 'zoom'\"\n pTooltip=\"Zoom\" tooltipPosition=\"bottom\"\n (onClick)=\"toggleSurface3dMode.emit('zoom')\">\n </p-button>\n <p-button icon=\"pi pi-arrows-alt\" size=\"small\"\n [text]=\"activeSurface3dMode !== 'pan'\"\n pTooltip=\"Pan\" tooltipPosition=\"bottom\"\n (onClick)=\"toggleSurface3dMode.emit('pan')\">\n </p-button>\n <p-button icon=\"pi pi-globe\" size=\"small\"\n [text]=\"activeSurface3dMode !== 'orbit'\"\n pTooltip=\"Orbital rotation\" tooltipPosition=\"bottom\"\n (onClick)=\"toggleSurface3dMode.emit('orbit')\">\n </p-button>\n <p-button icon=\"pi pi-sync\" size=\"small\"\n [text]=\"activeSurface3dMode !== 'turntable'\"\n pTooltip=\"Turntable rotation\" tooltipPosition=\"bottom\"\n (onClick)=\"toggleSurface3dMode.emit('turntable')\">\n </p-button>\n <p-button icon=\"pi pi-home\" size=\"small\" [text]=\"true\"\n pTooltip=\"Reset camera to default\" tooltipPosition=\"bottom\"\n (onClick)=\"resetSurfaceCamera.emit()\">\n </p-button>\n <!-- 3D coordinate-axes / scale gizmo toggle (napari-js WebGPU volume/isosurface only).\n `p-button-icon-only` gives the same square sizing as the neighbouring pi-icon buttons\n (the projected <img> doesn't trigger PrimeNG's automatic icon-only sizing). -->\n <p-button size=\"small\" *ngIf=\"isNapariMode\" styleClass=\"p-button-icon-only\"\n [text]=\"!axesVisible\"\n pTooltip=\"Toggle 3D axes / scale\" tooltipPosition=\"bottom\"\n (onClick)=\"toggleAxes.emit()\">\n <img src=\"assets/plotting/3d-coordinates.svg\"\n [class]=\"axesVisible ? 'toolbar-icon-active' : 'toolbar-icon-dark'\"\n style=\"width: 1rem\"/>\n </p-button>\n <!-- Surface wireframe toggle (napari-js WebGPU surface only). -->\n <p-button icon=\"pi pi-share-alt\" size=\"small\" *ngIf=\"isNapariSurfaceMode\"\n [text]=\"!wireframeActive\"\n pTooltip=\"Toggle wireframe\" tooltipPosition=\"bottom\"\n (onClick)=\"toggleWireframe.emit()\">\n </p-button>\n <!-- Resolution (decimate factor) for the napari 3D types \u2014 reloads at coarser/finer sampling. -->\n <ng-container *ngIf=\"isNapari3dMode\">\n <span class=\"flex align-items-center mx-1 toolbar-text\">Res</span>\n <p-dropdown [options]=\"decimateOptions\" optionLabel=\"label\" optionValue=\"value\"\n [ngModel]=\"resolutionScale\" appendTo=\"body\"\n pTooltip=\"Resolution (decimate factor)\" tooltipPosition=\"bottom\"\n [style]=\"{'min-width': '6rem'}\"\n (onChange)=\"selectResolution.emit($event.value)\">\n </p-dropdown>\n </ng-container>\n </ng-container>\n\n <ng-container *ngIf=\"isHeatmap && tools.regionTools\">\n <span class=\"toolbar-separator\"></span>\n <!-- Region Editor: opens the editor as a floating dialog (formerly the\n right-panel Regions tab). Sits with the region drawing tools. -->\n <p-button size=\"small\" [text]=\"true\"\n pTooltip=\"Open the Region Editor\" tooltipPosition=\"bottom\"\n (onClick)=\"openRegionEditor.emit()\">\n <img src=\"assets/plotting/region-editor.svg\" class=\"toolbar-icon-dark\" style=\"width: 1rem\"/>\n </p-button>\n <!-- Region drawing tools -->\n <p-button icon=\"pi pi-arrow-up-right\" size=\"small\"\n [text]=\"activeDragMode !== 'select'\"\n pTooltip=\"Selection mode (<kbd>s</kbd>)\"\n [escape]=\"false\" tooltipPosition=\"bottom\"\n (onClick)=\"toggleDragMode.emit('select')\">\n </p-button>\n <p-button icon=\"pi pi-stop\" size=\"small\"\n [text]=\"activeDragMode !== 'drawrect'\"\n pTooltip=\"Draw a rectangular region (<kbd>r</kbd>)\" [escape]=\"false\"\n tooltipPosition=\"bottom\"\n (onClick)=\"toggleDragMode.emit('drawrect')\">\n </p-button>\n <p-button size=\"small\"\n [text]=\"activeDragMode !== 'drawopenpath'\"\n pTooltip=\"Draw a polyline (<kbd>l</kbd>)\" [escape]=\"false\"\n tooltipPosition=\"bottom\"\n (onClick)=\"toggleDragMode.emit('drawopenpath')\">\n <img src=\"assets/plotting/polyline.svg\"\n [class]=\"activeDragMode === 'drawopenpath' ? 'toolbar-icon-active' : 'toolbar-icon-dark'\"\n style=\"width: 1rem\"/>\n </p-button>\n <p-button icon=\"pi pi-pencil\" size=\"small\"\n [text]=\"activeDragMode !== 'drawclosedpath'\"\n pTooltip=\"Draw a freeform region (<kbd>f</kbd>)\" [escape]=\"false\"\n tooltipPosition=\"bottom\"\n (onClick)=\"toggleDragMode.emit('drawclosedpath')\">\n </p-button>\n <!-- Brush: paint a region by dragging a disc of the given size; the Size\n slider appears while it's active. Hold Shift to erase (subtract). -->\n <p-button size=\"small\"\n [text]=\"activeDragMode !== 'brush'\"\n pTooltip=\"Brush: click and drag to paint a region.\n <br>Hold <kbd>Shift</kbd> while painting to erase from a region.\"\n [escape]=\"false\" tooltipPosition=\"bottom\"\n (onClick)=\"toggleDragMode.emit('brush')\">\n <img src=\"assets/plotting/paintbrush.svg\"\n [class]=\"activeDragMode === 'brush' ? 'toolbar-icon-active' : 'toolbar-icon-dark'\"\n style=\"width: 1rem\"/>\n </p-button>\n <ng-container *ngIf=\"activeDragMode === 'brush'\">\n <span class=\"flex align-items-center mx-1 toolbar-text\">Size</span>\n <p-slider [(ngModel)]=\"brushSize\"\n [min]=\"brushSizeMin\" [max]=\"brushSizeMax\" [step]=\"brushSizeStep\"\n (onChange)=\"brushSizeChange.emit($event.value)\"\n pTooltip=\"Brush diameter in image pixels.\"\n [escape]=\"false\" tooltipPosition=\"bottom\"\n styleClass=\"wand-sensitivity-slider\"></p-slider>\n <span class=\"flex align-items-center mx-1 toolbar-text wand-sensitivity-value\">\n {{ brushSize }}</span>\n </ng-container>\n\n <!-- Vertex editing \u2014 backends with a vertex-editing overlay (OSD + napari-js image). -->\n <ng-container *ngIf=\"supportsRegionVertexTools\">\n <p-button size=\"small\"\n [text]=\"activeDragMode !== 'drawpolygon'\"\n pTooltip=\"Polygon: click to place each vertex; click the first point to close\"\n [escape]=\"false\" tooltipPosition=\"bottom\"\n (onClick)=\"toggleDragMode.emit('drawpolygon')\">\n <img src=\"assets/plotting/polygon-vertices.svg\"\n [class]=\"activeDragMode === 'drawpolygon' ? 'toolbar-icon-active' : 'toolbar-icon-dark'\"\n style=\"width: 1rem\"/>\n </p-button>\n <p-button size=\"small\"\n [text]=\"activeDragMode !== 'addpoint'\"\n pTooltip=\"Add a vertex: click an edge of the selected polygon\"\n [escape]=\"false\" tooltipPosition=\"bottom\"\n (onClick)=\"toggleDragMode.emit('addpoint')\">\n <img src=\"assets/plotting/vertex-add.svg\"\n [class]=\"activeDragMode === 'addpoint' ? 'toolbar-icon-active' : 'toolbar-icon-dark'\"\n style=\"width: 1rem\"/>\n </p-button>\n <p-button size=\"small\"\n [text]=\"activeDragMode !== 'deletepoint'\"\n pTooltip=\"Delete a vertex: click a vertex of the selected polygon\"\n [escape]=\"false\" tooltipPosition=\"bottom\"\n (onClick)=\"toggleDragMode.emit('deletepoint')\">\n <img src=\"assets/plotting/vertex-delete.svg\"\n [class]=\"activeDragMode === 'deletepoint' ? 'toolbar-icon-active' : 'toolbar-icon-dark'\"\n style=\"width: 1rem\"/>\n </p-button>\n </ng-container>\n\n <p-button size=\"small\"\n [text]=\"activeDragMode !== 'wand'\"\n pTooltip=\"Wand: click and drag to grow a region by similar pixel values (<kbd>w</kbd>).\n <br>Hold <kbd>Shift</kbd> while clicking or dragging on a region to erase pixels from it.\"\n [escape]=\"false\" tooltipPosition=\"bottom\"\n (onClick)=\"toggleDragMode.emit('wand')\">\n <img src=\"assets/plotting/wand.svg\"\n [class]=\"activeDragMode === 'wand' ? 'toolbar-icon-active' : 'toolbar-icon-dark'\"\n style=\"width: 1rem\"/>\n </p-button>\n <ng-container *ngIf=\"activeDragMode === 'wand'\">\n <span class=\"flex align-items-center mx-1 toolbar-text\">Sens.</span>\n <p-slider [(ngModel)]=\"wandSensitivity\"\n [min]=\"wandSensitivityMin\" [max]=\"wandSensitivityMax\" [step]=\"wandSensitivityStep\"\n (onChange)=\"wandSensitivityChange.emit($event.value)\"\n pTooltip=\"Higher = stricter (smaller selection); lower = looser.\n Takes effect from the next click/drag tick.\"\n [escape]=\"false\" tooltipPosition=\"bottom\"\n styleClass=\"wand-sensitivity-slider\"></p-slider>\n <span class=\"flex align-items-center mx-1 toolbar-text wand-sensitivity-value\">\n {{ wandSensitivity.toFixed(1) }}</span>\n </ng-container>\n <p-button icon=\"pi pi-eraser\" size=\"small\"\n [text]=\"activeDragMode !== 'eraseVertex'\"\n pTooltip=\"Vertex eraser (<kbd>e</kbd>): click or drag to remove polygon vertices inside\n the cursor circle from any region.\"\n [escape]=\"false\" tooltipPosition=\"bottom\"\n (onClick)=\"toggleDragMode.emit('eraseVertex')\">\n </p-button>\n <ng-container *ngIf=\"activeDragMode === 'eraseVertex'\">\n <span class=\"flex align-items-center mx-1 toolbar-text\">Radius</span>\n <p-slider [(ngModel)]=\"vertexEraserRadius\"\n [min]=\"vertexEraserRadiusMin\" [max]=\"vertexEraserRadiusMax\" [step]=\"vertexEraserRadiusStep\"\n (onChange)=\"vertexEraserRadiusChange.emit($event.value)\"\n pTooltip=\"Eraser radius in image pixels.\n Vertices within this distance of the cursor are removed.\"\n [escape]=\"false\" tooltipPosition=\"bottom\"\n styleClass=\"wand-sensitivity-slider\"></p-slider>\n <span class=\"flex align-items-center mx-1 toolbar-text wand-sensitivity-value\">\n {{ vertexEraserRadius }}</span>\n </ng-container>\n <p-button size=\"small\" [text]=\"true\"\n [disabled]=\"!canUndo\"\n pTooltip=\"Undo the last region action (<kbd>Ctrl</kbd>+<kbd>Z</kbd>).\n Up to 10 actions can be undone.\"\n [escape]=\"false\" tooltipPosition=\"bottom\"\n (onClick)=\"undoRegion.emit()\">\n <img src=\"assets/plotting/undo.svg\" class=\"toolbar-icon-dark\" style=\"width: 1rem\"/>\n </p-button>\n <p-button size=\"small\" [text]=\"true\"\n [disabled]=\"!canRedo\"\n pTooltip=\"Redo the last undone region action\n (<kbd>Ctrl</kbd>+<kbd>Shift</kbd>+<kbd>Z</kbd> / <kbd>Ctrl</kbd>+<kbd>Y</kbd>).\"\n [escape]=\"false\" tooltipPosition=\"bottom\"\n (onClick)=\"redoRegion.emit()\">\n <img src=\"assets/plotting/redo.svg\" class=\"toolbar-icon-dark\" style=\"width: 1rem\"/>\n </p-button>\n <p-button icon=\"pi pi-trash\" size=\"small\" [text]=\"true\"\n pTooltip=\"Delete selected region (<kbd>Del</kbd>)\"\n [escape]=\"false\" tooltipPosition=\"bottom\"\n (onClick)=\"deleteRegion.emit()\">\n </p-button>\n <!-- SAM (jit-ui#90): box-prompt segment, interactive point prompts, and a\n model picker. -->\n <span class=\"toolbar-separator\"></span>\n <!-- SAM controls grouped: box-prompt Segment, then the point-prompt tool,\n then the model-picker dropdown (the chevron) which governs BOTH tools.\n Falls back to Segment + point with no model picker when none are set. -->\n <span *ngIf=\"samModels.length; else segmentPlain\" class=\"p-buttonset segment-split\">\n <p-button size=\"small\" [text]=\"true\"\n pTooltip=\"Segment the drawn rectangles with SAM (box prompt)\"\n tooltipPosition=\"bottom\"\n (onClick)=\"segmentRegions.emit()\">\n <img src=\"assets/plotting/picture-segmentation.svg\"\n class=\"toolbar-icon-dark\" style=\"width: 1rem\"/>\n </p-button>\n <ng-container [ngTemplateOutlet]=\"samPointBtn\"></ng-container>\n <p-button icon=\"pi pi-chevron-down\" size=\"small\" [text]=\"true\"\n pTooltip=\"Pick the SAM model (used by both segment tools)\"\n tooltipPosition=\"bottom\"\n (onClick)=\"samModelMenu.toggle($event)\">\n </p-button>\n </span>\n <ng-template #segmentPlain>\n <span class=\"p-buttonset segment-split\">\n <p-button size=\"small\" [text]=\"true\"\n pTooltip=\"Segment the drawn rectangles with SAM (box prompt)\"\n tooltipPosition=\"bottom\"\n (onClick)=\"segmentRegions.emit()\">\n <img src=\"assets/plotting/picture-segmentation.svg\"\n class=\"toolbar-icon-dark\" style=\"width: 1rem\"/>\n </p-button>\n <ng-container [ngTemplateOutlet]=\"samPointBtn\"></ng-container>\n </span>\n </ng-template>\n <!-- Point-prompt tool \u2014 shared between the models / no-models layouts so it\n always sits just left of the model dropdown. -->\n <ng-template #samPointBtn>\n <p-button size=\"small\"\n [text]=\"activeDragMode !== 'samPoint'\"\n pTooltip=\"SAM point prompts: click a fiber to segment it as a new region;\n Shift/Alt-click to refine (exclude) the current one.<br>Esc undoes it.\"\n [escape]=\"false\" tooltipPosition=\"bottom\"\n (onClick)=\"toggleDragMode.emit('samPoint')\">\n <img src=\"assets/plotting/click.svg\"\n [class]=\"activeDragMode === 'samPoint' ? 'toolbar-icon-active' : 'toolbar-icon-dark'\"\n style=\"width: 1rem\"/>\n </p-button>\n </ng-template>\n <p-menu #samModelMenu [model]=\"samMenuItems\" [popup]=\"true\" appendTo=\"body\"></p-menu>\n <!-- Help: explains the ViT-T vs ViT-B trade-off behind the model picker. -->\n <p-button *ngIf=\"samModels.length\" icon=\"pi pi-info-circle\" size=\"small\" [text]=\"true\"\n pTooltip=\"About the SAM models\" tooltipPosition=\"bottom\"\n (onClick)=\"samModelInfo.toggle($event)\">\n </p-button>\n <p-overlayPanel #samModelInfo appendTo=\"body\" styleClass=\"sam-model-info\">\n <div style=\"max-width: 24rem; line-height: 1.4\">\n <div class=\"font-bold mb-1\">SAM models</div>\n <p class=\"mt-0 mb-2\">\n All are promptable Segment Anything models finetuned for biomedical images. They differ\n in the <i>image encoder</i> (the heavy once-per-image step) and the training domain \u2014\n box and point prompting behave the same.\n </p>\n <div class=\"mb-2\">\n <div class=\"font-bold\">micro-sam ViT-T <span class=\"font-normal\">(default)</span></div>\n <div>Light-microscopy finetuned. TinyViT encoder, ~14&nbsp;MB, very fast, runs on WASM.\n Good mask quality \u2014 best for quick interactive work and many/large objects.</div>\n </div>\n <div class=\"mb-2\">\n <div class=\"font-bold\">micro-sam ViT-B</div>\n <div>Light-microscopy finetuned. ViT-Base encoder, ~172&nbsp;MB, slower, GPU-accelerated\n (WebGPU). Highest accuracy \u2014 when ViT-T misses subtle or overlapping boundaries.</div>\n </div>\n <div class=\"mb-2\">\n <div class=\"font-bold\">patho-sam ViT-B</div>\n <div>Histopathology finetuned (H&amp;E / stained tissue). ViT-Base encoder, GPU-accelerated.\n Use it for pathology slides \u2014 it segments tissue/nuclei better than the microscopy models.</div>\n </div>\n <div class=\"mb-2\">\n <div class=\"font-bold\">patho-sam ViT-B (int8)</div>\n <div>Same model, int8-quantized \u2014 ~100&nbsp;MB vs ~180&nbsp;MB (smaller download). Runs on\n WASM (slower encode than the fp16 version); masks are near-identical. Pick it on slow\n connections.</div>\n </div>\n <div class=\"text-sm\" style=\"opacity: 0.75\">\n The choice applies to both the box and point tools. Each tool loads its own encoder,\n so the first click after switching re-encodes once.\n </div>\n </div>\n </p-overlayPanel>\n <p-button size=\"small\" [text]=\"true\"\n pTooltip=\"Auto-segment cells inside each drawn rectangle (cellpose-SAM, client-side)\"\n tooltipPosition=\"bottom\"\n (onClick)=\"segmentCellpose.emit()\">\n <img src=\"assets/plotting/cells.svg\" class=\"toolbar-icon-dark\" style=\"width: 1rem\"/>\n </p-button>\n\n <!-- YOLO: detect objects across the current view. Unlike the SAM and\n cellpose tools it takes no prompt \u2014 a detector finds objects in a\n field rather than being pointed at one \u2014 so there is nothing to draw\n first. Split button: run, pick checkpoint, open parameters. -->\n <span *ngIf=\"yoloModels.length\" class=\"p-buttonset segment-split\">\n <p-button icon=\"pi pi-microchip-ai\" size=\"small\" [text]=\"true\"\n pTooltip=\"Detect objects in the current view with YOLO (client-side)\"\n tooltipPosition=\"bottom\"\n (onClick)=\"detectYolo.emit()\">\n </p-button>\n <p-button icon=\"pi pi-chevron-down\" size=\"small\" [text]=\"true\"\n pTooltip=\"Pick the YOLO model\"\n tooltipPosition=\"bottom\"\n (onClick)=\"yoloModelMenu.toggle($event)\">\n </p-button>\n <p-button icon=\"pi pi-sliders-h\" size=\"small\" [text]=\"true\"\n pTooltip=\"YOLO parameters\"\n tooltipPosition=\"bottom\"\n (onClick)=\"openYoloParams.emit()\">\n </p-button>\n </span>\n <p-menu #yoloModelMenu [model]=\"yoloMenuItems\" [popup]=\"true\" appendTo=\"body\"></p-menu>\n\n <!-- Retinal layers: semantic segmentation of the current view. Same\n no-prompt shape as YOLO \u2014 it labels every pixel rather than being\n pointed at an object \u2014 so the same split button: run, pick\n checkpoint, open parameters. -->\n <span *ngIf=\"retinalModels.length\" class=\"p-buttonset segment-split\">\n <p-button icon=\"pi pi-align-left\" size=\"small\" [text]=\"true\"\n pTooltip=\"Segment retinal layers in the current view (client-side)\"\n tooltipPosition=\"bottom\"\n (onClick)=\"segmentRetinal.emit()\">\n </p-button>\n <p-button icon=\"pi pi-chevron-down\" size=\"small\" [text]=\"true\"\n pTooltip=\"Pick the retinal layer model\"\n tooltipPosition=\"bottom\"\n (onClick)=\"retinalModelMenu.toggle($event)\">\n </p-button>\n <p-button icon=\"pi pi-sliders-h\" size=\"small\" [text]=\"true\"\n pTooltip=\"Retinal layer parameters\"\n tooltipPosition=\"bottom\"\n (onClick)=\"openRetinalParams.emit()\">\n </p-button>\n </span>\n <p-menu #retinalModelMenu [model]=\"retinalMenuItems\" [popup]=\"true\" appendTo=\"body\"></p-menu>\n </ng-container>\n\n <ng-container *ngIf=\"tools.help\">\n <span class=\"toolbar-separator\"></span>\n\n <!-- Help -->\n <p-button icon=\"pi pi-question-circle\" size=\"small\" [text]=\"true\"\n pTooltip=\"Help\" tooltipPosition=\"bottom\"\n (onClick)=\"showHelp()\">\n </p-button>\n </ng-container>\n\n </div>\n</p-toolbar>\n<p-dialog header=\"Plotting help\" [(visible)]=\"displayHelpDialog\" [modal]=\"true\" [style]=\"{width: '50vw'}\"\n [maximizable]=\"true\" [draggable]=\"false\" [resizable]=\"false\">\n <p>Click a file in the left-hand tree to display it. By default it opens in the\n <b>Image</b> view \u2014 a natively tiled, deeply zoomable raster (OpenSeadragon) with a\n navigator minimap and a physical-units scale bar. <b>Click the image to zoom</b> toward\n the clicked point; scroll to zoom and drag to pan. When a stack is open, a slice slider\n (Image view) or image-mode toggle (other views) appears. Regions can also be edited in the\n <b>Region Editor</b>, opened with the\n <img src=\"assets/plotting/region-editor.svg\" width=\"14\" height=\"14\" alt=\"Region Editor\"/>\n toolbar button as a floating dialog. Drag the <i class=\"pi pi-bars\"></i> handle to detach\n this toolbar into a floating window.\n </p>\n\n <h3>Plot-type dropdown</h3>\n <p>\n The <b>plot-type dropdown</b> switches how the file is visualized:\n <b>Image</b>, <b>Contour</b>, <b>Scatter (regions)</b>, <b>Surface (3D)</b>,\n <b>Scatter 3D</b>, and <b>Isosurface (3D)</b>. Scalar and 3D types require a grayscale\n image (the 3D volume types also require a z-stack). The non-image views render with\n Plotly and support \"real zooming\" (a downscaled overview that re-fetches higher\n resolution as you zoom in).\n </p>\n <ul>\n <li>\"<b>Isosurface band</b>\" slider (Isosurface): sets the intensity band the surfaces are\n drawn for, mapped onto the volume's real intensity range.</li>\n <li>\"<b>3D camera</b>\" (Surface / Scatter 3D / Isosurface): zoom <i class=\"pi pi-search\"></i>,\n pan <i class=\"pi pi-arrows-alt\"></i>, orbit <i class=\"pi pi-globe\"></i>,\n turntable <i class=\"pi pi-sync\"></i>, and reset camera <i class=\"pi pi-home\"></i>;\n scroll to zoom the 3D scene.</li>\n </ul>\n\n <h3>Image manipulation tools</h3>\n <ul>\n <li>\"<b>Region Editor</b>\" <img src=\"assets/plotting/region-editor.svg\" width=\"15\" height=\"15\">:\n opens the Region Editor as a floating, draggable dialog \u2014 a per-image table of every region\n (class, colour, area) with bulk select/label/colour/delete and GeoJSON import/export. It stays\n in sync with the image, so you can keep it open while drawing.</li>\n <li>\"<b>Intensity profile lines</b>\" <img src=\"assets/plotting/spline-chart.svg\" width=\"15\" height=\"15\">\n (<i>work in progress \u2014 not yet stable</i>): adds a coloured line ROI; a floating inset\n chart plots intensity along each line, updates live as you drag it, and re-samples at the\n current zoom resolution. Each added line gets its own colour; select a line and use the\n trash tool <i class=\"pi pi-trash\"></i> to remove it (and its trace).</li>\n <li>\"<b>Channels &amp; Histogram</b>\" <img src=\"assets/plotting/invert.svg\" width=\"15\" height=\"15\">:\n opens a floating dialog for brightness/contrast, gamma, and per-channel display, plus the\n <b>colormap / LUT</b> controls (grayscale only) \u2014 a <b>reverse</b> toggle and a colormap\n dropdown (default: inverted greys).</li>\n <li>\"<b>Smoothen</b>\" <img src=\"assets/plotting/smooth.svg\" width=\"15\" height=\"15\">\n (Image view only): the image opens showing <b>raw pixels</b> (nearest-neighbour), so\n zooming in renders crisp blocks for pixel inspection. Toggle this on for bilinear\n smoothing; the icon turns white while smoothing is active.</li>\n </ul>\n\n <h3>Download &amp; view tools</h3>\n <ul>\n <li>\"<b>Download</b>\" <i class=\"pi pi-download\"></i>: download the current image as a PNG.</li>\n <li>\"<b>Fit to view</b>\" / autoscale <i class=\"pi pi-window-maximize\"></i>: resets the image\n to its original dimensions. In stack mode it also resets to single-image mode.</li>\n </ul>\n\n <h3>Zoom tools</h3>\n <ul>\n <li>\"<b>Zoom box</b>\" <img src=\"assets/plotting/zoom-box.svg\" width=\"15\" height=\"15\">\n (<kbd>b</kbd>): drag a box to select an area to zoom into.</li>\n <li>\"<b>Pan</b>\" <i class=\"pi pi-arrows-alt\"></i> (<kbd>p</kbd>): drag to pan the image.</li>\n <li>\"<b>Zoom in</b>\" <i class=\"pi pi-search-plus\"></i> (<kbd>+</kbd>): zoom in one step.</li>\n <li>\"<b>Zoom out</b>\" <i class=\"pi pi-search-minus\"></i> (<kbd>-</kbd>): zoom out one step,\n stopping at the original image dimensions.</li>\n </ul>\n\n <h3>Region tools</h3>\n <p>By default no tool is selected (all greyed-out); selecting a tool highlights it. The\n vertex-editing tools (polygon, add/delete vertex, move, B\u00E9zier conversions) are available\n in the <b>Image</b> view only. On a <b>donut</b> (a region with a hole), the inner-ring\n vertices are editable too \u2014 drag, add, and delete them just like the outer ring.</p>\n <ul>\n <li>\"<b>Selection</b>\" <i class=\"pi pi-arrow-up-right\"></i> (<kbd>s</kbd>): a neutral mode\n that turns off whatever drawing tool was active (wand, vertex eraser, rectangle, etc.).\n Use it to step out of editing without picking another tool.</li>\n <li>\"<b>Rectangular region</b>\" <i class=\"pi pi-stop\"></i> (<kbd>r</kbd>): click and drag to\n create a rectangular region.</li>\n <li>\"<b>Polyline</b>\" <img src=\"assets/plotting/polyline.svg\" width=\"14\" height=\"14\">\n (<kbd>l</kbd>): draw an open (non-closed) polyline by clicking and dragging. Polylines can\n be edited and exported to GeoJSON as a <code>LineString</code> feature.</li>\n <li>\"<b>Freeform region</b>\" <i class=\"pi pi-pencil\"></i> (<kbd>f</kbd>): click and drag to\n draw a closed freeform polygon.</li>\n <li>\"<b>Brush</b>\" <img src=\"assets/plotting/paintbrush.svg\" width=\"15\" height=\"15\">: click\n and drag to paint a region with a circular brush \u2014 the painted stroke is the region.\n A <b>Size</b> slider (brush diameter, in image pixels) appears in the toolbar while the\n brush is active. Hold <kbd>Shift</kbd> while painting to <b>erase</b> (subtract) from a\n region instead. Painting over an existing region extends it; brushing across two regions\n merges them. Painting a closed ring (or shift-erasing an interior pocket) leaves the\n enclosed area empty \u2014 a <b>donut</b> (Image view).</li>\n <li>\"<b>Polygon</b>\" <img src=\"assets/plotting/polygon-vertices.svg\" width=\"15\" height=\"15\">:\n click to place each vertex; click the first point to close the polygon.</li>\n <li>\"<b>Add vertex</b>\" <img src=\"assets/plotting/vertex-add.svg\" width=\"15\" height=\"15\">:\n click an edge of the selected polygon to insert a new vertex.</li>\n <li>\"<b>Delete vertex</b>\" <img src=\"assets/plotting/vertex-delete.svg\" width=\"15\" height=\"15\">:\n click a vertex of the selected polygon to remove it.</li>\n <li>\"<b>Move region</b>\" <img src=\"assets/plotting/region-move.svg\" width=\"15\" height=\"15\">:\n drag to move the selected region.</li>\n <li>\"<b>To B\u00E9zier</b>\" <img src=\"assets/plotting/to-bezier.svg\" width=\"15\" height=\"15\"> /\n \"<b>To polygon</b>\" <img src=\"assets/plotting/to-polygon.svg\" width=\"15\" height=\"15\">:\n convert the selected region to a smooth B\u00E9zier curve, or back to a straight-edged polygon.</li>\n <li>\"<b>Wand</b>\" <img src=\"assets/plotting/wand.svg\" width=\"15\" height=\"15\"> (<kbd>w</kbd>):\n click to grow a region from pixels with similar values around the click point. Hold\n <kbd>Ctrl</kbd>/<kbd>Cmd</kbd> for an exact-match flood fill; drag to keep extending the\n selection while moving; hold <kbd>Shift</kbd> while clicking or dragging on an existing\n region to <b>erase</b> pixels from it. Use the <b>Sens.</b> slider that appears to make the\n wand stricter (higher = smaller selection) or looser. Subsequent clicks keep extending the\n same region; clicking inside an existing region adopts it, and growing into another region\n merges them.</li>\n <li>\"<b>Vertex eraser</b>\" <i class=\"pi pi-eraser\"></i> (<kbd>e</kbd>): click or click-and-drag\n to remove polygon vertices that fall inside the cursor circle from any region, including a\n region's inner rings (donut holes). Use the <b>Radius</b> slider to set the eraser size in\n image pixels. Polygons reduced below 3 vertices (or polylines below 2) are deleted entirely;\n a hole reduced below 3 vertices is removed.</li>\n <li>\"<b>Undo</b>\" <img src=\"assets/plotting/undo.svg\" width=\"15\" height=\"15\">\n (<kbd>Ctrl</kbd>/<kbd>Cmd</kbd>+<kbd>Z</kbd>): reverts the\n last region action (draw, delete, edit, wand/brush stroke, segmentation, \u2026). Up to the last\n <b>10</b> actions can be undone; the button greys out when there's nothing left to undo.</li>\n <li>\"<b>Redo</b>\" <img src=\"assets/plotting/redo.svg\" width=\"15\" height=\"15\">\n (<kbd>Ctrl</kbd>/<kbd>Cmd</kbd>+<kbd>Shift</kbd>+<kbd>Z</kbd> or <kbd>Ctrl</kbd>/<kbd>Cmd</kbd>+<kbd>Y</kbd>):\n re-applies the most recently undone action. Performing a new region action clears what can be redone.</li>\n <li>\"<b>Delete region</b>\" <i class=\"pi pi-trash\"></i> (<kbd>Del</kbd>): deletes the selected\n region. This can also be done from the Region Editor.</li>\n </ul>\n\n <h3>Region actions (right-click the image)</h3>\n <p>Right-click a region \u2014 or the current multi-selection \u2014 to open the\n \"<b>Selected region(s)</b>\" group at the top of the context menu. These act immediately on\n the selection (they're not drawing modes) and are fully undo/redo-able:</p>\n <ul>\n <li>\"<b>Select all regions</b>\" <i class=\"pi pi-check-square\"></i>: select every region on\n the image (also in the Region Editor toolbar).</li>\n <li>\"<b>Merge / group</b>\": combine 2+ selected regions into one. If the pieces don't touch,\n the result is a single multi-part region (sparse \"bits\").</li>\n <li>\"<b>Ungroup</b>\": split a multi-part region back into one region per piece.</li>\n <li>\"<b>Inverse</b>\": replace the selection with everything <i>outside</i> it \u2014 the image\n rectangle with the selected regions punched out as holes. Works at any image size.</li>\n <li>\"<b>Simplify</b>\": reduce a region's vertex count (Douglas\u2013Peucker) by an altitude\n threshold in pixels \u2014 pick <b>Light</b> (1&nbsp;px), <b>Medium</b> (3&nbsp;px),\n <b>Strong</b> (8&nbsp;px), or <b>Custom\u2026</b> for a slider. Applies to the exterior and any\n holes (and every part of a multi-part region).</li>\n <li>The B\u00E9zier conversions and Delete also live in this group (the right-click equivalents of\n the toolbar / vertex tools above).</li>\n </ul>\n\n <h3>Segmentation tools (SAM &amp; cellpose)</h3>\n <p>AI-assisted segmentation that runs entirely in the browser. The box and point\n tools use a promptable <b>micro-sam</b> model; the cellpose tool auto-detects cells.\n Generated regions inherit the color of the rectangle they came from. The first run\n downloads the model (a progress toast is shown); it's then cached.</p>\n <ul>\n <li>\"<b>Segment</b>\" <img src=\"assets/plotting/picture-segmentation.svg\" width=\"15\" height=\"15\">\n (box prompt): draw one or more <b>Rectangular regions</b> around the objects of interest,\n then click Segment. Each rectangle is replaced by the SAM mask of the object inside it.</li>\n <li>\"<b>Point prompts</b>\" <img src=\"assets/plotting/click.svg\" width=\"15\" height=\"15\">: click directly on an object to\n segment it as a <b>new</b> region. Each click starts a fresh object \u2014 clicking another\n object won't grow the previous one. <kbd>Shift</kbd>/<kbd>Alt</kbd>-click adds an\n <i>exclude</i> point that refines the current object; <kbd>Esc</kbd> undoes it,\n <kbd>Enter</kbd> commits it.</li>\n <li>\"<b>Model picker</b>\" <i class=\"pi pi-chevron-down\"></i>: the dropdown attached to the\n Segment button chooses the SAM model \u2014 <b>micro-sam ViT-T</b> (default; small, fast) or\n <b>micro-sam ViT-B</b> (larger, highest accuracy) for light microscopy, or\n <b>patho-sam ViT-B</b> for histopathology (H&amp;E / stained tissue). The choice applies to\n <i>both</i> the box and point tools. The <i class=\"pi pi-info-circle\"></i> info button next\n to it explains the trade-off.</li>\n <li>\"<b>Cellpose</b>\" <img src=\"assets/plotting/cells.svg\" width=\"15\" height=\"15\">: draw rectangles, then click Cellpose to\n automatically segment every cell inside each rectangle (client-side cellpose-SAM) \u2014 one\n region per detected cell. No clicking on individual cells needed.</li>\n </ul>\n\n <h3>Help</h3>\n <p>\"<b>Help</b>\" <i class=\"pi pi-question-circle\"></i>: opens this dialog.</p>\n\n <h3>Keyboard shortcuts</h3>\n <table class=\"shortcuts-table\">\n <tr><td><kbd>+</kbd> / <kbd>=</kbd></td><td>Zoom in</td></tr>\n <tr><td><kbd>-</kbd></td><td>Zoom out</td></tr>\n <tr><td><kbd>b</kbd></td><td>Toggle zoom box mode</td></tr>\n <tr><td><kbd>p</kbd></td><td>Toggle pan mode</td></tr>\n <tr><td><kbd>f</kbd></td><td>Toggle freeform region drawing</td></tr>\n <tr><td><kbd>l</kbd></td><td>Toggle polyline drawing</td></tr>\n <tr><td><kbd>r</kbd></td><td>Toggle rectangular region drawing</td></tr>\n <tr><td><kbd>w</kbd></td><td>Toggle wand tool</td></tr>\n <tr><td>Wand + <kbd>Shift</kbd></td><td>Erase pixels from the active region</td></tr>\n <tr><td>Wand + <kbd>Ctrl</kbd>/<kbd>Cmd</kbd></td><td>Exact-match flood fill (no smoothing)</td></tr>\n <tr><td><kbd>e</kbd></td><td>Toggle vertex eraser</td></tr>\n <tr><td><kbd>s</kbd></td><td>Selection mode (deactivates drawing tools)</td></tr>\n <tr><td>Point prompt + <kbd>Shift</kbd>/<kbd>Alt</kbd></td><td>Add an exclude point (refine the current object)</td></tr>\n <tr><td><kbd>Enter</kbd></td><td>Commit the current SAM point object</td></tr>\n <tr><td><kbd>Esc</kbd></td><td>Undo the current SAM point object</td></tr>\n <tr><td><kbd>Del</kbd> / <kbd>\u232B</kbd></td><td>Delete selected region</td></tr>\n <tr><td><kbd>Ctrl</kbd>/<kbd>Cmd</kbd>+<kbd>Z</kbd></td><td>Undo the last region action (up to 10)</td></tr>\n <tr><td><kbd>Ctrl</kbd>/<kbd>Cmd</kbd>+<kbd>Shift</kbd>+<kbd>Z</kbd> / <kbd>Ctrl</kbd>+<kbd>Y</kbd></td><td>Redo the last undone region action</td></tr>\n </table>\n\n <ng-template pTemplate=\"footer\">\n <p-button icon=\"pi pi-check\" (click)=\"displayHelpDialog=false\" label=\"Ok\" styleClass=\"p-button-text\">\n </p-button>\n </ng-template>\n</p-dialog>\n", styles: [":host{display:block}:host ::ng-deep .p-toolbar{padding:.25rem .5rem}.toolbar-text{color:var(--primary-color);font-size:.875rem}::ng-deep .toolbar-input .p-inputtext{color:var(--primary-color);border-color:var(--primary-color)}.toolbar-separator{border-left:1px solid var(--surface-border);height:1.5rem;margin:0 .25rem;align-self:center}.toolbar-icon-dark{filter:brightness(0) invert(47%) sepia(98%) saturate(567%) hue-rotate(181deg) brightness(103%) contrast(98%)}.toolbar-icon-active{filter:brightness(0) invert(1)}::ng-deep .plotmode-item{display:inline-flex;align-items:center;gap:.5rem;color:#000}::ng-deep .plotmode-item i{font-size:1rem;color:var(--primary-color)}::ng-deep .plotmode-item .plotmode-icon-img{width:1.1rem;height:1.1rem;object-fit:contain;filter:brightness(0) invert(47%) sepia(98%) saturate(567%) hue-rotate(181deg) brightness(103%) contrast(98%)}::ng-deep .wand-sensitivity-slider{width:8rem;align-self:center;margin:0 .25rem}.wand-sensitivity-value{min-width:2rem;text-align:right}span.colormap{width:50px;height:auto;display:inline-block}img.colormap{width:50px;height:auto}.colormap{background:no-repeat;background-size:100%;vertical-align:middle}kbd{background:#e8e8e8;border:1px solid #bbb;border-radius:3px;padding:1px 5px;font-family:monospace;font-size:.85em}.shortcuts-table{border-collapse:collapse}.shortcuts-table td{padding:.25rem .75rem .25rem 0;vertical-align:middle}.shortcuts-table td:first-child{white-space:nowrap}\n"] }]
224
242
  }], propDecorators: { imageInfo: [{
225
243
  type: Input
226
244
  }], plotTypeOptions: [{
@@ -269,6 +287,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.9", ngImpor
269
287
  type: Input
270
288
  }], yoloModelId: [{
271
289
  type: Input
290
+ }], retinalModels: [{
291
+ type: Input
292
+ }], retinalModelId: [{
293
+ type: Input
272
294
  }], selectPlotType: [{
273
295
  type: Output
274
296
  }], addProfileLine: [{
@@ -327,6 +349,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.9", ngImpor
327
349
  type: Output
328
350
  }], openYoloParams: [{
329
351
  type: Output
352
+ }], segmentRetinal: [{
353
+ type: Output
354
+ }], retinalModelChange: [{
355
+ type: Output
356
+ }], openRetinalParams: [{
357
+ type: Output
330
358
  }], samModelChange: [{
331
359
  type: Output
332
360
  }], wandSensitivityChange: [{
@@ -336,4 +364,4 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.9", ngImpor
336
364
  }], vertexEraserRadiusChange: [{
337
365
  type: Output
338
366
  }] } });
339
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidG9vbGJhci5jb21wb25lbnQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi9zcmMvbGliL3Rvb2xiYXIvdG9vbGJhci5jb21wb25lbnQudHMiLCIuLi8uLi8uLi8uLi9zcmMvbGliL3Rvb2xiYXIvdG9vbGJhci5jb21wb25lbnQuaHRtbCJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsU0FBUyxFQUFFLFlBQVksRUFBRSxLQUFLLEVBQWEsTUFBTSxFQUFpQixNQUFNLGVBQWUsQ0FBQztBQUlqRyxPQUFPLEVBQ0wsUUFBUSxFQUVSLFVBQVUsRUFDVixrQkFBa0IsRUFDbEIsZUFBZSxFQUNmLGVBQWUsRUFDZix1QkFBdUIsRUFDdkIsdUJBQXVCLEdBQ3hCLE1BQU0sd0JBQXdCLENBQUM7QUFDaEMsT0FBTyxFQUF5QixpQkFBaUIsRUFBRSxNQUFNLDZCQUE2QixDQUFDOzs7Ozs7Ozs7Ozs7Ozs7QUFFdkY7Ozs7Ozs7Ozs7O0dBV0c7QUFRSCxNQUFNLE9BQU8sZ0JBQWdCO0lBUDdCO1FBVUUsZ0RBQWdEO1FBQ3ZDLG9CQUFlLEdBQXlCLEVBQUUsQ0FBQztRQUMzQyxxQkFBZ0IsR0FBYSxRQUFRLENBQUMsS0FBSyxDQUFDO1FBQ3JEOzRFQUNvRTtRQUMzRCxhQUFRLEdBQWEsQ0FBQyxDQUFDLEVBQUUsR0FBRyxDQUFDLENBQUM7UUFDOUIsYUFBUSxHQUFHLENBQUMsQ0FBQztRQUNiLFdBQU0sR0FBRyxDQUFDLENBQUM7UUFDcEIsa0RBQWtEO1FBQ3pDLGNBQVMsR0FBRyxJQUFJLENBQUM7UUFDakIsbUJBQWMsR0FBa0IsSUFBSSxDQUFDO1FBQ3JDLHdCQUFtQixHQUFHLFdBQVcsQ0FBQztRQUMzQyw0RkFBNEY7UUFDbkYsZ0JBQVcsR0FBRyxJQUFJLENBQUM7UUFDbkIsb0JBQWUsR0FBRyxLQUFLLENBQUM7UUFDakMsc0VBQXNFO1FBQzdELG9CQUFlLEdBQUcsdUJBQXVCLENBQUM7UUFDbkQsMkRBQTJEO1FBQ2xE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YTtzQkFBdEIsTUFBTTtnQkFDRyxhQUFhO3NCQUF0QixNQUFNO2dCQUNHLGNBQWM7c0JBQXZCLE1BQU07Z0JBQ0csY0FBYztzQkFBdkIsTUFBTTtnQkFDRyxNQUFNO3NCQUFmLE1BQU07Z0JBQ0csT0FBTztzQkFBaEIsTUFBTTtnQkFDRyxtQkFBbUI7c0JBQTVCLE1BQU07Z0JBQ0csa0JBQWtCO3NCQUEzQixNQUFNO2dCQUVHLFVBQVU7c0JBQW5CLE1BQU07Z0JBRUcsZUFBZTtzQkFBeEIsTUFBTTtnQkFFRyxnQkFBZ0I7c0JBQXpCLE1BQU07Z0JBQ0csWUFBWTtzQkFBckIsTUFBTTtnQkFFRyxVQUFVO3NCQUFuQixNQUFNO2dCQUVHLFVBQVU7c0JBQW5CLE1BQU07Z0JBRUcsY0FBYztzQkFBdkIsTUFBTTtnQkFFRyxlQUFlO3NCQUF4QixNQUFNO2dCQUVHLFVBQVU7c0JBQW5CLE1BQU07Z0JBRUcsZUFBZTtzQkFBeEIsTUFBTTtnQkFFRyxjQUFjO3NCQUF2QixNQUFNO2dCQUVHLGNBQWM7c0JBQXZCLE1BQU07Z0JBQ0cscUJBQXFCO3NCQUE5QixNQUFNO2dCQUNHLGVBQWU7c0JBQXhCLE1BQU07Z0JBQ0csd0JBQXdCO3NCQUFqQyxNQUFNIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgQ29tcG9uZW50LCBFdmVudEVtaXR0ZXIsIElucHV0LCBPbkNoYW5nZXMsIE91dHB1dCwgU2ltcGxlQ2hhbmdlcyB9IGZyb20gJ0Bhbmd1bGFyL2NvcmUnO1xuaW1wb3J0IHsgTWVudUl0ZW0gfSBmcm9tICdwcmltZW5nL2FwaSc7XG5cbmltcG9ydCB7IElJbWFnZUluZm8gfSBmcm9tICcuLi9jb250cmFjdHMvaW1hZ2UuY29udHJhY3QnO1xuaW1wb3J0IHtcbiAgUGxvdFR5cGUsXG4gIFBsb3RUeXBlRGVzY3JpcHRvcixcbiAgaXNOYXBhcmkzZCxcbiAgaXNOYXBhcmlJc29zdXJmYWNlLFxuICBpc05hcGFyaVN1cmZhY2UsXG4gIGlzTmFwYXJpU2NhdHRlcixcbiAgTkFQQVJJX0RFQ0lNQVRFX09QVElPTlMsXG4gIE5BUEFSSV9ERUZBVUxUX0RFQ0lNQVRFLFxufSBmcm9tICcuLi9jb250cmFjdHMvcGxvdC10eXBlJztcbmltcG9ydCB7IFRvb2xiYXJUb29sVmlzaWJpbGl0eSwgQUxMX1RPT0xCQVJfVE9PTFMgfSBmcm9tICcuLi9jb250cmFjdHMvdG9vbGJhci1jb25maWcnO1xuXG4vKipcbiAqIFByZXNlbnRhdGlvbmFsIHRvb2xiYXIgZm9yIHRoZSBwbG90dGluZyB2aWV3cG9ydC5cbiAqXG4gKiBSZW5kZXJzIHRoZSBwbG90LXR5cGUgc2VsZWN0b3IsIGNvbG9ybWFwL0xVVCBjb250cm9scywgc3RhY2sgbmF2aWdhdGlvbixcbiAqIGltYWdlIGFjdGlvbnMgKGRvd25sb2FkIC8gYXV0b3NjYWxlIC8gcGlwZWxpbmUpLCB2aWV3cG9ydCB6b29tL3BhbiwgdGhlXG4gKiBTdXJmYWNlLTNEIGNhbWVyYSBjb250cm9scywgYW5kIHRoZSByZWdpb24tZHJhd2luZyB0b29scyDigJQgcGx1cyB0aGUgcGxvdHRpbmdcbiAqIGhlbHAgZGlhbG9nLiBJdCBvd25zIG5vIHJlbmRlcmluZyBvciB2aXN1YWxpemF0aW9uIHN0YXRlOiB0aGUgaG9zdFxuICogKFZpc3VhbGl6ZXJDb21wb25lbnQpIHN0YXlzIHRoZSBvcmNoZXN0cmF0b3IsIHBhc3Npbmcgc3RhdGUgaW4gdmlhIGBASW5wdXRgIGFuZFxuICogaGFuZGxpbmcgZXZlcnkgYWN0aW9uIHZpYSBgQE91dHB1dGAuIFRoZSBzYW1lIGFjdGlvbnMgYXJlIGFsc28gZHJpdmVuIGJ5IHRoZVxuICogZGlhZ3JhbSdzIHJpZ2h0LWNsaWNrIGNvbnRleHQgbWVudSBhbmQga2V5Ym9hcmQgc2hvcnRjdXRzLCBzbyBrZWVwaW5nIHRoZVxuICogaGFuZGxlcnMgaW4gdGhlIGhvc3QgYXZvaWRzIGR1cGxpY2F0aW5nIHRoYXQgbG9naWMuXG4gKi9cbkBDb21wb25lbnQoe1xuICAvLyBDYW5vbmljYWwgcHJlZml4ZWQgc2VsZWN0b3IgZmlyc3Q7IHRoZSB1bnByZWZpeGVkIG9yaWdpbmFsIGlzIGtlcHQgYXMgYW5cbiAgLy8gYWxpYXMgZm9yIG9uZSByZWxlYXNlIChwcmUtcHVibGljYXRpb24gYmFjay1jb21wYXQpLlxuICBzZWxlY3RvcjogJ3Bsb3R0aW5nLXRvb2xiYXInLFxuICB0ZW1wbGF0ZVVybDogJy4vdG9vbGJhci5jb21wb25lbnQuaHRtbCcsXG4gIHN0eWxlVXJsczogWycuL3Rvb2xiYXIuY29tcG9uZW50LnNjc3MnXSxcbn0pXG5leHBvcnQgY2xhc3MgVG9vbGJhckNvbXBvbmVudCBpbXBsZW1lbnRzIE9uQ2hhbmdlcyB7XG4gIC8qKiBDdXJyZW50IGltYWdlIChnYXRlcyB3aGljaCBjb250cm9sIGdyb3VwcyBhcmUgc2hvd24pLiAqL1xuICBASW5wdXQoKSBpbWFnZUluZm86IElJbWFnZUluZm8gfCB1bmRlZmluZWQ7XG4gIC8qKiBQbG90IHR5cGVzIHRoZSBhY3RpdmUgYmFja2VuZCBhZHZlcnRpc2VzLiAqL1xuICBASW5wdXQoKSBwbG90VHlwZU9wdGlvbnM6IFBsb3RUeXBlRGVzY3JpcHRvcltdID0gW107XG4gIEBJbnB1dCgpIHNlbGVjdGVkUGxvdFR5cGU6IFBsb3RUeXBlID0gUGxvdFR5cGUuSU1BR0U7XG4gIC8qKiBJc29zdXJmYWNlIGJhbmQgYXMgYSAw4oCTMjU1IHNsaWRlciBwb3NpdGlvbiAobWFwcGVkIG9udG8gdGhlIHZvbHVtZSdzIHJlYWxcbiAgICogIGludGVuc2l0eSByYW5nZSBieSB0aGUgcmVuZGVyZXIpLiBEZWZhdWx0cyB0byB0aGUgZnVsbCByYW5nZS4gKi9cbiAgQElucHV0KCkgaXNvUmFuZ2U6IG51bWJlcltdID0gWzAsIDI1NV07XG4gIEBJbnB1dCgpIG1heEluZGV4ID0gMDtcbiAgQElucHV0KCkgekluZGV4ID0gMDtcbiAgLyoqIFRydWUgZm9yIGFueSAyRCAobm9uIFN1cmZhY2UtM0QpIHBsb3QgdHlwZS4gKi9cbiAgQElucHV0KCkgaXNIZWF0bWFwID0gdHJ1ZTtcbiAgQElucHV0KCkgYWN0aXZlRHJhZ01vZGU6IHN0cmluZyB8IG51bGwgPSBudWxsO1xuICBASW5wdXQoKSBhY3RpdmVTdXJmYWNlM2RNb2RlID0gJ3R1cm50YWJsZSc7XG4gIC8qKiBXaGV0aGVyIHRoZSBuYXBhcmkgM0QgYXhlcy9zY2FsZSBnaXptbyBpcyBjdXJyZW50bHkgc2hvd24gKGRyaXZlcyB0aGUgdG9nZ2xlJ3MgbG9vaykuICovXG4gIEBJbnB1dCgpIGF4ZXNWaXNpYmxlID0gdHJ1ZTtcbiAgQElucHV0KCkgd2lyZWZyYW1lQWN0aXZlID0gZmFsc2U7XG4gIC8qKiBBY3RpdmUgbmFwYXJpIDNEIGRlY2ltYXRlIGZhY3RvciAoMSA9IEZ1bGwg4oCmIDggPSDihZs7IGRlZmF1bHQgwr0pLiAqL1xuICBASW5wdXQoKSByZXNvbHV0aW9uU2NhbGUgPSBOQVBBUklfREVGQVVMVF9ERUNJTUFURTtcbiAgLyoqIERlY2ltYXRlLWZhY3RvciBvcHRpb25zIGZvciB0aGUgUmVzb2x1dGlvbiBkcm9wZG93bi4gKi9cbiAgcmVhZG9ubHkgZGVjaW1hdGVPcHRpb25zID0gTkFQQVJJX0RFQ0lNQVRFX09QVElPTlM7XG4gIEBJbnB1dCgpIHdhbmRTZW5zaXRpdml0eSA9IDIuMDtcbiAgQElucHV0KCkgYnJ1c2hTaXplID0gNDA7XG4gIEBJbnB1dCgpIHZlcnRleEVyYXNlclJhZGl1cyA9IDIwO1xuICAvKiogV2hldGhlciBhIHJlZ2lvbiBhY3Rpb24gaXMgYXZhaWxhYmxlIHRvIHVuZG8gKGppdC11aSM4NSkuIEdyZXlzIG91dCB0aGVcbiAgICogIFVuZG8gYnV0dG9uIHdoZW4gZmFsc2UgKG5vdGhpbmcgZG9uZSB5ZXQsIG9yIGFsbCBzdGVwcyBhbHJlYWR5IHVuZG9uZSkuICovXG4gIEBJbnB1dCgpIGNhblVuZG8gPSBmYWxzZTtcbiAgLyoqIFdoZXRoZXIgYW4gdW5kb25lIHJlZ2lvbiBhY3Rpb24gaXMgYXZhaWxhYmxlIHRvIHJlZG8gKGppdC11aSM4NSkuIEdyZXlzXG4gICAqICBvdXQgdGhlIFJlZG8gYnV0dG9uIHdoZW4gZmFsc2UuICovXG4gIEBJbnB1dCgpIGNhblJlZG8gPSBmYWxzZTtcbiAgQElucHV0KCkgc3RhY2tPcHRpb25zOiB7IG5hbWU6IHN0cmluZzsgdmFsOiBzdHJpbmcgfVtdID1cbiAgICBbeyBuYW1lOiAnU2luZ2xlIGltYWdlJywgdmFsOiAnZmFsc2UnIH0sIHsgbmFtZTogJ1N0YWNrJywgdmFsOiAndHJ1ZScgfV07XG4gIC8qKiBXaGljaCB0b29sYmFyIGdyb3VwcyB0byBzaG93LiBEZWZhdWx0cyB0byB0aGUgZnVsbCB0b29sYmFyOyB0aGUgaG9zdCBmb3J3YXJkc1xuICAgKiAgdGhlIGNvbnN1bWVyJ3MgY2hvaWNlIChlLmcuIHRoZSBwaXBlbGluZSBzaG93cyBvbmx5IHpvb20gKyByZWdpb24gdG9vbHMpLiAqL1xuICBASW5wdXQoKSB0b29sczogUmVxdWlyZWQ8VG9vbGJhclRvb2xWaXNpYmlsaXR5PiA9IEFMTF9UT09MQkFSX1RPT0xTO1xuICAvKiogQ3VycmVudCBpbWFnZS1zbW9vdGhpbmcgc3RhdGUgKGRyaXZlcyB0aGUgU21vb3RoZW4gdG9nZ2xlIGJ1dHRvbiBsb29rKS5cbiAgICogIGBmYWxzZWAgPSBuZWFyZXN0LW5laWdoYm91ciAoY3Jpc3AgcmF3IHBpeGVscyksIHRoZSBkZWZhdWx0LiAqL1xuICBASW5wdXQoKSBpbWFnZVNtb290aGluZ0VuYWJsZWQgPSBmYWxzZTtcbiAgLyoqIFNBTSBtb2RlbCBwaWNrZXIgb3B0aW9ucyArIGN1cnJlbnQgc2VsZWN0aW9uIChqaXQtdWkjOTAgUDEpLiAqL1xuICBASW5wdXQoKSBzYW1Nb2RlbHM6IHsgaWQ6IHN0cmluZzsgbGFiZWw6IHN0cmluZyB9W10gPSBbXTtcbiAgQElucHV0KCkgc2FtTW9kZWxJZCA9ICcnO1xuICAvKiogWU9MTyBjaGVja3BvaW50cyBvZmZlcmVkIGJ5IHRoZSBob3N0J3MgcmVnaXN0cnksIGFuZCB0aGUgYWN0aXZlIG9uZS4gKi9cbiAgQElucHV0KCkgeW9sb01vZGVsczogeyBpZDogc3RyaW5nOyBsYWJlbDogc3RyaW5nIH1bXSA9IFtdO1xuICBASW5wdXQoKSB5b2xvTW9kZWxJZCA9ICcnO1xuXG4gIEBPdXRwdXQoKSBzZWxlY3RQbG90VHlwZSA9IG5ldyBFdmVudEVtaXR0ZXI8UGxvdFR5cGU+KCk7XG4gIC8qKiBJbnRlbnNpdHkgKExJTkUpIG1vZGU6IGFkZCBhbm90aGVyIGNvbG9yZWQgbGluZSBST0kgKyBpbnNldCB0cmFjZS4gKi9cbiAgQE91dHB1dCgpIGFkZFByb2ZpbGVMaW5lID0gbmV3IEV2ZW50RW1pdHRlcjx2b2lkPigpO1xuICAvKiogVG9nZ2xlIGltYWdlIHNtb290aGluZyAoYmlsaW5lYXIpIHZzIG5lYXJlc3QtbmVpZ2hib3VyIChjcmlzcCBwaXhlbHMpLiAqL1xuICBAT3V0cHV0KCkgdG9nZ2xlSW1hZ2VTbW9vdGhpbmcgPSBuZXcgRXZlbnRFbWl0dGVyPHZvaWQ+KCk7XG4gIC8vIFByaW1lTkcgc2xpZGVyL2lucHV0TnVtYmVyIGNoYW5nZSBldmVudHMgY2Fycnkgb3B0aW9uYWwgdmFsdWVzLCBtaXJyb3JlZCBieVxuICAvLyB0aGUgaG9zdCBoYW5kbGVycyAod2hpY2ggYWNjZXB0IGB8IHVuZGVmaW5lZGApLlxuICBAT3V0cHV0KCkgaXNvUmFuZ2VDaGFuZ2UgPSBuZXcgRXZlbnRFbWl0dGVyPG51bWJlcltdIHwgdW5kZWZpbmVkPigpO1xuICAvKiogT3BlbiB0aGUgQ2hhbm5lbHMgJiBIaXN0b2dyYW0gZGlhbG9nIChicmlnaHRuZXNzL2NvbnRyYXN0LCBjb2xvcm1hcCwg4oCmKS4gKi9cbiAgQE91dHB1dCgpIG9wZW5DaGFubmVsSGlzdG9ncmFtID0gbmV3IEV2ZW50RW1pdHRlcjx2b2lkPigpO1xuICBAT3V0cHV0KCkgb3BlblJlZ2lvbkVkaXRvciA9IG5ldyBFdmVudEVtaXR0ZXI8dm9pZD4oKTtcbiAgQE91dHB1dCgpIHNlbGVjdFN0YWNrT3B0aW9uID0gbmV3IEV2ZW50RW1pdHRlcjx7IG5hbWU6IHN0cmluZzsgdmFsOiBzdHJpbmcgfT4oKTtcbiAgQE91dHB1dCgpIHpTY3J1YiA9IG5ldyBFdmVudEVtaXR0ZXI8bnVtYmVyIHwgdW5kZWZpbmVkPigpO1xuICBAT3V0cHV0KCkgelNsaWRlID0gbmV3IEV2ZW50RW1pdHRlcjxudW1iZXIgfCB1bmRlZmluZWQ+KCk7XG4gIEBPdXRwdXQoKSB6SW5kZXhJbnB1dCA9IG5ldyBFdmVudEVtaXR0ZXI8bnVtYmVyPigpO1xuICBAT3V0cHV0KCkgcmVsb2FkQW5kUGxvdCA9IG5ldyBFdmVudEVtaXR0ZXI8dm9pZD4oKTtcbiAgQE91dHB1dCgpIGRvd25sb2FkSW1hZ2UgPSBuZXcgRXZlbnRFbWl0dGVyPHZvaWQ+KCk7XG4gIEBPdXRwdXQoKSBhdXRvc2NhbGVJbWFnZSA9IG5ldyBFdmVudEVtaXR0ZXI8dm9pZD4oKTtcbiAgQE91dHB1dCgpIHRvZ2dsZURyYWdNb2RlID0gbmV3IEV2ZW50RW1pdHRlcjxzdHJpbmc+KCk7XG4gIEBPdXRwdXQoKSB6b29tSW4gPSBuZXcgRXZlbnRFbWl0dGVyPHZvaWQ+KCk7XG4gIEBPdXRwdXQoKSB6b29tT3V0ID0gbmV3IEV2ZW50RW1pdHRlcjx2b2lkPigpO1xuICBAT3V0cHV0KCkgdG9nZ2xlU3VyZmFjZTNkTW9kZSA9IG5ldyBFdmVudEVtaXR0ZXI8c3RyaW5nPigpO1xuICBAT3V0cHV0KCkgcmVzZXRTdXJmYWNlQ2FtZXJhID0gbmV3IEV2ZW50RW1pdHRlcjx2b2lkPigpO1xuICAvKiogVG9nZ2xlIHRoZSBuYXBhcmkgM0QgY29vcmRpbmF0ZS1heGVzIC8gc2NhbGUgZ2l6bW8gb24vb2ZmLiAqL1xuICBAT3V0cHV0KCkgdG9nZ2xlQXhlcyA9IG5ldyBFdmVudEVtaXR0ZXI8dm9pZD4oKTtcbiAgLyoqIFRvZ2dsZSB0aGUgbmFwYXJpIHN1cmZhY2Ugd2lyZWZyYW1lIChlZGdlcykgb24vb2ZmLiAqL1xuICBAT3V0cHV0KCkgdG9nZ2xlV2lyZWZyYW1lID0gbmV3IEV2ZW50RW1pdHRlcjx2b2lkPigpO1xuICAvKiogQ2hhbmdlIHRoZSBuYXBhcmkgM0QgZGVjaW1hdGUgZmFjdG9yIChyZWxvYWRzIGF0IHRoZSBuZXcgcmVzb2x1dGlvbikuICovXG4gIEBPdXRwdXQoKSBzZWxlY3RSZXNvbHV0aW9uID0gbmV3IEV2ZW50RW1pdHRlcjxudW1iZXI+KCk7XG4gIEBPdXRwdXQoKSBkZWxldGVSZWdpb24gPSBuZXcgRXZlbnRFbWl0dGVyPHZvaWQ+KCk7XG4gIC8qKiBVbmRvIHRoZSBsYXN0IHJlZ2lvbiBhY3Rpb24gKGppdC11aSM4NSkuICovXG4gIEBPdXRwdXQoKSB1bmRvUmVnaW9uID0gbmV3IEV2ZW50RW1pdHRlcjx2b2lkPigpO1xuICAvKiogUmVkbyB0aGUgbGFzdCB1bmRvbmUgcmVnaW9uIGFjdGlvbiAoaml0LXVpIzg1KS4gKi9cbiAgQE91dHB1dCgpIHJlZG9SZWdpb24gPSBuZXcgRXZlbnRFbWl0dGVyPHZvaWQ+KCk7XG4gIC8qKiBSdW4gYm94LXByb21wdGVkIFNBTSBzZWdtZW50YXRpb24gb24gdGhlIGRyYXduIHJlY3RhbmdsZXMgKGppdC11aSM5MCkuICovXG4gIEBPdXRwdXQoKSBzZWdtZW50UmVnaW9ucyA9IG5ldyBFdmVudEVtaXR0ZXI8dm9pZD4oKTtcbiAgLyoqIFJ1biBjZWxscG9zZS1TQU0gKGF1dG8pIGluc2lkZSBlYWNoIGRyYXduIHJlY3RhbmdsZSdzIGNyb3AgKGppdC11aSM5MCkuICovXG4gIEBPdXRwdXQoKSBzZWdtZW50Q2VsbHBvc2UgPSBuZXcgRXZlbnRFbWl0dGVyPHZvaWQ+KCk7XG4gIC8qKiBSdW4gWU9MTyBkZXRlY3Rpb24gb3ZlciB0aGUgY3VycmVudCB2aWV3LiAqL1xuICBAT3V0cHV0KCkgZGV0ZWN0WW9sbyA9IG5ldyBFdmVudEVtaXR0ZXI8dm9pZD4oKTtcbiAgLyoqIFBpY2sgdGhlIFlPTE8gY2hlY2twb2ludC4gKi9cbiAgQE91dHB1dCgpIHlvbG9Nb2RlbENoYW5nZSA9IG5ldyBFdmVudEVtaXR0ZXI8c3RyaW5nPigpO1xuICAvKiogT3BlbiB0aGUgWU9MTyBwYXJhbWV0ZXIgZGlhbG9nLiAqL1xuICBAT3V0cHV0KCkgb3BlbllvbG9QYXJhbXMgPSBuZXcgRXZlbnRFbWl0dGVyPHZvaWQ+KCk7XG4gIC8qKiBTQU0gbW9kZWwgcGlja2VyIChqaXQtdWkjOTAgUDEpLiAqL1xuICBAT3V0cHV0KCkgc2FtTW9kZWxDaGFuZ2UgPSBuZXcgRXZlbnRFbWl0dGVyPHN0cmluZz4oKTtcbiAgQE91dHB1dCgpIHdhbmRTZW5zaXRpdml0eUNoYW5nZSA9IG5ldyBFdmVudEVtaXR0ZXI8bnVtYmVyIHwgdW5kZWZpbmVkPigpO1xuICBAT3V0cHV0KCkgYnJ1c2hTaXplQ2hhbmdlID0gbmV3IEV2ZW50RW1pdHRlcjxudW1iZXIgfCB1bmRlZmluZWQ+KCk7XG4gIEBPdXRwdXQoKSB2ZXJ0ZXhFcmFzZXJSYWRpdXNDaGFuZ2UgPSBuZXcgRXZlbnRFbWl0dGVyPG51bWJlciB8IHVuZGVmaW5lZD4oKTtcblxuICAvKiogU2xpZGVycycgYm91bmRzIOKAlCB0b29sYmFyIFVJIGNvbnN0YW50cyAodGhlIHZhbHVlcyBhcmUgaG9zdC1vd25lZCkuICovXG4gIHJlYWRvbmx5IHdhbmRTZW5zaXRpdml0eU1pbiA9IDAuNTtcbiAgcmVhZG9ubHkgd2FuZFNlbnNpdGl2aXR5TWF4ID0gMTAuMDtcbiAgcmVhZG9ubHkgd2FuZFNlbnNpdGl2aXR5U3RlcCA9IDAuMTtcbiAgcmVhZG9ubHkgYnJ1c2hTaXplTWluID0gNTtcbiAgcmVhZG9ubHkgYnJ1c2hTaXplTWF4ID0gMzAwO1xuICByZWFkb25seSBicnVzaFNpemVTdGVwID0gNTtcbiAgcmVhZG9ubHkgdmVydGV4RXJhc2VyUmFkaXVzTWluID0gNTtcbiAgcmVhZG9ubHkgdmVydGV4RXJhc2VyUmFkaXVzTWF4ID0gMzAwO1xuICByZWFkb25seSB2ZXJ0ZXhFcmFzZXJSYWRpdXNTdGVwID0gNTtcbiAgcmVhZG9ubHkgaXNvVmFsdWVNaW4gPSAwO1xuICByZWFkb25seSBpc29WYWx1ZU1heCA9IDI1NTtcbiAgcmVhZG9ubHkgaXNvVmFsdWVTdGVwID0gMTtcblxuICBkaXNwbGF5SGVscERpYWxvZyA9IGZhbHNlO1xuXG4gIC8qKiBNb2RlbCBwaWNrZXIgZm9yIHRoZSBTZWdtZW50IGJ1dHRvbidzIGRyb3Bkb3duIG1lbnUuIFRoZSBhY3RpdmUgbW9kZWxcbiAgICogIChgc2FtTW9kZWxJZGApIGlzIG1hcmtlZCB3aXRoIGEgY2hlY2s7IHNlbGVjdGluZyBhbiBpdGVtIGVtaXRzXG4gICAqICBgc2FtTW9kZWxDaGFuZ2VgIChqaXQtdWkjOTAgUDEpLlxuICAgKlxuICAgKiAgSGVsZCBhcyBhIHN0YWJsZSBhcnJheSAocmVidWlsdCBvbmx5IHdoZW4gYHNhbU1vZGVsc2AvYHNhbU1vZGVsSWRgIGNoYW5nZSksXG4gICAqICBOT1QgYSBnZXR0ZXI6IGEgZ2V0dGVyIHJldHVybnMgYSBmcmVzaCBhcnJheSB3aXRoIG5ldyBgY29tbWFuZGAgY2xvc3VyZXMgb25cbiAgICogIGV2ZXJ5IGNoYW5nZS1kZXRlY3Rpb24gdGljaywgd2hpY2ggbWFrZXMgdGhlIGJvdW5kIGBwLW1lbnVgIG92ZXJsYXkgcmVidWlsZFxuICAgKiAgaXRzIERPTSBtaWQtaW50ZXJhY3Rpb24gYW5kIHN3YWxsb3cgdGhlIGNsaWNrIG9uIGEgbWVudSBpdGVtLiAqL1xuICBzYW1NZW51SXRlbXM6IE1lbnVJdGVtW10gPSBbXTtcbiAgLyoqIFNhbWUgc3RhYmxlLWFycmF5IHJlYXNvbmluZyBhcyB7QGxpbmsgc2FtTWVudUl0ZW1zfS4gKi9cbiAgeW9sb01lbnVJdGVtczogTWVudUl0ZW1bXSA9IFtdO1xuXG4gIC8qKiBSZWJ1aWxkIHRoZSBTQU0gbW9kZWwgbWVudSB3aGVuIHRoZSBtb2RlbCBsaXN0IG9yIGFjdGl2ZSBzZWxlY3Rpb25cbiAgICogIGNoYW5nZXMgKGtlZXBzIHRoZSBhcnJheSByZWZlcmVuY2Ugc3RhYmxlIGFjcm9zcyBvdGhlciBDRCB0aWNrcykuICovXG4gIG5nT25DaGFuZ2VzKGNoYW5nZXM6IFNpbXBsZUNoYW5nZXMpOiB2b2lkIHtcbiAgICBpZiAoY2hhbmdlc1snc2FtTW9kZWxzJ10gfHwgY2hhbmdlc1snc2FtTW9kZWxJZCddKSB7XG4gICAgICB0aGlzLnNhbU1lbnVJdGVtcyA9IHRoaXMuc2FtTW9kZWxzLm1hcCgobSkgPT4gKHtcbiAgICAgICAgbGFiZWw6IG0ubGFiZWwsXG4gICAgICAgIGljb246IG0uaWQgPT09IHRoaXMuc2FtTW9kZWxJZCA/ICdwaSBwaS1jaGVjaycgOiAncGkgcGktZncnLFxuICAgICAgICBjb21tYW5kOiAoKSA9PiB0aGlzLnNhbU1vZGVsQ2hhbmdlLmVtaXQobS5pZCksXG4gICAgICB9KSk7XG4gICAgfVxuICAgIGlmIChjaGFuZ2VzWyd5b2xvTW9kZWxzJ10gfHwgY2hhbmdlc1sneW9sb01vZGVsSWQnXSkge1xuICAgICAgdGhpcy55b2xvTWVudUl0ZW1zID0gdGhpcy55b2xvTW9kZWxzLm1hcCgobSkgPT4gKHtcbiAgICAgICAgbGFiZWw6IG0ubGFiZWwsXG4gICAgICAgIGljb246IG0uaWQgPT09IHRoaXMueW9sb01vZGVsSWQgPyAncGkgcGktY2hlY2snIDogJ3BpIHBpLWZ3JyxcbiAgICAgICAgY29tbWFuZDogKCkgPT4gdGhpcy55b2xvTW9kZWxDaGFuZ2UuZW1pdChtLmlkKSxcbiAgICAgIH0pKTtcbiAgICB9XG4gIH1cblxuICAvKiogVGhlIEltYWdlIHBsb3QgdHlwZSByZW5kZXJzIGFzIGEgbmF0aXZlbHkgcGFuL3pvb20tYWJsZSByYXN0ZXIsIHNvIHRoZVxuICAgKiAgZ2VuZXJpYyB6b29tL3BhbiB0b29scyBhcmUgaGlkZGVuLiAqL1xuICBnZXQgaXNJbWFnZVZpZXcoKTogYm9vbGVhbiB7XG4gICAgcmV0dXJuIHRoaXMuc2VsZWN0ZWRQbG90VHlwZSA9PT0gUGxvdFR5cGUuSU1BR0U7XG4gIH1cblxuICAvKiogQSBwbG90LXR5cGUgaWNvbiBpcyBhIFByaW1lTkcgZm9udCBnbHlwaCAoZS5nLiBgcGkgcGktaW1hZ2VgKSByYXRoZXIgdGhhbiBhblxuICAgKiAgU1ZHIGFzc2V0IHBhdGgg4oCUIGRyaXZlcyB3aGljaCBlbGVtZW50IHRoZSBzZWxlY3RvciBpdGVtIHRlbXBsYXRlIHJlbmRlcnMuICovXG4gIGlzUGlJY29uKGljb246IHN0cmluZyB8IHVuZGVmaW5lZCk6IGJvb2xlYW4ge1xuICAgIHJldHVybiAhIWljb24gJiYgaWNvbi5zdGFydHNXaXRoKCdwaSAnKTtcbiAgfVxuXG4gIC8qKiBCYWNrZW5kcyB3aXRoIGEgdmVydGV4LWVkaXRpbmcgcmVnaW9uIG92ZXJsYXk6IE9TRCBJbWFnZSBhbmQgbmFwYXJpLWpzIFdlYkdQVSBpbWFnZVxuICAgKiAgKGppdC11aSMxMDIpLiBHYXRlcyB0aGUgcG9seWdvbiAvIGFkZC12ZXJ0ZXggLyBkZWxldGUtdmVydGV4IC8gQsOpemllci1jb252ZXJ0IHRvb2xzLiAqL1xuICBnZXQgc3VwcG9ydHNSZWdpb25WZXJ0ZXhUb29scygpOiBib29sZWFuIHtcbiAgICByZXR1cm4gKFxuICAgICAgdGhpcy5zZWxlY3RlZFBsb3RUeXBlID09PSBQbG90VHlwZS5JTUFHRSB8fFxuICAgICAgdGhpcy5zZWxlY3RlZFBsb3RUeXBlID09PSBQbG90VHlwZS5OQVBBUklfSU1BR0VcbiAgICApO1xuICB9XG5cbiAgLyoqIFBsb3QgdHlwZXMgdGhhdCBzY3J1YiBhIHotc3RhY2sgbGl2ZSAodGhlIHJlbmRlcmVyIHN3YXBzIHRoZSBzbGljZSBpbiBwbGFjZSk6IHRoZSBPU0QgSW1hZ2VcbiAgICogIHZpZXcsIHRoZSBuYXBhcmktanMgV2ViR1BVIGltYWdlLCBhbmQgdGhlIG5hcGFyaS1qcyBzdXJmYWNlIOKAlCB3aGljaCByZWJ1aWxkcyB0aGUgaGVpZ2h0IGZpZWxkXG4gICAqICBmcm9tIHRoZSBwaWNrZWQgc2xpY2UgKGppdC11aSMxMDIpLiBEcml2ZXMgdGhlIHBlci1zbGljZSBzbGlkZXIuICovXG4gIGdldCBzaG93c0xpdmVTbGljZVNjcnViYmVyKCk6IGJvb2xlYW4ge1xuICAgIHJldHVybiAoXG4gICAgICB0aGlzLnNlbGVjdGVkUGxvdFR5cGUgPT09IFBsb3RUeXBlLklNQUdFIHx8XG4gICAgICB0aGlzLnNlbGVjdGVkUGxvdFR5cGUgPT09IFBsb3RUeXBlLk5BUEFSSV9JTUFHRSB8fFxuICAgICAgaXNOYXBhcmlTdXJmYWNlKHRoaXMuc2VsZWN0ZWRQbG90VHlwZSkgfHxcbiAgICAgIGlzTmFwYXJpU2NhdHRlcih0aGlzLnNlbGVjdGVkUGxvdFR5cGUpXG4gICAgKTtcbiAgfVxuXG4gIC8qKiBBbnkgbmFwYXJpLWpzIFdlYkdQVSBwbG90IHR5cGUuIFRoZSBzaW5nbGUtaW1hZ2Uvc3RhY2sgdG9nZ2xlICsgc2xpY2UtbnVtYmVyIGZpZWxkIGFyZVxuICAgKiAgUGxvdGx5LW9ubHkgc3RhY2sgY29udHJvbHMsIHNvIHRoZXkncmUgaGlkZGVuIGZvciBuYXBhcmkgKGppdC11aSMxMDIpLiAqL1xuICBnZXQgaXNOYXBhcmlNb2RlKCk6IGJvb2xlYW4ge1xuICAgIHJldHVybiAoXG4gICAgICB0aGlzLnNlbGVjdGVkUGxvdFR5cGUgPT09IFBsb3RUeXBlLk5BUEFSSV9JTUFHRSB8fFxuICAgICAgaXNOYXBhcmlTY2F0dGVyKHRoaXMuc2VsZWN0ZWRQbG90VHlwZSkgfHxcbiAgICAgIGlzTmFwYXJpM2QodGhpcy5zZWxlY3RlZFBsb3RUeXBlKVxuICAgICk7XG4gIH1cblxuICAvKiogSVNPU1VSRkFDRSAoUGxvdGx5IG9yIG5hcGFyaS1qcyBXZWJHUFUsIGVpdGhlciByZXNvbHV0aW9uKSBzaG93cyB0aGUgaXNvdmFsdWUgcmFuZ2Ugc2xpZGVyLiAqL1xuICBnZXQgaXNJc29zdXJmYWNlTW9kZSgpOiBib29sZWFuIHtcbiAgICByZXR1cm4gKFxuICAgICAgdGhpcy5zZWxlY3RlZFBsb3RUeXBlID09PSBQbG90VHlwZS5JU09TVVJGQUNFIHx8IGlzTmFwYXJpSXNvc3VyZmFjZSh0aGlzLnNlbGVjdGVkUGxvdFR5cGUpXG4gICAgKTtcbiAgfVxuXG4gIC8qKiBUaGUgbmFwYXJpLWpzIFdlYkdQVSBzdXJmYWNlIOKAlCBzaG93cyB0aGUgd2lyZWZyYW1lIHRvZ2dsZS4gKi9cbiAgZ2V0IGlzTmFwYXJpU3VyZmFjZU1vZGUoKTogYm9vbGVhbiB7XG4gICAgcmV0dXJuIGlzTmFwYXJpU3VyZmFjZSh0aGlzLnNlbGVjdGVkUGxvdFR5cGUpO1xuICB9XG5cbiAgLyoqIEFueSBuYXBhcmktanMgV2ViR1BVIDNEIHR5cGUgKHZvbHVtZS9pc29zdXJmYWNlL3N1cmZhY2UpIOKAlCBzaG93cyB0aGUgUmVzb2x1dGlvbiBjb250cm9sLiAqL1xuICBnZXQgaXNOYXBhcmkzZE1vZGUoKTogYm9vbGVhbiB7XG4gICAgcmV0dXJuIGlzTmFwYXJpM2QodGhpcy5zZWxlY3RlZFBsb3RUeXBlKTtcbiAgfVxuXG4gIC8qKiBJbnRlbnNpdHkgcHJvZmlsZSBsaW5lcyBhcmUgUmVnaW9uLWJhc2VkIGFuZCBhdmFpbGFibGUgaW4gdGhlIEhlYXRtYXAgYW5kXG4gICAqICBJbWFnZSBwbG90IHR5cGVzLCB3aGljaCBzaG93IHRoZSBJbnRlbnNpdHkgdG9vbCBncm91cC4gKi9cbiAgZ2V0IGlzSW50ZW5zaXR5Q2FwYWJsZSgpOiBib29sZWFuIHtcbiAgICByZXR1cm4gdGhpcy5zZWxlY3RlZFBsb3RUeXBlID09PSBQbG90VHlwZS5IRUFUTUFQIHx8IHRoaXMuc2VsZWN0ZWRQbG90VHlwZSA9PT0gUGxvdFR5cGUuSU1BR0U7XG4gIH1cblxuICBzaG93SGVscCgpOiB2b2lkIHtcbiAgICB0aGlzLmRpc3BsYXlIZWxwRGlhbG9nID0gdHJ1ZTtcbiAgfVxufVxuIiwiPHAtdG9vbGJhcj5cbiAgPGRpdiBjbGFzcz1cInAtdG9vbGJhci1ncm91cC1zdGFydCBmbGV4LXdyYXAgYWxpZ24taXRlbXMtY2VudGVyIGdhcC0xXCI+XG5cbiAgICA8IS0tIEhvc3QtcHJvdmlkZWQgY3VzdG9tIHRvb2xiYXIgYnV0dG9ucywgc2hvd24gZmlyc3QgKGUuZy4gYXBwLXNwZWNpZmljXG4gICAgICAgICBhY3Rpb25zIHRoZSB2aXN1YWxpemF0aW9uIHZpZXcgaXRzZWxmIGRvZXNuJ3Qgb3duKS4gLS0+XG4gICAgPG5nLWNvbnRlbnQ+PC9uZy1jb250ZW50PlxuXG4gICAgPCEtLSBQbG90IHR5cGU6IGF2YWlsYWJsZSBmb3IgYW55IGltYWdlIChncmF5c2NhbGUgb3IgUkdCKS4gLS0+XG4gICAgPG5nLWNvbnRhaW5lciAqbmdJZj1cImltYWdlSW5mbyAmJiB0b29scy5zcGVjaWFsVG9vbHNcIj5cbiAgICAgIDxzcGFuIGNsYXNzPVwidG9vbGJhci1zZXBhcmF0b3JcIj48L3NwYW4+XG4gICAgICA8cC1kcm9wZG93biBbb3B0aW9uc109XCJwbG90VHlwZU9wdGlvbnNcIiBvcHRpb25MYWJlbD1cImxhYmVsXCIgb3B0aW9uVmFsdWU9XCJ0eXBlXCJcbiAgICAgICAgICAgICAgICAgIFsobmdNb2RlbCldPVwic2VsZWN0ZWRQbG90VHlwZVwiIGFwcGVuZFRvPVwiYm9keVwiXG4gICAgICAgICAgICAgICAgICBwVG9vbHRpcD1cIlNlbGVjdCBwbG90IHR5cGVcIiB0b29sdGlwUG9zaXRpb249XCJib3R0b21cIlxuICAgICAgICAgICAgICAgICAgW3N0eWxlXT1cInsnbWluLXdpZHRoJzogJzExcmVtJ31cIlxuICAgICAgICAgICAgICAgICAgc3R5bGVDbGFzcz1cInBsb3Rtb2RlLWRyb3Bkb3duXCIgcGFuZWxTdHlsZUNsYXNzPVwicGxvdG1vZGUtcGFuZWxcIlxuICAgICAgICAgICAgICAgICAgKG9uQ2hhbmdlKT1cInNlbGVjdFBsb3RUeXBlLmVtaXQoJGV2ZW50LnZhbHVlKVwiPlxuICAgICAgICA8bmctdGVtcGxhdGUgbGV0LW9wdCBwVGVtcGxhdGU9XCJzZWxlY3RlZEl0ZW1cIj5cbiAgICAgICAgICA8c3BhbiBjbGFzcz1cInBsb3Rtb2RlLWl0ZW1cIiAqbmdJZj1cIm9wdFwiPlxuICAgICAgICAgICAgPGkgKm5nSWY9XCJpc1BpSWNvbihvcHQuaWNvbilcIiBbY2xhc3NdPVwib3B0Lmljb25cIj48L2k+XG4gICAgICAgICAgICA8aW1nICpuZ0lmPVwib3B0Lmljb24gJiYgIWlzUGlJY29uKG9wdC5pY29uKVwiIGNsYXNzPVwicGxvdG1vZGUtaWNvbi1pbWdcIiBbc3JjXT1cIm9wdC5pY29uXCIgYWx0PVwiXCIgLz5cbiAgICAgICAgICAgIDxzcGFuPnt7IG9wdC5sYWJlbCB9fTwvc3Bhbj5cbiAgICAgICAgICA8L3NwYW4+XG4gICAgICAgIDwvbmctdGVtcGxhdGU+XG4gICAgICAgIDxuZy10ZW1wbGF0ZSBsZXQtb3B0IHBUZW1wbGF0ZT1cIml0ZW1cIj5cbiAgICAgICAgICA8c3BhbiBjbGFzcz1cInBsb3Rtb2RlLWl0ZW1cIj5cbiAgICAgICAgICAgIDxpICpuZ0lmPVwiaXNQaUljb24ob3B0Lmljb24pXCIgW2NsYXNzXT1cIm9wdC5pY29uXCI+PC9pPlxuICAgICAgICAgICAgPGltZyAqbmdJZj1cIm9wdC5pY29uICYmICFpc1BpSWNvbihvcHQuaWNvbilcIiBjbGFzcz1cInBsb3Rtb2RlLWljb24taW1nXCIgW3NyY109XCJvcHQuaWNvblwiIGFsdD1cIlwiIC8+XG4gICAgICAgICAgICA8c3Bhbj57eyBvcHQubGFiZWwgfX08L3NwYW4+XG4gICAgICAgICAgPC9zcGFuPlxuICAgICAgICA8L25nLXRlbXBsYXRlPlxuICAgICAgPC9wLWRyb3Bkb3duPlxuICAgICAgPG5nLWNvbnRhaW5lciAqbmdJZj1cImlzSXNvc3VyZmFjZU1vZGVcIj5cbiAgICAgICAgPHNwYW4gY2xhc3M9XCJmbGV4IGFsaWduLWl0ZW1zLWNlbnRlciBteC0xIHRvb2xiYXItdGV4dFwiPklzbzwvc3Bhbj5cbiAgICAgICAgPHAtc2xpZGVyIFsobmdNb2RlbCldPVwiaXNvUmFuZ2VcIiBbcmFuZ2VdPVwidHJ1ZVwiXG4gICAgICAgICAgICAgICAgICBbbWluXT1cImlzb1ZhbHVlTWluXCIgW21heF09XCJpc29WYWx1ZU1heFwiIFtzdGVwXT1cImlzb1ZhbHVlU3RlcFwiXG4gICAgICAgICAgICAgICAgICAob25DaGFuZ2UpPVwiaXNvUmFuZ2VDaGFuZ2UuZW1pdCgkZXZlbnQudmFsdWVzKVwiXG4gICAgICAgICAgICAgICAgICBwVG9vbHRpcD1cIklzb3N1cmZhY2UgYmFuZCAoMOKAkzI1NSksIG1hcHBlZCBvbnRvIHRoZSB2b2x1bWUnc1xuICAgICAgICAgICAgICAgICAgYWN0dWFsIGludGVuc2l0eSByYW5nZS4gU3VyZmFjZXMgYXJlIGRyYXduIGZvciB2b3hlbHMgd2hvc2VcbiAgICAgICAgICAgICAgICAgIGludGVuc2l0eSBmYWxscyBpbiB0aGUgc2VsZWN0ZWQgYmFuZCDigJQgbmFycm93IGl0IHRvIGlzb2xhdGVcbiAgICAgICAgICAgICAgICAgIGZlYXR1cmVzLCB3aWRlbiBpdCB0byBzZWUgdGhlIHdob2xlIHN0cnVjdHVyZS5cIlxuICAgICAgICAgICAgICAgICAgW2VzY2FwZV09XCJmYWxzZVwiIHRvb2x0aXBQb3NpdGlvbj1cImJvdHRvbVwiXG4gICAgICAgICAgICAgICAgICBzdHlsZUNsYXNzPVwid2FuZC1zZW5zaXRpdml0eS1zbGlkZXJcIj48L3Atc2xpZGVyPlxuICAgICAgICA8c3BhbiBjbGFzcz1cImZsZXggYWxpZ24taXRlbXMtY2VudGVyIG14LTEgdG9vbGJhci10ZXh0IHdhbmQtc2Vuc2l0aXZpdHktdmFsdWVcIj5cbiAgICAgICAgICB7eyBpc29SYW5nZVswXSB9feKAk3t7IGlzb1JhbmdlWzFdIH19PC9zcGFuPlxuICAgICAgPC9uZy1jb250YWluZXI+XG4gICAgICA8IS0tIEludGVuc2l0eSB0b29scyAoSGVhdG1hcCAvIEltYWdlIG1vZGUpOiBhZGQgYW5vdGhlciBjb2xvcmVkIGxpbmUgUk9JLlxuICAgICAgICAgICBFYWNoIGFkZGVkIGxpbmUgZ2V0cyB0aGUgbmV4dCBicmlnaHQgcGFsZXR0ZSBjb2xvdXIgYW5kIGl0cyBvd24gaW5zZXRcbiAgICAgICAgICAgdHJhY2U7IHNlbGVjdCBhIGxpbmUgb24gdGhlIGltYWdlIGFuZCB1c2UgdGhlIHRyYXNoIHRvb2wgdG8gcmVtb3ZlIGl0XG4gICAgICAgICAgIChhbmQgaXRzIHRyYWNlKS4gVGhlIGxpbmVzIGFyZSBSZWdpb24tYmFzZWQgYnV0IGV4Y2x1ZGVkIGZyb20gdGhlXG4gICAgICAgICAgIFJlZ2lvbiBFZGl0b3IgYW5kIGV4cG9ydHMuIC0tPlxuICAgICAgPG5nLWNvbnRhaW5lciAqbmdJZj1cImlzSW50ZW5zaXR5Q2FwYWJsZVwiPlxuICAgICAgICA8c3BhbiBjbGFzcz1cInRvb2xiYXItc2VwYXJhdG9yXCI+PC9zcGFuPlxuICAgICAgICA8cC1idXR0b24gc2l6ZT1cInNtYWxsXCIgW3RleHRdPVwidHJ1ZVwiXG4gICAgICAgICAgICAgICAgICBwVG9vbHRpcD1cIkFkZCBhbiBpbnRlbnNpdHkgbGluZSAobmV3IGNvbG91cikuIENsaWNrIGEgbGluZSB0byBzZWxlY3QgaXQsXG4gICAgICAgICAgICAgICAgICB0aGVuIHVzZSB0aGUgdHJhc2ggdG9vbCB0byByZW1vdmUgaXQuXCJcbiAgICAgICAgICAgICAgICAgIFtlc2NhcGVdPVwiZmFsc2VcIiB0b29sdGlwUG9zaXRpb249XCJib3R0b21cIlxuICAgICAgICAgICAgICAgICAgKG9uQ2xpY2spPVwiYWRkUHJvZmlsZUxpbmUuZW1pdCgpXCI+XG4gICAgICAgICAgPGltZyBzcmM9XCJhc3NldHMvcGxvdHRpbmcvc3BsaW5lLWNoYXJ0LnN2Z1wiIGNsYXNzPVwidG9vbGJhci1pY29uLWRhcmtcIiBzdHlsZT1cIndpZHRoOiAxcmVtXCIvPlxuICAgICAgICA8L3AtYnV0dG9uPlxuICAgICAgPC9uZy1jb250YWluZXI+XG4gICAgPC9uZy1jb250YWluZXI+XG4gICAgPCEtLSBDaGFubmVscyAmIEhpc3RvZ3JhbTogYnJpZ2h0bmVzcy9jb250cmFzdCwgZ2FtbWEsIHBlci1jaGFubmVsIGRpc3BsYXksXG4gICAgICAgICBjb2xvcm1hcC9yZXZlcnNlL2ludmVydCDigJQgb3BlbnMgdGhlIGZsb2F0aW5nIGRpYWxvZy4gLS0+XG4gICAgPHAtYnV0dG9uICpuZ0lmPVwidG9vbHMuc3BlY2lhbFRvb2xzXCIgcFJpcHBsZSBzaXplPVwic21hbGxcIiBbdGV4dF09XCJ0cnVlXCJcbiAgICAgICAgICAgICAgcFRvb2x0aXA9XCJDaGFubmVscyAmYW1wOyBIaXN0b2dyYW0gKGJyaWdodG5lc3MvY29udHJhc3QsIGNvbG9ybWFwKVwiXG4gICAgICAgICAgICAgIHRvb2x0aXBQb3NpdGlvbj1cImJvdHRvbVwiXG4gICAgICAgICAgICAgIChvbkNsaWNrKT1cIm9wZW5DaGFubmVsSGlzdG9ncmFtLmVtaXQoKVwiPlxuICAgICAgPGltZyBzcmM9XCJhc3NldHMvcGxvdHRpbmcvaW52ZXJ0LnN2Z1wiIGNsYXNzPVwidG9vbGJhci1pY29uLWRhcmtcIiBzdHlsZT1cIndpZHRoOiAxcmVtXCIvPlxuICAgIDwvcC1idXR0b24+XG5cbiAgICA8IS0tIFNtb290aGVuIHRvZ2dsZTogYmlsaW5lYXIgc21vb3RoaW5nIHZzIG5lYXJlc3QtbmVpZ2hib3VyIHJhdyBwaXhlbHMgKEltYWdlXG4gICAgICAgICB2aWV3IG9ubHkg4oCUIGl0J3MgYW4gT3BlblNlYWRyYWdvbiBkcmF3ZXIgc2V0dGluZykuIEhpZ2hsaWdodGVkIHdoZW4gc21vb3RoaW5nIG9uLiAtLT5cbiAgICA8cC1idXR0b24gKm5nSWY9XCJ0b29scy5zcGVjaWFsVG9vbHMgJiYgaXNJbWFnZVZpZXdcIiBzaXplPVwic21hbGxcIlxuICAgICAgICAgICAgICBbdGV4dF09XCIhaW1hZ2VTbW9vdGhpbmdFbmFibGVkXCJcbiAgICAgICAgICAgICAgcFRvb2x0aXA9XCJTbW9vdGhlbiAodHVybiBvbiBpbWFnZSBzbW9vdGhpbmcpXCIgdG9vbHRpcFBvc2l0aW9uPVwiYm90dG9tXCJcbiAgICAgICAgICAgICAgKG9uQ2xpY2spPVwidG9nZ2xlSW1hZ2VTbW9vdGhpbmcuZW1pdCgpXCI+XG4gICAgICA8aW1nIHNyYz1cImFzc2V0cy9wbG90dGluZy9zbW9vdGguc3ZnXCIgc3R5bGU9XCJ3aWR0aDogMXJlbVwiXG4gICAgICAgICAgIFtjbGFzc109XCJpbWFnZVNtb290aGluZ0VuYWJsZWQgPyAndG9vbGJhci1pY29uLWFjdGl2ZScgOiAndG9vbGJhci1pY29uLWRhcmsnXCIvPlxuICAgIDwvcC1idXR0b24+XG5cbiAgICA8IS0tIFN0YWNrIGNvbnRyb2xzIC0tPlxuICAgIDxuZy1jb250YWluZXIgKm5nSWY9XCJpbWFnZUluZm8/LmlzU3RhY2tcIj5cbiAgICAgIDxzcGFuIGNsYXNzPVwidG9vbGJhci1zZXBhcmF0b3JcIj48L3NwYW4+XG4gICAgICA8IS0tIEltYWdlIHZpZXc6IHNjcnViIHotc2xpY2VzIGxpdmUgKHRoZSByZW5kZXJlciBzd2FwcyBzbGljZXMgaW4gcGxhY2UpLiAtLT5cbiAgICAgIDxuZy1jb250YWluZXIgKm5nSWY9XCJzaG93c0xpdmVTbGljZVNjcnViYmVyXCI+XG4gICAgICAgIDxzcGFuIGNsYXNzPVwiZmxleCBhbGlnbi1pdGVtcy1jZW50ZXIgbXgtMSB0b29sYmFyLXRleHRcIj5TbGljZTwvc3Bhbj5cbiAgICAgICAgPHAtc2xpZGVyIFsobmdNb2RlbCldPVwiekluZGV4XCIgW21pbl09XCIwXCIgW21heF09XCJtYXhJbmRleFwiXG4gICAgICAgICAgICAgICAgICAob25DaGFuZ2UpPVwielNjcnViLmVtaXQoJGV2ZW50LnZhbHVlKVwiIChvblNsaWRlRW5kKT1cInpTbGlkZS5lbWl0KCRldmVudC52YWx1ZSlcIlxuICAgICAgICAgICAgICAgICAgcFRvb2x0aXA9XCJTbGljZSB0aHJvdWdoIHRoZSBzdGFjayAo4oaQIC8g4oaSIGtleXMpXCIgdG9vbHRpcFBvc2l0aW9uPVwiYm90dG9tXCJcbiAgICAgICAgICAgICAgICAgIFtzdHlsZV09XCJ7J3dpZHRoJzonMTZyZW0nfVwiIHN0eWxlQ2xhc3M9XCJ3YW5kLXNlbnNpdGl2aXR5LXNsaWRlclwiPjwvcC1zbGlkZXI+XG4gICAgICAgIDxzcGFuIGNsYXNzPVwiZmxleCBhbGlnbi1pdGVtcy1jZW50ZXIgbXgtMSB0b29sYmFyLXRleHRcIj57e3pJbmRleH19IG9mIHt7bWF4SW5kZXh9fTwvc3Bhbj5cbiAgICAgIDwvbmctY29udGFpbmVyPlxuICAgICAgPCEtLSBIZWF0bWFwIHZpZXc6IHNpbmdsZS1pbWFnZSAoaGlnaC1kZWYgem9vbSkgdnMgc3RhY2sgKGZyYW1lIHNsaWRlcikuIFBsb3RseS1vbmx5IOKAlFxuICAgICAgICAgICBoaWRkZW4gZm9yIG5hcGFyaSwgd2hpY2ggaGFzIG5vIHNpbmdsZS9zdGFjayB0b2dnbGUgb3IgcGVyLWZyYW1lIGZldGNoLiAtLT5cbiAgICAgIDxuZy1jb250YWluZXIgKm5nSWY9XCIhc2hvd3NMaXZlU2xpY2VTY3J1YmJlciAmJiAhaXNOYXBhcmlNb2RlXCI+XG4gICAgICA8cC1idXR0b24gaWNvbj1cInBpIHBpLWltYWdlXCIgc2l6ZT1cInNtYWxsXCJcbiAgICAgICAgICAgICAgICBbdGV4dF09XCJpbWFnZUluZm8/LnNob3dTdGFja1wiXG4gICAgICAgICAgICAgICAgcFRvb2x0aXA9XCJTaW5nbGUgaW1hZ2UgbW9kZTogZW5hYmxlcyBoaWdoLWRlZiB6b29tXCIgdG9vbHRpcFBvc2l0aW9uPVwiYm90dG9tXCJcbiAgICAgICAgICAgICAgICAob25DbGljayk9XCJzZWxlY3RTdGFja09wdGlvbi5lbWl0KHN0YWNrT3B0aW9uc1swXSlcIj5cbiAgICAgIDwvcC1idXR0b24+XG4gICAgICA8cC1idXR0b24gaWNvbj1cInBpIHBpLWltYWdlc1wiIHNpemU9XCJzbWFsbFwiXG4gICAgICAgICAgICAgICAgW3RleHRdPVwiIWltYWdlSW5mbz8uc2hvd1N0YWNrXCJcbiAgICAgICAgICAgICAgICBwVG9vbHRpcD1cIlN0YWNrIG1vZGUsIGVuYWJsZXMgc2xpY2luZyB0aHJvdWdoIGltYWdlIGZyYW1lc1wiIHRvb2x0aXBQb3NpdGlvbj1cImJvdHRvbVwiXG4gICAgICAgICAgICAgICAgKG9uQ2xpY2spPVwic2VsZWN0U3RhY2tPcHRpb24uZW1pdChzdGFja09wdGlvbnNbMV0pXCI+XG4gICAgICA8L3AtYnV0dG9uPlxuICAgICAgPG5nLWNvbnRhaW5lciAqbmdJZj1cIiFpbWFnZUluZm8/LnNob3dTdGFja1wiPlxuICAgICAgICA8c3BhbiBjbGFzcz1cImZsZXggYWxpZ24taXRlbXMtY2VudGVyIG14LTEgdG9vbGJhci10ZXh0XCI+U2xpY2U8L3NwYW4+XG4gICAgICAgIDxkaXYgY2xhc3M9XCJwLWlucHV0Z3JvdXBcIiBzdHlsZT1cIndpZHRoOiBhdXRvXCI+XG4gICAgICAgICAgPHAtaW5wdXROdW1iZXIgc3R5bGVDbGFzcz1cInctM3JlbSB0b29sYmFyLWlucHV0XCJcblx0XHRcdFx0XHRwVG9vbHRpcD1cIlZpZXcgaW1hZ2UgaW4gc3RhY2sgKCYjeDIzQ0U7KVwiXG5cdFx0XHRcdFx0WyhuZ01vZGVsKV09XCJ6SW5kZXhcIiBbbWluXT1cIjBcIiBbbWF4XT1cIm1heEluZGV4XCJcbiAgICAgICAgICAgKG9uSW5wdXQpPVwiekluZGV4SW5wdXQuZW1pdCh6SW5kZXgpXCIgKGtleWRvd24uZW50ZXIpPVwicmVsb2FkQW5kUGxvdC5lbWl0KClcIi8+XG4gICAgICAgICAgPHAtYnV0dG9uIGljb249XCJwaSBwaS1yZWZyZXNoXCIgc2l6ZT1cInNtYWxsXCIgW3RleHRdPVwidHJ1ZVwiIChvbkNsaWNrKT1cInJlbG9hZEFuZFBsb3QuZW1pdCgpXCJcbiAgICAgICAgICAgICAgICAgICAgcFRvb2x0aXA9XCJQbG90IHRoaXMgc2xpY2VcIiB0b29sdGlwUG9zaXRpb249XCJib3R0b21cIj5cbiAgICAgICAgICA8L3AtYnV0dG9uPlxuICAgICAgICA8L2Rpdj5cbiAgICAgICAgPHNwYW4gY2xhc3M9XCJmbGV4IGFsaWduLWl0ZW1zLWNlbnRlciBteC0xIHRvb2xiYXItdGV4dFwiPm9mIHt7bWF4SW5kZXh9fTwvc3Bhbj5cbiAgICAgICAgPC9uZy1jb250YWluZXI+XG4gICAgICA8L25nLWNvbnRhaW5lcj5cbiAgICA8L25nLWNvbnRhaW5lcj5cblxuICAgIDwhLS0gSW1hZ2UgYWN0aW9ucyAoc3BlY2lhbCB0b29scykgLS0+XG4gICAgPG5nLWNvbnRhaW5lciAqbmdJZj1cInRvb2xzLnNwZWNpYWxUb29sc1wiPlxuICAgICAgPHNwYW4gY2xhc3M9XCJ0b29sYmFyLXNlcGFyYXRvclwiPjwvc3Bhbj5cbiAgICAgIDxwLWJ1dHRvbiBpY29uPVwicGkgcGktZG93bmxvYWRcIiBzaXplPVwic21hbGxcIiBbdGV4dF09XCJ0cnVlXCJcbiAgICAgICAgICAgICAgICBwVG9vbHRpcD1cIkRvd25sb2FkIGltYWdlIGFzIFBOR1wiIHRvb2x0aXBQb3NpdGlvbj1cImJvdHRvbVwiXG4gICAgICAgICAgICAgICAgKG9uQ2xpY2spPVwiZG93bmxvYWRJbWFnZS5lbWl0KClcIj5cbiAgICAgIDwvcC1idXR0b24+XG4gICAgICA8cC1idXR0b24gKm5nSWY9XCJpc0hlYXRtYXBcIiBpY29uPVwicGkgcGktd2luZG93LW1heGltaXplXCIgc2l6ZT1cInNtYWxsXCIgW3RleHRdPVwidHJ1ZVwiXG4gICAgICAgICAgICAgICAgcFRvb2x0aXA9XCJGaXQgdG8gdmlld1wiIHRvb2x0aXBQb3NpdGlvbj1cImJvdHRvbVwiXG4gICAgICAgICAgICAgICAgKG9uQ2xpY2spPVwiYXV0b3NjYWxlSW1hZ2UuZW1pdCgpXCI+XG4gICAgICA8L3AtYnV0dG9uPlxuICAgIDwvbmctY29udGFpbmVyPlxuXG4gICAgPCEtLSBab29tIHRvb2xzLiBQbG90bHkgcnViYmVyLWJhbmQgZHJhZy16b29tOiBvbiBPcGVuU2VhZHJhZ29uIHRoZSBib3gtem9vbVxuICAgICAgICAgY292ZXJzIHRoaXMgYW5kIGEgYmFyZSBkcmFnIHBhbnMgbmF0aXZlbHksIHNvIGl0J3MgaGlkZGVuIGluIGltYWdlIHZpZXcuIC0tPlxuICAgIDxuZy1jb250YWluZXIgKm5nSWY9XCJpc0hlYXRtYXAgJiYgdG9vbHMuem9vbVRvb2xzXCI+XG4gICAgICA8c3BhbiBjbGFzcz1cInRvb2xiYXItc2VwYXJhdG9yXCI+PC9zcGFuPlxuICAgICAgPHAtYnV0dG9uICpuZ0lmPVwiIWlzSW1hZ2VWaWV3XCIgaWNvbj1cInBpIHBpLXNlYXJjaFwiIHNpemU9XCJzbWFsbFwiXG4gICAgICAgICAgICAgICAgW3RleHRdPVwiYWN0aXZlRHJhZ01vZGUgIT09ICd6b29tJ1wiXG4gICAgICAgICAgICAgICAgcFRvb2x0aXA9XCJab29tIGRyYWdcIiB0b29sdGlwUG9zaXRpb249XCJib3R0b21cIlxuICAgICAgICAgICAgICAgIChvbkNsaWNrKT1cInRvZ2dsZURyYWdNb2RlLmVtaXQoJ3pvb20nKVwiPlxuICAgICAgPC9wLWJ1dHRvbj5cbiAgICAgIDxwLWJ1dHRvbiBzaXplPVwic21hbGxcIlxuICAgICAgICAgICAgICAgIFt0ZXh0XT1cImFjdGl2ZURyYWdNb2RlICE9PSAnem9vbVRvQm94J1wiXG4gICAgICAgICAgICAgICAgcFRvb2x0aXA9XCJab29tIGJveCAoPGtiZD5iPC9rYmQ+KVwiIFtlc2NhcGVdPVwiZmFsc2VcIiB0b29sdGlwUG9zaXRpb249XCJib3R0b21cIlxuICAgICAgICAgICAgICAgIChvbkNsaWNrKT1cInRvZ2dsZURyYWdNb2RlLmVtaXQoJ3pvb21Ub0JveCcpXCI+XG4gICAgICAgIDxpbWcgc3JjPVwiYXNzZXRzL3Bsb3R0aW5nL3pvb20tYm94LnN2Z1wiXG4gICAgICAgICAgICAgW2NsYXNzXT1cImFjdGl2ZURyYWdNb2RlID09PSAnem9vbVRvQm94JyA/ICd0b29sYmFyLWljb24tYWN0aXZlJyA6ICd0b29sYmFyLWljb24tZGFyaydcIlxuICAgICAgICAgICAgIHN0eWxlPVwid2lkdGg6IDFyZW1cIi8+XG4gICAgICA8L3AtYnV0dG9uPlxuICAgICAgPHAtYnV0dG9uIGljb249XCJwaSBwaS1hcnJvd3MtYWx0XCIgc2l6ZT1cInNtYWxsXCJcbiAgICAgICAgICAgICAgICBbdGV4dF09XCJhY3RpdmVEcmFnTW9kZSAhPT0gJ3BhbidcIlxuICAgICAgICAgICAgICAgIHBUb29sdGlwPVwiUGFuIHRoZSBpbWFnZSAoPGtiZD5wPC9rYmQ+KVwiIFtlc2NhcGVdPVwiZmFsc2VcIiB0b29sdGlwUG9zaXRpb249XCJib3R0b21cIlxuICAgICAgICAgICAgICAgIChvbkNsaWNrKT1cInRvZ2dsZURyYWdNb2RlLmVtaXQoJ3BhbicpXCI+XG4gICAgICA8L3AtYnV0dG9uPlxuICAgICAgPHAtYnV0dG9uIGljb249XCJwaSBwaS1zZWFyY2gtcGx1c1wiIHNpemU9XCJzbWFsbFwiIFt0ZXh0XT1cInRydWVcIlxuICAgICAgICAgICAgICAgIHBUb29sdGlwPVwiWm9vbSBpbiAoPGtiZD4rPC9rYmQ+KVwiIFtlc2NhcGVdPVwiZmFsc2VcIiB0b29sdGlwUG9zaXRpb249XCJib3R0b21cIlxuICAgICAgICAgICAgICAgIChvbkNsaWNrKT1cInpvb21Jbi5lbWl0KClcIj5cbiAgICAgIDwvcC1idXR0b24+XG4gICAgICA8cC1idXR0b24gaWNvbj1cInBpIHBpLXNlYXJjaC1taW51c1wiIHNpemU9XCJzbWFsbFwiIFt0ZXh0XT1cInRydWVcIlxuICAgICAgICAgICAgICAgIHBUb29sdGlwPVwiWm9vbSBvdXQgKDxrYmQ+LTwva2JkPilcIiBbZXNjYXBlXT1cImZhbHNlXCIgdG9vbHRpcFBvc2l0aW9uPVwiYm90dG9tXCJcbiAgICAgICAgICAgICAgICAob25DbGljayk9XCJ6b29tT3V0LmVtaXQoKVwiPlxuICAgICAgPC9wLWJ1dHRvbj5cbiAgICA8L25nLWNvbnRhaW5lcj5cblxuICAgIDwhLS0gU3VyZmFjZSAzRCBjb250cm9scyAtLT5cbiAgICA8bmctY29udGFpbmVyICpuZ0lmPVwiIWlzSGVhdG1hcFwiPlxuICAgICAgPHNwYW4gY2xhc3M9XCJ0b29sYmFyLXNlcGFyYXRvclwiPjwvc3Bhbj5cbiAgICAgIDxwLWJ1dHRvbiBpY29uPVwicGkgcGktc2VhcmNoXCIgc2l6ZT1cInNtYWxsXCJcbiAgICAgICAgICAgICAgICBbdGV4dF09XCJhY3RpdmVTdXJmYWNlM2RNb2RlICE9PSAnem9vbSdcIlxuICAgICAgICAgICAgICAgIHBUb29sdGlwPVwiWm9vbVwiIHRvb2x0aXBQb3NpdGlvbj1cImJvdHRvbVwiXG4gICAgICAgICAgICAgICAgKG9uQ2xpY2spPVwidG9nZ2xlU3VyZmFjZTNkTW9kZS5lbWl0KCd6b29tJylcIj5cbiAgICAgIDwvcC1idXR0b24+XG4gICAgICA8cC1idXR0b24gaWNvbj1cInBpIHBpLWFycm93cy1hbHRcIiBzaXplPVwic21hbGxcIlxuICAgICAgICAgICAgICAgIFt0ZXh0XT1cImFjdGl2ZVN1cmZhY2UzZE1vZGUgIT09ICdwYW4nXCJcbiAgICAgICAgICAgICAgICBwVG9vbHRpcD1cIlBhblwiIHRvb2x0aXBQb3NpdGlvbj1cImJvdHRvbVwiXG4gICAgICAgICAgICAgICAgKG9uQ2xpY2spPVwidG9nZ2xlU3VyZmFjZTNkTW9kZS5lbWl0KCdwYW4nKVwiPlxuICAgICAgPC9wLWJ1dHRvbj5cbiAgICAgIDxwLWJ1dHRvbiBpY29uPVwicGkgcGktZ2xvYmVcIiBzaXplPVwic21hbGxcIlxuICAgICAgICAgICAgICAgIFt0ZXh0XT1cImFjdGl2ZVN1cmZhY2UzZE1vZGUgIT09ICdvcmJpdCdcIlxuICAgICAgICAgICAgICAgIHBUb29sdGlwPVwiT3JiaXRhbCByb3RhdGlvblwiIHRvb2x0aXBQb3NpdGlvbj1cImJvdHRvbVwiXG4gICAgICAgICAgICAgICAgKG9uQ2xpY2spPVwidG9nZ2xlU3VyZmFjZTNkTW9kZS5lbWl0KCdvcmJpdCcpXCI+XG4gICAgICA8L3AtYnV0dG9uPlxuICAgICAgPHAtYnV0dG9uIGljb249XCJwaSBwaS1zeW5jXCIgc2l6ZT1cInNtYWxsXCJcbiAgICAgICAgICAgICAgICBbdGV4dF09XCJhY3RpdmVTdXJmYWNlM2RNb2RlICE9PSAndHVybnRhYmxlJ1wiXG4gICAgICAgICAgICAgICAgcFRvb2x0aXA9XCJUdXJudGFibGUgcm90YXRpb25cIiB0b29sdGlwUG9zaXRpb249XCJib3R0b21cIlxuICAgICAgICAgICAgICAgIChvbkNsaWNrKT1cInRvZ2dsZVN1cmZhY2UzZE1vZGUuZW1pdCgndHVybnRhYmxlJylcIj5cbiAgICAgIDwvcC1idXR0b24+XG4gICAgICA8cC1idXR0b24gaWNvbj1cInBpIHBpLWhvbWVcIiBzaXplPVwic21hbGxcIiBbdGV4dF09XCJ0cnVlXCJcbiAgICAgICAgICAgICAgICBwVG9vbHRpcD1cIlJlc2V0IGNhbWVyYSB0byBkZWZhdWx0XCIgdG9vbHRpcFBvc2l0aW9uPVwiYm90dG9tXCJcbiAgICAgICAgICAgICAgICAob25DbGljayk9XCJyZXNldFN1cmZhY2VDYW1lcmEuZW1pdCgpXCI+XG4gICAgICA8L3AtYnV0dG9uPlxuICAgICAgPCEtLSAzRCBjb29yZGluYXRlLWF4ZXMgLyBzY2FsZSBnaXptbyB0b2dnbGUgKG5hcGFyaS1qcyBXZWJHUFUgdm9sdW1lL2lzb3N1cmZhY2Ugb25seSkuXG4gICAgICAgICAgIGBwLWJ1dHRvbi1pY29uLW9ubHlgIGdpdmVzIHRoZSBzYW1lIHNxdWFyZSBzaXppbmcgYXMgdGhlIG5laWdoYm91cmluZyBwaS1pY29uIGJ1dHRvbnNcbiAgICAgICAgICAgKHRoZSBwcm9qZWN0ZWQgPGltZz4gZG9lc24ndCB0cmlnZ2VyIFByaW1lTkcncyBhdXRvbWF0aWMgaWNvbi1vbmx5IHNpemluZykuIC0tPlxuICAgICAgPHAtYnV0dG9uIHNpemU9XCJzbWFsbFwiICpuZ0lmPVwiaXNOYXBhcmlNb2RlXCIgc3R5bGVDbGFzcz1cInAtYnV0dG9uLWljb24tb25seVwiXG4gICAgICAgICAgICAgICAgW3RleHRdPVwiIWF4ZXNWaXNpYmxlXCJcbiAgICAgICAgICAgICAgICBwVG9vbHRpcD1cIlRvZ2dsZSAzRCBheGVzIC8gc2NhbGVcIiB0b29sdGlwUG9zaXRpb249XCJib3R0b21cIlxuICAgICAgICAgICAgICAgIChvbkNsaWNrKT1cInRvZ2dsZUF4ZXMuZW1pdCgpXCI+XG4gICAgICAgIDxpbWcgc3JjPVwiYXNzZXRzL3Bsb3R0aW5nLzNkLWNvb3JkaW5hdGVzLnN2Z1wiXG4gICAgICAgICAgICAgW2NsYXNzXT1cImF4ZXNWaXNpYmxlID8gJ3Rvb2xiYXItaWNvbi1hY3RpdmUnIDogJ3Rvb2xiYXItaWNvbi1kYXJrJ1wiXG4gICAgICAgICAgICAgc3R5bGU9XCJ3aWR0aDogMXJlbVwiLz5cbiAgICAgIDwvcC1idXR0b24+XG4gICAgICA8IS0tIFN1cmZhY2Ugd2lyZWZyYW1lIHRvZ2dsZSAobmFwYXJpLWpzIFdlYkdQVSBzdXJmYWNlIG9ubHkpLiAtLT5cbiAgICAgIDxwLWJ1dHRvbiBpY29uPVwicGkgcGktc2hhcmUtYWx0XCIgc2l6ZT1cInNtYWxsXCIgKm5nSWY9XCJpc05hcGFyaVN1cmZhY2VNb2RlXCJcbiAgICAgICAgICAgICAgICBbdGV4dF09XCIhd2lyZWZyYW1lQWN0aXZlXCJcbiAgICAgICAgICAgICAgICBwVG9vbHRpcD1cIlRvZ2dsZSB3aXJlZnJhbWVcIiB0b29sdGlwUG9zaXRpb249XCJib3R0b21cIlxuICAgICAgICAgICAgICAgIChvbkNsaWNrKT1cInRvZ2dsZVdpcmVmcmFtZS5lbWl0KClcIj5cbiAgICAgIDwvcC1idXR0b24+XG4gICAgICA8IS0tIFJlc29sdXRpb24gKGRlY2ltYXRlIGZhY3RvcikgZm9yIHRoZSBuYXBhcmkgM0QgdHlwZXMg4oCUIHJlbG9hZHMgYXQgY29hcnNlci9maW5lciBzYW1wbGluZy4gLS0+XG4gICAgICA8bmctY29udGFpbmVyICpuZ0lmPVwiaXNOYXBhcmkzZE1vZGVcIj5cbiAgICAgICAgPHNwYW4gY2xhc3M9XCJmbGV4IGFsaWduLWl0ZW1zLWNlbnRlciBteC0xIHRvb2xiYXItdGV4dFwiPlJlczwvc3Bhbj5cbiAgICAgICAgPHAtZHJvcGRvd24gW29wdGlvbnNdPVwiZGVjaW1hdGVPcHRpb25zXCIgb3B0aW9uTGFiZWw9XCJsYWJlbFwiIG9wdGlvblZhbHVlPVwidmFsdWVcIlxuICAgICAgICAgICAgICAgICAgICBbbmdNb2RlbF09XCJyZXNvbHV0aW9uU2NhbGVcIiBhcHBlbmRUbz1cImJvZHlcIlxuICAgICAgICAgICAgICAgICAgICBwVG9vbHRpcD1cIlJlc29sdXRpb24gKGRlY2ltYXRlIGZhY3RvcilcIiB0b29sdGlwUG9zaXRpb249XCJib3R0b21cIlxuICAgICAgICAgICAgICAgICAgICBbc3R5bGVdPVwieydtaW4td2lkdGgnOiAnNnJlbSd9XCJcbiAgICAgICAgICAgICAgICAgICAgKG9uQ2hhbmdlKT1cInNlbGVjdFJlc29sdXRpb24uZW1pdCgkZXZlbnQudmFsdWUpXCI+XG4gICAgICAgIDwvcC1kcm9wZG93bj5cbiAgICAgIDwvbmctY29udGFpbmVyPlxuICAgIDwvbmctY29udGFpbmVyPlxuXG4gICAgPG5nLWNvbnRhaW5lciAqbmdJZj1cImlzSGVhdG1hcCAmJiB0b29scy5yZWdpb25Ub29sc1wiPlxuICAgICAgPHNwYW4gY2xhc3M9XCJ0b29sYmFyLXNlcGFyYXRvclwiPjwvc3Bhbj5cbiAgICAgIDwhLS0gUmVnaW9uIEVkaXRvcjogb3BlbnMgdGhlIGVkaXRvciBhcyBhIGZsb2F0aW5nIGRpYWxvZyAoZm9ybWVybHkgdGhlXG4gICAgICAgICAgIHJpZ2h0LXBhbmVsIFJlZ2lvbnMgdGFiKS4gU2l0cyB3aXRoIHRoZSByZWdpb24gZHJhd2luZyB0b29scy4gLS0+XG4gICAgICA8cC1idXR0b24gc2l6ZT1cInNtYWxsXCIgW3RleHRdPVwidHJ1ZVwiXG4gICAgICAgICAgICAgICAgcFRvb2x0aXA9XCJPcGVuIHRoZSBSZWdpb24gRWRpdG9yXCIgdG9vbHRpcFBvc2l0aW9uPVwiYm90dG9tXCJcbiAgICAgICAgICAgICAgICAob25DbGljayk9XCJvcGVuUmVnaW9uRWRpdG9yLmVtaXQoKVwiPlxuICAgICAgICA8aW1nIHNyYz1cImFzc2V0cy9wbG90dGluZy9yZWdpb24tZWRpdG9yLnN2Z1wiIGNsYXNzPVwidG9vbGJhci1pY29uLWRhcmtcIiBzdHlsZT1cIndpZHRoOiAxcmVtXCIvPlxuICAgICAgPC9wLWJ1dHRvbj5cbiAgICAgIDwhLS0gUmVnaW9uIGRyYXdpbmcgdG9vbHMgLS0+XG4gICAgICA8cC1idXR0b24gaWNvbj1cInBpIHBpLWFycm93LXVwLXJpZ2h0XCIgc2l6ZT1cInNtYWxsXCJcbiAgICAgICAgICAgICAgICBbdGV4dF09XCJhY3RpdmVEcmFnTW9kZSAhPT0gJ3NlbGVjdCdcIlxuICAgICAgICAgICAgICAgIHBUb29sdGlwPVwiU2VsZWN0aW9uIG1vZGUgKDxrYmQ+czwva2JkPilcIlxuICAgICAgICAgICAgICAgIFtlc2NhcGVdPVwiZmFsc2VcIiB0b29sdGlwUG9zaXRpb249XCJib3R0b21cIlxuICAgICAgICAgICAgICAgIChvbkNsaWNrKT1cInRvZ2dsZURyYWdNb2RlLmVtaXQoJ3NlbGVjdCcpXCI+XG4gICAgICA8L3AtYnV0dG9uPlxuICAgICAgPHAtYnV0dG9uIGljb249XCJwaSBwaS1zdG9wXCIgc2l6ZT1cInNtYWxsXCJcbiAgICAgICAgICAgICAgICBbdGV4dF09XCJhY3RpdmVEcmFnTW9kZSAhPT0gJ2RyYXdyZWN0J1wiXG4gICAgICAgICAgICAgICAgcFRvb2x0aXA9XCJEcmF3IGEgcmVjdGFuZ3VsYXIgcmVnaW9uICg8a2JkPnI8L2tiZD4pXCIgW2VzY2FwZV09XCJmYWxzZVwiXG4gICAgICAgICAgICAgICAgdG9vbHRpcFBvc2l0aW9uPVwiYm90dG9tXCJcbiAgICAgICAgICAgICAgICAob25DbGljayk9XCJ0b2dnbGVEcmFnTW9kZS5lbWl0KCdkcmF3cmVjdCcpXCI+XG4gICAgICA8L3AtYnV0dG9uPlxuICAgICAgPHAtYnV0dG9uIHNpemU9XCJzbWFsbFwiXG4gICAgICAgICAgICAgICAgW3RleHRdPVwiYWN0aXZlRHJhZ01vZGUgIT09ICdkcmF3b3BlbnBhdGgnXCJcbiAgICAgICAgICAgICAgICBwVG9vbHRpcD1cIkRyYXcgYSBwb2x5bGluZSAoPGtiZD5sPC9rYmQ+KVwiIFtlc2NhcGVdPVwiZmFsc2VcIlxuICAgICAgICAgICAgICAgIHRvb2x0aXBQb3NpdGlvbj1cImJvdHRvbVwiXG4gICAgICAgICAgICAgICAgKG9uQ2xpY2spPVwidG9nZ2xlRHJhZ01vZGUuZW1pdCgnZHJhd29wZW5wYXRoJylcIj5cbiAgICAgICAgPGltZyBzcmM9XCJhc3NldHMvcGxvdHRpbmcvcG9seWxpbmUuc3ZnXCJcbiAgICAgICAgICAgICBbY2xhc3NdPVwiYWN0aXZlRHJhZ01vZGUgPT09ICdkcmF3b3BlbnBhdGgnID8gJ3Rvb2xiYXItaWNvbi1hY3RpdmUnIDogJ3Rvb2xiYXItaWNvbi1kYXJrJ1wiXG4gICAgICAgICAgICAgc3R5bGU9XCJ3aWR0aDogMXJlbVwiLz5cbiAgICAgIDwvcC1idXR0b24+XG4gICAgICA8cC1idXR0b24gaWNvbj1cInBpIHBpLXBlbmNpbFwiIHNpemU9XCJzbWFsbFwiXG4gICAgICAgICAgICAgICAgW3RleHRdPVwiYWN0aXZlRHJhZ01vZGUgIT09ICdkcmF3Y2xvc2VkcGF0aCdcIlxuICAgICAgICAgICAgICAgIHBUb29sdGlwPVwiRHJhdyBhIGZyZWVmb3JtIHJlZ2lvbiAoPGtiZD5mPC9rYmQ+KVwiIFtlc2NhcGVdPVwiZmFsc2VcIlxuICAgICAgICAgICAgICAgIHRvb2x0aXBQb3NpdGlvbj1cImJvdHRvbVwiXG4gICAgICAgICAgICAgICAgKG9uQ2xpY2spPVwidG9nZ2xlRHJhZ01vZGUuZW1pdCgnZHJhd2Nsb3NlZHBhdGgnKVwiPlxuICAgICAgPC9wLWJ1dHRvbj5cbiAgICAgIDwhLS0gQnJ1c2g6IHBhaW50IGEgcmVnaW9uIGJ5IGRyYWdnaW5nIGEgZGlzYyBvZiB0aGUgZ2l2ZW4gc2l6ZTsgdGhlIFNpemVcbiAgICAgICAgICAgc2xpZGVyIGFwcGVhcnMgd2hpbGUgaXQncyBhY3RpdmUuIEhvbGQgU2hpZnQgdG8gZXJhc2UgKHN1YnRyYWN0KS4gLS0+XG4gICAgICA8cC1idXR0b24gc2l6ZT1cInNtYWxsXCJcbiAgICAgICAgICAgICAgICBbdGV4dF09XCJhY3RpdmVEcmFnTW9kZSAhPT0gJ2JydXNoJ1wiXG4gICAgICAgICAgICAgICAgcFRvb2x0aXA9XCJCcnVzaDogY2xpY2sgYW5kIGRyYWcgdG8gcGFpbnQgYSByZWdpb24uXG4gICAgICAgICAgICAgICAgPGJyPkhvbGQgPGtiZD5TaGlmdDwva2JkPiB3aGlsZSBwYWludGluZyB0byBlcmFzZSBmcm9tIGEgcmVnaW9uLlwiXG4gICAgICAgICAgICAgICAgW2VzY2FwZV09XCJmYWxzZVwiIHRvb2x0aXBQb3NpdGlvbj1cImJvdHRvbVwiXG4gICAgICAgICAgICAgICAgKG9uQ2xpY2spPVwidG9nZ2xlRHJhZ01vZGUuZW1pdCgnYnJ1c2gnKVwiPlxuICAgICAgICA8aW1nIHNyYz1cImFzc2V0cy9wbG90dGluZy9wYWludGJydXNoLnN2Z1wiXG4gICAgICAgICAgICAgW2NsYXNzXT1cImFjdGl2ZURyYWdNb2RlID09PSAnYnJ1c2gnID8gJ3Rvb2xiYXItaWNvbi1hY3RpdmUnIDogJ3Rvb2xiYXItaWNvbi1kYXJrJ1wiXG4gICAgICAgICAgICAgc3R5bGU9XCJ3aWR0aDogMXJlbVwiLz5cbiAgICAgIDwvcC1idXR0b24+XG4gICAgICA8bmctY29udGFpbmVyICpuZ0lmPVwiYWN0aXZlRHJhZ01vZGUgPT09ICdicnVzaCdcIj5cbiAgICAgICAgPHNwYW4gY2xhc3M9XCJmbGV4IGFsaWduLWl0ZW1zLWNlbnRlciBteC0xIHRvb2xiYXItdGV4dFwiPlNpemU8L3NwYW4+XG4gICAgICAgIDxwLXNsaWRlciBbKG5nTW9kZWwpXT1cImJydXNoU2l6ZVwiXG4gICAgICAgICAgICAgICAgICBbbWluXT1cImJydXNoU2l6ZU1pblwiIFttYXhdPVwiYnJ1c2hTaXplTWF4XCIgW3N0ZXBdPVwiYnJ1c2hTaXplU3RlcFwiXG4gICAgICAgICAgICAgICAgICAob25DaGFuZ2UpPVwiYnJ1c2hTaXplQ2hhbmdlLmVtaXQoJGV2ZW50LnZhbHVlKVwiXG4gICAgICAgICAgICAgICAgICBwVG9vbHRpcD1cIkJydXNoIGRpYW1ldGVyIGluIGltYWdlIHBpeGVscy5cIlxuICAgICAgICAgICAgICAgICAgW2VzY2FwZV09XCJmYWxzZVwiIHRvb2x0aXBQb3NpdGlvbj1cImJvdHRvbVwiXG4gICAgICAgICAgICAgICAgICBzdHlsZUNsYXNzPVwid2FuZC1zZW5zaXRpdml0eS1zbGlkZXJcIj48L3Atc2xpZGVyPlxuICAgICAgICA8c3BhbiBjbGFzcz1cImZsZXggYWxpZ24taXRlbXMtY2VudGVyIG14LTEgdG9vbGJhci10ZXh0IHdhbmQtc2Vuc2l0aXZpdHktdmFsdWVcIj5cbiAgICAgICAgICB7eyBicnVzaFNpemUgfX08L3NwYW4+XG4gICAgICA8L25nLWNvbnRhaW5lcj5cblxuICAgICAgPCEtLSBWZXJ0ZXggZWRpdGluZyDigJQgYmFja2VuZHMgd2l0aCBhIHZlcnRleC1lZGl0aW5nIG92ZXJsYXkgKE9TRCArIG5hcGFyaS1qcyBpbWFnZSkuIC0tPlxuICAgICAgPG5nLWNvbnRhaW5lciAqbmdJZj1cInN1cHBvcnRzUmVnaW9uVmVydGV4VG9vbHNcIj5cbiAgICAgICAgPHAtYnV0dG9uIHNpemU9XCJzbWFsbFwiXG4gICAgICAgICAgICAgICAgICBbdGV4dF09XCJhY3RpdmVEcmFnTW9kZSAhPT0gJ2RyYXdwb2x5Z29uJ1wiXG4gICAgICAgICAgICAgICAgICBwVG9vbHRpcD1cIlBvbHlnb246IGNsaWNrIHRvIHBsYWNlIGVhY2ggdmVydGV4OyBjbGljayB0aGUgZmlyc3QgcG9pbnQgdG8gY2xvc2VcIlxuICAgICAgICAgICAgICAgICAgW2VzY2FwZV09XCJmYWxzZVwiIHRvb2x0aXBQb3NpdGlvbj1cImJvdHRvbVwiXG4gICAgICAgICAgICAgICAgICAob25DbGljayk9XCJ0b2dnbGVEcmFnTW9kZS5lbWl0KCdkcmF3cG9seWdvbicpXCI+XG4gICAgICAgICAgPGltZyBzcmM9XCJhc3NldHMvcGxvdHRpbmcvcG9seWdvbi12ZXJ0aWNlcy5zdmdcIlxuICAgICAgICAgICAgICAgW2NsYXNzXT1cImFjdGl2ZURyYWdNb2RlID09PSAnZHJhd3BvbHlnb24nID8gJ3Rvb2xiYXItaWNvbi1hY3RpdmUnIDogJ3Rvb2xiYXItaWNvbi1kYXJrJ1wiXG4gICAgICAgICAgICAgICBzdHlsZT1cIndpZHRoOiAxcmVtXCIvPlxuICAgICAgICA8L3AtYnV0dG9uPlxuICAgICAgICA8cC1idXR0b24gc2l6ZT1cInNtYWxsXCJcbiAgICAgICAgICAgICAgICAgIFt0ZXh0XT1cImFjdGl2ZURyYWdNb2RlICE9PSAnYWRkcG9pbnQnXCJcbiAgICAgICAgICAgICAgICAgIHBUb29sdGlwPVwiQWRkIGEgdmVydGV4OiBjbGljayBhbiBlZGdlIG9mIHRoZSBzZWxlY3RlZCBwb2x5Z29uXCJcbiAgICAgICAgICAgICAgICAgIFtlc2NhcGVdPVwiZmFsc2VcIiB0b29sdGlwUG9zaXRpb249XCJib3R0b21cIlxuICAgICAgICAgICAgICAgICAgKG9uQ2xpY2spPVwidG9nZ2xlRHJhZ01vZGUuZW1pdCgnYWRkcG9pbnQnKVwiPlxuICAgICAgICAgIDxpbWcgc3JjPVwiYXNzZXRzL3Bsb3R0aW5nL3ZlcnRleC1hZGQuc3ZnXCJcbiAgICAgICAgICAgICAgIFtjbGFzc109XCJhY3RpdmVEcmFnTW9kZSA9PT0gJ2FkZHBvaW50JyA/ICd0b29sYmFyLWljb24tYWN0aXZlJyA6ICd0b29sYmFyLWljb24tZGFyaydcIlxuICAgICAgICAgICAgICAgc3R5bGU9XCJ3aWR0aDogMXJlbVwiLz5cbiAgICAgICAgPC9wLWJ1dHRvbj5cbiAgICAgICAgPHAtYnV0dG9uIHNpemU9XCJzbWFsbFwiXG4gICAgICAgICAgICAgICAgICBbdGV4dF09XCJhY3RpdmVEcmFnTW9kZSAhPT0gJ2RlbGV0ZXBvaW50J1wiXG4gICAgICAgICAgICAgICAgICBwVG9vbHRpcD1cIkRlbGV0ZSBhIHZlcnRleDogY2xpY2sgYSB2ZXJ0ZXggb2YgdGhlIHNlbGVjdGVkIHBvbHlnb25cIlxuICAgICAgICAgICAgICAgICAgW2VzY2FwZV09XCJmYWxzZVwiIHRvb2x0aXBQb3NpdGlvbj1cImJvdHRvbVwiXG4gICAgICAgICAgICAgICAgICAob25DbGljayk9XCJ0b2dnbGVEcmFnTW9kZS5lbWl0KCdkZWxldGVwb2ludCcpXCI+XG4gICAgICAgICAgPGltZyBzcmM9XCJhc3NldHMvcGxvdHRpbmcvdmVydGV4LWRlbGV0ZS5zdmdcIlxuICAgICAgICAgICAgICAgW2NsYXNzXT1cImFjdGl2ZURyYWdNb2RlID09PSAnZGVsZXRlcG9pbnQnID8gJ3Rvb2xiYXItaWNvbi1hY3RpdmUnIDogJ3Rvb2xiYXItaWNvbi1kYXJrJ1wiXG4gICAgICAgICAgICAgICBzdHlsZT1cIndpZHRoOiAxcmVtXCIvPlxuICAgICAgICA8L3AtYnV0dG9uPlxuICAgICAgPC9uZy1jb250YWluZXI+XG5cbiAgICAgIDxwLWJ1dHRvbiBzaXplPVwic21hbGxcIlxuICAgICAgICAgICAgICAgIFt0ZXh0XT1cImFjdGl2ZURyYWdNb2RlICE9PSAnd2FuZCdcIlxuICAgICAgICAgICAgICAgIHBUb29sdGlwPVwiV2FuZDogY2xpY2sgYW5kIGRyYWcgdG8gZ3JvdyBhIHJlZ2lvbiBieSBzaW1pbGFyIHBpeGVsIHZhbHVlcyAoPGtiZD53PC9rYmQ+KS5cbiAgICAgICAgICAgICAgICA8YnI+SG9sZCA8a2JkPlNoaWZ0PC9rYmQ+IHdoaWxlIGNsaWNraW5nIG9yIGRyYWdnaW5nIG9uIGEgcmVnaW9uIHRvIGVyYXNlIHBpeGVscyBmcm9tIGl0LlwiXG4gICAgICAgICAgICAgICAgW2VzY2FwZV09XCJmYWxzZVwiIHRvb2x0aXBQb3NpdGlvbj1cImJvdHRvbVwiXG4gICAgICAgICAgICAgICAgKG9uQ2xpY2spPVwidG9nZ2xlRHJhZ01vZGUuZW1pdCgnd2FuZCcpXCI+XG4gICAgICAgIDxpbWcgc3JjPVwiYXNzZXRzL3Bsb3R0aW5nL3dhbmQuc3ZnXCJcbiAgICAgICAgICAgICBbY2xhc3NdPVwiYWN0aXZlRHJhZ01vZGUgPT09ICd3YW5kJyA/ICd0b29sYmFyLWljb24tYWN0aXZlJyA6ICd0b29sYmFyLWljb24tZGFyaydcIlxuICAgICAgICAgICAgIHN0eWxlPVwid2lkdGg6IDFyZW1cIi8+XG4gICAgICA8L3AtYnV0dG9uPlxuICAgICAgPG5nLWNvbnRhaW5lciAqbmdJZj1cImFjdGl2ZURyYWdNb2RlID09PSAnd2FuZCdcIj5cbiAgICAgICAgPHNwYW4gY2xhc3M9XCJmbGV4IGFsaWduLWl0ZW1zLWNlbnRlciBteC0xIHRvb2xiYXItdGV4dFwiPlNlbnMuPC9zcGFuPlxuICAgICAgICA8cC1zbGlkZXIgWyhuZ01vZGVsKV09XCJ3YW5kU2Vuc2l0aXZpdHlcIlxuICAgICAgICAgICAgICAgICAgW21pbl09XCJ3YW5kU2Vuc2l0aXZpdHlNaW5cIiBbbWF4XT1cIndhbmRTZW5zaXRpdml0eU1heFwiIFtzdGVwXT1cIndhbmRTZW5zaXRpdml0eVN0ZXBcIlxuICAgICAgICAgICAgICAgICAgKG9uQ2hhbmdlKT1cIndhbmRTZW5zaXRpdml0eUNoYW5nZS5lbWl0KCRldmVudC52YWx1ZSlcIlxuICAgICAgICAgICAgICAgICAgcFRvb2x0aXA9XCJIaWdoZXIgPSBzdHJpY3RlciAoc21hbGxlciBzZWxlY3Rpb24pOyBsb3dlciA9IGxvb3Nlci5cbiAgICAgICAgICAgICAgICAgIFRha2VzIGVmZmVjdCBmcm9tIHRoZSBuZXh0IGNsaWNrL2RyYWcgdGljay5cIlxuICAgICAgICAgICAgICAgICAgW2VzY2FwZV09XCJmYWxzZVwiIHRvb2x0aXBQb3NpdGlvbj1cImJvdHRvbVwiXG4gICAgICAgICAgICAgICAgICBzdHlsZUNsYXNzPVwid2FuZC1zZW5zaXRpdml0eS1zbGlkZXJcIj48L3Atc2xpZGVyPlxuICAgICAgICA8c3BhbiBjbGFzcz1cImZsZXggYWxpZ24taXRlbXMtY2VudGVyIG14LTEgdG9vbGJhci10ZXh0IHdhbmQtc2Vuc2l0aXZpdHktdmFsdWVcIj5cbiAgICAgICAgICB7eyB3YW5kU2Vuc2l0aXZpdHkudG9GaXhlZCgxKSB9fTwvc3Bhbj5cbiAgICAgIDwvbmctY29udGFpbmVyPlxuICAgICAgPHAtYnV0dG9uIGljb249XCJwaSBwaS1lcmFzZXJcIiBzaXplPVwic21hbGxcIlxuICAgICAgICAgICAgICAgIFt0ZXh0XT1cImFjdGl2ZURyYWdNb2RlICE9PSAnZXJhc2VWZXJ0ZXgnXCJcbiAgICAgICAgICAgICAgICBwVG9vbHRpcD1cIlZlcnRleCBlcmFzZXIgKDxrYmQ+ZTwva2JkPik6IGNsaWNrIG9yIGRyYWcgdG8gcmVtb3ZlIHBvbHlnb24gdmVydGljZXMgaW5zaWRlXG4gICAgICAgICAgICAgICAgdGhlIGN1cnNvciBjaXJjbGUgZnJvbSBhbnkgcmVnaW9uLlwiXG4gICAgICAgICAgICAgICAgW2VzY2FwZV09XCJmYWxzZVwiIHRvb2x0aXBQb3NpdGlvbj1cImJvdHRvbVwiXG4gICAgICAgICAgICAgICAgKG9uQ2xpY2spPVwidG9nZ2xlRHJhZ01vZGUuZW1pdCgnZXJhc2VWZXJ0ZXgnKVwiPlxuICAgICAgPC9wLWJ1dHRvbj5cbiAgICAgIDxuZy1jb250YWluZXIgKm5nSWY9XCJhY3RpdmVEcmFnTW9kZSA9PT0gJ2VyYXNlVmVydGV4J1wiPlxuICAgICAgICA8c3BhbiBjbGFzcz1cImZsZXggYWxpZ24taXRlbXMtY2VudGVyIG14LTEgdG9vbGJhci10ZXh0XCI+UmFkaXVzPC9zcGFuPlxuICAgICAgICA8cC1zbGlkZXIgWyhuZ01vZGVsKV09XCJ2ZXJ0ZXhFcmFzZXJSYWRpdXNcIlxuICAgICAgICAgICAgICAgICAgW21pbl09XCJ2ZXJ0ZXhFcmFzZXJSYWRpdXNNaW5cIiBbbWF4XT1cInZlcnRleEVyYXNlclJhZGl1c01heFwiIFtzdGVwXT1cInZlcnRleEVyYXNlclJhZGl1c1N0ZXBcIlxuICAgICAgICAgICAgICAgICAgKG9uQ2hhbmdlKT1cInZlcnRleEVyYXNlclJhZGl1c0NoYW5nZS5lbWl0KCRldmVudC52YWx1ZSlcIlxuICAgICAgICAgICAgICAgICAgcFRvb2x0aXA9XCJFcmFzZXIgcmFkaXVzIGluIGltYWdlIHBpeGVscy5cbiAgICAgICAgICAgICAgICAgIFZlcnRpY2VzIHdpdGhpbiB0aGlzIGRpc3RhbmNlIG9mIHRoZSBjdXJzb3IgYXJlIHJlbW92ZWQuXCJcbiAgICAgICAgICAgICAgICAgIFtlc2NhcGVdPVwiZmFsc2VcIiB0b29sdGlwUG9zaXRpb249XCJib3R0b21cIlxuICAgICAgICAgICAgICAgICAgc3R5bGVDbGFzcz1cIndhbmQtc2Vuc2l0aXZpdHktc2xpZGVyXCI+PC9wLXNsaWRlcj5cbiAgICAgICAgPHNwYW4gY2xhc3M9XCJmbGV4IGFsaWduLWl0ZW1zLWNlbnRlciBteC0xIHRvb2xiYXItdGV4dCB3YW5kLXNlbnNpdGl2aXR5LXZhbHVlXCI+XG4gICAgICAgICAge3sgdmVydGV4RXJhc2VyUmFkaXVzIH19PC9zcGFuPlxuICAgICAgPC9uZy1jb250YWluZXI+XG4gICAgICA8cC1idXR0b24gc2l6ZT1cInNtYWxsXCIgW3RleHRdPVwidHJ1ZVwiXG4gICAgICAgICAgICAgICAgW2Rpc2FibGVkXT1cIiFjYW5VbmRvXCJcbiAgICAgICAgICAgICAgICBwVG9vbHRpcD1cIlVuZG8gdGhlIGxhc3QgcmVnaW9uIGFjdGlvbiAoPGtiZD5DdHJsPC9rYmQ+KzxrYmQ+Wjwva2JkPikuXG4gICAgICAgICAgICAgICAgVXAgdG8gMTAgYWN0aW9ucyBjYW4gYmUgdW5kb25lLlwiXG4gICAgICAgICAgICAgICAgW2VzY2FwZV09XCJmYWxzZVwiIHRvb2x0aXBQb3NpdGlvbj1cImJvdHRvbVwiXG4gICAgICAgICAgICAgICAgKG9uQ2xpY2spPVwidW5kb1JlZ2lvbi5lbWl0KClcIj5cbiAgICAgICAgPGltZyBzcmM9XCJhc3NldHMvcGxvdHRpbmcvdW5kby5zdmdcIiBjbGFzcz1cInRvb2xiYXItaWNvbi1kYXJrXCIgc3R5bGU9XCJ3aWR0aDogMXJlbVwiLz5cbiAgICAgIDwvcC1idXR0b24+XG4gICAgICA8cC1idXR0b24gc2l6ZT1cInNtYWxsXCIgW3RleHRdPVwidHJ1ZVwiXG4gICAgICAgICAgICAgICAgW2Rpc2FibGVkXT1cIiFjYW5SZWRvXCJcbiAgICAgICAgICAgICAgICBwVG9vbHRpcD1cIlJlZG8gdGhlIGxhc3QgdW5kb25lIHJlZ2lvbiBhY3Rpb25cbiAgICAgICAgICAgICAgICAoPGtiZD5DdHJsPC9rYmQ+KzxrYmQ+U2hpZnQ8L2tiZD4rPGtiZD5aPC9rYmQ+IC8gPGtiZD5DdHJsPC9rYmQ+KzxrYmQ+WTwva2JkPikuXCJcbiAgICAgICAgICAgICAgICBbZXNjYXBlXT1cImZhbHNlXCIgdG9vbHRpcFBvc2l0aW9uPVwiYm90dG9tXCJcbiAgICAgICAgICAgICAgICAob25DbGljayk9XCJyZWRvUmVnaW9uLmVtaXQoKVwiPlxuICAgICAgICA8aW1nIHNyYz1cImFzc2V0cy9wbG90dGluZy9yZWRvLnN2Z1wiIGNsYXNzPVwidG9vbGJhci1pY29uLWRhcmtcIiBzdHlsZT1cIndpZHRoOiAxcmVtXCIvPlxuICAgICAgPC9wLWJ1dHRvbj5cbiAgICAgIDxwLWJ1dHRvbiBpY29uPVwicGkgcGktdHJhc2hcIiBzaXplPVwic21hbGxcIiBbdGV4dF09XCJ0cnVlXCJcbiAgICAgICAgICAgICAgICBwVG9vbHRpcD1cIkRlbGV0ZSBzZWxlY3RlZCByZWdpb24gKDxrYmQ+RGVsPC9rYmQ+KVwiXG4gICAgICAgICAgICAgICAgW2VzY2FwZV09XCJmYWxzZVwiIHRvb2x0aXBQb3NpdGlvbj1cImJvdHRvbVwiXG4gICAgICAgICAgICAgICAgKG9uQ2xpY2spPVwiZGVsZXRlUmVnaW9uLmVtaXQoKVwiPlxuICAgICAgPC9wLWJ1dHRvbj5cbiAgICAgIDwhLS0gU0FNIChqaXQtdWkjOTApOiBib3gtcHJvbXB0IHNlZ21lbnQsIGludGVyYWN0aXZlIHBvaW50IHByb21wdHMsIGFuZCBhXG4gICAgICAgICAgIG1vZGVsIHBpY2tlci4gLS0+XG4gICAgICA8c3BhbiBjbGFzcz1cInRvb2xiYXItc2VwYXJhdG9yXCI+PC9zcGFuPlxuICAgICAgPCEtLSBTQU0gY29udHJvbHMgZ3JvdXBlZDogYm94LXByb21wdCBTZWdtZW50LCB0aGVuIHRoZSBwb2ludC1wcm9tcHQgdG9vbCxcbiAgICAgICAgICAgdGhlbiB0aGUgbW9kZWwtcGlja2VyIGRyb3Bkb3duICh0aGUgY2hldnJvbikgd2hpY2ggZ292ZXJucyBCT1RIIHRvb2xzLlxuICAgICAgICAgICBGYWxscyBiYWNrIHRvIFNlZ21lbnQgKyBwb2ludCB3aXRoIG5vIG1vZGVsIHBpY2tlciB3aGVuIG5vbmUgYXJlIHNldC4gLS0+XG4gICAgICA8c3BhbiAqbmdJZj1cInNhbU1vZGVscy5sZW5ndGg7IGVsc2Ugc2VnbWVudFBsYWluXCIgY2xhc3M9XCJwLWJ1dHRvbnNldCBzZWdtZW50LXNwbGl0XCI+XG4gICAgICAgIDxwLWJ1dHRvbiBzaXplPVwic21hbGxcIiBbdGV4dF09XCJ0cnVlXCJcbiAgICAgICAgICAgICAgICAgIHBUb29sdGlwPVwiU2VnbWVudCB0aGUgZHJhd24gcmVjdGFuZ2xlcyB3aXRoIFNBTSAoYm94IHByb21wdClcIlxuICAgICAgICAgICAgICAgICAgdG9vbHRpcFBvc2l0aW9uPVwiYm90dG9tXCJcbiAgICAgICAgICAgICAgICAgIChvbkNsaWNrKT1cInNlZ21lbnRSZWdpb25zLmVtaXQoKVwiPlxuICAgICAgICAgIDxpbWcgc3JjPVwiYXNzZXRzL3Bsb3R0aW5nL3BpY3R1cmUtc2VnbWVudGF0aW9uLnN2Z1wiXG4gICAgICAgICAgICAgICBjbGFzcz1cInRvb2xiYXItaWNvbi1kYXJrXCIgc3R5bGU9XCJ3aWR0aDogMXJlbVwiLz5cbiAgICAgICAgPC9wLWJ1dHRvbj5cbiAgICAgICAgPG5nLWNvbnRhaW5lciBbbmdUZW1wbGF0ZU91dGxldF09XCJzYW1Qb2ludEJ0blwiPjwvbmctY29udGFpbmVyPlxuICAgICAgICA8cC1idXR0b24gaWNvbj1cInBpIHBpLWNoZXZyb24tZG93blwiIHNpemU9XCJzbWFsbFwiIFt0ZXh0XT1cInRydWVcIlxuICAgICAgICAgICAgICAgICAgcFRvb2x0aXA9XCJQaWNrIHRoZSBTQU0gbW9kZWwgKHVzZWQgYnkgYm90aCBzZWdtZW50IHRvb2xzKVwiXG4gICAgICAgICAgICAgICAgICB0b29sdGlwUG9zaXRpb249XCJib3R0b21cIlxuICAgICAgICAgICAgICAgICAgKG9uQ2xpY2spPVwic2FtTW9kZWxNZW51LnRvZ2dsZSgkZXZlbnQpXCI+XG4gICAgICAgIDwvcC1idXR0b24+XG4gICAgICA8L3NwYW4+XG4gICAgICA8bmctdGVtcGxhdGUgI3NlZ21lbnRQbGFpbj5cbiAgICAgICAgPHNwYW4gY2xhc3M9XCJwLWJ1dHRvbnNldCBzZWdtZW50LXNwbGl0XCI+XG4gICAgICAgICAgPHAtYnV0dG9uIHNpemU9XCJzbWFsbFwiIFt0ZXh0XT1cInRydWVcIlxuICAgICAgICAgICAgICAgICAgICBwVG9vbHRpcD1cIlNlZ21lbnQgdGhlIGRyYXduIHJlY3RhbmdsZXMgd2l0aCBTQU0gKGJveCBwcm9tcHQpXCJcbiAgICAgICAgICAgICAgICAgICAgdG9vbHRpcFBvc2l0aW9uPVwiYm90dG9tXCJcbiAgICAgICAgICAgICAgICAgICAgKG9uQ2xpY2spPVwic2VnbWVudFJlZ2lvbnMuZW1pdCgpXCI+XG4gICAgICAgICAgICA8aW1nIHNyYz1cImFzc2V0cy9wbG90dGluZy9waWN0dXJlLXNlZ21lbnRhdGlvbi5zdmdcIlxuICAgICAgICAgICAgICAgICBjbGFzcz1cInRvb2xiYXItaWNvbi1kYXJrXCIgc3R5bGU9XCJ3aWR0aDogMXJlbVwiLz5cbiAgICAgICAgICA8L3AtYnV0dG9uPlxuICAgICAgICAgIDxuZy1jb250YWluZXIgW25nVGVtcGxhdGVPdXRsZXRdPVwic2FtUG9pbnRCdG5cIj48L25nLWNvbnRhaW5lcj5cbiAgICAgICAgPC9zcGFuPlxuICAgICAgPC9uZy10ZW1wbGF0ZT5cbiAgICAgIDwhLS0gUG9pbnQtcHJvbXB0IHRvb2wg4oCUIHNoYXJlZCBiZXR3ZWVuIHRoZSBtb2RlbHMgLyBuby1tb2RlbHMgbGF5b3V0cyBzbyBpdFxuICAgICAgICAgICBhbHdheXMgc2l0cyBqdXN0IGxlZnQgb2YgdGhlIG1vZGVsIGRyb3Bkb3duLiAtLT5cbiAgICAgIDxuZy10ZW1wbGF0ZSAjc2FtUG9pbnRCdG4+XG4gICAgICAgIDxwLWJ1dHRvbiBzaXplPVwic21hbGxcIlxuICAgICAgICAgICAgICAgICAgW3RleHRdPVwiYWN0aXZlRHJhZ01vZGUgIT09ICdzYW1Qb2ludCdcIlxuICAgICAgICAgICAgICAgICAgcFRvb2x0aXA9XCJTQU0gcG9pbnQgcHJvbXB0czogY2xpY2sgYSBmaWJlciB0byBzZWdtZW50IGl0IGFzIGEgbmV3IHJlZ2lvbjtcbiAgICAgICAgICAgICAgICAgIFNoaWZ0L0FsdC1jbGljayB0byByZWZpbmUgKGV4Y2x1ZGUpIHRoZSBjdXJyZW50IG9uZS48YnI+RXNjIHVuZG9lcyBpdC5cIlxuICAgICAgICAgICAgICAgICAgW2VzY2FwZV09XCJmYWxzZVwiIHRvb2x0aXBQb3NpdGlvbj1cImJvdHRvbVwiXG4gICAgICAgICAgICAgICAgICAob25DbGljayk9XCJ0b2dnbGVEcmFnTW9kZS5lbWl0KCdzYW1Qb2ludCcpXCI+XG4gICAgICAgICAgPGltZyBzcmM9XCJhc3NldHMvcGxvdHRpbmcvY2xpY2suc3ZnXCJcbiAgICAgICAgICAgICAgIFtjbGFzc109XCJhY3RpdmVEcmFnTW9kZSA9PT0gJ3NhbVBvaW50JyA/ICd0b29sYmFyLWljb24tYWN0aXZlJyA6ICd0b29sYmFyLWljb24tZGFyaydcIlxuICAgICAgICAgICAgICAgc3R5bGU9XCJ3aWR0aDogMXJlbVwiLz5cbiAgICAgICAgPC9wLWJ1dHRvbj5cbiAgICAgIDwvbmctdGVtcGxhdGU+XG4gICAgICA8cC1tZW51ICNzYW1Nb2RlbE1lbnUgW21vZGVsXT1cInNhbU1lbnVJdGVtc1wiIFtwb3B1cF09XCJ0cnVlXCIgYXBwZW5kVG89XCJib2R5XCI+PC9wLW1lbnU+XG4gICAgICA8IS0tIEhlbHA6IGV4cGxhaW5zIHRoZSBWaVQtVCB2cyBWaVQtQiB0cmFkZS1vZmYgYmVoaW5kIHRoZSBtb2RlbCBwaWNrZXIuIC0tPlxuICAgICAgPHAtYnV0dG9uICpuZ0lmPVwic2FtTW9kZWxzLmxlbmd0aFwiIGljb249XCJwaSBwaS1pbmZvLWNpcmNsZVwiIHNpemU9XCJzbWFsbFwiIFt0ZXh0XT1cInRydWVcIlxuICAgICAgICAgICAgICAgIHBUb29sdGlwPVwiQWJvdXQgdGhlIFNBTSBtb2RlbHNcIiB0b29sdGlwUG9zaXRpb249XCJib3R0b21cIlxuICAgICAgICAgICAgICAgIChvbkNsaWNrKT1cInNhbU1vZGVsSW5mby50b2dnbGUoJGV2ZW50KVwiPlxuICAgICAgPC9wLWJ1dHRvbj5cbiAgICAgIDxwLW92ZXJsYXlQYW5lbCAjc2FtTW9kZWxJbmZvIGFwcGVuZFRvPVwiYm9keVwiIHN0eWxlQ2xhc3M9XCJzYW0tbW9kZWwtaW5mb1wiPlxuICAgICAgICA8ZGl2IHN0eWxlPVwibWF4LXdpZHRoOiAyNHJlbTsgbGluZS1oZWlnaHQ6IDEuNFwiPlxuICAgICAgICAgIDxkaXYgY2xhc3M9XCJmb250LWJvbGQgbWItMVwiPlNBTSBtb2RlbHM8L2Rpdj5cbiAgICAgICAgICA8cCBjbGFzcz1cIm10LTAgbWItMlwiPlxuICAgICAgICAgICAgQWxsIGFyZSBwcm9tcHRhYmxlIFNlZ21lbnQgQW55dGhpbmcgbW9kZWxzIGZpbmV0dW5lZCBmb3IgYmlvbWVkaWNhbCBpbWFnZXMuIFRoZXkgZGlmZmVyXG4gICAgICAgICAgICBpbiB0aGUgPGk+aW1hZ2UgZW5jb2RlcjwvaT4gKHRoZSBoZWF2eSBvbmNlLXBlci1pbWFnZSBzdGVwKSBhbmQgdGhlIHRyYWluaW5nIGRvbWFpbiDigJRcbiAgICAgICAgICAgIGJveCBhbmQgcG9pbnQgcHJvbXB0aW5nIGJlaGF2ZSB0aGUgc2FtZS5cbiAgICAgICAgICA8L3A+XG4gICAgICAgICAgPGRpdiBjbGFzcz1cIm1iLTJcIj5cbiAgICAgICAgICAgIDxkaXYgY2xhc3M9XCJmb250LWJvbGRcIj5taWNyby1zYW0gVmlULVQgPHNwYW4gY2xhc3M9XCJmb250LW5vcm1hbFwiPihkZWZhdWx0KTwvc3Bhbj48L2Rpdj5cbiAgICAgICAgICAgIDxkaXY+TGlnaHQtbWljcm9zY29weSBmaW5ldHVuZWQuIFRpbnlWaVQgZW5jb2RlciwgfjE0Jm5ic3A7TUIsIHZlcnkgZmFzdCwgcnVucyBvbiBXQVNNLlxuICAgICAgICAgICAgICBHb29kIG1hc2sgcXVhbGl0eSDigJQgYmVzdCBmb3IgcXVpY2sgaW50ZXJhY3RpdmUgd29yayBhbmQgbWFueS9sYXJnZSBvYmplY3RzLjwvZGl2PlxuICAgICAgICAgIDwvZGl2PlxuICAgICAgICAgIDxkaXYgY2xhc3M9XCJtYi0yXCI+XG4gICAgICAgICAgICA8ZGl2IGNsYXNzPVwiZm9udC1ib2xkXCI+bWljcm8tc2FtIFZpVC1CPC9kaXY+XG4gICAgICAgICAgICA8ZGl2PkxpZ2h0LW1pY3Jvc2NvcHkgZmluZXR1bmVkLiBWaVQtQmFzZSBlbmNvZGVyLCB+MTcyJm5ic3A7TUIsIHNsb3dlciwgR1BVLWFjY2VsZXJhdGVkXG4gICAgICAgICAgICAgIChXZWJHUFUpLiBIaWdoZXN0IGFjY3VyYWN5IOKAlCB3aGVuIFZpVC1UIG1pc3NlcyBzdWJ0bGUgb3Igb3ZlcmxhcHBpbmcgYm91bmRhcmllcy48L2Rpdj5cbiAgICAgICAgICA8L2Rpdj5cbiAgICAgICAgICA8ZGl2IGNsYXNzPVwibWItMlwiPlxuICAgICAgICAgICAgPGRpdiBjbGFzcz1cImZvbnQtYm9sZFwiPnBhdGhvLXNhbSBWaVQtQjwvZGl2PlxuICAgICAgICAgICAgPGRpdj5IaXN0b3BhdGhvbG9neSBmaW5ldHVuZWQgKEgmYW1wO0UgLyBzdGFpbmVkIHRpc3N1ZSkuIFZpVC1CYXNlIGVuY29kZXIsIEdQVS1hY2NlbGVyYXRlZC5cbiAgICAgICAgICAgICAgVXNlIGl0IGZvciBwYXRob2xvZ3kgc2xpZGVzIOKAlCBpdCBzZWdtZW50cyB0aXNzdWUvbnVjbGVpIGJldHRlciB0aGFuIHRoZSBtaWNyb3Njb3B5IG1vZGVscy48L2Rpdj5cbiAgICAgICAgICA8L2Rpdj5cbiAgICAgICAgICA8ZGl2IGNsYXNzPVwibWItMlwiPlxuICAgICAgICAgICAgPGRpdiBjbGFzcz1cImZvbnQtYm9sZFwiPnBhdGhvLXNhbSBWaVQtQiAoaW50OCk8L2Rpdj5cbiAgICAgICAgICAgIDxkaXY+U2FtZSBtb2RlbCwgaW50OC1xdWFudGl6ZWQg4oCUIH4xMDAmbmJzcDtNQiB2cyB+MTgwJm5ic3A7TUIgKHNtYWxsZXIgZG93bmxvYWQpLiBSdW5zIG9uXG4gICAgICAgICAgICAgIFdBU00gKHNsb3dlciBlbmNvZGUgdGhhbiB0aGUgZnAxNiB2ZXJzaW9uKTsgbWFza3MgYXJlIG5lYXItaWRlbnRpY2FsLiBQaWNrIGl0IG9uIHNsb3dcbiAgICAgICAgICAgICAgY29ubmVjdGlvbnMuPC9kaXY+XG4gICAgICAgICAgPC9kaXY+XG4gICAgICAgICAgPGRpdiBjbGFzcz1cInRleHQtc21cIiBzdHlsZT1cIm9wYWNpdHk6IDAuNzVcIj5cbiAgICAgICAgICAgIFRoZSBjaG9pY2UgYXBwbGllcyB0byBib3RoIHRoZSBib3ggYW5kIHBvaW50IHRvb2xzLiBFYWNoIHRvb2wgbG9hZHMgaXRzIG93biBlbmNvZGVyLFxuICAgICAgICAgICAgc28gdGhlIGZpcnN0IGNsaWNrIGFmdGVyIHN3aXRjaGluZyByZS1lbmNvZGVzIG9uY2UuXG4gICAgICAgICAgPC9kaXY+XG4gICAgICAgIDwvZGl2PlxuICAgICAgPC9wLW92ZXJsYXlQYW5lbD5cbiAgICAgIDxwLWJ1dHRvbiBzaXplPVwic21hbGxcIiBbdGV4dF09XCJ0cnVlXCJcbiAgICAgICAgICAgICAgICBwVG9vbHRpcD1cIkF1dG8tc2VnbWVudCBjZWxscyBpbnNpZGUgZWFjaCBkcmF3biByZWN0YW5nbGUgKGNlbGxwb3NlLVNBTSwgY2xpZW50LXNpZGUpXCJcbiAgICAgICAgICAgICAgICB0b29sdGlwUG9zaXRpb249XCJib3R0b21cIlxuICAgICAgICAgICAgICAgIChvbkNsaWNrKT1cInNlZ21lbnRDZWxscG9zZS5lbWl0KClcIj5cbiAgICAgICAgPGltZyBzcmM9XCJhc3NldHMvcGxvdHRpbmcvY2VsbHMuc3ZnXCIgY2xhc3M9XCJ0b29sYmFyLWljb24tZGFya1wiIHN0eWxlPVwid2lkdGg6IDFyZW1cIi8+XG4gICAgICA8L3AtYnV0dG9uPlxuXG4gICAgICA8IS0tIFlPTE86IGRldGVjdCBvYmplY3RzIGFjcm9zcyB0aGUgY3VycmVudCB2aWV3LiBVbmxpa2UgdGhlIFNBTSBhbmRcbiAgICAgICAgICAgY2VsbHBvc2UgdG9vbHMgaXQgdGFrZXMgbm8gcHJvbXB0IOKAlCBhIGRldGVjdG9yIGZpbmRzIG9iamVjdHMgaW4gYVxuICAgICAgICAgICBmaWVsZCByYXRoZXIgdGhhbiBiZWluZyBwb2ludGVkIGF0IG9uZSDigJQgc28gdGhlcmUgaXMgbm90aGluZyB0byBkcmF3XG4gICAgICAgICAgIGZpcnN0LiBTcGxpdCBidXR0b246IHJ1biwgcGljayBjaGVja3BvaW50LCBvcGVuIHBhcmFtZXRlcnMuIC0tPlxuICAgICAgPHNwYW4gKm5nSWY9XCJ5b2xvTW9kZWxzLmxlbmd0aFwiIGNsYXNzPVwicC1idXR0b25zZXQgc2VnbWVudC1zcGxpdFwiPlxuICAgICAgICA8cC1idXR0b24gaWNvbj1cInBpIHBpLW1pY3JvY2hpcC1haVwiIHNpemU9XCJzbWFsbFwiIFt0ZXh0XT1cInRydWVcIlxuICAgICAgICAgICAgICAgICAgcFRvb2x0aXA9XCJEZXRlY3Qgb2JqZWN0cyBpbiB0aGUgY3VycmVudCB2aWV3IHdpdGggWU9MTyAoY2xpZW50LXNpZGUpXCJcbiAgICAgICAgICAgICAgICAgIHRvb2x0aXBQb3NpdGlvbj1cImJvdHRvbVwiXG4gICAgICAgICAgICAgICAgICAob25DbGljayk9XCJkZXRlY3RZb2xvLmVtaXQoKVwiPlxuICAgICAgICA8L3AtYnV0dG9uPlxuICAgICAgICA8cC1idXR0b24gaWNvbj1cInBpIHBpLWNoZXZyb24tZG93blwiIHNpemU9XCJzbWFsbFwiIFt0ZXh0XT1cInRydWVcIlxuICAgICAgICAgICAgICAgICAgcFRvb2x0aXA9XCJQaWNrIHRoZSBZT0xPIG1vZGVsXCJcbiAgICAgICAgICAgICAgICAgIHRvb2x0aXBQb3NpdGlvbj1cImJvdHRvbVwiXG4gICAgICAgICAgICAgICAgICAob25DbGljayk9XCJ5b2xvTW9kZWxNZW51LnRvZ2dsZSgkZXZlbnQpXCI+XG4gICAgICAgIDwvcC1idXR0b24+XG4gICAgICAgIDxwLWJ1dHRvbiBpY29uPVwicGkgcGktc2xpZGVycy1oXCIgc2l6ZT1cInNtYWxsXCIgW3RleHRdPVwidHJ1ZVwiXG4gICAgICAgICAgICAgICAgICBwVG9vbHRpcD1cIllPTE8gcGFyYW1ldGVyc1wiXG4gICAgICAgICAgICAgICAgICB0b29sdGlwUG9zaXRpb249XCJib3R0b21cIlxuICAgICAgICAgICAgICAgICAgKG9uQ2xpY2spPVwib3BlbllvbG9QYXJhbXMuZW1pdCgpXCI+XG4gICAgICAgIDwvcC1idXR0b24+XG4gICAgICA8L3NwYW4+XG4gICAgICA8cC1tZW51ICN5b2xvTW9kZWxNZW51IFttb2RlbF09XCJ5b2xvTWVudUl0ZW1zXCIgW3BvcHVwXT1cInRydWVcIiBhcHBlbmRUbz1cImJvZHlcIj48L3AtbWVudT5cbiAgICA8L25nLWNvbnRhaW5lcj5cblxuICAgIDxuZy1jb250YWluZXIgKm5nSWY9XCJ0b29scy5oZWxwXCI+XG4gICAgICA8c3BhbiBjbGFzcz1cInRvb2xiYXItc2VwYXJhdG9yXCI+PC9zcGFuPlxuXG4gICAgICA8IS0tIEhlbHAgLS0+XG4gICAgICA8cC1idXR0b24gaWNvbj1cInBpIHBpLXF1ZXN0aW9uLWNpcmNsZVwiIHNpemU9XCJzbWFsbFwiIFt0ZXh0XT1cInRydWVcIlxuICAgICAgICAgICAgICAgIHBUb29sdGlwPVwiSGVscFwiIHRvb2x0aXBQb3NpdGlvbj1cImJvdHRvbVwiXG4gICAgICAgICAgICAgICAgKG9uQ2xpY2spPVwic2hvd0hlbHAoKVwiPlxuICAgICAgPC9wLWJ1dHRvbj5cbiAgICA8L25nLWNvbnRhaW5lcj5cblxuICA8L2Rpdj5cbjwvcC10b29sYmFyPlxuPHAtZGlhbG9nIGhlYWRlcj1cIlBsb3R0aW5nIGhlbHBcIiBbKHZpc2libGUpXT1cImRpc3BsYXlIZWxwRGlhbG9nXCIgW21vZGFsXT1cInRydWVcIiBbc3R5bGVdPVwie3dpZHRoOiAnNTB2dyd9XCJcbiAgICAgICAgICBbbWF4aW1pemFibGVdPVwidHJ1ZVwiIFtkcmFnZ2FibGVdPVwiZmFsc2VcIiBbcmVzaXphYmxlXT1cImZhbHNlXCI+XG4gIDxwPkNsaWNrIGEgZmlsZSBpbiB0aGUgbGVmdC1oYW5kIHRyZWUgdG8gZGlzcGxheSBpdC4gQnkgZGVmYXVsdCBpdCBvcGVucyBpbiB0aGVcbiAgICA8Yj5JbWFnZTwvYj4gdmlldyDigJQgYSBuYXRpdmVseSB0aWxlZCwgZGVlcGx5IHpvb21hYmxlIHJhc3RlciAoT3BlblNlYWRyYWdvbikgd2l0aCBhXG4gICAgbmF2aWdhdG9yIG1pbmltYXAgYW5kIGEgcGh5c2ljYWwtdW5pdHMgc2NhbGUgYmFyLiA8Yj5DbGljayB0aGUgaW1hZ2UgdG8gem9vbTwvYj4gdG93YXJkXG4gICAgdGhlIGNsaWNrZWQgcG9pbnQ7IHNjcm9sbCB0byB6b29tIGFuZCBkcmFnIHRvIHBhbi4gV2hlbiBhIHN0YWNrIGlzIG9wZW4sIGEgc2xpY2Ugc2xpZGVyXG4gICAgKEltYWdlIHZpZXcpIG9yIGltYWdlLW1vZGUgdG9nZ2xlIChvdGhlciB2aWV3cykgYXBwZWFycy4gUmVnaW9ucyBjYW4gYWxzbyBiZSBlZGl0ZWQgaW4gdGhlXG4gICAgPGI+UmVnaW9uIEVkaXRvcjwvYj4sIG9wZW5lZCB3aXRoIHRoZVxuICAgIDxpbWcgc3JjPVwiYXNzZXRzL3Bsb3R0aW5nL3JlZ2lvbi1lZGl0b3Iuc3ZnXCIgd2lkdGg9XCIxNFwiIGhlaWdodD1cIjE0XCIgYWx0PVwiUmVnaW9uIEVkaXRvclwiLz5cbiAgICB0b29sYmFyIGJ1dHRvbiBhcyBhIGZsb2F0aW5nIGRpYWxvZy4gRHJhZyB0aGUgPGkgY2xhc3M9XCJwaSBwaS1iYXJzXCI+PC9pPiBoYW5kbGUgdG8gZGV0YWNoXG4gICAgdGhpcyB0b29sYmFyIGludG8gYSBmbG9hdGluZyB3aW5kb3cuXG4gIDwvcD5cblxuICA8aDM+UGxvdC10eXBlIGRyb3Bkb3duPC9oMz5cbiAgPHA+XG4gICAgVGhlIDxiPnBsb3QtdHlwZSBkcm9wZG93bjwvYj4gc3dpdGNoZXMgaG93IHRoZSBmaWxlIGlzIHZpc3VhbGl6ZWQ6XG4gICAgPGI+SW1hZ2U8L2I+LCA8Yj5Db250b3VyPC9iPiwgPGI+U2NhdHRlciAocmVnaW9ucyk8L2I+LCA8Yj5TdXJmYWNlICgzRCk8L2I+LFxuICAgIDxiPlNjYXR0ZXIgM0Q8L2I+LCBhbmQgPGI+SXNvc3VyZmFjZSAoM0QpPC9iPi4gU2NhbGFyIGFuZCAzRCB0eXBlcyByZXF1aXJlIGEgZ3JheXNjYWxlXG4gICAgaW1hZ2UgKHRoZSAzRCB2b2x1bWUgdHlwZXMgYWxzbyByZXF1aXJlIGEgei1zdGFjaykuIFRoZSBub24taW1hZ2Ugdmlld3MgcmVuZGVyIHdpdGhcbiAgICBQbG90bHkgYW5kIHN1cHBvcnQgXCJyZWFsIHpvb21pbmdcIiAoYSBkb3duc2NhbGVkIG92ZXJ2aWV3IHRoYXQgcmUtZmV0Y2hlcyBoaWdoZXJcbiAgICByZXNvbHV0aW9uIGFzIHlvdSB6b29tIGluKS5cbiAgPC9wPlxuICA8dWw+XG4gICAgPGxpPlwiPGI+SXNvc3VyZmFjZSBiYW5kPC9iPlwiIHNsaWRlciAoSXNvc3VyZmFjZSk6IHNldHMgdGhlIGludGVuc2l0eSBiYW5kIHRoZSBzdXJmYWNlcyBhcmVcbiAgICAgIGRyYXduIGZvciwgbWFwcGVkIG9udG8gdGhlIHZvbHVtZSdzIHJlYWwgaW50ZW5zaXR5IHJhbmdlLjwvbGk+XG4gICAgPGxpPlwiPGI+M0QgY2FtZXJhPC9iPlwiIChTdXJmYWNlIC8gU2NhdHRlciAzRCAvIElzb3N1cmZhY2UpOiB6b29tIDxpIGNsYXNzPVwicGkgcGktc2VhcmNoXCI+PC9pPixcbiAgICAgIHBhbiA8aSBjbGFzcz1cInBpIHBpLWFycm93cy1hbHRcIj48L2k+LCBvcmJpdCA8aSBjbGFzcz1cInBpIHBpLWdsb2JlXCI+PC9pPixcbiAgICAgIHR1cm50YWJsZSA8aSBjbGFzcz1cInBpIHBpLXN5bmNcIj48L2k+LCBhbmQgcmVzZXQgY2FtZXJhIDxpIGNsYXNzPVwicGkgcGktaG9tZVwiPjwvaT47XG4gICAgICBzY3JvbGwgdG8gem9vbSB0aGUgM0Qgc2NlbmUuPC9saT5cbiAgPC91bD5cblxuICA8aDM+SW1hZ2UgbWFuaXB1bGF0aW9uIHRvb2xzPC9oMz5cbiAgPHVsPlxuICAgIDxsaT5cIjxiPlJlZ2lvbiBFZGl0b3I8L2I+XCIgPGltZyBzcmM9XCJhc3NldHMvcGxvdHRpbmcvcmVnaW9uLWVkaXRvci5zdmdcIiB3aWR0aD1cIjE1XCIgaGVpZ2h0PVwiMTVcIj46XG4gICAgICBvcGVucyB0aGUgUmVnaW9uIEVkaXRvciBhcyBhIGZsb2F0aW5nLCBkcmFnZ2FibGUgZGlhbG9nIOKAlCBhIHBlci1pbWFnZSB0YWJsZSBvZiBldmVyeSByZWdpb25cbiAgICAgIChjbGFzcywgY29sb3VyLCBhcmVhKSB3aXRoIGJ1bGsgc2VsZWN0L2xhYmVsL2NvbG91ci9kZWxldGUgYW5kIEdlb0pTT04gaW1wb3J0L2V4cG9ydC4gSXQgc3RheXNcbiAgICAgIGluIHN5bmMgd2l0aCB0aGUgaW1hZ2UsIHNvIHlvdSBjYW4ga2VlcCBpdCBvcGVuIHdoaWxlIGRyYXdpbmcuPC9saT5cbiAgICA8bGk+XCI8Yj5JbnRlbnNpdHkgcHJvZmlsZSBsaW5lczwvYj5cIiA8aW1nIHNyYz1cImFzc2V0cy9wbG90dGluZy9zcGxpbmUtY2hhcnQuc3ZnXCIgd2lkdGg9XCIxNVwiIGhlaWdodD1cIjE1XCI+XG4gICAgICAoPGk+d29yayBpbiBwcm9ncmVzcyDigJQgbm90IHlldCBzdGFibGU8L2k+KTogYWRkcyBhIGNvbG91cmVkIGxpbmUgUk9JOyBhIGZsb2F0aW5nIGluc2V0XG4gICAgICBjaGFydCBwbG90cyBpbnRlbnNpdHkgYWxvbmcgZWFjaCBsaW5lLCB1cGRhdGVzIGxpdmUgYXMgeW91IGRyYWcgaXQsIGFuZCByZS1zYW1wbGVzIGF0IHRoZVxuICAgICAgY3VycmVudCB6b29tIHJlc29sdXRpb24uIEVhY2ggYWRkZWQgbGluZSBnZXRzIGl0cyBvd24gY29sb3VyOyBzZWxlY3QgYSBsaW5lIGFuZCB1c2UgdGhlXG4gICAgICB0cmFzaCB0b29sIDxpIGNsYXNzPVwicGkgcGktdHJhc2hcIj48L2k+IHRvIHJlbW92ZSBpdCAoYW5kIGl0cyB0cmFjZSkuPC9saT5cbiAgICA8bGk+XCI8Yj5DaGFubmVscyAmYW1wOyBIaXN0b2dyYW08L2I+XCIgPGltZyBzcmM9XCJhc3NldHMvcGxvdHRpbmcvaW52ZXJ0LnN2Z1wiIHdpZHRoPVwiMTVcIiBoZWlnaHQ9XCIxNVwiPjpcbiAgICAgIG9wZW5zIGEgZmxvYXRpbmcgZGlhbG9nIGZvciBicmlnaHRuZXNzL2NvbnRyYXN0LCBnYW1tYSwgYW5kIHBlci1jaGFubmVsIGRpc3BsYXksIHBsdXMgdGhlXG4gICAgICA8Yj5jb2xvcm1hcCAvIExVVDwvYj4gY29udHJvbHMgKGdyYXlzY2FsZSBvbmx5KSDigJQgYSA8Yj5yZXZlcnNlPC9iPiB0b2dnbGUgYW5kIGEgY29sb3JtYXBcbiAgICAgIGRyb3Bkb3duIChkZWZhdWx0OiBpbnZlcnRlZCBncmV5cykuPC9saT5cbiAgICA8bGk+XCI8Yj5TbW9vdGhlbjwvYj5cIiA8aW1nIHNyYz1cImFzc2V0cy9wbG90dGluZy9zbW9vdGguc3ZnXCIgd2lkdGg9XCIxNVwiIGhlaWdodD1cIjE1XCI+XG4gICAgICAoSW1hZ2UgdmlldyBvbmx5KTogdGhlIGltYWdlIG9wZW5zIHNob3dpbmcgPGI+cmF3IHBpeGVsczwvYj4gKG5lYXJlc3QtbmVpZ2hib3VyKSwgc29cbiAgICAgIHpvb21pbmcgaW4gcmVuZGVycyBjcmlzcCBibG9ja3MgZm9yIHBpeGVsIGluc3BlY3Rpb24uIFRvZ2dsZSB0aGlzIG9uIGZvciBiaWxpbmVhclxuICAgICAgc21vb3RoaW5nOyB0aGUgaWNvbiB0dXJucyB3aGl0ZSB3aGlsZSBzbW9vdGhpbmcgaXMgYWN0aXZlLjwvbGk+XG4gIDwvdWw+XG5cbiAgPGgzPkRvd25sb2FkICZhbXA7IHZpZXcgdG9vbHM8L2gzPlxuICA8dWw+XG4gICAgPGxpPlwiPGI+RG93bmxvYWQ8L2I+XCIgPGkgY2xhc3M9XCJwaSBwaS1kb3dubG9hZFwiPjwvaT46IGRvd25sb2FkIHRoZSBjdXJyZW50IGltYWdlIGFzIGEgUE5HLjwvbGk+XG4gICAgPGxpPlwiPGI+Rml0IHRvIHZpZXc8L2I+XCIgLyBhdXRvc2NhbGUgPGkgY2xhc3M9XCJwaSBwaS13aW5kb3ctbWF4aW1pemVcIj48L2k+OiByZXNldHMgdGhlIGltYWdlXG4gICAgICB0byBpdHMgb3JpZ2luYWwgZGltZW5zaW9ucy4gSW4gc3RhY2sgbW9kZSBpdCBhbHNvIHJlc2V0cyB0byBzaW5nbGUtaW1hZ2UgbW9kZS48L2xpPlxuICA8L3VsPlxuXG4gIDxoMz5ab29tIHRvb2xzPC9oMz5cbiAgPHVsPlxuICAgIDxsaT5cIjxiPlpvb20gYm94PC9iPlwiIDxpbWcgc3JjPVwiYXNzZXRzL3Bsb3R0aW5nL3pvb20tYm94LnN2Z1wiIHdpZHRoPVwiMTVcIiBoZWlnaHQ9XCIxNVwiPlxuICAgICAgKDxrYmQ+Yjwva2JkPik6IGRyYWcgYSBib3ggdG8gc2VsZWN0IGFuIGFyZWEgdG8gem9vbSBpbnRvLjwvbGk+XG4gICAgPGxpPlwiPGI+UGFuPC9iPlwiIDxpIGNsYXNzPVwicGkgcGktYXJyb3dzLWFsdFwiPjwvaT4gKDxrYmQ+cDwva2JkPik6IGRyYWcgdG8gcGFuIHRoZSBpbWFnZS48L2xpPlxuICAgIDxsaT5cIjxiPlpvb20gaW48L2I+XCIgPGkgY2xhc3M9XCJwaSBwaS1zZWFyY2gtcGx1c1wiPjwvaT4gKDxrYmQ+Kzwva2JkPik6IHpvb20gaW4gb25lIHN0ZXAuPC9saT5cbiAgICA8bGk+XCI8Yj5ab29tIG91dDwvYj5cIiA8aSBjbGFzcz1cInBpIHBpLXNlYXJjaC1taW51c1wiPjwvaT4gKDxrYmQ+LTwva2JkPik6IHpvb20gb3V0IG9uZSBzdGVwLFxuICAgICAgc3RvcHBpbmcgYXQgdGhlIG9yaWdpbmFsIGltYWdlIGRpbWVuc2lvbnMuPC9saT5cbiAgPC91bD5cblxuICA8aDM+UmVnaW9uIHRvb2xzPC9oMz5cbiAgPHA+QnkgZGVmYXVsdCBubyB0b29sIGlzIHNlbGVjdGVkIChhbGwgZ3JleWVkLW91dCk7IHNlbGVjdGluZyBhIHRvb2wgaGlnaGxpZ2h0cyBpdC4gVGhlXG4gICAgdmVydGV4LWVkaXRpbmcgdG9vbHMgKHBvbHlnb24sIGFkZC9kZWxldGUgdmVydGV4LCBtb3ZlLCBCw6l6aWVyIGNvbnZlcnNpb25zKSBhcmUgYXZhaWxhYmxlXG4gICAgaW4gdGhlIDxiPkltYWdlPC9iPiB2aWV3IG9ubHkuIE9uIGEgPGI+ZG9udXQ8L2I+IChhIHJlZ2lvbiB3aXRoIGEgaG9sZSksIHRoZSBpbm5lci1yaW5nXG4gICAgdmVydGljZXMgYXJlIGVkaXRhYmxlIHRvbyDigJQgZHJhZywgYWRkLCBhbmQgZGVsZXRlIHRoZW0ganVzdCBsaWtlIHRoZSBvdXRlciByaW5nLjwvcD5cbiAgPHVsPlxuICAgIDxsaT5cIjxiPlNlbGVjdGlvbjwvYj5cIiA8aSBjbGFzcz1cInBpIHBpLWFycm93LXVwLXJpZ2h0XCI+PC9pPiAoPGtiZD5zPC9rYmQ+KTogYSBuZXV0cmFsIG1vZGVcbiAgICAgIHRoYXQgdHVybnMgb2ZmIHdoYXRldmVyIGRyYXdpbmcgdG9vbCB3YXMgYWN0aXZlICh3YW5kLCB2ZXJ0ZXggZXJhc2VyLCByZWN0YW5nbGUsIGV0Yy4pLlxuICAgICAgVXNlIGl0IHRvIHN0ZXAgb3V0IG9mIGVkaXRpbmcgd2l0aG91dCBwaWNraW5nIGFub3RoZXIgdG9vbC48L2xpPlxuICAgIDxsaT5cIjxiPlJlY3Rhbmd1bGFyIHJlZ2lvbjwvYj5cIiA8aSBjbGFzcz1cInBpIHBpLXN0b3BcIj48L2k+ICg8a2JkPnI8L2tiZD4pOiBjbGljayBhbmQgZHJhZyB0b1xuICAgICAgY3JlYXRlIGEgcmVjdGFuZ3VsYXIgcmVnaW9uLjwvbGk+XG4gICAgPGxpPlwiPGI+UG9seWxpbmU8L2I+XCIgPGltZyBzcmM9XCJhc3NldHMvcGxvdHRpbmcvcG9seWxpbmUuc3ZnXCIgd2lkdGg9XCIxNFwiIGhlaWdodD1cIjE0XCI+XG4gICAgICAoPGtiZD5sPC9rYmQ+KTogZHJhdyBhbiBvcGVuIChub24tY2xvc2VkKSBwb2x5bGluZSBieSBjbGlja2luZyBhbmQgZHJhZ2dpbmcuIFBvbHlsaW5lcyBjYW5cbiAgICAgIGJlIGVkaXRlZCBhbmQgZXhwb3J0ZWQgdG8gR2VvSlNPTiBhcyBhIDxjb2RlPkxpbmVTdHJpbmc8L2NvZGU+IGZlYXR1cmUuPC9saT5cbiAgICA8bGk+XCI8Yj5GcmVlZm9ybSByZWdpb248L2I+XCIgPGkgY2xhc3M9XCJwaSBwaS1wZW5jaWxcIj48L2k+ICg8a2JkPmY8L2tiZD4pOiBjbGljayBhbmQgZHJhZyB0b1xuICAgICAgZHJhdyBhIGNsb3NlZCBmcmVlZm9ybSBwb2x5Z29uLjwvbGk+XG4gICAgPGxpPlwiPGI+QnJ1c2g8L2I+XCIgPGltZyBzcmM9XCJhc3NldHMvcGxvdHRpbmcvcGFpbnRicnVzaC5zdmdcIiB3aWR0aD1cIjE1XCIgaGVpZ2h0PVwiMTVcIj46IGNsaWNrXG4gICAgICBhbmQgZHJhZyB0byBwYWludCBhIHJlZ2lvbiB3aXRoIGEgY2lyY3VsYXIgYnJ1c2gg4oCUIHRoZSBwYWludGVkIHN0cm9rZSBpcyB0aGUgcmVnaW9uLlxuICAgICAgQSA8Yj5TaXplPC9iPiBzbGlkZXIgKGJydXNoIGRpYW1ldGVyLCBpbiBpbWFnZSBwaXhlbHMpIGFwcGVhcnMgaW4gdGhlIHRvb2xiYXIgd2hpbGUgdGhlXG4gICAgICBicnVzaCBpcyBhY3RpdmUuIEhvbGQgPGtiZD5TaGlmdDwva2JkPiB3aGlsZSBwYWludGluZyB0byA8Yj5lcmFzZTwvYj4gKHN1YnRyYWN0KSBmcm9tIGFcbiAgICAgIHJlZ2lvbiBpbnN0ZWFkLiBQYWludGluZyBvdmVyIGFuIGV4aXN0aW5nIHJlZ2lvbiBleHRlbmRzIGl0OyBicnVzaGluZyBhY3Jvc3MgdHdvIHJlZ2lvbnNcbiAgICAgIG1lcmdlcyB0aGVtLiBQYWludGluZyBhIGNsb3NlZCByaW5nIChvciBzaGlmdC1lcmFzaW5nIGFuIGludGVyaW9yIHBvY2tldCkgbGVhdmVzIHRoZVxuICAgICAgZW5jbG9zZWQgYXJlYSBlbXB0eSDigJQgYSA8Yj5kb251dDwvYj4gKEltYWdlIHZpZXcpLjwvbGk+XG4gICAgPGxpPlwiPGI+UG9seWdvbjwvYj5cIiA8aW1nIHNyYz1cImFzc2V0cy9wbG90dGluZy9wb2x5Z29uLXZlcnRpY2VzLnN2Z1wiIHdpZHRoPVwiMTVcIiBoZWlnaHQ9XCIxNVwiPjpcbiAgICAgIGNsaWNrIHRvIHBsYWNlIGVhY2ggdmVydGV4OyBjbGljayB0aGUgZmlyc3QgcG9pbnQgdG8gY2xvc2UgdGhlIHBvbHlnb24uPC9saT5cbiAgICA8bGk+XCI8Yj5BZGQgdmVydGV4PC9iPlwiIDxpbWcgc3JjPVwiYXNzZXRzL3Bsb3R0aW5nL3ZlcnRleC1hZGQuc3ZnXCIgd2lkdGg9XCIxNVwiIGhlaWdodD1cIjE1XCI+OlxuICAgICAgY2xpY2sgYW4gZWRnZSBvZiB0aGUgc2VsZWN0ZWQgcG9seWdvbiB0byBpbnNlcnQgYSBuZXcgdmVydGV4LjwvbGk+XG4gICAgPGxpPlwiPGI+RGVsZXRlIHZlcnRleDwvYj5cIiA8aW1nIHNyYz1cImFzc2V0cy9wbG90dGluZy92ZXJ0ZXgtZGVsZXRlLnN2Z1wiIHdpZHRoPVwiMTVcIiBoZWlnaHQ9XCIxNVwiPjpcbiAgICAgIGNsaWNrIGEgdmVydGV4IG9mIHRoZSBzZWxlY3RlZCBwb2x5Z29uIHRvIHJlbW92ZSBpdC48L2xpPlxuICAgIDxsaT5cIjxiPk1vdmUgcmVnaW9uPC9iPlwiIDxpbWcgc3JjPVwiYXNzZXRzL3Bsb3R0aW5nL3JlZ2lvbi1tb3ZlLnN2Z1wiIHdpZHRoPVwiMTVcIiBoZWlnaHQ9XCIxNVwiPjpcbiAgICAgIGRyYWcgdG8gbW92ZSB0aGUgc2VsZWN0ZWQgcmVnaW9uLjwvbGk+XG4gICAgPGxpPlwiPGI+VG8gQsOpemllcjwvYj5cIiA8aW1nIHNyYz1cImFzc2V0cy9wbG90dGluZy90by1iZXppZXIuc3ZnXCIgd2lkdGg9XCIxNVwiIGhlaWdodD1cIjE1XCI+IC9cbiAgICAgIFwiPGI+VG8gcG9seWdvbjwvYj5cIiA8aW1nIHNyYz1cImFzc2V0cy9wbG90dGluZy90by1wb2x5Z29uLnN2Z1wiIHdpZHRoPVwiMTVcIiBoZWlnaHQ9XCIxNVwiPjpcbiAgICAgIGNvbnZlcnQgdGhlIHNlbGVjdGVkIHJlZ2lvbiB0byBhIHNtb290aCBCw6l6aWVyIGN1cnZlLCBvciBiYWNrIHRvIGEgc3RyYWlnaHQtZWRnZWQgcG9seWdvbi48L2xpPlxuICAgIDxsaT5cIjxiPldhbmQ8L2I+XCIgPGltZyBzcmM9XCJhc3NldHMvcGxvdHRpbmcvd2FuZC5zdmdcIiB3aWR0aD1cIjE1XCIgaGVpZ2h0PVwiMTVcIj4gKDxrYmQ+dzwva2JkPik6XG4gICAgICBjbGljayB0byBncm93IGEgcmVnaW9uIGZyb20gcGl4ZWxzIHdpdGggc2ltaWxhciB2YWx1ZXMgYXJvdW5kIHRoZSBjbGljayBwb2ludC4gSG9sZFxuICAgICAgPGtiZD5DdHJsPC9rYmQ+LzxrYmQ+Q21kPC9rYmQ+IGZvciBhbiBleGFjdC1tYXRjaCBmbG9vZCBmaWxsOyBkcmFnIHRvIGtlZXAgZXh0ZW5kaW5nIHRoZVxuICAgICAgc2VsZWN0aW9uIHdoaWxlIG1vdmluZzsgaG9sZCA8a2JkPlNoaWZ0PC9rYmQ+IHdoaWxlIGNsaWNraW5nIG9yIGRyYWdnaW5nIG9uIGFuIGV4aXN0aW5nXG4gICAgICByZWdpb24gdG8gPGI+ZXJhc2U8L2I+IHBpeGVscyBmcm9tIGl0LiBVc2UgdGhlIDxiPlNlbnMuPC9iPiBzbGlkZXIgdGhhdCBhcHBlYXJzIHRvIG1ha2UgdGhlXG4gICAgICB3YW5kIHN0cmljdGVyIChoaWdoZXIgPSBzbWFsbGVyIHNlbGVjdGlvbikgb3IgbG9vc2VyLiBTdWJzZXF1ZW50IGNsaWNrcyBrZWVwIGV4dGVuZGluZyB0aGVcbiAgICAgIHNhbWUgcmVnaW9uOyBjbGlja2luZyBpbnNpZGUgYW4gZXhpc3RpbmcgcmVnaW9uIGFkb3B0cyBpdCwgYW5kIGdyb3dpbmcgaW50byBhbm90aGVyIHJlZ2lvblxuICAgICAgbWVyZ2VzIHRoZW0uPC9saT5cbiAgICA8bGk+XCI8Yj5WZXJ0ZXggZXJhc2VyPC9iPlwiIDxpIGNsYXNzPVwicGkgcGktZXJhc2VyXCI+PC9pPiAoPGtiZD5lPC9rYmQ+KTogY2xpY2sgb3IgY2xpY2stYW5kLWRyYWdcbiAgICAgIHRvIHJlbW92ZSBwb2x5Z29uIHZlcnRpY2VzIHRoYXQgZmFsbCBpbnNpZGUgdGhlIGN1cnNvciBjaXJjbGUgZnJvbSBhbnkgcmVnaW9uLCBpbmNsdWRpbmcgYVxuICAgICAgcmVnaW9uJ3MgaW5uZXIgcmluZ3MgKGRvbnV0IGhvbGVzKS4gVXNlIHRoZSA8Yj5SYWRpdXM8L2I+IHNsaWRlciB0byBzZXQgdGhlIGVyYXNlciBzaXplIGluXG4gICAgICBpbWFnZSBwaXhlbHMuIFBvbHlnb25zIHJlZHVjZWQgYmVsb3cgMyB2ZXJ0aWNlcyAob3IgcG9seWxpbmVzIGJlbG93IDIpIGFyZSBkZWxldGVkIGVudGlyZWx5O1xuICAgICAgYSBob2xlIHJlZHVjZWQgYmVsb3cgMyB2ZXJ0aWNlcyBpcyByZW1vdmVkLjwvbGk+XG4gICAgPGxpPlwiPGI+VW5kbzwvYj5cIiA8aW1nIHNyYz1cImFzc2V0cy9wbG90dGluZy91bmRvLnN2Z1wiIHdpZHRoPVwiMTVcIiBoZWlnaHQ9XCIxNVwiPlxuICAgICAgKDxrYmQ+Q3RybDwva2JkPi88a2JkPkNtZDwva2JkPis8a2JkPlo8L2tiZD4pOiByZXZlcnRzIHRoZVxuICAgICAgbGFzdCByZWdpb24gYWN0aW9uIChkcmF3LCBkZWxldGUsIGVkaXQsIHdhbmQvYnJ1c2ggc3Ryb2tlLCBzZWdtZW50YXRpb24sIOKApikuIFVwIHRvIHRoZSBsYXN0XG4gICAgICA8Yj4xMDwvYj4gYWN0aW9ucyBjYW4gYmUgdW5kb25lOyB0aGUgYnV0dG9uIGdyZXlzIG91dCB3aGVuIHRoZXJlJ3Mgbm90aGluZyBsZWZ0IHRvIHVuZG8uPC9saT5cbiAgICA8bGk+XCI8Yj5SZWRvPC9iPlwiIDxpbWcgc3JjPVwiYXNzZXRzL3Bsb3R0aW5nL3JlZG8uc3ZnXCIgd2lkdGg9XCIxNVwiIGhlaWdodD1cIjE1XCI+XG4gICAgICAoPGtiZD5DdHJsPC9rYmQ+LzxrYmQ+Q21kPC9rYmQ+KzxrYmQ+U2hpZnQ8L2tiZD4rPGtiZD5aPC9rYmQ+IG9yIDxrYmQ+Q3RybDwva2JkPi88a2JkPkNtZDwva2JkPis8a2JkPlk8L2tiZD4pOlxuICAgICAgcmUtYXBwbGllcyB0aGUgbW9zdCByZWNlbnRseSB1bmRvbmUgYWN0aW9uLiBQZXJmb3JtaW5nIGEgbmV3IHJlZ2lvbiBhY3Rpb24gY2xlYXJzIHdoYXQgY2FuIGJlIHJlZG9uZS48L2xpPlxuICAgIDxsaT5cIjxiPkRlbGV0ZSByZWdpb248L2I+XCIgPGkgY2xhc3M9XCJwaSBwaS10cmFzaFwiPjwvaT4gKDxrYmQ+RGVsPC9rYmQ+KTogZGVsZXRlcyB0aGUgc2VsZWN0ZWRcbiAgICAgIHJlZ2lvbi4gVGhpcyBjYW4gYWxzbyBiZSBkb25lIGZyb20gdGhlIFJlZ2lvbiBFZGl0b3IuPC9saT5cbiAgPC91bD5cblxuICA8aDM+UmVnaW9uIGFjdGlvbnMgKHJpZ2h0LWNsaWNrIHRoZSBpbWFnZSk8L2gzPlxuICA8cD5SaWdodC1jbGljayBhIHJlZ2lvbiDigJQgb3IgdGhlIGN1cnJlbnQgbXVsdGktc2VsZWN0aW9uIOKAlCB0byBvcGVuIHRoZVxuICAgIFwiPGI+U2VsZWN0ZWQgcmVnaW9uKHMpPC9iPlwiIGdyb3VwIGF0IHRoZSB0b3Agb2YgdGhlIGNvbnRleHQgbWVudS4gVGhlc2UgYWN0IGltbWVkaWF0ZWx5IG9uXG4gICAgdGhlIHNlbGVjdGlvbiAodGhleSdyZSBub3QgZHJhd2luZyBtb2RlcykgYW5kIGFyZSBmdWxseSB1bmRvL3JlZG8tYWJsZTo8L3A+XG4gIDx1bD5cbiAgICA8bGk+XCI8Yj5TZWxlY3QgYWxsIHJlZ2lvbnM8L2I+XCIgPGkgY2xhc3M9XCJwaSBwaS1jaGVjay1zcXVhcmVcIj48L2k+OiBzZWxlY3QgZXZlcnkgcmVnaW9uIG9uXG4gICAgICB0aGUgaW1hZ2UgKGFsc28gaW4gdGhlIFJlZ2lvbiBFZGl0b3IgdG9vbGJhcikuPC9saT5cbiAgICA8bGk+XCI8Yj5NZXJnZSAvIGdyb3VwPC9iPlwiOiBjb21iaW5lIDIrIHNlbGVjdGVkIHJlZ2lvbnMgaW50byBvbmUuIElmIHRoZSBwaWVjZXMgZG9uJ3QgdG91Y2gsXG4gICAgICB0aGUgcmVzdWx0IGlzIGEgc2luZ2xlIG11bHRpLXBhcnQgcmVnaW9uIChzcGFyc2UgXCJiaXRzXCIpLjwvbGk+XG4gICAgPGxpPlwiPGI+VW5ncm91cDwvYj5cIjogc3BsaXQgYSBtdWx0aS1wYXJ0IHJlZ2lvbiBiYWNrIGludG8gb25lIHJlZ2lvbiBwZXIgcGllY2UuPC9saT5cbiAgICA8bGk+XCI8Yj5JbnZlcnNlPC9iPlwiOiByZXBsYWNlIHRoZSBzZWxlY3Rpb24gd2l0aCBldmVyeXRoaW5nIDxpPm91dHNpZGU8L2k+IGl0IOKAlCB0aGUgaW1hZ2VcbiAgICAgIHJlY3RhbmdsZSB3aXRoIHRoZSBzZWxlY3RlZCByZWdpb25zIHB1bmNoZWQgb3V0IGFzIGhvbGVzLiBXb3JrcyBhdCBhbnkgaW1hZ2Ugc2l6ZS48L2xpPlxuICAgIDxsaT5cIjxiPlNpbXBsaWZ5PC9iPlwiOiByZWR1Y2UgYSByZWdpb24ncyB2ZXJ0ZXggY291bnQgKERvdWdsYXPigJNQZXVja2VyKSBieSBhbiBhbHRpdHVkZVxuICAgICAgdGhyZXNob2xkIGluIHBpeGVscyDigJQgcGljayA8Yj5MaWdodDwvYj4gKDEmbmJzcDtweCksIDxiPk1lZGl1bTwvYj4gKDMmbmJzcDtweCksXG4gICAgICA8Yj5TdHJvbmc8L2I+ICg4Jm5ic3A7cHgpLCBvciA8Yj5DdXN0b23igKY8L2I+IGZvciBhIHNsaWRlci4gQXBwbGllcyB0byB0aGUgZXh0ZXJpb3IgYW5kIGFueVxuICAgICAgaG9sZXMgKGFuZCBldmVyeSBwYXJ0IG9mIGEgbXVsdGktcGFydCByZWdpb24pLjwvbGk+XG4gICAgPGxpPlRoZSBCw6l6aWVyIGNvbnZlcnNpb25zIGFuZCBEZWxldGUgYWxzbyBsaXZlIGluIHRoaXMgZ3JvdXAgKHRoZSByaWdodC1jbGljayBlcXVpdmFsZW50cyBvZlxuICAgICAgdGhlIHRvb2xiYXIgLyB2ZXJ0ZXggdG9vbHMgYWJvdmUpLjwvbGk+XG4gIDwvdWw+XG5cbiAgPGgzPlNlZ21lbnRhdGlvbiB0b29scyAoU0FNICZhbXA7IGNlbGxwb3NlKTwvaDM+XG4gIDxwPkFJLWFzc2lzdGVkIHNlZ21lbnRhdGlvbiB0aGF0IHJ1bnMgZW50aXJlbHkgaW4gdGhlIGJyb3dzZXIuIFRoZSBib3ggYW5kIHBvaW50XG4gICAgdG9vbHMgdXNlIGEgcHJvbXB0YWJsZSA8Yj5taWNyby1zYW08L2I+IG1vZGVsOyB0aGUgY2VsbHBvc2UgdG9vbCBhdXRvLWRldGVjdHMgY2VsbHMuXG4gICAgR2VuZXJhdGVkIHJlZ2lvbnMgaW5oZXJpdCB0aGUgY29sb3Igb2YgdGhlIHJlY3RhbmdsZSB0aGV5IGNhbWUgZnJvbS4gVGhlIGZpcnN0IHJ1blxuICAgIGRvd25sb2FkcyB0aGUgbW9kZWwgKGEgcHJvZ3Jlc3MgdG9hc3QgaXMgc2hvd24pOyBpdCdzIHRoZW4gY2FjaGVkLjwvcD5cbiAgPHVsPlxuICAgIDxsaT5cIjxiPlNlZ21lbnQ8L2I+XCIgPGltZyBzcmM9XCJhc3NldHMvcGxvdHRpbmcvcGljdHVyZS1zZWdtZW50YXRpb24uc3ZnXCIgd2lkdGg9XCIxNVwiIGhlaWdodD1cIjE1XCI+XG4gICAgICAoYm94IHByb21wdCk6IGRyYXcgb25lIG9yIG1vcmUgPGI+UmVjdGFuZ3VsYXIgcmVnaW9uczwvYj4gYXJvdW5kIHRoZSBvYmplY3RzIG9mIGludGVyZXN0LFxuICAgICAgdGhlbiBjbGljayBTZWdtZW50LiBFYWNoIHJlY3RhbmdsZSBpcyByZXBsYWNlZCBieSB0aGUgU0FNIG1hc2sgb2YgdGhlIG9iamVjdCBpbnNpZGUgaXQuPC9saT5cbiAgICA8bGk+XCI8Yj5Qb2ludCBwcm9tcHRzPC9iPlwiIDxpbWcgc3JjPVwiYXNzZXRzL3Bsb3R0aW5nL2NsaWNrLnN2Z1wiIHdpZHRoPVwiMTVcIiBoZWlnaHQ9XCIxNVwiPjogY2xpY2sgZGlyZWN0bHkgb24gYW4gb2JqZWN0IHRvXG4gICAgICBzZWdtZW50IGl0IGFzIGEgPGI+bmV3PC9iPiByZWdpb24uIEVhY2ggY2xpY2sgc3RhcnRzIGEgZnJlc2ggb2JqZWN0IOKAlCBjbGlja2luZyBhbm90aGVyXG4gICAgICBvYmplY3Qgd29uJ3QgZ3JvdyB0aGUgcHJldmlvdXMgb25lLiA8a2JkPlNoaWZ0PC9rYmQ+LzxrYmQ+QWx0PC9rYmQ+LWNsaWNrIGFkZHMgYW5cbiAgICAgIDxpPmV4Y2x1ZGU8L2k+IHBvaW50IHRoYXQgcmVmaW5lcyB0aGUgY3VycmVudCBvYmplY3Q7IDxrYmQ+RXNjPC9rYmQ+IHVuZG9lcyBpdCxcbiAgICAgIDxrYmQ+RW50ZXI8L2tiZD4gY29tbWl0cyBpdC48L2xpPlxuICAgIDxsaT5cIjxiPk1vZGVsIHBpY2tlcjwvYj5cIiA8aSBjbGFzcz1cInBpIHBpLWNoZXZyb24tZG93blwiPjwvaT46IHRoZSBkcm9wZG93biBhdHRhY2hlZCB0byB0aGVcbiAgICAgIFNlZ21lbnQgYnV0dG9uIGNob29zZXMgdGhlIFNBTSBtb2RlbCDigJQgPGI+bWljcm8tc2FtIFZpVC1UPC9iPiAoZGVmYXVsdDsgc21hbGwsIGZhc3QpIG9yXG4gICAgICA8Yj5taWNyby1zYW0gVmlULUI8L2I+IChsYXJnZXIsIGhpZ2hlc3QgYWNjdXJhY3kpIGZvciBsaWdodCBtaWNyb3Njb3B5LCBvclxuICAgICAgPGI+cGF0aG8tc2FtIFZpVC1CPC9iPiBmb3IgaGlzdG9wYXRob2xvZ3kgKEgmYW1wO0UgLyBzdGFpbmVkIHRpc3N1ZSkuIFRoZSBjaG9pY2UgYXBwbGllcyB0b1xuICAgICAgPGk+Ym90aDwvaT4gdGhlIGJveCBhbmQgcG9pbnQgdG9vbHMuIFRoZSA8aSBjbGFzcz1cInBpIHBpLWluZm8tY2lyY2xlXCI+PC9pPiBpbmZvIGJ1dHRvbiBuZXh0XG4gICAgICB0byBpdCBleHBsYWlucyB0aGUgdHJhZGUtb2ZmLjwvbGk+XG4gICAgPGxpPlwiPGI+Q2VsbHBvc2U8L2I+XCIgPGltZyBzcmM9XCJhc3NldHMvcGxvdHRpbmcvY2VsbHMuc3ZnXCIgd2lkdGg9XCIxNVwiIGhlaWdodD1cIjE1XCI+OiBkcmF3IHJlY3RhbmdsZXMsIHRoZW4gY2xpY2sgQ2VsbHBvc2UgdG9cbiAgICAgIGF1dG9tYXRpY2FsbHkgc2VnbWVudCBldmVyeSBjZWxsIGluc2lkZSBlYWNoIHJlY3RhbmdsZSAoY2xpZW50LXNpZGUgY2VsbHBvc2UtU0FNKSDigJQgb25lXG4gICAgICByZWdpb24gcGVyIGRldGVjdGVkIGNlbGwuIE5vIGNsaWNraW5nIG9uIGluZGl2aWR1YWwgY2VsbHMgbmVlZGVkLjwvbGk+XG4gIDwvdWw+XG5cbiAgPGgzPkhlbHA8L2gzPlxuICA8cD5cIjxiPkhlbHA8L2I+XCIgPGkgY2xhc3M9XCJwaSBwaS1xdWVzdGlvbi1jaXJjbGVcIj48L2k+OiBvcGVucyB0aGlzIGRpYWxvZy48L3A+XG5cbiAgPGgzPktleWJvYXJkIHNob3J0Y3V0czwvaDM+XG4gIDx0YWJsZSBjbGFzcz1cInNob3J0Y3V0cy10YWJsZVwiPlxuICAgIDx0cj48dGQ+PGtiZD4rPC9rYmQ+IC8gPGtiZD49PC9rYmQ+PC90ZD48dGQ+Wm9vbSBpbjwvdGQ+PC90cj5cbiAgICA8dHI+PHRkPjxrYmQ+LTwva2JkPjwvdGQ+PHRkPlpvb20gb3V0PC90ZD48L3RyPlxuICAgIDx0cj48dGQ+PGtiZD5iPC9rYmQ+PC90ZD48dGQ+VG9nZ2xlIHpvb20gYm94IG1vZGU8L3RkPjwvdHI+XG4gICAgPHRyPjx0ZD48a2JkPnA8L2tiZD48L3RkPjx0ZD5Ub2dnbGUgcGFuIG1vZGU8L3RkPjwvdHI+XG4gICAgPHRyPjx0ZD48a2JkPmY8L2tiZD48L3RkPjx0ZD5Ub2dnbGUgZnJlZWZvcm0gcmVnaW9uIGRyYXdpbmc8L3RkPjwvdHI+XG4gICAgPHRyPjx0ZD48a2JkPmw8L2tiZD48L3RkPjx0ZD5Ub2dnbGUgcG9seWxpbmUgZHJhd2luZzwvdGQ+PC90cj5cbiAgICA8dHI+PHRkPjxrYmQ+cjwva2JkPjwvdGQ+PHRkPlRvZ2dsZSByZWN0YW5ndWxhciByZWdpb24gZHJhd2luZzwvdGQ+PC90cj5cbiAgICA8dHI+PHRkPjxrYmQ+dzwva2JkPjwvdGQ+PHRkPlRvZ2dsZSB3YW5kIHRvb2w8L3RkPjwvdHI+XG4gICAgPHRyPjx0ZD5XYW5kICsgPGtiZD5TaGlmdDwva2JkPjwvdGQ+PHRkPkVyYXNlIHBpeGVscyBmcm9tIHRoZSBhY3RpdmUgcmVnaW9uPC90ZD48L3RyPlxuICAgIDx0cj48dGQ+V2FuZCArIDxrYmQ+Q3RybDwva2JkPi88a2JkPkNtZDwva2JkPjwvdGQ+PHRkPkV4YWN0LW1hdGNoIGZsb29kIGZpbGwgKG5vIHNtb290aGluZyk8L3RkPjwvdHI+XG4gICAgPHRyPjx0ZD48a2JkPmU8L2tiZD48L3RkPjx0ZD5Ub2dnbGUgdmVydGV4IGVyYXNlcjwvdGQ+PC90cj5cbiAgICA8dHI+PHRkPjxrYmQ+czwva2JkPjwvdGQ+PHRkPlNlbGVjdGlvbiBtb2RlIChkZWFjdGl2YXRlcyBkcmF3aW5nIHRvb2xzKTwvdGQ+PC90cj5cbiAgICA8dHI+PHRkPlBvaW50IHByb21wdCArIDxrYmQ+U2hpZnQ8L2tiZD4vPGtiZD5BbHQ8L2tiZD48L3RkPjx0ZD5BZGQgYW4gZXhjbHVkZSBwb2ludCAocmVmaW5lIHRoZSBjdXJyZW50IG9iamVjdCk8L3RkPjwvdHI+XG4gICAgPHRyPjx0ZD48a2JkPkVudGVyPC9rYmQ+PC90ZD48dGQ+Q29tbWl0IHRoZSBjdXJyZW50IFNBTSBwb2ludCBvYmplY3Q8L3RkPjwvdHI+XG4gICAgPHRyPjx0ZD48a2JkPkVzYzwva2JkPjwvdGQ+PHRkPlVuZG8gdGhlIGN1cnJlbnQgU0FNIHBvaW50IG9iamVjdDwvdGQ+PC90cj5cbiAgICA8dHI+PHRkPjxrYmQ+RGVsPC9rYmQ+IC8gPGtiZD7ijKs8L2tiZD48L3RkPjx0ZD5EZWxldGUgc2VsZWN0ZWQgcmVnaW9uPC90ZD48L3RyPlxuICAgIDx0cj48dGQ+PGtiZD5DdHJsPC9rYmQ+LzxrYmQ+Q21kPC9rYmQ+KzxrYmQ+Wjwva2JkPjwvdGQ+PHRkPlVuZG8gdGhlIGxhc3QgcmVnaW9uIGFjdGlvbiAodXAgdG8gMTApPC90ZD48L3RyPlxuICAgIDx0cj48dGQ+PGtiZD5DdHJsPC9rYmQ+LzxrYmQ+Q21kPC9rYmQ+KzxrYmQ+U2hpZnQ8L2tiZD4rPGtiZD5aPC9rYmQ+IC8gPGtiZD5DdHJsPC9rYmQ+KzxrYmQ+WTwva2JkPjwvdGQ+PHRkPlJlZG8gdGhlIGxhc3QgdW5kb25lIHJlZ2lvbiBhY3Rpb248L3RkPjwvdHI+XG4gIDwvdGFibGU+XG5cbiAgPG5nLXRlbXBsYXRlIHBUZW1wbGF0ZT1cImZvb3RlclwiPlxuICAgIDxwLWJ1dHRvbiBpY29uPVwicGkgcGktY2hlY2tcIiAoY2xpY2spPVwiZGlzcGxheUhlbHBEaWFsb2c9ZmFsc2VcIiBsYWJlbD1cIk9rXCIgc3R5bGVDbGFzcz1cInAtYnV0dG9uLXRleHRcIj5cbiAgICA8L3AtYnV0dG9uPlxuICA8L25nLXRlbXBsYXRlPlxuPC9wLWRpYWxvZz5cbiJdfQ==
367
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidG9vbGJhci5jb21wb25lbnQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi9zcmMvbGliL3Rvb2xiYXIvdG9vbGJhci5jb21wb25lbnQudHMiLCIuLi8uLi8uLi8uLi9zcmMvbGliL3Rvb2xiYXIvdG9vbGJhci5jb21wb25lbnQuaHRtbCJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsU0FBUyxFQUFFLFlBQVksRUFBRSxLQUFLLEVBQWEsTUFBTSxFQUFpQixNQUFNLGVBQWUsQ0FBQztBQUlqRyxPQUFPLEVBQ0wsUUFBUSxFQUVSLFVBQVUsRUFDVixrQkFBa0IsRUFDbEIsZUFBZSxFQUNmLGVBQWUsRUFDZix1QkFBdUIsRUFDdkIsdUJBQXVCLEdBQ3hCLE1BQU0sd0JBQXdCLENBQUM7QUFDaEMsT0FBTyxFQUF5QixpQkFBaUIsRUFBRSxNQUFNLDZCQUE2QixDQUFDOzs7Ozs7Ozs7Ozs7Ozs7QUFFdkY7Ozs7Ozs7Ozs7O0dBV0c7QUFRSCxNQUFNLE9BQU8sZ0JBQWdCO0lBUDdCO1FBVUUsZ0RBQWdEO1FBQ3ZDLG9CQUFlLEdBQXlCLEVBQUUsQ0FBQztRQUMzQyxxQkFBZ0IsR0FBYSxRQUFRLENBQUMsS0FBSyxDQUFDO1FBQ3JEOzRFQUNvRTtRQUMzRCxhQUFRLEdBQWEsQ0FBQyxDQUFDLEVBQUUsR0FBRyxDQUFDLENBQUM7UUFDOUIsYUFBUSxHQUFHLENBQUMsQ0FBQztRQUNiLFdBQU0sR0FBRyxDQUFDLENBQUM7UUFDcEIsa0RBQWtEO1FBQ3pDLGNBQVMsR0FBRyxJQUFJLENBQUM7UUFDakIsbUJBQWMsR0FBa0IsSUFBSSxDQUFDO1FBQ3JDLHdCQUFtQixHQUFHLFdBQVcsQ0FBQztRQUMzQyw0RkFBNEY7UUFDbkYsZ0JBQVcsR0FBRyxJQUFJLENBQUM7UUFDbkIsb0JBQWUsR0FBRyxLQUFLLENBQUM7UUFDakMsc0VBQXNFO1FBQzdELG9CQUFlLEdBQUcsdUJBQXVCLENBQUM7UUFDbkQsMkRBQTJEO1FBQ2xE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O2dCQUVHLGVBQWU7c0JBQXhCLE1BQU07Z0JBRUcsZ0JBQWdCO3NCQUF6QixNQUFNO2dCQUNHLFlBQVk7c0JBQXJCLE1BQU07Z0JBRUcsVUFBVTtzQkFBbkIsTUFBTTtnQkFFRyxVQUFVO3NCQUFuQixNQUFNO2dCQUVHLGNBQWM7c0JBQXZCLE1BQU07Z0JBRUcsZUFBZTtzQkFBeEIsTUFBTTtnQkFFRyxVQUFVO3NCQUFuQixNQUFNO2dCQUVHLGVBQWU7c0JBQXhCLE1BQU07Z0JBRUcsY0FBYztzQkFBdkIsTUFBTTtnQkFFRyxjQUFjO3NCQUF2QixNQUFNO2dCQUVHLGtCQUFrQjtzQkFBM0IsTUFBTTtnQkFFRyxpQkFBaUI7c0JBQTFCLE1BQU07Z0JBRUcsY0FBYztzQkFBdkIsTUFBTTtnQkFDRyxxQkFBcUI7c0JBQTlCLE1BQU07Z0JBQ0csZUFBZTtzQkFBeEIsTUFBTTtnQkFDRyx3QkFBd0I7c0JBQWpDLE1BQU0iLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyBDb21wb25lbnQsIEV2ZW50RW1pdHRlciwgSW5wdXQsIE9uQ2hhbmdlcywgT3V0cHV0LCBTaW1wbGVDaGFuZ2VzIH0gZnJvbSAnQGFuZ3VsYXIvY29yZSc7XG5pbXBvcnQgeyBNZW51SXRlbSB9IGZyb20gJ3ByaW1lbmcvYXBpJztcblxuaW1wb3J0IHsgSUltYWdlSW5mbyB9IGZyb20gJy4uL2NvbnRyYWN0cy9pbWFnZS5jb250cmFjdCc7XG5pbXBvcnQge1xuICBQbG90VHlwZSxcbiAgUGxvdFR5cGVEZXNjcmlwdG9yLFxuICBpc05hcGFyaTNkLFxuICBpc05hcGFyaUlzb3N1cmZhY2UsXG4gIGlzTmFwYXJpU3VyZmFjZSxcbiAgaXNOYXBhcmlTY2F0dGVyLFxuICBOQVBBUklfREVDSU1BVEVfT1BUSU9OUyxcbiAgTkFQQVJJX0RFRkFVTFRfREVDSU1BVEUsXG59IGZyb20gJy4uL2NvbnRyYWN0cy9wbG90LXR5cGUnO1xuaW1wb3J0IHsgVG9vbGJhclRvb2xWaXNpYmlsaXR5LCBBTExfVE9PTEJBUl9UT09MUyB9IGZyb20gJy4uL2NvbnRyYWN0cy90b29sYmFyLWNvbmZpZyc7XG5cbi8qKlxuICogUHJlc2VudGF0aW9uYWwgdG9vbGJhciBmb3IgdGhlIHBsb3R0aW5nIHZpZXdwb3J0LlxuICpcbiAqIFJlbmRlcnMgdGhlIHBsb3QtdHlwZSBzZWxlY3RvciwgY29sb3JtYXAvTFVUIGNvbnRyb2xzLCBzdGFjayBuYXZpZ2F0aW9uLFxuICogaW1hZ2UgYWN0aW9ucyAoZG93bmxvYWQgLyBhdXRvc2NhbGUgLyBwaXBlbGluZSksIHZpZXdwb3J0IHpvb20vcGFuLCB0aGVcbiAqIFN1cmZhY2UtM0QgY2FtZXJhIGNvbnRyb2xzLCBhbmQgdGhlIHJlZ2lvbi1kcmF3aW5nIHRvb2xzIOKAlCBwbHVzIHRoZSBwbG90dGluZ1xuICogaGVscCBkaWFsb2cuIEl0IG93bnMgbm8gcmVuZGVyaW5nIG9yIHZpc3VhbGl6YXRpb24gc3RhdGU6IHRoZSBob3N0XG4gKiAoVmlzdWFsaXplckNvbXBvbmVudCkgc3RheXMgdGhlIG9yY2hlc3RyYXRvciwgcGFzc2luZyBzdGF0ZSBpbiB2aWEgYEBJbnB1dGAgYW5kXG4gKiBoYW5kbGluZyBldmVyeSBhY3Rpb24gdmlhIGBAT3V0cHV0YC4gVGhlIHNhbWUgYWN0aW9ucyBhcmUgYWxzbyBkcml2ZW4gYnkgdGhlXG4gKiBkaWFncmFtJ3MgcmlnaHQtY2xpY2sgY29udGV4dCBtZW51IGFuZCBrZXlib2FyZCBzaG9ydGN1dHMsIHNvIGtlZXBpbmcgdGhlXG4gKiBoYW5kbGVycyBpbiB0aGUgaG9zdCBhdm9pZHMgZHVwbGljYXRpbmcgdGhhdCBsb2dpYy5cbiAqL1xuQENvbXBvbmVudCh7XG4gIC8vIENhbm9uaWNhbCBwcmVmaXhlZCBzZWxlY3RvciBmaXJzdDsgdGhlIHVucHJlZml4ZWQgb3JpZ2luYWwgaXMga2VwdCBhcyBhblxuICAvLyBhbGlhcyBmb3Igb25lIHJlbGVhc2UgKHByZS1wdWJsaWNhdGlvbiBiYWNrLWNvbXBhdCkuXG4gIHNlbGVjdG9yOiAncGxvdHRpbmctdG9vbGJhcicsXG4gIHRlbXBsYXRlVXJsOiAnLi90b29sYmFyLmNvbXBvbmVudC5odG1sJyxcbiAgc3R5bGVVcmxzOiBbJy4vdG9vbGJhci5jb21wb25lbnQuc2NzcyddLFxufSlcbmV4cG9ydCBjbGFzcyBUb29sYmFyQ29tcG9uZW50IGltcGxlbWVudHMgT25DaGFuZ2VzIHtcbiAgLyoqIEN1cnJlbnQgaW1hZ2UgKGdhdGVzIHdoaWNoIGNvbnRyb2wgZ3JvdXBzIGFyZSBzaG93bikuICovXG4gIEBJbnB1dCgpIGltYWdlSW5mbzogSUltYWdlSW5mbyB8IHVuZGVmaW5lZDtcbiAgLyoqIFBsb3QgdHlwZXMgdGhlIGFjdGl2ZSBiYWNrZW5kIGFkdmVydGlzZXMuICovXG4gIEBJbnB1dCgpIHBsb3RUeXBlT3B0aW9uczogUGxvdFR5cGVEZXNjcmlwdG9yW10gPSBbXTtcbiAgQElucHV0KCkgc2VsZWN0ZWRQbG90VHlwZTogUGxvdFR5cGUgPSBQbG90VHlwZS5JTUFHRTtcbiAgLyoqIElzb3N1cmZhY2UgYmFuZCBhcyBhIDDigJMyNTUgc2xpZGVyIHBvc2l0aW9uIChtYXBwZWQgb250byB0aGUgdm9sdW1lJ3MgcmVhbFxuICAgKiAgaW50ZW5zaXR5IHJhbmdlIGJ5IHRoZSByZW5kZXJlcikuIERlZmF1bHRzIHRvIHRoZSBmdWxsIHJhbmdlLiAqL1xuICBASW5wdXQoKSBpc29SYW5nZTogbnVtYmVyW10gPSBbMCwgMjU1XTtcbiAgQElucHV0KCkgbWF4SW5kZXggPSAwO1xuICBASW5wdXQoKSB6SW5kZXggPSAwO1xuICAvKiogVHJ1ZSBmb3IgYW55IDJEIChub24gU3VyZmFjZS0zRCkgcGxvdCB0eXBlLiAqL1xuICBASW5wdXQoKSBpc0hlYXRtYXAgPSB0cnVlO1xuICBASW5wdXQoKSBhY3RpdmVEcmFnTW9kZTogc3RyaW5nIHwgbnVsbCA9IG51bGw7XG4gIEBJbnB1dCgpIGFjdGl2ZVN1cmZhY2UzZE1vZGUgPSAndHVybnRhYmxlJztcbiAgLyoqIFdoZXRoZXIgdGhlIG5hcGFyaSAzRCBheGVzL3NjYWxlIGdpem1vIGlzIGN1cnJlbnRseSBzaG93biAoZHJpdmVzIHRoZSB0b2dnbGUncyBsb29rKS4gKi9cbiAgQElucHV0KCkgYXhlc1Zpc2libGUgPSB0cnVlO1xuICBASW5wdXQoKSB3aXJlZnJhbWVBY3RpdmUgPSBmYWxzZTtcbiAgLyoqIEFjdGl2ZSBuYXBhcmkgM0QgZGVjaW1hdGUgZmFjdG9yICgxID0gRnVsbCDigKYgOCA9IOKFmzsgZGVmYXVsdCDCvSkuICovXG4gIEBJbnB1dCgpIHJlc29sdXRpb25TY2FsZSA9IE5BUEFSSV9ERUZBVUxUX0RFQ0lNQVRFO1xuICAvKiogRGVjaW1hdGUtZmFjdG9yIG9wdGlvbnMgZm9yIHRoZSBSZXNvbHV0aW9uIGRyb3Bkb3duLiAqL1xuICByZWFkb25seSBkZWNpbWF0ZU9wdGlvbnMgPSBOQVBBUklfREVDSU1BVEVfT1BUSU9OUztcbiAgQElucHV0KCkgd2FuZFNlbnNpdGl2aXR5ID0gMi4wO1xuICBASW5wdXQoKSBicnVzaFNpemUgPSA0MDtcbiAgQElucHV0KCkgdmVydGV4RXJhc2VyUmFkaXVzID0gMjA7XG4gIC8qKiBXaGV0aGVyIGEgcmVnaW9uIGFjdGlvbiBpcyBhdmFpbGFibGUgdG8gdW5kbyAoaml0LXVpIzg1KS4gR3JleXMgb3V0IHRoZVxuICAgKiAgVW5kbyBidXR0b24gd2hlbiBmYWxzZSAobm90aGluZyBkb25lIHlldCwgb3IgYWxsIHN0ZXBzIGFscmVhZHkgdW5kb25lKS4gKi9cbiAgQElucHV0KCkgY2FuVW5kbyA9IGZhbHNlO1xuICAvKiogV2hldGhlciBhbiB1bmRvbmUgcmVnaW9uIGFjdGlvbiBpcyBhdmFpbGFibGUgdG8gcmVkbyAoaml0LXVpIzg1KS4gR3JleXNcbiAgICogIG91dCB0aGUgUmVkbyBidXR0b24gd2hlbiBmYWxzZS4gKi9cbiAgQElucHV0KCkgY2FuUmVkbyA9IGZhbHNlO1xuICBASW5wdXQoKSBzdGFja09wdGlvbnM6IHsgbmFtZTogc3RyaW5nOyB2YWw6IHN0cmluZyB9W10gPVxuICAgIFt7IG5hbWU6ICdTaW5nbGUgaW1hZ2UnLCB2YWw6ICdmYWxzZScgfSwgeyBuYW1lOiAnU3RhY2snLCB2YWw6ICd0cnVlJyB9XTtcbiAgLyoqIFdoaWNoIHRvb2xiYXIgZ3JvdXBzIHRvIHNob3cuIERlZmF1bHRzIHRvIHRoZSBmdWxsIHRvb2xiYXI7IHRoZSBob3N0IGZvcndhcmRzXG4gICAqICB0aGUgY29uc3VtZXIncyBjaG9pY2UgKGUuZy4gdGhlIHBpcGVsaW5lIHNob3dzIG9ubHkgem9vbSArIHJlZ2lvbiB0b29scykuICovXG4gIEBJbnB1dCgpIHRvb2xzOiBSZXF1aXJlZDxUb29sYmFyVG9vbFZpc2liaWxpdHk+ID0gQUxMX1RPT0xCQVJfVE9PTFM7XG4gIC8qKiBDdXJyZW50IGltYWdlLXNtb290aGluZyBzdGF0ZSAoZHJpdmVzIHRoZSBTbW9vdGhlbiB0b2dnbGUgYnV0dG9uIGxvb2spLlxuICAgKiAgYGZhbHNlYCA9IG5lYXJlc3QtbmVpZ2hib3VyIChjcmlzcCByYXcgcGl4ZWxzKSwgdGhlIGRlZmF1bHQuICovXG4gIEBJbnB1dCgpIGltYWdlU21vb3RoaW5nRW5hYmxlZCA9IGZhbHNlO1xuICAvKiogU0FNIG1vZGVsIHBpY2tlciBvcHRpb25zICsgY3VycmVudCBzZWxlY3Rpb24gKGppdC11aSM5MCBQMSkuICovXG4gIEBJbnB1dCgpIHNhbU1vZGVsczogeyBpZDogc3RyaW5nOyBsYWJlbDogc3RyaW5nIH1bXSA9IFtdO1xuICBASW5wdXQoKSBzYW1Nb2RlbElkID0gJyc7XG4gIC8qKiBZT0xPIGNoZWNrcG9pbnRzIG9mZmVyZWQgYnkgdGhlIGhvc3QncyByZWdpc3RyeSwgYW5kIHRoZSBhY3RpdmUgb25lLiAqL1xuICBASW5wdXQoKSB5b2xvTW9kZWxzOiB7IGlkOiBzdHJpbmc7IGxhYmVsOiBzdHJpbmcgfVtdID0gW107XG4gIEBJbnB1dCgpIHlvbG9Nb2RlbElkID0gJyc7XG4gIC8qKiBSZXRpbmFsLWxheWVyIGNoZWNrcG9pbnRzIG9mZmVyZWQgYnkgdGhlIGhvc3QncyByZWdpc3RyeSwgYW5kIHRoZSBhY3RpdmUgb25lLiAqL1xuICBASW5wdXQoKSByZXRpbmFsTW9kZWxzOiB7IGlkOiBzdHJpbmc7IGxhYmVsOiBzdHJpbmcgfVtdID0gW107XG4gIEBJbnB1dCgpIHJldGluYWxNb2RlbElkID0gJyc7XG5cbiAgQE91dHB1dCgpIHNlbGVjdFBsb3RUeXBlID0gbmV3IEV2ZW50RW1pdHRlcjxQbG90VHlwZT4oKTtcbiAgLyoqIEludGVuc2l0eSAoTElORSkgbW9kZTogYWRkIGFub3RoZXIgY29sb3JlZCBsaW5lIFJPSSArIGluc2V0IHRyYWNlLiAqL1xuICBAT3V0cHV0KCkgYWRkUHJvZmlsZUxpbmUgPSBuZXcgRXZlbnRFbWl0dGVyPHZvaWQ+KCk7XG4gIC8qKiBUb2dnbGUgaW1hZ2Ugc21vb3RoaW5nIChiaWxpbmVhcikgdnMgbmVhcmVzdC1uZWlnaGJvdXIgKGNyaXNwIHBpeGVscykuICovXG4gIEBPdXRwdXQoKSB0b2dnbGVJbWFnZVNtb290aGluZyA9IG5ldyBFdmVudEVtaXR0ZXI8dm9pZD4oKTtcbiAgLy8gUHJpbWVORyBzbGlkZXIvaW5wdXROdW1iZXIgY2hhbmdlIGV2ZW50cyBjYXJyeSBvcHRpb25hbCB2YWx1ZXMsIG1pcnJvcmVkIGJ5XG4gIC8vIHRoZSBob3N0IGhhbmRsZXJzICh3aGljaCBhY2NlcHQgYHwgdW5kZWZpbmVkYCkuXG4gIEBPdXRwdXQoKSBpc29SYW5nZUNoYW5nZSA9IG5ldyBFdmVudEVtaXR0ZXI8bnVtYmVyW10gfCB1bmRlZmluZWQ+KCk7XG4gIC8qKiBPcGVuIHRoZSBDaGFubmVscyAmIEhpc3RvZ3JhbSBkaWFsb2cgKGJyaWdodG5lc3MvY29udHJhc3QsIGNvbG9ybWFwLCDigKYpLiAqL1xuICBAT3V0cHV0KCkgb3BlbkNoYW5uZWxIaXN0b2dyYW0gPSBuZXcgRXZlbnRFbWl0dGVyPHZvaWQ+KCk7XG4gIEBPdXRwdXQoKSBvcGVuUmVnaW9uRWRpdG9yID0gbmV3IEV2ZW50RW1pdHRlcjx2b2lkPigpO1xuICBAT3V0cHV0KCkgc2VsZWN0U3RhY2tPcHRpb24gPSBuZXcgRXZlbnRFbWl0dGVyPHsgbmFtZTogc3RyaW5nOyB2YWw6IHN0cmluZyB9PigpO1xuICBAT3V0cHV0KCkgelNjcnViID0gbmV3IEV2ZW50RW1pdHRlcjxudW1iZXIgfCB1bmRlZmluZWQ+KCk7XG4gIEBPdXRwdXQoKSB6U2xpZGUgPSBuZXcgRXZlbnRFbWl0dGVyPG51bWJlciB8IHVuZGVmaW5lZD4oKTtcbiAgQE91dHB1dCgpIHpJbmRleElucHV0ID0gbmV3IEV2ZW50RW1pdHRlcjxudW1iZXI+KCk7XG4gIEBPdXRwdXQoKSByZWxvYWRBbmRQbG90ID0gbmV3IEV2ZW50RW1pdHRlcjx2b2lkPigpO1xuICBAT3V0cHV0KCkgZG93bmxvYWRJbWFnZSA9IG5ldyBFdmVudEVtaXR0ZXI8dm9pZD4oKTtcbiAgQE91dHB1dCgpIGF1dG9zY2FsZUltYWdlID0gbmV3IEV2ZW50RW1pdHRlcjx2b2lkPigpO1xuICBAT3V0cHV0KCkgdG9nZ2xlRHJhZ01vZGUgPSBuZXcgRXZlbnRFbWl0dGVyPHN0cmluZz4oKTtcbiAgQE91dHB1dCgpIHpvb21JbiA9IG5ldyBFdmVudEVtaXR0ZXI8dm9pZD4oKTtcbiAgQE91dHB1dCgpIHpvb21PdXQgPSBuZXcgRXZlbnRFbWl0dGVyPHZvaWQ+KCk7XG4gIEBPdXRwdXQoKSB0b2dnbGVTdXJmYWNlM2RNb2RlID0gbmV3IEV2ZW50RW1pdHRlcjxzdHJpbmc+KCk7XG4gIEBPdXRwdXQoKSByZXNldFN1cmZhY2VDYW1lcmEgPSBuZXcgRXZlbnRFbWl0dGVyPHZvaWQ+KCk7XG4gIC8qKiBUb2dnbGUgdGhlIG5hcGFyaSAzRCBjb29yZGluYXRlLWF4ZXMgLyBzY2FsZSBnaXptbyBvbi9vZmYuICovXG4gIEBPdXRwdXQoKSB0b2dnbGVBeGVzID0gbmV3IEV2ZW50RW1pdHRlcjx2b2lkPigpO1xuICAvKiogVG9nZ2xlIHRoZSBuYXBhcmkgc3VyZmFjZSB3aXJlZnJhbWUgKGVkZ2VzKSBvbi9vZmYuICovXG4gIEBPdXRwdXQoKSB0b2dnbGVXaXJlZnJhbWUgPSBuZXcgRXZlbnRFbWl0dGVyPHZvaWQ+KCk7XG4gIC8qKiBDaGFuZ2UgdGhlIG5hcGFyaSAzRCBkZWNpbWF0ZSBmYWN0b3IgKHJlbG9hZHMgYXQgdGhlIG5ldyByZXNvbHV0aW9uKS4gKi9cbiAgQE91dHB1dCgpIHNlbGVjdFJlc29sdXRpb24gPSBuZXcgRXZlbnRFbWl0dGVyPG51bWJlcj4oKTtcbiAgQE91dHB1dCgpIGRlbGV0ZVJlZ2lvbiA9IG5ldyBFdmVudEVtaXR0ZXI8dm9pZD4oKTtcbiAgLyoqIFVuZG8gdGhlIGxhc3QgcmVnaW9uIGFjdGlvbiAoaml0LXVpIzg1KS4gKi9cbiAgQE91dHB1dCgpIHVuZG9SZWdpb24gPSBuZXcgRXZlbnRFbWl0dGVyPHZvaWQ+KCk7XG4gIC8qKiBSZWRvIHRoZSBsYXN0IHVuZG9uZSByZWdpb24gYWN0aW9uIChqaXQtdWkjODUpLiAqL1xuICBAT3V0cHV0KCkgcmVkb1JlZ2lvbiA9IG5ldyBFdmVudEVtaXR0ZXI8dm9pZD4oKTtcbiAgLyoqIFJ1biBib3gtcHJvbXB0ZWQgU0FNIHNlZ21lbnRhdGlvbiBvbiB0aGUgZHJhd24gcmVjdGFuZ2xlcyAoaml0LXVpIzkwKS4gKi9cbiAgQE91dHB1dCgpIHNlZ21lbnRSZWdpb25zID0gbmV3IEV2ZW50RW1pdHRlcjx2b2lkPigpO1xuICAvKiogUnVuIGNlbGxwb3NlLVNBTSAoYXV0bykgaW5zaWRlIGVhY2ggZHJhd24gcmVjdGFuZ2xlJ3MgY3JvcCAoaml0LXVpIzkwKS4gKi9cbiAgQE91dHB1dCgpIHNlZ21lbnRDZWxscG9zZSA9IG5ldyBFdmVudEVtaXR0ZXI8dm9pZD4oKTtcbiAgLyoqIFJ1biBZT0xPIGRldGVjdGlvbiBvdmVyIHRoZSBjdXJyZW50IHZpZXcuICovXG4gIEBPdXRwdXQoKSBkZXRlY3RZb2xvID0gbmV3IEV2ZW50RW1pdHRlcjx2b2lkPigpO1xuICAvKiogUGljayB0aGUgWU9MTyBjaGVja3BvaW50LiAqL1xuICBAT3V0cHV0KCkgeW9sb01vZGVsQ2hhbmdlID0gbmV3IEV2ZW50RW1pdHRlcjxzdHJpbmc+KCk7XG4gIC8qKiBPcGVuIHRoZSBZT0xPIHBhcmFtZXRlciBkaWFsb2cuICovXG4gIEBPdXRwdXQoKSBvcGVuWW9sb1BhcmFtcyA9IG5ldyBFdmVudEVtaXR0ZXI8dm9pZD4oKTtcbiAgLyoqIFJ1biByZXRpbmFsLWxheWVyIHNlZ21lbnRhdGlvbiBvdmVyIHRoZSBjdXJyZW50IHZpZXcuICovXG4gIEBPdXRwdXQoKSBzZWdtZW50UmV0aW5hbCA9IG5ldyBFdmVudEVtaXR0ZXI8dm9pZD4oKTtcbiAgLyoqIFBpY2sgdGhlIHJldGluYWwtbGF5ZXIgY2hlY2twb2ludC4gKi9cbiAgQE91dHB1dCgpIHJldGluYWxNb2RlbENoYW5nZSA9IG5ldyBFdmVudEVtaXR0ZXI8c3RyaW5nPigpO1xuICAvKiogT3BlbiB0aGUgcmV0aW5hbC1sYXllciBwYXJhbWV0ZXIgZGlhbG9nLiAqL1xuICBAT3V0cHV0KCkgb3BlblJldGluYWxQYXJhbXMgPSBuZXcgRXZlbnRFbWl0dGVyPHZvaWQ+KCk7XG4gIC8qKiBTQU0gbW9kZWwgcGlja2VyIChqaXQtdWkjOTAgUDEpLiAqL1xuICBAT3V0cHV0KCkgc2FtTW9kZWxDaGFuZ2UgPSBuZXcgRXZlbnRFbWl0dGVyPHN0cmluZz4oKTtcbiAgQE91dHB1dCgpIHdhbmRTZW5zaXRpdml0eUNoYW5nZSA9IG5ldyBFdmVudEVtaXR0ZXI8bnVtYmVyIHwgdW5kZWZpbmVkPigpO1xuICBAT3V0cHV0KCkgYnJ1c2hTaXplQ2hhbmdlID0gbmV3IEV2ZW50RW1pdHRlcjxudW1iZXIgfCB1bmRlZmluZWQ+KCk7XG4gIEBPdXRwdXQoKSB2ZXJ0ZXhFcmFzZXJSYWRpdXNDaGFuZ2UgPSBuZXcgRXZlbnRFbWl0dGVyPG51bWJlciB8IHVuZGVmaW5lZD4oKTtcblxuICAvKiogU2xpZGVycycgYm91bmRzIOKAlCB0b29sYmFyIFVJIGNvbnN0YW50cyAodGhlIHZhbHVlcyBhcmUgaG9zdC1vd25lZCkuICovXG4gIHJlYWRvbmx5IHdhbmRTZW5zaXRpdml0eU1pbiA9IDAuNTtcbiAgcmVhZG9ubHkgd2FuZFNlbnNpdGl2aXR5TWF4ID0gMTAuMDtcbiAgcmVhZG9ubHkgd2FuZFNlbnNpdGl2aXR5U3RlcCA9IDAuMTtcbiAgcmVhZG9ubHkgYnJ1c2hTaXplTWluID0gNTtcbiAgcmVhZG9ubHkgYnJ1c2hTaXplTWF4ID0gMzAwO1xuICByZWFkb25seSBicnVzaFNpemVTdGVwID0gNTtcbiAgcmVhZG9ubHkgdmVydGV4RXJhc2VyUmFkaXVzTWluID0gNTtcbiAgcmVhZG9ubHkgdmVydGV4RXJhc2VyUmFkaXVzTWF4ID0gMzAwO1xuICByZWFkb25seSB2ZXJ0ZXhFcmFzZXJSYWRpdXNTdGVwID0gNTtcbiAgcmVhZG9ubHkgaXNvVmFsdWVNaW4gPSAwO1xuICByZWFkb25seSBpc29WYWx1ZU1heCA9IDI1NTtcbiAgcmVhZG9ubHkgaXNvVmFsdWVTdGVwID0gMTtcblxuICBkaXNwbGF5SGVscERpYWxvZyA9IGZhbHNlO1xuXG4gIC8qKiBNb2RlbCBwaWNrZXIgZm9yIHRoZSBTZWdtZW50IGJ1dHRvbidzIGRyb3Bkb3duIG1lbnUuIFRoZSBhY3RpdmUgbW9kZWxcbiAgICogIChgc2FtTW9kZWxJZGApIGlzIG1hcmtlZCB3aXRoIGEgY2hlY2s7IHNlbGVjdGluZyBhbiBpdGVtIGVtaXRzXG4gICAqICBgc2FtTW9kZWxDaGFuZ2VgIChqaXQtdWkjOTAgUDEpLlxuICAgKlxuICAgKiAgSGVsZCBhcyBhIHN0YWJsZSBhcnJheSAocmVidWlsdCBvbmx5IHdoZW4gYHNhbU1vZGVsc2AvYHNhbU1vZGVsSWRgIGNoYW5nZSksXG4gICAqICBOT1QgYSBnZXR0ZXI6IGEgZ2V0dGVyIHJldHVybnMgYSBmcmVzaCBhcnJheSB3aXRoIG5ldyBgY29tbWFuZGAgY2xvc3VyZXMgb25cbiAgICogIGV2ZXJ5IGNoYW5nZS1kZXRlY3Rpb24gdGljaywgd2hpY2ggbWFrZXMgdGhlIGJvdW5kIGBwLW1lbnVgIG92ZXJsYXkgcmVidWlsZFxuICAgKiAgaXRzIERPTSBtaWQtaW50ZXJhY3Rpb24gYW5kIHN3YWxsb3cgdGhlIGNsaWNrIG9uIGEgbWVudSBpdGVtLiAqL1xuICBzYW1NZW51SXRlbXM6IE1lbnVJdGVtW10gPSBbXTtcbiAgLyoqIFNhbWUgc3RhYmxlLWFycmF5IHJlYXNvbmluZyBhcyB7QGxpbmsgc2FtTWVudUl0ZW1zfS4gKi9cbiAgeW9sb01lbnVJdGVtczogTWVudUl0ZW1bXSA9IFtdO1xuICAvKiogU2FtZSBzdGFibGUtYXJyYXkgcmVhc29uaW5nIGFzIHtAbGluayBzYW1NZW51SXRlbXN9LiAqL1xuICByZXRpbmFsTWVudUl0ZW1zOiBNZW51SXRlbVtdID0gW107XG5cbiAgLyoqIFJlYnVpbGQgdGhlIFNBTSBtb2RlbCBtZW51IHdoZW4gdGhlIG1vZGVsIGxpc3Qgb3IgYWN0aXZlIHNlbGVjdGlvblxuICAgKiAgY2hhbmdlcyAoa2VlcHMgdGhlIGFycmF5IHJlZmVyZW5jZSBzdGFibGUgYWNyb3NzIG90aGVyIENEIHRpY2tzKS4gKi9cbiAgbmdPbkNoYW5nZXMoY2hhbmdlczogU2ltcGxlQ2hhbmdlcyk6IHZvaWQge1xuICAgIGlmIChjaGFuZ2VzWydzYW1Nb2RlbHMnXSB8fCBjaGFuZ2VzWydzYW1Nb2RlbElkJ10pIHtcbiAgICAgIHRoaXMuc2FtTWVudUl0ZW1zID0gdGhpcy5zYW1Nb2RlbHMubWFwKChtKSA9PiAoe1xuICAgICAgICBsYWJlbDogbS5sYWJlbCxcbiAgICAgICAgaWNvbjogbS5pZCA9PT0gdGhpcy5zYW1Nb2RlbElkID8gJ3BpIHBpLWNoZWNrJyA6ICdwaSBwaS1mdycsXG4gICAgICAgIGNvbW1hbmQ6ICgpID0+IHRoaXMuc2FtTW9kZWxDaGFuZ2UuZW1pdChtLmlkKSxcbiAgICAgIH0pKTtcbiAgICB9XG4gICAgaWYgKGNoYW5nZXNbJ3lvbG9Nb2RlbHMnXSB8fCBjaGFuZ2VzWyd5b2xvTW9kZWxJZCddKSB7XG4gICAgICB0aGlzLnlvbG9NZW51SXRlbXMgPSB0aGlzLnlvbG9Nb2RlbHMubWFwKChtKSA9PiAoe1xuICAgICAgICBsYWJlbDogbS5sYWJlbCxcbiAgICAgICAgaWNvbjogbS5pZCA9PT0gdGhpcy55b2xvTW9kZWxJZCA/ICdwaSBwaS1jaGVjaycgOiAncGkgcGktZncnLFxuICAgICAgICBjb21tYW5kOiAoKSA9PiB0aGlzLnlvbG9Nb2RlbENoYW5nZS5lbWl0KG0uaWQpLFxuICAgICAgfSkpO1xuICAgIH1cbiAgICBpZiAoY2hhbmdlc1sncmV0aW5hbE1vZGVscyddIHx8IGNoYW5nZXNbJ3JldGluYWxNb2RlbElkJ10pIHtcbiAgICAgIHRoaXMucmV0aW5hbE1lbnVJdGVtcyA9IHRoaXMucmV0aW5hbE1vZGVscy5tYXAoKG0pID0+ICh7XG4gICAgICAgIGxhYmVsOiBtLmxhYmVsLFxuICAgICAgICBpY29uOiBtLmlkID09PSB0aGlzLnJldGluYWxNb2RlbElkID8gJ3BpIHBpLWNoZWNrJyA6ICdwaSBwaS1mdycsXG4gICAgICAgIGNvbW1hbmQ6ICgpID0+IHRoaXMucmV0aW5hbE1vZGVsQ2hhbmdlLmVtaXQobS5pZCksXG4gICAgICB9KSk7XG4gICAgfVxuICB9XG5cbiAgLyoqIFRoZSBJbWFnZSBwbG90IHR5cGUgcmVuZGVycyBhcyBhIG5hdGl2ZWx5IHBhbi96b29tLWFibGUgcmFzdGVyLCBzbyB0aGVcbiAgICogIGdlbmVyaWMgem9vbS9wYW4gdG9vbHMgYXJlIGhpZGRlbi4gKi9cbiAgZ2V0IGlzSW1hZ2VWaWV3KCk6IGJvb2xlYW4ge1xuICAgIHJldHVybiB0aGlzLnNlbGVjdGVkUGxvdFR5cGUgPT09IFBsb3RUeXBlLklNQUdFO1xuICB9XG5cbiAgLyoqIEEgcGxvdC10eXBlIGljb24gaXMgYSBQcmltZU5HIGZvbnQgZ2x5cGggKGUuZy4gYHBpIHBpLWltYWdlYCkgcmF0aGVyIHRoYW4gYW5cbiAgICogIFNWRyBhc3NldCBwYXRoIOKAlCBkcml2ZXMgd2hpY2ggZWxlbWVudCB0aGUgc2VsZWN0b3IgaXRlbSB0ZW1wbGF0ZSByZW5kZXJzLiAqL1xuICBpc1BpSWNvbihpY29uOiBzdHJpbmcgfCB1bmRlZmluZWQpOiBib29sZWFuIHtcbiAgICByZXR1cm4gISFpY29uICYmIGljb24uc3RhcnRzV2l0aCgncGkgJyk7XG4gIH1cblxuICAvKiogQmFja2VuZHMgd2l0aCBhIHZlcnRleC1lZGl0aW5nIHJlZ2lvbiBvdmVybGF5OiBPU0QgSW1hZ2UgYW5kIG5hcGFyaS1qcyBXZWJHUFUgaW1hZ2VcbiAgICogIChqaXQtdWkjMTAyKS4gR2F0ZXMgdGhlIHBvbHlnb24gLyBhZGQtdmVydGV4IC8gZGVsZXRlLXZlcnRleCAvIELDqXppZXItY29udmVydCB0b29scy4gKi9cbiAgZ2V0IHN1cHBvcnRzUmVnaW9uVmVydGV4VG9vbHMoKTogYm9vbGVhbiB7XG4gICAgcmV0dXJuIChcbiAgICAgIHRoaXMuc2VsZWN0ZWRQbG90VHlwZSA9PT0gUGxvdFR5cGUuSU1BR0UgfHxcbiAgICAgIHRoaXMuc2VsZWN0ZWRQbG90VHlwZSA9PT0gUGxvdFR5cGUuTkFQQVJJX0lNQUdFXG4gICAgKTtcbiAgfVxuXG4gIC8qKiBQbG90IHR5cGVzIHRoYXQgc2NydWIgYSB6LXN0YWNrIGxpdmUgKHRoZSByZW5kZXJlciBzd2FwcyB0aGUgc2xpY2UgaW4gcGxhY2UpOiB0aGUgT1NEIEltYWdlXG4gICAqICB2aWV3LCB0aGUgbmFwYXJpLWpzIFdlYkdQVSBpbWFnZSwgYW5kIHRoZSBuYXBhcmktanMgc3VyZmFjZSDigJQgd2hpY2ggcmVidWlsZHMgdGhlIGhlaWdodCBmaWVsZFxuICAgKiAgZnJvbSB0aGUgcGlja2VkIHNsaWNlIChqaXQtdWkjMTAyKS4gRHJpdmVzIHRoZSBwZXItc2xpY2Ugc2xpZGVyLiAqL1xuICBnZXQgc2hvd3NMaXZlU2xpY2VTY3J1YmJlcigpOiBib29sZWFuIHtcbiAgICByZXR1cm4gKFxuICAgICAgdGhpcy5zZWxlY3RlZFBsb3RUeXBlID09PSBQbG90VHlwZS5JTUFHRSB8fFxuICAgICAgdGhpcy5zZWxlY3RlZFBsb3RUeXBlID09PSBQbG90VHlwZS5OQVBBUklfSU1BR0UgfHxcbiAgICAgIGlzTmFwYXJpU3VyZmFjZSh0aGlzLnNlbGVjdGVkUGxvdFR5cGUpIHx8XG4gICAgICBpc05hcGFyaVNjYXR0ZXIodGhpcy5zZWxlY3RlZFBsb3RUeXBlKVxuICAgICk7XG4gIH1cblxuICAvKiogQW55IG5hcGFyaS1qcyBXZWJHUFUgcGxvdCB0eXBlLiBUaGUgc2luZ2xlLWltYWdlL3N0YWNrIHRvZ2dsZSArIHNsaWNlLW51bWJlciBmaWVsZCBhcmVcbiAgICogIFBsb3RseS1vbmx5IHN0YWNrIGNvbnRyb2xzLCBzbyB0aGV5J3JlIGhpZGRlbiBmb3IgbmFwYXJpIChqaXQtdWkjMTAyKS4gKi9cbiAgZ2V0IGlzTmFwYXJpTW9kZSgpOiBib29sZWFuIHtcbiAgICByZXR1cm4gKFxuICAgICAgdGhpcy5zZWxlY3RlZFBsb3RUeXBlID09PSBQbG90VHlwZS5OQVBBUklfSU1BR0UgfHxcbiAgICAgIGlzTmFwYXJpU2NhdHRlcih0aGlzLnNlbGVjdGVkUGxvdFR5cGUpIHx8XG4gICAgICBpc05hcGFyaTNkKHRoaXMuc2VsZWN0ZWRQbG90VHlwZSlcbiAgICApO1xuICB9XG5cbiAgLyoqIElTT1NVUkZBQ0UgKFBsb3RseSBvciBuYXBhcmktanMgV2ViR1BVLCBlaXRoZXIgcmVzb2x1dGlvbikgc2hvd3MgdGhlIGlzb3ZhbHVlIHJhbmdlIHNsaWRlci4gKi9cbiAgZ2V0IGlzSXNvc3VyZmFjZU1vZGUoKTogYm9vbGVhbiB7XG4gICAgcmV0dXJuIChcbiAgICAgIHRoaXMuc2VsZWN0ZWRQbG90VHlwZSA9PT0gUGxvdFR5cGUuSVNPU1VSRkFDRSB8fCBpc05hcGFyaUlzb3N1cmZhY2UodGhpcy5zZWxlY3RlZFBsb3RUeXBlKVxuICAgICk7XG4gIH1cblxuICAvKiogVGhlIG5hcGFyaS1qcyBXZWJHUFUgc3VyZmFjZSDigJQgc2hvd3MgdGhlIHdpcmVmcmFtZSB0b2dnbGUuICovXG4gIGdldCBpc05hcGFyaVN1cmZhY2VNb2RlKCk6IGJvb2xlYW4ge1xuICAgIHJldHVybiBpc05hcGFyaVN1cmZhY2UodGhpcy5zZWxlY3RlZFBsb3RUeXBlKTtcbiAgfVxuXG4gIC8qKiBBbnkgbmFwYXJpLWpzIFdlYkdQVSAzRCB0eXBlICh2b2x1bWUvaXNvc3VyZmFjZS9zdXJmYWNlKSDigJQgc2hvd3MgdGhlIFJlc29sdXRpb24gY29udHJvbC4gKi9cbiAgZ2V0IGlzTmFwYXJpM2RNb2RlKCk6IGJvb2xlYW4ge1xuICAgIHJldHVybiBpc05hcGFyaTNkKHRoaXMuc2VsZWN0ZWRQbG90VHlwZSk7XG4gIH1cblxuICAvKiogSW50ZW5zaXR5IHByb2ZpbGUgbGluZXMgYXJlIFJlZ2lvbi1iYXNlZCBhbmQgYXZhaWxhYmxlIGluIHRoZSBIZWF0bWFwIGFuZFxuICAgKiAgSW1hZ2UgcGxvdCB0eXBlcywgd2hpY2ggc2hvdyB0aGUgSW50ZW5zaXR5IHRvb2wgZ3JvdXAuICovXG4gIGdldCBpc0ludGVuc2l0eUNhcGFibGUoKTogYm9vbGVhbiB7XG4gICAgcmV0dXJuIHRoaXMuc2VsZWN0ZWRQbG90VHlwZSA9PT0gUGxvdFR5cGUuSEVBVE1BUCB8fCB0aGlzLnNlbGVjdGVkUGxvdFR5cGUgPT09IFBsb3RUeXBlLklNQUdFO1xuICB9XG5cbiAgc2hvd0hlbHAoKTogdm9pZCB7XG4gICAgdGhpcy5kaXNwbGF5SGVscERpYWxvZyA9IHRydWU7XG4gIH1cbn1cbiIsIjxwLXRvb2xiYXI+XG4gIDxkaXYgY2xhc3M9XCJwLXRvb2xiYXItZ3JvdXAtc3RhcnQgZmxleC13cmFwIGFsaWduLWl0ZW1zLWNlbnRlciBnYXAtMVwiPlxuXG4gICAgPCEtLSBIb3N0LXByb3ZpZGVkIGN1c3RvbSB0b29sYmFyIGJ1dHRvbnMsIHNob3duIGZpcnN0IChlLmcuIGFwcC1zcGVjaWZpY1xuICAgICAgICAgYWN0aW9ucyB0aGUgdmlzdWFsaXphdGlvbiB2aWV3IGl0c2VsZiBkb2Vzbid0IG93bikuIC0tPlxuICAgIDxuZy1jb250ZW50PjwvbmctY29udGVudD5cblxuICAgIDwhLS0gUGxvdCB0eXBlOiBhdmFpbGFibGUgZm9yIGFueSBpbWFnZSAoZ3JheXNjYWxlIG9yIFJHQikuIC0tPlxuICAgIDxuZy1jb250YWluZXIgKm5nSWY9XCJpbWFnZUluZm8gJiYgdG9vbHMuc3BlY2lhbFRvb2xzXCI+XG4gICAgICA8c3BhbiBjbGFzcz1cInRvb2xiYXItc2VwYXJhdG9yXCI+PC9zcGFuPlxuICAgICAgPHAtZHJvcGRvd24gW29wdGlvbnNdPVwicGxvdFR5cGVPcHRpb25zXCIgb3B0aW9uTGFiZWw9XCJsYWJlbFwiIG9wdGlvblZhbHVlPVwidHlwZVwiXG4gICAgICAgICAgICAgICAgICBbKG5nTW9kZWwpXT1cInNlbGVjdGVkUGxvdFR5cGVcIiBhcHBlbmRUbz1cImJvZHlcIlxuICAgICAgICAgICAgICAgICAgcFRvb2x0aXA9XCJTZWxlY3QgcGxvdCB0eXBlXCIgdG9vbHRpcFBvc2l0aW9uPVwiYm90dG9tXCJcbiAgICAgICAgICAgICAgICAgIFtzdHlsZV09XCJ7J21pbi13aWR0aCc6ICcxMXJlbSd9XCJcbiAgICAgICAgICAgICAgICAgIHN0eWxlQ2xhc3M9XCJwbG90bW9kZS1kcm9wZG93blwiIHBhbmVsU3R5bGVDbGFzcz1cInBsb3Rtb2RlLXBhbmVsXCJcbiAgICAgICAgICAgICAgICAgIChvbkNoYW5nZSk9XCJzZWxlY3RQbG90VHlwZS5lbWl0KCRldmVudC52YWx1ZSlcIj5cbiAgICAgICAgPG5nLXRlbXBsYXRlIGxldC1vcHQgcFRlbXBsYXRlPVwic2VsZWN0ZWRJdGVtXCI+XG4gICAgICAgICAgPHNwYW4gY2xhc3M9XCJwbG90bW9kZS1pdGVtXCIgKm5nSWY9XCJvcHRcIj5cbiAgICAgICAgICAgIDxpICpuZ0lmPVwiaXNQaUljb24ob3B0Lmljb24pXCIgW2NsYXNzXT1cIm9wdC5pY29uXCI+PC9pPlxuICAgICAgICAgICAgPGltZyAqbmdJZj1cIm9wdC5pY29uICYmICFpc1BpSWNvbihvcHQuaWNvbilcIiBjbGFzcz1cInBsb3Rtb2RlLWljb24taW1nXCIgW3NyY109XCJvcHQuaWNvblwiIGFsdD1cIlwiIC8+XG4gICAgICAgICAgICA8c3Bhbj57eyBvcHQubGFiZWwgfX08L3NwYW4+XG4gICAgICAgICAgPC9zcGFuPlxuICAgICAgICA8L25nLXRlbXBsYXRlPlxuICAgICAgICA8bmctdGVtcGxhdGUgbGV0LW9wdCBwVGVtcGxhdGU9XCJpdGVtXCI+XG4gICAgICAgICAgPHNwYW4gY2xhc3M9XCJwbG90bW9kZS1pdGVtXCI+XG4gICAgICAgICAgICA8aSAqbmdJZj1cImlzUGlJY29uKG9wdC5pY29uKVwiIFtjbGFzc109XCJvcHQuaWNvblwiPjwvaT5cbiAgICAgICAgICAgIDxpbWcgKm5nSWY9XCJvcHQuaWNvbiAmJiAhaXNQaUljb24ob3B0Lmljb24pXCIgY2xhc3M9XCJwbG90bW9kZS1pY29uLWltZ1wiIFtzcmNdPVwib3B0Lmljb25cIiBhbHQ9XCJcIiAvPlxuICAgICAgICAgICAgPHNwYW4+e3sgb3B0LmxhYmVsIH19PC9zcGFuPlxuICAgICAgICAgIDwvc3Bhbj5cbiAgICAgICAgPC9uZy10ZW1wbGF0ZT5cbiAgICAgIDwvcC1kcm9wZG93bj5cbiAgICAgIDxuZy1jb250YWluZXIgKm5nSWY9XCJpc0lzb3N1cmZhY2VNb2RlXCI+XG4gICAgICAgIDxzcGFuIGNsYXNzPVwiZmxleCBhbGlnbi1pdGVtcy1jZW50ZXIgbXgtMSB0b29sYmFyLXRleHRcIj5Jc288L3NwYW4+XG4gICAgICAgIDxwLXNsaWRlciBbKG5nTW9kZWwpXT1cImlzb1JhbmdlXCIgW3JhbmdlXT1cInRydWVcIlxuICAgICAgICAgICAgICAgICAgW21pbl09XCJpc29WYWx1ZU1pblwiIFttYXhdPVwiaXNvVmFsdWVNYXhcIiBbc3RlcF09XCJpc29WYWx1ZVN0ZXBcIlxuICAgICAgICAgICAgICAgICAgKG9uQ2hhbmdlKT1cImlzb1JhbmdlQ2hhbmdlLmVtaXQoJGV2ZW50LnZhbHVlcylcIlxuICAgICAgICAgICAgICAgICAgcFRvb2x0aXA9XCJJc29zdXJmYWNlIGJhbmQgKDDigJMyNTUpLCBtYXBwZWQgb250byB0aGUgdm9sdW1lJ3NcbiAgICAgICAgICAgICAgICAgIGFjdHVhbCBpbnRlbnNpdHkgcmFuZ2UuIFN1cmZhY2VzIGFyZSBkcmF3biBmb3Igdm94ZWxzIHdob3NlXG4gICAgICAgICAgICAgICAgICBpbnRlbnNpdHkgZmFsbHMgaW4gdGhlIHNlbGVjdGVkIGJhbmQg4oCUIG5hcnJvdyBpdCB0byBpc29sYXRlXG4gICAgICAgICAgICAgICAgICBmZWF0dXJlcywgd2lkZW4gaXQgdG8gc2VlIHRoZSB3aG9sZSBzdHJ1Y3R1cmUuXCJcbiAgICAgICAgICAgICAgICAgIFtlc2NhcGVdPVwiZmFsc2VcIiB0b29sdGlwUG9zaXRpb249XCJib3R0b21cIlxuICAgICAgICAgICAgICAgICAgc3R5bGVDbGFzcz1cIndhbmQtc2Vuc2l0aXZpdHktc2xpZGVyXCI+PC9wLXNsaWRlcj5cbiAgICAgICAgPHNwYW4gY2xhc3M9XCJmbGV4IGFsaWduLWl0ZW1zLWNlbnRlciBteC0xIHRvb2xiYXItdGV4dCB3YW5kLXNlbnNpdGl2aXR5LXZhbHVlXCI+XG4gICAgICAgICAge3sgaXNvUmFuZ2VbMF0gfX3igJN7eyBpc29SYW5nZVsxXSB9fTwvc3Bhbj5cbiAgICAgIDwvbmctY29udGFpbmVyPlxuICAgICAgPCEtLSBJbnRlbnNpdHkgdG9vbHMgKEhlYXRtYXAgLyBJbWFnZSBtb2RlKTogYWRkIGFub3RoZXIgY29sb3JlZCBsaW5lIFJPSS5cbiAgICAgICAgICAgRWFjaCBhZGRlZCBsaW5lIGdldHMgdGhlIG5leHQgYnJpZ2h0IHBhbGV0dGUgY29sb3VyIGFuZCBpdHMgb3duIGluc2V0XG4gICAgICAgICAgIHRyYWNlOyBzZWxlY3QgYSBsaW5lIG9uIHRoZSBpbWFnZSBhbmQgdXNlIHRoZSB0cmFzaCB0b29sIHRvIHJlbW92ZSBpdFxuICAgICAgICAgICAoYW5kIGl0cyB0cmFjZSkuIFRoZSBsaW5lcyBhcmUgUmVnaW9uLWJhc2VkIGJ1dCBleGNsdWRlZCBmcm9tIHRoZVxuICAgICAgICAgICBSZWdpb24gRWRpdG9yIGFuZCBleHBvcnRzLiAtLT5cbiAgICAgIDxuZy1jb250YWluZXIgKm5nSWY9XCJpc0ludGVuc2l0eUNhcGFibGVcIj5cbiAgICAgICAgPHNwYW4gY2xhc3M9XCJ0b29sYmFyLXNlcGFyYXRvclwiPjwvc3Bhbj5cbiAgICAgICAgPHAtYnV0dG9uIHNpemU9XCJzbWFsbFwiIFt0ZXh0XT1cInRydWVcIlxuICAgICAgICAgICAgICAgICAgcFRvb2x0aXA9XCJBZGQgYW4gaW50ZW5zaXR5IGxpbmUgKG5ldyBjb2xvdXIpLiBDbGljayBhIGxpbmUgdG8gc2VsZWN0IGl0LFxuICAgICAgICAgICAgICAgICAgdGhlbiB1c2UgdGhlIHRyYXNoIHRvb2wgdG8gcmVtb3ZlIGl0LlwiXG4gICAgICAgICAgICAgICAgICBbZXNjYXBlXT1cImZhbHNlXCIgdG9vbHRpcFBvc2l0aW9uPVwiYm90dG9tXCJcbiAgICAgICAgICAgICAgICAgIChvbkNsaWNrKT1cImFkZFByb2ZpbGVMaW5lLmVtaXQoKVwiPlxuICAgICAgICAgIDxpbWcgc3JjPVwiYXNzZXRzL3Bsb3R0aW5nL3NwbGluZS1jaGFydC5zdmdcIiBjbGFzcz1cInRvb2xiYXItaWNvbi1kYXJrXCIgc3R5bGU9XCJ3aWR0aDogMXJlbVwiLz5cbiAgICAgICAgPC9wLWJ1dHRvbj5cbiAgICAgIDwvbmctY29udGFpbmVyPlxuICAgIDwvbmctY29udGFpbmVyPlxuICAgIDwhLS0gQ2hhbm5lbHMgJiBIaXN0b2dyYW06IGJyaWdodG5lc3MvY29udHJhc3QsIGdhbW1hLCBwZXItY2hhbm5lbCBkaXNwbGF5LFxuICAgICAgICAgY29sb3JtYXAvcmV2ZXJzZS9pbnZlcnQg4oCUIG9wZW5zIHRoZSBmbG9hdGluZyBkaWFsb2cuIC0tPlxuICAgIDxwLWJ1dHRvbiAqbmdJZj1cInRvb2xzLnNwZWNpYWxUb29sc1wiIHBSaXBwbGUgc2l6ZT1cInNtYWxsXCIgW3RleHRdPVwidHJ1ZVwiXG4gICAgICAgICAgICAgIHBUb29sdGlwPVwiQ2hhbm5lbHMgJmFtcDsgSGlzdG9ncmFtIChicmlnaHRuZXNzL2NvbnRyYXN0LCBjb2xvcm1hcClcIlxuICAgICAgICAgICAgICB0b29sdGlwUG9zaXRpb249XCJib3R0b21cIlxuICAgICAgICAgICAgICAob25DbGljayk9XCJvcGVuQ2hhbm5lbEhpc3RvZ3JhbS5lbWl0KClcIj5cbiAgICAgIDxpbWcgc3JjPVwiYXNzZXRzL3Bsb3R0aW5nL2ludmVydC5zdmdcIiBjbGFzcz1cInRvb2xiYXItaWNvbi1kYXJrXCIgc3R5bGU9XCJ3aWR0aDogMXJlbVwiLz5cbiAgICA8L3AtYnV0dG9uPlxuXG4gICAgPCEtLSBTbW9vdGhlbiB0b2dnbGU6IGJpbGluZWFyIHNtb290aGluZyB2cyBuZWFyZXN0LW5laWdoYm91ciByYXcgcGl4ZWxzIChJbWFnZVxuICAgICAgICAgdmlldyBvbmx5IOKAlCBpdCdzIGFuIE9wZW5TZWFkcmFnb24gZHJhd2VyIHNldHRpbmcpLiBIaWdobGlnaHRlZCB3aGVuIHNtb290aGluZyBvbi4gLS0+XG4gICAgPHAtYnV0dG9uICpuZ0lmPVwidG9vbHMuc3BlY2lhbFRvb2xzICYmIGlzSW1hZ2VWaWV3XCIgc2l6ZT1cInNtYWxsXCJcbiAgICAgICAgICAgICAgW3RleHRdPVwiIWltYWdlU21vb3RoaW5nRW5hYmxlZFwiXG4gICAgICAgICAgICAgIHBUb29sdGlwPVwiU21vb3RoZW4gKHR1cm4gb24gaW1hZ2Ugc21vb3RoaW5nKVwiIHRvb2x0aXBQb3NpdGlvbj1cImJvdHRvbVwiXG4gICAgICAgICAgICAgIChvbkNsaWNrKT1cInRvZ2dsZUltYWdlU21vb3RoaW5nLmVtaXQoKVwiPlxuICAgICAgPGltZyBzcmM9XCJhc3NldHMvcGxvdHRpbmcvc21vb3RoLnN2Z1wiIHN0eWxlPVwid2lkdGg6IDFyZW1cIlxuICAgICAgICAgICBbY2xhc3NdPVwiaW1hZ2VTbW9vdGhpbmdFbmFibGVkID8gJ3Rvb2xiYXItaWNvbi1hY3RpdmUnIDogJ3Rvb2xiYXItaWNvbi1kYXJrJ1wiLz5cbiAgICA8L3AtYnV0dG9uPlxuXG4gICAgPCEtLSBTdGFjayBjb250cm9scyAtLT5cbiAgICA8bmctY29udGFpbmVyICpuZ0lmPVwiaW1hZ2VJbmZvPy5pc1N0YWNrXCI+XG4gICAgICA8c3BhbiBjbGFzcz1cInRvb2xiYXItc2VwYXJhdG9yXCI+PC9zcGFuPlxuICAgICAgPCEtLSBJbWFnZSB2aWV3OiBzY3J1YiB6LXNsaWNlcyBsaXZlICh0aGUgcmVuZGVyZXIgc3dhcHMgc2xpY2VzIGluIHBsYWNlKS4gLS0+XG4gICAgICA8bmctY29udGFpbmVyICpuZ0lmPVwic2hvd3NMaXZlU2xpY2VTY3J1YmJlclwiPlxuICAgICAgICA8c3BhbiBjbGFzcz1cImZsZXggYWxpZ24taXRlbXMtY2VudGVyIG14LTEgdG9vbGJhci10ZXh0XCI+U2xpY2U8L3NwYW4+XG4gICAgICAgIDxwLXNsaWRlciBbKG5nTW9kZWwpXT1cInpJbmRleFwiIFttaW5dPVwiMFwiIFttYXhdPVwibWF4SW5kZXhcIlxuICAgICAgICAgICAgICAgICAgKG9uQ2hhbmdlKT1cInpTY3J1Yi5lbWl0KCRldmVudC52YWx1ZSlcIiAob25TbGlkZUVuZCk9XCJ6U2xpZGUuZW1pdCgkZXZlbnQudmFsdWUpXCJcbiAgICAgICAgICAgICAgICAgIHBUb29sdGlwPVwiU2xpY2UgdGhyb3VnaCB0aGUgc3RhY2sgKOKGkCAvIOKGkiBrZXlzKVwiIHRvb2x0aXBQb3NpdGlvbj1cImJvdHRvbVwiXG4gICAgICAgICAgICAgICAgICBbc3R5bGVdPVwieyd3aWR0aCc6JzE2cmVtJ31cIiBzdHlsZUNsYXNzPVwid2FuZC1zZW5zaXRpdml0eS1zbGlkZXJcIj48L3Atc2xpZGVyPlxuICAgICAgICA8c3BhbiBjbGFzcz1cImZsZXggYWxpZ24taXRlbXMtY2VudGVyIG14LTEgdG9vbGJhci10ZXh0XCI+e3t6SW5kZXh9fSBvZiB7e21heEluZGV4fX08L3NwYW4+XG4gICAgICA8L25nLWNvbnRhaW5lcj5cbiAgICAgIDwhLS0gSGVhdG1hcCB2aWV3OiBzaW5nbGUtaW1hZ2UgKGhpZ2gtZGVmIHpvb20pIHZzIHN0YWNrIChmcmFtZSBzbGlkZXIpLiBQbG90bHktb25seSDigJRcbiAgICAgICAgICAgaGlkZGVuIGZvciBuYXBhcmksIHdoaWNoIGhhcyBubyBzaW5nbGUvc3RhY2sgdG9nZ2xlIG9yIHBlci1mcmFtZSBmZXRjaC4gLS0+XG4gICAgICA8bmctY29udGFpbmVyICpuZ0lmPVwiIXNob3dzTGl2ZVNsaWNlU2NydWJiZXIgJiYgIWlzTmFwYXJpTW9kZVwiPlxuICAgICAgPHAtYnV0dG9uIGljb249XCJwaSBwaS1pbWFnZVwiIHNpemU9XCJzbWFsbFwiXG4gICAgICAgICAgICAgICAgW3RleHRdPVwiaW1hZ2VJbmZvPy5zaG93U3RhY2tcIlxuICAgICAgICAgICAgICAgIHBUb29sdGlwPVwiU2luZ2xlIGltYWdlIG1vZGU6IGVuYWJsZXMgaGlnaC1kZWYgem9vbVwiIHRvb2x0aXBQb3NpdGlvbj1cImJvdHRvbVwiXG4gICAgICAgICAgICAgICAgKG9uQ2xpY2spPVwic2VsZWN0U3RhY2tPcHRpb24uZW1pdChzdGFja09wdGlvbnNbMF0pXCI+XG4gICAgICA8L3AtYnV0dG9uPlxuICAgICAgPHAtYnV0dG9uIGljb249XCJwaSBwaS1pbWFnZXNcIiBzaXplPVwic21hbGxcIlxuICAgICAgICAgICAgICAgIFt0ZXh0XT1cIiFpbWFnZUluZm8/LnNob3dTdGFja1wiXG4gICAgICAgICAgICAgICAgcFRvb2x0aXA9XCJTdGFjayBtb2RlLCBlbmFibGVzIHNsaWNpbmcgdGhyb3VnaCBpbWFnZSBmcmFtZXNcIiB0b29sdGlwUG9zaXRpb249XCJib3R0b21cIlxuICAgICAgICAgICAgICAgIChvbkNsaWNrKT1cInNlbGVjdFN0YWNrT3B0aW9uLmVtaXQoc3RhY2tPcHRpb25zWzFdKVwiPlxuICAgICAgPC9wLWJ1dHRvbj5cbiAgICAgIDxuZy1jb250YWluZXIgKm5nSWY9XCIhaW1hZ2VJbmZvPy5zaG93U3RhY2tcIj5cbiAgICAgICAgPHNwYW4gY2xhc3M9XCJmbGV4IGFsaWduLWl0ZW1zLWNlbnRlciBteC0xIHRvb2xiYXItdGV4dFwiPlNsaWNlPC9zcGFuPlxuICAgICAgICA8ZGl2IGNsYXNzPVwicC1pbnB1dGdyb3VwXCIgc3R5bGU9XCJ3aWR0aDogYXV0b1wiPlxuICAgICAgICAgIDxwLWlucHV0TnVtYmVyIHN0eWxlQ2xhc3M9XCJ3LTNyZW0gdG9vbGJhci1pbnB1dFwiXG5cdFx0XHRcdFx0cFRvb2x0aXA9XCJWaWV3IGltYWdlIGluIHN0YWNrICgmI3gyM0NFOylcIlxuXHRcdFx0XHRcdFsobmdNb2RlbCldPVwiekluZGV4XCIgW21pbl09XCIwXCIgW21heF09XCJtYXhJbmRleFwiXG4gICAgICAgICAgIChvbklucHV0KT1cInpJbmRleElucHV0LmVtaXQoekluZGV4KVwiIChrZXlkb3duLmVudGVyKT1cInJlbG9hZEFuZFBsb3QuZW1pdCgpXCIvPlxuICAgICAgICAgIDxwLWJ1dHRvbiBpY29uPVwicGkgcGktcmVmcmVzaFwiIHNpemU9XCJzbWFsbFwiIFt0ZXh0XT1cInRydWVcIiAob25DbGljayk9XCJyZWxvYWRBbmRQbG90LmVtaXQoKVwiXG4gICAgICAgICAgICAgICAgICAgIHBUb29sdGlwPVwiUGxvdCB0aGlzIHNsaWNlXCIgdG9vbHRpcFBvc2l0aW9uPVwiYm90dG9tXCI+XG4gICAgICAgICAgPC9wLWJ1dHRvbj5cbiAgICAgICAgPC9kaXY+XG4gICAgICAgIDxzcGFuIGNsYXNzPVwiZmxleCBhbGlnbi1pdGVtcy1jZW50ZXIgbXgtMSB0b29sYmFyLXRleHRcIj5vZiB7e21heEluZGV4fX08L3NwYW4+XG4gICAgICAgIDwvbmctY29udGFpbmVyPlxuICAgICAgPC9uZy1jb250YWluZXI+XG4gICAgPC9uZy1jb250YWluZXI+XG5cbiAgICA8IS0tIEltYWdlIGFjdGlvbnMgKHNwZWNpYWwgdG9vbHMpIC0tPlxuICAgIDxuZy1jb250YWluZXIgKm5nSWY9XCJ0b29scy5zcGVjaWFsVG9vbHNcIj5cbiAgICAgIDxzcGFuIGNsYXNzPVwidG9vbGJhci1zZXBhcmF0b3JcIj48L3NwYW4+XG4gICAgICA8cC1idXR0b24gaWNvbj1cInBpIHBpLWRvd25sb2FkXCIgc2l6ZT1cInNtYWxsXCIgW3RleHRdPVwidHJ1ZVwiXG4gICAgICAgICAgICAgICAgcFRvb2x0aXA9XCJEb3dubG9hZCBpbWFnZSBhcyBQTkdcIiB0b29sdGlwUG9zaXRpb249XCJib3R0b21cIlxuICAgICAgICAgICAgICAgIChvbkNsaWNrKT1cImRvd25sb2FkSW1hZ2UuZW1pdCgpXCI+XG4gICAgICA8L3AtYnV0dG9uPlxuICAgICAgPHAtYnV0dG9uICpuZ0lmPVwiaXNIZWF0bWFwXCIgaWNvbj1cInBpIHBpLXdpbmRvdy1tYXhpbWl6ZVwiIHNpemU9XCJzbWFsbFwiIFt0ZXh0XT1cInRydWVcIlxuICAgICAgICAgICAgICAgIHBUb29sdGlwPVwiRml0IHRvIHZpZXdcIiB0b29sdGlwUG9zaXRpb249XCJib3R0b21cIlxuICAgICAgICAgICAgICAgIChvbkNsaWNrKT1cImF1dG9zY2FsZUltYWdlLmVtaXQoKVwiPlxuICAgICAgPC9wLWJ1dHRvbj5cbiAgICA8L25nLWNvbnRhaW5lcj5cblxuICAgIDwhLS0gWm9vbSB0b29scy4gUGxvdGx5IHJ1YmJlci1iYW5kIGRyYWctem9vbTogb24gT3BlblNlYWRyYWdvbiB0aGUgYm94LXpvb21cbiAgICAgICAgIGNvdmVycyB0aGlzIGFuZCBhIGJhcmUgZHJhZyBwYW5zIG5hdGl2ZWx5LCBzbyBpdCdzIGhpZGRlbiBpbiBpbWFnZSB2aWV3LiAtLT5cbiAgICA8bmctY29udGFpbmVyICpuZ0lmPVwiaXNIZWF0bWFwICYmIHRvb2xzLnpvb21Ub29sc1wiPlxuICAgICAgPHNwYW4gY2xhc3M9XCJ0b29sYmFyLXNlcGFyYXRvclwiPjwvc3Bhbj5cbiAgICAgIDxwLWJ1dHRvbiAqbmdJZj1cIiFpc0ltYWdlVmlld1wiIGljb249XCJwaSBwaS1zZWFyY2hcIiBzaXplPVwic21hbGxcIlxuICAgICAgICAgICAgICAgIFt0ZXh0XT1cImFjdGl2ZURyYWdNb2RlICE9PSAnem9vbSdcIlxuICAgICAgICAgICAgICAgIHBUb29sdGlwPVwiWm9vbSBkcmFnXCIgdG9vbHRpcFBvc2l0aW9uPVwiYm90dG9tXCJcbiAgICAgICAgICAgICAgICAob25DbGljayk9XCJ0b2dnbGVEcmFnTW9kZS5lbWl0KCd6b29tJylcIj5cbiAgICAgIDwvcC1idXR0b24+XG4gICAgICA8cC1idXR0b24gc2l6ZT1cInNtYWxsXCJcbiAgICAgICAgICAgICAgICBbdGV4dF09XCJhY3RpdmVEcmFnTW9kZSAhPT0gJ3pvb21Ub0JveCdcIlxuICAgICAgICAgICAgICAgIHBUb29sdGlwPVwiWm9vbSBib3ggKDxrYmQ+Yjwva2JkPilcIiBbZXNjYXBlXT1cImZhbHNlXCIgdG9vbHRpcFBvc2l0aW9uPVwiYm90dG9tXCJcbiAgICAgICAgICAgICAgICAob25DbGljayk9XCJ0b2dnbGVEcmFnTW9kZS5lbWl0KCd6b29tVG9Cb3gnKVwiPlxuICAgICAgICA8aW1nIHNyYz1cImFzc2V0cy9wbG90dGluZy96b29tLWJveC5zdmdcIlxuICAgICAgICAgICAgIFtjbGFzc109XCJhY3RpdmVEcmFnTW9kZSA9PT0gJ3pvb21Ub0JveCcgPyAndG9vbGJhci1pY29uLWFjdGl2ZScgOiAndG9vbGJhci1pY29uLWRhcmsnXCJcbiAgICAgICAgICAgICBzdHlsZT1cIndpZHRoOiAxcmVtXCIvPlxuICAgICAgPC9wLWJ1dHRvbj5cbiAgICAgIDxwLWJ1dHRvbiBpY29uPVwicGkgcGktYXJyb3dzLWFsdFwiIHNpemU9XCJzbWFsbFwiXG4gICAgICAgICAgICAgICAgW3RleHRdPVwiYWN0aXZlRHJhZ01vZGUgIT09ICdwYW4nXCJcbiAgICAgICAgICAgICAgICBwVG9vbHRpcD1cIlBhbiB0aGUgaW1hZ2UgKDxrYmQ+cDwva2JkPilcIiBbZXNjYXBlXT1cImZhbHNlXCIgdG9vbHRpcFBvc2l0aW9uPVwiYm90dG9tXCJcbiAgICAgICAgICAgICAgICAob25DbGljayk9XCJ0b2dnbGVEcmFnTW9kZS5lbWl0KCdwYW4nKVwiPlxuICAgICAgPC9wLWJ1dHRvbj5cbiAgICAgIDxwLWJ1dHRvbiBpY29uPVwicGkgcGktc2VhcmNoLXBsdXNcIiBzaXplPVwic21hbGxcIiBbdGV4dF09XCJ0cnVlXCJcbiAgICAgICAgICAgICAgICBwVG9vbHRpcD1cIlpvb20gaW4gKDxrYmQ+Kzwva2JkPilcIiBbZXNjYXBlXT1cImZhbHNlXCIgdG9vbHRpcFBvc2l0aW9uPVwiYm90dG9tXCJcbiAgICAgICAgICAgICAgICAob25DbGljayk9XCJ6b29tSW4uZW1pdCgpXCI+XG4gICAgICA8L3AtYnV0dG9uPlxuICAgICAgPHAtYnV0dG9uIGljb249XCJwaSBwaS1zZWFyY2gtbWludXNcIiBzaXplPVwic21hbGxcIiBbdGV4dF09XCJ0cnVlXCJcbiAgICAgICAgICAgICAgICBwVG9vbHRpcD1cIlpvb20gb3V0ICg8a2JkPi08L2tiZD4pXCIgW2VzY2FwZV09XCJmYWxzZVwiIHRvb2x0aXBQb3NpdGlvbj1cImJvdHRvbVwiXG4gICAgICAgICAgICAgICAgKG9uQ2xpY2spPVwiem9vbU91dC5lbWl0KClcIj5cbiAgICAgIDwvcC1idXR0b24+XG4gICAgPC9uZy1jb250YWluZXI+XG5cbiAgICA8IS0tIFN1cmZhY2UgM0QgY29udHJvbHMgLS0+XG4gICAgPG5nLWNvbnRhaW5lciAqbmdJZj1cIiFpc0hlYXRtYXBcIj5cbiAgICAgIDxzcGFuIGNsYXNzPVwidG9vbGJhci1zZXBhcmF0b3JcIj48L3NwYW4+XG4gICAgICA8cC1idXR0b24gaWNvbj1cInBpIHBpLXNlYXJjaFwiIHNpemU9XCJzbWFsbFwiXG4gICAgICAgICAgICAgICAgW3RleHRdPVwiYWN0aXZlU3VyZmFjZTNkTW9kZSAhPT0gJ3pvb20nXCJcbiAgICAgICAgICAgICAgICBwVG9vbHRpcD1cIlpvb21cIiB0b29sdGlwUG9zaXRpb249XCJib3R0b21cIlxuICAgICAgICAgICAgICAgIChvbkNsaWNrKT1cInRvZ2dsZVN1cmZhY2UzZE1vZGUuZW1pdCgnem9vbScpXCI+XG4gICAgICA8L3AtYnV0dG9uPlxuICAgICAgPHAtYnV0dG9uIGljb249XCJwaSBwaS1hcnJvd3MtYWx0XCIgc2l6ZT1cInNtYWxsXCJcbiAgICAgICAgICAgICAgICBbdGV4dF09XCJhY3RpdmVTdXJmYWNlM2RNb2RlICE9PSAncGFuJ1wiXG4gICAgICAgICAgICAgICAgcFRvb2x0aXA9XCJQYW5cIiB0b29sdGlwUG9zaXRpb249XCJib3R0b21cIlxuICAgICAgICAgICAgICAgIChvbkNsaWNrKT1cInRvZ2dsZVN1cmZhY2UzZE1vZGUuZW1pdCgncGFuJylcIj5cbiAgICAgIDwvcC1idXR0b24+XG4gICAgICA8cC1idXR0b24gaWNvbj1cInBpIHBpLWdsb2JlXCIgc2l6ZT1cInNtYWxsXCJcbiAgICAgICAgICAgICAgICBbdGV4dF09XCJhY3RpdmVTdXJmYWNlM2RNb2RlICE9PSAnb3JiaXQnXCJcbiAgICAgICAgICAgICAgICBwVG9vbHRpcD1cIk9yYml0YWwgcm90YXRpb25cIiB0b29sdGlwUG9zaXRpb249XCJib3R0b21cIlxuICAgICAgICAgICAgICAgIChvbkNsaWNrKT1cInRvZ2dsZVN1cmZhY2UzZE1vZGUuZW1pdCgnb3JiaXQnKVwiPlxuICAgICAgPC9wLWJ1dHRvbj5cbiAgICAgIDxwLWJ1dHRvbiBpY29uPVwicGkgcGktc3luY1wiIHNpemU9XCJzbWFsbFwiXG4gICAgICAgICAgICAgICAgW3RleHRdPVwiYWN0aXZlU3VyZmFjZTNkTW9kZSAhPT0gJ3R1cm50YWJsZSdcIlxuICAgICAgICAgICAgICAgIHBUb29sdGlwPVwiVHVybnRhYmxlIHJvdGF0aW9uXCIgdG9vbHRpcFBvc2l0aW9uPVwiYm90dG9tXCJcbiAgICAgICAgICAgICAgICAob25DbGljayk9XCJ0b2dnbGVTdXJmYWNlM2RNb2RlLmVtaXQoJ3R1cm50YWJsZScpXCI+XG4gICAgICA8L3AtYnV0dG9uPlxuICAgICAgPHAtYnV0dG9uIGljb249XCJwaSBwaS1ob21lXCIgc2l6ZT1cInNtYWxsXCIgW3RleHRdPVwidHJ1ZVwiXG4gICAgICAgICAgICAgICAgcFRvb2x0aXA9XCJSZXNldCBjYW1lcmEgdG8gZGVmYXVsdFwiIHRvb2x0aXBQb3NpdGlvbj1cImJvdHRvbVwiXG4gICAgICAgICAgICAgICAgKG9uQ2xpY2spPVwicmVzZXRTdXJmYWNlQ2FtZXJhLmVtaXQoKVwiPlxuICAgICAgPC9wLWJ1dHRvbj5cbiAgICAgIDwhLS0gM0QgY29vcmRpbmF0ZS1heGVzIC8gc2NhbGUgZ2l6bW8gdG9nZ2xlIChuYXBhcmktanMgV2ViR1BVIHZvbHVtZS9pc29zdXJmYWNlIG9ubHkpLlxuICAgICAgICAgICBgcC1idXR0b24taWNvbi1vbmx5YCBnaXZlcyB0aGUgc2FtZSBzcXVhcmUgc2l6aW5nIGFzIHRoZSBuZWlnaGJvdXJpbmcgcGktaWNvbiBidXR0b25zXG4gICAgICAgICAgICh0aGUgcHJvamVjdGVkIDxpbWc+IGRvZXNuJ3QgdHJpZ2dlciBQcmltZU5HJ3MgYXV0b21hdGljIGljb24tb25seSBzaXppbmcpLiAtLT5cbiAgICAgIDxwLWJ1dHRvbiBzaXplPVwic21hbGxcIiAqbmdJZj1cImlzTmFwYXJpTW9kZVwiIHN0eWxlQ2xhc3M9XCJwLWJ1dHRvbi1pY29uLW9ubHlcIlxuICAgICAgICAgICAgICAgIFt0ZXh0XT1cIiFheGVzVmlzaWJsZVwiXG4gICAgICAgICAgICAgICAgcFRvb2x0aXA9XCJUb2dnbGUgM0QgYXhlcyAvIHNjYWxlXCIgdG9vbHRpcFBvc2l0aW9uPVwiYm90dG9tXCJcbiAgICAgICAgICAgICAgICAob25DbGljayk9XCJ0b2dnbGVBeGVzLmVtaXQoKVwiPlxuICAgICAgICA8aW1nIHNyYz1cImFzc2V0cy9wbG90dGluZy8zZC1jb29yZGluYXRlcy5zdmdcIlxuICAgICAgICAgICAgIFtjbGFzc109XCJheGVzVmlzaWJsZSA/ICd0b29sYmFyLWljb24tYWN0aXZlJyA6ICd0b29sYmFyLWljb24tZGFyaydcIlxuICAgICAgICAgICAgIHN0eWxlPVwid2lkdGg6IDFyZW1cIi8+XG4gICAgICA8L3AtYnV0dG9uPlxuICAgICAgPCEtLSBTdXJmYWNlIHdpcmVmcmFtZSB0b2dnbGUgKG5hcGFyaS1qcyBXZWJHUFUgc3VyZmFjZSBvbmx5KS4gLS0+XG4gICAgICA8cC1idXR0b24gaWNvbj1cInBpIHBpLXNoYXJlLWFsdFwiIHNpemU9XCJzbWFsbFwiICpuZ0lmPVwiaXNOYXBhcmlTdXJmYWNlTW9kZVwiXG4gICAgICAgICAgICAgICAgW3RleHRdPVwiIXdpcmVmcmFtZUFjdGl2ZVwiXG4gICAgICAgICAgICAgICAgcFRvb2x0aXA9XCJUb2dnbGUgd2lyZWZyYW1lXCIgdG9vbHRpcFBvc2l0aW9uPVwiYm90dG9tXCJcbiAgICAgICAgICAgICAgICAob25DbGljayk9XCJ0b2dnbGVXaXJlZnJhbWUuZW1pdCgpXCI+XG4gICAgICA8L3AtYnV0dG9uPlxuICAgICAgPCEtLSBSZXNvbHV0aW9uIChkZWNpbWF0ZSBmYWN0b3IpIGZvciB0aGUgbmFwYXJpIDNEIHR5cGVzIOKAlCByZWxvYWRzIGF0IGNvYXJzZXIvZmluZXIgc2FtcGxpbmcuIC0tPlxuICAgICAgPG5nLWNvbnRhaW5lciAqbmdJZj1cImlzTmFwYXJpM2RNb2RlXCI+XG4gICAgICAgIDxzcGFuIGNsYXNzPVwiZmxleCBhbGlnbi1pdGVtcy1jZW50ZXIgbXgtMSB0b29sYmFyLXRleHRcIj5SZXM8L3NwYW4+XG4gICAgICAgIDxwLWRyb3Bkb3duIFtvcHRpb25zXT1cImRlY2ltYXRlT3B0aW9uc1wiIG9wdGlvbkxhYmVsPVwibGFiZWxcIiBvcHRpb25WYWx1ZT1cInZhbHVlXCJcbiAgICAgICAgICAgICAgICAgICAgW25nTW9kZWxdPVwicmVzb2x1dGlvblNjYWxlXCIgYXBwZW5kVG89XCJib2R5XCJcbiAgICAgICAgICAgICAgICAgICAgcFRvb2x0aXA9XCJSZXNvbHV0aW9uIChkZWNpbWF0ZSBmYWN0b3IpXCIgdG9vbHRpcFBvc2l0aW9uPVwiYm90dG9tXCJcbiAgICAgICAgICAgICAgICAgICAgW3N0eWxlXT1cInsnbWluLXdpZHRoJzogJzZyZW0nfVwiXG4gICAgICAgICAgICAgICAgICAgIChvbkNoYW5nZSk9XCJzZWxlY3RSZXNvbHV0aW9uLmVtaXQoJGV2ZW50LnZhbHVlKVwiPlxuICAgICAgICA8L3AtZHJvcGRvd24+XG4gICAgICA8L25nLWNvbnRhaW5lcj5cbiAgICA8L25nLWNvbnRhaW5lcj5cblxuICAgIDxuZy1jb250YWluZXIgKm5nSWY9XCJpc0hlYXRtYXAgJiYgdG9vbHMucmVnaW9uVG9vbHNcIj5cbiAgICAgIDxzcGFuIGNsYXNzPVwidG9vbGJhci1zZXBhcmF0b3JcIj48L3NwYW4+XG4gICAgICA8IS0tIFJlZ2lvbiBFZGl0b3I6IG9wZW5zIHRoZSBlZGl0b3IgYXMgYSBmbG9hdGluZyBkaWFsb2cgKGZvcm1lcmx5IHRoZVxuICAgICAgICAgICByaWdodC1wYW5lbCBSZWdpb25zIHRhYikuIFNpdHMgd2l0aCB0aGUgcmVnaW9uIGRyYXdpbmcgdG9vbHMuIC0tPlxuICAgICAgPHAtYnV0dG9uIHNpemU9XCJzbWFsbFwiIFt0ZXh0XT1cInRydWVcIlxuICAgICAgICAgICAgICAgIHBUb29sdGlwPVwiT3BlbiB0aGUgUmVnaW9uIEVkaXRvclwiIHRvb2x0aXBQb3NpdGlvbj1cImJvdHRvbVwiXG4gICAgICAgICAgICAgICAgKG9uQ2xpY2spPVwib3BlblJlZ2lvbkVkaXRvci5lbWl0KClcIj5cbiAgICAgICAgPGltZyBzcmM9XCJhc3NldHMvcGxvdHRpbmcvcmVnaW9uLWVkaXRvci5zdmdcIiBjbGFzcz1cInRvb2xiYXItaWNvbi1kYXJrXCIgc3R5bGU9XCJ3aWR0aDogMXJlbVwiLz5cbiAgICAgIDwvcC1idXR0b24+XG4gICAgICA8IS0tIFJlZ2lvbiBkcmF3aW5nIHRvb2xzIC0tPlxuICAgICAgPHAtYnV0dG9uIGljb249XCJwaSBwaS1hcnJvdy11cC1yaWdodFwiIHNpemU9XCJzbWFsbFwiXG4gICAgICAgICAgICAgICAgW3RleHRdPVwiYWN0aXZlRHJhZ01vZGUgIT09ICdzZWxlY3QnXCJcbiAgICAgICAgICAgICAgICBwVG9vbHRpcD1cIlNlbGVjdGlvbiBtb2RlICg8a2JkPnM8L2tiZD4pXCJcbiAgICAgICAgICAgICAgICBbZXNjYXBlXT1cImZhbHNlXCIgdG9vbHRpcFBvc2l0aW9uPVwiYm90dG9tXCJcbiAgICAgICAgICAgICAgICAob25DbGljayk9XCJ0b2dnbGVEcmFnTW9kZS5lbWl0KCdzZWxlY3QnKVwiPlxuICAgICAgPC9wLWJ1dHRvbj5cbiAgICAgIDxwLWJ1dHRvbiBpY29uPVwicGkgcGktc3RvcFwiIHNpemU9XCJzbWFsbFwiXG4gICAgICAgICAgICAgICAgW3RleHRdPVwiYWN0aXZlRHJhZ01vZGUgIT09ICdkcmF3cmVjdCdcIlxuICAgICAgICAgICAgICAgIHBUb29sdGlwPVwiRHJhdyBhIHJlY3Rhbmd1bGFyIHJlZ2lvbiAoPGtiZD5yPC9rYmQ+KVwiIFtlc2NhcGVdPVwiZmFsc2VcIlxuICAgICAgICAgICAgICAgIHRvb2x0aXBQb3NpdGlvbj1cImJvdHRvbVwiXG4gICAgICAgICAgICAgICAgKG9uQ2xpY2spPVwidG9nZ2xlRHJhZ01vZGUuZW1pdCgnZHJhd3JlY3QnKVwiPlxuICAgICAgPC9wLWJ1dHRvbj5cbiAgICAgIDxwLWJ1dHRvbiBzaXplPVwic21hbGxcIlxuICAgICAgICAgICAgICAgIFt0ZXh0XT1cImFjdGl2ZURyYWdNb2RlICE9PSAnZHJhd29wZW5wYXRoJ1wiXG4gICAgICAgICAgICAgICAgcFRvb2x0aXA9XCJEcmF3IGEgcG9seWxpbmUgKDxrYmQ+bDwva2JkPilcIiBbZXNjYXBlXT1cImZhbHNlXCJcbiAgICAgICAgICAgICAgICB0b29sdGlwUG9zaXRpb249XCJib3R0b21cIlxuICAgICAgICAgICAgICAgIChvbkNsaWNrKT1cInRvZ2dsZURyYWdNb2RlLmVtaXQoJ2RyYXdvcGVucGF0aCcpXCI+XG4gICAgICAgIDxpbWcgc3JjPVwiYXNzZXRzL3Bsb3R0aW5nL3BvbHlsaW5lLnN2Z1wiXG4gICAgICAgICAgICAgW2NsYXNzXT1cImFjdGl2ZURyYWdNb2RlID09PSAnZHJhd29wZW5wYXRoJyA/ICd0b29sYmFyLWljb24tYWN0aXZlJyA6ICd0b29sYmFyLWljb24tZGFyaydcIlxuICAgICAgICAgICAgIHN0eWxlPVwid2lkdGg6IDFyZW1cIi8+XG4gICAgICA8L3AtYnV0dG9uPlxuICAgICAgPHAtYnV0dG9uIGljb249XCJwaSBwaS1wZW5jaWxcIiBzaXplPVwic21hbGxcIlxuICAgICAgICAgICAgICAgIFt0ZXh0XT1cImFjdGl2ZURyYWdNb2RlICE9PSAnZHJhd2Nsb3NlZHBhdGgnXCJcbiAgICAgICAgICAgICAgICBwVG9vbHRpcD1cIkRyYXcgYSBmcmVlZm9ybSByZWdpb24gKDxrYmQ+Zjwva2JkPilcIiBbZXNjYXBlXT1cImZhbHNlXCJcbiAgICAgICAgICAgICAgICB0b29sdGlwUG9zaXRpb249XCJib3R0b21cIlxuICAgICAgICAgICAgICAgIChvbkNsaWNrKT1cInRvZ2dsZURyYWdNb2RlLmVtaXQoJ2RyYXdjbG9zZWRwYXRoJylcIj5cbiAgICAgIDwvcC1idXR0b24+XG4gICAgICA8IS0tIEJydXNoOiBwYWludCBhIHJlZ2lvbiBieSBkcmFnZ2luZyBhIGRpc2Mgb2YgdGhlIGdpdmVuIHNpemU7IHRoZSBTaXplXG4gICAgICAgICAgIHNsaWRlciBhcHBlYXJzIHdoaWxlIGl0J3MgYWN0aXZlLiBIb2xkIFNoaWZ0IHRvIGVyYXNlIChzdWJ0cmFjdCkuIC0tPlxuICAgICAgPHAtYnV0dG9uIHNpemU9XCJzbWFsbFwiXG4gICAgICAgICAgICAgICAgW3RleHRdPVwiYWN0aXZlRHJhZ01vZGUgIT09ICdicnVzaCdcIlxuICAgICAgICAgICAgICAgIHBUb29sdGlwPVwiQnJ1c2g6IGNsaWNrIGFuZCBkcmFnIHRvIHBhaW50IGEgcmVnaW9uLlxuICAgICAgICAgICAgICAgIDxicj5Ib2xkIDxrYmQ+U2hpZnQ8L2tiZD4gd2hpbGUgcGFpbnRpbmcgdG8gZXJhc2UgZnJvbSBhIHJlZ2lvbi5cIlxuICAgICAgICAgICAgICAgIFtlc2NhcGVdPVwiZmFsc2VcIiB0b29sdGlwUG9zaXRpb249XCJib3R0b21cIlxuICAgICAgICAgICAgICAgIChvbkNsaWNrKT1cInRvZ2dsZURyYWdNb2RlLmVtaXQoJ2JydXNoJylcIj5cbiAgICAgICAgPGltZyBzcmM9XCJhc3NldHMvcGxvdHRpbmcvcGFpbnRicnVzaC5zdmdcIlxuICAgICAgICAgICAgIFtjbGFzc109XCJhY3RpdmVEcmFnTW9kZSA9PT0gJ2JydXNoJyA/ICd0b29sYmFyLWljb24tYWN0aXZlJyA6ICd0b29sYmFyLWljb24tZGFyaydcIlxuICAgICAgICAgICAgIHN0eWxlPVwid2lkdGg6IDFyZW1cIi8+XG4gICAgICA8L3AtYnV0dG9uPlxuICAgICAgPG5nLWNvbnRhaW5lciAqbmdJZj1cImFjdGl2ZURyYWdNb2RlID09PSAnYnJ1c2gnXCI+XG4gICAgICAgIDxzcGFuIGNsYXNzPVwiZmxleCBhbGlnbi1pdGVtcy1jZW50ZXIgbXgtMSB0b29sYmFyLXRleHRcIj5TaXplPC9zcGFuPlxuICAgICAgICA8cC1zbGlkZXIgWyhuZ01vZGVsKV09XCJicnVzaFNpemVcIlxuICAgICAgICAgICAgICAgICAgW21pbl09XCJicnVzaFNpemVNaW5cIiBbbWF4XT1cImJydXNoU2l6ZU1heFwiIFtzdGVwXT1cImJydXNoU2l6ZVN0ZXBcIlxuICAgICAgICAgICAgICAgICAgKG9uQ2hhbmdlKT1cImJydXNoU2l6ZUNoYW5nZS5lbWl0KCRldmVudC52YWx1ZSlcIlxuICAgICAgICAgICAgICAgICAgcFRvb2x0aXA9XCJCcnVzaCBkaWFtZXRlciBpbiBpbWFnZSBwaXhlbHMuXCJcbiAgICAgICAgICAgICAgICAgIFtlc2NhcGVdPVwiZmFsc2VcIiB0b29sdGlwUG9zaXRpb249XCJib3R0b21cIlxuICAgICAgICAgICAgICAgICAgc3R5bGVDbGFzcz1cIndhbmQtc2Vuc2l0aXZpdHktc2xpZGVyXCI+PC9wLXNsaWRlcj5cbiAgICAgICAgPHNwYW4gY2xhc3M9XCJmbGV4IGFsaWduLWl0ZW1zLWNlbnRlciBteC0xIHRvb2xiYXItdGV4dCB3YW5kLXNlbnNpdGl2aXR5LXZhbHVlXCI+XG4gICAgICAgICAge3sgYnJ1c2hTaXplIH19PC9zcGFuPlxuICAgICAgPC9uZy1jb250YWluZXI+XG5cbiAgICAgIDwhLS0gVmVydGV4IGVkaXRpbmcg4oCUIGJhY2tlbmRzIHdpdGggYSB2ZXJ0ZXgtZWRpdGluZyBvdmVybGF5IChPU0QgKyBuYXBhcmktanMgaW1hZ2UpLiAtLT5cbiAgICAgIDxuZy1jb250YWluZXIgKm5nSWY9XCJzdXBwb3J0c1JlZ2lvblZlcnRleFRvb2xzXCI+XG4gICAgICAgIDxwLWJ1dHRvbiBzaXplPVwic21hbGxcIlxuICAgICAgICAgICAgICAgICAgW3RleHRdPVwiYWN0aXZlRHJhZ01vZGUgIT09ICdkcmF3cG9seWdvbidcIlxuICAgICAgICAgICAgICAgICAgcFRvb2x0aXA9XCJQb2x5Z29uOiBjbGljayB0byBwbGFjZSBlYWNoIHZlcnRleDsgY2xpY2sgdGhlIGZpcnN0IHBvaW50IHRvIGNsb3NlXCJcbiAgICAgICAgICAgICAgICAgIFtlc2NhcGVdPVwiZmFsc2VcIiB0b29sdGlwUG9zaXRpb249XCJib3R0b21cIlxuICAgICAgICAgICAgICAgICAgKG9uQ2xpY2spPVwidG9nZ2xlRHJhZ01vZGUuZW1pdCgnZHJhd3BvbHlnb24nKVwiPlxuICAgICAgICAgIDxpbWcgc3JjPVwiYXNzZXRzL3Bsb3R0aW5nL3BvbHlnb24tdmVydGljZXMuc3ZnXCJcbiAgICAgICAgICAgICAgIFtjbGFzc109XCJhY3RpdmVEcmFnTW9kZSA9PT0gJ2RyYXdwb2x5Z29uJyA/ICd0b29sYmFyLWljb24tYWN0aXZlJyA6ICd0b29sYmFyLWljb24tZGFyaydcIlxuICAgICAgICAgICAgICAgc3R5bGU9XCJ3aWR0aDogMXJlbVwiLz5cbiAgICAgICAgPC9wLWJ1dHRvbj5cbiAgICAgICAgPHAtYnV0dG9uIHNpemU9XCJzbWFsbFwiXG4gICAgICAgICAgICAgICAgICBbdGV4dF09XCJhY3RpdmVEcmFnTW9kZSAhPT0gJ2FkZHBvaW50J1wiXG4gICAgICAgICAgICAgICAgICBwVG9vbHRpcD1cIkFkZCBhIHZlcnRleDogY2xpY2sgYW4gZWRnZSBvZiB0aGUgc2VsZWN0ZWQgcG9seWdvblwiXG4gICAgICAgICAgICAgICAgICBbZXNjYXBlXT1cImZhbHNlXCIgdG9vbHRpcFBvc2l0aW9uPVwiYm90dG9tXCJcbiAgICAgICAgICAgICAgICAgIChvbkNsaWNrKT1cInRvZ2dsZURyYWdNb2RlLmVtaXQoJ2FkZHBvaW50JylcIj5cbiAgICAgICAgICA8aW1nIHNyYz1cImFzc2V0cy9wbG90dGluZy92ZXJ0ZXgtYWRkLnN2Z1wiXG4gICAgICAgICAgICAgICBbY2xhc3NdPVwiYWN0aXZlRHJhZ01vZGUgPT09ICdhZGRwb2ludCcgPyAndG9vbGJhci1pY29uLWFjdGl2ZScgOiAndG9vbGJhci1pY29uLWRhcmsnXCJcbiAgICAgICAgICAgICAgIHN0eWxlPVwid2lkdGg6IDFyZW1cIi8+XG4gICAgICAgIDwvcC1idXR0b24+XG4gICAgICAgIDxwLWJ1dHRvbiBzaXplPVwic21hbGxcIlxuICAgICAgICAgICAgICAgICAgW3RleHRdPVwiYWN0aXZlRHJhZ01vZGUgIT09ICdkZWxldGVwb2ludCdcIlxuICAgICAgICAgICAgICAgICAgcFRvb2x0aXA9XCJEZWxldGUgYSB2ZXJ0ZXg6IGNsaWNrIGEgdmVydGV4IG9mIHRoZSBzZWxlY3RlZCBwb2x5Z29uXCJcbiAgICAgICAgICAgICAgICAgIFtlc2NhcGVdPVwiZmFsc2VcIiB0b29sdGlwUG9zaXRpb249XCJib3R0b21cIlxuICAgICAgICAgICAgICAgICAgKG9uQ2xpY2spPVwidG9nZ2xlRHJhZ01vZGUuZW1pdCgnZGVsZXRlcG9pbnQnKVwiPlxuICAgICAgICAgIDxpbWcgc3JjPVwiYXNzZXRzL3Bsb3R0aW5nL3ZlcnRleC1kZWxldGUuc3ZnXCJcbiAgICAgICAgICAgICAgIFtjbGFzc109XCJhY3RpdmVEcmFnTW9kZSA9PT0gJ2RlbGV0ZXBvaW50JyA/ICd0b29sYmFyLWljb24tYWN0aXZlJyA6ICd0b29sYmFyLWljb24tZGFyaydcIlxuICAgICAgICAgICAgICAgc3R5bGU9XCJ3aWR0aDogMXJlbVwiLz5cbiAgICAgICAgPC9wLWJ1dHRvbj5cbiAgICAgIDwvbmctY29udGFpbmVyPlxuXG4gICAgICA8cC1idXR0b24gc2l6ZT1cInNtYWxsXCJcbiAgICAgICAgICAgICAgICBbdGV4dF09XCJhY3RpdmVEcmFnTW9kZSAhPT0gJ3dhbmQnXCJcbiAgICAgICAgICAgICAgICBwVG9vbHRpcD1cIldhbmQ6IGNsaWNrIGFuZCBkcmFnIHRvIGdyb3cgYSByZWdpb24gYnkgc2ltaWxhciBwaXhlbCB2YWx1ZXMgKDxrYmQ+dzwva2JkPikuXG4gICAgICAgICAgICAgICAgPGJyPkhvbGQgPGtiZD5TaGlmdDwva2JkPiB3aGlsZSBjbGlja2luZyBvciBkcmFnZ2luZyBvbiBhIHJlZ2lvbiB0byBlcmFzZSBwaXhlbHMgZnJvbSBpdC5cIlxuICAgICAgICAgICAgICAgIFtlc2NhcGVdPVwiZmFsc2VcIiB0b29sdGlwUG9zaXRpb249XCJib3R0b21cIlxuICAgICAgICAgICAgICAgIChvbkNsaWNrKT1cInRvZ2dsZURyYWdNb2RlLmVtaXQoJ3dhbmQnKVwiPlxuICAgICAgICA8aW1nIHNyYz1cImFzc2V0cy9wbG90dGluZy93YW5kLnN2Z1wiXG4gICAgICAgICAgICAgW2NsYXNzXT1cImFjdGl2ZURyYWdNb2RlID09PSAnd2FuZCcgPyAndG9vbGJhci1pY29uLWFjdGl2ZScgOiAndG9vbGJhci1pY29uLWRhcmsnXCJcbiAgICAgICAgICAgICBzdHlsZT1cIndpZHRoOiAxcmVtXCIvPlxuICAgICAgPC9wLWJ1dHRvbj5cbiAgICAgIDxuZy1jb250YWluZXIgKm5nSWY9XCJhY3RpdmVEcmFnTW9kZSA9PT0gJ3dhbmQnXCI+XG4gICAgICAgIDxzcGFuIGNsYXNzPVwiZmxleCBhbGlnbi1pdGVtcy1jZW50ZXIgbXgtMSB0b29sYmFyLXRleHRcIj5TZW5zLjwvc3Bhbj5cbiAgICAgICAgPHAtc2xpZGVyIFsobmdNb2RlbCldPVwid2FuZFNlbnNpdGl2aXR5XCJcbiAgICAgICAgICAgICAgICAgIFttaW5dPVwid2FuZFNlbnNpdGl2aXR5TWluXCIgW21heF09XCJ3YW5kU2Vuc2l0aXZpdHlNYXhcIiBbc3RlcF09XCJ3YW5kU2Vuc2l0aXZpdHlTdGVwXCJcbiAgICAgICAgICAgICAgICAgIChvbkNoYW5nZSk9XCJ3YW5kU2Vuc2l0aXZpdHlDaGFuZ2UuZW1pdCgkZXZlbnQudmFsdWUpXCJcbiAgICAgICAgICAgICAgICAgIHBUb29sdGlwPVwiSGlnaGVyID0gc3RyaWN0ZXIgKHNtYWxsZXIgc2VsZWN0aW9uKTsgbG93ZXIgPSBsb29zZXIuXG4gICAgICAgICAgICAgICAgICBUYWtlcyBlZmZlY3QgZnJvbSB0aGUgbmV4dCBjbGljay9kcmFnIHRpY2suXCJcbiAgICAgICAgICAgICAgICAgIFtlc2NhcGVdPVwiZmFsc2VcIiB0b29sdGlwUG9zaXRpb249XCJib3R0b21cIlxuICAgICAgICAgICAgICAgICAgc3R5bGVDbGFzcz1cIndhbmQtc2Vuc2l0aXZpdHktc2xpZGVyXCI+PC9wLXNsaWRlcj5cbiAgICAgICAgPHNwYW4gY2xhc3M9XCJmbGV4IGFsaWduLWl0ZW1zLWNlbnRlciBteC0xIHRvb2xiYXItdGV4dCB3YW5kLXNlbnNpdGl2aXR5LXZhbHVlXCI+XG4gICAgICAgICAge3sgd2FuZFNlbnNpdGl2aXR5LnRvRml4ZWQoMSkgfX08L3NwYW4+XG4gICAgICA8L25nLWNvbnRhaW5lcj5cbiAgICAgIDxwLWJ1dHRvbiBpY29uPVwicGkgcGktZXJhc2VyXCIgc2l6ZT1cInNtYWxsXCJcbiAgICAgICAgICAgICAgICBbdGV4dF09XCJhY3RpdmVEcmFnTW9kZSAhPT0gJ2VyYXNlVmVydGV4J1wiXG4gICAgICAgICAgICAgICAgcFRvb2x0aXA9XCJWZXJ0ZXggZXJhc2VyICg8a2JkPmU8L2tiZD4pOiBjbGljayBvciBkcmFnIHRvIHJlbW92ZSBwb2x5Z29uIHZlcnRpY2VzIGluc2lkZVxuICAgICAgICAgICAgICAgIHRoZSBjdXJzb3IgY2lyY2xlIGZyb20gYW55IHJlZ2lvbi5cIlxuICAgICAgICAgICAgICAgIFtlc2NhcGVdPVwiZmFsc2VcIiB0b29sdGlwUG9zaXRpb249XCJib3R0b21cIlxuICAgICAgICAgICAgICAgIChvbkNsaWNrKT1cInRvZ2dsZURyYWdNb2RlLmVtaXQoJ2VyYXNlVmVydGV4JylcIj5cbiAgICAgIDwvcC1idXR0b24+XG4gICAgICA8bmctY29udGFpbmVyICpuZ0lmPVwiYWN0aXZlRHJhZ01vZGUgPT09ICdlcmFzZVZlcnRleCdcIj5cbiAgICAgICAgPHNwYW4gY2xhc3M9XCJmbGV4IGFsaWduLWl0ZW1zLWNlbnRlciBteC0xIHRvb2xiYXItdGV4dFwiPlJhZGl1czwvc3Bhbj5cbiAgICAgICAgPHAtc2xpZGVyIFsobmdNb2RlbCldPVwidmVydGV4RXJhc2VyUmFkaXVzXCJcbiAgICAgICAgICAgICAgICAgIFttaW5dPVwidmVydGV4RXJhc2VyUmFkaXVzTWluXCIgW21heF09XCJ2ZXJ0ZXhFcmFzZXJSYWRpdXNNYXhcIiBbc3RlcF09XCJ2ZXJ0ZXhFcmFzZXJSYWRpdXNTdGVwXCJcbiAgICAgICAgICAgICAgICAgIChvbkNoYW5nZSk9XCJ2ZXJ0ZXhFcmFzZXJSYWRpdXNDaGFuZ2UuZW1pdCgkZXZlbnQudmFsdWUpXCJcbiAgICAgICAgICAgICAgICAgIHBUb29sdGlwPVwiRXJhc2VyIHJhZGl1cyBpbiBpbWFnZSBwaXhlbHMuXG4gICAgICAgICAgICAgICAgICBWZXJ0aWNlcyB3aXRoaW4gdGhpcyBkaXN0YW5jZSBvZiB0aGUgY3Vyc29yIGFyZSByZW1vdmVkLlwiXG4gICAgICAgICAgICAgICAgICBbZXNjYXBlXT1cImZhbHNlXCIgdG9vbHRpcFBvc2l0aW9uPVwiYm90dG9tXCJcbiAgICAgICAgICAgICAgICAgIHN0eWxlQ2xhc3M9XCJ3YW5kLXNlbnNpdGl2aXR5LXNsaWRlclwiPjwvcC1zbGlkZXI+XG4gICAgICAgIDxzcGFuIGNsYXNzPVwiZmxleCBhbGlnbi1pdGVtcy1jZW50ZXIgbXgtMSB0b29sYmFyLXRleHQgd2FuZC1zZW5zaXRpdml0eS12YWx1ZVwiPlxuICAgICAgICAgIHt7IHZlcnRleEVyYXNlclJhZGl1cyB9fTwvc3Bhbj5cbiAgICAgIDwvbmctY29udGFpbmVyPlxuICAgICAgPHAtYnV0dG9uIHNpemU9XCJzbWFsbFwiIFt0ZXh0XT1cInRydWVcIlxuICAgICAgICAgICAgICAgIFtkaXNhYmxlZF09XCIhY2FuVW5kb1wiXG4gICAgICAgICAgICAgICAgcFRvb2x0aXA9XCJVbmRvIHRoZSBsYXN0IHJlZ2lvbiBhY3Rpb24gKDxrYmQ+Q3RybDwva2JkPis8a2JkPlo8L2tiZD4pLlxuICAgICAgICAgICAgICAgIFVwIHRvIDEwIGFjdGlvbnMgY2FuIGJlIHVuZG9uZS5cIlxuICAgICAgICAgICAgICAgIFtlc2NhcGVdPVwiZmFsc2VcIiB0b29sdGlwUG9zaXRpb249XCJib3R0b21cIlxuICAgICAgICAgICAgICAgIChvbkNsaWNrKT1cInVuZG9SZWdpb24uZW1pdCgpXCI+XG4gICAgICAgIDxpbWcgc3JjPVwiYXNzZXRzL3Bsb3R0aW5nL3VuZG8uc3ZnXCIgY2xhc3M9XCJ0b29sYmFyLWljb24tZGFya1wiIHN0eWxlPVwid2lkdGg6IDFyZW1cIi8+XG4gICAgICA8L3AtYnV0dG9uPlxuICAgICAgPHAtYnV0dG9uIHNpemU9XCJzbWFsbFwiIFt0ZXh0XT1cInRydWVcIlxuICAgICAgICAgICAgICAgIFtkaXNhYmxlZF09XCIhY2FuUmVkb1wiXG4gICAgICAgICAgICAgICAgcFRvb2x0aXA9XCJSZWRvIHRoZSBsYXN0IHVuZG9uZSByZWdpb24gYWN0aW9uXG4gICAgICAgICAgICAgICAgKDxrYmQ+Q3RybDwva2JkPis8a2JkPlNoaWZ0PC9rYmQ+KzxrYmQ+Wjwva2JkPiAvIDxrYmQ+Q3RybDwva2JkPis8a2JkPlk8L2tiZD4pLlwiXG4gICAgICAgICAgICAgICAgW2VzY2FwZV09XCJmYWxzZVwiIHRvb2x0aXBQb3NpdGlvbj1cImJvdHRvbVwiXG4gICAgICAgICAgICAgICAgKG9uQ2xpY2spPVwicmVkb1JlZ2lvbi5lbWl0KClcIj5cbiAgICAgICAgPGltZyBzcmM9XCJhc3NldHMvcGxvdHRpbmcvcmVkby5zdmdcIiBjbGFzcz1cInRvb2xiYXItaWNvbi1kYXJrXCIgc3R5bGU9XCJ3aWR0aDogMXJlbVwiLz5cbiAgICAgIDwvcC1idXR0b24+XG4gICAgICA8cC1idXR0b24gaWNvbj1cInBpIHBpLXRyYXNoXCIgc2l6ZT1cInNtYWxsXCIgW3RleHRdPVwidHJ1ZVwiXG4gICAgICAgICAgICAgICAgcFRvb2x0aXA9XCJEZWxldGUgc2VsZWN0ZWQgcmVnaW9uICg8a2JkPkRlbDwva2JkPilcIlxuICAgICAgICAgICAgICAgIFtlc2NhcGVdPVwiZmFsc2VcIiB0b29sdGlwUG9zaXRpb249XCJib3R0b21cIlxuICAgICAgICAgICAgICAgIChvbkNsaWNrKT1cImRlbGV0ZVJlZ2lvbi5lbWl0KClcIj5cbiAgICAgIDwvcC1idXR0b24+XG4gICAgICA8IS0tIFNBTSAoaml0LXVpIzkwKTogYm94LXByb21wdCBzZWdtZW50LCBpbnRlcmFjdGl2ZSBwb2ludCBwcm9tcHRzLCBhbmQgYVxuICAgICAgICAgICBtb2RlbCBwaWNrZXIuIC0tPlxuICAgICAgPHNwYW4gY2xhc3M9XCJ0b29sYmFyLXNlcGFyYXRvclwiPjwvc3Bhbj5cbiAgICAgIDwhLS0gU0FNIGNvbnRyb2xzIGdyb3VwZWQ6IGJveC1wcm9tcHQgU2VnbWVudCwgdGhlbiB0aGUgcG9pbnQtcHJvbXB0IHRvb2wsXG4gICAgICAgICAgIHRoZW4gdGhlIG1vZGVsLXBpY2tlciBkcm9wZG93biAodGhlIGNoZXZyb24pIHdoaWNoIGdvdmVybnMgQk9USCB0b29scy5cbiAgICAgICAgICAgRmFsbHMgYmFjayB0byBTZWdtZW50ICsgcG9pbnQgd2l0aCBubyBtb2RlbCBwaWNrZXIgd2hlbiBub25lIGFyZSBzZXQuIC0tPlxuICAgICAgPHNwYW4gKm5nSWY9XCJzYW1Nb2RlbHMubGVuZ3RoOyBlbHNlIHNlZ21lbnRQbGFpblwiIGNsYXNzPVwicC1idXR0b25zZXQgc2VnbWVudC1zcGxpdFwiPlxuICAgICAgICA8cC1idXR0b24gc2l6ZT1cInNtYWxsXCIgW3RleHRdPVwidHJ1ZVwiXG4gICAgICAgICAgICAgICAgICBwVG9vbHRpcD1cIlNlZ21lbnQgdGhlIGRyYXduIHJlY3RhbmdsZXMgd2l0aCBTQU0gKGJveCBwcm9tcHQpXCJcbiAgICAgICAgICAgICAgICAgIHRvb2x0aXBQb3NpdGlvbj1cImJvdHRvbVwiXG4gICAgICAgICAgICAgICAgICAob25DbGljayk9XCJzZWdtZW50UmVnaW9ucy5lbWl0KClcIj5cbiAgICAgICAgICA8aW1nIHNyYz1cImFzc2V0cy9wbG90dGluZy9waWN0dXJlLXNlZ21lbnRhdGlvbi5zdmdcIlxuICAgICAgICAgICAgICAgY2xhc3M9XCJ0b29sYmFyLWljb24tZGFya1wiIHN0eWxlPVwid2lkdGg6IDFyZW1cIi8+XG4gICAgICAgIDwvcC1idXR0b24+XG4gICAgICAgIDxuZy1jb250YWluZXIgW25nVGVtcGxhdGVPdXRsZXRdPVwic2FtUG9pbnRCdG5cIj48L25nLWNvbnRhaW5lcj5cbiAgICAgICAgPHAtYnV0dG9uIGljb249XCJwaSBwaS1jaGV2cm9uLWRvd25cIiBzaXplPVwic21hbGxcIiBbdGV4dF09XCJ0cnVlXCJcbiAgICAgICAgICAgICAgICAgIHBUb29sdGlwPVwiUGljayB0aGUgU0FNIG1vZGVsICh1c2VkIGJ5IGJvdGggc2VnbWVudCB0b29scylcIlxuICAgICAgICAgICAgICAgICAgdG9vbHRpcFBvc2l0aW9uPVwiYm90dG9tXCJcbiAgICAgICAgICAgICAgICAgIChvbkNsaWNrKT1cInNhbU1vZGVsTWVudS50b2dnbGUoJGV2ZW50KVwiPlxuICAgICAgICA8L3AtYnV0dG9uPlxuICAgICAgPC9zcGFuPlxuICAgICAgPG5nLXRlbXBsYXRlICNzZWdtZW50UGxhaW4+XG4gICAgICAgIDxzcGFuIGNsYXNzPVwicC1idXR0b25zZXQgc2VnbWVudC1zcGxpdFwiPlxuICAgICAgICAgIDxwLWJ1dHRvbiBzaXplPVwic21hbGxcIiBbdGV4dF09XCJ0cnVlXCJcbiAgICAgICAgICAgICAgICAgICAgcFRvb2x0aXA9XCJTZWdtZW50IHRoZSBkcmF3biByZWN0YW5nbGVzIHdpdGggU0FNIChib3ggcHJvbXB0KVwiXG4gICAgICAgICAgICAgICAgICAgIHRvb2x0aXBQb3NpdGlvbj1cImJvdHRvbVwiXG4gICAgICAgICAgICAgICAgICAgIChvbkNsaWNrKT1cInNlZ21lbnRSZWdpb25zLmVtaXQoKVwiPlxuICAgICAgICAgICAgPGltZyBzcmM9XCJhc3NldHMvcGxvdHRpbmcvcGljdHVyZS1zZWdtZW50YXRpb24uc3ZnXCJcbiAgICAgICAgICAgICAgICAgY2xhc3M9XCJ0b29sYmFyLWljb24tZGFya1wiIHN0eWxlPVwid2lkdGg6IDFyZW1cIi8+XG4gICAgICAgICAgPC9wLWJ1dHRvbj5cbiAgICAgICAgICA8bmctY29udGFpbmVyIFtuZ1RlbXBsYXRlT3V0bGV0XT1cInNhbVBvaW50QnRuXCI+PC9uZy1jb250YWluZXI+XG4gICAgICAgIDwvc3Bhbj5cbiAgICAgIDwvbmctdGVtcGxhdGU+XG4gICAgICA8IS0tIFBvaW50LXByb21wdCB0b29sIOKAlCBzaGFyZWQgYmV0d2VlbiB0aGUgbW9kZWxzIC8gbm8tbW9kZWxzIGxheW91dHMgc28gaXRcbiAgICAgICAgICAgYWx3YXlzIHNpdHMganVzdCBsZWZ0IG9mIHRoZSBtb2RlbCBkcm9wZG93bi4gLS0+XG4gICAgICA8bmctdGVtcGxhdGUgI3NhbVBvaW50QnRuPlxuICAgICAgICA8cC1idXR0b24gc2l6ZT1cInNtYWxsXCJcbiAgICAgICAgICAgICAgICAgIFt0ZXh0XT1cImFjdGl2ZURyYWdNb2RlICE9PSAnc2FtUG9pbnQnXCJcbiAgICAgICAgICAgICAgICAgIHBUb29sdGlwPVwiU0FNIHBvaW50IHByb21wdHM6IGNsaWNrIGEgZmliZXIgdG8gc2VnbWVudCBpdCBhcyBhIG5ldyByZWdpb247XG4gICAgICAgICAgICAgICAgICBTaGlmdC9BbHQtY2xpY2sgdG8gcmVmaW5lIChleGNsdWRlKSB0aGUgY3VycmVudCBvbmUuPGJyPkVzYyB1bmRvZXMgaXQuXCJcbiAgICAgICAgICAgICAgICAgIFtlc2NhcGVdPVwiZmFsc2VcIiB0b29sdGlwUG9zaXRpb249XCJib3R0b21cIlxuICAgICAgICAgICAgICAgICAgKG9uQ2xpY2spPVwidG9nZ2xlRHJhZ01vZGUuZW1pdCgnc2FtUG9pbnQnKVwiPlxuICAgICAgICAgIDxpbWcgc3JjPVwiYXNzZXRzL3Bsb3R0aW5nL2NsaWNrLnN2Z1wiXG4gICAgICAgICAgICAgICBbY2xhc3NdPVwiYWN0aXZlRHJhZ01vZGUgPT09ICdzYW1Qb2ludCcgPyAndG9vbGJhci1pY29uLWFjdGl2ZScgOiAndG9vbGJhci1pY29uLWRhcmsnXCJcbiAgICAgICAgICAgICAgIHN0eWxlPVwid2lkdGg6IDFyZW1cIi8+XG4gICAgICAgIDwvcC1idXR0b24+XG4gICAgICA8L25nLXRlbXBsYXRlPlxuICAgICAgPHAtbWVudSAjc2FtTW9kZWxNZW51IFttb2RlbF09XCJzYW1NZW51SXRlbXNcIiBbcG9wdXBdPVwidHJ1ZVwiIGFwcGVuZFRvPVwiYm9keVwiPjwvcC1tZW51PlxuICAgICAgPCEtLSBIZWxwOiBleHBsYWlucyB0aGUgVmlULVQgdnMgVmlULUIgdHJhZGUtb2ZmIGJlaGluZCB0aGUgbW9kZWwgcGlja2VyLiAtLT5cbiAgICAgIDxwLWJ1dHRvbiAqbmdJZj1cInNhbU1vZGVscy5sZW5ndGhcIiBpY29uPVwicGkgcGktaW5mby1jaXJjbGVcIiBzaXplPVwic21hbGxcIiBbdGV4dF09XCJ0cnVlXCJcbiAgICAgICAgICAgICAgICBwVG9vbHRpcD1cIkFib3V0IHRoZSBTQU0gbW9kZWxzXCIgdG9vbHRpcFBvc2l0aW9uPVwiYm90dG9tXCJcbiAgICAgICAgICAgICAgICAob25DbGljayk9XCJzYW1Nb2RlbEluZm8udG9nZ2xlKCRldmVudClcIj5cbiAgICAgIDwvcC1idXR0b24+XG4gICAgICA8cC1vdmVybGF5UGFuZWwgI3NhbU1vZGVsSW5mbyBhcHBlbmRUbz1cImJvZHlcIiBzdHlsZUNsYXNzPVwic2FtLW1vZGVsLWluZm9cIj5cbiAgICAgICAgPGRpdiBzdHlsZT1cIm1heC13aWR0aDogMjRyZW07IGxpbmUtaGVpZ2h0OiAxLjRcIj5cbiAgICAgICAgICA8ZGl2IGNsYXNzPVwiZm9udC1ib2xkIG1iLTFcIj5TQU0gbW9kZWxzPC9kaXY+XG4gICAgICAgICAgPHAgY2xhc3M9XCJtdC0wIG1iLTJcIj5cbiAgICAgICAgICAgIEFsbCBhcmUgcHJvbXB0YWJsZSBTZWdtZW50IEFueXRoaW5nIG1vZGVscyBmaW5ldHVuZWQgZm9yIGJpb21lZGljYWwgaW1hZ2VzLiBUaGV5IGRpZmZlclxuICAgICAgICAgICAgaW4gdGhlIDxpPmltYWdlIGVuY29kZXI8L2k+ICh0aGUgaGVhdnkgb25jZS1wZXItaW1hZ2Ugc3RlcCkgYW5kIHRoZSB0cmFpbmluZyBkb21haW4g4oCUXG4gICAgICAgICAgICBib3ggYW5kIHBvaW50IHByb21wdGluZyBiZWhhdmUgdGhlIHNhbWUuXG4gICAgICAgICAgPC9wPlxuICAgICAgICAgIDxkaXYgY2xhc3M9XCJtYi0yXCI+XG4gICAgICAgICAgICA8ZGl2IGNsYXNzPVwiZm9udC1ib2xkXCI+bWljcm8tc2FtIFZpVC1UIDxzcGFuIGNsYXNzPVwiZm9udC1ub3JtYWxcIj4oZGVmYXVsdCk8L3NwYW4+PC9kaXY+XG4gICAgICAgICAgICA8ZGl2PkxpZ2h0LW1pY3Jvc2NvcHkgZmluZXR1bmVkLiBUaW55VmlUIGVuY29kZXIsIH4xNCZuYnNwO01CLCB2ZXJ5IGZhc3QsIHJ1bnMgb24gV0FTTS5cbiAgICAgICAgICAgICAgR29vZCBtYXNrIHF1YWxpdHkg4oCUIGJlc3QgZm9yIHF1aWNrIGludGVyYWN0aXZlIHdvcmsgYW5kIG1hbnkvbGFyZ2Ugb2JqZWN0cy48L2Rpdj5cbiAgICAgICAgICA8L2Rpdj5cbiAgICAgICAgICA8ZGl2IGNsYXNzPVwibWItMlwiPlxuICAgICAgICAgICAgPGRpdiBjbGFzcz1cImZvbnQtYm9sZFwiPm1pY3JvLXNhbSBWaVQtQjwvZGl2PlxuICAgICAgICAgICAgPGRpdj5MaWdodC1taWNyb3Njb3B5IGZpbmV0dW5lZC4gVmlULUJhc2UgZW5jb2RlciwgfjE3MiZuYnNwO01CLCBzbG93ZXIsIEdQVS1hY2NlbGVyYXRlZFxuICAgICAgICAgICAgICAoV2ViR1BVKS4gSGlnaGVzdCBhY2N1cmFjeSDigJQgd2hlbiBWaVQtVCBtaXNzZXMgc3VidGxlIG9yIG92ZXJsYXBwaW5nIGJvdW5kYXJpZXMuPC9kaXY+XG4gICAgICAgICAgPC9kaXY+XG4gICAgICAgICAgPGRpdiBjbGFzcz1cIm1iLTJcIj5cbiAgICAgICAgICAgIDxkaXYgY2xhc3M9XCJmb250LWJvbGRcIj5wYXRoby1zYW0gVmlULUI8L2Rpdj5cbiAgICAgICAgICAgIDxkaXY+SGlzdG9wYXRob2xvZ3kgZmluZXR1bmVkIChIJmFtcDtFIC8gc3RhaW5lZCB0aXNzdWUpLiBWaVQtQmFzZSBlbmNvZGVyLCBHUFUtYWNjZWxlcmF0ZWQuXG4gICAgICAgICAgICAgIFVzZSBpdCBmb3IgcGF0aG9sb2d5IHNsaWRlcyDigJQgaXQgc2VnbWVudHMgdGlzc3VlL251Y2xlaSBiZXR0ZXIgdGhhbiB0aGUgbWljcm9zY29weSBtb2RlbHMuPC9kaXY+XG4gICAgICAgICAgPC9kaXY+XG4gICAgICAgICAgPGRpdiBjbGFzcz1cIm1iLTJcIj5cbiAgICAgICAgICAgIDxkaXYgY2xhc3M9XCJmb250LWJvbGRcIj5wYXRoby1zYW0gVmlULUIgKGludDgpPC9kaXY+XG4gICAgICAgICAgICA8ZGl2PlNhbWUgbW9kZWwsIGludDgtcXVhbnRpemVkIOKAlCB+MTAwJm5ic3A7TUIgdnMgfjE4MCZuYnNwO01CIChzbWFsbGVyIGRvd25sb2FkKS4gUnVucyBvblxuICAgICAgICAgICAgICBXQVNNIChzbG93ZXIgZW5jb2RlIHRoYW4gdGhlIGZwMTYgdmVyc2lvbik7IG1hc2tzIGFyZSBuZWFyLWlkZW50aWNhbC4gUGljayBpdCBvbiBzbG93XG4gICAgICAgICAgICAgIGNvbm5lY3Rpb25zLjwvZGl2PlxuICAgICAgICAgIDwvZGl2PlxuICAgICAgICAgIDxkaXYgY2xhc3M9XCJ0ZXh0LXNtXCIgc3R5bGU9XCJvcGFjaXR5OiAwLjc1XCI+XG4gICAgICAgICAgICBUaGUgY2hvaWNlIGFwcGxpZXMgdG8gYm90aCB0aGUgYm94IGFuZCBwb2ludCB0b29scy4gRWFjaCB0b29sIGxvYWRzIGl0cyBvd24gZW5jb2RlcixcbiAgICAgICAgICAgIHNvIHRoZSBmaXJzdCBjbGljayBhZnRlciBzd2l0Y2hpbmcgcmUtZW5jb2RlcyBvbmNlLlxuICAgICAgICAgIDwvZGl2PlxuICAgICAgICA8L2Rpdj5cbiAgICAgIDwvcC1vdmVybGF5UGFuZWw+XG4gICAgICA8cC1idXR0b24gc2l6ZT1cInNtYWxsXCIgW3RleHRdPVwidHJ1ZVwiXG4gICAgICAgICAgICAgICAgcFRvb2x0aXA9XCJBdXRvLXNlZ21lbnQgY2VsbHMgaW5zaWRlIGVhY2ggZHJhd24gcmVjdGFuZ2xlIChjZWxscG9zZS1TQU0sIGNsaWVudC1zaWRlKVwiXG4gICAgICAgICAgICAgICAgdG9vbHRpcFBvc2l0aW9uPVwiYm90dG9tXCJcbiAgICAgICAgICAgICAgICAob25DbGljayk9XCJzZWdtZW50Q2VsbHBvc2UuZW1pdCgpXCI+XG4gICAgICAgIDxpbWcgc3JjPVwiYXNzZXRzL3Bsb3R0aW5nL2NlbGxzLnN2Z1wiIGNsYXNzPVwidG9vbGJhci1pY29uLWRhcmtcIiBzdHlsZT1cIndpZHRoOiAxcmVtXCIvPlxuICAgICAgPC9wLWJ1dHRvbj5cblxuICAgICAgPCEtLSBZT0xPOiBkZXRlY3Qgb2JqZWN0cyBhY3Jvc3MgdGhlIGN1cnJlbnQgdmlldy4gVW5saWtlIHRoZSBTQU0gYW5kXG4gICAgICAgICAgIGNlbGxwb3NlIHRvb2xzIGl0IHRha2VzIG5vIHByb21wdCDigJQgYSBkZXRlY3RvciBmaW5kcyBvYmplY3RzIGluIGFcbiAgICAgICAgICAgZmllbGQgcmF0aGVyIHRoYW4gYmVpbmcgcG9pbnRlZCBhdCBvbmUg4oCUIHNvIHRoZXJlIGlzIG5vdGhpbmcgdG8gZHJhd1xuICAgICAgICAgICBmaXJzdC4gU3BsaXQgYnV0dG9uOiBydW4sIHBpY2sgY2hlY2twb2ludCwgb3BlbiBwYXJhbWV0ZXJzLiAtLT5cbiAgICAgIDxzcGFuICpuZ0lmPVwieW9sb01vZGVscy5sZW5ndGhcIiBjbGFzcz1cInAtYnV0dG9uc2V0IHNlZ21lbnQtc3BsaXRcIj5cbiAgICAgICAgPHAtYnV0dG9uIGljb249XCJwaSBwaS1taWNyb2NoaXAtYWlcIiBzaXplPVwic21hbGxcIiBbdGV4dF09XCJ0cnVlXCJcbiAgICAgICAgICAgICAgICAgIHBUb29sdGlwPVwiRGV0ZWN0IG9iamVjdHMgaW4gdGhlIGN1cnJlbnQgdmlldyB3aXRoIFlPTE8gKGNsaWVudC1zaWRlKVwiXG4gICAgICAgICAgICAgICAgICB0b29sdGlwUG9zaXRpb249XCJib3R0b21cIlxuICAgICAgICAgICAgICAgICAgKG9uQ2xpY2spPVwiZGV0ZWN0WW9sby5lbWl0KClcIj5cbiAgICAgICAgPC9wLWJ1dHRvbj5cbiAgICAgICAgPHAtYnV0dG9uIGljb249XCJwaSBwaS1jaGV2cm9uLWRvd25cIiBzaXplPVwic21hbGxcIiBbdGV4dF09XCJ0cnVlXCJcbiAgICAgICAgICAgICAgICAgIHBUb29sdGlwPVwiUGljayB0aGUgWU9MTyBtb2RlbFwiXG4gICAgICAgICAgICAgICAgICB0b29sdGlwUG9zaXRpb249XCJib3R0b21cIlxuICAgICAgICAgICAgICAgICAgKG9uQ2xpY2spPVwieW9sb01vZGVsTWVudS50b2dnbGUoJGV2ZW50KVwiPlxuICAgICAgICA8L3AtYnV0dG9uPlxuICAgICAgICA8cC1idXR0b24gaWNvbj1cInBpIHBpLXNsaWRlcnMtaFwiIHNpemU9XCJzbWFsbFwiIFt0ZXh0XT1cInRydWVcIlxuICAgICAgICAgICAgICAgICAgcFRvb2x0aXA9XCJZT0xPIHBhcmFtZXRlcnNcIlxuICAgICAgICAgICAgICAgICAgdG9vbHRpcFBvc2l0aW9uPVwiYm90dG9tXCJcbiAgICAgICAgICAgICAgICAgIChvbkNsaWNrKT1cIm9wZW5Zb2xvUGFyYW1zLmVtaXQoKVwiPlxuICAgICAgICA8L3AtYnV0dG9uPlxuICAgICAgPC9zcGFuPlxuICAgICAgPHAtbWVudSAjeW9sb01vZGVsTWVudSBbbW9kZWxdPVwieW9sb01lbnVJdGVtc1wiIFtwb3B1cF09XCJ0cnVlXCIgYXBwZW5kVG89XCJib2R5XCI+PC9wLW1lbnU+XG5cbiAgICAgIDwhLS0gUmV0aW5hbCBsYXllcnM6IHNlbWFudGljIHNlZ21lbnRhdGlvbiBvZiB0aGUgY3VycmVudCB2aWV3LiBTYW1lXG4gICAgICAgICAgIG5vLXByb21wdCBzaGFwZSBhcyBZT0xPIOKAlCBpdCBsYWJlbHMgZXZlcnkgcGl4ZWwgcmF0aGVyIHRoYW4gYmVpbmdcbiAgICAgICAgICAgcG9pbnRlZCBhdCBhbiBvYmplY3Qg4oCUIHNvIHRoZSBzYW1lIHNwbGl0IGJ1dHRvbjogcnVuLCBwaWNrXG4gICAgICAgICAgIGNoZWNrcG9pbnQsIG9wZW4gcGFyYW1ldGVycy4gLS0+XG4gICAgICA8c3BhbiAqbmdJZj1cInJldGluYWxNb2RlbHMubGVuZ3RoXCIgY2xhc3M9XCJwLWJ1dHRvbnNldCBzZWdtZW50LXNwbGl0XCI+XG4gICAgICAgIDxwLWJ1dHRvbiBpY29uPVwicGkgcGktYWxpZ24tbGVmdFwiIHNpemU9XCJzbWFsbFwiIFt0ZXh0XT1cInRydWVcIlxuICAgICAgICAgICAgICAgICAgcFRvb2x0aXA9XCJTZWdtZW50IHJldGluYWwgbGF5ZXJzIGluIHRoZSBjdXJyZW50IHZpZXcgKGNsaWVudC1zaWRlKVwiXG4gICAgICAgICAgICAgICAgICB0b29sdGlwUG9zaXRpb249XCJib3R0b21cIlxuICAgICAgICAgICAgICAgICAgKG9uQ2xpY2spPVwic2VnbWVudFJldGluYWwuZW1pdCgpXCI+XG4gICAgICAgIDwvcC1idXR0b24+XG4gICAgICAgIDxwLWJ1dHRvbiBpY29uPVwicGkgcGktY2hldnJvbi1kb3duXCIgc2l6ZT1cInNtYWxsXCIgW3RleHRdPVwidHJ1ZVwiXG4gICAgICAgICAgICAgICAgICBwVG9vbHRpcD1cIlBpY2sgdGhlIHJldGluYWwgbGF5ZXIgbW9kZWxcIlxuICAgICAgICAgICAgICAgICAgdG9vbHRpcFBvc2l0aW9uPVwiYm90dG9tXCJcbiAgICAgICAgICAgICAgICAgIChvbkNsaWNrKT1cInJldGluYWxNb2RlbE1lbnUudG9nZ2xlKCRldmVudClcIj5cbiAgICAgICAgPC9wLWJ1dHRvbj5cbiAgICAgICAgPHAtYnV0dG9uIGljb249XCJwaSBwaS1zbGlkZXJzLWhcIiBzaXplPVwic21hbGxcIiBbdGV4dF09XCJ0cnVlXCJcbiAgICAgICAgICAgICAgICAgIHBUb29sdGlwPVwiUmV0aW5hbCBsYXllciBwYXJhbWV0ZXJzXCJcbiAgICAgICAgICAgICAgICAgIHRvb2x0aXBQb3NpdGlvbj1cImJvdHRvbVwiXG4gICAgICAgICAgICAgICAgICAob25DbGljayk9XCJvcGVuUmV0aW5hbFBhcmFtcy5lbWl0KClcIj5cbiAgICAgICAgPC9wLWJ1dHRvbj5cbiAgICAgIDwvc3Bhbj5cbiAgICAgIDxwLW1lbnUgI3JldGluYWxNb2RlbE1lbnUgW21vZGVsXT1cInJldGluYWxNZW51SXRlbXNcIiBbcG9wdXBdPVwidHJ1ZVwiIGFwcGVuZFRvPVwiYm9keVwiPjwvcC1tZW51PlxuICAgIDwvbmctY29udGFpbmVyPlxuXG4gICAgPG5nLWNvbnRhaW5lciAqbmdJZj1cInRvb2xzLmhlbHBcIj5cbiAgICAgIDxzcGFuIGNsYXNzPVwidG9vbGJhci1zZXBhcmF0b3JcIj48L3NwYW4+XG5cbiAgICAgIDwhLS0gSGVscCAtLT5cbiAgICAgIDxwLWJ1dHRvbiBpY29uPVwicGkgcGktcXVlc3Rpb24tY2lyY2xlXCIgc2l6ZT1cInNtYWxsXCIgW3RleHRdPVwidHJ1ZVwiXG4gICAgICAgICAgICAgICAgcFRvb2x0aXA9XCJIZWxwXCIgdG9vbHRpcFBvc2l0aW9uPVwiYm90dG9tXCJcbiAgICAgICAgICAgICAgICAob25DbGljayk9XCJzaG93SGVscCgpXCI+XG4gICAgICA8L3AtYnV0dG9uPlxuICAgIDwvbmctY29udGFpbmVyPlxuXG4gIDwvZGl2PlxuPC9wLXRvb2xiYXI+XG48cC1kaWFsb2cgaGVhZGVyPVwiUGxvdHRpbmcgaGVscFwiIFsodmlzaWJsZSldPVwiZGlzcGxheUhlbHBEaWFsb2dcIiBbbW9kYWxdPVwidHJ1ZVwiIFtzdHlsZV09XCJ7d2lkdGg6ICc1MHZ3J31cIlxuICAgICAgICAgIFttYXhpbWl6YWJsZV09XCJ0cnVlXCIgW2RyYWdnYWJsZV09XCJmYWxzZVwiIFtyZXNpemFibGVdPVwiZmFsc2VcIj5cbiAgPHA+Q2xpY2sgYSBmaWxlIGluIHRoZSBsZWZ0LWhhbmQgdHJlZSB0byBkaXNwbGF5IGl0LiBCeSBkZWZhdWx0IGl0IG9wZW5zIGluIHRoZVxuICAgIDxiPkltYWdlPC9iPiB2aWV3IOKAlCBhIG5hdGl2ZWx5IHRpbGVkLCBkZWVwbHkgem9vbWFibGUgcmFzdGVyIChPcGVuU2VhZHJhZ29uKSB3aXRoIGFcbiAgICBuYXZpZ2F0b3IgbWluaW1hcCBhbmQgYSBwaHlzaWNhbC11bml0cyBzY2FsZSBiYXIuIDxiPkNsaWNrIHRoZSBpbWFnZSB0byB6b29tPC9iPiB0b3dhcmRcbiAgICB0aGUgY2xpY2tlZCBwb2ludDsgc2Nyb2xsIHRvIHpvb20gYW5kIGRyYWcgdG8gcGFuLiBXaGVuIGEgc3RhY2sgaXMgb3BlbiwgYSBzbGljZSBzbGlkZXJcbiAgICAoSW1hZ2Ugdmlldykgb3IgaW1hZ2UtbW9kZSB0b2dnbGUgKG90aGVyIHZpZXdzKSBhcHBlYXJzLiBSZWdpb25zIGNhbiBhbHNvIGJlIGVkaXRlZCBpbiB0aGVcbiAgICA8Yj5SZWdpb24gRWRpdG9yPC9iPiwgb3BlbmVkIHdpdGggdGhlXG4gICAgPGltZyBzcmM9XCJhc3NldHMvcGxvdHRpbmcvcmVnaW9uLWVkaXRvci5zdmdcIiB3aWR0aD1cIjE0XCIgaGVpZ2h0PVwiMTRcIiBhbHQ9XCJSZWdpb24gRWRpdG9yXCIvPlxuICAgIHRvb2xiYXIgYnV0dG9uIGFzIGEgZmxvYXRpbmcgZGlhbG9nLiBEcmFnIHRoZSA8aSBjbGFzcz1cInBpIHBpLWJhcnNcIj48L2k+IGhhbmRsZSB0byBkZXRhY2hcbiAgICB0aGlzIHRvb2xiYXIgaW50byBhIGZsb2F0aW5nIHdpbmRvdy5cbiAgPC9wPlxuXG4gIDxoMz5QbG90LXR5cGUgZHJvcGRvd248L2gzPlxuICA8cD5cbiAgICBUaGUgPGI+cGxvdC10eXBlIGRyb3Bkb3duPC9iPiBzd2l0Y2hlcyBob3cgdGhlIGZpbGUgaXMgdmlzdWFsaXplZDpcbiAgICA8Yj5JbWFnZTwvYj4sIDxiPkNvbnRvdXI8L2I+LCA8Yj5TY2F0dGVyIChyZWdpb25zKTwvYj4sIDxiPlN1cmZhY2UgKDNEKTwvYj4sXG4gICAgPGI+U2NhdHRlciAzRDwvYj4sIGFuZCA8Yj5Jc29zdXJmYWNlICgzRCk8L2I+LiBTY2FsYXIgYW5kIDNEIHR5cGVzIHJlcXVpcmUgYSBncmF5c2NhbGVcbiAgICBpbWFnZSAodGhlIDNEIHZvbHVtZSB0eXBlcyBhbHNvIHJlcXVpcmUgYSB6LXN0YWNrKS4gVGhlIG5vbi1pbWFnZSB2aWV3cyByZW5kZXIgd2l0aFxuICAgIFBsb3RseSBhbmQgc3VwcG9ydCBcInJlYWwgem9vbWluZ1wiIChhIGRvd25zY2FsZWQgb3ZlcnZpZXcgdGhhdCByZS1mZXRjaGVzIGhpZ2hlclxuICAgIHJlc29sdXRpb24gYXMgeW91IHpvb20gaW4pLlxuICA8L3A+XG4gIDx1bD5cbiAgICA8bGk+XCI8Yj5Jc29zdXJmYWNlIGJhbmQ8L2I+XCIgc2xpZGVyIChJc29zdXJmYWNlKTogc2V0cyB0aGUgaW50ZW5zaXR5IGJhbmQgdGhlIHN1cmZhY2VzIGFyZVxuICAgICAgZHJhd24gZm9yLCBtYXBwZWQgb250byB0aGUgdm9sdW1lJ3MgcmVhbCBpbnRlbnNpdHkgcmFuZ2UuPC9saT5cbiAgICA8bGk+XCI8Yj4zRCBjYW1lcmE8L2I+XCIgKFN1cmZhY2UgLyBTY2F0dGVyIDNEIC8gSXNvc3VyZmFjZSk6IHpvb20gPGkgY2xhc3M9XCJwaSBwaS1zZWFyY2hcIj48L2k+LFxuICAgICAgcGFuIDxpIGNsYXNzPVwicGkgcGktYXJyb3dzLWFsdFwiPjwvaT4sIG9yYml0IDxpIGNsYXNzPVwicGkgcGktZ2xvYmVcIj48L2k+LFxuICAgICAgdHVybnRhYmxlIDxpIGNsYXNzPVwicGkgcGktc3luY1wiPjwvaT4sIGFuZCByZXNldCBjYW1lcmEgPGkgY2xhc3M9XCJwaSBwaS1ob21lXCI+PC9pPjtcbiAgICAgIHNjcm9sbCB0byB6b29tIHRoZSAzRCBzY2VuZS48L2xpPlxuICA8L3VsPlxuXG4gIDxoMz5JbWFnZSBtYW5pcHVsYXRpb24gdG9vbHM8L2gzPlxuICA8dWw+XG4gICAgPGxpPlwiPGI+UmVnaW9uIEVkaXRvcjwvYj5cIiA8aW1nIHNyYz1cImFzc2V0cy9wbG90dGluZy9yZWdpb24tZWRpdG9yLnN2Z1wiIHdpZHRoPVwiMTVcIiBoZWlnaHQ9XCIxNVwiPjpcbiAgICAgIG9wZW5zIHRoZSBSZWdpb24gRWRpdG9yIGFzIGEgZmxvYXRpbmcsIGRyYWdnYWJsZSBkaWFsb2cg4oCUIGEgcGVyLWltYWdlIHRhYmxlIG9mIGV2ZXJ5IHJlZ2lvblxuICAgICAgKGNsYXNzLCBjb2xvdXIsIGFyZWEpIHdpdGggYnVsayBzZWxlY3QvbGFiZWwvY29sb3VyL2RlbGV0ZSBhbmQgR2VvSlNPTiBpbXBvcnQvZXhwb3J0LiBJdCBzdGF5c1xuICAgICAgaW4gc3luYyB3aXRoIHRoZSBpbWFnZSwgc28geW91IGNhbiBrZWVwIGl0IG9wZW4gd2hpbGUgZHJhd2luZy48L2xpPlxuICAgIDxsaT5cIjxiPkludGVuc2l0eSBwcm9maWxlIGxpbmVzPC9iPlwiIDxpbWcgc3JjPVwiYXNzZXRzL3Bsb3R0aW5nL3NwbGluZS1jaGFydC5zdmdcIiB3aWR0aD1cIjE1XCIgaGVpZ2h0PVwiMTVcIj5cbiAgICAgICg8aT53b3JrIGluIHByb2dyZXNzIOKAlCBub3QgeWV0IHN0YWJsZTwvaT4pOiBhZGRzIGEgY29sb3VyZWQgbGluZSBST0k7IGEgZmxvYXRpbmcgaW5zZXRcbiAgICAgIGNoYXJ0IHBsb3RzIGludGVuc2l0eSBhbG9uZyBlYWNoIGxpbmUsIHVwZGF0ZXMgbGl2ZSBhcyB5b3UgZHJhZyBpdCwgYW5kIHJlLXNhbXBsZXMgYXQgdGhlXG4gICAgICBjdXJyZW50IHpvb20gcmVzb2x1dGlvbi4gRWFjaCBhZGRlZCBsaW5lIGdldHMgaXRzIG93biBjb2xvdXI7IHNlbGVjdCBhIGxpbmUgYW5kIHVzZSB0aGVcbiAgICAgIHRyYXNoIHRvb2wgPGkgY2xhc3M9XCJwaSBwaS10cmFzaFwiPjwvaT4gdG8gcmVtb3ZlIGl0IChhbmQgaXRzIHRyYWNlKS48L2xpPlxuICAgIDxsaT5cIjxiPkNoYW5uZWxzICZhbXA7IEhpc3RvZ3JhbTwvYj5cIiA8aW1nIHNyYz1cImFzc2V0cy9wbG90dGluZy9pbnZlcnQuc3ZnXCIgd2lkdGg9XCIxNVwiIGhlaWdodD1cIjE1XCI+OlxuICAgICAgb3BlbnMgYSBmbG9hdGluZyBkaWFsb2cgZm9yIGJyaWdodG5lc3MvY29udHJhc3QsIGdhbW1hLCBhbmQgcGVyLWNoYW5uZWwgZGlzcGxheSwgcGx1cyB0aGVcbiAgICAgIDxiPmNvbG9ybWFwIC8gTFVUPC9iPiBjb250cm9scyAoZ3JheXNjYWxlIG9ubHkpIOKAlCBhIDxiPnJldmVyc2U8L2I+IHRvZ2dsZSBhbmQgYSBjb2xvcm1hcFxuICAgICAgZHJvcGRvd24gKGRlZmF1bHQ6IGludmVydGVkIGdyZXlzKS48L2xpPlxuICAgIDxsaT5cIjxiPlNtb290aGVuPC9iPlwiIDxpbWcgc3JjPVwiYXNzZXRzL3Bsb3R0aW5nL3Ntb290aC5zdmdcIiB3aWR0aD1cIjE1XCIgaGVpZ2h0PVwiMTVcIj5cbiAgICAgIChJbWFnZSB2aWV3IG9ubHkpOiB0aGUgaW1hZ2Ugb3BlbnMgc2hvd2luZyA8Yj5yYXcgcGl4ZWxzPC9iPiAobmVhcmVzdC1uZWlnaGJvdXIpLCBzb1xuICAgICAgem9vbWluZyBpbiByZW5kZXJzIGNyaXNwIGJsb2NrcyBmb3IgcGl4ZWwgaW5zcGVjdGlvbi4gVG9nZ2xlIHRoaXMgb24gZm9yIGJpbGluZWFyXG4gICAgICBzbW9vdGhpbmc7IHRoZSBpY29uIHR1cm5zIHdoaXRlIHdoaWxlIHNtb290aGluZyBpcyBhY3RpdmUuPC9saT5cbiAgPC91bD5cblxuICA8aDM+RG93bmxvYWQgJmFtcDsgdmlldyB0b29sczwvaDM+XG4gIDx1bD5cbiAgICA8bGk+XCI8Yj5Eb3dubG9hZDwvYj5cIiA8aSBjbGFzcz1cInBpIHBpLWRvd25sb2FkXCI+PC9pPjogZG93bmxvYWQgdGhlIGN1cnJlbnQgaW1hZ2UgYXMgYSBQTkcuPC9saT5cbiAgICA8bGk+XCI8Yj5GaXQgdG8gdmlldzwvYj5cIiAvIGF1dG9zY2FsZSA8aSBjbGFzcz1cInBpIHBpLXdpbmRvdy1tYXhpbWl6ZVwiPjwvaT46IHJlc2V0cyB0aGUgaW1hZ2VcbiAgICAgIHRvIGl0cyBvcmlnaW5hbCBkaW1lbnNpb25zLiBJbiBzdGFjayBtb2RlIGl0IGFsc28gcmVzZXRzIHRvIHNpbmdsZS1pbWFnZSBtb2RlLjwvbGk+XG4gIDwvdWw+XG5cbiAgPGgzPlpvb20gdG9vbHM8L2gzPlxuICA8dWw+XG4gICAgPGxpPlwiPGI+Wm9vbSBib3g8L2I+XCIgPGltZyBzcmM9XCJhc3NldHMvcGxvdHRpbmcvem9vbS1ib3guc3ZnXCIgd2lkdGg9XCIxNVwiIGhlaWdodD1cIjE1XCI+XG4gICAgICAoPGtiZD5iPC9rYmQ+KTogZHJhZyBhIGJveCB0byBzZWxlY3QgYW4gYXJlYSB0byB6b29tIGludG8uPC9saT5cbiAgICA8bGk+XCI8Yj5QYW48L2I+XCIgPGkgY2xhc3M9XCJwaSBwaS1hcnJvd3MtYWx0XCI+PC9pPiAoPGtiZD5wPC9rYmQ+KTogZHJhZyB0byBwYW4gdGhlIGltYWdlLjwvbGk+XG4gICAgPGxpPlwiPGI+Wm9vbSBpbjwvYj5cIiA8aSBjbGFzcz1cInBpIHBpLXNlYXJjaC1wbHVzXCI+PC9pPiAoPGtiZD4rPC9rYmQ+KTogem9vbSBpbiBvbmUgc3RlcC48L2xpPlxuICAgIDxsaT5cIjxiPlpvb20gb3V0PC9iPlwiIDxpIGNsYXNzPVwicGkgcGktc2VhcmNoLW1pbnVzXCI+PC9pPiAoPGtiZD4tPC9rYmQ+KTogem9vbSBvdXQgb25lIHN0ZXAsXG4gICAgICBzdG9wcGluZyBhdCB0aGUgb3JpZ2luYWwgaW1hZ2UgZGltZW5zaW9ucy48L2xpPlxuICA8L3VsPlxuXG4gIDxoMz5SZWdpb24gdG9vbHM8L2gzPlxuICA8cD5CeSBkZWZhdWx0IG5vIHRvb2wgaXMgc2VsZWN0ZWQgKGFsbCBncmV5ZWQtb3V0KTsgc2VsZWN0aW5nIGEgdG9vbCBoaWdobGlnaHRzIGl0LiBUaGVcbiAgICB2ZXJ0ZXgtZWRpdGluZyB0b29scyAocG9seWdvbiwgYWRkL2RlbGV0ZSB2ZXJ0ZXgsIG1vdmUsIELDqXppZXIgY29udmVyc2lvbnMpIGFyZSBhdmFpbGFibGVcbiAgICBpbiB0aGUgPGI+SW1hZ2U8L2I+IHZpZXcgb25seS4gT24gYSA8Yj5kb251dDwvYj4gKGEgcmVnaW9uIHdpdGggYSBob2xlKSwgdGhlIGlubmVyLXJpbmdcbiAgICB2ZXJ0aWNlcyBhcmUgZWRpdGFibGUgdG9vIOKAlCBkcmFnLCBhZGQsIGFuZCBkZWxldGUgdGhlbSBqdXN0IGxpa2UgdGhlIG91dGVyIHJpbmcuPC9wPlxuICA8dWw+XG4gICAgPGxpPlwiPGI+U2VsZWN0aW9uPC9iPlwiIDxpIGNsYXNzPVwicGkgcGktYXJyb3ctdXAtcmlnaHRcIj48L2k+ICg8a2JkPnM8L2tiZD4pOiBhIG5ldXRyYWwgbW9kZVxuICAgICAgdGhhdCB0dXJucyBvZmYgd2hhdGV2ZXIgZHJhd2luZyB0b29sIHdhcyBhY3RpdmUgKHdhbmQsIHZlcnRleCBlcmFzZXIsIHJlY3RhbmdsZSwgZXRjLikuXG4gICAgICBVc2UgaXQgdG8gc3RlcCBvdXQgb2YgZWRpdGluZyB3aXRob3V0IHBpY2tpbmcgYW5vdGhlciB0b29sLjwvbGk+XG4gICAgPGxpPlwiPGI+UmVjdGFuZ3VsYXIgcmVnaW9uPC9iPlwiIDxpIGNsYXNzPVwicGkgcGktc3RvcFwiPjwvaT4gKDxrYmQ+cjwva2JkPik6IGNsaWNrIGFuZCBkcmFnIHRvXG4gICAgICBjcmVhdGUgYSByZWN0YW5ndWxhciByZWdpb24uPC9saT5cbiAgICA8bGk+XCI8Yj5Qb2x5bGluZTwvYj5cIiA8aW1nIHNyYz1cImFzc2V0cy9wbG90dGluZy9wb2x5bGluZS5zdmdcIiB3aWR0aD1cIjE0XCIgaGVpZ2h0PVwiMTRcIj5cbiAgICAgICg8a2JkPmw8L2tiZD4pOiBkcmF3IGFuIG9wZW4gKG5vbi1jbG9zZWQpIHBvbHlsaW5lIGJ5IGNsaWNraW5nIGFuZCBkcmFnZ2luZy4gUG9seWxpbmVzIGNhblxuICAgICAgYmUgZWRpdGVkIGFuZCBleHBvcnRlZCB0byBHZW9KU09OIGFzIGEgPGNvZGU+TGluZVN0cmluZzwvY29kZT4gZmVhdHVyZS48L2xpPlxuICAgIDxsaT5cIjxiPkZyZWVmb3JtIHJlZ2lvbjwvYj5cIiA8aSBjbGFzcz1cInBpIHBpLXBlbmNpbFwiPjwvaT4gKDxrYmQ+Zjwva2JkPik6IGNsaWNrIGFuZCBkcmFnIHRvXG4gICAgICBkcmF3IGEgY2xvc2VkIGZyZWVmb3JtIHBvbHlnb24uPC9saT5cbiAgICA8bGk+XCI8Yj5CcnVzaDwvYj5cIiA8aW1nIHNyYz1cImFzc2V0cy9wbG90dGluZy9wYWludGJydXNoLnN2Z1wiIHdpZHRoPVwiMTVcIiBoZWlnaHQ9XCIxNVwiPjogY2xpY2tcbiAgICAgIGFuZCBkcmFnIHRvIHBhaW50IGEgcmVnaW9uIHdpdGggYSBjaXJjdWxhciBicnVzaCDigJQgdGhlIHBhaW50ZWQgc3Ryb2tlIGlzIHRoZSByZWdpb24uXG4gICAgICBBIDxiPlNpemU8L2I+IHNsaWRlciAoYnJ1c2ggZGlhbWV0ZXIsIGluIGltYWdlIHBpeGVscykgYXBwZWFycyBpbiB0aGUgdG9vbGJhciB3aGlsZSB0aGVcbiAgICAgIGJydXNoIGlzIGFjdGl2ZS4gSG9sZCA8a2JkPlNoaWZ0PC9rYmQ+IHdoaWxlIHBhaW50aW5nIHRvIDxiPmVyYXNlPC9iPiAoc3VidHJhY3QpIGZyb20gYVxuICAgICAgcmVnaW9uIGluc3RlYWQuIFBhaW50aW5nIG92ZXIgYW4gZXhpc3RpbmcgcmVnaW9uIGV4dGVuZHMgaXQ7IGJydXNoaW5nIGFjcm9zcyB0d28gcmVnaW9uc1xuICAgICAgbWVyZ2VzIHRoZW0uIFBhaW50aW5nIGEgY2xvc2VkIHJpbmcgKG9yIHNoaWZ0LWVyYXNpbmcgYW4gaW50ZXJpb3IgcG9ja2V0KSBsZWF2ZXMgdGhlXG4gICAgICBlbmNsb3NlZCBhcmVhIGVtcHR5IOKAlCBhIDxiPmRvbnV0PC9iPiAoSW1hZ2UgdmlldykuPC9saT5cbiAgICA8bGk+XCI8Yj5Qb2x5Z29uPC9iPlwiIDxpbWcgc3JjPVwiYXNzZXRzL3Bsb3R0aW5nL3BvbHlnb24tdmVydGljZXMuc3ZnXCIgd2lkdGg9XCIxNVwiIGhlaWdodD1cIjE1XCI+OlxuICAgICAgY2xpY2sgdG8gcGxhY2UgZWFjaCB2ZXJ0ZXg7IGNsaWNrIHRoZSBmaXJzdCBwb2ludCB0byBjbG9zZSB0aGUgcG9seWdvbi48L2xpPlxuICAgIDxsaT5cIjxiPkFkZCB2ZXJ0ZXg8L2I+XCIgPGltZyBzcmM9XCJhc3NldHMvcGxvdHRpbmcvdmVydGV4LWFkZC5zdmdcIiB3aWR0aD1cIjE1XCIgaGVpZ2h0PVwiMTVcIj46XG4gICAgICBjbGljayBhbiBlZGdlIG9mIHRoZSBzZWxlY3RlZCBwb2x5Z29uIHRvIGluc2VydCBhIG5ldyB2ZXJ0ZXguPC9saT5cbiAgICA8bGk+XCI8Yj5EZWxldGUgdmVydGV4PC9iPlwiIDxpbWcgc3JjPVwiYXNzZXRzL3Bsb3R0aW5nL3ZlcnRleC1kZWxldGUuc3ZnXCIgd2lkdGg9XCIxNVwiIGhlaWdodD1cIjE1XCI+OlxuICAgICAgY2xpY2sgYSB2ZXJ0ZXggb2YgdGhlIHNlbGVjdGVkIHBvbHlnb24gdG8gcmVtb3ZlIGl0LjwvbGk+XG4gICAgPGxpPlwiPGI+TW92ZSByZWdpb248L2I+XCIgPGltZyBzcmM9XCJhc3NldHMvcGxvdHRpbmcvcmVnaW9uLW1vdmUuc3ZnXCIgd2lkdGg9XCIxNVwiIGhlaWdodD1cIjE1XCI+OlxuICAgICAgZHJhZyB0byBtb3ZlIHRoZSBzZWxlY3RlZCByZWdpb24uPC9saT5cbiAgICA8bGk+XCI8Yj5UbyBCw6l6aWVyPC9iPlwiIDxpbWcgc3JjPVwiYXNzZXRzL3Bsb3R0aW5nL3RvLWJlemllci5zdmdcIiB3aWR0aD1cIjE1XCIgaGVpZ2h0PVwiMTVcIj4gL1xuICAgICAgXCI8Yj5UbyBwb2x5Z29uPC9iPlwiIDxpbWcgc3JjPVwiYXNzZXRzL3Bsb3R0aW5nL3RvLXBvbHlnb24uc3ZnXCIgd2lkdGg9XCIxNVwiIGhlaWdodD1cIjE1XCI+OlxuICAgICAgY29udmVydCB0aGUgc2VsZWN0ZWQgcmVnaW9uIHRvIGEgc21vb3RoIELDqXppZXIgY3VydmUsIG9yIGJhY2sgdG8gYSBzdHJhaWdodC1lZGdlZCBwb2x5Z29uLjwvbGk+XG4gICAgPGxpPlwiPGI+V2FuZDwvYj5cIiA8aW1nIHNyYz1cImFzc2V0cy9wbG90dGluZy93YW5kLnN2Z1wiIHdpZHRoPVwiMTVcIiBoZWlnaHQ9XCIxNVwiPiAoPGtiZD53PC9rYmQ+KTpcbiAgICAgIGNsaWNrIHRvIGdyb3cgYSByZWdpb24gZnJvbSBwaXhlbHMgd2l0aCBzaW1pbGFyIHZhbHVlcyBhcm91bmQgdGhlIGNsaWNrIHBvaW50LiBIb2xkXG4gICAgICA8a2JkPkN0cmw8L2tiZD4vPGtiZD5DbWQ8L2tiZD4gZm9yIGFuIGV4YWN0LW1hdGNoIGZsb29kIGZpbGw7IGRyYWcgdG8ga2VlcCBleHRlbmRpbmcgdGhlXG4gICAgICBzZWxlY3Rpb24gd2hpbGUgbW92aW5nOyBob2xkIDxrYmQ+U2hpZnQ8L2tiZD4gd2hpbGUgY2xpY2tpbmcgb3IgZHJhZ2dpbmcgb24gYW4gZXhpc3RpbmdcbiAgICAgIHJlZ2lvbiB0byA8Yj5lcmFzZTwvYj4gcGl4ZWxzIGZyb20gaXQuIFVzZSB0aGUgPGI+U2Vucy48L2I+IHNsaWRlciB0aGF0IGFwcGVhcnMgdG8gbWFrZSB0aGVcbiAgICAgIHdhbmQgc3RyaWN0ZXIgKGhpZ2hlciA9IHNtYWxsZXIgc2VsZWN0aW9uKSBvciBsb29zZXIuIFN1YnNlcXVlbnQgY2xpY2tzIGtlZXAgZXh0ZW5kaW5nIHRoZVxuICAgICAgc2FtZSByZWdpb247IGNsaWNraW5nIGluc2lkZSBhbiBleGlzdGluZyByZWdpb24gYWRvcHRzIGl0LCBhbmQgZ3Jvd2luZyBpbnRvIGFub3RoZXIgcmVnaW9uXG4gICAgICBtZXJnZXMgdGhlbS48L2xpPlxuICAgIDxsaT5cIjxiPlZlcnRleCBlcmFzZXI8L2I+XCIgPGkgY2xhc3M9XCJwaSBwaS1lcmFzZXJcIj48L2k+ICg8a2JkPmU8L2tiZD4pOiBjbGljayBvciBjbGljay1hbmQtZHJhZ1xuICAgICAgdG8gcmVtb3ZlIHBvbHlnb24gdmVydGljZXMgdGhhdCBmYWxsIGluc2lkZSB0aGUgY3Vyc29yIGNpcmNsZSBmcm9tIGFueSByZWdpb24sIGluY2x1ZGluZyBhXG4gICAgICByZWdpb24ncyBpbm5lciByaW5ncyAoZG9udXQgaG9sZXMpLiBVc2UgdGhlIDxiPlJhZGl1czwvYj4gc2xpZGVyIHRvIHNldCB0aGUgZXJhc2VyIHNpemUgaW5cbiAgICAgIGltYWdlIHBpeGVscy4gUG9seWdvbnMgcmVkdWNlZCBiZWxvdyAzIHZlcnRpY2VzIChvciBwb2x5bGluZXMgYmVsb3cgMikgYXJlIGRlbGV0ZWQgZW50aXJlbHk7XG4gICAgICBhIGhvbGUgcmVkdWNlZCBiZWxvdyAzIHZlcnRpY2VzIGlzIHJlbW92ZWQuPC9saT5cbiAgICA8bGk+XCI8Yj5VbmRvPC9iPlwiIDxpbWcgc3JjPVwiYXNzZXRzL3Bsb3R0aW5nL3VuZG8uc3ZnXCIgd2lkdGg9XCIxNVwiIGhlaWdodD1cIjE1XCI+XG4gICAgICAoPGtiZD5DdHJsPC9rYmQ+LzxrYmQ+Q21kPC9rYmQ+KzxrYmQ+Wjwva2JkPik6IHJldmVydHMgdGhlXG4gICAgICBsYXN0IHJlZ2lvbiBhY3Rpb24gKGRyYXcsIGRlbGV0ZSwgZWRpdCwgd2FuZC9icnVzaCBzdHJva2UsIHNlZ21lbnRhdGlvbiwg4oCmKS4gVXAgdG8gdGhlIGxhc3RcbiAgICAgIDxiPjEwPC9iPiBhY3Rpb25zIGNhbiBiZSB1bmRvbmU7IHRoZSBidXR0b24gZ3JleXMgb3V0IHdoZW4gdGhlcmUncyBub3RoaW5nIGxlZnQgdG8gdW5kby48L2xpPlxuICAgIDxsaT5cIjxiPlJlZG88L2I+XCIgPGltZyBzcmM9XCJhc3NldHMvcGxvdHRpbmcvcmVkby5zdmdcIiB3aWR0aD1cIjE1XCIgaGVpZ2h0PVwiMTVcIj5cbiAgICAgICg8a2JkPkN0cmw8L2tiZD4vPGtiZD5DbWQ8L2tiZD4rPGtiZD5TaGlmdDwva2JkPis8a2JkPlo8L2tiZD4gb3IgPGtiZD5DdHJsPC9rYmQ+LzxrYmQ+Q21kPC9rYmQ+KzxrYmQ+WTwva2JkPik6XG4gICAgICByZS1hcHBsaWVzIHRoZSBtb3N0IHJlY2VudGx5IHVuZG9uZSBhY3Rpb24uIFBlcmZvcm1pbmcgYSBuZXcgcmVnaW9uIGFjdGlvbiBjbGVhcnMgd2hhdCBjYW4gYmUgcmVkb25lLjwvbGk+XG4gICAgPGxpPlwiPGI+RGVsZXRlIHJlZ2lvbjwvYj5cIiA8aSBjbGFzcz1cInBpIHBpLXRyYXNoXCI+PC9pPiAoPGtiZD5EZWw8L2tiZD4pOiBkZWxldGVzIHRoZSBzZWxlY3RlZFxuICAgICAgcmVnaW9uLiBUaGlzIGNhbiBhbHNvIGJlIGRvbmUgZnJvbSB0aGUgUmVnaW9uIEVkaXRvci48L2xpPlxuICA8L3VsPlxuXG4gIDxoMz5SZWdpb24gYWN0aW9ucyAocmlnaHQtY2xpY2sgdGhlIGltYWdlKTwvaDM+XG4gIDxwPlJpZ2h0LWNsaWNrIGEgcmVnaW9uIOKAlCBvciB0aGUgY3VycmVudCBtdWx0aS1zZWxlY3Rpb24g4oCUIHRvIG9wZW4gdGhlXG4gICAgXCI8Yj5TZWxlY3RlZCByZWdpb24ocyk8L2I+XCIgZ3JvdXAgYXQgdGhlIHRvcCBvZiB0aGUgY29udGV4dCBtZW51LiBUaGVzZSBhY3QgaW1tZWRpYXRlbHkgb25cbiAgICB0aGUgc2VsZWN0aW9uICh0aGV5J3JlIG5vdCBkcmF3aW5nIG1vZGVzKSBhbmQgYXJlIGZ1bGx5IHVuZG8vcmVkby1hYmxlOjwvcD5cbiAgPHVsPlxuICAgIDxsaT5cIjxiPlNlbGVjdCBhbGwgcmVnaW9uczwvYj5cIiA8aSBjbGFzcz1cInBpIHBpLWNoZWNrLXNxdWFyZVwiPjwvaT46IHNlbGVjdCBldmVyeSByZWdpb24gb25cbiAgICAgIHRoZSBpbWFnZSAoYWxzbyBpbiB0aGUgUmVnaW9uIEVkaXRvciB0b29sYmFyKS48L2xpPlxuICAgIDxsaT5cIjxiPk1lcmdlIC8gZ3JvdXA8L2I+XCI6IGNvbWJpbmUgMisgc2VsZWN0ZWQgcmVnaW9ucyBpbnRvIG9uZS4gSWYgdGhlIHBpZWNlcyBkb24ndCB0b3VjaCxcbiAgICAgIHRoZSByZXN1bHQgaXMgYSBzaW5nbGUgbXVsdGktcGFydCByZWdpb24gKHNwYXJzZSBcImJpdHNcIikuPC9saT5cbiAgICA8bGk+XCI8Yj5Vbmdyb3VwPC9iPlwiOiBzcGxpdCBhIG11bHRpLXBhcnQgcmVnaW9uIGJhY2sgaW50byBvbmUgcmVnaW9uIHBlciBwaWVjZS48L2xpPlxuICAgIDxsaT5cIjxiPkludmVyc2U8L2I+XCI6IHJlcGxhY2UgdGhlIHNlbGVjdGlvbiB3aXRoIGV2ZXJ5dGhpbmcgPGk+b3V0c2lkZTwvaT4gaXQg4oCUIHRoZSBpbWFnZVxuICAgICAgcmVjdGFuZ2xlIHdpdGggdGhlIHNlbGVjdGVkIHJlZ2lvbnMgcHVuY2hlZCBvdXQgYXMgaG9sZXMuIFdvcmtzIGF0IGFueSBpbWFnZSBzaXplLjwvbGk+XG4gICAgPGxpPlwiPGI+U2ltcGxpZnk8L2I+XCI6IHJlZHVjZSBhIHJlZ2lvbidzIHZlcnRleCBjb3VudCAoRG91Z2xhc+KAk1BldWNrZXIpIGJ5IGFuIGFsdGl0dWRlXG4gICAgICB0aHJlc2hvbGQgaW4gcGl4ZWxzIOKAlCBwaWNrIDxiPkxpZ2h0PC9iPiAoMSZuYnNwO3B4KSwgPGI+TWVkaXVtPC9iPiAoMyZuYnNwO3B4KSxcbiAgICAgIDxiPlN0cm9uZzwvYj4gKDgmbmJzcDtweCksIG9yIDxiPkN1c3RvbeKApjwvYj4gZm9yIGEgc2xpZGVyLiBBcHBsaWVzIHRvIHRoZSBleHRlcmlvciBhbmQgYW55XG4gICAgICBob2xlcyAoYW5kIGV2ZXJ5IHBhcnQgb2YgYSBtdWx0aS1wYXJ0IHJlZ2lvbikuPC9saT5cbiAgICA8bGk+VGhlIELDqXppZXIgY29udmVyc2lvbnMgYW5kIERlbGV0ZSBhbHNvIGxpdmUgaW4gdGhpcyBncm91cCAodGhlIHJpZ2h0LWNsaWNrIGVxdWl2YWxlbnRzIG9mXG4gICAgICB0aGUgdG9vbGJhciAvIHZlcnRleCB0b29scyBhYm92ZSkuPC9saT5cbiAgPC91bD5cblxuICA8aDM+U2VnbWVudGF0aW9uIHRvb2xzIChTQU0gJmFtcDsgY2VsbHBvc2UpPC9oMz5cbiAgPHA+QUktYXNzaXN0ZWQgc2VnbWVudGF0aW9uIHRoYXQgcnVucyBlbnRpcmVseSBpbiB0aGUgYnJvd3Nlci4gVGhlIGJveCBhbmQgcG9pbnRcbiAgICB0b29scyB1c2UgYSBwcm9tcHRhYmxlIDxiPm1pY3JvLXNhbTwvYj4gbW9kZWw7IHRoZSBjZWxscG9zZSB0b29sIGF1dG8tZGV0ZWN0cyBjZWxscy5cbiAgICBHZW5lcmF0ZWQgcmVnaW9ucyBpbmhlcml0IHRoZSBjb2xvciBvZiB0aGUgcmVjdGFuZ2xlIHRoZXkgY2FtZSBmcm9tLiBUaGUgZmlyc3QgcnVuXG4gICAgZG93bmxvYWRzIHRoZSBtb2RlbCAoYSBwcm9ncmVzcyB0b2FzdCBpcyBzaG93bik7IGl0J3MgdGhlbiBjYWNoZWQuPC9wPlxuICA8dWw+XG4gICAgPGxpPlwiPGI+U2VnbWVudDwvYj5cIiA8aW1nIHNyYz1cImFzc2V0cy9wbG90dGluZy9waWN0dXJlLXNlZ21lbnRhdGlvbi5zdmdcIiB3aWR0aD1cIjE1XCIgaGVpZ2h0PVwiMTVcIj5cbiAgICAgIChib3ggcHJvbXB0KTogZHJhdyBvbmUgb3IgbW9yZSA8Yj5SZWN0YW5ndWxhciByZWdpb25zPC9iPiBhcm91bmQgdGhlIG9iamVjdHMgb2YgaW50ZXJlc3QsXG4gICAgICB0aGVuIGNsaWNrIFNlZ21lbnQuIEVhY2ggcmVjdGFuZ2xlIGlzIHJlcGxhY2VkIGJ5IHRoZSBTQU0gbWFzayBvZiB0aGUgb2JqZWN0IGluc2lkZSBpdC48L2xpPlxuICAgIDxsaT5cIjxiPlBvaW50IHByb21wdHM8L2I+XCIgPGltZyBzcmM9XCJhc3NldHMvcGxvdHRpbmcvY2xpY2suc3ZnXCIgd2lkdGg9XCIxNVwiIGhlaWdodD1cIjE1XCI+OiBjbGljayBkaXJlY3RseSBvbiBhbiBvYmplY3QgdG9cbiAgICAgIHNlZ21lbnQgaXQgYXMgYSA8Yj5uZXc8L2I+IHJlZ2lvbi4gRWFjaCBjbGljayBzdGFydHMgYSBmcmVzaCBvYmplY3Qg4oCUIGNsaWNraW5nIGFub3RoZXJcbiAgICAgIG9iamVjdCB3b24ndCBncm93IHRoZSBwcmV2aW91cyBvbmUuIDxrYmQ+U2hpZnQ8L2tiZD4vPGtiZD5BbHQ8L2tiZD4tY2xpY2sgYWRkcyBhblxuICAgICAgPGk+ZXhjbHVkZTwvaT4gcG9pbnQgdGhhdCByZWZpbmVzIHRoZSBjdXJyZW50IG9iamVjdDsgPGtiZD5Fc2M8L2tiZD4gdW5kb2VzIGl0LFxuICAgICAgPGtiZD5FbnRlcjwva2JkPiBjb21taXRzIGl0LjwvbGk+XG4gICAgPGxpPlwiPGI+TW9kZWwgcGlja2VyPC9iPlwiIDxpIGNsYXNzPVwicGkgcGktY2hldnJvbi1kb3duXCI+PC9pPjogdGhlIGRyb3Bkb3duIGF0dGFjaGVkIHRvIHRoZVxuICAgICAgU2VnbWVudCBidXR0b24gY2hvb3NlcyB0aGUgU0FNIG1vZGVsIOKAlCA8Yj5taWNyby1zYW0gVmlULVQ8L2I+IChkZWZhdWx0OyBzbWFsbCwgZmFzdCkgb3JcbiAgICAgIDxiPm1pY3JvLXNhbSBWaVQtQjwvYj4gKGxhcmdlciwgaGlnaGVzdCBhY2N1cmFjeSkgZm9yIGxpZ2h0IG1pY3Jvc2NvcHksIG9yXG4gICAgICA8Yj5wYXRoby1zYW0gVmlULUI8L2I+IGZvciBoaXN0b3BhdGhvbG9neSAoSCZhbXA7RSAvIHN0YWluZWQgdGlzc3VlKS4gVGhlIGNob2ljZSBhcHBsaWVzIHRvXG4gICAgICA8aT5ib3RoPC9pPiB0aGUgYm94IGFuZCBwb2ludCB0b29scy4gVGhlIDxpIGNsYXNzPVwicGkgcGktaW5mby1jaXJjbGVcIj48L2k+IGluZm8gYnV0dG9uIG5leHRcbiAgICAgIHRvIGl0IGV4cGxhaW5zIHRoZSB0cmFkZS1vZmYuPC9saT5cbiAgICA8bGk+XCI8Yj5DZWxscG9zZTwvYj5cIiA8aW1nIHNyYz1cImFzc2V0cy9wbG90dGluZy9jZWxscy5zdmdcIiB3aWR0aD1cIjE1XCIgaGVpZ2h0PVwiMTVcIj46IGRyYXcgcmVjdGFuZ2xlcywgdGhlbiBjbGljayBDZWxscG9zZSB0b1xuICAgICAgYXV0b21hdGljYWxseSBzZWdtZW50IGV2ZXJ5IGNlbGwgaW5zaWRlIGVhY2ggcmVjdGFuZ2xlIChjbGllbnQtc2lkZSBjZWxscG9zZS1TQU0pIOKAlCBvbmVcbiAgICAgIHJlZ2lvbiBwZXIgZGV0ZWN0ZWQgY2VsbC4gTm8gY2xpY2tpbmcgb24gaW5kaXZpZHVhbCBjZWxscyBuZWVkZWQuPC9saT5cbiAgPC91bD5cblxuICA8aDM+SGVscDwvaDM+XG4gIDxwPlwiPGI+SGVscDwvYj5cIiA8aSBjbGFzcz1cInBpIHBpLXF1ZXN0aW9uLWNpcmNsZVwiPjwvaT46IG9wZW5zIHRoaXMgZGlhbG9nLjwvcD5cblxuICA8aDM+S2V5Ym9hcmQgc2hvcnRjdXRzPC9oMz5cbiAgPHRhYmxlIGNsYXNzPVwic2hvcnRjdXRzLXRhYmxlXCI+XG4gICAgPHRyPjx0ZD48a2JkPis8L2tiZD4gLyA8a2JkPj08L2tiZD48L3RkPjx0ZD5ab29tIGluPC90ZD48L3RyPlxuICAgIDx0cj48dGQ+PGtiZD4tPC9rYmQ+PC90ZD48dGQ+Wm9vbSBvdXQ8L3RkPjwvdHI+XG4gICAgPHRyPjx0ZD48a2JkPmI8L2tiZD48L3RkPjx0ZD5Ub2dnbGUgem9vbSBib3ggbW9kZTwvdGQ+PC90cj5cbiAgICA8dHI+PHRkPjxrYmQ+cDwva2JkPjwvdGQ+PHRkPlRvZ2dsZSBwYW4gbW9kZTwvdGQ+PC90cj5cbiAgICA8dHI+PHRkPjxrYmQ+Zjwva2JkPjwvdGQ+PHRkPlRvZ2dsZSBmcmVlZm9ybSByZWdpb24gZHJhd2luZzwvdGQ+PC90cj5cbiAgICA8dHI+PHRkPjxrYmQ+bDwva2JkPjwvdGQ+PHRkPlRvZ2dsZSBwb2x5bGluZSBkcmF3aW5nPC90ZD48L3RyPlxuICAgIDx0cj48dGQ+PGtiZD5yPC9rYmQ+PC90ZD48dGQ+VG9nZ2xlIHJlY3Rhbmd1bGFyIHJlZ2lvbiBkcmF3aW5nPC90ZD48L3RyPlxuICAgIDx0cj48dGQ+PGtiZD53PC9rYmQ+PC90ZD48dGQ+VG9nZ2xlIHdhbmQgdG9vbDwvdGQ+PC90cj5cbiAgICA8dHI+PHRkPldhbmQgKyA8a2JkPlNoaWZ0PC9rYmQ+PC90ZD48dGQ+RXJhc2UgcGl4ZWxzIGZyb20gdGhlIGFjdGl2ZSByZWdpb248L3RkPjwvdHI+XG4gICAgPHRyPjx0ZD5XYW5kICsgPGtiZD5DdHJsPC9rYmQ+LzxrYmQ+Q21kPC9rYmQ+PC90ZD48dGQ+RXhhY3QtbWF0Y2ggZmxvb2QgZmlsbCAobm8gc21vb3RoaW5nKTwvdGQ+PC90cj5cbiAgICA8dHI+PHRkPjxrYmQ+ZTwva2JkPjwvdGQ+PHRkPlRvZ2dsZSB2ZXJ0ZXggZXJhc2VyPC90ZD48L3RyPlxuICAgIDx0cj48dGQ+PGtiZD5zPC9rYmQ+PC90ZD48dGQ+U2VsZWN0aW9uIG1vZGUgKGRlYWN0aXZhdGVzIGRyYXdpbmcgdG9vbHMpPC90ZD48L3RyPlxuICAgIDx0cj48dGQ+UG9pbnQgcHJvbXB0ICsgPGtiZD5TaGlmdDwva2JkPi88a2JkPkFsdDwva2JkPjwvdGQ+PHRkPkFkZCBhbiBleGNsdWRlIHBvaW50IChyZWZpbmUgdGhlIGN1cnJlbnQgb2JqZWN0KTwvdGQ+PC90cj5cbiAgICA8dHI+PHRkPjxrYmQ+RW50ZXI8L2tiZD48L3RkPjx0ZD5Db21taXQgdGhlIGN1cnJlbnQgU0FNIHBvaW50IG9iamVjdDwvdGQ+PC90cj5cbiAgICA8dHI+PHRkPjxrYmQ+RXNjPC9rYmQ+PC90ZD48dGQ+VW5kbyB0aGUgY3VycmVudCBTQU0gcG9pbnQgb2JqZWN0PC90ZD48L3RyPlxuICAgIDx0cj48dGQ+PGtiZD5EZWw8L2tiZD4gLyA8a2JkPuKMqzwva2JkPjwvdGQ+PHRkPkRlbGV0ZSBzZWxlY3RlZCByZWdpb248L3RkPjwvdHI+XG4gICAgPHRyPjx0ZD48a2JkPkN0cmw8L2tiZD4vPGtiZD5DbWQ8L2tiZD4rPGtiZD5aPC9rYmQ+PC90ZD48dGQ+VW5kbyB0aGUgbGFzdCByZWdpb24gYWN0aW9uICh1cCB0byAxMCk8L3RkPjwvdHI+XG4gICAgPHRyPjx0ZD48a2JkPkN0cmw8L2tiZD4vPGtiZD5DbWQ8L2tiZD4rPGtiZD5TaGlmdDwva2JkPis8a2JkPlo8L2tiZD4gLyA8a2JkPkN0cmw8L2tiZD4rPGtiZD5ZPC9rYmQ+PC90ZD48dGQ+UmVkbyB0aGUgbGFzdCB1bmRvbmUgcmVnaW9uIGFjdGlvbjwvdGQ+PC90cj5cbiAgPC90YWJsZT5cblxuICA8bmctdGVtcGxhdGUgcFRlbXBsYXRlPVwiZm9vdGVyXCI+XG4gICAgPHAtYnV0dG9uIGljb249XCJwaSBwaS1jaGVja1wiIChjbGljayk9XCJkaXNwbGF5SGVscERpYWxvZz1mYWxzZVwiIGxhYmVsPVwiT2tcIiBzdHlsZUNsYXNzPVwicC1idXR0b24tdGV4dFwiPlxuICAgIDwvcC1idXR0b24+XG4gIDwvbmctdGVtcGxhdGU+XG48L3AtZGlhbG9nPlxuIl19