@santinobch/os-window-angular
Version:
Create windows inside a browser window!
734 lines (729 loc) • 727 kB
JavaScript
import * as i0 from '@angular/core';
import { Injectable, Directive, Component, ViewEncapsulation, HostBinding, Input, NgModule } from '@angular/core';
import { coerceNumberProperty, coerceBooleanProperty } from '@angular/cdk/coercion';
import * as i2 from '@angular/cdk/drag-drop';
import { DragDropModule } from '@angular/cdk/drag-drop';
import * as i3 from 'ngx-scrollbar';
import { NgScrollbarModule } from 'ngx-scrollbar';
import { CommonModule } from '@angular/common';
const THEME_LIST = [
{
name: 'arc',
variants: ['light', 'dark'],
palette: [
'red',
'orange',
'yellow',
'lime',
'green',
'aqua',
'blue',
'purple',
'magenta',
],
},
{
name: 'win98',
variants: ['classic', 'vaporwave'],
palette: [
'red',
'orange',
'yellow',
'lime',
'green',
'aqua',
'blue',
'purple',
'magenta',
],
},
];
class OsConfigService {
constructor() {
this.globalTheme = {
name: 'arc',
variant: 'light',
};
this.zIndex = 1;
this.userThemeList = THEME_LIST;
}
// This stores windows information (id and styles)
getInstances() {
return this.instances;
}
setInstances(shared) {
this.instances.push(shared);
}
getGlobalTheme() {
const GLOBAL_THEME = localStorage.getItem('GLOBAL_THEME');
if (GLOBAL_THEME === null) {
return this.globalTheme;
}
return JSON.parse(GLOBAL_THEME);
}
setGlobalTheme(style) {
this.globalTheme = style;
localStorage.setItem('GLOBAL_THEME', JSON.stringify(this.globalTheme));
}
/**
* Returns last zIndex value
*
* When a window is focused, it's zIndex value changes,
* making it appear in front of the other windows
*/
getZIndex() {
return this.zIndex;
}
/**
* Sets last zIndex value
*
* When a window is focused, it's zIndex value changes,
* making it appear in front of the other windows
*/
setZIndex(zIndex) {
this.zIndex = zIndex;
}
addTheme(theme) {
this.userThemeList.push(theme);
localStorage.setItem('THEME_LIST', JSON.stringify(this.userThemeList));
}
getThemes() {
const THEME_LIST = localStorage.getItem('THEME_LIST');
if (THEME_LIST === null) {
return THEME_LIST;
}
return JSON.parse(THEME_LIST);
}
}
OsConfigService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.1.4", ngImport: i0, type: OsConfigService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
OsConfigService.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "15.1.4", ngImport: i0, type: OsConfigService, providedIn: 'root' });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.1.4", ngImport: i0, type: OsConfigService, decorators: [{
type: Injectable,
args: [{
providedIn: 'root',
}]
}], ctorParameters: function () { return []; } });
class StyleClass {
constructor(_componentElement, _renderer, _globalConfigService, _componentName) {
this._componentElement = _componentElement;
this._renderer = _renderer;
this._globalConfigService = _globalConfigService;
this._componentName = _componentName;
this.globalConfigData = {
name: '',
variant: '',
};
this.style = {
name: '',
variant: '',
color: '',
};
this.previousStyle = {
name: '',
variant: '',
color: '',
};
this.componentElement = _componentElement;
this.renderer = _renderer;
this.globalConfigService = _globalConfigService;
this.componentName = _componentName;
}
isValidStyle() {
if (this.style.name !== '' &&
this.style.name !== undefined &&
this.style.variant !== '' &&
this.style.variant !== undefined) {
for (const i of this.globalConfigService.getThemes()) {
if (i.name == this.style.name) {
for (const v of i.variants) {
if (v == this.style.variant) {
return true;
}
}
console.error('Invalid variant at ' +
this.componentName +
' component: ' +
this.style.variant);
return false;
}
}
console.error('Invalid Theme at ' +
this.componentName +
' component: ' +
this.style.name);
return false;
}
return false;
}
isValidColor() {
if (this.style.name !== '' &&
this.style.name !== undefined &&
this.style.color !== '' &&
this.style.color !== undefined) {
for (const i of this.globalConfigService.getThemes()) {
if (i.name == this.style.name) {
for (const p of i.palette) {
if (p == this.style.color) {
return true;
}
}
console.error('Invalid color at ' +
this.componentName +
' component: ' +
this.style.color);
return false;
}
}
console.error('Invalid Theme at ' +
this.componentName +
' component: ' +
this.style.name);
return false;
}
return false;
}
getStyle() {
return (this.style.name + '-' + this.style.variant + '-os-' + this.componentName);
}
getPreviousStyle() {
return (this.previousStyle.name +
'-' +
this.previousStyle.variant +
'-os-' +
this.componentName);
}
getColor() {
return this.style.color + '-os-' + this.componentName;
}
getPreviousColor() {
return this.previousStyle.color + '-os-' + this.componentName;
}
loadGlobalStyles() {
//Global theme config
this.globalConfigData = this.globalConfigService.getGlobalTheme();
this.style.name = this.globalConfigData.name;
this.style.variant = this.globalConfigData.variant;
this.renderer.addClass(this.componentElement.nativeElement, this.getStyle());
}
loadStyles() {
if (this.isValidStyle()) {
//Removes old theme class
if (this.previousStyle.name !== '' &&
this.previousStyle.name !== undefined &&
this.previousStyle.variant !== '' &&
this.previousStyle.variant !== undefined) {
this.renderer.removeClass(this.componentElement.nativeElement, this.getPreviousStyle());
}
this.previousStyle.name = this.style.name;
this.previousStyle.variant = this.style.variant;
//Adds theme class
this.renderer.addClass(this.componentElement.nativeElement, this.getStyle());
}
else {
this.loadGlobalStyles();
}
}
loadColor() {
if (this.isValidColor()) {
if (this.previousStyle.color !== '' &&
this.previousStyle.color !== undefined) {
this.renderer.removeClass(this.componentElement.nativeElement, this.getPreviousColor());
}
this.previousStyle.color = this.style.color;
this.renderer.addClass(this.componentElement.nativeElement, this.getColor());
}
}
onChanges(changes) {
if (changes != undefined) {
if (changes['theme'] != undefined) {
this.style.name = changes['theme'].currentValue;
}
if (changes.variant != undefined) {
this.style.variant = changes['variant'].currentValue;
}
if (changes['theme'] != undefined || changes['variant'] != undefined) {
this.loadStyles();
}
if (changes['color'] != undefined) {
this.loadColor();
}
}
}
}
function clamp(v, min = 0, max = Number.MAX_SAFE_INTEGER) {
return Math.max(min, Math.min(max, coerceNumberProperty(v)));
}
class OsWindowClass {
constructor(componentElement, renderer, globalConfigService) {
this.componentElement = componentElement;
this.renderer = renderer;
this.globalConfigService = globalConfigService;
this.mousePos = { x: 0, y: 0 };
//Anchor stores temporary point of the current resize CdkDragMove event
this.anchor = { x: 0, y: 0 };
this.minHeight = 200;
this.minWidth = 200;
this.size = {
height: {
previous: 200,
current: 200,
unit: 'px',
},
width: {
previous: 200,
current: 200,
unit: 'px',
},
};
this.position = {
resize: { x: 0, y: 0 },
current: { x: 0, y: 0 },
next: { x: 0, y: 0 },
zIndex: {
current: 0,
next: 1,
},
};
this.cdkAnchors = {
n: { x: 0, y: 0 },
ne: { x: 0, y: 0 },
e: { x: 0, y: 0 },
se: { x: 0, y: 0 },
s: { x: 0, y: 0 },
sw: { x: 0, y: 0 },
w: { x: 0, y: 0 },
nw: { x: 0, y: 0 },
};
this.state = {
minimized: false,
maximized: false,
};
this.rules = {
disableResize: false,
minimizable: true,
maximizable: true,
closable: true,
};
this.styleConfig = new StyleClass(componentElement, renderer, globalConfigService, 'window');
}
setStyle(_elementRef, property, value) {
_elementRef.nativeElement.style.setProperty(property, value);
}
getStyle(_elementRef, property) {
return getComputedStyle(_elementRef.nativeElement).getPropertyValue(property);
}
clamp(input, max) {
return input >= max ? input : max;
}
clampHeight(_elementRef, _height, _minHeight) {
if (_minHeight) {
_height = this.clamp(_height, _minHeight);
}
return _height;
}
clampWidth(_elementRef, _width, _minWidth) {
if (_minWidth) {
_width = this.clamp(_width, _minWidth);
}
return _width;
}
setDimesions() {
this.size.width.current = this.clampWidth(this.componentElement, this.size.width.current, this.minWidth);
this.size.height.current = this.clampHeight(this.componentElement, this.size.height.current, this.minHeight);
}
////////////////////////
// Position //
////////////////////////
setPosition(positionStr) {
const X = parseInt(positionStr[0]);
const Y = parseInt(positionStr[1]);
if (!Number.isNaN(X)) {
this.position.next.x = X;
}
else {
switch (positionStr[0]) {
case 'left':
this.position.next.x = 0;
break;
case 'center':
this.position.next.x =
window.innerWidth / 2 - this.size.width.current / 2;
break;
case 'right':
this.position.next.x = window.innerWidth - this.size.width.current;
break;
default:
this.position.next.x = 0;
break;
}
}
if (!Number.isNaN(Y)) {
this.position.next.y = Y + window.innerHeight;
}
else {
//To hide the window element we need to set it top: -100% in scss,
//so we later need to calculate everything + innerHeight
switch (positionStr[1]) {
case 'top':
this.position.next.y = window.innerHeight;
break;
case 'center':
this.position.next.y =
window.innerHeight +
(window.innerHeight / 2 - this.size.height.current / 2);
break;
case 'bottom':
this.position.next.y =
window.innerHeight +
(window.innerHeight - this.size.height.current);
break;
default:
this.position.next.y = window.innerHeight;
break;
}
}
this.position.current = this.position.next;
}
////////////////////////
// Style //
////////////////////////
////////////////////////
// Rules //
////////////////////////
loadRules() {
//Minimizable?
if (!this.rules.minimizable) {
this.setStyle(this.componentElement, '--minimizeButton', 'none');
}
//Maximizable?
if (!this.rules.maximizable) {
this.setStyle(this.componentElement, '--maximizeButton', 'none');
}
//Closable?
if (!this.rules.closable) {
this.setStyle(this.componentElement, '--closeButton', 'none');
}
//Resizable?
if (this.rules.disableResize) {
this.setStyle(this.componentElement, '--cursorN', 'auto');
this.setStyle(this.componentElement, '--cursorNE', 'auto');
this.setStyle(this.componentElement, '--cursorE', 'auto');
this.setStyle(this.componentElement, '--cursorSE', 'auto');
this.setStyle(this.componentElement, '--cursorS', 'auto');
this.setStyle(this.componentElement, '--cursorSW', 'auto');
this.setStyle(this.componentElement, '--cursorW', 'auto');
this.setStyle(this.componentElement, '--cursorNW', 'auto');
}
}
////////////////////////
// Controls //
////////////////////////
minimize() {
//TODO
}
maximize() {
if (this.rules.maximizable) {
if (this.state.maximized == false) {
//Saving value for later
this.size.height.previous = this.size.height.current;
this.size.width.previous = this.size.width.current;
this.size.height.current = 100;
this.size.height.unit = 'vh';
this.size.width.current = 100;
this.size.width.unit = 'vw';
this.position.current = { x: 0, y: window.innerHeight };
this.state.maximized = true;
this.rules.disableResize = true;
}
else {
//Restoring window size
this.size.height.current = this.size.height.previous;
this.size.height.unit = 'px';
this.size.width.current = this.size.width.previous;
this.size.width.unit = 'px';
this.position.current = this.position.next;
this.state.maximized = false;
this.rules.disableResize = false;
}
}
}
//When maximized and then dragged the window demaximizes
//and puts itself aligned with the mouse position
demaximize() {
if (this.state.maximized == true) {
this.position.next = {
x: this.mousePos.x - this.size.width.current / 2,
y: this.mousePos.y + window.innerHeight - 20,
};
this.maximize();
}
}
close() {
this.componentElement.nativeElement.remove();
}
////////////////////////
// Resize & movement //
////////////////////////
storeMousePos(event) {
this.mousePos = {
x: event.x,
y: event.y,
};
}
//Sets some variables when the resize drag starts, we use them later
startResize() {
this.size.height.previous = this.size.height.current;
this.size.width.previous = this.size.width.current;
}
resize(dragEvent, direction) {
let directionSplit = [direction.charAt(0), direction.charAt(1)];
this.anchor = dragEvent.source.getFreeDragPosition();
directionSplit.forEach(dir => {
this.resizeDirection(dir);
});
//Reset anchor position
switch (direction) {
case 'n':
this.cdkAnchors.n = { x: 0, y: 0 };
break;
case 'ne':
this.cdkAnchors.n = { x: 0, y: 0 };
break;
case 'e':
this.cdkAnchors.e = { x: 0, y: 0 };
break;
case 'se':
this.cdkAnchors.se = { x: 0, y: 0 };
break;
case 's':
this.cdkAnchors.s = { x: 0, y: 0 };
break;
case 'sw':
this.cdkAnchors.sw = { x: 0, y: 0 };
break;
case 'w':
this.cdkAnchors.w = { x: 0, y: 0 };
break;
case 'nw':
this.cdkAnchors.nw = { x: 0, y: 0 };
break;
}
}
resizeDirection(direction) {
this.position.resize = this.position.next;
switch (direction) {
case 'n':
//Checks that the new position and dimesions produce a minHeight lower than the required
if (this.size.height.previous - this.anchor.y >= this.minHeight) {
this.position.resize = {
x: this.position.resize.x,
y: this.position.next.y + this.anchor.y,
};
this.size.height.current = this.size.height.previous - this.anchor.y;
this.size.height.current = this.clampHeight(this.componentElement, this.size.height.current, this.minHeight);
this.position.current = {
x: this.position.current.x,
y: this.position.resize.y,
};
}
break;
case 'e':
this.size.width.current = this.size.width.previous + this.anchor.x;
this.size.width.current = this.clampWidth(this.componentElement, this.size.width.current, this.minWidth);
break;
case 's':
this.size.height.current = this.size.height.previous + this.anchor.y;
this.size.height.current = this.clampHeight(this.componentElement, this.size.height.current, this.minHeight);
break;
case 'w':
//Checks that the new position and dimesions produce a minHeight lower than the required
if (this.size.width.previous - this.anchor.x >= this.minWidth) {
this.position.resize = {
x: this.position.next.x + this.anchor.x,
y: this.position.resize.y,
};
this.size.width.current = this.size.width.previous - this.anchor.x;
this.size.width.current = this.clampWidth(this.componentElement, this.size.width.current, this.minWidth);
this.position.current = {
x: this.position.resize.x,
y: this.position.current.y,
};
}
break;
}
}
endResize() {
this.position.next = this.position.current;
}
//When releasing the os-window the user may leave it outside of the browser window
//which would make it imposible to interact with the component again,
//this makes the window 'bounce' back into sight
correctEndPosition(event) {
this.position.next = event.source.getFreeDragPosition();
//Fix for Y position, the window-bar will always be visible
if (this.position.next.y < window.innerHeight) {
this.position.next.y = window.innerHeight;
}
else if (this.position.next.y > window.innerHeight * 2 - 40) {
this.position.next.y = window.innerHeight * 2 - 40;
}
//Fix for X position, a quarter of the window will always be visible
if (this.position.next.x < -((this.size.width.current / 4) * 3)) {
this.position.next.x = -((this.size.width.current / 4) * 3);
}
else if (this.position.next.x >
window.innerWidth - this.size.width.current / 4) {
this.position.next.x = window.innerWidth - this.size.width.current / 4;
}
this.position.current = this.position.next;
}
//////////////////////////////
// Other user interaction //
//////////////////////////////
//When a window is clicked we want to change it's z-index value and apply some styles
focus() {
//We get the current global z-index
this.position.zIndex.next = this.globalConfigService.getZIndex();
//This will be unequal if another window has been focused on
if (this.position.zIndex.current != this.position.zIndex.next) {
this.position.zIndex.next++;
this.position.zIndex.current = this.position.zIndex.next;
//Updating global z-index
this.globalConfigService.setZIndex(this.position.zIndex.current);
}
//After that we remove the 'focused' class from all the windows
let focused = document.getElementsByClassName('focused');
let i = 0;
while (i < focused.length) {
this.renderer.removeClass(focused[i], 'focused');
i++;
}
//We add the 'focused' class to the current window
this.renderer.addClass(this.componentElement.nativeElement.firstChild, 'focused');
}
}
class OsWindowTitle {
}
OsWindowTitle.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.1.4", ngImport: i0, type: OsWindowTitle, deps: [], target: i0.ɵɵFactoryTarget.Directive });
OsWindowTitle.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "15.1.4", type: OsWindowTitle, selector: "window-title, [window-title], [windowTitle]", exportAs: ["OsWindowTitle"], ngImport: i0 });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.1.4", ngImport: i0, type: OsWindowTitle, decorators: [{
type: Directive,
args: [{
selector: `window-title, [window-title], [windowTitle]`,
exportAs: 'OsWindowTitle',
}]
}] });
class OsWindowContent {
}
OsWindowContent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.1.4", ngImport: i0, type: OsWindowContent, deps: [], target: i0.ɵɵFactoryTarget.Directive });
OsWindowContent.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "15.1.4", type: OsWindowContent, selector: "window-content, [window-content], [windowContent]", exportAs: ["WindowContent"], ngImport: i0 });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.1.4", ngImport: i0, type: OsWindowContent, decorators: [{
type: Directive,
args: [{
selector: `window-content, [window-content], [windowContent]`,
exportAs: 'WindowContent',
}]
}] });
class OsWindowComponent {
constructor(componentElement, renderer, globalConfigService) {
this.componentElement = componentElement;
this.renderer = renderer;
this.globalConfigService = globalConfigService;
this.win = new OsWindowClass(this.componentElement, this.renderer, this.globalConfigService);
}
/////////////////////////
//// Host bindings ////
/////////////////////////
get zIndex() {
return this.win.position.zIndex.current;
}
//////////////////////
//// Inputs ////
//////////////////////
// Component theme //
get theme() {
return this.win.styleConfig.style.name;
}
set theme(v) {
this.win.styleConfig.style.name = v;
}
get variant() {
return this.win.styleConfig.style.variant;
}
set variant(v) {
this.win.styleConfig.style.variant = v;
}
// Size & position ///
get minHeight() {
return this.win.minHeight;
}
set minHeight(v) {
this.win.minHeight = clamp(v || this.win.minHeight);
}
get minWidth() {
return this.win.minWidth;
}
set minWidth(v) {
this.win.minWidth = clamp(v || this.win.minWidth);
}
get height() {
return this.win.size.height.current;
}
set height(v) {
this.win.size.height.current = clamp(v || this.win.minHeight);
}
get width() {
return this.win.size.width.current;
}
set width(v) {
this.win.size.width.current = clamp(v || this.win.minWidth);
}
set position(v) {
this.positionStr = v.split(' ', 2);
}
// Rules //
get resizable() {
return this.win.rules.disableResize;
}
set resizable(v) {
this.win.rules.disableResize = !v;
}
get minimizable() {
return this.win.rules.minimizable;
}
set minimizable(v) {
this.win.rules.minimizable = v;
}
get maximizable() {
return this.win.rules.maximizable;
}
set maximizable(v) {
this.win.rules.maximizable = v;
}
get closable() {
return this.win.rules.closable;
}
set closable(v) {
this.win.rules.closable = v;
}
ngOnInit() { }
ngAfterViewInit() {
/* We first care about the dimensions and position of the window */
//Initial width & height, also returns corrected value if bellow minimal
this.win.setDimesions();
//Sets initial position
this.win.setPosition(this.positionStr);
/* After dimensions & position we set the themes and rules */
//Setting theme of component
this.win.styleConfig.loadStyles();
//Colored windows someday?
//this.win.styleConfig.loadColor();
this.win.loadRules();
}
ngOnChanges(changes) {
//Changing styles on runtime
this.win.styleConfig.onChanges(changes);
}
}
OsWindowComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.1.4", ngImport: i0, type: OsWindowComponent, deps: [{ token: i0.ElementRef }, { token: i0.Renderer2 }, { token: OsConfigService }], target: i0.ɵɵFactoryTarget.Component });
OsWindowComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "15.1.4", type: OsWindowComponent, selector: "os-window", inputs: { theme: "theme", variant: "variant", minHeight: "minHeight", minWidth: "minWidth", height: "height", width: "width", position: "position", resizable: "resizable", minimizable: "minimizable", maximizable: "maximizable", closable: "closable" }, host: { properties: { "style.z-index": "this.zIndex" }, classAttribute: "os-window" }, usesOnChanges: true, ngImport: i0, template: "<!--\nTO DO:\n- Maximized change screen DELAYED\n- Arc Theme HALF DONE\n- Win98 Theme Check inner shadow scroll right & bottom, next release\n- Revise code\n- Documentation\n- Licencing\n- Release\n\n-->\n\n<div\n class=\"window-child\"\n [style.width]=\"win.size.width.current + win.size.width.unit\"\n [style.height]=\"win.size.height.current + win.size.height.unit\"\n cdkDrag\n (cdkDragStarted)=\"this.win.demaximize()\"\n (cdkDragEnded)=\"this.win.correctEndPosition($event)\"\n [cdkDragFreeDragPosition]=\"this.win.position.current\"\n (mousedown)=\"this.win.storeMousePos($event)\"\n (mousedown)=\"this.win.focus()\">\n <div\n cdkDrag\n cdkDragLockAxis=\"y\"\n (cdkDragStarted)=\"this.win.startResize()\"\n (cdkDragMoved)=\"this.win.resize($event, 'n')\"\n (cdkDragEnded)=\"this.win.endResize()\"\n [cdkDragFreeDragPosition]=\"this.win.cdkAnchors.n\"\n [cdkDragDisabled]=\"this.win.rules.disableResize\"\n (mousedown)=\"this.win.focus()\"\n class=\"window-resize n\"></div>\n <div\n cdkDrag\n (cdkDragStarted)=\"this.win.startResize()\"\n (cdkDragMoved)=\"this.win.resize($event, 'ne')\"\n [cdkDragFreeDragPosition]=\"this.win.cdkAnchors.ne\"\n [cdkDragDisabled]=\"this.win.rules.disableResize\"\n (mousedown)=\"this.win.focus()\"\n class=\"window-resize ne\"></div>\n <div\n cdkDrag\n cdkDragLockAxis=\"x\"\n (cdkDragStarted)=\"this.win.startResize()\"\n (cdkDragMoved)=\"this.win.resize($event, 'e')\"\n [cdkDragFreeDragPosition]=\"this.win.cdkAnchors.e\"\n [cdkDragDisabled]=\"this.win.rules.disableResize\"\n (mousedown)=\"this.win.focus()\"\n class=\"window-resize e\"></div>\n <div\n cdkDrag\n (cdkDragStarted)=\"this.win.startResize()\"\n (cdkDragMoved)=\"this.win.resize($event, 'se')\"\n [cdkDragFreeDragPosition]=\"this.win.cdkAnchors.se\"\n [cdkDragDisabled]=\"this.win.rules.disableResize\"\n (mousedown)=\"this.win.focus()\"\n class=\"window-resize se\"></div>\n <div\n cdkDrag\n cdkDragLockAxis=\"y\"\n (cdkDragStarted)=\"this.win.startResize()\"\n (cdkDragMoved)=\"this.win.resize($event, 's')\"\n [cdkDragFreeDragPosition]=\"this.win.cdkAnchors.s\"\n [cdkDragDisabled]=\"this.win.rules.disableResize\"\n (mousedown)=\"this.win.focus()\"\n class=\"window-resize s\"></div>\n <div\n cdkDrag\n (cdkDragStarted)=\"this.win.startResize()\"\n (cdkDragMoved)=\"this.win.resize($event, 'sw')\"\n (cdkDragEnded)=\"this.win.endResize()\"\n [cdkDragFreeDragPosition]=\"this.win.cdkAnchors.sw\"\n [cdkDragDisabled]=\"this.win.rules.disableResize\"\n (mousedown)=\"this.win.focus()\"\n class=\"window-resize sw\"></div>\n <div\n cdkDrag\n cdkDragLockAxis=\"x\"\n (cdkDragStarted)=\"this.win.startResize()\"\n (cdkDragMoved)=\"this.win.resize($event, 'w')\"\n (cdkDragEnded)=\"this.win.endResize()\"\n [cdkDragFreeDragPosition]=\"this.win.cdkAnchors.w\"\n [cdkDragDisabled]=\"this.win.rules.disableResize\"\n (mousedown)=\"this.win.focus()\"\n class=\"window-resize w\"></div>\n <div\n cdkDrag\n (cdkDragStarted)=\"this.win.startResize()\"\n (cdkDragMoved)=\"this.win.resize($event, 'nw')\"\n (cdkDragEnded)=\"this.win.endResize()\"\n [cdkDragFreeDragPosition]=\"this.win.cdkAnchors.nw\"\n [cdkDragDisabled]=\"this.win.rules.disableResize\"\n (mousedown)=\"this.win.focus()\"\n class=\"window-resize nw\"></div>\n\n <div class=\"window-bar\">\n <div\n class=\"window-bar-anchor\"\n cdkDragHandle\n (dblclick)=\"this.win.maximize()\">\n <div class=\"window-title\">\n <ng-content\n select=\"window-title, [window-title], [windowTitle]\"></ng-content>\n </div>\n\n <div class=\"controls-separator\"></div>\n\n <div class=\"controls-container\">\n <button class=\"control minimize\" (click)=\"this.win.minimize()\"></button>\n <button class=\"control maximize\" (click)=\"this.win.maximize()\"></button>\n <button class=\"control close\" (click)=\"this.win.close()\"></button>\n </div>\n </div>\n </div>\n\n <div class=\"window-content\">\n <ng-scrollbar\n [class]=\"theme + '-scrollbar'\"\n track=\"all\"\n trackClass=\"os-scrollbar-track\"\n thumbClass=\"os-scrollbar-thumb\">\n <ng-content select=\"window-content, [window-content], [windowContent]\">\n </ng-content>\n </ng-scrollbar>\n </div>\n</div>\n", styles: [".os-window{--minimizeButton: block;--maximizeButton: block;--closeButton: block;--cursorN: n-resize;--cursorNE: ne-resize;--cursorE: e-resize;--cursorSE: se-resize;--cursorS: s-resize;--cursorSW: sw-resize;--cursorW: w-resize;--cursorNW: nw-resize;position:fixed;top:-100%;left:0}.window-child{display:flex;flex-direction:column}.window-child .window-resize{position:absolute}.window-child .n{top:calc(-4px - var(--winBorder));left:calc(4px - var(--winBorder));height:8px;width:calc(100% - (8px - 2 * var(--winBorder)));cursor:var(--cursorN)}.window-child .ne{top:calc(-4px - var(--winBorder));right:calc(-4px - var(--winBorder));height:8px;width:8px;cursor:var(--cursorNE)}.window-child .e{top:calc(4px - var(--winBorder));right:calc(-4px - var(--winBorder));height:calc(100% - (8px - 2 * var(--winBorder)));width:8px;cursor:var(--cursorE)}.window-child .se{bottom:calc(-4px - var(--winBorder));right:calc(-4px - var(--winBorder));height:8px;width:8px;cursor:var(--cursorSE)}.window-child .s{bottom:calc(-4px - var(--winBorder));left:calc(4px - var(--winBorder));height:8px;width:calc(100% - (8px - 2 * var(--winBorder)));cursor:var(--cursorS)}.window-child .sw{bottom:calc(-4px - var(--winBorder));left:calc(-4px - var(--winBorder));height:8px;width:8px;cursor:var(--cursorSW)}.window-child .w{top:calc(4px - var(--winBorder));left:calc(-4px - var(--winBorder));height:calc(100% - (8px - 2 * var(--winBorder)));width:8px;cursor:var(--cursorW)}.window-child .nw{top:calc(-4px - var(--winBorder));left:calc(-4px - var(--winBorder));height:8px;width:8px;cursor:var(--cursorNW)}.window-child .window-bar{padding-top:4px;padding-right:4px;padding-left:4px}.window-child .window-bar .window-bar-anchor{display:flex;flex-direction:row;align-items:center}.window-child .window-bar .window-bar-anchor .window-title{flex:1 1 0%}.window-child .window-bar .window-bar-anchor .controls-container{display:flex;align-items:center}.window-child .window-bar .window-bar-anchor .controls-container .minimize{display:var(--minimizeButton)}.window-child .window-bar .window-bar-anchor .controls-container .maximize{display:var(--maximizeButton)}.window-child .window-bar .window-bar-anchor .controls-container .close{display:var(--closeButton)}.window-child .window-content{height:100%;width:100%;overflow:auto}.window-child .window-content ng-scrollbar ng-content{flex:1 1 0%}.maximized{width:100vw;height:100vh}\n", "@import\"https://fonts.googleapis.com/css2?family=Open+Sans&display=swap\";.arc-light-os-window{--winBorder: 1px;--winRadius: 5px}.arc-light-os-window .window-child{border-radius:var(--winRadius);border:var(--winBorder) solid rgba(212,213,219,.95);box-shadow:0 0 0 1px #0000001a,0 8px 8px #0003;font-family:Futura Bk bt,\"Open Sans\",sans,Sans-Serif;font-size:9pt}.arc-light-os-window .window-child .window-bar{height:44px;background-color:#e7e8ebf2;border-color:#d4d5dbf2;border-bottom:1px solid rgba(212,213,219,.95);border-top-left-radius:calc(var(--winRadius) - 1);border-top-right-radius:calc(var(--winRadius) - 1)}.arc-light-os-window .window-child .window-bar .window-bar-anchor{height:36px;width:100%}.arc-light-os-window .window-child .window-bar .window-bar-anchor .window-title{margin-left:30px;color:#525d76cc}.arc-light-os-window .window-child .window-content{background-color:#fff;color:#5c616c}.arc-light-os-window .focused .window-bar{background-color:#e7e8eb}.arc-light-os-window .maximized{border:0px;border-radius:0}.arc-light-os-window .maximized .window-bar{border:0px;border-radius:0;background-color:#e7e8eb}.arc-light-os-window .controls-separator{margin-right:14px;transform:translate(4px,-1px);width:1px;height:15px;background:rgba(82,93,118,.15)}.arc-light-os-window .controls-container{margin-right:8px;width:76px;height:auto;display:flex;gap:14px}.arc-light-os-window .controls-container .control{padding:0;min-height:16px;width:16px;height:16px;border:0px;background-color:#0000;box-shadow:none;outline:0}.arc-light-os-window a{color:#2679db}.arc-light-os-window a:visited{color:#1e61b0}.arc-light-os-window a:visited:hover{color:#2679db}.arc-light-os-window a:hover{color:#5294e2}.arc-light-os-window .os-scrollbar-track{background-color:#ffffff1a;margin-bottom:5px}.arc-light-os-window .os-scrollbar-thumb{background-color:#b8babf99}.arc-light-os-window .minimize{background:url(\"data:image/svg+xml,%3C%3Fxml version%3D%221.0%22 encoding%3D%22UTF-8%22 standalone%3D%22no%22%3F%3E%3C!-- Created with Inkscape (http%3A%2F%2Fwww.inkscape.org%2F) --%3E%3Csvg width%3D%2216%22 height%3D%2216%22 id%3D%22svg9892%22 version%3D%221.1%22 inkscape%3Aversion%3D%220.91 r13725%22 sodipodi%3Adocname%3D%22assets.svg%22 xmlns%3Ainkscape%3D%22http%3A%2F%2Fwww.inkscape.org%2Fnamespaces%2Finkscape%22 xmlns%3Asodipodi%3D%22http%3A%2F%2Fsodipodi.sourceforge.net%2FDTD%2Fsodipodi-0.dtd%22 xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22 xmlns%3Asvg%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22 xmlns%3Ardf%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2F02%2F22-rdf-syntax-ns%23%22 xmlns%3Acc%3D%22http%3A%2F%2Fcreativecommons.org%2Fns%23%22 xmlns%3Adc%3D%22http%3A%2F%2Fpurl.org%2Fdc%2Felements%2F1.1%2F%22%3E %3Cdefs id%3D%22defs9894%22%3E %3ClinearGradient id%3D%22linearGradient6651%22%3E %3Cstop style%3D%22stop-color%3A%23ffffff%3Bstop-opacity%3A1%3B%22 offset%3D%220%22 id%3D%22stop6653%22 %2F%3E %3C%2FlinearGradient%3E %3ClinearGradient id%3D%22selected_fg_color%22 inkscape%3Aswatch%3D%22solid%22%3E %3Cstop style%3D%22stop-color%3A%23ffffffgit%3Bstop-opacity%3A1%3B%22 offset%3D%220%22 id%3D%22stop6555%22 %2F%3E %3C%2FlinearGradient%3E %3ClinearGradient id%3D%22selected_bg_color%22 inkscape%3Aswatch%3D%22solid%22%3E %3Cstop style%3D%22stop-color%3A%235294e2%3Bstop-opacity%3A1%3B%22 offset%3D%220%22 id%3D%22stop5956%22 %2F%3E %3C%2FlinearGradient%3E %3C%2Fdefs%3E %3Csodipodi%3Anamedview id%3D%22base%22 pagecolor%3D%22%23ffffff%22 bordercolor%3D%22%23666666%22 borderopacity%3D%221.0%22 inkscape%3Apageopacity%3D%220.0%22 inkscape%3Apageshadow%3D%222%22 inkscape%3Azoom%3D%228%22 inkscape%3Acx%3D%2237.764915%22 inkscape%3Acy%3D%2268.141665%22 inkscape%3Adocument-units%3D%22px%22 inkscape%3Acurrent-layer%3D%22layer3%22 showgrid%3D%22false%22 showborder%3D%22false%22 inkscape%3Awindow-width%3D%221259%22 inkscape%3Awindow-height%3D%22630%22 inkscape%3Awindow-x%3D%2265%22 inkscape%3Awindow-y%3D%2224%22 inkscape%3Awindow-maximized%3D%221%22 inkscape%3Asnap-nodes%3D%22true%22 inkscape%3Asnap-bbox%3D%22true%22 inkscape%3Abbox-paths%3D%22true%22 inkscape%3Asnap-global%3D%22true%22 showguides%3D%22false%22 inkscape%3Aguide-bbox%3D%22true%22 inkscape%3Abbox-nodes%3D%22true%22 inkscape%3Aobject-nodes%3D%22true%22 inkscape%3Asnap-bbox-midpoints%3D%22false%22 fit-margin-top%3D%220%22 fit-margin-left%3D%220%22 fit-margin-right%3D%220%22 fit-margin-bottom%3D%220%22 inkscape%3Ashowpageshadow%3D%222%22 inkscape%3Apagecheckerboard%3D%220%22 inkscape%3Adeskcolor%3D%22%23d1d1d1%22%3E %3Cinkscape%3Agrid type%3D%22xygrid%22 id%3D%22grid10919%22 empspacing%3D%225%22 visible%3D%22true%22 enabled%3D%22true%22 snapvisiblegridlinesonly%3D%22true%22 originx%3D%228.000005%22 originy%3D%22-1062%22 %2F%3E %3Csodipodi%3Aguide orientation%3D%221%2C0%22 position%3D%22255.875%2C-490.75%22 id%3D%22guide8384%22 inkscape%3Alocked%3D%22false%22 %2F%3E %3C%2Fsodipodi%3Anamedview%3E %3Cmetadata id%3D%22metadata9897%22%3E %3Crdf%3ARDF%3E %3Ccc%3AWork rdf%3Aabout%3D%22%22%3E %3Cdc%3Aformat%3Eimage%2Fsvg%2Bxml%3C%2Fdc%3Aformat%3E %3Cdc%3Atype rdf%3Aresource%3D%22http%3A%2F%2Fpurl.org%2Fdc%2Fdcmitype%2FStillImage%22 %2F%3E %3Cdc%3Atitle %2F%3E %3C%2Fcc%3AWork%3E %3C%2Frdf%3ARDF%3E %3C%2Fmetadata%3E %3Cg inkscape%3Alabel%3D%22assets%22 inkscape%3Agroupmode%3D%22layer%22 id%3D%22layer1%22 style%3D%22display%3Ainline%22 transform%3D%22translate(-116%2C228.63782)%22%3E %3Cg id%3D%22titlebutton-minimize%22 inkscape%3Alabel%3D%22%23g6247%22 transform%3D%22translate(-471%2C-43)%22 style%3D%22opacity%3A0.8%22%3E %3Cg id%3D%22g7138%22 style%3D%22display%3Ainline%3Bopacity%3A1%22 transform%3D%22translate(-781%2C-432.63782)%22%3E %3Cg transform%3D%22translate(-58)%22 style%3D%22display%3Ainline%3Bopacity%3A1%22 id%3D%22g4490-3-75%22%3E %3Cg id%3D%22g4092-0-7-0%22 style%3D%22display%3Ainline%22 transform%3D%22translate(58)%22 %2F%3E %3C%2Fg%3E %3Cpath inkscape%3Aconnector-curvature%3D%220%22 d%3D%22m 1373%2C254 v 2 h 6 v -2 z%22 id%3D%22rect9057-4-3%22 style%3D%22color%3A%23000000%3Bfont-style%3Anormal%3Bfont-variant%3Anormal%3Bfont-weight%3Anormal%3Bfont-stretch%3Anormal%3Bfont-size%3Amedium%3Bline-height%3Anormal%3Bfont-family%3ASans%3B-inkscape-font-specification%3ASans%3Btext-indent%3A0%3Btext-align%3Astart%3Btext-decoration%3Anone%3Btext-decoration-line%3Anone%3Bletter-spacing%3Anormal%3Bword-spacing%3Anormal%3Btext-transform%3Anone%3Bwriting-mode%3Alr-tb%3Bdirection%3Altr%3Bbaseline-shift%3Abaseline%3Btext-anchor%3Astart%3Bdisplay%3Ainline%3Boverflow%3Avisible%3Bvisibility%3Avisible%3Bopacity%3A1%3Bfill%3A%237a7f8b%3Bfill-opacity%3A1%3Bstroke%3Anone%3Bstroke-width%3A2%3Bmarker%3Anone%3Benable-background%3Aaccumulate%22 sodipodi%3Anodetypes%3D%22ccccc%22 %2F%3E %3C%2Fg%3E %3Crect y%3D%22-185.63782%22 x%3D%22587%22 height%3D%2216%22 width%3D%2216%22 id%3D%22rect17883-32%22 style%3D%22display%3Ainline%3Bopacity%3A1%3Bfill%3Anone%3Bfill-opacity%3A1%3Bstroke%3Anone%3Bstroke-width%3A1%3Bstroke-linecap%3Abutt%3Bstroke-linejoin%3Amiter%3Bstroke-miterlimit%3A4%3Bstroke-dasharray%3Anone%3Bstroke-dashoffset%3A0%3Bstroke-opacity%3A0%22 %2F%3E %3C%2Fg%3E %3C%2Fg%3E%3C%2Fsvg%3E\")}.arc-light-os-window .minimize:hover{background:url(\"data:image/svg+xml,%3C%3Fxml version%3D%221.0%22 encoding%3D%22UTF-8%22 standalone%3D%22no%22%3F%3E%3C!-- Created with Inkscape (http%3A%2F%2Fwww.inkscape.org%2F) --%3E%3Csvg width%3D%2216%22 height%3D%2216%22 id%3D%22svg9892%22 version%3D%221.1%22 inkscape%3Aversion%3D%220.91 r13725%22 sodipodi%3Adocname%3D%22assets.svg%22 xmlns%3Ainkscape%3D%22http%3A%2F%2Fwww.inkscape.org%2Fnamespaces%2Finkscape%22 xmlns%3Asodipodi%3D%22http%3A%2F%2Fsodipodi.sourceforge.net%2FDTD%2Fsodipodi-0.dtd%22 xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22 xmlns%3Asvg%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22 xmlns%3Ardf%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2F02%2F22-rdf-syntax-ns%23%22 xmlns%3Acc%3D%22http%3A%2F%2Fcreativecommons.org%2Fns%23%22 xmlns%3Adc%3D%22http%3A%2F%2Fpurl.org%2Fdc%2Felements%2F1.1%2F%22%3E %3Cdefs id%3D%22defs9894%22%3E %3ClinearGradient id%3D%22linearGradient6651%22%3E %3Cstop style%3D%22stop-color%3A%23ffffff%3Bstop-opacity%3A1%3B%22 offset%3D%220%22 id%3D%22stop6653%22 %2F%3E %3C%2FlinearGradient%3E %3ClinearGradient id%3D%22selected_fg_color%22 inkscape%3Aswatch%3D%22solid%22%3E %3Cstop style%3D%22stop-color%3A%23ffffffgit%3Bstop-opacity%3A1%3B%22 offset%3D%220%22 id%3D%22stop6555%22 %2F%3E %3C%2FlinearGradient%3E %3ClinearGradient id%3D%22selected_bg_color%22 inkscape%3Aswatch%3D%22solid%22%3E %3Cstop style%3D%22stop-color%3A%235294e2%3Bstop-opacity%3A1%3B%22 offset%3D%220%22 id%3D%22stop5956%22 %2F%3E %3C%2FlinearGradient%3E %3C%2Fdefs%3E %3Csodipodi%3Anamedview id%3D%22base%22 pagecolor%3D%22%23ffffff%22 bordercolor%3D%22%23666666%22 borderopacity%3D%221.0%22 inkscape%3Apageopacity%3D%220.0%22 inkscape%3Apageshadow%3D%222%22 inkscape%3Azoom%3D%228%22 inkscape%3Acx%3D%2237.764915%22 inkscape%3Acy%3D%2268.141665%22 inkscape%3Adocument-units%3D%22px%22 inkscape%3Acurrent-layer%3D%22layer3%22 showgrid%3D%22false%22 showborder%3D%22false%22 inkscape%3Awindow-width%3D%221259%22 inkscape%3Awindow-height%3D%22630%22 inkscape%3Awindow-x%3D%2265%22 inkscape%3Awindow-y%3D%2224%22 inkscape%3Awindow-maximized%3D%221%22 inkscape%3Asnap-nodes%3D%22true%22 inkscape%3Asnap-bbox%3D%22true%22 inkscape%3Abbox-paths%3D%22true%22 inkscape%3Asnap-global%3D%22true%22 showguides%3D%22false%22 inkscape%3Aguide-bbox%3D%22true%22 inkscape%3Abbox-nodes%3D%22true%22 inkscape%3Aobject-nodes%3D%22true%22 inkscape%3Asnap-bbox-midpoints%3D%22false%22 fit-margin-top%3D%220%22 fit-margin-left%3D%220%22 fit-margin-right%3D%220%22 fit-margin-bottom%3D%220%22 inkscape%3Ashowpageshadow%3D%222%22 inkscape%3Apagecheckerboard%3D%220%22 inkscape%3Adeskcolor%3D%22%23d1d1d1%22%3E %3Cinkscape%3Agrid type%3D%22xygrid%22 id%3D%22grid10919%22 empspacing%3D%225%22 visible%3D%22true%22 enabled%3D%22true%22 snapvisiblegridlinesonly%3D%22true%22 originx%3D%228.000005%22 originy%3D%22-1062%22 %2F%3E %3Csodipodi%3Aguide orientation%3D%221%2C0%22 position%3D%22255.875%2C-456.75%22 id%3D%22guide8384%22 inkscape%3Alocked%3D%22false%22 %2F%3E %3C%2Fsodipodi%3Anamedview%3E %3Cmetadata id%3D%22metadata9897%22%3E %3Crdf%3ARDF%3E %3Ccc%3AWork rdf%3Aabout%3D%22%22%3E %3Cdc%3Aformat%3Eimage%2Fsvg%2Bxml%3C%2Fdc%3Aformat%3E %3Cdc%3Atype rdf%3Aresource%3D%22http%3A%2F%2Fpurl.org%2Fdc%2Fdcmitype%2FStillImage%22 %2F%3E %3Cdc%3Atitle %2F%3E %3C%2Fcc%3AWork%3E %3C%2Frdf%3ARDF%3E %3C%2Fmetadata%3E %3Cg inkscape%3Alabel%3D%22assets%22 inkscape%3Agroupmode%3D%22layer%22 id%3D%22layer1%22 style%3D%22display%3Ainline%22 transform%3D%22translate(-116%2C194.63782)%22%3E %3Cg id%3D%22titlebutton-minimize-hover%22 inkscape%3Alabel%3D%22%23g6308%22 transform%3D%22translate(-368%2C-9)%22%3E %3Cg id%3D%22g4909%22 style%3D%22display%3Ainline%3Bopacity%3A1%22 transform%3D%22translate(-781%2C-432.63782)%22%3E %3Cellipse cy%3D%22255%22 cx%3D%221273%22 style%3D%22display%3Ainline%3Bopacity%3A0.95%3Bfill%3A%23ffffff%3Bfill-opacity%3A1%3Bstroke%3Anone%3Bstroke-width%3A0%3Bstroke-linecap%3Abutt%3Bstroke-linejoin%3Amiter%3Bstroke-miterlimit%3A4%3Bstroke-dasharray%3Anone%3Bstroke-dashoffset%3A0%3Bstroke-opacity%3A1%22 id%3D%22path4068-7-5-9-6-7-2-1%22 rx%3D%226%22 ry%3D%226.0000005%22 %2F%3E %3Cpath style%3D%22display%3Ainline%3Bopacity%3A0.15%3Bfill%3A%23525d76%3Bfill-opacity%3A1%3Bstroke%3Anone%3Bstroke-width%3A0%3Bstroke-linecap%3Abutt%3Bstroke-linejoin%3Amiter%3Bstroke-miterlimit%3A4%3Bstroke-dasharray%3Anone%3Bstroke-dashoffset%3A0%3Bstroke-opacity%3A1%22 d%3D%22m 1273%2C248 a 7%2C7 0 0 0 -7%2C7 7%2C7 0 0 0 7%2C7 7%2C7 0 0 0 7%2C-7 7%2C7 0 0 0 -7%2C-7 z m 0%2C1 a 6%2C6 0 0 1 6%2C6 6%2C6 0 0 1 -6%2C6 6%2C6 0 0 1 -6%2C-6 6%2C6 0 0 1 6%2C-6 z%22 id%3D%22path4068-7-5-9-6-7-2-5-23%22 inkscape%3Aconnector-curvature%3D%220%22 %2F%3E %3Cg style%3D%22display%3Ainline%3Bopacity%3A1%3Bfill%3A%23c0e3ff%3Bfill-opacity%3A1%22 id%3D%22g4834-0%22 transform%3D%22translate(1265%2C247)%22%3E %3Cg transform%3D%22translate(-81.0002%2C-967)%22 style%3D%22display%3Ainline%3Bfill%3A%23c0e3ff%3Bfill-opacity%3A1%22 inkscape%3Alabel%3D%22status%22 id%3D%22layer9-3-4%22 %2F%3E %3Cg transform%3D%22translate(-81.0002%2C-967)%22 inkscape%3Alabel%3D%22devices%22 id%3D%22layer10-4-1%22 style%3D%22fill%3A%23c0e3ff%3Bfill-opacity%3A1%22 %2F%3E %3Cg transform%3D%22translate(-81.0002%2C-967)%22 inkscape%3Alabel%3D%22apps%22 id%3D%22layer11-2-6%22 style%3D%22fill%3A%23c0e3ff%3Bfill-opacity%3A1%22 %2F%3E %3Cg transform%3D%22translate(-81.0002%2C-967)%22 inkscape%3Alabel%3D%22places%22 id%3D%22layer13-5-4%22 style%3D%22fill%3A%23c0e3ff%3Bfill-opacity%3A1%22 %2F%3E %3Cg transform%3D%22translate(-81.0002%2C-967)%22 inkscape%3Alabel%3D%22mimetypes%22 id%3D%22layer14-6-0%22 style%3D%22fill%3A%23c0e3ff%3Bfill-opacity%3A1%22 %2F%3E %3Cg transform%3D%22translate(-81.0002%2C-967)%22 style%3D%22display%3Ainline%3Bfill%3A%23c0e3ff%3Bfill-opacity%3A1%22 inkscape%3Alabel%3D%22emblems%22 id%3D%22layer15-52-1%22 %2F%3E %3Cg transform%3D%22translate(-81.0002%2C-967)%22 style%3D%22display%3Ainline%3Bfill%3A%23c0e3ff%3Bfill-opacity%3A1%22 inkscape%3Alabel%3D%22emotes%22 id%3D%22g71291-3-9%22 %2F%3E %3Cg transform%3D%22translate(-81.0002%2C-967)%22 style%3D%22display%3Ainline%3Bfill%3A%23c0e3ff%3Bfill-opacity%3A1%22 inkscape%3Alabel%3D%22categories%22 id%3D%22g4953-8-2%22 %2F%3E %3Cg transform%3D%22translate(-81.0002%2C-967)%22 style%3D%22display%3Ainline%3Bfill%3A%23c0e3ff%3Bfill-opacity%3A1%22 inkscape%3Alabel%3D%22actions%22 id%3D%22layer12-45-6%22%3E %3Cpath sodipodi%3Anodetypes%3D%22ccccc%22