From a1df915da44ab2824aef8d8ed70e26da6db7e0bb Mon Sep 17 00:00:00 2001 From: Oscar Lorentzon Date: Wed, 9 Jun 2021 21:23:05 +0200 Subject: [PATCH] fix: viewer configuration as observables Avoid setting static properties which affects all future Viewers created in the same app. Use observables instead. --- .../attribution/AttributionComponent.ts | 25 ++-- src/component/cover/CoverComponent.ts | 17 ++- src/component/image/ImageComponent.ts | 117 ++++++++++-------- src/component/slider/SliderComponent.ts | 108 +++++++++------- src/viewer/ConfigurationService.ts | 29 +++++ src/viewer/Container.ts | 19 +-- src/viewer/PlayService.ts | 1 - src/viewer/Viewer.ts | 3 - src/viewer/ViewerConfiguration.ts | 40 ------ styles/cover.css | 19 ++- .../helper/ConfigurationServiceMockCreator.ts | 26 ++++ test/helper/ContainerMockCreator.ts | 6 + test/util/ViewerConfiguration.test.ts | 26 ---- test/viewer/ConfigurationService.test.ts | 89 +++++++++++++ 14 files changed, 327 insertions(+), 198 deletions(-) create mode 100644 src/viewer/ConfigurationService.ts delete mode 100644 src/viewer/ViewerConfiguration.ts create mode 100644 test/helper/ConfigurationServiceMockCreator.ts delete mode 100644 test/util/ViewerConfiguration.test.ts create mode 100644 test/viewer/ConfigurationService.test.ts diff --git a/src/component/attribution/AttributionComponent.ts b/src/component/attribution/AttributionComponent.ts index 2d3f3a6e6..8d19b6552 100644 --- a/src/component/attribution/AttributionComponent.ts +++ b/src/component/attribution/AttributionComponent.ts @@ -6,7 +6,6 @@ import { map } from "rxjs/operators"; import { Image } from "../../graph/Image"; import { ViewportSize } from "../../render/interfaces/ViewportSize"; import { VirtualNodeHash } from "../../render/interfaces/VirtualNodeHash"; -import { ViewerConfiguration } from "../../viewer/ViewerConfiguration"; import { Component } from "../Component"; import { ComponentConfiguration } from "../interfaces/ComponentConfiguration"; @@ -18,13 +17,15 @@ export class AttributionComponent extends Component { protected _activate(): void { this._subscriptions.push( observableCombineLatest( + this._container.configurationService.exploreUrl$, this._navigator.stateService.currentImage$, this._container.renderService.size$).pipe( map( - ([image, size]: [Image, ViewportSize]): VirtualNodeHash => { + ([exploreUrl, image, size]: [string, Image, ViewportSize]): VirtualNodeHash => { const attribution = this._makeAttribution( image.creatorUsername, + exploreUrl, image.id, image.capturedAt, size.width); @@ -44,8 +45,13 @@ export class AttributionComponent extends Component { return {}; } + private makeImageUrl(exploreUrl: string, id: string): string { + return `${exploreUrl}/app/?pKey=${id}&focus=photo`; + } + private _makeAttribution( creatorUsername: string, + exploreUrl: string, imageId: string, capturedAt: number, viewportWidth: number) @@ -53,7 +59,7 @@ export class AttributionComponent extends Component { const compact = viewportWidth <= 640; const date = this._makeDate(capturedAt, compact); - const by = this._makeBy(creatorUsername, imageId, compact); + const by = this._makeBy(creatorUsername, exploreUrl, imageId, compact); const compactClass = compact ? ".mapillary-attribution-compact" : ""; @@ -66,6 +72,7 @@ export class AttributionComponent extends Component { private _makeBy( creatorUsername: string, + exploreUrl: string, imageId: string, compact: boolean): vd.VNode[] { @@ -73,18 +80,19 @@ export class AttributionComponent extends Component { "div.mapillary-attribution-logo", []); return creatorUsername ? - this._makeCreatorBy(icon, creatorUsername, imageId, compact) : - this._makeGeneralBy(icon, imageId, compact); + this._makeCreatorBy(icon, creatorUsername, exploreUrl, imageId, compact) : + this._makeGeneralBy(icon, exploreUrl, imageId, compact); } private _makeCreatorBy( icon: vd.VNode, creatorUsername: string, + exploreUrl: string, imageId: string, compact: boolean): vd.VNode[] { const mapillary = vd.h( "a.mapillary-attribution-icon-container", - { href: ViewerConfiguration.explore, rel: "noreferrer", target: "_blank" }, + { href: exploreUrl, rel: "noreferrer", target: "_blank" }, [icon]); const content = compact ? @@ -97,7 +105,7 @@ export class AttributionComponent extends Component { const image = vd.h( "a.mapillary-attribution-image-container", { - href: ViewerConfiguration.exploreImage(imageId), + href: this.makeImageUrl(exploreUrl, imageId), rel: "noreferrer", target: "_blank", }, @@ -108,6 +116,7 @@ export class AttributionComponent extends Component { private _makeGeneralBy( icon: vd.VNode, + exploreUrl: string, imageId: string, compact: boolean): vd.VNode[] { @@ -134,7 +143,7 @@ export class AttributionComponent extends Component { const image = vd.h( "a.mapillary-attribution-image-container", { - href: ViewerConfiguration.exploreImage(imageId), + href: this.makeImageUrl(exploreUrl, imageId), rel: "noreferrer", target: "_blank", }, diff --git a/src/component/cover/CoverComponent.ts b/src/component/cover/CoverComponent.ts index 8ec2731ec..cbff843c4 100644 --- a/src/component/cover/CoverComponent.ts +++ b/src/component/cover/CoverComponent.ts @@ -26,7 +26,6 @@ import { MapillaryError } from "../../error/MapillaryError"; import { Image as MImage } from "../../graph/Image"; import { ViewportSize } from "../../render/interfaces/ViewportSize"; import { VirtualNodeHash } from "../../render/interfaces/VirtualNodeHash"; -import { ViewerConfiguration } from "../../viewer/ViewerConfiguration"; import { Container } from "../../viewer/Container"; import { Navigator } from "../../viewer/Navigator"; import { ImagesContract } from "../../api/contracts/ImagesContract"; @@ -128,9 +127,11 @@ export class CoverComponent extends Component { subs.push(observableCombineLatest( this._configuration$, + this._container.configurationService.exploreUrl$, this._container.renderService.size$).pipe( map( - ([configuration, size]: [CoverConfiguration, ViewportSize]): VirtualNodeHash => { + ([configuration, exploreUrl, size]: + [CoverConfiguration, string, ViewportSize]): VirtualNodeHash => { if (!configuration.src) { return { name: this._name, vNode: vd.h("div", []) }; } @@ -147,7 +148,7 @@ export class CoverComponent extends Component { const container: vd.VNode = vd.h( "div.mapillary-cover-container" + compactClass, - [this._getCoverButtonVNode(configuration)]); + [this._getCoverButtonVNode(configuration, exploreUrl)]); return { name: this._name, vNode: container }; })) @@ -162,13 +163,19 @@ export class CoverComponent extends Component { return { state: CoverState.Visible }; } - private _getCoverButtonVNode(configuration: CoverConfiguration): vd.VNode { + private _getCoverButtonVNode( + configuration: CoverConfiguration, + exploreUrl: string): vd.VNode { + const cover: string = configuration.state === CoverState.Loading ? "div.mapillary-cover.mapillary-cover-loading" : "div.mapillary-cover"; const coverButton: vd.VNode = vd.h( "div.mapillary-cover-button", [vd.h("div.mapillary-cover-button-icon", [])]); - const coverLogo: vd.VNode = vd.h("a.mapillary-cover-logo", { href: ViewerConfiguration.explore, target: "_blank" }, []); + const coverLogo: vd.VNode = vd.h( + "a.mapillary-cover-logo", + { href: exploreUrl, target: "_blank" }, + []); const coverIndicator: vd.VNode = vd.h( "div.mapillary-cover-indicator", { onclick: (): void => { this.configure({ state: CoverState.Loading }); } }, diff --git a/src/component/image/ImageComponent.ts b/src/component/image/ImageComponent.ts index 4e16c4cbf..49f88c7f7 100644 --- a/src/component/image/ImageComponent.ts +++ b/src/component/image/ImageComponent.ts @@ -46,10 +46,8 @@ import { RegionOfInterestCalculator } import { TextureProvider } from "../../tile/TextureProvider"; import { ComponentConfiguration } from "../interfaces/ComponentConfiguration"; import { Transform } from "../../geo/Transform"; -import { ViewerConfiguration } from "../../viewer/ViewerConfiguration"; import { ComponentName } from "../ComponentName"; import { State } from "../../state/State"; -import { Camera } from "three"; interface ImageGLRendererOperation { (renderer: ImageGLRenderer): ImageGLRenderer; @@ -170,9 +168,14 @@ export class ImageComponent extends Component { })) .subscribe(this._rendererOperation$)); - const textureProvider$ = this._navigator.stateService.currentState$ - .pipe( - filter(() => ViewerConfiguration.imageTiling), + const textureProvider$ = + this._container.configurationService.imageTiling$.pipe( + switchMap( + (active): Observable => { + return active ? + this._navigator.stateService.currentState$ : + new Subject(); + }), distinctUntilChanged( undefined, (frame: AnimationFrame): string => { @@ -220,56 +223,60 @@ export class ImageComponent extends Component { previous.abort(); })); - const roiTrigger$ = ViewerConfiguration.imageTiling ? - observableCombineLatest( - this._navigator.stateService.state$, - this._navigator.stateService.inTranslation$) - .pipe( - switchMap( - ([state, inTranslation]: [State, boolean]) => { - const streetState = - state === State.Traversing || - state === State.Waiting || - state === State.WaitingInteractively; - const active = streetState && !inTranslation; - return active ? - this._container.renderService.renderCameraFrame$ : - observableEmpty(); - }), - map( - (camera: RenderCamera): PositionLookat => { - return { - camera, - height: camera.size.height.valueOf(), - lookat: camera.camera.lookat.clone(), - width: camera.size.width.valueOf(), - zoom: camera.zoom.valueOf(), - }; - }), - pairwise(), - map( - ([pl0, pl1]: [PositionLookat, PositionLookat]) - : StalledCamera => { - const stalled = - pl0.width === pl1.width && - pl0.height === pl1.height && - pl0.zoom === pl1.zoom && - pl0.lookat.equals(pl1.lookat); - - return { camera: pl1.camera, stalled }; - }), - distinctUntilChanged( - (x, y): boolean => { - return x.stalled === y.stalled; - }), - filter( - (camera: StalledCamera): boolean => { - return camera.stalled; - }), - withLatestFrom( - this._container.renderService.size$, - this._navigator.stateService.currentTransform$)) : - observableEmpty(); + const roiTrigger$ = + this._container.configurationService.imageTiling$.pipe( + switchMap( + (active): Observable<[State, boolean]> => { + return active ? + observableCombineLatest( + this._navigator.stateService.state$, + this._navigator.stateService.inTranslation$) : + new Subject(); + }), + switchMap( + ([state, inTranslation]: [State, boolean]) => { + const streetState = + state === State.Traversing || + state === State.Waiting || + state === State.WaitingInteractively; + const active = streetState && !inTranslation; + return active ? + this._container.renderService.renderCameraFrame$ : + observableEmpty(); + }), + map( + (camera: RenderCamera): PositionLookat => { + return { + camera, + height: camera.size.height.valueOf(), + lookat: camera.camera.lookat.clone(), + width: camera.size.width.valueOf(), + zoom: camera.zoom.valueOf(), + }; + }), + pairwise(), + map( + ([pl0, pl1]: [PositionLookat, PositionLookat]) + : StalledCamera => { + const stalled = + pl0.width === pl1.width && + pl0.height === pl1.height && + pl0.zoom === pl1.zoom && + pl0.lookat.equals(pl1.lookat); + + return { camera: pl1.camera, stalled }; + }), + distinctUntilChanged( + (x, y): boolean => { + return x.stalled === y.stalled; + }), + filter( + (camera: StalledCamera): boolean => { + return camera.stalled; + }), + withLatestFrom( + this._container.renderService.size$, + this._navigator.stateService.currentTransform$)); subs.push(textureProvider$.pipe( switchMap( diff --git a/src/component/slider/SliderComponent.ts b/src/component/slider/SliderComponent.ts index f842f92aa..b8de183f4 100644 --- a/src/component/slider/SliderComponent.ts +++ b/src/component/slider/SliderComponent.ts @@ -64,7 +64,6 @@ import { SliderGLRenderer } from "./SliderGLRenderer"; import { Transform } from "../../geo/Transform"; import { SliderDOMRenderer } from "./SliderDOMRenderer"; import { isSpherical } from "../../geo/Geo"; -import { ViewerConfiguration } from "../../viewer/ViewerConfiguration"; import { ComponentName } from "../ComponentName"; /** @@ -396,9 +395,14 @@ export class SliderComponent extends Component { })); - const textureProvider$ = this._navigator.stateService.currentState$ - .pipe( - filter(() => ViewerConfiguration.imageTiling), + const textureProvider$ = + this._container.configurationService.imageTiling$.pipe( + switchMap( + (active): Observable => { + return active ? + this._navigator.stateService.currentState$ : + new Subject(); + }), distinctUntilChanged( undefined, (frame: AnimationFrame): string => { @@ -449,10 +453,16 @@ export class SliderComponent extends Component { previous.abort(); })); - const roiTrigger$ = observableCombineLatest( - this._container.renderService.renderCameraFrame$, - this._container.renderService.size$.pipe(debounceTime(250))).pipe( - filter(() => ViewerConfiguration.imageTiling), + const roiTrigger$ = + this._container.configurationService.imageTiling$.pipe( + switchMap( + (active): Observable<[RenderCamera, ViewportSize]> => { + return active ? + observableCombineLatest( + this._container.renderService.renderCameraFrame$, + this._container.renderService.size$.pipe(debounceTime(250))) : + new Subject(); + }), map( ([camera, size]: [RenderCamera, ViewportSize]): PositionLookat => { return [ @@ -527,37 +537,43 @@ export class SliderComponent extends Component { subs.push(hasTexture$.subscribe(() => { /*noop*/ })); - const textureProviderPrev$ = this._navigator.stateService.currentState$.pipe( - filter(() => ViewerConfiguration.imageTiling), - filter( - (frame: AnimationFrame): boolean => { - return !!frame.state.previousImage; - }), - distinctUntilChanged( - undefined, - (frame: AnimationFrame): string => { - return frame.state.previousImage.id; - }), - withLatestFrom( - this._container.glRenderer.webGLRenderer$, - this._container.renderService.size$), - map( - ([frame, renderer, size]: [AnimationFrame, THREE.WebGLRenderer, ViewportSize]): TextureProvider => { - const state = frame.state; - const previousImage = state.previousImage; - const previousTransform = state.previousTransform; - - return new TextureProvider( - previousImage.id, - previousTransform.basicWidth, - previousTransform.basicHeight, - previousImage.image, - this._imageTileLoader, - new TileStore(), - renderer); - }), - publishReplay(1), - refCount()); + const textureProviderPrev$ = + this._container.configurationService.imageTiling$.pipe( + switchMap( + (active): Observable => { + return active ? + this._navigator.stateService.currentState$ : + new Subject(); + }), + filter( + (frame: AnimationFrame): boolean => { + return !!frame.state.previousImage; + }), + distinctUntilChanged( + undefined, + (frame: AnimationFrame): string => { + return frame.state.previousImage.id; + }), + withLatestFrom( + this._container.glRenderer.webGLRenderer$, + this._container.renderService.size$), + map( + ([frame, renderer, size]: [AnimationFrame, THREE.WebGLRenderer, ViewportSize]): TextureProvider => { + const state = frame.state; + const previousImage = state.previousImage; + const previousTransform = state.previousTransform; + + return new TextureProvider( + previousImage.id, + previousTransform.basicWidth, + previousTransform.basicHeight, + previousImage.image, + this._imageTileLoader, + new TileStore(), + renderer); + }), + publishReplay(1), + refCount()); subs.push(textureProviderPrev$.subscribe(() => { /*noop*/ })); @@ -580,10 +596,16 @@ export class SliderComponent extends Component { previous.abort(); })); - const roiTriggerPrev$ = observableCombineLatest( - this._container.renderService.renderCameraFrame$, - this._container.renderService.size$.pipe(debounceTime(250))).pipe( - filter(() => ViewerConfiguration.imageTiling), + const roiTriggerPrev$ = + this._container.configurationService.imageTiling$.pipe( + switchMap( + (active): Observable<[RenderCamera, ViewportSize]> => { + return active ? + observableCombineLatest( + this._container.renderService.renderCameraFrame$, + this._container.renderService.size$.pipe(debounceTime(250))) : + new Subject(); + }), map( ([camera, size]: [RenderCamera, ViewportSize]): PositionLookat => { return [ diff --git a/src/viewer/ConfigurationService.ts b/src/viewer/ConfigurationService.ts new file mode 100644 index 000000000..992fbc815 --- /dev/null +++ b/src/viewer/ConfigurationService.ts @@ -0,0 +1,29 @@ +import { + Observable, + of as observableOf, +} from "rxjs"; + +import { ViewerOptions } from "../external/viewer"; + +export class ConfigurationService { + private _imageTiling$: Observable; + private _exploreUrl$: Observable; + + constructor(options: ViewerOptions) { + const host = options?.url?.exploreHost ?? "www.mapillary.com"; + const scheme = options?.url?.scheme ?? "https"; + const exploreUrl = `${scheme}://${host}`; + this._exploreUrl$ = observableOf(exploreUrl); + + const imageTiling = options?.imageTiling === false ? false : true; + this._imageTiling$ = observableOf(imageTiling); + } + + public get exploreUrl$(): Observable { + return this._exploreUrl$; + } + + public get imageTiling$(): Observable { + return this._imageTiling$; + } +} diff --git a/src/viewer/Container.ts b/src/viewer/Container.ts index 854912464..98357f874 100644 --- a/src/viewer/Container.ts +++ b/src/viewer/Container.ts @@ -1,15 +1,16 @@ -import { KeyboardService } from "./KeyboardService"; -import { MouseService } from "./MouseService"; -import { SpriteService } from "./SpriteService"; -import { TouchService } from "./TouchService"; -import { ViewerOptions } from "./options/ViewerOptions"; - import { DOMRenderer } from "../render/DOMRenderer"; import { GLRenderer } from "../render/GLRenderer"; import { RenderService } from "../render/RenderService"; import { StateService } from "../state/StateService"; import { DOM } from "../util/DOM"; +import { ViewerOptions } from "./options/ViewerOptions"; +import { KeyboardService } from "./KeyboardService"; +import { MouseService } from "./MouseService"; +import { SpriteService } from "./SpriteService"; +import { TouchService } from "./TouchService"; +import { ConfigurationService } from "./ConfigurationService"; + export class Container { public id: string; @@ -24,6 +25,8 @@ export class Container { public spriteService: SpriteService; + public readonly configurationService: ConfigurationService; + private _canvasContainer: HTMLDivElement; private _canvas: HTMLCanvasElement; private _container: HTMLElement; @@ -87,6 +90,8 @@ export class Container { "mapillary-dom", this._container); + this.configurationService = new ConfigurationService(options); + this.renderService = new RenderService( this._container, @@ -166,7 +171,7 @@ export class Container { if (this._trackResize) { this.renderService.resize$.next(); } - } + }; private _removeNode(node: Node): void { if (node.parentNode) { diff --git a/src/viewer/PlayService.ts b/src/viewer/PlayService.ts index 5b31b7dfe..6cbbcd769 100644 --- a/src/viewer/PlayService.ts +++ b/src/viewer/PlayService.ts @@ -27,7 +27,6 @@ import { withLatestFrom, } from "rxjs/operators"; -import { GraphCalculator } from "../graph/GraphCalculator"; import { GraphMode } from "../graph/GraphMode"; import { GraphService } from "../graph/GraphService"; import { Image } from '../graph/Image'; diff --git a/src/viewer/Viewer.ts b/src/viewer/Viewer.ts index bddff03eb..154d83e16 100644 --- a/src/viewer/Viewer.ts +++ b/src/viewer/Viewer.ts @@ -19,7 +19,6 @@ import { RenderCamera } from "../render/RenderCamera"; import { RenderMode } from "../render/RenderMode"; import { TransitionMode } from "../state/TransitionMode"; import { EventEmitter } from "../util/EventEmitter"; -import { ViewerConfiguration } from "./ViewerConfiguration"; import { ICustomRenderer } from "./interfaces/ICustomRenderer"; import { PointOfView } from "./interfaces/PointOfView"; import { ViewerOptions } from "./options/ViewerOptions"; @@ -141,8 +140,6 @@ export class Viewer extends EventEmitter implements IViewer { constructor(options: ViewerOptions) { super(); - ViewerConfiguration.setOptions(options); - this._navigator = new Navigator(options); diff --git a/src/viewer/ViewerConfiguration.ts b/src/viewer/ViewerConfiguration.ts deleted file mode 100644 index d02601db8..000000000 --- a/src/viewer/ViewerConfiguration.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { ViewerOptions } from "./options/ViewerOptions"; - -export class ViewerConfiguration { - private static _exploreHost: string = "www.mapillary.com"; - private static _scheme: string = "https"; - private static _imageTiling: boolean = true; - - public static get explore(): string { - const scheme = ViewerConfiguration._scheme; - const host = ViewerConfiguration._exploreHost; - return `${scheme}://${host}`; - } - - public static get imageTiling(): boolean { - return ViewerConfiguration._imageTiling; - } - - public static exploreImage(id: string): string { - return `${ViewerConfiguration.explore}/app/?pKey=${id}&focus=photo`; - } - - public static exploreUser(username: string): string { - return `${ViewerConfiguration.explore}/app/user/${username}`; - } - - public static setOptions(options: ViewerOptions): void { - if (!options) { return; } - if (options.imageTiling === false) { - ViewerConfiguration._imageTiling = false; - } - - if (!options.url) { return; } - if (!!options.url.exploreHost) { - ViewerConfiguration._exploreHost = options.url.exploreHost; - } - if (!!options.url.scheme) { - ViewerConfiguration._scheme = options.url.scheme; - } - } -} diff --git a/styles/cover.css b/styles/cover.css index 73bfd1012..301ecf0c3 100644 --- a/styles/cover.css +++ b/styles/cover.css @@ -30,7 +30,7 @@ top: 0; z-index: 0; background-color: rgba(0, 0, 0, 0.2); - transition: opacity .1s ease-in-out; + transition: opacity 0.1s ease-in-out; cursor: pointer; } @@ -49,7 +49,7 @@ margin-left: -64px; margin-top: -64px; position: absolute; - transition: opacity .2s ease-in-out; + transition: opacity 0.2s ease-in-out; z-index: 1; pointer-events: none; } @@ -64,7 +64,7 @@ .mapillary-cover-button-icon { background-size: contain; background-repeat: no-repeat; - background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjgiIGhlaWdodD0iMTI4IiB2aWV3Qm94PSIwIDAgMTI4IDEyOCI+PGcgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIj4gIDxwYXRoIGZpbGw9IiNGRkYiIGQ9Ik01Ny43NDc4MjExLDU2LjkzMTc4NDUgTDQwLjkxNDUzODEsNjQuNjM0MjkyIEMzOC40NjQ1MzU5LDY1Ljc1NDY0MzIgMzQuNzg1NzMyMiw2NS43NTMzNDM1IDMyLjc2MzkxMDMsNjQuNjMyOTkyMyBDMzAuNzczNzU4Niw2My41Mjk1MzczIDMxLjE4ODAwNCw2MS43Nzg4MjYxIDMzLjYyNTMzODIsNjAuNzE5NTYxMyBMNjAuMjgzOTk3MSw0OS4xNTIxMzAyIEM2Mi4yODU1NTAxLDQ4LjI4MjYyMzMgNjUuNTAwNzAzLDQ4LjI4MjYyMzMgNjcuNTE2MTkwOCw0OS4xNTIxMzAyIEw5NC4zNDIwNjgyLDYwLjcyNjA1OTkgQzk2Ljc5NDYwNCw2MS43ODQwMjUgOTcuMjM0MTg1Niw2My41MzYwMzU5IDk1LjI2MDUwMjMsNjQuNjM5NDkwOCBDOTMuMjU1MTQ4OSw2NS43NTk4NDIgODkuNTczODExNSw2NS43NTk4NDIgODcuMTA4NjA3Nyw2NC42MzgxOTExIEw3MC42MjEzMjk1LDU3LjE0MDMxMjIgTDcxLjc5NzkzNTgsNzcuMzkyODQ3MiBDNzEuODk1MjAzMSw3OS4wODYzOTEyIDY4LjU0MjcyMzIsODAuNSA2NC4zNDU5NjMsODAuNSBDNjAuMTQ5MjAyOSw4MC41IDU2Ljc3NzI2OTUsNzkuMDg2MzkxMiA1Ni44NTExOTI2LDc3LjM5Mjg0NzIgTDU3Ljc0NzgyMTEsNTYuOTMxNzg0NSBaIi8+PC9nPiAgPC9zdmc+ICA='); + background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjgiIGhlaWdodD0iMTI4IiB2aWV3Qm94PSIwIDAgMTI4IDEyOCI+PGcgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIj4gIDxwYXRoIGZpbGw9IiNGRkYiIGQ9Ik01Ny43NDc4MjExLDU2LjkzMTc4NDUgTDQwLjkxNDUzODEsNjQuNjM0MjkyIEMzOC40NjQ1MzU5LDY1Ljc1NDY0MzIgMzQuNzg1NzMyMiw2NS43NTMzNDM1IDMyLjc2MzkxMDMsNjQuNjMyOTkyMyBDMzAuNzczNzU4Niw2My41Mjk1MzczIDMxLjE4ODAwNCw2MS43Nzg4MjYxIDMzLjYyNTMzODIsNjAuNzE5NTYxMyBMNjAuMjgzOTk3MSw0OS4xNTIxMzAyIEM2Mi4yODU1NTAxLDQ4LjI4MjYyMzMgNjUuNTAwNzAzLDQ4LjI4MjYyMzMgNjcuNTE2MTkwOCw0OS4xNTIxMzAyIEw5NC4zNDIwNjgyLDYwLjcyNjA1OTkgQzk2Ljc5NDYwNCw2MS43ODQwMjUgOTcuMjM0MTg1Niw2My41MzYwMzU5IDk1LjI2MDUwMjMsNjQuNjM5NDkwOCBDOTMuMjU1MTQ4OSw2NS43NTk4NDIgODkuNTczODExNSw2NS43NTk4NDIgODcuMTA4NjA3Nyw2NC42MzgxOTExIEw3MC42MjEzMjk1LDU3LjE0MDMxMjIgTDcxLjc5NzkzNTgsNzcuMzkyODQ3MiBDNzEuODk1MjAzMSw3OS4wODYzOTEyIDY4LjU0MjcyMzIsODAuNSA2NC4zNDU5NjMsODAuNSBDNjAuMTQ5MjAyOSw4MC41IDU2Ljc3NzI2OTUsNzkuMDg2MzkxMiA1Ni44NTExOTI2LDc3LjM5Mjg0NzIgTDU3Ljc0NzgyMTEsNTYuOTMxNzg0NSBaIi8+PC9nPiAgPC9zdmc+ICA="); height: 100%; width: 100%; position: absolute; @@ -79,16 +79,15 @@ .mapillary-cover-logo { background-size: contain; background-repeat: no-repeat; - background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNTciIGhlaWdodD0iNTIiIHZpZXdCb3g9IjAgMCAxNTcgNTIiPjxnIGZpbGw9Im5vbmUiIGZpbGwtcnVsZT0iZXZlbm9kZCI+ICA8cGF0aCBmaWxsPSIjMDAwIiBmaWxsLW9wYWNpdHk9Ii41IiBkPSJNODMuMjMwNCAxOS4wNjg2TDgzLjIzMDQgMjAuMjk1NkM4Mi44NDQ0IDE5LjkxNDYgODIuNDIzNCAxOS41ODI2IDgxLjk2MTQgMTkuMzQwNiA4MS4xMTI0IDE4Ljg5NTYgODAuMTkyNCAxOC42NzE2IDc5LjIyODQgMTguNjcxNiA3OC41MzQ0IDE4LjY3MTYgNzcuODY4NCAxOC43NzM2IDc3LjIzNjQgMTguOTgwNkw3NC4yMDI0IDE4Ljk4MDYgNjcuNDg3NCAxOC45Nzg2QzY3LjAyNzQgMTguODYzNiA2Ni41NDk0IDE4LjgwMzYgNjYuMDU2NCAxOC44MDM2IDY0LjIwMzQgMTguODAzNiA2Mi42OTU0IDE5LjM4MjYgNjEuNTQ0NCAyMC4zOTA2TDYxLjU0NDQgMTUuNDU3NiA1NC45MzA0IDE1LjQ1NzYgNTEuOTAwNCAyMi43MDE2IDQ4Ljg3MDQgMTUuNDU3NiA0Mi4yNTY0IDE1LjQ1NzYgNDIuMjU2NCAzNi4wMDY2IDYxLjU0NDQgMzYuMDA2NiA2MS41NDQ0IDM0Ljc1MTZDNjIuNjk1NCAzNS43NTg2IDY0LjIwMzQgMzYuMzM4NiA2Ni4wNTY0IDM2LjMzODYgNjYuNTIwNCAzNi4zMzg2IDY2Ljk3NDQgMzYuMjc3NiA2Ny40MTc0IDM2LjE2MjZMNzEuNDYwNCAzNi4xNjI2IDcxLjQ2MDQgNDEuNDg1NiA3OC42NDk0IDM5LjkxMzYgNzguNjQ5NCAzNi4yOTM2Qzc4Ljg1MjQgMzYuMzA5NiA3OS4wNjA0IDM2LjMxNzYgNzkuMjc1NCAzNi4zMTc2IDgwLjI0NzQgMzYuMzE3NiA4MS4xNjg0IDM2LjA5OTYgODIuMDEwNCAzNS42NjM2IDgyLjQ1MzQgMzUuNDM0NiA4Mi44NTk0IDM1LjE0NzYgODMuMjMwNCAzNC44MDk2TDgzLjIzMDQgMzYuMDA2NiA4OC4xMDg0IDM2LjAwNjYgOTAuNDQzNCAzNi4wMDY2IDkyLjk2MTQgMzYuMDA2NiA5NS4yOTY0IDM2LjAwNjYgMTAwLjE1MDQgMzYuMDA2NiAxMDAuMTUwNCAzNC43MjU2QzEwMS4zMDU0IDM1Ljc0OTYgMTAyLjgyMTQgMzYuMzM4NiAxMDQuNjkwNCAzNi4zMzg2IDEwNS4xNTQ0IDM2LjMzODYgMTA1LjYwODQgMzYuMjc3NiAxMDYuMDUxNCAzNi4xNjI2TDExMi44MzY0IDM2LjE2MjYgMTE3LjY0MTQgMzYuMTYxNiAxMTcuNjQxNCAyNi4wMzE2IDExOS41ODM0IDI3Ljc3NDYgMTIwLjA5NjQgMjYuNDc4NiAxMjIuMzYwNCAzMi44ODY2IDExOS42NzE0IDQwLjU5NzYgMTI3LjIzMTQgNDAuNTk3NiAxMzQuODM0NCAxOS4wNjg2IDEyNy4wOTM0IDE5LjA2ODYgMTI2LjE5MjQgMjEuOTI3NiAxMjUuMjkwNCAxOS4wNjg2IDExOS45NTk0IDE5LjA2ODZDMTE5Ljg5ODQgMTkuMDQ0NiAxMTkuODQwNCAxOS4wMTE2IDExOS43NzY0IDE4Ljk5MDYgMTE5LjIzNjQgMTguODExNiAxMTguNjc1NCAxOC42NzE2IDExOC4xMDI0IDE4LjY3MTYgMTE3LjQ0ODQgMTguNjcxNiAxMTYuODEwNCAxOC43NzM2IDExNi4xOTc0IDE4Ljk4MDZMMTEyLjgzNjQgMTguOTgwNiAxMDYuMTIxNCAxOC45Nzg2QzEwNS42NjE0IDE4Ljg2MzYgMTA1LjE4MzQgMTguODAzNiAxMDQuNjkwNCAxOC44MDM2IDEwMi44MjE0IDE4LjgwMzYgMTAxLjMwNTQgMTkuMzkyNiAxMDAuMTUwNCAyMC40MTY2TDEwMC4xNTA0IDEzLjc2NjYgOTUuMjk2NCAxNS40NDE2IDk1LjI5NjQgMTMuNzY2NiA4OS41MTg0IDE1Ljc1OTZDODkuNDc4NCAxNS43MTQ2IDg5LjQ0OTQgMTUuNjY0NiA4OS40MDc0IDE1LjYyMDYgODguNzE1NCAxNC44OTc2IDg3LjgxNzQgMTQuNTAxNiA4Ni44MjU0IDE0LjUwMTYgODUuNzkyNCAxNC41MDE2IDg0Ljg2MjQgMTQuOTA3NiA4NC4xODA0IDE1LjY3NTYgODMuNTU1NCAxNi4zODA2IDgzLjIzMDQgMTcuMjUyNiA4My4yMzA0IDE4LjE5ODZMODMuMjMwNCAxOS4wNjg2ek0zOS44ODkxIDM1LjM0NUMzOS44NTAxIDM1LjI3NiAzOS41MDExIDM0LjY0NCAzOC44MDIxIDMzLjM3NSAzOC44NTMxIDMzLjQ2OCAzNy4xMTExIDMwLjMwNyAzNi41NTgxIDI5LjMwNiAzNC4zNDkxIDI1LjMwMSAzMy4yOTIxIDIzLjM4OCAzMy4xNzYxIDIzLjE5NyAzMi45MzUxIDIyLjc5OSAzMi41OTUxIDIyLjQ4OSAzMi4yMDkxIDIyLjI1OCAzMi4yMzMxIDIxLjgyIDMyLjE2MTEgMjEuMzcgMzEuOTY0MSAyMC45NDEgMzEuODU1MSAyMC43MDIgMzEuMDg0MSAxOS4zMSAyOS4zMjQxIDE2LjE1MSAyOS4yNDIxIDE2LjAwMyAyNi44MDUxIDExLjYzMiAyNi43NzUxIDExLjU3NSAyNS42MzMxIDkuNDA4IDIyLjQ3NDEgOS40NDYgMjEuNjMzMSAxMS44NzlMMjEuMjE3MSAxMy4wODIgMjAuNDYyMSAxNS4yNjYgMTkuMjAxMSAxOC45MTVDMTUuNjQ5MSAyMC4wODkgMTIuNTg3MSAyMS4wOTMgMTIuMTM2MSAyMS4yMiA5LjcxMzEgMjEuOTAxIDkuMTY3MSAyNS4wODcgMTEuNDU5MSAyNi4zMTMgMTEuNjU1MSAyNi40MTggMjAuNjMzMSAzMS4xMSAyMS4xNjIxIDMxLjM4NSAyMS42MTgxIDMxLjYyMiAyMi4xMTIxIDMxLjcyMSAyMi41OTUxIDMxLjY5OSAyMi44NjAxIDMyLjEwNiAyMy4yMzIxIDMyLjQ1OSAyMy43MjQxIDMyLjcwNiAyNC4xMTgxIDMyLjkwNSAzNS42MzAxIDM4LjkwOCAzNi4xMDQxIDM5LjE2IDM4LjU2MjEgNDAuNDY4IDQxLjI5NDEgMzcuODI3IDM5Ljg4OTEgMzUuMzQ1Ii8+ICA8cGF0aCBmaWxsPSIjRkZGIiBkPSJNNTkuNTQ0NCAxNy40NTczTDU5LjU0NDQgMzQuMDA3MyA1Ni4zNTU0IDM0LjAwNzMgNTYuMzU1NCAyNS4zOTczIDUyLjc2ODQgMzQuMDA3MyA1MS4wMzM0IDM0LjAwNzMgNDcuNDQ1NCAyNS4zOTczIDQ3LjQ0NTQgMzQuMDA3MyA0NC4yNTY0IDM0LjAwNzMgNDQuMjU2NCAxNy40NTczIDQ3LjUzOTQgMTcuNDU3MyA1MS45MDA0IDI3Ljg4NDMgNTYuMjYxNCAxNy40NTczIDU5LjU0NDQgMTcuNDU3M3pNNzMuNDYxNCAyMS4wNjgzTDc2LjI1MTQgMjEuMDY4MyA3Ni4yNTE0IDIyLjE0NDNDNzYuNDM4NCAyMS43NjIzIDc2LjgxMzQgMjEuNDM2MyA3Ny4zNzY0IDIxLjE2NDMgNzcuOTM4NCAyMC44OTQzIDc4LjU1NjQgMjAuNzU4MyA3OS4yMjg0IDIwLjc1ODMgNzkuODY5NCAyMC43NTgzIDgwLjQ3MDQgMjAuOTA1MyA4MS4wMzQ0IDIxLjIwMDMgODEuNTk2NCAyMS40OTUzIDgyLjA4NTQgMjEuOTMwMyA4Mi40OTk0IDIyLjUwMzMgODIuOTEzNCAyMy4wNzczIDgzLjI0MjQgMjMuNzg3MyA4My40ODQ0IDI0LjYzMjMgODMuNzI2NCAyNS40NzczIDgzLjg0NzQgMjYuNDQxMyA4My44NDc0IDI3LjUyNTMgODMuODQ3NCAyOC42NDIzIDgzLjczMDQgMjkuNjIyMyA4My40OTY0IDMwLjQ2NzMgODMuMjYxNCAzMS4zMTIzIDgyLjk0MDQgMzIuMDIxMyA4Mi41MzQ0IDMyLjU5NjMgODIuMTI4NCAzMy4xNzAzIDgxLjY0NzQgMzMuNjAwMyA4MS4wOTI0IDMzLjg4NzMgODAuNTM4NCAzNC4xNzQzIDc5LjkzMTQgMzQuMzE3MyA3OS4yNzU0IDM0LjMxNzMgNzguNTcyNCAzNC4zMTczIDc4LjAwNDQgMzQuMjA2MyA3Ny41NzU0IDMzLjk4MjMgNzcuMTQ1NCAzMy43NjAzIDc2LjgzNjQgMzMuNDgwMyA3Ni42NDk0IDMzLjE0NTNMNzYuNjQ5NCAzOC4zMDMzIDczLjQ2MTQgMzkuMDAxMyA3My40NjE0IDIxLjA2ODN6TTc4LjQ3ODQgMzEuMjMyM0M4MC4wMjU0IDMxLjIzMjMgODAuNzk5NCAzMC4wMDUzIDgwLjc5OTQgMjcuNTUwMyA4MC43OTk0IDI2LjM1MzMgODAuNjA4NCAyNS40NDUzIDgwLjIyNDQgMjQuODIzMyA3OS44NDE0IDI0LjIwMTMgNzkuMjkxNCAyMy44OTEzIDc4LjU3MjQgMjMuODkxMyA3OC4wNDA0IDIzLjg5MTMgNzcuNTk4NCAyNC4wNzgzIDc3LjI0NzQgMjQuNDUyMyA3Ni44OTU0IDI0LjgyNzMgNzYuNjI2NCAyNS4yNjkzIDc2LjQzODQgMjUuNzc5M0w3Ni40Mzg0IDI5LjQ2MzNDNzYuNjU3NCAzMC4wNTMzIDc2Ljk1MDQgMzAuNDk1MyA3Ny4zMTc0IDMwLjc5MDMgNzcuNjg0NCAzMS4wODUzIDc4LjA3MTQgMzEuMjMyMyA3OC40Nzg0IDMxLjIzMjN6TTg1LjIzMDQgMzQuMDA3M0w4OC40NDM0IDM0LjAwNzMgODguNDQzNCAyMS4wNjkzIDg1LjIzMDQgMjEuMDY5MyA4NS4yMzA0IDM0LjAwNzN6TTg1LjIzMDQgMTguMTk4M0M4NS4yMzA0IDE3LjczNjMgODUuMzc5NCAxNy4zMzgzIDg1LjY3NjQgMTcuMDAzMyA4NS45NzM0IDE2LjY2ODMgODYuMzU2NCAxNi41MDEzIDg2LjgyNTQgMTYuNTAxMyA4Ny4yNjI0IDE2LjUwMTMgODcuNjQxNCAxNi42NjgzIDg3Ljk2MjQgMTcuMDAzMyA4OC4yODM0IDE3LjMzODMgODguNDQzNCAxNy43MzYzIDg4LjQ0MzQgMTguMTk4MyA4OC40NDM0IDE4LjY0NTMgODguMjgzNCAxOS4wMjMzIDg3Ljk2MjQgMTkuMzM1MyA4Ny42NDE0IDE5LjY0NTMgODcuMjYyNCAxOS44MDEzIDg2LjgyNTQgMTkuODAxMyA4Ni4zNTY0IDE5LjgwMTMgODUuOTczNCAxOS42NDUzIDg1LjY3NjQgMTkuMzM1MyA4NS4zNzk0IDE5LjAyMzMgODUuMjMwNCAxOC42NDUzIDg1LjIzMDQgMTguMTk4M3pNOTAuMTA3NCAxNy42NzMzTDkzLjI5NjQgMTYuNTcyMyA5My4yOTY0IDM0LjAwNzMgOTAuMTA3NCAzNC4wMDczIDkwLjEwNzQgMTcuNjczM3pNOTQuOTYxNCAxNy42NzMzTDk4LjE1MDQgMTYuNTcyMyA5OC4xNTA0IDM0LjAwNzMgOTQuOTYxNCAzNC4wMDczIDk0Ljk2MTQgMTcuNjczM3pNMTE1LjQ1MzQgMjEuMDY4M0wxMTUuNDUzNCAyMS45NTMzQzExNS42NzE0IDIxLjU4NzMgMTE2LjAzMjQgMjEuMjk2MyAxMTYuNTMyNCAyMS4wODAzIDExNy4wMzI0IDIwLjg2NTMgMTE3LjU1NTQgMjAuNzU4MyAxMTguMTAzNCAyMC43NTgzIDExOC40NjI0IDIwLjc1ODMgMTE4LjgxMDQgMjAuODEzMyAxMTkuMTQ2NCAyMC45MjUzIDExOS40ODI0IDIxLjAzNjMgMTE5Ljc0NDQgMjEuMjIwMyAxMTkuOTMxNCAyMS40NzUzTDExOC43ODM0IDI0LjM2OTNDMTE4LjM5MjQgMjQuMDE4MyAxMTcuOTYxNCAyMy44NDMzIDExNy40OTM0IDIzLjg0MzMgMTE3LjEwMjQgMjMuODQzMyAxMTYuNzQ3NCAyMy45OTAzIDExNi40MjY0IDI0LjI4NTMgMTE2LjEwNjQgMjQuNTgwMyAxMTUuODQ0NCAyNS4wMzgzIDExNS42NDE0IDI1LjY2MDNMMTE1LjY0MTQgMzQuMDA3MyAxMTIuNDUyNCAzNC4wMDczIDExMi40NTI0IDIxLjA2ODMgMTE1LjQ1MzQgMjEuMDY4M3pNMTA3LjU4NjQgMzMuMDYzM0MxMDYuNzM3NCAzMy44MTUzIDEwNS43NDc0IDM0LjMzODMgMTA0LjY5MDQgMzQuMzM4MyAxMDEuMDMxNCAzNC4zMzgzIDk5LjQ5ODQgMzEuMzA5MyA5OS40OTg0IDI3LjU3MDMgOTkuNDk4NCAyMy44MzMzIDEwMS4wMzE0IDIwLjgwNDMgMTA0LjY5MDQgMjAuODA0MyAxMDUuODI4NCAyMC44MDQzIDEwNi43OTU0IDIxLjI5MDMgMTA3LjU4NjQgMjIuMDIxM0wxMDcuNTg2NCAyMC45NzgzIDExMC44MzY0IDIwLjk3ODMgMTEwLjgzNjQgMzQuMTYyMyAxMDcuNTg2NCAzNC4xNjIzIDEwNy41ODY0IDMzLjA2MzN6TTEwNy41ODY0IDI5Ljc5NjNDMTA3LjE3OTQgMzAuNjk0MyAxMDYuNDYxNCAzMS4yNTYzIDEwNS40MjA0IDMxLjI3MzMgMTAzLjQ1NTQgMzEuMzA5MyAxMDIuNzAxNCAyOS42NTgzIDEwMi42NjU0IDI3LjYwNjMgMTAyLjYyOTQgMjUuNTU1MyAxMDMuMzI3NCAyMy44NzkzIDEwNS4yOTM0IDIzLjg0NDMgMTA2LjMwNjQgMjMuODI2MyAxMDcuMTI4NCAyNC40ODAzIDEwNy41ODY0IDI1LjUzNDNMMTA3LjU4NjQgMjkuNzk2M3pNNjguOTUzNCAzMy4wNjMzQzY4LjEwMzQgMzMuODE1MyA2Ny4xMTM0IDM0LjMzODMgNjYuMDU2NCAzNC4zMzgzIDYyLjM5NzQgMzQuMzM4MyA2MC44NjM0IDMxLjMwOTMgNjAuODYzNCAyNy41NzAzIDYwLjg2MzQgMjMuODMzMyA2Mi4zOTc0IDIwLjgwNDMgNjYuMDU2NCAyMC44MDQzIDY3LjE5NDQgMjAuODA0MyA2OC4xNjE0IDIxLjI5MDMgNjguOTUzNCAyMi4wMjEzTDY4Ljk1MzQgMjAuOTc4MyA3Mi4yMDI0IDIwLjk3ODMgNzIuMjAyNCAzNC4xNjIzIDY4Ljk1MzQgMzQuMTYyMyA2OC45NTM0IDMzLjA2MzN6TTY4Ljk1MzQgMjkuNzk2M0M2OC41NDU0IDMwLjY5NDMgNjcuODI2NCAzMS4yNTYzIDY2Ljc4NzQgMzEuMjczMyA2NC44MjE0IDMxLjMwOTMgNjQuMDY2NCAyOS42NTgzIDY0LjAzMTQgMjcuNjA2MyA2My45OTY0IDI1LjU1NTMgNjQuNjkyNCAyMy44NzkzIDY2LjY1ODQgMjMuODQ0MyA2Ny42NzI0IDIzLjgyNjMgNjguNDk0NCAyNC40ODAzIDY4Ljk1MzQgMjUuNTM0M0w2OC45NTM0IDI5Ljc5NjN6TTEyNS44MTY0IDM4LjU5OTNMMTIyLjQ4NzQgMzguNTk5MyAxMjQuNDgwNCAzMi44ODMzIDEyMC4zMDY0IDIxLjA2ODMgMTIzLjgyNDQgMjEuMDY4MyAxMjYuMTkyNCAyOC41NzgzIDEyOC41NjA0IDIxLjA2ODMgMTMyLjAwNjQgMjEuMDY4MyAxMjUuODE2NCAzOC41OTkzek0yNC42MjQgMzAuOTIwMUMyNC4xNDYgMzAuNjgwMSAyNC4wMyAzMC4yNjcxIDI0LjI4NiAyOS44MTgxIDI0LjQxNiAyOS41OTExIDI0LjY5NyAyOS4xMzcxIDI0LjkyNyAyOC43NzExIDI1LjI0MiAyOC4yNjcxIDI1LjcwMyAyOC4yNjYxIDI1Ljk2MiAyOC40MTUxIDI2LjIyMiAyOC41NjUxIDI5Ljg5NyAzMC40NzAxIDMwLjEzNSAzMC41OTkxIDMwLjY5NiAzMC45MDMxIDMxLjUyMyAzMC4yMDYxIDMxLjE1MiAyOS41OTUxIDMwLjk3NSAyOS4zMDMxIDI5LjQyNiAyNi41MTcxIDI4LjkzNCAyNS41OTkxIDI4LjcxIDI1LjE4MTEgMjguNzcxIDI0LjgwNTEgMjkuMjcgMjQuNTU1MSAyOS41MTggMjQuNDMyMSAyOS45NTkgMjQuMTczMSAzMC4zNDMgMjMuOTQzMSAzMC43MzQgMjMuNzA5MSAzMS4yNTUgMjMuODg2MSAzMS40NjcgMjQuMjM0MSAzMS42NzggMjQuNTgzMSAzNy45NDkgMzUuOTc2MSAzOC4xNDkgMzYuMzI5MSAzOC41NDYgMzcuMDMyMSAzNy43NzMgMzcuNzgzMSAzNy4wNDMgMzcuMzk1MSAzNi42NzggMzcuMjAwMSAyNS4xMDIgMzEuMTYwMSAyNC42MjQgMzAuOTIwMU0xMi40MDIgMjQuNTQ5MUMxMS43ODYgMjQuMjIwMSAxMS44OTkgMjMuMzYzMSAxMi42NzcgMjMuMTQ2MSAxMy4zMzUgMjIuOTYwMSAxOC42ODYgMjEuMTkxMSAyMC4zMDUgMjAuNjU2MSAyMC41OTcgMjAuNTYwMSAyMC44MjUgMjAuMzM3MSAyMC45MjMgMjAuMDU0MSAyMS40NzUgMTguNDU4MSAyMy4zMTYgMTMuMTMwMSAyMy41MjMgMTIuNTMyMSAyMy43NjYgMTEuODI5MSAyNC42NDYgMTEuODI2MSAyNS4wMDUgMTIuNTA4MSAyNS4xMzMgMTIuNzQ5MSAyOS45NzggMjEuNDA3MSAzMC4xNDYgMjEuNzc0MSAzMC4zMTUgMjIuMTQyMSAzMC4yMDYgMjIuNTQ5MSAyOS44MjQgMjIuNzgzMSAyOS40NDIgMjMuMDE4MSAyOC44MTkgMjMuMzY5MSAyOC42MDIgMjMuNTAyMSAyOC4yMDkgMjMuNzQyMSAyNy44MzIgMjMuNTc1MSAyNy42NTMgMjMuMjAyMSAyNy40NzQgMjIuODI5MSAyNi4wOTIgMjAuNDI0MSAyNS41MjggMTkuNDAyMSAyNS4yNDcgMTguODkyMSAyNC4zOTggMTguNjk1MSAyNC4xMTIgMTkuNTIxMSAyMy45MDYgMjAuMTE3MSAyMy40MjUgMjEuNTA5MSAyMy4xNzUgMjIuMjMyMSAyMy4wNzkgMjIuNTEwMSAyMi44MzYgMjIuNzQ4MSAyMi41NTMgMjIuODQxMSAyMS44NDkgMjMuMDcwMSAyMC41MTIgMjMuNTA4MSAxOS43MzEgMjMuNzYzMSAxOS4xNzQgMjMuOTQ0MSAxOC45MDMgMjQuNzg2MSAxOS42NTUgMjUuMTQxMSAxOS44MTIgMjUuMjE0MSAyMy4xOTkgMjcuMDA2MSAyMy41NDkgMjcuMTcwMSAyMy44OTggMjcuMzM0MSAyNC4wOTMgMjcuODA3MSAyMy44ODggMjguMTQzMSAyMy42MDkgMjguNjAwMSAyMy4yNTEgMjkuMTgwMSAyMy4xMjIgMjkuMzY4MSAyMi44OTcgMjkuNjk0MSAyMi40NTYgMjkuODAzMSAyMi4wODUgMjkuNjEwMSAyMS43MTQgMjkuNDE4MSAxMi42MjMgMjQuNjY3MSAxMi40MDIgMjQuNTQ5MSIvPjwvZz4gIDwvc3ZnPiAg'); + background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNTciIGhlaWdodD0iNTIiIHZpZXdCb3g9IjAgMCAxNTcgNTIiPjxnIGZpbGw9Im5vbmUiIGZpbGwtcnVsZT0iZXZlbm9kZCI+ICA8cGF0aCBmaWxsPSIjMDAwIiBmaWxsLW9wYWNpdHk9Ii41IiBkPSJNODMuMjMwNCAxOS4wNjg2TDgzLjIzMDQgMjAuMjk1NkM4Mi44NDQ0IDE5LjkxNDYgODIuNDIzNCAxOS41ODI2IDgxLjk2MTQgMTkuMzQwNiA4MS4xMTI0IDE4Ljg5NTYgODAuMTkyNCAxOC42NzE2IDc5LjIyODQgMTguNjcxNiA3OC41MzQ0IDE4LjY3MTYgNzcuODY4NCAxOC43NzM2IDc3LjIzNjQgMTguOTgwNkw3NC4yMDI0IDE4Ljk4MDYgNjcuNDg3NCAxOC45Nzg2QzY3LjAyNzQgMTguODYzNiA2Ni41NDk0IDE4LjgwMzYgNjYuMDU2NCAxOC44MDM2IDY0LjIwMzQgMTguODAzNiA2Mi42OTU0IDE5LjM4MjYgNjEuNTQ0NCAyMC4zOTA2TDYxLjU0NDQgMTUuNDU3NiA1NC45MzA0IDE1LjQ1NzYgNTEuOTAwNCAyMi43MDE2IDQ4Ljg3MDQgMTUuNDU3NiA0Mi4yNTY0IDE1LjQ1NzYgNDIuMjU2NCAzNi4wMDY2IDYxLjU0NDQgMzYuMDA2NiA2MS41NDQ0IDM0Ljc1MTZDNjIuNjk1NCAzNS43NTg2IDY0LjIwMzQgMzYuMzM4NiA2Ni4wNTY0IDM2LjMzODYgNjYuNTIwNCAzNi4zMzg2IDY2Ljk3NDQgMzYuMjc3NiA2Ny40MTc0IDM2LjE2MjZMNzEuNDYwNCAzNi4xNjI2IDcxLjQ2MDQgNDEuNDg1NiA3OC42NDk0IDM5LjkxMzYgNzguNjQ5NCAzNi4yOTM2Qzc4Ljg1MjQgMzYuMzA5NiA3OS4wNjA0IDM2LjMxNzYgNzkuMjc1NCAzNi4zMTc2IDgwLjI0NzQgMzYuMzE3NiA4MS4xNjg0IDM2LjA5OTYgODIuMDEwNCAzNS42NjM2IDgyLjQ1MzQgMzUuNDM0NiA4Mi44NTk0IDM1LjE0NzYgODMuMjMwNCAzNC44MDk2TDgzLjIzMDQgMzYuMDA2NiA4OC4xMDg0IDM2LjAwNjYgOTAuNDQzNCAzNi4wMDY2IDkyLjk2MTQgMzYuMDA2NiA5NS4yOTY0IDM2LjAwNjYgMTAwLjE1MDQgMzYuMDA2NiAxMDAuMTUwNCAzNC43MjU2QzEwMS4zMDU0IDM1Ljc0OTYgMTAyLjgyMTQgMzYuMzM4NiAxMDQuNjkwNCAzNi4zMzg2IDEwNS4xNTQ0IDM2LjMzODYgMTA1LjYwODQgMzYuMjc3NiAxMDYuMDUxNCAzNi4xNjI2TDExMi44MzY0IDM2LjE2MjYgMTE3LjY0MTQgMzYuMTYxNiAxMTcuNjQxNCAyNi4wMzE2IDExOS41ODM0IDI3Ljc3NDYgMTIwLjA5NjQgMjYuNDc4NiAxMjIuMzYwNCAzMi44ODY2IDExOS42NzE0IDQwLjU5NzYgMTI3LjIzMTQgNDAuNTk3NiAxMzQuODM0NCAxOS4wNjg2IDEyNy4wOTM0IDE5LjA2ODYgMTI2LjE5MjQgMjEuOTI3NiAxMjUuMjkwNCAxOS4wNjg2IDExOS45NTk0IDE5LjA2ODZDMTE5Ljg5ODQgMTkuMDQ0NiAxMTkuODQwNCAxOS4wMTE2IDExOS43NzY0IDE4Ljk5MDYgMTE5LjIzNjQgMTguODExNiAxMTguNjc1NCAxOC42NzE2IDExOC4xMDI0IDE4LjY3MTYgMTE3LjQ0ODQgMTguNjcxNiAxMTYuODEwNCAxOC43NzM2IDExNi4xOTc0IDE4Ljk4MDZMMTEyLjgzNjQgMTguOTgwNiAxMDYuMTIxNCAxOC45Nzg2QzEwNS42NjE0IDE4Ljg2MzYgMTA1LjE4MzQgMTguODAzNiAxMDQuNjkwNCAxOC44MDM2IDEwMi44MjE0IDE4LjgwMzYgMTAxLjMwNTQgMTkuMzkyNiAxMDAuMTUwNCAyMC40MTY2TDEwMC4xNTA0IDEzLjc2NjYgOTUuMjk2NCAxNS40NDE2IDk1LjI5NjQgMTMuNzY2NiA4OS41MTg0IDE1Ljc1OTZDODkuNDc4NCAxNS43MTQ2IDg5LjQ0OTQgMTUuNjY0NiA4OS40MDc0IDE1LjYyMDYgODguNzE1NCAxNC44OTc2IDg3LjgxNzQgMTQuNTAxNiA4Ni44MjU0IDE0LjUwMTYgODUuNzkyNCAxNC41MDE2IDg0Ljg2MjQgMTQuOTA3NiA4NC4xODA0IDE1LjY3NTYgODMuNTU1NCAxNi4zODA2IDgzLjIzMDQgMTcuMjUyNiA4My4yMzA0IDE4LjE5ODZMODMuMjMwNCAxOS4wNjg2ek0zOS44ODkxIDM1LjM0NUMzOS44NTAxIDM1LjI3NiAzOS41MDExIDM0LjY0NCAzOC44MDIxIDMzLjM3NSAzOC44NTMxIDMzLjQ2OCAzNy4xMTExIDMwLjMwNyAzNi41NTgxIDI5LjMwNiAzNC4zNDkxIDI1LjMwMSAzMy4yOTIxIDIzLjM4OCAzMy4xNzYxIDIzLjE5NyAzMi45MzUxIDIyLjc5OSAzMi41OTUxIDIyLjQ4OSAzMi4yMDkxIDIyLjI1OCAzMi4yMzMxIDIxLjgyIDMyLjE2MTEgMjEuMzcgMzEuOTY0MSAyMC45NDEgMzEuODU1MSAyMC43MDIgMzEuMDg0MSAxOS4zMSAyOS4zMjQxIDE2LjE1MSAyOS4yNDIxIDE2LjAwMyAyNi44MDUxIDExLjYzMiAyNi43NzUxIDExLjU3NSAyNS42MzMxIDkuNDA4IDIyLjQ3NDEgOS40NDYgMjEuNjMzMSAxMS44NzlMMjEuMjE3MSAxMy4wODIgMjAuNDYyMSAxNS4yNjYgMTkuMjAxMSAxOC45MTVDMTUuNjQ5MSAyMC4wODkgMTIuNTg3MSAyMS4wOTMgMTIuMTM2MSAyMS4yMiA5LjcxMzEgMjEuOTAxIDkuMTY3MSAyNS4wODcgMTEuNDU5MSAyNi4zMTMgMTEuNjU1MSAyNi40MTggMjAuNjMzMSAzMS4xMSAyMS4xNjIxIDMxLjM4NSAyMS42MTgxIDMxLjYyMiAyMi4xMTIxIDMxLjcyMSAyMi41OTUxIDMxLjY5OSAyMi44NjAxIDMyLjEwNiAyMy4yMzIxIDMyLjQ1OSAyMy43MjQxIDMyLjcwNiAyNC4xMTgxIDMyLjkwNSAzNS42MzAxIDM4LjkwOCAzNi4xMDQxIDM5LjE2IDM4LjU2MjEgNDAuNDY4IDQxLjI5NDEgMzcuODI3IDM5Ljg4OTEgMzUuMzQ1Ii8+ICA8cGF0aCBmaWxsPSIjRkZGIiBkPSJNNTkuNTQ0NCAxNy40NTczTDU5LjU0NDQgMzQuMDA3MyA1Ni4zNTU0IDM0LjAwNzMgNTYuMzU1NCAyNS4zOTczIDUyLjc2ODQgMzQuMDA3MyA1MS4wMzM0IDM0LjAwNzMgNDcuNDQ1NCAyNS4zOTczIDQ3LjQ0NTQgMzQuMDA3MyA0NC4yNTY0IDM0LjAwNzMgNDQuMjU2NCAxNy40NTczIDQ3LjUzOTQgMTcuNDU3MyA1MS45MDA0IDI3Ljg4NDMgNTYuMjYxNCAxNy40NTczIDU5LjU0NDQgMTcuNDU3M3pNNzMuNDYxNCAyMS4wNjgzTDc2LjI1MTQgMjEuMDY4MyA3Ni4yNTE0IDIyLjE0NDNDNzYuNDM4NCAyMS43NjIzIDc2LjgxMzQgMjEuNDM2MyA3Ny4zNzY0IDIxLjE2NDMgNzcuOTM4NCAyMC44OTQzIDc4LjU1NjQgMjAuNzU4MyA3OS4yMjg0IDIwLjc1ODMgNzkuODY5NCAyMC43NTgzIDgwLjQ3MDQgMjAuOTA1MyA4MS4wMzQ0IDIxLjIwMDMgODEuNTk2NCAyMS40OTUzIDgyLjA4NTQgMjEuOTMwMyA4Mi40OTk0IDIyLjUwMzMgODIuOTEzNCAyMy4wNzczIDgzLjI0MjQgMjMuNzg3MyA4My40ODQ0IDI0LjYzMjMgODMuNzI2NCAyNS40NzczIDgzLjg0NzQgMjYuNDQxMyA4My44NDc0IDI3LjUyNTMgODMuODQ3NCAyOC42NDIzIDgzLjczMDQgMjkuNjIyMyA4My40OTY0IDMwLjQ2NzMgODMuMjYxNCAzMS4zMTIzIDgyLjk0MDQgMzIuMDIxMyA4Mi41MzQ0IDMyLjU5NjMgODIuMTI4NCAzMy4xNzAzIDgxLjY0NzQgMzMuNjAwMyA4MS4wOTI0IDMzLjg4NzMgODAuNTM4NCAzNC4xNzQzIDc5LjkzMTQgMzQuMzE3MyA3OS4yNzU0IDM0LjMxNzMgNzguNTcyNCAzNC4zMTczIDc4LjAwNDQgMzQuMjA2MyA3Ny41NzU0IDMzLjk4MjMgNzcuMTQ1NCAzMy43NjAzIDc2LjgzNjQgMzMuNDgwMyA3Ni42NDk0IDMzLjE0NTNMNzYuNjQ5NCAzOC4zMDMzIDczLjQ2MTQgMzkuMDAxMyA3My40NjE0IDIxLjA2ODN6TTc4LjQ3ODQgMzEuMjMyM0M4MC4wMjU0IDMxLjIzMjMgODAuNzk5NCAzMC4wMDUzIDgwLjc5OTQgMjcuNTUwMyA4MC43OTk0IDI2LjM1MzMgODAuNjA4NCAyNS40NDUzIDgwLjIyNDQgMjQuODIzMyA3OS44NDE0IDI0LjIwMTMgNzkuMjkxNCAyMy44OTEzIDc4LjU3MjQgMjMuODkxMyA3OC4wNDA0IDIzLjg5MTMgNzcuNTk4NCAyNC4wNzgzIDc3LjI0NzQgMjQuNDUyMyA3Ni44OTU0IDI0LjgyNzMgNzYuNjI2NCAyNS4yNjkzIDc2LjQzODQgMjUuNzc5M0w3Ni40Mzg0IDI5LjQ2MzNDNzYuNjU3NCAzMC4wNTMzIDc2Ljk1MDQgMzAuNDk1MyA3Ny4zMTc0IDMwLjc5MDMgNzcuNjg0NCAzMS4wODUzIDc4LjA3MTQgMzEuMjMyMyA3OC40Nzg0IDMxLjIzMjN6TTg1LjIzMDQgMzQuMDA3M0w4OC40NDM0IDM0LjAwNzMgODguNDQzNCAyMS4wNjkzIDg1LjIzMDQgMjEuMDY5MyA4NS4yMzA0IDM0LjAwNzN6TTg1LjIzMDQgMTguMTk4M0M4NS4yMzA0IDE3LjczNjMgODUuMzc5NCAxNy4zMzgzIDg1LjY3NjQgMTcuMDAzMyA4NS45NzM0IDE2LjY2ODMgODYuMzU2NCAxNi41MDEzIDg2LjgyNTQgMTYuNTAxMyA4Ny4yNjI0IDE2LjUwMTMgODcuNjQxNCAxNi42NjgzIDg3Ljk2MjQgMTcuMDAzMyA4OC4yODM0IDE3LjMzODMgODguNDQzNCAxNy43MzYzIDg4LjQ0MzQgMTguMTk4MyA4OC40NDM0IDE4LjY0NTMgODguMjgzNCAxOS4wMjMzIDg3Ljk2MjQgMTkuMzM1MyA4Ny42NDE0IDE5LjY0NTMgODcuMjYyNCAxOS44MDEzIDg2LjgyNTQgMTkuODAxMyA4Ni4zNTY0IDE5LjgwMTMgODUuOTczNCAxOS42NDUzIDg1LjY3NjQgMTkuMzM1MyA4NS4zNzk0IDE5LjAyMzMgODUuMjMwNCAxOC42NDUzIDg1LjIzMDQgMTguMTk4M3pNOTAuMTA3NCAxNy42NzMzTDkzLjI5NjQgMTYuNTcyMyA5My4yOTY0IDM0LjAwNzMgOTAuMTA3NCAzNC4wMDczIDkwLjEwNzQgMTcuNjczM3pNOTQuOTYxNCAxNy42NzMzTDk4LjE1MDQgMTYuNTcyMyA5OC4xNTA0IDM0LjAwNzMgOTQuOTYxNCAzNC4wMDczIDk0Ljk2MTQgMTcuNjczM3pNMTE1LjQ1MzQgMjEuMDY4M0wxMTUuNDUzNCAyMS45NTMzQzExNS42NzE0IDIxLjU4NzMgMTE2LjAzMjQgMjEuMjk2MyAxMTYuNTMyNCAyMS4wODAzIDExNy4wMzI0IDIwLjg2NTMgMTE3LjU1NTQgMjAuNzU4MyAxMTguMTAzNCAyMC43NTgzIDExOC40NjI0IDIwLjc1ODMgMTE4LjgxMDQgMjAuODEzMyAxMTkuMTQ2NCAyMC45MjUzIDExOS40ODI0IDIxLjAzNjMgMTE5Ljc0NDQgMjEuMjIwMyAxMTkuOTMxNCAyMS40NzUzTDExOC43ODM0IDI0LjM2OTNDMTE4LjM5MjQgMjQuMDE4MyAxMTcuOTYxNCAyMy44NDMzIDExNy40OTM0IDIzLjg0MzMgMTE3LjEwMjQgMjMuODQzMyAxMTYuNzQ3NCAyMy45OTAzIDExNi40MjY0IDI0LjI4NTMgMTE2LjEwNjQgMjQuNTgwMyAxMTUuODQ0NCAyNS4wMzgzIDExNS42NDE0IDI1LjY2MDNMMTE1LjY0MTQgMzQuMDA3MyAxMTIuNDUyNCAzNC4wMDczIDExMi40NTI0IDIxLjA2ODMgMTE1LjQ1MzQgMjEuMDY4M3pNMTA3LjU4NjQgMzMuMDYzM0MxMDYuNzM3NCAzMy44MTUzIDEwNS43NDc0IDM0LjMzODMgMTA0LjY5MDQgMzQuMzM4MyAxMDEuMDMxNCAzNC4zMzgzIDk5LjQ5ODQgMzEuMzA5MyA5OS40OTg0IDI3LjU3MDMgOTkuNDk4NCAyMy44MzMzIDEwMS4wMzE0IDIwLjgwNDMgMTA0LjY5MDQgMjAuODA0MyAxMDUuODI4NCAyMC44MDQzIDEwNi43OTU0IDIxLjI5MDMgMTA3LjU4NjQgMjIuMDIxM0wxMDcuNTg2NCAyMC45NzgzIDExMC44MzY0IDIwLjk3ODMgMTEwLjgzNjQgMzQuMTYyMyAxMDcuNTg2NCAzNC4xNjIzIDEwNy41ODY0IDMzLjA2MzN6TTEwNy41ODY0IDI5Ljc5NjNDMTA3LjE3OTQgMzAuNjk0MyAxMDYuNDYxNCAzMS4yNTYzIDEwNS40MjA0IDMxLjI3MzMgMTAzLjQ1NTQgMzEuMzA5MyAxMDIuNzAxNCAyOS42NTgzIDEwMi42NjU0IDI3LjYwNjMgMTAyLjYyOTQgMjUuNTU1MyAxMDMuMzI3NCAyMy44NzkzIDEwNS4yOTM0IDIzLjg0NDMgMTA2LjMwNjQgMjMuODI2MyAxMDcuMTI4NCAyNC40ODAzIDEwNy41ODY0IDI1LjUzNDNMMTA3LjU4NjQgMjkuNzk2M3pNNjguOTUzNCAzMy4wNjMzQzY4LjEwMzQgMzMuODE1MyA2Ny4xMTM0IDM0LjMzODMgNjYuMDU2NCAzNC4zMzgzIDYyLjM5NzQgMzQuMzM4MyA2MC44NjM0IDMxLjMwOTMgNjAuODYzNCAyNy41NzAzIDYwLjg2MzQgMjMuODMzMyA2Mi4zOTc0IDIwLjgwNDMgNjYuMDU2NCAyMC44MDQzIDY3LjE5NDQgMjAuODA0MyA2OC4xNjE0IDIxLjI5MDMgNjguOTUzNCAyMi4wMjEzTDY4Ljk1MzQgMjAuOTc4MyA3Mi4yMDI0IDIwLjk3ODMgNzIuMjAyNCAzNC4xNjIzIDY4Ljk1MzQgMzQuMTYyMyA2OC45NTM0IDMzLjA2MzN6TTY4Ljk1MzQgMjkuNzk2M0M2OC41NDU0IDMwLjY5NDMgNjcuODI2NCAzMS4yNTYzIDY2Ljc4NzQgMzEuMjczMyA2NC44MjE0IDMxLjMwOTMgNjQuMDY2NCAyOS42NTgzIDY0LjAzMTQgMjcuNjA2MyA2My45OTY0IDI1LjU1NTMgNjQuNjkyNCAyMy44NzkzIDY2LjY1ODQgMjMuODQ0MyA2Ny42NzI0IDIzLjgyNjMgNjguNDk0NCAyNC40ODAzIDY4Ljk1MzQgMjUuNTM0M0w2OC45NTM0IDI5Ljc5NjN6TTEyNS44MTY0IDM4LjU5OTNMMTIyLjQ4NzQgMzguNTk5MyAxMjQuNDgwNCAzMi44ODMzIDEyMC4zMDY0IDIxLjA2ODMgMTIzLjgyNDQgMjEuMDY4MyAxMjYuMTkyNCAyOC41NzgzIDEyOC41NjA0IDIxLjA2ODMgMTMyLjAwNjQgMjEuMDY4MyAxMjUuODE2NCAzOC41OTkzek0yNC42MjQgMzAuOTIwMUMyNC4xNDYgMzAuNjgwMSAyNC4wMyAzMC4yNjcxIDI0LjI4NiAyOS44MTgxIDI0LjQxNiAyOS41OTExIDI0LjY5NyAyOS4xMzcxIDI0LjkyNyAyOC43NzExIDI1LjI0MiAyOC4yNjcxIDI1LjcwMyAyOC4yNjYxIDI1Ljk2MiAyOC40MTUxIDI2LjIyMiAyOC41NjUxIDI5Ljg5NyAzMC40NzAxIDMwLjEzNSAzMC41OTkxIDMwLjY5NiAzMC45MDMxIDMxLjUyMyAzMC4yMDYxIDMxLjE1MiAyOS41OTUxIDMwLjk3NSAyOS4zMDMxIDI5LjQyNiAyNi41MTcxIDI4LjkzNCAyNS41OTkxIDI4LjcxIDI1LjE4MTEgMjguNzcxIDI0LjgwNTEgMjkuMjcgMjQuNTU1MSAyOS41MTggMjQuNDMyMSAyOS45NTkgMjQuMTczMSAzMC4zNDMgMjMuOTQzMSAzMC43MzQgMjMuNzA5MSAzMS4yNTUgMjMuODg2MSAzMS40NjcgMjQuMjM0MSAzMS42NzggMjQuNTgzMSAzNy45NDkgMzUuOTc2MSAzOC4xNDkgMzYuMzI5MSAzOC41NDYgMzcuMDMyMSAzNy43NzMgMzcuNzgzMSAzNy4wNDMgMzcuMzk1MSAzNi42NzggMzcuMjAwMSAyNS4xMDIgMzEuMTYwMSAyNC42MjQgMzAuOTIwMU0xMi40MDIgMjQuNTQ5MUMxMS43ODYgMjQuMjIwMSAxMS44OTkgMjMuMzYzMSAxMi42NzcgMjMuMTQ2MSAxMy4zMzUgMjIuOTYwMSAxOC42ODYgMjEuMTkxMSAyMC4zMDUgMjAuNjU2MSAyMC41OTcgMjAuNTYwMSAyMC44MjUgMjAuMzM3MSAyMC45MjMgMjAuMDU0MSAyMS40NzUgMTguNDU4MSAyMy4zMTYgMTMuMTMwMSAyMy41MjMgMTIuNTMyMSAyMy43NjYgMTEuODI5MSAyNC42NDYgMTEuODI2MSAyNS4wMDUgMTIuNTA4MSAyNS4xMzMgMTIuNzQ5MSAyOS45NzggMjEuNDA3MSAzMC4xNDYgMjEuNzc0MSAzMC4zMTUgMjIuMTQyMSAzMC4yMDYgMjIuNTQ5MSAyOS44MjQgMjIuNzgzMSAyOS40NDIgMjMuMDE4MSAyOC44MTkgMjMuMzY5MSAyOC42MDIgMjMuNTAyMSAyOC4yMDkgMjMuNzQyMSAyNy44MzIgMjMuNTc1MSAyNy42NTMgMjMuMjAyMSAyNy40NzQgMjIuODI5MSAyNi4wOTIgMjAuNDI0MSAyNS41MjggMTkuNDAyMSAyNS4yNDcgMTguODkyMSAyNC4zOTggMTguNjk1MSAyNC4xMTIgMTkuNTIxMSAyMy45MDYgMjAuMTE3MSAyMy40MjUgMjEuNTA5MSAyMy4xNzUgMjIuMjMyMSAyMy4wNzkgMjIuNTEwMSAyMi44MzYgMjIuNzQ4MSAyMi41NTMgMjIuODQxMSAyMS44NDkgMjMuMDcwMSAyMC41MTIgMjMuNTA4MSAxOS43MzEgMjMuNzYzMSAxOS4xNzQgMjMuOTQ0MSAxOC45MDMgMjQuNzg2MSAxOS42NTUgMjUuMTQxMSAxOS44MTIgMjUuMjE0MSAyMy4xOTkgMjcuMDA2MSAyMy41NDkgMjcuMTcwMSAyMy44OTggMjcuMzM0MSAyNC4wOTMgMjcuODA3MSAyMy44ODggMjguMTQzMSAyMy42MDkgMjguNjAwMSAyMy4yNTEgMjkuMTgwMSAyMy4xMjIgMjkuMzY4MSAyMi44OTcgMjkuNjk0MSAyMi40NTYgMjkuODAzMSAyMi4wODUgMjkuNjEwMSAyMS43MTQgMjkuNDE4MSAxMi42MjMgMjQuNjY3MSAxMi40MDIgMjQuNTQ5MSIvPjwvZz4gIDwvc3ZnPiAg"); bottom: 0px; height: 52px; left: 50%; margin-left: -73px; position: absolute; - transition: opacity .2s ease-in-out; + transition: opacity 0.2s ease-in-out; width: 157px; z-index: 1; - pointer-events: none; } .mapillary-cover-compact .mapillary-cover-logo { @@ -111,7 +110,7 @@ animation: rotate 1s linear infinite; background-repeat: no-repeat; background-size: 100%; - background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI4NCIgaGVpZ2h0PSI4NCIgdmlld0JveD0iMCAwIDg0IDg0Ij48ZyBmaWxsPSJub25lIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiPiAgPHBhdGggZD0iTTQyLDg0IEM2NS4xOTYsODQgODQsNjUuMTk2IDg0LDQyIEM4NCwxOC44MDQgNjUuMTk2LDAgNDIsMCBDMTguODA0LDAgMCwxOC44MDQgMCw0MiBDMCw2NS4xOTYgMTguODA0LDg0IDQyLDg0Ii8+ICA8cGF0aCBmaWxsPSIjRkZGIiBmaWxsLXJ1bGU9Im5vbnplcm8iIGQ9Ik00Miw4My45OTg1MTA0IEM0MC4zNDMxNDU4LDgzLjk5ODUxMDQgMzksODIuNjU1MzY0NiAzOSw4MC45OTg1MTA0IEMzOSw3OS4zNDE2NTYxIDQwLjM0MzE0NTgsNzcuOTk4NTEwNCA0Miw3Ny45OTg1MTA0IEM2MS44ODIyODg2LDc3Ljk5ODUxMDQgNzgsNjEuODgwNzk5IDc4LDQxLjk5ODUxMDQgQzc4LDIyLjIzNDkyOTQgNjIuMDY2MzMxNiw2LjE3MDg3ODA2IDQyLjMxODc2ODIsNS45OTk4ODk5NCBDNDAuNjYxOTc2LDUuOTg1NTQ0MjggMzkuMzMwNTEwMSw0LjYzMDgxOTQyIDM5LjM0NDg1NTgsMi45NzQwMjcyOCBDMzkuMzU5MjAxNCwxLjMxNzIzNTE0IDQwLjcxMzkyNjMsLTAuMDE0MjMwODE0NiA0Mi4zNzA3MTg0LDAuMDAwMTE0ODQyOTUyIEM2NS40MTA5MTU3LDAuMTk5NjEyODggODQsMTguOTQwODA5MSA4NCw0MS45OTg1MTA0IEM4NCw2NS4xOTQ1MDc1IDY1LjE5NTk5NzEsODMuOTk4NTEwNCA0Miw4My45OTg1MTA0IFoiLz48L2c+ICA8L3N2Zz4gIA=='); + background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI4NCIgaGVpZ2h0PSI4NCIgdmlld0JveD0iMCAwIDg0IDg0Ij48ZyBmaWxsPSJub25lIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiPiAgPHBhdGggZD0iTTQyLDg0IEM2NS4xOTYsODQgODQsNjUuMTk2IDg0LDQyIEM4NCwxOC44MDQgNjUuMTk2LDAgNDIsMCBDMTguODA0LDAgMCwxOC44MDQgMCw0MiBDMCw2NS4xOTYgMTguODA0LDg0IDQyLDg0Ii8+ICA8cGF0aCBmaWxsPSIjRkZGIiBmaWxsLXJ1bGU9Im5vbnplcm8iIGQ9Ik00Miw4My45OTg1MTA0IEM0MC4zNDMxNDU4LDgzLjk5ODUxMDQgMzksODIuNjU1MzY0NiAzOSw4MC45OTg1MTA0IEMzOSw3OS4zNDE2NTYxIDQwLjM0MzE0NTgsNzcuOTk4NTEwNCA0Miw3Ny45OTg1MTA0IEM2MS44ODIyODg2LDc3Ljk5ODUxMDQgNzgsNjEuODgwNzk5IDc4LDQxLjk5ODUxMDQgQzc4LDIyLjIzNDkyOTQgNjIuMDY2MzMxNiw2LjE3MDg3ODA2IDQyLjMxODc2ODIsNS45OTk4ODk5NCBDNDAuNjYxOTc2LDUuOTg1NTQ0MjggMzkuMzMwNTEwMSw0LjYzMDgxOTQyIDM5LjM0NDg1NTgsMi45NzQwMjcyOCBDMzkuMzU5MjAxNCwxLjMxNzIzNTE0IDQwLjcxMzkyNjMsLTAuMDE0MjMwODE0NiA0Mi4zNzA3MTg0LDAuMDAwMTE0ODQyOTUyIEM2NS40MTA5MTU3LDAuMTk5NjEyODggODQsMTguOTQwODA5MSA4NCw0MS45OTg1MTA0IEM4NCw2NS4xOTQ1MDc1IDY1LjE5NTk5NzEsODMuOTk4NTEwNCA0Miw4My45OTg1MTA0IFoiLz48L2c+ICA8L3N2Zz4gIA=="); height: 32px; left: 50%; margin-left: -16px; @@ -123,7 +122,7 @@ } @keyframes rotate { - 100% { - transform: rotate(360deg); - } + 100% { + transform: rotate(360deg); + } } diff --git a/test/helper/ConfigurationServiceMockCreator.ts b/test/helper/ConfigurationServiceMockCreator.ts new file mode 100644 index 000000000..619cd9e5e --- /dev/null +++ b/test/helper/ConfigurationServiceMockCreator.ts @@ -0,0 +1,26 @@ +import { Subject } from "rxjs"; + +import { MockCreator } from "./MockCreator"; +import { MockCreatorBase } from "./MockCreatorBase"; + +import { ConfigurationService } from "../../src/viewer/ConfigurationService"; + +export class ConfigurationServiceMockCreator extends + MockCreatorBase { + public create(): ConfigurationService { + const mock: ConfigurationService = + new MockCreator() + .create(ConfigurationService, "ConfigurationService"); + + this._mockProperty( + mock, + "exploreUrl$", + new Subject()); + this._mockProperty( + mock, + "imageTiling$", + new Subject()); + + return mock; + } +} diff --git a/test/helper/ContainerMockCreator.ts b/test/helper/ContainerMockCreator.ts index 9ecd9ab0d..6e90151c6 100644 --- a/test/helper/ContainerMockCreator.ts +++ b/test/helper/ContainerMockCreator.ts @@ -1,4 +1,6 @@ import { Container } from "../../src/viewer/Container"; +import { ConfigurationServiceMockCreator } + from "./ConfigurationServiceMockCreator"; import { DOMRendererMockCreator } from "./DOMRendererMockCreator"; import { GLRendererMockCreator } from "./GLRendererMockCreator"; import { KeyboardServiceMockCreator } from "./KeyboardServiceMockCreator"; @@ -14,6 +16,10 @@ export class ContainerMockCreator extends MockCreatorBase { const mock: Container = new MockCreator().create(Container, "Container"); this._mockProperty(mock, "canvasContainer", document.createElement("canvas")); + this._mockProperty( + mock, + "configurationService", + new ConfigurationServiceMockCreator().create()); this._mockProperty(mock, "domRenderer", new DOMRendererMockCreator().create()); this._mockProperty(mock, "container", document.createElement("div")); this._mockProperty(mock, "glRenderer", new GLRendererMockCreator().create()); diff --git a/test/util/ViewerConfiguration.test.ts b/test/util/ViewerConfiguration.test.ts deleted file mode 100644 index ff229100a..000000000 --- a/test/util/ViewerConfiguration.test.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { ViewerConfiguration } from "../../src/viewer/ViewerConfiguration"; -import { ViewerOptions } from "../../src/viewer/options/ViewerOptions"; - -describe("Urls.setOptions", () => { - it("should set all option properties", () => { - const options: ViewerOptions = { - container: "container-id", - imageTiling: false, - url: { - exploreHost: "test-explore", - scheme: "test-scheme", - }, - }; - - expect(ViewerConfiguration.imageTiling).toBe(true); - expect(ViewerConfiguration.explore).toBe("https://www.mapillary.com"); - - ViewerConfiguration.setOptions(options); - - expect(ViewerConfiguration.imageTiling).toBe(false); - expect(ViewerConfiguration.explore).not.toContain("https"); - expect(ViewerConfiguration.explore).not.toContain("mapillary"); - expect(ViewerConfiguration.explore).toContain(options.url.scheme); - expect(ViewerConfiguration.explore).toContain(options.url.exploreHost); - }); -}); diff --git a/test/viewer/ConfigurationService.test.ts b/test/viewer/ConfigurationService.test.ts new file mode 100644 index 000000000..349f07549 --- /dev/null +++ b/test/viewer/ConfigurationService.test.ts @@ -0,0 +1,89 @@ +import { ConfigurationService } from "../../src/viewer/ConfigurationService"; +import { ViewerOptions } from "../../src/viewer/options/ViewerOptions"; + +describe("ConfigurationService.ctor", () => { + it("should set all option properties", () => { + const options: ViewerOptions = { container: "container-id" }; + const service = new ConfigurationService(options); + + expect(service).toBeDefined(); + + }); +}); + +describe("ConfigurationService.exploreHost$", () => { + it("should emit default URL on each subscription", () => { + const options: ViewerOptions = { container: "container-id" }; + + const service = new ConfigurationService(options); + + let count = 0; + service.exploreUrl$.subscribe( + url => { + count++; + expect(url).toBe("https://www.mapillary.com"); + }); + service.exploreUrl$.subscribe( + url => { + count++; + expect(url).toBe("https://www.mapillary.com"); + }); + + expect(count).toBe(2); + }); + + it("should emit configured URL", (done: Function) => { + const options: ViewerOptions = { + container: "container-id", + url: { + exploreHost: "test-explore", + scheme: "test-scheme", + }, + }; + + const service = new ConfigurationService(options); + + service.exploreUrl$.subscribe( + url => { + expect(url).toBe("test-scheme://test-explore"); + done(); + }); + }); +}); + +describe("ConfigurationService.imageTiling$", () => { + it("should emit default value on each subscription", () => { + const options: ViewerOptions = { container: "container-id" }; + + const service = new ConfigurationService(options); + + let count = 0; + service.imageTiling$.subscribe( + active => { + count++; + expect(active).toBe(true); + }); + service.imageTiling$.subscribe( + active => { + count++; + expect(active).toBe(true); + }); + + expect(count).toBe(2); + }); + + it("should emit configured value", (done: Function) => { + const options: ViewerOptions = { + container: "container-id", + imageTiling: false, + }; + + const service = new ConfigurationService(options); + + service.imageTiling$.subscribe( + active => { + expect(active).toBe(false); + done(); + }); + }); +});