diff --git a/web/src/audio/hatnote_audio.ts b/web/src/audio/hatnote_audio.ts
index 2a8769192b1076245440f5a3810d5dcd270d263f..d478cb10bd09f87987dc0c74992976a5a6483f9c 100644
--- a/web/src/audio/hatnote_audio.ts
+++ b/web/src/audio/hatnote_audio.ts
@@ -12,11 +12,9 @@ export class HatnoteAudio {
     private readonly swells: Howl[] = []
     private readonly harp: Howl[] = []
     private lastAudioPlayed: number = 0
-    private readonly showAudioInfoboxSubject: Subject<boolean>
     private readonly settings_data: SettingsData
 
-    constructor(settings_data: SettingsData, showAudioInfoboxSubject: Subject<boolean> ) {
-        this.showAudioInfoboxSubject = showAudioInfoboxSubject
+    constructor(settings_data: SettingsData ) {
         this.settings_data = settings_data
 
         let loaded_sounds = 0
@@ -47,10 +45,7 @@ export class HatnoteAudio {
                         console.log("Number of audio playing (subtract): " + thisAudio.current_notes)
                     }
                 },
-                onload: sound_load,
-                onunlock: function() {
-                    thisAudio.showAudioInfoboxSubject.next(false)
-                },
+                onload: sound_load
             }))
 
             this.clav.push(new Howl({
@@ -63,10 +58,7 @@ export class HatnoteAudio {
                         console.log("Number of audio playing (subtract): " + thisAudio.current_notes)
                     }
                 },
-                onload: sound_load,
-                onunlock: function() {
-                    thisAudio.showAudioInfoboxSubject.next(false)
-                },
+                onload: sound_load
             }))
         }
 
@@ -82,10 +74,7 @@ export class HatnoteAudio {
                         console.log("Number of audio playing (subtract): " + thisAudio.current_notes)
                     }
                 },
-                onload : sound_load,
-                onunlock: function() {
-                    thisAudio.showAudioInfoboxSubject.next(false)
-                },
+                onload : sound_load
             }))
         }
 
@@ -93,26 +82,17 @@ export class HatnoteAudio {
         this.harp.push(new Howl({
             src : [sound_map.get('sounds/ConcertHarp-small/samples/C3_mf3.wav')],
             volume : 0.2,
-            onload : sound_load,
-            onunlock: function() {
-                thisAudio.showAudioInfoboxSubject.next(false)
-            },
+            onload : sound_load
         }))
        this.harp.push(new Howl({
             src : [sound_map.get('sounds/ConcertHarp-small/samples/F2_f1.wav')],
             volume : 0.2,
-            onload : sound_load,
-            onunlock: function() {
-                thisAudio.showAudioInfoboxSubject.next(false)
-            },
+            onload : sound_load
         }))
         this.harp.push(new Howl({
             src : [sound_map.get('sounds/ConcertHarp-small/samples/A2_mf1.wav')],
             volume : 0.2,
             onload : sound_load,
-            onunlock: function() {
-                thisAudio.showAudioInfoboxSubject.next(false)
-            },
         }))
     }
 
diff --git a/web/src/canvas/banner.ts b/web/src/canvas/banner.ts
index f27f17ec1c4c4dfd821c3c578f70e84723630fe3..2efee82418bace74b9d110886948d334058c03c9 100644
--- a/web/src/canvas/banner.ts
+++ b/web/src/canvas/banner.ts
@@ -11,7 +11,7 @@ export class Banner{
         this.bannerLayer = bannerLayer
 
         this.root = bannerLayer.appendSVGElement('g')
-            .attr('transform', 'translate(0, ' +  bannerLayer.canvas.theme.header_height +')');
+            .attr('transform', 'translate(0, ' +  bannerLayer.canvas.visDirector.hatnoteTheme.header_height +')');
 
         this.user_container = this.root.append('g')
 
@@ -28,7 +28,7 @@ export class Banner{
 
         this.user_container.append('rect')
             .attr('opacity', 0)
-            .attr('fill', bannerLayer.canvas.theme.getThemeColor(bannerData.serviceEvent))
+            .attr('fill', bannerLayer.canvas.visDirector.getThemeColor(bannerData.serviceEvent))
             .attr('width', bannerLayer.canvas.width)
             .attr('height', 35)
             .transition()
diff --git a/web/src/canvas/banner_layer.ts b/web/src/canvas/banner_layer.ts
index f7ca218063784c47398bb3f3f122b15e1b65913a..7db32caff3f76a6f3712f19ed55612cd418b91a7 100644
--- a/web/src/canvas/banner_layer.ts
+++ b/web/src/canvas/banner_layer.ts
@@ -1,13 +1,13 @@
 import {Selection} from "d3";
-import {ListenToCanvas} from "./listen/listenToCanvas";
 import {BannerData} from "../observable/model";
 import {Banner} from "./banner";
+import {Canvas} from "./canvas";
 
 export class BannerLayer{
-    public readonly canvas: ListenToCanvas
+    public readonly canvas: Canvas
     private readonly root: Selection<SVGGElement, unknown, null, any>;
     private banner:  Banner | null
-    constructor(canvas: ListenToCanvas) {
+    constructor(canvas: Canvas) {
         this.canvas = canvas
         this.root = canvas.appendSVGElement('g').attr('id', 'banner_layer')
         this.banner = null;
@@ -18,7 +18,7 @@ export class BannerLayer{
     }
 
     public addBanner(bannerData: BannerData){
-        if(this.banner !== null || this.canvas.theme.current_service_theme?.id_name !== this.canvas.theme.getHatnoteService(bannerData.serviceEvent)) {
+        if(this.banner !== null || this.canvas.visDirector.current_service_theme?.id_name !== this.canvas.visDirector.getHatnoteService(bannerData.serviceEvent)) {
             return;
         }
 
diff --git a/web/src/canvas/canvas.ts b/web/src/canvas/canvas.ts
index e519271f7a9c87abf3620cbb3a018fa9f889a1ce..e9092bc97096d67d32bf5103adb85d9bf363735e 100644
--- a/web/src/canvas/canvas.ts
+++ b/web/src/canvas/canvas.ts
@@ -1,27 +1,46 @@
 import {select, Selection} from "d3";
 import {BehaviorSubject, Subject} from "rxjs";
-import {CircleData, DatabaseInfo, NetworkInfoboxData} from "../observable/model";
+import {BannerData, CircleData, DatabaseInfo, NetworkInfoboxData} from "../observable/model";
 import {SettingsData} from "../configuration/hatnote_settings";
 import {InfoBox, InfoboxType} from "./info_box";
-import {CirclesLayer} from "./listen/circles_layer";
 import {Header} from "./header";
-import {Theme} from "../theme/theme";
+import {VisualisationDirector} from "../theme/visualisationDirector";
 import {HatnoteVisService} from "../service_event/model";
+import {Navigation} from "./navigation";
+import {BannerLayer} from "./banner_layer";
+import {QRCode} from "./qr_code";
+import {MuteIcon} from "./mute_icon";
+import {Carousel} from "./carousel";
+import {ListenToVisualisation} from "./listen/listenToVisualisation";
+import {GeoVisualisation} from "./geo/geoVisualisation";
+import {Visualisation} from "../theme/model";
+import {set} from "lodash";
 
-export abstract class Canvas {
-    abstract header: Header;
-    public readonly theme: Theme;
-    abstract info_box_websocket: InfoBox;
-    abstract info_box_legend: InfoBox;
+export class Canvas {
+    public readonly banner_layer:  BannerLayer;
+    public readonly qr_code: QRCode;
+    public readonly header: Header;
+    public readonly visDirector: VisualisationDirector;
+    public readonly mute_icon: MuteIcon;
+    public readonly carousel: Carousel | undefined
+    public readonly navigation: Navigation | undefined;
+    public readonly info_box_websocket: InfoBox;
+    public readonly info_box_legend: InfoBox;
     public readonly isMobileScreen: boolean = false;
     public readonly settings: SettingsData;
     public readonly showNetworkInfoboxObservable: Subject<NetworkInfoboxData>
     public readonly updateDatabaseInfoSubject: Subject<DatabaseInfo>
-    public readonly hatnoteVisServiceChangedSubject: BehaviorSubject<HatnoteVisService>
+    public readonly onCarouselTransitionStart: BehaviorSubject<[HatnoteVisService, Visualisation]>
+    public readonly onThemeHasChanged: BehaviorSubject<[HatnoteVisService, Visualisation]>
+    public readonly onCarouselTransitionEnd: BehaviorSubject<[HatnoteVisService, Visualisation]>
     public readonly updateVersionSubject: Subject<[string, number]>
     public readonly newCircleSubject: Subject<CircleData>
+    public readonly newBannerSubject: Subject<BannerData>
+    private readonly listenToVis: ListenToVisualisation;
+    public readonly geoPopUpContainer:  Selection<HTMLDivElement, unknown, null, undefined>
+    private readonly geoVis: GeoVisualisation;
     public readonly appContainer:  Selection<HTMLDivElement, unknown, null, undefined>;
-    protected abstract _root: Selection<SVGSVGElement, unknown, null, any>;
+    private readonly _root: Selection<SVGSVGElement, unknown, null, any>;
     public get root(): Selection<SVGSVGElement, unknown, null, any> {
         return this._root;
     }
@@ -40,30 +59,129 @@ export abstract class Canvas {
         this._height = value;
     }
 
-    protected constructor(theme: Theme, settings: SettingsData, newCircleSubject: Subject<CircleData>,
+    constructor(theme: VisualisationDirector, settings: SettingsData, newCircleSubject: Subject<CircleData>,
                           showNetworkInfoboxObservable: Subject<NetworkInfoboxData>,
                           updateVersionSubject: Subject<[string, number]>,
-                          hatnoteVisServiceChangedSubject: BehaviorSubject<HatnoteVisService>,
+                          onCarouselTransitionStart: BehaviorSubject<[HatnoteVisService, Visualisation]>,
+                          onCarouselTransitionMid: BehaviorSubject<[HatnoteVisService, Visualisation]>,
+                          onCarouselTransitionEnd: BehaviorSubject<[HatnoteVisService, Visualisation]>,
                           updateDatabaseInfoSubject: Subject<DatabaseInfo>,
+                          newBannerSubject: Subject<BannerData>,
                           appContainer:  Selection<HTMLDivElement, unknown, null, undefined>) {
         this._width = window.innerWidth;
         this._height = window.innerHeight;
-        this.theme = theme;
+        this.visDirector = theme;
         this.settings = settings
-        this.hatnoteVisServiceChangedSubject = hatnoteVisServiceChangedSubject
+        this.onCarouselTransitionStart = onCarouselTransitionStart
+        this.onThemeHasChanged = onCarouselTransitionMid
+        this.onCarouselTransitionEnd = onCarouselTransitionEnd
         this.newCircleSubject = newCircleSubject
         this.showNetworkInfoboxObservable = showNetworkInfoboxObservable
         this.updateDatabaseInfoSubject = updateDatabaseInfoSubject
         this.updateVersionSubject = updateVersionSubject
         this.appContainer = appContainer;
+        this.newBannerSubject = newBannerSubject
         if (this.width <= 430 || this.height <= 430) { // iPhone 12 Pro Max 430px viewport width
             this.isMobileScreen = true;
         }
+
+        // draw order matters in this function. Do not change without checking the result.
+        this._root = this.appContainer.append("svg")
+            .attr("width", this.width)
+            .attr("height", this.height)
+            .attr('fill', this.visDirector.hatnoteTheme.svg_background_color)
+            .style('background-color', '#1c2733');
+        this.geoPopUpContainer = this.appContainer.append('div')
+            .attr("id","geo-visualisation-popup-container").attr("style", "opacity: 1;")
+
+        this.qr_code = new QRCode(this)
+
+        this.listenToVis = new ListenToVisualisation(this)
+        this.geoVis = new GeoVisualisation(this)
+
+        this.banner_layer = new BannerLayer(this)
+        this.header = new Header(this)
+        // needs to be added last to the svg because it should draw over everything else
+        this.info_box_websocket = new InfoBox(this, InfoboxType.network_websocket_connecting)
+        this.info_box_legend = new InfoBox(this, InfoboxType.legend)
+
+        if(settings.carousel_mode && !this.isMobileScreen){
+            this.carousel = new Carousel(this)
+        }
+
+        // needs to be added after the carousel transition because the transition layer spans over the entire screen
+        // which captures mouse clicks that otherwise would not arrive at the navigation buttons
+        if (this.isMobileScreen && !this.settings.embedded_mode) {
+            this.navigation = new Navigation(this)
+        }
+
+        // needs to be here because otherwise the transition animation and mobile navigation layer of the carousel would lay above the mute icon
+        // and block the cursor event of the mute icon
+        this.mute_icon = new MuteIcon(this)
+
+        this.onThemeHasChanged.subscribe({
+            next: (value) => {
+                this.renderCurrentTheme()
+            }
+        })
+
+        this.renderCurrentTheme();
+
+        if(!settings.kiosk_mode && !settings.audio_mute && !(!settings.carousel_mode && settings.map)){
+            this.mute_icon.show()
+        }
+
+        window.onresize = (_) => this.windowUpdate();
     }
 
     public appendSVGElement(type: string): Selection<SVGGElement, unknown, null, any> {
         return this._root.append(type)
     }
 
-    protected abstract windowUpdate() : void
+    public renderCurrentTheme(){
+        // remove circles and visualisation from other services
+        this.listenToVis.renderCurrentTheme()
+        this.geoVis.renderCurrentTheme()
+
+        // remove banner
+        this.banner_layer.removeBanner();
+
+        // update qr code
+        this.qr_code.themeUpdate(this.visDirector.current_service_theme)
+
+        // update header logo
+        this.header.themeUpdate(this.visDirector.current_service_theme)
+
+        this.navigation?.themeUpdate(this.visDirector.current_service_theme)
+    }
+
+    // This method does not cover all ui elements. There is no requirement for this nor a need for a mobile version. People
+    // will use the website as a background animation. If you resize the window it is easier to just reload the page for a moment.
+    public windowUpdate() {
+        // update canvas root dimensions
+        this.width = window.innerWidth;
+        this.height = window.innerHeight;
+        this._root.attr("width", this.width).attr("height", this.height);
+
+        // update canvas header dimensions
+        this.header.windowUpdate()
+
+        // update banner
+        this.banner_layer.windowUpdate()
+
+        // update progress indicator
+        this.carousel?.windowUpdate()
+
+        // update qr_code
+        this.qr_code?.windowUpdate()
+
+        // update navigation
+        this.navigation?.windowUpdate()
+
+        // update websocket info box
+        this.info_box_websocket.windowUpdate()
+
+        // update mute icon
+        this.mute_icon.windowUpdate()
+    }
 }
\ No newline at end of file
diff --git a/web/src/canvas/carousel.ts b/web/src/canvas/carousel.ts
index 273d74e4fc36351cda4007da67ad91104477556e..df8cfa00bddd7231afa56abf62b4d61b783ce8bb 100644
--- a/web/src/canvas/carousel.ts
+++ b/web/src/canvas/carousel.ts
@@ -1,11 +1,10 @@
-import {ListenToCanvas} from "./listen/listenToCanvas";
 import {Transition} from "./transition";
 import {ProgressIndicator} from "./progress_indicator";
 import {DatabaseInfo} from "../observable/model";
-import {Subject} from "rxjs";
+import {BehaviorSubject, Subject} from "rxjs";
 import {HatnoteVisService} from "../service_event/model";
-import {ServiceTheme} from "../theme/model";
-import {GeoCanvas} from "./geo/geoCanvas";
+import {ServiceTheme, Visualisation} from "../theme/model";
+import {Canvas} from "./canvas";
 
 export class Carousel {
     public readonly transition: Transition;
@@ -15,21 +14,32 @@ export class Carousel {
     public serviceError: Map<HatnoteVisService, boolean>
     public allServicesHaveError: boolean
     private startCarouselService: HatnoteVisService | null
-    private readonly canvas: ListenToCanvas | GeoCanvas
+    private readonly canvas: Canvas;
+    private nextTheme: ServiceTheme | undefined;
     private currentCarouselOrderIndex;
-    constructor(canvas: ListenToCanvas | GeoCanvas) {
+    constructor(canvas: Canvas) {
         this.canvas = canvas
         this.transition = new Transition(this.canvas)
-        this.transition.onTransitionMid.subscribe(_ => this.canvas.hatnoteVisServiceChangedSubject.next(this.canvas.theme.current_service_theme.id_name))
-        this.transition.onTransitionEnd.subscribe(_ => this.continueCarousel())
+        this.transition.onTransitionStart.subscribe(_ => this.canvas.onCarouselTransitionStart.next(
+            [this.canvas.visDirector.current_service_theme.id_name,this.canvas.visDirector.current_visualisation]))
+        this.transition.onTransitionMid.subscribe(_ => {
+            this.initNextTheme();
+            this.canvas.onThemeHasChanged.next(
+            [this.canvas.visDirector.current_service_theme.id_name,this.canvas.visDirector.current_visualisation]);
+        })
+        this.transition.onTransitionEnd.subscribe(_ => {
+            this.canvas.onCarouselTransitionEnd.next(
+                [this.canvas.visDirector.current_service_theme.id_name,this.canvas.visDirector.current_visualisation])
+            this.continueCarousel();
+        })
         this.progess_indicator = new ProgressIndicator(this.canvas)
         this.updateDatabaseInfoSubject = this.canvas.updateDatabaseInfoSubject
         this.serviceError = new Map<HatnoteVisService, boolean>()
         this.currentCarouselOrderIndex = 0
         this.allServicesHaveError = false
-        this.startCarouselService = this.canvas.theme.carousel_service_order[0].id_name
+        this.startCarouselService = this.canvas.visDirector.carousel_service_order[0].id_name
         this.databaseInfo = new Map<HatnoteVisService, DatabaseInfo>()
-        this.canvas.theme.service_themes.forEach((serviceTheme => {
+        this.canvas.visDirector.service_themes.forEach((serviceTheme => {
             this.serviceError.set(serviceTheme.id_name, false)
             this.databaseInfo.set(serviceTheme.id_name, {
                 service: serviceTheme.id_name,
@@ -57,8 +67,8 @@ export class Carousel {
 
                 let serviceErrors = 0
                 this.startCarouselService = null
-                for (let i = 0; i < this.canvas.theme.carousel_service_order.length; i++) {
-                    let serviceTheme = this.canvas.theme.carousel_service_order[i]
+                for (let i = 0; i < this.canvas.visDirector.carousel_service_order.length; i++) {
+                    let serviceTheme = this.canvas.visDirector.carousel_service_order[i]
                     if(this.serviceError.get(serviceTheme.id_name)){
                         serviceErrors++
                     } else {
@@ -68,7 +78,7 @@ export class Carousel {
                     }
                 }
 
-                if(serviceErrors === this.canvas.theme.service_themes.size){
+                if(serviceErrors === this.canvas.visDirector.service_themes.size){
                     this.allServicesHaveError = true
                 }
 
@@ -76,9 +86,9 @@ export class Carousel {
                 if(this.serviceError.get(dbInfo.service)){
                     this.progess_indicator.service_indicators.get(dbInfo.service)?.setError()
 
-                    if(dbInfo.service === this.canvas.theme.current_service_theme.id_name && !this.allServicesHaveError) {
+                    if(dbInfo.service === this.canvas.visDirector.current_service_theme.id_name && !this.allServicesHaveError) {
                         this.initNextTheme()
-                        this.transition.startTransition(this.canvas.theme.current_service_theme)
+                        this.transition.startTransition(this.canvas.visDirector.current_service_theme)
                     }
                     return;
                 }
@@ -90,15 +100,15 @@ export class Carousel {
         let nextTheme: ServiceTheme | undefined;
 
         let iterationNumber = 0
-        let iterationNumberLimit = this.canvas.theme.carousel_service_order.length;
+        let iterationNumberLimit = this.canvas.visDirector.carousel_service_order.length;
         let iterationIndex = (this.currentCarouselOrderIndex + 1) % iterationNumberLimit
         while(iterationNumber < iterationNumberLimit){
-            if(this.serviceError.get(this.canvas.theme.carousel_service_order[iterationIndex].id_name)){
+            if(this.serviceError.get(this.canvas.visDirector.carousel_service_order[iterationIndex].id_name)){
                 iterationIndex = (iterationIndex + 1) % iterationNumberLimit
                 iterationNumber++;
             } else {
                 this.currentCarouselOrderIndex = iterationIndex
-                nextTheme = this.canvas.theme.carousel_service_order[iterationIndex]
+                nextTheme = this.canvas.visDirector.carousel_service_order[iterationIndex]
                 break;
             }
         }
@@ -107,10 +117,11 @@ export class Carousel {
     }
 
     private initNextTheme(){
-        let nextTheme: ServiceTheme | undefined = this.getNextServiceTheme()
-        if(nextTheme){
-            this.canvas.theme.set_current_theme(nextTheme);
-            this.progess_indicator.setCurrentServiceIndicator(nextTheme)
+        let nextVisualisation = this.canvas.visDirector.getNextVisualisation()
+        if(this.nextTheme){
+            this.canvas.visDirector.set_current_theme(this.nextTheme);
+            this.canvas.visDirector.setCurrentVisualisation(nextVisualisation);
+            this.progess_indicator.setCurrentServiceIndicator(this.nextTheme)
         }
     }
 
@@ -123,7 +134,7 @@ export class Carousel {
         if(this.canvas.settings.carousel_mode) {
             let indicator = this.progess_indicator.currentServiceIndicator;
 
-            if (this.canvas.theme.current_service_theme.id_name === this.startCarouselService) {
+            if (this.canvas.visDirector.current_service_theme.id_name === this.startCarouselService) {
                 this.serviceError.forEach((error, service) => {
                     if (!error) {
                         this.progess_indicator.service_indicators.get(service)?.reset()
@@ -133,8 +144,10 @@ export class Carousel {
 
             indicator?.start(() => {
                 if (!this.allServicesHaveError) {
-                    this.initNextTheme()
-                    this.transition.startTransition(this.canvas.theme.current_service_theme)
+                    this.nextTheme = this.getNextServiceTheme()
+                    if(this.nextTheme !== undefined) {
+                        this.transition.startTransition(this.nextTheme)
+                    }
                 }
             })
         }
diff --git a/web/src/canvas/geo/circle.ts b/web/src/canvas/geo/geoCircle.ts
similarity index 75%
rename from web/src/canvas/geo/circle.ts
rename to web/src/canvas/geo/geoCircle.ts
index d4f6eab3fa009c13c61424875195584db38d1419..489a2bd1ccabbee6888c922927cbd149e12f53ba 100644
--- a/web/src/canvas/geo/circle.ts
+++ b/web/src/canvas/geo/geoCircle.ts
@@ -1,19 +1,22 @@
-import {CirclesLayer} from "./circles_layer";
 import {HatnoteVisService} from "../../service_event/model";
 import {BaseType, select, Selection} from "d3";
 import {CircleData} from "../../observable/model";
+import {GeoCirclesLayer} from "./geoCirclesLayer";
+import {Canvas} from "../canvas";
 
-export class Circle{
-    private readonly circlesLayer: CirclesLayer
+export class GeoCircle {
+    private readonly circlesLayer: GeoCirclesLayer
     private readonly root:  Selection<SVGCircleElement, unknown, null, undefined>;
+    private readonly canvas: Canvas;
 
-    constructor(circlesLayer: CirclesLayer, circleData: CircleData,
+    constructor(circlesLayer: GeoCirclesLayer, circleData: CircleData,
                 svgCircle:  SVGCircleElement, service: HatnoteVisService) {
         this.circlesLayer = circlesLayer
+        this.canvas = this.circlesLayer.geoVis.canvas
         // init circle values
         this.root = select(svgCircle)
         let point;
-        if(this.circlesLayer.canvas.theme.current_service_theme.id_name === HatnoteVisService.Bloxberg){
+        if(this.canvas.visDirector.current_service_theme.id_name === HatnoteVisService.Bloxberg){
             point = this.circlesLayer.worldProjection([circleData.location?.coordinate.long ?? 0, circleData.location?.coordinate.lat?? 0])
         } else {
             point = this.circlesLayer.germanyProjection([circleData.location?.coordinate.long ?? 0, circleData.location?.coordinate.lat?? 0])
@@ -30,7 +33,7 @@ export class Circle{
             .attr('cy', y)
             .attr("data-hatnote-event-type", circleData.type)
             .attr("data-hatnote-service-name", service)
-            .style('fill', this.circlesLayer.canvas.theme.getThemeColor(circleData.type))
+            .style('fill', this.canvas.visDirector.getThemeColor(circleData.type))
             .attr('fill-opacity', 0.75)
             .attr('r', 0)
             .transition()
@@ -43,30 +46,30 @@ export class Circle{
             .on('interrupt', _ => {
                 highlightedArea.interrupt()
                 popUpContainer.remove();
-                if(this.circlesLayer.canvas.settings.debug_mode){
+                if(this.canvas.settings.debug_mode){
                     console.log('Circle removed for ' + circleData.type)
                 }
             })
             .remove()
             .each( _ => {
-                if(this.circlesLayer.canvas.settings.debug_mode){
+                if(this.canvas.settings.debug_mode){
                     console.log('Circle removed for ' + circleData.type)
                 }
             })
 
         let highlightedArea: Selection<BaseType, unknown, null, any>;
         // highlight region
-        if(this.circlesLayer.canvas.theme.current_service_theme.id_name === HatnoteVisService.Bloxberg){
+        if(this.canvas.visDirector.current_service_theme.id_name === HatnoteVisService.Bloxberg){
             highlightedArea = this.highlightCountry(circleData.location.countryId)
         } else {
             highlightedArea = this.highlightState(circleData.location.stateId)
         }
 
         // add pop up
-        const popUpContainer = this.circlesLayer.canvas.appContainer.append("div");
+        const popUpContainer = this.canvas.geoPopUpContainer.append("div");
         popUpContainer
             .style('position', 'absolute')
-            .style('top', `${y - this.circlesLayer.canvas.theme.header_height + 10}px`)
+            .style('top', `${y - this.canvas.visDirector.hatnoteTheme.header_height + 10}px`)
             .style('left', `${x + 10}px`)
             .style('padding', '4px')
             .style('border-radius', '1px')
@@ -83,7 +86,7 @@ export class Circle{
     }
 
     private highlightCountry(countryId: string): Selection<BaseType, unknown, null, any> {
-        let country = this.circlesLayer.canvas.root.select(`path[data-country-id="${countryId}"]`)
+        let country = this.canvas.root.select(`path[data-country-id="${countryId}"]`)
             .style('fill', '#eddc4e')
         country.transition()
             .duration(5000)
@@ -92,7 +95,7 @@ export class Circle{
     };
 
     private highlightState(stateId: string): Selection<BaseType, unknown, null, any> {
-        let country = this.circlesLayer.canvas.root.select(`path[data-state-id="${stateId}"]`)
+        let country = this.canvas.root.select(`path[data-state-id="${stateId}"]`)
             .style('fill', '#eddc4e')
         country.transition()
             .duration(5000)
diff --git a/web/src/canvas/geo/circles_layer.ts b/web/src/canvas/geo/geoCirclesLayer.ts
similarity index 60%
rename from web/src/canvas/geo/circles_layer.ts
rename to web/src/canvas/geo/geoCirclesLayer.ts
index 9397efaa0cc8449e3e4033984abf1fe8887ebfde..7213ddbc0b685b33195cc3ee19a2504456ea9a07 100644
--- a/web/src/canvas/geo/circles_layer.ts
+++ b/web/src/canvas/geo/geoCirclesLayer.ts
@@ -1,22 +1,24 @@
 import {GeoProjection, Selection} from "d3";
 import {ServiceTheme} from "../../theme/model";
-import {Circle} from "./circle";
 import {CircleData} from "../../observable/model";
 import {Canvas} from "../canvas";
 import {ServiceEvent} from "../../service_event/model";
+import {GeoCircle} from "./geoCircle";
+import {GeoVisualisation} from "./geoVisualisation";
 
-export class CirclesLayer{
+export class GeoCirclesLayer{
     private readonly root: Selection<SVGGElement, unknown, null, any>;
-    public readonly canvas: Canvas
+    public readonly geoVis: GeoVisualisation
     public readonly germanyProjection: GeoProjection
     public readonly worldProjection: GeoProjection
+    private readonly rootId = "geo-vis-circle-layer"
 
-    constructor(canvas: Canvas, germanyProjection: GeoProjection, worldProjection: GeoProjection) {
-        this.canvas = canvas
+    constructor(geoVis: GeoVisualisation, germanyProjection: GeoProjection, worldProjection: GeoProjection) {
+        this.geoVis = geoVis
         this.germanyProjection = germanyProjection;
         this.worldProjection = worldProjection;
-        this.root = canvas.appendSVGElement('g').attr('id', 'circle_layer')
-        canvas.newCircleSubject.subscribe({
+        this.root = geoVis.appendSVGElement('g').attr('id', this.rootId)
+        geoVis.canvas.newCircleSubject.subscribe({
             next: (value) => this.addCircle(value)
         })
     }
@@ -30,21 +32,24 @@ export class CirclesLayer{
             .enter()
             .append('circle')
             .each(function (circleData, _) {
-                let service = that.canvas.theme.getHatnoteService(circleData.type)
-                if (that.canvas.theme.current_service_theme?.id_name !== service){
+                let service = that.geoVis.canvas.visDirector.getHatnoteService(circleData.type)
+                if (that.geoVis.canvas.visDirector.current_service_theme?.id_name !== service){
                     return
                 }
 
-                new Circle(that,circleData,
+                new GeoCircle(that,circleData,
                     this, service)
             })
     }
 
     public removeOtherServiceCircles(currentServiceTheme: ServiceTheme) {
-        for (let serviceTheme of this.canvas.theme.service_themes.values()) {
+        for (let serviceTheme of this.geoVis.canvas.visDirector.service_themes.values()) {
             if(serviceTheme.id_name !== currentServiceTheme.id_name) {
                 this.root.select(`circle[data-hatnote-service-name="${serviceTheme.id_name}"]`).interrupt().remove()
             }
         }
+        let circleLayer = document.getElementById(this.rootId);
+        circleLayer?.replaceChildren()
+        this.geoVis.canvas.geoPopUpContainer.selectChildren().remove()
     }
 }
\ No newline at end of file
diff --git a/web/src/canvas/geo/geoCanvas.ts b/web/src/canvas/geo/geoVisualisation.ts
similarity index 54%
rename from web/src/canvas/geo/geoCanvas.ts
rename to web/src/canvas/geo/geoVisualisation.ts
index a1985283ddf4170cf6ee8518a93d6ff942c2af58..ce88e356c5fd8ead9800600509903c5dcc579e49 100644
--- a/web/src/canvas/geo/geoCanvas.ts
+++ b/web/src/canvas/geo/geoVisualisation.ts
@@ -1,13 +1,6 @@
 import {geoAlbers, geoBounds, geoEqualEarth, geoPath, GeoProjection, Selection} from "d3";
 import '../../style/normalize.css';
 import '../../style/main.css';
-import {CirclesLayer} from "./circles_layer";
-import {Header} from "../header";
-import {InfoBox, InfoboxType} from "../info_box";
-import {Theme} from "../../theme/theme";
-import {BehaviorSubject, Subject} from "rxjs";
-import {CircleData, DatabaseInfo, NetworkInfoboxData} from "../../observable/model";
-import {SettingsData} from "../../configuration/hatnote_settings";
 import {feature, mesh} from "topojson";
 import countriesJson from '../../../assets/countries-50m.json'
 import germanyJson from '../../../assets/germany.json'
@@ -15,65 +8,49 @@ import {GeometryObject, Topology} from 'topojson-specification';
 import {FeatureCollection, GeoJsonProperties} from 'geojson';
 import {Canvas} from "../canvas";
 import {HatnoteVisService} from "../../service_event/model";
-import {Carousel} from "../carousel";
-
-export class GeoCanvas extends Canvas{
-    public readonly  circles_layer: CirclesLayer
-    public readonly  header: Header;
-    protected readonly _root: Selection<SVGSVGElement, unknown, null, any>;
-    public readonly  info_box_websocket: InfoBox;
-    public readonly  info_box_legend: InfoBox;
+import {GeoCirclesLayer} from "./geoCirclesLayer";
+import {Visualisation} from "../../theme/model";
+
+export class GeoVisualisation {
+    public readonly  circles_layer: GeoCirclesLayer
+    public readonly canvas: Canvas;
+    protected readonly root: Selection<SVGGElement, unknown, null, any>;
     public readonly worldMap: Selection<SVGGElement, unknown, null, any>;
     public readonly worldMapProjection: GeoProjection;
     public readonly germanyMap: Selection<SVGGElement, unknown, null, any>;
     public readonly germanyMapProjection: GeoProjection;
-    public readonly carousel: Carousel | undefined
 
-    constructor(theme: Theme, settings: SettingsData, newCircleSubject: Subject<CircleData>,
-                showNetworkInfoboxObservable: Subject<NetworkInfoboxData>,
-                updateVersionSubject: Subject<[string, number]>,
-                hatnoteVisServiceChangedSubject: BehaviorSubject<HatnoteVisService>,
-                updateDatabaseInfoSubject: Subject<DatabaseInfo>,
-                appContainer:  Selection<HTMLDivElement, unknown, null, undefined>) {
-        super(theme, settings, newCircleSubject, showNetworkInfoboxObservable, updateVersionSubject,hatnoteVisServiceChangedSubject, updateDatabaseInfoSubject, appContainer)
+    constructor(canvas: Canvas) {
+        this.canvas = canvas;
 
+        this.root = canvas.appendSVGElement('g').attr("id", "geo-visualisation")
 
         // draw order matters in this function. Do not change without checking the result.
-        this._root = appContainer.append("svg")
-            .attr("id", 'hatnote-canvas')
-            .attr("width", this.width)
-            .attr("height", this.height)
+        this.root
             .attr("style", "max-width: 100%; height: auto;");
 
-        this.worldMap = this._root.append("g").attr("id", "world-map")
-        this.germanyMap = this._root.append("g").attr("id", "germany-map")
+        this.worldMap = this.root.append("g").attr("id", "world-map")
+        this.germanyMap = this.root.append("g").attr("id", "germany-map")
         this.worldMapProjection = this.initWorldMapSvg()
         this.germanyMapProjection = this.initGermanyMapSvg()
-        this.circles_layer = new CirclesLayer(this, this.germanyMapProjection, this.worldMapProjection)
-        this.header = new Header(this, false)
-        // needs to be added last to the svg because it should draw over everything else
-        this.info_box_websocket = new InfoBox(this, InfoboxType.network_websocket_connecting, false, undefined, undefined)
-        this.info_box_legend = new InfoBox(this, InfoboxType.legend, false, undefined, undefined)
-
-        if(settings.carousel_mode && !this.isMobileScreen){
-            this.carousel = new Carousel(this)
-        }
-
-        this.hatnoteVisServiceChangedSubject.subscribe({
-            next: (value) => {
-                this.renderCurrentTheme()
-            }
-        })
+        this.circles_layer = new GeoCirclesLayer(this, this.germanyMapProjection, this.worldMapProjection)
 
         this.renderCurrentTheme();
 
-        window.onresize = (_) => this.windowUpdate();
+        this.canvas.onCarouselTransitionStart.subscribe({
+            next: (_) => this.canvas.geoPopUpContainer.attr("style", "opacity: 0;")
+        })
+
+        this.canvas.onCarouselTransitionEnd.subscribe({
+            next: (_) => this.canvas.geoPopUpContainer.attr("style", "opacity: 1;")
+        })
     }
 
     private germanyProjection(states: any): GeoProjection{
-        const width = this.width;
-        const marginTop = this.theme.header_height;
-        const height = this.height;
+        const width = this.canvas.width;
+        const marginTop = this.canvas.visDirector.hatnoteTheme.header_height + 10;
+        const height = this.canvas.height;
+        const carouselProgressIndicatorSafeZone = this.canvas.visDirector.hatnoteTheme.progress_indicator_y_padding + 10;
 
         // from https://observablehq.com/@sto3psl/map-of-germany-in-d3-js
         const [bottomLeft, topRight] = geoBounds(states);
@@ -93,7 +70,7 @@ export class GeoCanvas extends Canvas{
             .translate([width / 2, height / 2])
             .rotate([lambda, 0, 0])
             .center(center)
-            .scale(scale * 200).fitExtent([[2, marginTop + 2], [width - 2, height - 2 ]], states);
+            .scale(scale * 200).fitExtent([[2, marginTop + 2], [width - 2, height - 2 - carouselProgressIndicatorSafeZone ]], states);
     }
 
     private initGermanyMapSvg(){
@@ -132,26 +109,17 @@ export class GeoCanvas extends Canvas{
     }
 
     private initWorldMapSvg(){
-        const width = this.width;
-        const marginTop = this.theme.header_height;
-        const height = this.height;
+        const width = this.canvas.width;
+        const marginTop = this.canvas.visDirector.hatnoteTheme.header_height + 10;
+        const height = this.canvas.height;
+        const carouselProgressIndicatorSafeZone = this.canvas.visDirector.hatnoteTheme.progress_indicator_y_padding + 10;
 
         // create projection
-        let projection = geoEqualEarth().fitExtent([[2, marginTop + 2], [width - 2, height - 2 ]], {type: "Sphere"})
+        let projection = geoEqualEarth().fitExtent([[2, marginTop + 2], [width - 2, height - 2 - carouselProgressIndicatorSafeZone ]], {type: "Sphere"})
 
         // Fit the projection.
         const path = geoPath(projection);
 
-        // draw order matters here, check before changing something
-        // Add a white sphere with a black border.
-        this.worldMap.append("path")
-            .attr("id", "black-world-boundary")
-            .datum({type: "Sphere"})
-            .attr("fill", "white")
-            .attr("stroke", "currentColor")
-            // @ts-ignore
-            .attr("d", path);
-
         let world: Topology = (countriesJson as unknown) as Topology
         let countriesGeometry: GeometryObject<GeoJsonProperties> = world.objects.countries;
         let countries = feature(world, countriesGeometry)
@@ -179,7 +147,13 @@ export class GeoCanvas extends Canvas{
     }
 
     public renderCurrentTheme(){
-        if (this.theme.current_service_theme.id_name == HatnoteVisService.Bloxberg) {
+        if(this.canvas.visDirector.current_visualisation === Visualisation.listenTo){
+            this.root.attr("opacity", "0")
+        } else {
+            this.root.attr("opacity", "1")
+        }
+
+        if (this.canvas.visDirector.current_service_theme.id_name == HatnoteVisService.Bloxberg) {
             this.worldMap.attr("opacity", 1)
             this.germanyMap.attr("opacity", 0)
         } else {
@@ -188,24 +162,10 @@ export class GeoCanvas extends Canvas{
         }
 
         // remove circles from other services
-        this.circles_layer.removeOtherServiceCircles(this.theme.current_service_theme)
-
-        // update header logo
-        this.header.themeUpdate(this.theme.current_service_theme)
+        this.circles_layer.removeOtherServiceCircles(this.canvas.visDirector.current_service_theme)
     }
 
-    // This method does not cover all ui elements. There is no requirement for this nor a need for a mobile version. People
-    // will use the website as a background animation. If you resize the window it is easier to just reload the page for a moment.
-    protected windowUpdate() : void {
-        // update canvas root dimensions
-        this.width = window.innerWidth;
-        this.height = window.innerHeight;
-        this._root.attr("width", this.width).attr("height", this.height);
-
-        // update canvas header dimensions
-        this.header.windowUpdate()
-
-        // update websocket info box
-        this.info_box_websocket.windowUpdate()
+    public appendSVGElement(type: string): Selection<SVGGElement, unknown, null, any> {
+        return this.root.append(type)
     }
 }
\ No newline at end of file
diff --git a/web/src/canvas/header.ts b/web/src/canvas/header.ts
index 531ea393a92cb36988bf38743ff4bbcbe5c65a8a..78726f19e54bf0556c0dd29a417009c26c054454 100644
--- a/web/src/canvas/header.ts
+++ b/web/src/canvas/header.ts
@@ -1,13 +1,12 @@
 import {Selection} from "d3";
 import MinervaLogo from "../../assets/images/minervamessenger-banner-kussmund+bulb.png";
 import {LegendItem} from "./legend_item";
-import {ServiceTheme} from "../theme/model";
+import {ServiceTheme, Visualisation} from "../theme/model";
 import {environmentVariables} from "../configuration/environment";
 import {Canvas} from "./canvas";
 
 export class Header{
     public readonly canvas: Canvas;
-    private readonly isMobileScreen: boolean;
     private readonly root: Selection<SVGGElement, unknown, null, any>
     private readonly background_rect: Selection<SVGRectElement, unknown, null, any>
     private readonly title: Selection<SVGTextElement, unknown, null, any>
@@ -17,9 +16,8 @@ export class Header{
     private readonly updateText: Selection<SVGTextElement, unknown, null, any>
     private readonly legend_items: LegendItem[] = [];
 
-    constructor(canvas: Canvas, isMobileScreen: boolean) {
+    constructor(canvas: Canvas) {
         this.canvas = canvas;
-        this.isMobileScreen = isMobileScreen;
 
         this.root = canvas.appendSVGElement('g')
             .attr('id', 'header')
@@ -28,8 +26,8 @@ export class Header{
 
         this.background_rect = this.root.append('rect')
             .attr('width', canvas.width)
-            .attr('height', canvas.theme.header_height)
-            .attr('fill', canvas.theme.header_bg_color)
+            .attr('height', canvas.visDirector.hatnoteTheme.header_height)
+            .attr('fill', canvas.visDirector.hatnoteTheme.header_bg_color)
 
         this.logo = this.root.append('image')
             .attr('x', 10).attr('y', 4)
@@ -39,16 +37,16 @@ export class Header{
         this.title = this.root.append('text')
             .text('Hatnote title')
             .attr('font-family', 'HatnoteVisBold')
-            .attr('font-size', this.isMobileScreen ? '22px' : '32px')
-            .attr('fill', canvas.theme.header_text_color)
-            .attr('x', this.isMobileScreen ? 174 : 224).attr('y', canvas.theme.header_height/2 + 8.5)
+            .attr('font-size', this.canvas.isMobileScreen ? '22px' : '32px')
+            .attr('fill', canvas.visDirector.hatnoteTheme.header_text_color)
+            .attr('x', this.canvas.isMobileScreen ? 174 : 224).attr('y', canvas.visDirector.hatnoteTheme.header_height/2 + 8.5)
 
         this.title0 = this.root.append('text')
-            .text('Listen to')
+            .text(this.canvas.visDirector.current_visualisation === Visualisation.listenTo ? 'Listen to' : 'Locate')
             .attr('font-family', 'HatnoteVisNormal')
-            .attr('font-size', this.isMobileScreen ? '22px' : '32px')
-            .attr('fill', canvas.theme.header_text_color)
-            .attr('x', 70).attr('y', canvas.theme.header_height/2 + 8.5)
+            .attr('font-size', this.canvas.isMobileScreen ? '22px' : '32px')
+            .attr('fill', canvas.visDirector.hatnoteTheme.header_text_color)
+            .attr('x', 70).attr('y', canvas.visDirector.hatnoteTheme.header_height/2 + 8.5)
 
         this.updateBox = this.root.append('rect')
             .attr('opacity', 0)
@@ -57,7 +55,7 @@ export class Header{
             .attr('height', '30')
             .attr('rx', 7)
             .attr('ry', 7)
-            .attr('fill', this.canvas.theme.header_version_update_bg)
+            .attr('fill', this.canvas.visDirector.hatnoteTheme.header_version_update_bg)
 
         this.updateText = this.root.append('text')
             .attr('opacity', 0)
@@ -68,7 +66,7 @@ export class Header{
             .attr('font-size', '16px')
             .attr('fill', '#fff')
 
-        if(!this.isMobileScreen){
+        if(!this.canvas.isMobileScreen){
             for (let i = 0; i < 3; i++) {
                 this.legend_items.push(new LegendItem(this, undefined, this.canvas))
             }
@@ -106,6 +104,7 @@ export class Header{
         this.logo.attr('y', currentServiceTheme.header_y)
 
         this.title.text(currentServiceTheme.header_title)
+        this.title0.text(this.canvas.visDirector.current_visualisation === Visualisation.listenTo ? 'Listen to' : 'Locate')
 
         // update legend items
         this.clearLegendItems()
@@ -122,7 +121,7 @@ export class Header{
         this.updateBox.attr('transform', `translate(${this.canvas.width/2 - 95}, 8)`)
         this.updateText.attr('transform', `translate(${this.canvas.width/2}, 28)`)
 
-        this.canvas.theme.current_service_theme.legend_items.forEach((theme_legend_item, i) => {
+        this.canvas.visDirector.current_service_theme.legend_items.forEach((theme_legend_item, i) => {
             if(i < this.legend_items.length) {
                 this.legend_items[i].windowUpdate(theme_legend_item)
             }
diff --git a/web/src/canvas/icon_button.ts b/web/src/canvas/icon_button.ts
index 71ae8ea7dd515d7ce4dc47b3e14124d2de16b4a6..8ae960816ad2dc0d8f26531245b85d9faf864576 100644
--- a/web/src/canvas/icon_button.ts
+++ b/web/src/canvas/icon_button.ts
@@ -20,7 +20,7 @@ export class IconButton {
         this.bg = this.root.append('circle')
             .attr('transform', 'translate(' + xPos + ', ' + yPos+ ')')
             .attr('r', this.circleRadius)
-            .attr('stroke', this.navigation.canvas.theme.progress_indicator_fg_color)
+            .attr('stroke', this.navigation.canvas.visDirector.hatnoteTheme.progress_indicator_fg_color)
             .attr('stroke-width', 4 )
             .attr('fill', '#fff')
 
diff --git a/web/src/canvas/info_box.ts b/web/src/canvas/info_box.ts
index 3bdea8ddc284df5a45218538a5d38ed878aa7e0f..d702938630e21b0c83f07201fad26396a8857656 100644
--- a/web/src/canvas/info_box.ts
+++ b/web/src/canvas/info_box.ts
@@ -1,12 +1,10 @@
 import {Selection} from "d3";
-import {ListenToCanvas} from "./listen/listenToCanvas";
 import InfoboxAudioImg from "../../assets/images/DancingDoodle.svg";
 import LoadingSpinner from "../../assets/images/spinner.svg";
 import InfoboxWebsocketConnectingImg from "../../assets/images/SprintingDoodle.svg";
 import {NetworkInfoboxData} from "../observable/model";
 import InfoboxDbConnectingImg from "../../assets/images/MessyDoodle.svg";
 import {Carousel} from "./carousel";
-import {Subject} from "rxjs";
 import {Canvas} from "./canvas";
 
 export class InfoBox{
@@ -31,14 +29,12 @@ export class InfoBox{
     private readonly isMobileScreen : boolean
     private readonly carousel : Carousel | undefined
     private currentType: InfoboxType
-    public readonly showAudioInfoboxObservable: Subject<boolean> | undefined
 
 
-    constructor(canvas: Canvas, type: InfoboxType, isMobileScreen: boolean, carousel: Carousel | undefined, showAudioInfoboxObservable: Subject<boolean> | undefined) {
+    constructor(canvas: Canvas, type: InfoboxType) {
         this.canvas = canvas
-        this.isMobileScreen = isMobileScreen
-        this.carousel = carousel
-        this.showAudioInfoboxObservable = showAudioInfoboxObservable
+        this.isMobileScreen = this.canvas.isMobileScreen
+        this.carousel = this.canvas.carousel
         this.currentType = type
         this.root = canvas.appendSVGElement('g')
             .attr('opacity', 0)
@@ -60,11 +56,11 @@ export class InfoBox{
             .text('')
             .attr('font-family', 'HatnoteVisBold')
             .attr('font-size', '26px')
-            .attr('fill', canvas.theme.header_text_color)
+            .attr('fill', canvas.visDirector.hatnoteTheme.header_text_color)
         this.text = this.root.append('text')
             .attr('font-family', 'HatnoteVisNormal')
             .attr('font-size', '16px')
-            .attr('fill', canvas.theme.header_text_color)
+            .attr('fill', canvas.visDirector.hatnoteTheme.header_text_color)
         let line1 = this.text.append('tspan')
         let line12 = this.text.append('tspan')
         let line2 = this.text.append('tspan')
@@ -98,16 +94,6 @@ export class InfoBox{
                     next: (value) => this.show(value.infoboxType, value.show, value)
                 })
                 break;
-            case InfoboxType.audio_enable:
-                line5link = this.text.append('a')
-                line5 = line5link.append('tspan')
-                line52 = line5link.append('tspan')
-                this.line5link = line5link
-                this.loadingSpinner = undefined
-                this.showAudioInfoboxObservable?.subscribe({
-                    next: (value) => this.show(InfoboxType.audio_enable, value)
-                })
-                break;
         }
 
         this.setPosition()
@@ -119,20 +105,6 @@ export class InfoBox{
 
     public show(type: InfoboxType, show: boolean, network_infobox_data?: NetworkInfoboxData) {
         this.currentType = type
-        if(type=== InfoboxType.audio_enable) {
-            this.root.attr('opacity', show ? 1 : 0)
-            this.image?.attr('href', InfoboxAudioImg)
-            this.title.text('Enable audio')
-            this.line1[0].text('Your browser autoplay policy may prevent')
-            this.line2[0].text('audio from being played. Please ')
-            this.line2[1].text('click ').attr('font-family', 'HatnoteVisBold')
-            this.line3[0].text('somewhere on this page ').attr('font-family', 'HatnoteVisBold')
-            this.line3[1].text('to enable audio.')
-            this.line4[0].text('For more info see:')
-            this.line5link?.attr('href', 'https://jamonserrano.github.io/state-of-autoplay/')
-                .attr('target', '_blank')
-            this.line5[0].text('jamonserrano.github.io/state-of-autoplay')
-        }
 
         if(type === InfoboxType.network_websocket_connecting){
             this.root.attr('opacity', show ? 1 : 0)
@@ -149,11 +121,11 @@ export class InfoBox{
 
         // current theme service must match with websocket event service, otherwise, when carousel mode is active and e.g. minerva service
         // is shown, the keeper db infobox might appear
-        if(type === InfoboxType.network_database_connecting && this.canvas.theme.current_service_theme.id_name === network_infobox_data?.service){
+        if(type === InfoboxType.network_database_connecting && this.canvas.visDirector.current_service_theme.id_name === network_infobox_data?.service){
             this.root.attr('opacity', show ? 1 : 0)
             this.image?.attr('href', InfoboxWebsocketConnectingImg)
             this.title.text('Connecting to database')
-            this.line1[0].text('Connecting to ' + this.canvas.theme.current_service_theme.name + ' database.')
+            this.line1[0].text('Connecting to ' + this.canvas.visDirector.current_service_theme.name + ' database.')
             this.line2[0].text(' ')
             this.line3[0].text(' ')
             this.line3[1].text(' ')
@@ -162,12 +134,12 @@ export class InfoBox{
             this.loadingSpinner?.attr('opacity', 1)
         }
 
-        if(type === InfoboxType.network_database_can_not_connect && this.canvas.theme.current_service_theme.id_name === network_infobox_data?.service &&
+        if(type === InfoboxType.network_database_can_not_connect && this.canvas.visDirector.current_service_theme.id_name === network_infobox_data?.service &&
             (!this.canvas.settings.carousel_mode || this.carousel?.allServicesHaveError)){
             this.root.attr('opacity', show ? 1 : 0)
             this.image?.attr('href', InfoboxDbConnectingImg)
             this.title.text('Cannot connect to database')
-            this.line1[0].text('The backend can not connect ' + this.canvas.theme.current_service_theme.name + ' database.')
+            this.line1[0].text('The backend can not connect ' + this.canvas.visDirector.current_service_theme.name + ' database.')
             this.line2[0].text(' ')
             this.line3[0].text('Next reconnect: ').attr('font-family', 'HatnoteVisBold')
             this.line3[1].text(network_infobox_data?.next_reconnect_date ?? '')
@@ -209,9 +181,6 @@ export class InfoBox{
             case InfoboxType.network_database_connecting:
                 this.loadingSpinner?.attr('x', this.canvas.width/2 + 40).attr('y', this.canvas.height/2 + 10)
                 break;
-            case InfoboxType.audio_enable:
-                this.line5[0].attr('x', text_x).attr('dy', '20px')
-                break;
         }
     }
 }
@@ -220,6 +189,5 @@ export enum InfoboxType {
     network_websocket_connecting,
     network_database_connecting,
     network_database_can_not_connect,
-    audio_enable, // nowhere used because it was dismissed in favour of the mute icon
     legend
 }
\ No newline at end of file
diff --git a/web/src/canvas/legend_item.ts b/web/src/canvas/legend_item.ts
index 4bd6cc3d4aeaac61a6a14ea35d490f7614754063..1317dc7a3d0d1e49a46ce6a4450aef15024f661c 100644
--- a/web/src/canvas/legend_item.ts
+++ b/web/src/canvas/legend_item.ts
@@ -2,7 +2,7 @@ import {Selection} from "d3";
 import {Header} from "./header";
 import {ThemeLegendItem} from "../theme/model";
 import {InfoBox} from "./info_box";
-import {Theme} from "../theme/theme";
+import {VisualisationDirector} from "../theme/visualisationDirector";
 import {Canvas} from "./canvas";
 
 export class LegendItem{
@@ -14,13 +14,13 @@ export class LegendItem{
     private readonly header: Header | undefined;
     private readonly legendInfoBox: InfoBox | undefined;
     private readonly canvas: Canvas;
-    private readonly theme: Theme;
+    private readonly theme: VisualisationDirector;
 
     constructor(header: Header | undefined, legendInfoBox: InfoBox | undefined, canvas: Canvas) {
         this.header = header;
         this.legendInfoBox = legendInfoBox;
         this.canvas = canvas
-        this.theme = this.canvas.theme
+        this.theme = this.canvas.visDirector
 
         if(this.header) {
             this.root = this.header?.appendSVGElement('g')
@@ -37,33 +37,33 @@ export class LegendItem{
             .text('legend item')
             .attr('font-family', 'HatnoteVisNormal')
             .attr('font-size', '26px')
-            .attr('fill', this.theme.header_text_color)
-            .attr('x', this.theme.legend_item_circle_r + 10)
-            .attr('y', this.theme.header_height/2 + 8.5)
+            .attr('fill', this.theme.hatnoteTheme.header_text_color)
+            .attr('x', this.theme.hatnoteTheme.legend_item_circle_r + 10)
+            .attr('y', this.theme.hatnoteTheme.header_height/2 + 8.5)
             .attr('opacity', 1)
 
         this.smallTitle1 = this.root?.append('text')
             .text('legend item small1')
             .attr('font-family', 'HatnoteVisNormal')
             .attr('font-size', '16px')
-            .attr('fill', this.theme.header_text_color)
-            .attr('x', this.theme.legend_item_circle_r + 10)
-            .attr('y', this.theme.header_height/2 - 4)
+            .attr('fill', this.theme.hatnoteTheme.header_text_color)
+            .attr('x', this.theme.hatnoteTheme.legend_item_circle_r + 10)
+            .attr('y', this.theme.hatnoteTheme.header_height/2 - 4)
             .attr('opacity', 0)
 
         this.smallTitle2 = this.root?.append('text')
             .text('legend item small2')
             .attr('font-family', 'HatnoteVisNormal')
             .attr('font-size', '16px')
-            .attr('fill', this.theme.header_text_color)
-            .attr('x', this.theme.legend_item_circle_r + 10)
-            .attr('y', this.theme.header_height/2 + 13)
+            .attr('fill', this.theme.hatnoteTheme.header_text_color)
+            .attr('x', this.theme.hatnoteTheme.legend_item_circle_r + 10)
+            .attr('y', this.theme.hatnoteTheme.header_height/2 + 13)
             .attr('opacity', 0)
 
         this.circle = this.root?.append('circle')
-            .attr('r', this.theme.legend_item_circle_r)
+            .attr('r', this.theme.hatnoteTheme.legend_item_circle_r)
             .attr('cx', 0)
-            .attr('cy', this.theme.header_height/2)
+            .attr('cy', this.theme.hatnoteTheme.header_height/2)
             .attr('fill', '#000') // default value
     }
 
diff --git a/web/src/canvas/listen/listenToCanvas.ts b/web/src/canvas/listen/listenToCanvas.ts
deleted file mode 100644
index 4a864646380651a88377b5fac92486fff0a27e9d..0000000000000000000000000000000000000000
--- a/web/src/canvas/listen/listenToCanvas.ts
+++ /dev/null
@@ -1,136 +0,0 @@
-import {select, Selection} from "d3";
-import '../../style/normalize.css';
-import '../../style/main.css';
-import {CirclesLayer} from "./circles_layer";
-import {BannerLayer} from "../banner_layer";
-import {QRCode} from "../qr_code";
-import {Header} from "../header";
-import {InfoBox, InfoboxType} from "../info_box";
-import {Theme} from "../../theme/theme";
-import {BehaviorSubject, Subject} from "rxjs";
-import {BannerData, CircleData, DatabaseInfo, NetworkInfoboxData} from "../../observable/model";
-import {SettingsData} from "../../configuration/hatnote_settings";
-import {HatnoteVisService} from "../../service_event/model";
-import {Carousel} from "../carousel";
-import {Navigation} from "../navigation";
-import {MuteIcon} from "../mute_icon";
-import {Canvas} from "../canvas";
-
-export class ListenToCanvas extends Canvas {
-    public readonly circles_layer: CirclesLayer;
-    public readonly banner_layer:  BannerLayer;
-    public readonly qr_code: QRCode | undefined;
-    public readonly header: Header;
-    protected readonly _root: Selection<SVGSVGElement, unknown, null, any>;
-    public readonly navigation: Navigation | undefined;
-    public readonly info_box_websocket: InfoBox;
-    public readonly info_box_audio: InfoBox;
-    public readonly info_box_legend: InfoBox;
-    public readonly mute_icon: MuteIcon;
-    public readonly newBannerSubject: Subject<BannerData>
-    public readonly showAudioInfoboxObservable: Subject<boolean>
-    public readonly carousel: Carousel | undefined
-
-    constructor(theme: Theme, settings: SettingsData, newCircleSubject: Subject<CircleData>,
-                newBannerSubject: Subject<BannerData>,
-                showAudioInfoboxObservable: Subject<boolean>,
-                showNetworkInfoboxObservable: Subject<NetworkInfoboxData>,
-                updateVersionSubject: Subject<[string, number]>,
-                hatnoteVisServiceChangedSubject: BehaviorSubject<HatnoteVisService>,
-                updateDatabaseInfoSubject: Subject<DatabaseInfo>,
-                appContainer:  Selection<HTMLDivElement, unknown, null, undefined>){
-        super(theme, settings, newCircleSubject, showNetworkInfoboxObservable, updateVersionSubject,hatnoteVisServiceChangedSubject,updateDatabaseInfoSubject, appContainer)
-
-        this.newBannerSubject = newBannerSubject
-        this.showAudioInfoboxObservable = showAudioInfoboxObservable
-
-        // draw order matters in this function. Do not change without checking the result.
-        this._root = this.appContainer.append("svg")
-            .attr("width", this.width)
-            .attr("height", this.height)
-            .attr('fill', theme.svg_background_color)
-            .style('background-color', '#1c2733');
-
-        this.circles_layer = new CirclesLayer(this)
-        this.banner_layer = new BannerLayer(this)
-        if (!this.isMobileScreen) {
-            this.qr_code = new QRCode(this)
-        }
-        this.header = new Header(this, this.isMobileScreen)
-        // needs to be added last to the svg because it should draw over everything else
-        this.info_box_websocket = new InfoBox(this, InfoboxType.network_websocket_connecting, this.isMobileScreen, this.carousel, this.showAudioInfoboxObservable)
-        this.info_box_audio = new InfoBox(this, InfoboxType.audio_enable, this.isMobileScreen, this.carousel, this.showAudioInfoboxObservable)
-        this.info_box_legend = new InfoBox(this, InfoboxType.legend, this.isMobileScreen, this.carousel, this.showAudioInfoboxObservable)
-
-        if(settings.carousel_mode && !this.isMobileScreen){
-            this.carousel = new Carousel(this)
-        }
-
-        // needs to be added after the carousel transition because the transition layer spans over the entire screen
-        // which captures mouse clicks that otherwise would not arrive at the navigation buttons
-        if (this.isMobileScreen && !this.settings.embedded_mode) {
-            this.navigation = new Navigation(this)
-        }
-
-        // needs to be here because otherwise the transition animation and mobile navigation layer of the carousel would lay above the mute icon
-        // and block the cursor event of the mute icon
-        this.mute_icon = new MuteIcon(this)
-
-        this.renderCurrentTheme();
-
-        if(!settings.kiosk_mode && !settings.audio_mute){
-            this.mute_icon.show()
-        }
-
-        window.onresize = (_) => this.windowUpdate();
-    }
-
-    public renderCurrentTheme(){
-        // remove circles from other services
-        this.circles_layer.removeOtherServiceCircles(this.theme.current_service_theme)
-
-        // remove banner
-        this.banner_layer.removeBanner();
-
-        // update qr code
-        this.qr_code?.themeUpdate(this.theme.current_service_theme)
-
-        // update header logo
-        this.header.themeUpdate(this.theme.current_service_theme)
-
-        this.navigation?.themeUpdate(this.theme.current_service_theme)
-    }
-
-    // This method does not cover all ui elements. There is no requirement for this nor a need for a mobile version. People
-    // will use the website as a background animation. If you resize the window it is easier to just reload the page for a moment.
-    public windowUpdate() {
-        // update canvas root dimensions
-        this.width = window.innerWidth;
-        this.height = window.innerHeight;
-        this._root.attr("width", this.width).attr("height", this.height);
-
-        // update canvas header dimensions
-        this.header.windowUpdate()
-
-        // update banner
-        this.banner_layer.windowUpdate()
-
-        // update progress indicator
-        this.carousel?.windowUpdate()
-
-        // update qr_code
-        this.qr_code?.windowUpdate()
-
-        // update navigation
-        this.navigation?.windowUpdate()
-
-        // update websocket info box
-        this.info_box_websocket.windowUpdate()
-
-        // update audio info box
-        this.info_box_audio.windowUpdate()
-
-        // update mute icon
-        this.mute_icon.windowUpdate()
-    }
-}
\ No newline at end of file
diff --git a/web/src/canvas/listen/circle.ts b/web/src/canvas/listen/listenToCircle.ts
similarity index 86%
rename from web/src/canvas/listen/circle.ts
rename to web/src/canvas/listen/listenToCircle.ts
index c030057664c5087bbcfdaf4667dc7a0148543744..8d4142bf8ce98d1d21154bfad4319068956c72c6 100644
--- a/web/src/canvas/listen/circle.ts
+++ b/web/src/canvas/listen/listenToCircle.ts
@@ -1,10 +1,10 @@
 import {Selection} from "d3";
-import {CirclesLayer} from "./circles_layer";
 import {ServiceEvent} from "../../service_event/model";
 import {getRandomIntInclusive} from "../../util/random";
+import {ListenToCirclesLayer} from "./listenToCirclesLayer";
 
-export class Circle{
-    private readonly circlesLayer: CirclesLayer
+export class ListenToCircle{
+    private readonly circlesLayer: ListenToCirclesLayer
     private readonly root: Selection<SVGGElement, unknown, null, any>;
     private readonly ring: Selection<SVGCircleElement, unknown, null, any>
     private readonly circle_container: Selection<SVGGElement, unknown, null, any>
@@ -13,18 +13,18 @@ export class Circle{
     private no_label = false;
     private titleColor = '#fff'
 
-    constructor(circlesLayer: CirclesLayer, type: ServiceEvent, label_text: string, circle_radius: number, removeCircle: (serviceEvent: ServiceEvent) => void) {
+    constructor(circlesLayer: ListenToCirclesLayer, type: ServiceEvent, label_text: string, circle_radius: number, removeCircle: (serviceEvent: ServiceEvent) => void) {
         this.circlesLayer = circlesLayer
 
         // Otherwise the same label text will reset the seed to the same value which results in Math.random() returning the same number over and over
         //Math.seedrandom(label_text + Date.now())
         // give circle spawn a padding of 20
         let x = getRandomIntInclusive(20, circlesLayer.canvas.width - 20);
-        let y = getRandomIntInclusive(20 + circlesLayer.canvas.theme.header_height, circlesLayer.canvas.height - 20) ;
+        let y = getRandomIntInclusive(20 + circlesLayer.canvas.visDirector.hatnoteTheme.header_height, circlesLayer.canvas.height - 20) ;
 
         this.root = circlesLayer.appendSVGElement('g')
             .attr('transform', 'translate(' + x + ', ' + y + ')')
-            .attr('fill', circlesLayer.canvas.theme.circle_wave_color)
+            .attr('fill', circlesLayer.canvas.visDirector.hatnoteTheme.circle_wave_color)
             .style('opacity', 1.0)
 
         this.ring = this.root.append('circle')
@@ -41,7 +41,7 @@ export class Circle{
         this.circle_container = this.root.append('g')
 
         this.circle = this.circle_container.append('circle')
-            .attr('fill', this.circlesLayer.canvas.theme.getThemeColor(type))
+            .attr('fill', this.circlesLayer.canvas.visDirector.getThemeColor(type))
             .attr('r', circle_radius)
             .style('opacity', this.circlesLayer.canvas.settings.circle_start_opacity)
         this.circle.transition()
diff --git a/web/src/canvas/listen/circles_layer.ts b/web/src/canvas/listen/listenToCirclesLayer.ts
similarity index 67%
rename from web/src/canvas/listen/circles_layer.ts
rename to web/src/canvas/listen/listenToCirclesLayer.ts
index 22dcc38df12b5920f4abf8aa3741a7aff171f374..09d10ed8061df75de3e5e475a42d7e71386c8dd8 100644
--- a/web/src/canvas/listen/circles_layer.ts
+++ b/web/src/canvas/listen/listenToCirclesLayer.ts
@@ -1,37 +1,40 @@
 import {Selection} from "d3";
 import {ServiceTheme} from "../../theme/model";
-import {Circle} from "./circle";
 import {CircleData} from "../../observable/model";
 import {HatnoteVisService, ServiceEvent} from "../../service_event/model";
 import {Canvas} from "../canvas";
+import {ListenToVisualisation} from "./listenToVisualisation";
+import {ListenToCircle} from "./listenToCircle";
 
-export class CirclesLayer{
+export class ListenToCirclesLayer{
     private readonly root: Selection<SVGGElement, unknown, null, any>;
-    private readonly service_circles: Map<HatnoteVisService, Circle[]>;
+    private readonly service_circles: Map<HatnoteVisService, ListenToCircle[]>;
+    public readonly listenToVisualisation: ListenToVisualisation
     public readonly canvas: Canvas
 
-    constructor(canvas: Canvas) {
-        this.canvas = canvas
-        this.root = canvas.appendSVGElement('g').attr('id', 'circle_layer')
-        this.service_circles = new Map<HatnoteVisService, Circle[]>([
+    constructor(listenToVisualisation: ListenToVisualisation) {
+        this.listenToVisualisation = listenToVisualisation
+        this.canvas = this.listenToVisualisation.canvas
+            this.root = this.listenToVisualisation.appendSVGElement('g').attr('id', 'listen-to-circle-layer')
+        this.service_circles = new Map<HatnoteVisService, ListenToCircle[]>([
             [HatnoteVisService.Minerva, []],
             [HatnoteVisService.Keeper, []],
             [HatnoteVisService.Bloxberg, []],
         ])
-        canvas.newCircleSubject.subscribe({
+        this.canvas.newCircleSubject.subscribe({
             next: (value) => this.addCircle(value)
         })
     }
 
     public addCircle(circleData: CircleData) {
-        let service = this.canvas.theme.getHatnoteService(circleData.type)
+        let service = this.canvas.visDirector.getHatnoteService(circleData.type)
 
         if (isNaN(circleData.circle_radius) ||
-            this.canvas.theme.current_service_theme?.id_name !== service){
+            this.canvas.visDirector.current_service_theme?.id_name !== service){
             return
         }
 
-        let circle = new Circle(this,circleData.type,circleData.label_text,circleData.circle_radius,
+        let circle = new ListenToCircle(this,circleData.type,circleData.label_text,circleData.circle_radius,
             (serviceEvent) => this.removeOldestCircle(serviceEvent))
 
         if(service !== undefined){
@@ -46,7 +49,7 @@ export class CirclesLayer{
     }
 
     public removeOldestCircle(serviceEvent: ServiceEvent){
-        let service = this.canvas.theme.getHatnoteService(serviceEvent)
+        let service = this.canvas.visDirector.getHatnoteService(serviceEvent)
 
         if(service !== undefined){
             // when adding to the end of the list we can delete the first entry if a circle has finished its animation
diff --git a/web/src/canvas/listen/listenToVisualisation.ts b/web/src/canvas/listen/listenToVisualisation.ts
new file mode 100644
index 0000000000000000000000000000000000000000..418dd07f80a0b6ddb211addb6112d42d13a84cda
--- /dev/null
+++ b/web/src/canvas/listen/listenToVisualisation.ts
@@ -0,0 +1,40 @@
+import {Selection} from "d3";
+import '../../style/normalize.css';
+import '../../style/main.css';
+import {Canvas} from "../canvas";
+import {ListenToCirclesLayer} from "./listenToCirclesLayer";
+import {Visualisation} from "../../theme/model";
+
+export class ListenToVisualisation {
+    public readonly circles_layer: ListenToCirclesLayer;
+    protected readonly root: Selection<SVGGElement, unknown, null, any>;
+    public readonly canvas: Canvas;
+
+    constructor(canvas: Canvas){
+        this.canvas = canvas;
+
+        this.root = canvas.appendSVGElement('g').attr("id", "liston-to-visualisation")
+
+        // draw order matters in this function. Do not change without checking the result.
+        this.root
+            .attr('fill', this.canvas.visDirector.hatnoteTheme.svg_background_color)
+            .style('background-color', '#1c2733');
+
+        this.circles_layer = new ListenToCirclesLayer(this)
+
+    }
+
+    public renderCurrentTheme(){
+        if(this.canvas.visDirector.current_visualisation === Visualisation.geo){
+            this.root.attr("opacity", "0")
+        } else {
+            this.root.attr("opacity", "1")
+        }
+
+        this.circles_layer.removeOtherServiceCircles(this.canvas.visDirector.current_service_theme)
+    }
+
+    public appendSVGElement(type: string): Selection<SVGGElement, unknown, null, any> {
+        return this.root.append(type)
+    }
+}
\ No newline at end of file
diff --git a/web/src/canvas/mute_icon.ts b/web/src/canvas/mute_icon.ts
index c59729fbfbc72e04833708934d0970f5f6f669b1..7f45f8864c3ca19151499cf5ce2e5454ebdc6561 100644
--- a/web/src/canvas/mute_icon.ts
+++ b/web/src/canvas/mute_icon.ts
@@ -1,6 +1,6 @@
 import {Selection} from "d3";
-import {ListenToCanvas} from "./listen/listenToCanvas";
 import QrCodeMinerva from "../../assets/images/volume_off_FILL1_wght400_GRAD0_opsz24.svg";
+import {Canvas} from "./canvas";
 
 export class MuteIcon{
     private readonly root: Selection<SVGGElement, unknown, null, any>;
@@ -10,10 +10,10 @@ export class MuteIcon{
     private readonly background: Selection<SVGRectElement, unknown, null, any>;
     private readonly image_width = 100;
     private readonly text_color = '#fff';
-    private readonly canvas: ListenToCanvas;
+    private readonly canvas: Canvas;
 
     // consists not only of the icon but also spans a transparent clickable container above everything
-    constructor(canvas: ListenToCanvas) {
+    constructor(canvas: Canvas) {
         this.canvas = canvas
         this.root = canvas.appendSVGElement('g')
             .attr('id', 'qr_code')
diff --git a/web/src/canvas/navigation.ts b/web/src/canvas/navigation.ts
index ba81a2dbe7a4f134ad55837f8a9198e8b2cd74c1..94d3c53a5497fe5cee127f0d6b8ed11fae0e6f37 100644
--- a/web/src/canvas/navigation.ts
+++ b/web/src/canvas/navigation.ts
@@ -1,12 +1,12 @@
 import {Selection} from "d3";
-import {ListenToCanvas} from "./listen/listenToCanvas";
 import {IconButton} from "./icon_button";
 import {ServiceTheme} from "../theme/model";
 import {LegendItem} from "./legend_item";
 import {InfoboxType} from "./info_box";
+import {Canvas} from "./canvas";
 
 export class Navigation{
-    public readonly canvas: ListenToCanvas;
+    public readonly canvas: Canvas;
     public readonly root: Selection<SVGGElement, unknown, null, any>;
     private readonly backButton: IconButton;
     private readonly nextButton: IconButton;
@@ -16,7 +16,7 @@ export class Navigation{
     private currentServiceIndex = 0;
     private readonly legend_items: LegendItem[] = [];
 
-    constructor(canvas: ListenToCanvas) {
+    constructor(canvas: Canvas) {
         this.canvas = canvas
 
         this.root = canvas.appendSVGElement('g').attr('id', 'navigation')
@@ -47,7 +47,7 @@ export class Navigation{
     }
 
     private nextService(nav: Navigation){
-        nav.currentServiceIndex = (nav.currentServiceIndex + 1) % nav.canvas.theme.carousel_service_order.length
+        nav.currentServiceIndex = (nav.currentServiceIndex + 1) % nav.canvas.visDirector.carousel_service_order.length
         nav.changeTheme()
     }
 
@@ -72,10 +72,11 @@ export class Navigation{
     }
 
     private changeTheme(){
-        let nextService = this.canvas.theme.carousel_service_order[this.currentServiceIndex]
-        this.canvas.theme.set_current_theme(nextService);
+        let nextService = this.canvas.visDirector.carousel_service_order[this.currentServiceIndex]
+        this.canvas.visDirector.set_current_theme(nextService);
         this.canvas.renderCurrentTheme()
-        this.canvas.hatnoteVisServiceChangedSubject.next(this.canvas.theme.current_service_theme.id_name)
+        this.canvas.onThemeHasChanged.next(
+            [this.canvas.visDirector.current_service_theme.id_name, this.canvas.visDirector.current_visualisation])
     }
 
     private clearLegendItems(){
@@ -96,7 +97,7 @@ export class Navigation{
     public windowUpdate() {
         this.setPosition()
 
-        this.canvas.theme.current_service_theme.legend_items.forEach((theme_legend_item, i) => {
+        this.canvas.visDirector.current_service_theme.legend_items.forEach((theme_legend_item, i) => {
             if(i < this.legend_items.length) {
                 this.legend_items[i].windowUpdate(theme_legend_item)
             }
diff --git a/web/src/canvas/progress_indicator.ts b/web/src/canvas/progress_indicator.ts
index 0b595ba90cb389f0b2070d77825af1f13e568117..b4eea84c9d53552ef59e52531d45e3b5e2742471 100644
--- a/web/src/canvas/progress_indicator.ts
+++ b/web/src/canvas/progress_indicator.ts
@@ -1,8 +1,7 @@
 import { easeLinear, Selection, transition} from "d3";
-import {ListenToCanvas} from "./listen/listenToCanvas";
 import {ServiceTheme} from "../theme/model";
 import {HatnoteVisService} from "../service_event/model";
-import {GeoCanvas} from "./geo/geoCanvas";
+import {Canvas} from "./canvas";
 
 export class ProgressIndicator{
     public readonly service_indicators: Map<HatnoteVisService, ServiceProgressIndicator>;
@@ -12,12 +11,12 @@ export class ProgressIndicator{
         return this._currentServiceIndicator;
     }
 
-    private readonly canvas: ListenToCanvas | GeoCanvas
+    private readonly canvas: Canvas
 
-    constructor(canvas: ListenToCanvas | GeoCanvas) {
+    constructor(canvas: Canvas) {
         this.canvas = canvas
         this.service_indicators = new Map<HatnoteVisService, ServiceProgressIndicator>()
-        canvas.theme.service_themes.forEach(service => {
+        canvas.visDirector.service_themes.forEach(service => {
             this.service_indicators.set(service.id_name, new ServiceProgressIndicator(canvas, service))
         });
         this._currentServiceIndicator = this.service_indicators.get(HatnoteVisService.Minerva);
@@ -28,7 +27,7 @@ export class ProgressIndicator{
     }
 
     windowUpdate() {
-        let progress_indicator_width = this.canvas.theme.progress_indicator_width(this.canvas.width);
+        let progress_indicator_width = this.canvas.visDirector.progress_indicator_width(this.canvas.width);
         this.service_indicators.forEach(indicator => {
             indicator.windowUpdate(progress_indicator_width)
         })
@@ -42,27 +41,27 @@ class ServiceProgressIndicator{
     private readonly textBox: Selection<SVGRectElement, unknown, null, any>
     private readonly text: Selection<SVGTextElement, unknown, null, any>
     public readonly service_id: HatnoteVisService
-    private readonly canvas: ListenToCanvas | GeoCanvas
+    private readonly canvas: Canvas
 
-    constructor(canvas: ListenToCanvas | GeoCanvas, service_theme: ServiceTheme) {
+    constructor(canvas: Canvas, service_theme: ServiceTheme) {
         this.canvas = canvas;
-        let progress_indicator_width = canvas.theme.progress_indicator_width(canvas.width);
-        let pos_x = canvas.theme.progress_indicator_pos_x(service_theme.id_name, canvas.width, progress_indicator_width, canvas.theme.progress_indicator_gap_width);
+        let progress_indicator_width = canvas.visDirector.progress_indicator_width(canvas.width);
+        let pos_x = canvas.visDirector.progress_indicator_pos_x(service_theme.id_name, canvas.width, progress_indicator_width, canvas.visDirector.hatnoteTheme.progress_indicator_gap_width);
         let showIndicator = canvas.settings.carousel_mode
 
         this.root = canvas.appendSVGElement('g').attr('id', 'progress_' + service_theme.name)
-            .attr('transform', 'translate(' + pos_x + ', ' + (canvas.height - canvas.theme.progress_indicator_y_padding) + ')')
+            .attr('transform', 'translate(' + pos_x + ', ' + (canvas.height - canvas.visDirector.hatnoteTheme.progress_indicator_y_padding) + ')')
             .attr('opacity', showIndicator ? 1 : 0);
 
         this.bg = this.root.append('rect')
             .attr('width', progress_indicator_width)
-            .attr('height', canvas.theme.progress_indicator_height)
-            .attr('fill', canvas.theme.progress_indicator_bg_color)
+            .attr('height', canvas.visDirector.hatnoteTheme.progress_indicator_height)
+            .attr('fill', canvas.visDirector.hatnoteTheme.progress_indicator_bg_color)
 
         this.fg = this.root.append('rect')
             .attr('width', 0)
-            .attr('height', canvas.theme.progress_indicator_height)
-            .attr('fill', canvas.theme.progress_indicator_fg_color)
+            .attr('height', canvas.visDirector.hatnoteTheme.progress_indicator_height)
+            .attr('fill', canvas.visDirector.hatnoteTheme.progress_indicator_fg_color)
 
         this.textBox = this.root.append('rect')
             .attr('opacity', 0)
@@ -71,7 +70,7 @@ class ServiceProgressIndicator{
             .attr('height', '30')
             .attr('rx', 7)
             .attr('ry', 7)
-            .attr('fill', this.canvas.theme.progress_indicator_error_color)
+            .attr('fill', this.canvas.visDirector.hatnoteTheme.progress_indicator_error_color)
 
         this.text = this.root.append('text')
             .attr('opacity', 0)
@@ -96,29 +95,29 @@ class ServiceProgressIndicator{
 
     public start(onEnd: () => void){
         const t = transition()
-            .duration(this.canvas.theme.current_service_theme.carousel_time)
+            .duration(this.canvas.visDirector.current_service_theme.carousel_time)
             .ease(easeLinear);
         this.showTextBox(false);
-        this.bg.attr('fill', this.canvas.theme.progress_indicator_bg_color)
-        let progress_indicator_width = this.canvas.theme.progress_indicator_width(this.canvas.width);
+        this.bg.attr('fill', this.canvas.visDirector.hatnoteTheme.progress_indicator_bg_color)
+        let progress_indicator_width = this.canvas.visDirector.progress_indicator_width(this.canvas.width);
         this.fg.transition(t).attr('width', progress_indicator_width).on('end', onEnd)
     }
 
     public reset() {
-        this.bg.attr('fill', this.canvas.theme.progress_indicator_bg_color)
+        this.bg.attr('fill', this.canvas.visDirector.hatnoteTheme.progress_indicator_bg_color)
         this.fg.attr('width', 0);
     }
 
     public setError(){
         this.fg.interrupt();
-        this.bg.attr('fill', this.canvas.theme.progress_indicator_error_color);
+        this.bg.attr('fill', this.canvas.visDirector.hatnoteTheme.progress_indicator_error_color);
         this.fg.attr('width', 0);
         this.showTextBox(true);
     }
 
     public windowUpdate(progress_indicator_width: number, ) {
-        let pos_x = this.canvas.theme.progress_indicator_pos_x(this.service_id, this.canvas.width, progress_indicator_width, this.canvas.theme.progress_indicator_gap_width);
-        this.root.attr('transform', 'translate(' + pos_x + ', ' + (this.canvas.height - this.canvas.theme.progress_indicator_y_padding) + ')');
+        let pos_x = this.canvas.visDirector.progress_indicator_pos_x(this.service_id, this.canvas.width, progress_indicator_width, this.canvas.visDirector.hatnoteTheme.progress_indicator_gap_width);
+        this.root.attr('transform', 'translate(' + pos_x + ', ' + (this.canvas.height - this.canvas.visDirector.hatnoteTheme.progress_indicator_y_padding) + ')');
         this.bg.attr('width', progress_indicator_width)
     }
 }
\ No newline at end of file
diff --git a/web/src/canvas/qr_code.ts b/web/src/canvas/qr_code.ts
index 3f99ac12b414e90e29425feafaa78f947e22a4cb..a4551b09589c74172050082a7cda2548ad586e24 100644
--- a/web/src/canvas/qr_code.ts
+++ b/web/src/canvas/qr_code.ts
@@ -1,7 +1,7 @@
 import {Selection} from "d3";
 import QrCodeMinerva from "../../assets/images/qr-code-minerva.png";
-import {ListenToCanvas} from "./listen/listenToCanvas";
-import {ServiceTheme} from "../theme/model";
+import {ServiceTheme, Visualisation} from "../theme/model";
+import {Canvas} from "./canvas";
 
 export class QRCode{
     private readonly root: Selection<SVGGElement, unknown, null, any>;
@@ -12,9 +12,9 @@ export class QRCode{
     private readonly image_width = 100;
     private readonly image_right_padding = 50;
     private readonly text_color = '#5d7da1';
-    private readonly canvas: ListenToCanvas;
+    private readonly canvas: Canvas;
 
-    constructor(canvas: ListenToCanvas) {
+    constructor(canvas: Canvas) {
         this.canvas = canvas
         this.root = canvas.appendSVGElement('g').attr('id', 'qr_code')
         this.image = this.root.append('image')
@@ -36,10 +36,12 @@ export class QRCode{
             .attr('text-anchor', 'middle')
 
         this.setPosition()
+        this.setOpacity()
     }
 
     public windowUpdate(){
         this.setPosition();
+        this.setOpacity()
     }
 
     private setPosition(){
@@ -50,9 +52,18 @@ export class QRCode{
         this.line2.attr('x', text_x).attr('dy', 16)
     }
 
+    private setOpacity(){
+        if(!this.canvas.isMobileScreen) {
+            this.root.attr("opacity", 1)
+        } else {
+            this.root.attr("opacity", 0)
+        }
+    }
+
     public themeUpdate(currentServiceTheme: ServiceTheme) {
         this.image.attr('href', currentServiceTheme.qr_code.image)
         this.line1.text(currentServiceTheme.qr_code.line1)
         this.line2.text(currentServiceTheme.qr_code.line2)
+        this.setOpacity()
     }
 }
\ No newline at end of file
diff --git a/web/src/canvas/transition.ts b/web/src/canvas/transition.ts
index a00c8b78a9ef6267d70763e097f383e985ce9315..98207fc0955a2233cd4e69c7e072fd2dc0751947 100644
--- a/web/src/canvas/transition.ts
+++ b/web/src/canvas/transition.ts
@@ -1,11 +1,10 @@
 import {easeBackOut, easeCircleOut, easeCubicOut, easeExpOut, easeQuadOut, Selection} from "d3";
-import {ListenToCanvas} from "./listen/listenToCanvas";
 import MpdlLogo from "../../assets/images/logo-mpdl-twocolor-dark-var1.png";
 import {ServiceTheme} from "../theme/model";
 import {HatnoteVisService} from "../service_event/model";
-import {GeoCanvas} from "./geo/geoCanvas";
 import {Subject} from "rxjs";
 import {NetworkInfoboxData} from "../observable/model";
+import {Canvas} from "./canvas";
 
 export class Transition{
     private readonly root: Selection<SVGGElement, unknown, null, any>;
@@ -20,12 +19,12 @@ export class Transition{
     private readonly mpdl_logo:  Selection<SVGImageElement, unknown, null, any>;
     private readonly text: Selection<SVGTextElement, unknown, null, any>;
     private readonly service_logo: Selection<SVGImageElement, unknown, null, any>;
-    private readonly canvas: ListenToCanvas | GeoCanvas;
+    private readonly canvas: Canvas;
     public readonly onTransitionStart: Subject<void>
     public readonly onTransitionMid: Subject<void>
     public readonly onTransitionEnd: Subject<void>
 
-    constructor(canvas: ListenToCanvas | GeoCanvas) {
+    constructor(canvas: Canvas) {
         this.canvas = canvas
         this.root = canvas.appendSVGElement('g').attr('id', 'transition_layer').attr('opacity', 0)
 
diff --git a/web/src/configuration/hatnote_settings.ts b/web/src/configuration/hatnote_settings.ts
index d8698473703ed55c61710a74c8ad6e34bf678c94..b284257a5d983a6df314abcd3ecd3547b4cdfc81 100644
--- a/web/src/configuration/hatnote_settings.ts
+++ b/web/src/configuration/hatnote_settings.ts
@@ -26,7 +26,8 @@ export class HatnoteSettings {
             circle_radius_max: window.innerHeight/2,
             circle_radius_min: 3,
             help: false,
-            map: false
+            map: false,
+            mixed: false
         }
 
         this.loadUrlParameters()
@@ -47,6 +48,10 @@ export class HatnoteSettings {
             this._settings_data.map = true;
         }
 
+        if(url_search_parameters.has("mixed")){
+            this._settings_data.mixed = true;
+        }
+
         if(url_search_parameters.has("mute")){
             this._settings_data.audio_mute = true;
         }
@@ -170,6 +175,10 @@ export class HatnoteSettings {
                 this._settings_data.initialService = HatnoteVisService.Minerva
                 this._settings_data.carousel_mode = true;
         }
+
+        if (this._settings_data.kiosk_mode){
+            this._settings_data.mixed = true;
+        }
     }
 }
 
@@ -191,5 +200,6 @@ export interface SettingsData{
     circle_radius_max: number,
     circle_radius_min: number,
     help: boolean,
-    map: boolean
+    map: boolean,
+    mixed: boolean
 }
\ No newline at end of file
diff --git a/web/src/help/help_page.ts b/web/src/help/help_page.ts
index e48de5ad326e4dbee7aee93a1713e9371384256e..4b8df184bb306f6c10f0dc58898fc51e62cb1bc1 100644
--- a/web/src/help/help_page.ts
+++ b/web/src/help/help_page.ts
@@ -13,6 +13,8 @@ export class HelpPage {
         root.append("p").html('Following url parameters are supported:')
         this.list = root.append("ul")
         this.createHelpListItem('mute', 'mutes sounds.', this.baseUrl+'?<b>mute</b>', `${settings.audio_mute}`)
+        this.createHelpListItem('map', 'shows the geographic visualisation.', this.baseUrl+'?<b>map</b>', `${settings.map}`)
+        this.createHelpListItem('mixed', 'if the carousel mode is activated and map mode deactivated this flag will cause the carousel mix visualisation types.', this.baseUrl+'?<b>mixed</b>', `${settings.mixed}`)
         this.createHelpListItem('service=${option}', 'displays given service. Disables carousel. Options: keeper, minerva, bloxberg.', this.baseUrl+'?<b>service=bloxberg</b>', `carousel mode enabled`)
         this.createHelpListItem('carousel-time=${minerva},${keeper},${bloxberg}', 'modifies the carousel display time for individual services. All values must be given and are read in milliseconds.', this.baseUrl+'?<b>carousel-time=20000,10000,14000</b>', `${settings.carousel_time[0]},${settings.carousel_time[1]},${settings.carousel_time[2]}`)
         this.createHelpListItem('audio-protection=${time}', 'timeframe in which successive events will be muted. Time is read in milliseconds.', this.baseUrl+'?<b>audio-protection=200</b>', `${settings.audioProtection}`)
@@ -24,6 +26,8 @@ export class HelpPage {
         this.createHelpListItem('debug', 'enables debug mode that will generate output in the javascript console.', this.baseUrl+'?<b>debug</b>', `${settings.debug_mode}`)
         this.createHelpListItem('help', 'shows help page.', ` Example: ${this.baseUrl}?<b>help</b>`, `${settings.help}`)
         root.append("p").html(`Url parameters can be combined with "<b>&</b>". Order does not matter. <u>Example</u>: ${this.baseUrl}?mute<b>&</b>service=bloxberg`)
+        root.append("h2").html('GIS (Geographic Information System) for hatnote')
+        root.append("p").html(`Geographic information for hatnote can be modified at <a href="http://gis.hatnote.mpdl.mpg.de" target="_blank">http://gis.hatnote.mpdl.mpg.de</a>`)
 
         document.body.setAttribute("style", "overflow: auto")
     }
diff --git a/web/src/main.ts b/web/src/main.ts
index dc9313306fdbeb1d38e66103972d094b6213f71b..bc2737631c6145ff7d2c61f9ba13a3857d92b664 100644
--- a/web/src/main.ts
+++ b/web/src/main.ts
@@ -1,15 +1,15 @@
 import {BehaviorSubject, Subject} from "rxjs";
 import {BannerData, CircleData, DatabaseInfo, NetworkInfoboxData} from "./observable/model";
-import {ListenToCanvas} from "./canvas/listen/listenToCanvas";
 import {HatnoteAudio} from "./audio/hatnote_audio";
 import {HatnoteSettings} from "./configuration/hatnote_settings";
-import {Theme} from "./theme/theme";
+import {VisualisationDirector} from "./theme/visualisationDirector";
 import {EventBridge} from "./service_event/event_bridge";
 import {WebsocketManager} from "./websocket/websocket";
 import {HatnoteVisService} from "./service_event/model";
 import {HelpPage} from "./help/help_page";
-import {GeoCanvas} from "./canvas/geo/geoCanvas";
 import {select} from "d3";
+import {Canvas} from "./canvas/canvas";
+import {Visualisation} from "./theme/model";
 
 main();
 
@@ -27,35 +27,28 @@ function main(){
     }
 
     // load theme
-    let theme = new Theme(settings_data);
+    let theme = new VisualisationDirector(settings_data);
 
     // create observables
     let newCircleSubject: Subject<CircleData> = new Subject()
     let newBannerSubject: Subject<BannerData> = new Subject()
-    let hatnoteVisServiceChangedSubject: BehaviorSubject<HatnoteVisService> = new BehaviorSubject(theme.current_service_theme.id_name)
-    let showAudioInfoboxSubject: Subject<boolean> = new Subject()
+    let onCarouselTransitionStart: BehaviorSubject<[HatnoteVisService, Visualisation]> = new BehaviorSubject([theme.current_service_theme.id_name, theme.current_visualisation])
+    let onCarouselTransitionMid: BehaviorSubject<[HatnoteVisService, Visualisation]> = new BehaviorSubject([theme.current_service_theme.id_name, theme.current_visualisation])
+    let onCarouselTransitionEnd: BehaviorSubject<[HatnoteVisService, Visualisation]> = new BehaviorSubject([theme.current_service_theme.id_name, theme.current_visualisation])
     let showWebsocketInfoboxSubject: Subject<NetworkInfoboxData> = new Subject()
     let updateDatabaseInfoSubject: Subject<DatabaseInfo> = new Subject()
     let updateVersionSubject: Subject<[string,number]> = new Subject()
 
     // build canvas
-    if (settings_data.map) {
-        new GeoCanvas(theme, settings_data, newCircleSubject,
-            showWebsocketInfoboxSubject, updateVersionSubject, hatnoteVisServiceChangedSubject, updateDatabaseInfoSubject, select(appContainer))
-    } else {
-        new ListenToCanvas(theme, settings_data, newCircleSubject, newBannerSubject,
-            showAudioInfoboxSubject, showWebsocketInfoboxSubject, updateVersionSubject, hatnoteVisServiceChangedSubject, updateDatabaseInfoSubject,select(appContainer))
-    }
+    new Canvas(theme, settings_data, newCircleSubject,
+        showWebsocketInfoboxSubject, updateVersionSubject, onCarouselTransitionStart, onCarouselTransitionMid,onCarouselTransitionEnd, updateDatabaseInfoSubject, newBannerSubject, select(appContainer))
 
     // load audio
-    let audio;
-    if (!settings_data.map) {
-        audio = new HatnoteAudio(settings_data, showAudioInfoboxSubject);
-    }
+    let audio = new HatnoteAudio(settings_data);
 
     // init event bridge
     let event_bridge = new EventBridge(audio, newCircleSubject, newBannerSubject, updateVersionSubject,
-        hatnoteVisServiceChangedSubject, settings_data)
+        onCarouselTransitionStart, onCarouselTransitionMid,onCarouselTransitionEnd, settings_data)
 
     // start websocket
     new WebsocketManager(settings_data, showWebsocketInfoboxSubject, updateDatabaseInfoSubject, event_bridge);
diff --git a/web/src/service_event/event_bridge.ts b/web/src/service_event/event_bridge.ts
index 85a2641118a9a8cb997318e046b686bf52dc91ad..12529f57c1c0945c4a4ee609d1db9be0528f1698 100644
--- a/web/src/service_event/event_bridge.ts
+++ b/web/src/service_event/event_bridge.ts
@@ -8,12 +8,14 @@ import {
     BloxbergTransformedData,
     DelayedCircleEvent,
     HatnoteVisService,
-    KeeperTransformedData, MinervaTransformedData
+    KeeperTransformedData,
+    MinervaTransformedData
 } from "./model";
 import {EventBuffer} from "./event_buffer";
 import {SettingsData} from "../configuration/hatnote_settings";
 import {KeeperTransformer} from "./keeper_transformer";
 import {MinervaTransformer} from "./minerva_transformer";
+import {Visualisation} from "../theme/model";
 
 export class EventBridge{
     private bloxbergTransformer: BloxbergTransformer;
@@ -22,38 +24,53 @@ export class EventBridge{
     private audio: HatnoteAudio | undefined;
     private newCircleSubject: Subject<CircleData>;
     private newBannerSubject: Subject<BannerData>;
-    private hatnoteVisServiceChangedSubject: BehaviorSubject<HatnoteVisService>
+    private onCarouselTransitionStart: BehaviorSubject<[HatnoteVisService, Visualisation]>
+    private onThemeHasChanged: BehaviorSubject<[HatnoteVisService, Visualisation]>
     private eventBuffer: EventBuffer;
     private updateVersionSubject: Subject<[string,number]>
     private _currentService: HatnoteVisService
+    private _currentVisualisation: Visualisation
     private settings_data: SettingsData
     private readonly event_delay_protection: number
     public get currentService(): HatnoteVisService{
         return this._currentService;
     }
+
+    public get currentVisualisation(): Visualisation{
+        return this._currentVisualisation;
+    }
     constructor(audio: HatnoteAudio | undefined, newCircleSubject: Subject<CircleData>, newBanenrSubject: Subject<BannerData>,
-                updateVersionSubject: Subject<[string,number]>, hatnoteVisServiceChangedSubject: BehaviorSubject<HatnoteVisService>,
+                updateVersionSubject: Subject<[string,number]>,
+                onCarouselTransitionStart: BehaviorSubject<[HatnoteVisService, Visualisation]>,
+                onCarouselTransitionMid: BehaviorSubject<[HatnoteVisService, Visualisation]>,
+                onCarouselTransitionEnd: BehaviorSubject<[HatnoteVisService, Visualisation]>,
                 settings_data: SettingsData) {
         this.settings_data = settings_data
         this.event_delay_protection = settings_data.event_delay_protection
         this._currentService = settings_data.initialService
+        this._currentVisualisation = settings_data.map ? Visualisation.geo : Visualisation.listenTo
         this.updateVersionSubject = updateVersionSubject
         this.newBannerSubject = newBanenrSubject
-        this.hatnoteVisServiceChangedSubject = hatnoteVisServiceChangedSubject
+        this.onCarouselTransitionStart = onCarouselTransitionStart
+        this.onThemeHasChanged = onCarouselTransitionMid
         this.minervaTransformer = new MinervaTransformer()
         this.bloxbergTransformer = new BloxbergTransformer(settings_data)
         this.keeperTransformer = new KeeperTransformer(settings_data)
         this.audio = audio
         this.newCircleSubject = newCircleSubject
         this.eventBuffer = new EventBuffer(this.settings_data.default_event_buffer_timespan,
-            (value) => this.publishCircleEvent(value), this.settings_data.map)
+            (value) => this.publishCircleEvent(value), this)
 
-        this.hatnoteVisServiceChangedSubject.subscribe({
+        this.onCarouselTransitionStart.subscribe({
             next: (value) => {
-                this._currentService = value
-                if (settings_data.carousel_mode) {
-                    this.audio?.play_transition_sound()
-                }
+                this.audio?.play_transition_sound()
+            }
+        })
+
+        this.onThemeHasChanged.subscribe({
+            next: (value) => {
+                this._currentService = value[0]
+                this._currentVisualisation = value[1]
             }
         })
     }
@@ -176,7 +193,9 @@ export class EventBridge{
         // browser stops animations to save energy and to increase performance when a tab is inactive
         if (!document.hidden){
             for (const delayedCircleEvent of circleEvents) {
-                this.audio?.play_sound(delayedCircleEvent.radius, delayedCircleEvent.event)
+                if(this._currentVisualisation === Visualisation.listenTo){
+                    this.audio?.play_sound(delayedCircleEvent.radius, delayedCircleEvent.event)
+                }
                 this.newCircleSubject.next({label_text: delayedCircleEvent.title,
                     circle_radius: delayedCircleEvent.radius, type: delayedCircleEvent.event,
                     location: delayedCircleEvent.location})
@@ -186,7 +205,9 @@ export class EventBridge{
 
     public publishBannerEvent(bannerEvent: BannerEvent){
         if (!document.hidden){
-            this.audio?.play_sound(0, bannerEvent.event)
+            if(this._currentVisualisation === Visualisation.listenTo) {
+                this.audio?.play_sound(0, bannerEvent.event)
+            }
             this.newBannerSubject.next({message: bannerEvent.title, serviceEvent: bannerEvent.event})
         }
     }
diff --git a/web/src/service_event/event_buffer.ts b/web/src/service_event/event_buffer.ts
index 9f8c5461a9a8c158a910013dd0267cad11bdd9fb..6c75a8e83c57d1f41f7f22cbd8e042168e1aef13 100644
--- a/web/src/service_event/event_buffer.ts
+++ b/web/src/service_event/event_buffer.ts
@@ -1,22 +1,24 @@
 import {EventBufferData} from "./event_buffer_data";
 import {DelayedCircleEvent, ServiceEvent} from "./model";
 import {getRandomIntInclusive} from "../util/random";
+import {EventBridge} from "./event_bridge";
+import {Visualisation} from "../theme/model";
 
 export class EventBuffer {
     private readonly eventBuffer: Map<ServiceEvent, EventBufferData>;
-    private readonly hatnote_map: boolean;
+    public readonly eventBridge: EventBridge;
 
-    constructor(default_event_buffer_timespan: number, publishCircleEvent: (circleEvent: DelayedCircleEvent[]) => void, hatnote_map: boolean) {
-        this.hatnote_map = hatnote_map;
+    constructor(default_event_buffer_timespan: number, publishCircleEvent: (circleEvent: DelayedCircleEvent[]) => void, eventBridge: EventBridge) {
+        this.eventBridge = eventBridge;
         this.eventBuffer = new Map([
-            [ServiceEvent.bloxberg_block, new EventBufferData(publishCircleEvent, hatnote_map, default_event_buffer_timespan)],
-            [ServiceEvent.bloxberg_confirmed_transaction, new EventBufferData(publishCircleEvent, hatnote_map, default_event_buffer_timespan, hatnote_map ? 1000 : 1200)],
-            [ServiceEvent.keeper_file_create, new EventBufferData(publishCircleEvent, hatnote_map, 1000,1000)], // Keeper only returns time precision of seconds
-            [ServiceEvent.keeper_file_edit, new EventBufferData(publishCircleEvent, hatnote_map, 1000,1000)], // Keeper only returns time precision of seconds
-            [ServiceEvent.keeper_new_library, new EventBufferData(publishCircleEvent, hatnote_map, 1000,1000)], // Keeper only returns time precision of seconds
-            [ServiceEvent.minerva_direct_message, new EventBufferData(publishCircleEvent, hatnote_map, default_event_buffer_timespan)],
-            [ServiceEvent.minerva_private_message, new EventBufferData(publishCircleEvent, hatnote_map, default_event_buffer_timespan)],
-            [ServiceEvent.minerva_public_message, new EventBufferData(publishCircleEvent, hatnote_map, default_event_buffer_timespan)],
+            [ServiceEvent.bloxberg_block, new EventBufferData(publishCircleEvent, this, default_event_buffer_timespan)],
+            [ServiceEvent.bloxberg_confirmed_transaction, new EventBufferData(publishCircleEvent, this, default_event_buffer_timespan,1200)],
+            [ServiceEvent.keeper_file_create, new EventBufferData(publishCircleEvent, this, 1000,1000)], // Keeper only returns time precision of seconds
+            [ServiceEvent.keeper_file_edit, new EventBufferData(publishCircleEvent, this, 1000,1000)], // Keeper only returns time precision of seconds
+            [ServiceEvent.keeper_new_library, new EventBufferData(publishCircleEvent, this, 1000,1000)], // Keeper only returns time precision of seconds
+            [ServiceEvent.minerva_direct_message, new EventBufferData(publishCircleEvent, this, default_event_buffer_timespan)],
+            [ServiceEvent.minerva_private_message, new EventBufferData(publishCircleEvent, this, default_event_buffer_timespan)],
+            [ServiceEvent.minerva_public_message, new EventBufferData(publishCircleEvent, this, default_event_buffer_timespan)],
         ]);
     }
 
@@ -37,7 +39,7 @@ export class EventBuffer {
                 switch (circleEvent) {
                     case ServiceEvent.keeper_file_create:
                     case ServiceEvent.keeper_file_edit:
-                        if (!that.hatnote_map) {
+                        if (that.eventBridge.currentVisualisation !== Visualisation.geo) {
                             let splitRandom = getRandomIntInclusive(1,4)
                             eventBufferData?.splitBufferAndRelease(splitRandom)
                         } else {
@@ -45,7 +47,7 @@ export class EventBuffer {
                         }
                         break;
                     case ServiceEvent.bloxberg_confirmed_transaction:
-                        if (!that.hatnote_map) {
+                        if (that.eventBridge.currentVisualisation !== Visualisation.geo) {
                             let splitRandomBloxberg = 3
                             eventBufferData?.splitBufferAndRelease(splitRandomBloxberg)
                         } else {
diff --git a/web/src/service_event/event_buffer_data.ts b/web/src/service_event/event_buffer_data.ts
index 29e51eed7e2f548bf4f8113671ed71cebb2ae61a..6795bf906fac969c342d476633dbd78dd816e797 100644
--- a/web/src/service_event/event_buffer_data.ts
+++ b/web/src/service_event/event_buffer_data.ts
@@ -1,19 +1,21 @@
 import {DelayedCircleEvent, ServiceEvent} from "./model";
 import {getRandomIntInclusive} from "../util/random";
+import {EventBuffer} from "./event_buffer";
+import {Visualisation} from "../theme/model";
 
 export class EventBufferData {
     private eventCirclesMap: Map<string, DelayedCircleEvent[]>
     private eventCirclesArray: DelayedCircleEvent[]
-    private readonly hatnote_map: boolean;
+    private readonly eventBuffer: EventBuffer;
     private radii: number[]
     public circleGroupCatchTimespan; // in ms
     public bufferSplitDelayTimespan; // in ms
     private readonly publishCircleEvent : (circleEvent: DelayedCircleEvent[]) => void
 
-    constructor(publishCircleEvent: (circleEvent: DelayedCircleEvent[]) => void, hatnote_map: boolean, circleGroupCatchTimespan: number, bufferSplitDelayTimespan: number = 1000) {
+    constructor(publishCircleEvent: (circleEvent: DelayedCircleEvent[]) => void, eventBuffer: EventBuffer, circleGroupCatchTimespan: number, bufferSplitDelayTimespan: number = 1000) {
         this.circleGroupCatchTimespan = circleGroupCatchTimespan;
         this.bufferSplitDelayTimespan = bufferSplitDelayTimespan
-        this.hatnote_map = hatnote_map;
+        this.eventBuffer = eventBuffer;
         this.eventCirclesMap = new Map<string, DelayedCircleEvent[]>;
         this.eventCirclesArray = [];
         this.radii = [];
@@ -103,7 +105,7 @@ export class EventBufferData {
 
         let thisBufferData = this
         setTimeout(function(){
-            if(thisBufferData.hatnote_map){
+            if(thisBufferData.eventBuffer.eventBridge.currentVisualisation === Visualisation.geo){
                 for (let [key, circles] of thisBufferData.eventCirclesMap) {
                     if(circles[0].location !== undefined){
                         thisBufferData.publishCircleEvent([{
@@ -139,7 +141,7 @@ export class EventBufferData {
         for (let i = 0; i < this.eventCirclesArray.length; i += chunkSizeInt) {
             const chunk = this.eventCirclesArray.slice(i, i + chunkSizeInt);
             let chunkBufferData = new EventBufferData(
-                (circleEvent) => this.publishCircleEvent(circleEvent),this.hatnote_map,
+                (circleEvent) => this.publishCircleEvent(circleEvent),this.eventBuffer,
                 this.circleGroupCatchTimespan, this.bufferSplitDelayTimespan
             )
             chunkBufferData.addCircleEvents(chunk)
diff --git a/web/src/theme/hatnote.ts b/web/src/theme/hatnote.ts
new file mode 100644
index 0000000000000000000000000000000000000000..fe17f813f3a6281e3e6256fdf3f433cb3f3386eb
--- /dev/null
+++ b/web/src/theme/hatnote.ts
@@ -0,0 +1,17 @@
+import {HatnoteTheme} from "./model";
+
+export const hatnote_theme: HatnoteTheme = {
+    svg_background_color: '#1c2733',
+    header_bg_color: '#fff',
+    header_height: 45,
+    header_version_update_bg: '#b62121',
+    header_text_color: '#000',
+    legend_item_circle_r: 17,
+    progress_indicator_bg_color: '#fff',
+    progress_indicator_fg_color: 'rgb(41, 128, 185)',
+    progress_indicator_error_color: '#b62121',
+    progress_indicator_height: 7,
+    progress_indicator_gap_width: 10,
+    progress_indicator_y_padding: 20,
+    circle_wave_color: '#fff'
+}
\ No newline at end of file
diff --git a/web/src/theme/model.ts b/web/src/theme/model.ts
index fbb61e0976caa639fed7f29cc25a637173eda764..6767a063fe3b817920f60a82af8da0d7622ed80a 100644
--- a/web/src/theme/model.ts
+++ b/web/src/theme/model.ts
@@ -1,5 +1,21 @@
 import {HatnoteVisService, ServiceEvent} from "../service_event/model";
 
+export interface HatnoteTheme {
+    svg_background_color: string,
+    header_bg_color: string,
+    header_height: number,
+    header_version_update_bg: string,
+    header_text_color: string,
+    legend_item_circle_r: number,
+    progress_indicator_bg_color: string,
+    progress_indicator_fg_color: string,
+    progress_indicator_error_color: string,
+    progress_indicator_height: number,
+    progress_indicator_gap_width: number,
+    progress_indicator_y_padding: number,
+    circle_wave_color: string
+}
+
 export interface ServiceTheme {
     name: string,
     id_name: HatnoteVisService,
@@ -29,4 +45,9 @@ export interface ThemeLegendItem {
     smallTitle1?: string,
     smallTitle2?: string,
     event: ServiceEvent
+}
+
+export enum Visualisation {
+    listenTo,
+    geo
 }
\ No newline at end of file
diff --git a/web/src/theme/theme.ts b/web/src/theme/visualisationDirector.ts
similarity index 81%
rename from web/src/theme/theme.ts
rename to web/src/theme/visualisationDirector.ts
index 920f22bfe85f886fff08429e08edefb3ad4f7fc4..fc27f8de8324697686c7f5392e52a998b22f985b 100644
--- a/web/src/theme/theme.ts
+++ b/web/src/theme/visualisationDirector.ts
@@ -1,40 +1,40 @@
-import {ServiceTheme} from "./model";
+import {HatnoteTheme, ServiceTheme, Visualisation} from "./model";
 import {minerva_service_theme} from "./minerva";
 import {SettingsData} from "../configuration/hatnote_settings";
 import {HatnoteVisService, ServiceEvent} from "../service_event/model";
 import {keeper_service_theme} from "./keeper";
 import {bloxberg_service_theme} from "./bloxberg";
+import {hatnote_theme} from "./hatnote";
+import {getRandomIntInclusive} from "../util/random";
 
-export class Theme {
-    readonly svg_background_color: string = '#1c2733'
-    readonly header_bg_color: string = '#fff'
-    readonly header_height: number = 45
-    readonly header_version_update_bg = '#b62121'
-    readonly header_text_color: string = '#000'
-    readonly legend_item_circle_r: number = 17
-    readonly progress_indicator_bg_color = '#fff'
-    readonly progress_indicator_fg_color = 'rgb(41, 128, 185)'
-    readonly progress_indicator_error_color = '#b62121'
-    readonly progress_indicator_height = 7
-    readonly progress_indicator_gap_width = 10
-    readonly progress_indicator_y_padding = 20
-    readonly circle_wave_color= '#fff'
+export class VisualisationDirector {
     service_themes: Map<HatnoteVisService, ServiceTheme> = new Map<HatnoteVisService, ServiceTheme>()
     carousel_service_order: ServiceTheme[]
     current_service_theme: ServiceTheme;
     readonly minervaTheme: ServiceTheme;
     readonly keeperTheme: ServiceTheme;
     readonly bloxbergTheme: ServiceTheme;
+    readonly hatnoteTheme: HatnoteTheme;
+    current_visualisation: Visualisation;
+    private settings_data: SettingsData;
 
     constructor(settings_data: SettingsData) {
+        this.settings_data = settings_data
         this.minervaTheme = minerva_service_theme
         this.keeperTheme = keeper_service_theme
         this.bloxbergTheme = bloxberg_service_theme
+        this.hatnoteTheme = hatnote_theme
 
         this.minervaTheme.carousel_time = settings_data.carousel_time[0]
         this.keeperTheme.carousel_time = settings_data.carousel_time[1]
         this.bloxbergTheme.carousel_time = settings_data.carousel_time[2]
 
+        if(settings_data.map) {
+            this.current_visualisation = Visualisation.geo
+        } else {
+            this.current_visualisation = Visualisation.listenTo
+        }
+
         this.service_themes.set(HatnoteVisService.Minerva, this.minervaTheme)
         this.service_themes.set(HatnoteVisService.Keeper, this.keeperTheme)
         this.service_themes.set(HatnoteVisService.Bloxberg, this.bloxbergTheme)
@@ -44,6 +44,22 @@ export class Theme {
         this.current_service_theme = this.service_themes.get(settings_data.initialService) ?? this.minervaTheme
     }
 
+    getNextVisualisation(): Visualisation{
+        let vis: Visualisation = this.current_visualisation
+        if(this.settings_data.mixed) {
+            if(vis === Visualisation.listenTo){
+                vis = Visualisation.geo
+            } else {
+                vis = Visualisation.listenTo
+            }
+        }
+        return vis
+    }
+
+    setCurrentVisualisation(vis: Visualisation){
+        this.current_visualisation = vis;
+    }
+
     set_current_theme(serviceTheme: ServiceTheme){
         this.current_service_theme = serviceTheme;
     }