diff --git a/src/atlasViewer/atlasViewer.component.ts b/src/atlasViewer/atlasViewer.component.ts
index f58775aa9923e70c4e83ee5496e3cb2c21705335..e62861864810e27237ce38c9fec1dae40f19aeaa 100644
--- a/src/atlasViewer/atlasViewer.component.ts
+++ b/src/atlasViewer/atlasViewer.component.ts
@@ -1,6 +1,6 @@
 import { Component, HostBinding, ViewChild, ViewContainerRef, OnDestroy, OnInit, TemplateRef, AfterViewInit, ElementRef, Renderer2 } from "@angular/core";
 import { Store, select, ActionsSubject } from "@ngrx/store";
-import { ViewerStateInterface, isDefined, FETCHED_SPATIAL_DATA, UPDATE_SPATIAL_DATA, TOGGLE_SIDE_PANEL, safeFilter, UIStateInterface, OPEN_SIDE_PANEL, CLOSE_SIDE_PANEL } from "../services/stateStore.service";
+import { ViewerStateInterface, isDefined, FETCHED_SPATIAL_DATA, UPDATE_SPATIAL_DATA, TOGGLE_SIDE_PANEL, safeFilter, OPEN_SIDE_PANEL, CLOSE_SIDE_PANEL } from "../services/stateStore.service";
 import { Observable, Subscription, combineLatest, interval, merge, of, fromEvent } from "rxjs";
 import { map, filter, distinctUntilChanged, delay, concatMap, debounceTime, withLatestFrom, switchMap, takeUntil, scan, takeLast } from "rxjs/operators";
 import { AtlasViewerDataService } from "./atlasViewer.dataService.service";
@@ -17,7 +17,7 @@ import { DatabrowserService } from "src/ui/databrowserModule/databrowser.service
 import { AGREE_COOKIE, AGREE_KG_TOS, SHOW_KG_TOS } from "src/services/state/uiState.store";
 import { TabsetComponent } from "ngx-bootstrap/tabs";
 import { LocalFileService } from "src/services/localFile.service";
-import { MatDialog, MatDialogRef } from "@angular/material";
+import { MatDialog, MatDialogRef, MatSnackBar, MatSnackBarRef } from "@angular/material";
 
 /**
  * TODO
@@ -71,6 +71,9 @@ export class AtlasViewer implements OnDestroy, OnInit, AfterViewInit {
   public selectedRegions$: Observable<any[]>
   public selectedPOI$ : Observable<any[]>
   private showHelp$: Observable<any>
+  
+  private snackbarRef: MatSnackBarRef<any>
+  public snackbarMessage$: Observable<string>
 
   public dedicatedView$: Observable<string | null>
   public onhoverSegments$: Observable<string[]>
@@ -108,9 +111,15 @@ export class AtlasViewer implements OnDestroy, OnInit, AfterViewInit {
     private databrowserService: DatabrowserService,
     private dispatcher$: ActionsSubject,
     private rd: Renderer2,
-    public localFileService: LocalFileService
+    public localFileService: LocalFileService,
+    private snackbar: MatSnackBar
   ) {
 
+    this.snackbarMessage$ = this.store.pipe(
+      select('uiState'),
+      select("snackbarMessage")
+    )
+
     /**
      * TODO deprecated
      */
@@ -267,6 +276,25 @@ export class AtlasViewer implements OnDestroy, OnInit, AfterViewInit {
       })
     }
 
+    this.subscriptions.push(
+      this.snackbarMessage$.pipe(
+        // angular material issue
+        // see https://github.com/angular/angular/issues/15634
+        // and https://github.com/angular/components/issues/11357
+        delay(0),
+      ).subscribe(messageSymbol => {
+        this.snackbarRef && this.snackbarRef.dismiss()
+
+        if (!messageSymbol) return
+
+        // https://stackoverflow.com/a/48191056/6059235
+        const message = messageSymbol.toString().slice(7, -1)
+        this.snackbarRef = this.snackbar.open(message, 'Dismiss', {
+          duration: 5000
+        })
+      })
+    )
+
     this.subscriptions.push(
       this.showHelp$.subscribe(() => {
         this.helpDialogRef = this.matDialog.open(this.helpComponent, {
diff --git a/src/atlasViewer/atlasViewer.constantService.service.ts b/src/atlasViewer/atlasViewer.constantService.service.ts
index 7b9026aec1fed0984ebc59361fdc0c8be26423d1..eaf6726edd26e175da90580dac24a77e1f5d2ba6 100644
--- a/src/atlasViewer/atlasViewer.constantService.service.ts
+++ b/src/atlasViewer/atlasViewer.constantService.service.ts
@@ -4,7 +4,7 @@ import { ViewerStateInterface } from "../services/stateStore.service";
 import { Subject, Observable } from "rxjs";
 import { ACTION_TYPES, ViewerConfiguration } from 'src/services/state/viewerConfig.store'
 import { map, shareReplay, filter } from "rxjs/operators";
-import { MatSnackBar } from "@angular/material";
+import { SNACKBAR_MESSAGE } from "src/services/state/uiState.store";
 
 export const CM_THRESHOLD = `0.05`
 export const CM_MATLAB_JET = `float r;if( x < 0.7 ){r = 4.0 * x - 1.5;} else {r = -4.0 * x + 4.5;}float g;if (x < 0.5) {g = 4.0 * x - 0.5;} else {g = -4.0 * x + 3.5;}float b;if (x < 0.3) {b = 4.0 * x + 0.5;} else {b = -4.0 * x + 2.5;}float a = 1.0;`
@@ -241,8 +241,7 @@ Interactive atlas viewer requires **webgl2.0**, and the \`EXT_color_buffer_float
   private repoUrl = `https://github.com/HumanBrainProject/interactive-viewer`
 
   constructor(
-    private store : Store<ViewerStateInterface>,
-    private snackbar: MatSnackBar  
+    private store : Store<ViewerStateInterface>
   ){
 
     const ua = window && window.navigator && window.navigator.userAgent
@@ -274,10 +273,13 @@ Interactive atlas viewer requires **webgl2.0**, and the \`EXT_color_buffer_float
   }
 
   catchError(e: Error | string){
-    this.snackbar.open(e.toString(), 'Dismiss', {
-      duration: 3000
+    this.store.dispatch({
+      type: SNACKBAR_MESSAGE,
+      snackbarMessage: e.toString()
     })
   }
+
+  public cyclePanelMessage: string = `[spacebar] to cycle through views`
 }
 
 const parseURLToElement = (url:string):HTMLElement=>{
diff --git a/src/main.module.ts b/src/main.module.ts
index ad4bd2ea47da47dd16e7e149f1d0f0c07f507951..4e9e7854e6e871b415982336706d6af6fd7d34cc 100644
--- a/src/main.module.ts
+++ b/src/main.module.ts
@@ -48,6 +48,7 @@ import { DialogComponent } from "./components/dialog/dialog.component";
 import { ViewerStateControllerUseEffect } from "./ui/viewerStateController/viewerState.useEffect";
 import { ConfirmDialogComponent } from "./components/confirmDialog/confirmDialog.component";
 import { ViewerStateUseEffect } from "./services/state/viewerState.store";
+import { NgViewerUseEffect } from "./services/state/ngViewerState.store";
 
 import 'hammerjs'
 
@@ -69,6 +70,7 @@ import 'hammerjs'
       UserConfigStateUseEffect,
       ViewerStateControllerUseEffect,
       ViewerStateUseEffect,
+      NgViewerUseEffect
     ]),
     StoreModule.forRoot({
       pluginState,
diff --git a/src/services/localFile.service.ts b/src/services/localFile.service.ts
index d38450012e3d7a8e4ea680f483c27c52a4ea4013..8ef82668dd4ec7e237a5e1e2e6de43d8b4983dac 100644
--- a/src/services/localFile.service.ts
+++ b/src/services/localFile.service.ts
@@ -1,6 +1,8 @@
 import { Injectable } from "@angular/core";
 import { MatSnackBar } from "@angular/material";
 import { DatabrowserService } from "src/ui/databrowserModule/databrowser.service";
+import { Store } from "@ngrx/store";
+import { SNACKBAR_MESSAGE } from "./state/uiState.store";
 
 /**
  * experimental service handling local user files such as nifti and gifti
@@ -15,8 +17,8 @@ export class LocalFileService {
   private supportedExtSet = new Set(SUPPORTED_EXT)
 
   constructor(
-    private snackBar: MatSnackBar,
-    private dbService: DatabrowserService  
+    private dbService: DatabrowserService  ,
+    private store: Store<any>
   ){
 
   }
@@ -38,10 +40,10 @@ export class LocalFileService {
         }
       }
     } catch (e) {
-      this.snackBar.open(e, `Dismiss`, {
-        duration: 5000
+      this.store.dispatch({
+        type: SNACKBAR_MESSAGE,
+        snackbarMessage: `Opening local NIFTI error: ${e.toString()}`
       })
-      console.error(e)
     }
   }
 
@@ -76,8 +78,9 @@ export class LocalFileService {
   }
 
   private showLocalWarning() {
-    this.snackBar.open(`Warning: sharing URL will not share the loaded local file`, 'Dismiss', {
-      duration: 5000
+    this.store.dispatch({
+      type: SNACKBAR_MESSAGE,
+      snackbarMessage: `Warning: sharing URL will not share the loaded local file`
     })
   }
 }
diff --git a/src/services/state/ngViewerState.store.ts b/src/services/state/ngViewerState.store.ts
index 289489d7e549bd4266aac0a57ffd67c877aa2804..4464aa288cd5605334ccf261e12f4b5dc8457cdb 100644
--- a/src/services/state/ngViewerState.store.ts
+++ b/src/services/state/ngViewerState.store.ts
@@ -1,4 +1,10 @@
-import { Action } from '@ngrx/store'
+import { Action, Store, select } from '@ngrx/store'
+import { Injectable, OnDestroy } from '@angular/core';
+import { Observable, combineLatest, fromEvent, Subscription } from 'rxjs';
+import { Effect, Actions, ofType } from '@ngrx/effects';
+import { withLatestFrom, map, distinctUntilChanged, scan, shareReplay, filter, mapTo, tap, delay } from 'rxjs/operators';
+import { AtlasViewerConstantsServices } from 'src/atlasViewer/atlasViewer.constantService.service';
+import { SNACKBAR_MESSAGE } from './uiState.store';
 
 export const FOUR_PANEL = 'FOUR_PANEL'
 export const V_ONE_THREE = 'V_ONE_THREE'
@@ -104,6 +110,136 @@ export function ngViewerState(prevState:NgViewerStateInterface = defaultState, a
   }
 }
 
+@Injectable({
+  providedIn: 'root'
+})
+
+export class NgViewerUseEffect implements OnDestroy{
+  @Effect()
+  public toggleMaximiseMode$: Observable<any>
+
+  @Effect()
+  public toggleMaximiseOrder$: Observable<any>
+
+  @Effect()
+  public toggleMaximiseCycleMessage$: Observable<any>
+
+  @Effect()
+  public cycleViews$: Observable<any>
+
+  @Effect()
+  public spacebarListener$: Observable<any>
+
+  private panelOrder$: Observable<string>
+  private panelMode$: Observable<string>
+
+  private subscriptions: Subscription[] = []
+
+  constructor(
+    private actions: Actions,
+    private store$: Store<any>,
+    private constantService: AtlasViewerConstantsServices
+  ){
+    const toggleMaxmimise$ = this.actions.pipe(
+      ofType(ACTION_TYPES.TOGGLE_MAXIMISE),
+      shareReplay(1)
+    )
+
+    this.panelOrder$ = this.store$.pipe(
+      select('ngViewerState'),
+      select('panelOrder'),
+      distinctUntilChanged(),
+    )
+
+    this.panelMode$ = this.store$.pipe(
+      select('ngViewerState'),
+      select('panelMode'),
+      distinctUntilChanged(),
+    )
+
+    this.cycleViews$ = this.actions.pipe(
+      ofType(ACTION_TYPES.CYCLE_VIEWS),
+      withLatestFrom(this.panelOrder$),
+      map(([_, panelOrder]) => {
+        return {
+          type: ACTION_TYPES.SET_PANEL_ORDER,
+          payload: {
+            panelOrder: [...panelOrder.slice(1), ...panelOrder.slice(0,1)].join('')
+          }
+        }
+      })
+    )
+
+    this.toggleMaximiseOrder$ = toggleMaxmimise$.pipe(
+      withLatestFrom(
+        combineLatest(
+          this.panelOrder$.pipe(
+            scan((acc, curr: string) => [curr, ...acc.slice(0,1)], []),
+          ),
+          this.panelMode$
+        )
+      ),
+      map(([action, [panelOrders, panelMode]]) => {
+        const { payload } = action as NgViewerAction
+        const { index = 0 } = payload
+
+        const panelOrder = panelMode === SINGLE_PANEL && !!panelOrders[1]
+          ? panelOrders[1]
+          : [...panelOrders[0].slice(index), ...panelOrders[0].slice(0, index)].join('')
+        return {
+          type: ACTION_TYPES.SET_PANEL_ORDER,
+          payload: {
+            panelOrder
+          }
+        }
+      })
+    )
+
+    this.toggleMaximiseMode$ = toggleMaxmimise$.pipe(
+      withLatestFrom(this.panelMode$.pipe(
+        scan((acc, curr: string) => [curr, ...acc.slice(0,1)], [])
+      )),
+      map(([ _, panelModes ]) => {
+        return {
+          type: ACTION_TYPES.SWITCH_PANEL_MODE,
+          payload: {
+            panelMode: panelModes[0] === SINGLE_PANEL
+              ? (panelModes[1] || FOUR_PANEL)
+              : SINGLE_PANEL
+          }
+        }
+      })
+    )
+
+    this.toggleMaximiseCycleMessage$ = this.toggleMaximiseMode$.pipe(
+      filter(() => !this.constantService.mobile),
+      filter(({ payload }) => payload.panelMode && payload.panelMode === SINGLE_PANEL),
+      mapTo({
+        type: SNACKBAR_MESSAGE,
+        snackbarMessage: this.constantService.cyclePanelMessage
+      })
+    )
+
+    this.spacebarListener$ = combineLatest(
+      fromEvent(document.body, 'keydown', { capture: true }).pipe(
+        filter((ev: KeyboardEvent) => ev.key === ' ')
+      ),
+      this.panelMode$
+    ).pipe(
+      filter(([_ , panelMode]) => panelMode === SINGLE_PANEL),
+      mapTo({
+        type: ACTION_TYPES.CYCLE_VIEWS
+      })
+    )
+  }
+
+  ngOnDestroy(){
+    while(this.subscriptions.length > 0) {
+      this.subscriptions.pop().unsubscribe()
+    }
+  }
+}
+
 export const ADD_NG_LAYER = 'ADD_NG_LAYER'
 export const REMOVE_NG_LAYER = 'REMOVE_NG_LAYER'
 export const SHOW_NG_LAYER = 'SHOW_NG_LAYER'
@@ -122,7 +258,10 @@ interface NgLayerInterface{
 
 const ACTION_TYPES = {
   SWITCH_PANEL_MODE: 'SWITCH_PANEL_MODE',
-  SET_PANEL_ORDER: 'SET_PANEL_ORDER'
+  SET_PANEL_ORDER: 'SET_PANEL_ORDER',
+
+  TOGGLE_MAXIMISE: 'TOGGLE_MAXIMISE',
+  CYCLE_VIEWS: 'CYCLE_VIEWS'
 }
 
 export const SUPPORTED_PANEL_MODES = [
diff --git a/src/services/state/uiState.store.ts b/src/services/state/uiState.store.ts
index fadd5b87fcc506e504ff011a429675648d85c303..09639a50a5c50bbb9bab8f8f2d2fefcdecd09005 100644
--- a/src/services/state/uiState.store.ts
+++ b/src/services/state/uiState.store.ts
@@ -10,6 +10,8 @@ const defaultState : UIStateInterface = {
   focusedSidePanel: null,
   sidePanelOpen: false,
 
+  snackbarMessage: null,
+
   /**
    * replace with server side logic (?)
    */
@@ -35,6 +37,15 @@ export function uiState(state:UIStateInterface = defaultState,action:UIAction){
         ...state,
         mouseOverLandmark : action.landmark
       }
+    case SNACKBAR_MESSAGE:
+      const { snackbarMessage } = action
+      /**
+       * Need to use symbol here, or repeated snackbarMessage will not trigger new event
+       */
+      return {
+        ...state,
+        snackbarMessage: Symbol(snackbarMessage)
+      }
     /**
      * TODO deprecated
      * remove ASAP
@@ -89,6 +100,8 @@ export interface UIStateInterface{
   mouseOverLandmark : any 
   focusedSidePanel : string | null
 
+  snackbarMessage: Symbol
+
   agreedCookies: boolean
   agreedKgTos: boolean
 }
@@ -102,7 +115,8 @@ export interface UIAction extends Action{
       name: string
     }
     segment: any | null
-  }[]
+  }[],
+  snackbarMessage: string
 }
 
 export const MOUSE_OVER_SEGMENT = `MOUSE_OVER_SEGMENT`
@@ -115,4 +129,6 @@ export const OPEN_SIDE_PANEL = `OPEN_SIDE_PANEL`
 
 export const AGREE_COOKIE = `AGREE_COOKIE`
 export const AGREE_KG_TOS = `AGREE_KG_TOS`
-export const SHOW_KG_TOS = `SHOW_KG_TOS`
\ No newline at end of file
+export const SHOW_KG_TOS = `SHOW_KG_TOS`
+
+export const SNACKBAR_MESSAGE = `SNACKBAR_MESSAGE`
\ No newline at end of file
diff --git a/src/ui/config/config.component.ts b/src/ui/config/config.component.ts
index 5d7d25e44e0905c6ee932d202994684842ed8ef6..8f5083ee1affacc11e13e910888e1ec04ecc7be2 100644
--- a/src/ui/config/config.component.ts
+++ b/src/ui/config/config.component.ts
@@ -1,14 +1,16 @@
 import { Component, OnInit, OnDestroy } from '@angular/core'
 import { Store, select } from '@ngrx/store';
 import { ViewerConfiguration, ACTION_TYPES } from 'src/services/state/viewerConfig.store'
-import { Observable, Subscription } from 'rxjs';
-import { map, distinctUntilChanged, startWith, shareReplay } from 'rxjs/operators';
+import { Observable, Subscription, combineLatest } from 'rxjs';
+import { map, distinctUntilChanged, startWith, debounceTime } from 'rxjs/operators';
 import { MatSlideToggleChange, MatSliderChange } from '@angular/material';
 import { NG_VIEWER_ACTION_TYPES, SUPPORTED_PANEL_MODES } from 'src/services/state/ngViewerState.store';
+import { isIdentityQuat } from '../nehubaContainer/util';
 
 const GPU_TOOLTIP = `GPU TOOLTIP`
 const ANIMATION_TOOLTIP = `ANIMATION_TOOLTIP`
-const ROOT_TEXT_ORDER = ['Coronal', 'Sagittal', 'Axial', '3D']
+const ROOT_TEXT_ORDER : [string, string, string, string] = ['Coronal', 'Sagittal', 'Axial', '3D']
+const OBLIQUE_ROOT_TEXT_ORDER : [string, string, string, string] = ['Slice View 1', 'Slice View 2', 'Slice View 3', '3D']
 
 @Component({
   selector: 'config-component',
@@ -41,6 +43,8 @@ export class ConfigComponent implements OnInit, OnDestroy{
   private panelOrder$: Observable<string>
   public panelTexts$: Observable<[string, string, string, string]>
 
+  private viewerObliqueRotated$: Observable<boolean>
+
   constructor(private store: Store<ViewerConfiguration>) {
     this.gpuLimit$ = this.store.pipe(
       select('viewerConfigState'),
@@ -65,9 +69,24 @@ export class ConfigComponent implements OnInit, OnDestroy{
       select('panelOrder')
     )
     
-    this.panelTexts$ = this.panelOrder$.pipe(
-      map(string => string.split('').map(s => Number(s))),
-      map(arr => arr.map(idx => ROOT_TEXT_ORDER[idx]) as [string, string, string, string])
+    this.viewerObliqueRotated$ = this.store.pipe(
+      select('viewerState'),
+      select('navigation'),
+      map(navigation => (navigation && navigation.orientation) || [0, 0, 0, 1]),
+      debounceTime(100),
+      map(isIdentityQuat),
+      map(flag => !flag),
+      distinctUntilChanged(),
+    )
+
+    this.panelTexts$ = combineLatest(
+      this.panelOrder$.pipe(
+        map(string => string.split('').map(s => Number(s))),
+      ),
+      this.viewerObliqueRotated$
+    ).pipe(
+      map(([arr, isObliqueRotated]) => arr.map(idx => (isObliqueRotated ? OBLIQUE_ROOT_TEXT_ORDER : ROOT_TEXT_ORDER)[idx]) as [string, string, string, string]),
+      startWith(ROOT_TEXT_ORDER)
     )
   }
 
diff --git a/src/ui/config/config.template.html b/src/ui/config/config.template.html
index 20927120dd97ec7f77e783ae3fcabfbcdc6243b6..91f0dde64b0b2dc1d442f4bdacc616c284ad1237 100644
--- a/src/ui/config/config.template.html
+++ b/src/ui/config/config.template.html
@@ -106,8 +106,10 @@
         </layout-four-panel>
       </button>
 
+      <!-- temporarily disabling 1-3 layout -->
+
       <!-- horizontal 1 3 card -->
-      <button
+      <!-- <button
         class="m-2 p-2"
         mat-flat-button
         (click)="usePanelMode(supportedPanelModes[1])"
@@ -118,10 +120,10 @@
           <div class="border w-100 h-100" cell-iii></div>
           <div class="border w-100 h-100" cell-iv></div>
         </layout-horizontal-one-three>
-      </button>
+      </button> -->
   
       <!-- vertical 1 3 card -->
-      <button
+      <!-- <button
         class="m-2 p-2"
         mat-flat-button
         (click)="usePanelMode(supportedPanelModes[2])"
@@ -132,7 +134,7 @@
           <div class="border w-100 h-100" cell-iii></div>
           <div class="border w-100 h-100" cell-iv></div>
         </layout-vertical-one-three>
-      </button>
+      </button> -->
 
       <!-- single -->
       <button
diff --git a/src/ui/nehubaContainer/maximisePanelButton/maximisePanelButton.component.ts b/src/ui/nehubaContainer/maximisePanelButton/maximisePanelButton.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..f8cef8eca8656d0d31fa3c1c2dba418c133cd77e
--- /dev/null
+++ b/src/ui/nehubaContainer/maximisePanelButton/maximisePanelButton.component.ts
@@ -0,0 +1,43 @@
+import { Component, Input } from "@angular/core";
+import { Store, select } from "@ngrx/store";
+import { Observable } from "rxjs";
+import { distinctUntilChanged, map } from "rxjs/operators";
+import { SINGLE_PANEL } from "src/services/state/ngViewerState.store";
+
+@Component({
+  selector: 'maximise-panel-button',
+  templateUrl: './maximisePanelButton.template.html',
+  styleUrls: [
+    './maximisePanelButton.style.css'
+  ]
+})
+
+export class MaximmisePanelButton{
+  
+  @Input() panelIndex: number
+
+  private panelMode$: Observable<string>
+  private panelOrder$: Observable<string>
+
+  public isMaximised$: Observable<boolean>
+
+  constructor(
+    private store$: Store<any>
+  ){
+    this.panelMode$ = this.store$.pipe(
+      select('ngViewerState'),
+      select('panelMode'),
+      distinctUntilChanged()
+    )
+
+    this.panelOrder$ = this.store$.pipe(
+      select('ngViewerState'),
+      select('panelOrder'),
+      distinctUntilChanged()
+    )
+
+    this.isMaximised$ = this.panelMode$.pipe(
+      map(panelMode => panelMode === SINGLE_PANEL)
+    )
+  }
+}
\ No newline at end of file
diff --git a/src/ui/nehubaContainer/maximisePanelButton/maximisePanelButton.style.css b/src/ui/nehubaContainer/maximisePanelButton/maximisePanelButton.style.css
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/src/ui/nehubaContainer/maximisePanelButton/maximisePanelButton.template.html b/src/ui/nehubaContainer/maximisePanelButton/maximisePanelButton.template.html
new file mode 100644
index 0000000000000000000000000000000000000000..02d7cdeaa40ea2c519cca54fcd0c49d6eab4e365
--- /dev/null
+++ b/src/ui/nehubaContainer/maximisePanelButton/maximisePanelButton.template.html
@@ -0,0 +1,10 @@
+<button
+  [matTooltip]="(isMaximised$ | async) ? 'Restore four panel view' : 'Maximise this panel'"
+  mat-icon-button
+  color="primary">
+  <i *ngIf="isMaximised$ | async; else expandIconTemplate" class="fas fa-compress-arrows-alt"></i>
+</button>
+
+<ng-template #expandIconTemplate>
+  <i class="fas fa-expand-arrows-alt"></i>
+</ng-template>
\ No newline at end of file
diff --git a/src/ui/nehubaContainer/nehubaContainer.component.ts b/src/ui/nehubaContainer/nehubaContainer.component.ts
index daf61453f1c3c18e6e0d176d0f72d55f965241c3..241907ecd138682823b992c2e405c0326997729c 100644
--- a/src/ui/nehubaContainer/nehubaContainer.component.ts
+++ b/src/ui/nehubaContainer/nehubaContainer.component.ts
@@ -1,15 +1,15 @@
 import { Component, ViewChild, ViewContainerRef, ComponentFactoryResolver, ComponentFactory, ComponentRef, OnInit, OnDestroy, ElementRef } from "@angular/core";
-import { NehubaViewerUnit } from "./nehubaViewer/nehubaViewer.component";
+import { NehubaViewerUnit, computeDistance } from "./nehubaViewer/nehubaViewer.component";
 import { Store, select } from "@ngrx/store";
 import { ViewerStateInterface, safeFilter, CHANGE_NAVIGATION, isDefined, USER_LANDMARKS, ADD_NG_LAYER, REMOVE_NG_LAYER, NgViewerStateInterface, MOUSE_OVER_LANDMARK, SELECT_LANDMARKS, Landmark, PointLandmarkGeometry, PlaneLandmarkGeometry, OtherLandmarkGeometry, getNgIds, getMultiNgIdsRegionsLabelIndexMap, generateLabelIndexId, DataEntry } from "../../services/stateStore.service";
-import { Observable, Subscription, fromEvent, combineLatest, merge } from "rxjs";
-import { filter,map, take, scan, debounceTime, distinctUntilChanged, switchMap, skip, withLatestFrom, buffer, tap, switchMapTo, shareReplay, throttleTime, bufferTime, startWith } from "rxjs/operators";
+import { Observable, Subscription, fromEvent, combineLatest, merge, of } from "rxjs";
+import { filter,map, take, scan, debounceTime, distinctUntilChanged, switchMap, skip, withLatestFrom, buffer, tap, switchMapTo, shareReplay, mapTo, takeUntil } from "rxjs/operators";
 import { AtlasViewerAPIServices, UserLandmark } from "../../atlasViewer/atlasViewer.apiService.service";
 import { timedValues } from "../../util/generator";
 import { AtlasViewerConstantsServices } from "../../atlasViewer/atlasViewer.constantService.service";
 import { ViewerConfiguration } from "src/services/state/viewerConfig.store";
 import { pipeFromArray } from "rxjs/internal/util/pipe";
-import { NEHUBA_READY, H_ONE_THREE, V_ONE_THREE, FOUR_PANEL, SINGLE_PANEL } from "src/services/state/ngViewerState.store";
+import { NEHUBA_READY, H_ONE_THREE, V_ONE_THREE, FOUR_PANEL, SINGLE_PANEL, NG_VIEWER_ACTION_TYPES } from "src/services/state/ngViewerState.store";
 import { MOUSE_OVER_SEGMENTS } from "src/services/state/uiState.store";
 import { getHorizontalOneThree, getVerticalOneThree, getFourPanel, getSinglePanel } from "./util";
 import { SELECT_REGIONS_WITH_ID, NEHUBA_LAYER_CHANGED, VIEWERSTATE_ACTION_TYPES } from "src/services/state/viewerState.store";
@@ -144,9 +144,16 @@ export class NehubaContainer implements OnInit, OnDestroy{
 
   private viewPanels: [HTMLElement, HTMLElement, HTMLElement, HTMLElement] = [null, null, null, null]
   public panelMode$: Observable<string>
+
+  private panelOrder: string
+  public panelOrder$: Observable<string>
   private redrawLayout$: Observable<[string, string]>
   public favDataEntries$: Observable<DataEntry[]>
 
+  public hoveredPanelIndices$: Observable<number>
+
+  private ngPanelTouchMove$: Observable<any>
+
   constructor(
     private constantService : AtlasViewerConstantsServices,
     private apiService :AtlasViewerAPIServices,
@@ -173,22 +180,29 @@ export class NehubaContainer implements OnInit, OnDestroy{
       filter(() => isDefined(this.nehubaViewer) && isDefined(this.nehubaViewer.nehubaViewer))
     )
 
+    this.panelMode$ = this.store.pipe(
+      select('ngViewerState'),
+      select('panelMode'),
+      distinctUntilChanged(),
+      shareReplay(1)
+    )
+
+    this.panelOrder$ = this.store.pipe(
+      select('ngViewerState'),
+      select('panelOrder'),
+      distinctUntilChanged(),
+      shareReplay(1),
+      tap(panelOrder => this.panelOrder = panelOrder)
+    )
+    
     this.redrawLayout$ = this.store.pipe(
       select('ngViewerState'),
       select('nehubaReady'),
       distinctUntilChanged(),
       filter(v => !!v),
       switchMapTo(combineLatest(
-        this.store.pipe(
-          select('ngViewerState'),
-          select('panelMode'),
-          distinctUntilChanged()
-        ),
-        this.store.pipe(
-          select('ngViewerState'),
-          select('panelOrder'),
-          distinctUntilChanged()
-        )
+        this.panelMode$,
+        this.panelOrder$
       ))
     )
 
@@ -414,10 +428,20 @@ export class NehubaContainer implements OnInit, OnDestroy{
         : false)
     )
 
-    this.panelMode$ = this.store.pipe(
-      select('ngViewerState'),
-      select('panelMode'),
-      distinctUntilChanged(),
+    this.ngPanelTouchMove$ = fromEvent(this.elementRef.nativeElement, 'touchstart').pipe(
+      switchMap((touchStartEv:TouchEvent) => fromEvent(this.elementRef.nativeElement, 'touchmove').pipe(
+        tap((ev: TouchEvent) => ev.preventDefault()),
+        scan((acc, curr: TouchEvent) => [curr, ...acc.slice(0,1)], []),
+        map((touchMoveEvs:TouchEvent[]) => {
+          return {
+            touchStartEv,
+            touchMoveEvs
+          }
+        }),
+        takeUntil(fromEvent(this.elementRef.nativeElement, 'touchend').pipe(
+          filter((ev: TouchEvent) => ev.touches.length === 0))
+        )
+      ))
     )
   }
 
@@ -433,8 +457,100 @@ export class NehubaContainer implements OnInit, OnDestroy{
     return element
   }
 
+  private findPanelIndex = (panel: HTMLElement) => this.viewPanels.findIndex(p => p === panel)
+
   ngOnInit(){
 
+    // translation on mobile
+    this.subscriptions.push(
+      this.ngPanelTouchMove$.pipe(
+        filter(({ touchMoveEvs }) => touchMoveEvs.length > 1 && (touchMoveEvs as TouchEvent[]).every(ev => ev.touches.length === 1)),
+      ).subscribe(({ touchMoveEvs, touchStartEv }) => {
+
+        // get deltaX and deltaY of touchmove
+        const deltaX = touchMoveEvs[1].touches[0].screenX - touchMoveEvs[0].touches[0].screenX
+        const deltaY = touchMoveEvs[1].touches[0].screenY - touchMoveEvs[0].touches[0].screenY
+
+        // figure out the target of touch start
+        const panelIdx = this.findPanelIndex(touchStartEv.target as HTMLElement) 
+
+        // translate if panelIdx < 3
+        if (panelIdx >=0 && panelIdx < 3) {
+          const { position } = this.nehubaViewer.nehubaViewer.ngviewer.navigationState
+          const pos = position.spatialCoordinates
+          window['export_nehuba'].vec3.set(pos, deltaX, deltaY, 0)
+          window['export_nehuba'].vec3.transformMat4(pos, pos, this.nehubaViewer.viewportToDatas[panelIdx])
+          position.changed.dispatch()
+        } 
+
+        // rotate 3D if panelIdx === 3
+
+        else if (panelIdx === 3) {
+          const {perspectiveNavigationState} = this.nehubaViewer.nehubaViewer.ngviewer
+          const { vec3 } = window['export_nehuba']
+          perspectiveNavigationState.pose.rotateRelative(vec3.fromValues(0, 1, 0), -deltaX / 4.0 * Math.PI / 180.0)
+          perspectiveNavigationState.pose.rotateRelative(vec3.fromValues(1, 0, 0), deltaY / 4.0 * Math.PI / 180.0)
+          this.nehubaViewer.nehubaViewer.ngviewer.perspectiveNavigationState.changed.dispatch()
+        }
+
+        // this shoudn't happen?
+        else {
+          console.warn(`panelIdx not found`)
+        }
+      })
+    )
+
+    // perspective reorientation on mobile
+    this.subscriptions.push(
+      this.ngPanelTouchMove$.pipe(
+        filter(({ touchMoveEvs }) => touchMoveEvs.length > 1 && (touchMoveEvs as TouchEvent[]).every(ev => ev.touches.length === 2)),
+      ).subscribe(({ touchMoveEvs, touchStartEv }) => {
+
+        const d1 = computeDistance(
+          [touchMoveEvs[1].touches[0].screenX, touchMoveEvs[1].touches[0].screenY],
+          [touchMoveEvs[1].touches[1].screenX, touchMoveEvs[1].touches[1].screenY]
+        )
+        const d2 = computeDistance(
+          [touchMoveEvs[0].touches[0].screenX, touchMoveEvs[0].touches[0].screenY],
+          [touchMoveEvs[0].touches[1].screenX, touchMoveEvs[0].touches[1].screenY]
+        )
+        const factor = d1/d2
+
+        // figure out the target of touch start
+        const panelIdx = this.findPanelIndex(touchStartEv.target as HTMLElement) 
+
+        // zoom slice view if slice
+        if (panelIdx >=0 && panelIdx < 3) {
+          this.nehubaViewer.nehubaViewer.ngviewer.navigationState.zoomBy(factor)
+        }
+
+        // zoom perspective view if on perspective
+        else if (panelIdx === 3) {
+          const { minZoom = null, maxZoom = null } = (this.selectedTemplate.nehubaConfig
+            && this.selectedTemplate.nehubaConfig.layout
+            && this.selectedTemplate.nehubaConfig.layout.useNehubaPerspective
+            && this.selectedTemplate.nehubaConfig.layout.useNehubaPerspective.restrictZoomLevel)
+            || {}
+          
+          const { zoomFactor } = this.nehubaViewer.nehubaViewer.ngviewer.perspectiveNavigationState
+          if (!!minZoom && zoomFactor.value * factor < minZoom) return
+          if (!!maxZoom && zoomFactor.value * factor > maxZoom) return
+          zoomFactor.zoomBy(factor)
+        }
+      })
+    )
+
+    this.hoveredPanelIndices$ = fromEvent(this.elementRef.nativeElement, 'mouseover').pipe(
+      switchMap((ev:MouseEvent) => merge(
+        of(this.findPanelIndex(ev.target as HTMLElement)),
+        fromEvent(this.elementRef.nativeElement, 'mouseout').pipe(
+          mapTo(null)
+        )
+      )),
+      debounceTime(20),
+      shareReplay(1)
+    )
+
     /* each time a new viewer is initialised, take the first event to get the translation function */
     this.subscriptions.push(
       this.newViewer$.pipe(
@@ -803,6 +919,15 @@ export class NehubaContainer implements OnInit, OnDestroy{
     this.subscriptions.forEach(s=>s.unsubscribe())
   }
 
+  toggleMaximiseMinimise(index: number){
+    this.store.dispatch({
+      type: NG_VIEWER_ACTION_TYPES.TOGGLE_MAXIMISE,
+      payload: {
+        index
+      }
+    })
+  }
+
   public tunableMobileProperties = ['Oblique Rotate X', 'Oblique Rotate Y', 'Oblique Rotate Z']
   public selectedProp = null
 
diff --git a/src/ui/nehubaContainer/nehubaContainer.style.css b/src/ui/nehubaContainer/nehubaContainer.style.css
index 57a6ad380921978025fdb9692967d716743d5eab..160d701048c08d585d06629e40fbeb3ca00dec3e 100644
--- a/src/ui/nehubaContainer/nehubaContainer.style.css
+++ b/src/ui/nehubaContainer/nehubaContainer.style.css
@@ -176,4 +176,37 @@ div#scratch-pad
 {
   right: 0;
   bottom: 0;
-}
\ No newline at end of file
+}
+
+maximise-panel-button
+{
+  transition: opacity 170ms ease-in-out,
+    transform 250ms ease-in-out;
+
+  position: absolute;
+  top: 0;
+  right: 0;
+}
+
+/* if not mobile, then show on hover */
+maximise-panel-button
+{
+  opacity: 0.0;
+  pointer-events: none;
+}
+
+maximise-panel-button.onHover,
+maximise-panel-button:hover,
+:host-context([ismobile="true"]) maximise-panel-button
+{
+  opacity: 1.0 !important;
+  pointer-events: all !important;
+}
+
+
+maximise-panel-button.touch-top.touch-right.onHover,
+maximise-panel-button.touch-top.touch-right:hover,
+:host-context(:not([ismobile="true"])) maximise-panel-button.touch-top.touch-right
+{
+  transform: translate(-3em, 3em)
+}
diff --git a/src/ui/nehubaContainer/nehubaContainer.template.html b/src/ui/nehubaContainer/nehubaContainer.template.html
index f9bf6b9798bff3e47b2178ce6fa08c750c5ff9ba..f8b623cfe8929c49e12c92f13afe4b6cde1a6777 100644
--- a/src/ui/nehubaContainer/nehubaContainer.template.html
+++ b/src/ui/nehubaContainer/nehubaContainer.template.html
@@ -7,7 +7,7 @@
 <!-- spatial landmarks overlay -->
 <!-- loading indicator -->
 
-<current-layout class="position-absolute w-100 h-100 d-block pe-none">
+<current-layout *ngIf="viewerLoaded" class="position-absolute w-100 h-100 d-block pe-none">
   <div class="w-100 h-100 position-relative" cell-i>
     <ng-content *ngTemplateOutlet="overlayi"></ng-content>
   </div>
@@ -49,7 +49,6 @@
 </layout-floating-container>
 
 <div id="scratch-pad">
-
 </div>
 
 <!-- mobile nub, allowing for ooblique slicing in mobile -->
@@ -97,6 +96,13 @@
       [positionZ]="getPositionZ(0,spatialData)">
     </nehuba-2dlandmark-unit>
 
+    <!-- maximise/minimise button -->
+    <maximise-panel-button
+      (click)="toggleMaximiseMinimise(0)"
+      [ngClass]="{ onHover: (panelOrder$ | async | reorderPanelIndexPipe : ( hoveredPanelIndices$ | async  )) === 0 }"
+      [touch-side-class]="0 " class="pe-all">
+    </maximise-panel-button>
+    
     <div *ngIf="sliceViewLoading0$ | async" class="loadingIndicator">
       <div class="spinnerAnimationCircle">
 
@@ -115,6 +121,13 @@
         [positionZ]="getPositionZ(1,spatialData)">
       </nehuba-2dlandmark-unit>
 
+      <!-- maximise/minimise button -->
+      <maximise-panel-button
+        (click)="toggleMaximiseMinimise(1)"
+        [ngClass]="{ onHover: (panelOrder$ | async | reorderPanelIndexPipe : ( hoveredPanelIndices$ | async  )) === 1 }"
+        [touch-side-class]="1 " class="pe-all">
+      </maximise-panel-button>
+
       <div *ngIf="sliceViewLoading1$ | async" class="loadingIndicator">
         <div class="spinnerAnimationCircle">
 
@@ -133,6 +146,13 @@
       [positionZ]="getPositionZ(2,spatialData)">
     </nehuba-2dlandmark-unit>
 
+    <!-- maximise/minimise button -->
+    <maximise-panel-button
+      (click)="toggleMaximiseMinimise(2)"
+      [ngClass]="{ onHover: (panelOrder$ | async | reorderPanelIndexPipe : ( hoveredPanelIndices$ | async  )) === 2 }"
+      [touch-side-class]="2 " class="pe-all">
+    </maximise-panel-button>
+
     <div *ngIf="sliceViewLoading2$ | async" class="loadingIndicator">
       <div class="spinnerAnimationCircle">
 
@@ -143,8 +163,17 @@
 
 <ng-template #overlayiv>
   <layout-floating-container pos11 landmarkContainer>
+
+    <!-- maximise/minimise button -->
+    <maximise-panel-button
+      (click)="toggleMaximiseMinimise(3)"
+      [ngClass]="{ onHover: (panelOrder$ | async | reorderPanelIndexPipe : ( hoveredPanelIndices$ | async  )) === 3 }"
+      [touch-side-class]="3 " class="pe-all">
+    </maximise-panel-button>
+    
     <div *ngIf="perspectiveViewLoading$ | async" class="loadingIndicator">
       <div class="spinnerAnimationCircle"></div>
+
       <div perspectiveLoadingText>
         {{ perspectiveViewLoading$ | async }}
       </div>
diff --git a/src/ui/nehubaContainer/nehubaViewer/nehubaViewer.component.ts b/src/ui/nehubaContainer/nehubaViewer/nehubaViewer.component.ts
index 75311ee37a95cec91e1437cf79cfa8fca8ca1b4a..632afe95980a6da36b072b5a8c40fa86f020b981 100644
--- a/src/ui/nehubaContainer/nehubaViewer/nehubaViewer.component.ts
+++ b/src/ui/nehubaContainer/nehubaViewer/nehubaViewer.component.ts
@@ -77,8 +77,6 @@ export class NehubaViewerUnit implements OnDestroy{
 
   ondestroySubscriptions: Subscription[] = []
 
-  touchStart$ : Observable<any>
-
   constructor(
     private rd: Renderer2,
     public elementRef:ElementRef,
@@ -352,7 +350,7 @@ export class NehubaViewerUnit implements OnDestroy{
   public mouseOverSegment: number | null
   public mouseOverLayer: {name:string,url:string}| null
 
-  private viewportToDatas : [any, any, any] = [null, null, null]
+  public viewportToDatas : [any, any, any] = [null, null, null]
 
   public getNgHash : () => string = () => window['export_nehuba']
     ? window['export_nehuba'].getNgHash()
@@ -394,6 +392,11 @@ export class NehubaViewerUnit implements OnDestroy{
 
     this.onDestroyCb.push(() => window['nehubaViewer'] = null)
 
+    /**
+     * TODO
+     * move this to nehubaContainer
+     * do NOT use position logic to determine idx
+     */
     this.ondestroySubscriptions.push(
       // fromEvent(this.elementRef.nativeElement, 'viewportToData').pipe(
       //   ...takeOnePipe
@@ -405,89 +408,6 @@ export class NehubaViewerUnit implements OnDestroy{
         [0,1,2].forEach(idx => this.viewportToDatas[idx] = events[idx].detail.viewportToData)
       })
     )
-
-    this.touchStart$ = fromEvent(this.elementRef.nativeElement, 'touchstart').pipe(
-      map((ev:TouchEvent) => {
-        const srcElement : HTMLElement = ev.srcElement || (ev as any).originalTarget
-        return {
-          startPos: [ev.touches[0].screenX, ev.touches[0].screenY],
-          elementId: identifySrcElement(srcElement),
-          srcElement,
-          event: ev
-        }
-      })
-    )
-
-    this.ondestroySubscriptions.push(
-
-      this.touchStart$.pipe(
-        switchMap(({startPos, elementId, srcElement}) => fromEvent(this.elementRef.nativeElement,'touchmove').pipe(
-          map((ev: TouchEvent) => (ev.stopPropagation(), ev.preventDefault(), ev)),
-          filter((ev:TouchEvent) => ev.touches.length === 1),
-          map((event:TouchEvent) => ({
-            startPos,
-            event,
-            elementId,
-            srcElement
-          })),
-          scan((acc,ev:any) => {
-            return acc.length < 2
-              ? acc.concat(ev)
-              : acc.slice(1).concat(ev)
-          },[]),
-          map(double => ({
-            elementId: double[0].elementId,
-            deltaX: double.length === 1
-              ? null // startPos[0] - (double[0].event as TouchEvent).touches[0].screenX
-              : double.length === 2
-                ? (double[0].event as TouchEvent).touches[0].screenX - (double[1].event as TouchEvent).touches[0].screenX 
-                : null,
-            deltaY: double.length === 1
-              ? null // startPos[0] - (double[0].event as TouchEvent).touches[0].screenY
-              : double.length === 2
-                ? (double[0].event as TouchEvent).touches[0].screenY - (double[1].event as TouchEvent).touches[0].screenY 
-                : null
-          })),
-          takeUntil(fromEvent(this.elementRef.nativeElement, 'touchend').pipe(filter((ev: TouchEvent) => ev.touches.length === 0)))
-        ))
-      ).subscribe(({ elementId, deltaX, deltaY }) => {
-        if(deltaX === null || deltaY === null){
-          console.warn('deltax/y is null')
-          return
-        }
-        if(elementId === 0 || elementId === 1 || elementId === 2){
-          const {position} = this.nehubaViewer.ngviewer.navigationState 
-          const pos = position.spatialCoordinates
-          window['export_nehuba'].vec3.set(pos, deltaX, deltaY, 0)
-          window['export_nehuba'].vec3.transformMat4(pos, pos, this.viewportToDatas[elementId])
-          position.changed.dispatch()
-        }else if(elementId === 3){
-          const {perspectiveNavigationState} = this.nehubaViewer.ngviewer
-          perspectiveNavigationState.pose.rotateRelative(this.vec3([0, 1, 0]), -deltaX / 4.0 * Math.PI / 180.0)
-          perspectiveNavigationState.pose.rotateRelative(this.vec3([1, 0, 0]), deltaY / 4.0 * Math.PI / 180.0)
-          this.nehubaViewer.ngviewer.perspectiveNavigationState.changed.dispatch()
-        }
-      })
-    )
-
-    this.ondestroySubscriptions.push(
-      this.touchStart$.pipe(
-        switchMap(() => 
-          fromEvent(this.elementRef.nativeElement, 'touchmove').pipe(
-            takeWhile((ev:TouchEvent) => ev.touches.length === 2),
-            map((ev:TouchEvent) => computeDistance(
-                [ev.touches[0].screenX, ev.touches[0].screenY],
-                [ev.touches[1].screenX, ev.touches[1].screenY]
-              )),
-            scan((acc, curr:number) => acc.length < 2
-              ? acc.concat(curr)
-              : acc.slice(1).concat(curr), []),
-            filter(dist => dist.length > 1),
-            map(dist => dist[0] / dist[1])
-          ))
-      ).subscribe(factor => 
-        this.nehubaViewer.ngviewer.navigationState.zoomBy(factor))
-    )
   }
 
   ngOnDestroy(){
diff --git a/src/ui/nehubaContainer/reorderPanelIndex.pipe.ts b/src/ui/nehubaContainer/reorderPanelIndex.pipe.ts
new file mode 100644
index 0000000000000000000000000000000000000000..3077fb30211614615da0e98120c22576aa07b209
--- /dev/null
+++ b/src/ui/nehubaContainer/reorderPanelIndex.pipe.ts
@@ -0,0 +1,14 @@
+import { Pipe, PipeTransform } from "@angular/core";
+
+
+@Pipe({
+  name: 'reorderPanelIndexPipe'
+})
+
+export class ReorderPanelIndexPipe implements PipeTransform{
+  public transform(panelOrder: string, uncorrectedIndex: number){
+    return uncorrectedIndex === null
+      ? null
+      : panelOrder.indexOf(uncorrectedIndex.toString())
+  }
+}
\ No newline at end of file
diff --git a/src/ui/nehubaContainer/touchSideClass.directive.ts b/src/ui/nehubaContainer/touchSideClass.directive.ts
new file mode 100644
index 0000000000000000000000000000000000000000..8d19fd677f226ba580c442633380feba694e91fb
--- /dev/null
+++ b/src/ui/nehubaContainer/touchSideClass.directive.ts
@@ -0,0 +1,49 @@
+import { Directive, Input, ElementRef, OnDestroy, OnInit } from "@angular/core";
+import { Store, select } from "@ngrx/store";
+import { Observable, Subscription } from "rxjs";
+import { distinctUntilChanged, tap } from "rxjs/operators";
+import { removeTouchSideClasses, addTouchSideClasses } from "./util";
+
+@Directive({
+  selector: '[touch-side-class]',
+  exportAs: 'touchSideClass'
+})
+
+export class TouchSideClass implements OnDestroy, OnInit{
+  @Input('touch-side-class')
+  public panelNativeIndex: number
+
+  public panelMode: string
+  private panelMode$: Observable<string>
+
+  private subscriptions: Subscription[] = []
+
+  constructor(
+    private store$: Store<any>,
+    private el: ElementRef
+  ){
+
+    this.panelMode$ = this.store$.pipe(
+      select('ngViewerState'),
+      select('panelMode'),
+      distinctUntilChanged(),
+      tap(mode => this.panelMode = mode)
+    )
+  }
+
+  ngOnInit(){
+    this.subscriptions.push(
+
+      this.panelMode$.subscribe(panelMode => {
+        removeTouchSideClasses(this.el.nativeElement)
+        addTouchSideClasses(this.el.nativeElement, this.panelNativeIndex, panelMode)
+      })
+    )
+  }
+
+  ngOnDestroy(){
+    while(this.subscriptions.length > 0) {
+      this.subscriptions.pop().unsubscribe()
+    }
+  }
+}
\ No newline at end of file
diff --git a/src/ui/nehubaContainer/util.ts b/src/ui/nehubaContainer/util.ts
index d16c41ed10083b19ec7d49783e3db04e8a6cc1a3..d3d1f78e8b7e1761a51f611b3a77b635e19343f9 100644
--- a/src/ui/nehubaContainer/util.ts
+++ b/src/ui/nehubaContainer/util.ts
@@ -1,3 +1,5 @@
+import { FOUR_PANEL, SINGLE_PANEL, H_ONE_THREE, V_ONE_THREE } from "src/services/state/ngViewerState.store";
+
 const flexContCmnCls = ['w-100', 'h-100', 'd-flex', 'justify-content-center', 'align-items-stretch']
 
 const makeRow = (...els:HTMLElement[]) => {
@@ -25,10 +27,54 @@ const washPanels = (panels: [HTMLElement, HTMLElement, HTMLElement, HTMLElement]
   return panels
 }
 
+const top = true
+const left = true
+const right = true
+const bottom = true
+
+const mapModeIdxClass = new Map()
+
+mapModeIdxClass.set(FOUR_PANEL, new Map([
+  [0, { top, left }],
+  [1, { top, right }],
+  [2, { bottom, left }],
+  [3, { right, bottom }]
+]))
+
+mapModeIdxClass.set(SINGLE_PANEL, new Map([
+  [0, { top, left, right, bottom }],
+  [1, {}],
+  [2, {}],
+  [3, {}]
+]))
+
+mapModeIdxClass.set(H_ONE_THREE, new Map([
+  [0, { top, left, bottom }],
+  [1, { top, right }],
+  [2, { right }],
+  [3, { bottom, right }]
+]))
+
+mapModeIdxClass.set(V_ONE_THREE, new Map([
+  [0, { top, left, right }],
+  [1, { bottom, left }],
+  [2, { bottom }],
+  [3, { bottom, right }]
+]))
+
+export const removeTouchSideClasses = (panel: HTMLElement) => {
+  panel.classList.remove(
+    `touch-top`,
+    `touch-left`,
+    `touch-right`,
+    `touch-bottom`)
+  return panel
+}
+
 /**
  * gives a clue of the approximate location of the panel, allowing position of checkboxes/scale bar to be placed in unobtrustive places
  */
-const panelTouchSide = (panel: HTMLElement, { top, left, right, bottom }: any) => {
+export const panelTouchSide = (panel: HTMLElement, { top, left, right, bottom }: any) => {
   if (top) panel.classList.add(`touch-top`)
   if (left) panel.classList.add(`touch-left`)
   if (right) panel.classList.add(`touch-right`)
@@ -36,18 +82,23 @@ const panelTouchSide = (panel: HTMLElement, { top, left, right, bottom }: any) =
   return panel
 }
 
-const top = true
-const left = true
-const right = true
-const bottom = true
+export const addTouchSideClasses = (panel: HTMLElement, actualOrderIndex: number, panelMode: string) => {
+  
+  if (actualOrderIndex < 0) return panel
+
+  const mapIdxClass = mapModeIdxClass.get(panelMode)
+  if (!mapIdxClass) return panel
+
+  const classArg = mapIdxClass.get(actualOrderIndex)
+  if (!classArg) return panel
+
+  return panelTouchSide(panel, classArg)
+}
 
 export const getHorizontalOneThree = (panels:[HTMLElement, HTMLElement, HTMLElement, HTMLElement]) => {
   washPanels(panels)
 
-  panelTouchSide(panels[0], { top, left, bottom })
-  panelTouchSide(panels[1], { top, right })
-  panelTouchSide(panels[2], { right })
-  panelTouchSide(panels[3], { right, bottom })
+  panels.forEach((panel, idx) => addTouchSideClasses(panel, idx, H_ONE_THREE))
   
   const majorContainer = makeCol(panels[0])
   const minorContainer = makeCol(panels[1], panels[2], panels[3])
@@ -61,10 +112,7 @@ export const getHorizontalOneThree = (panels:[HTMLElement, HTMLElement, HTMLElem
 export const getVerticalOneThree = (panels:[HTMLElement, HTMLElement, HTMLElement, HTMLElement]) => {
   washPanels(panels)
   
-  panelTouchSide(panels[0], { top, left, right })
-  panelTouchSide(panels[1], { bottom, left })
-  panelTouchSide(panels[2], { bottom })
-  panelTouchSide(panels[3], { right, bottom })
+  panels.forEach((panel, idx) => addTouchSideClasses(panel, idx, V_ONE_THREE))
 
   const majorContainer = makeRow(panels[0])
   const minorContainer = makeRow(panels[1], panels[2], panels[3])
@@ -79,10 +127,7 @@ export const getVerticalOneThree = (panels:[HTMLElement, HTMLElement, HTMLElemen
 export const getFourPanel = (panels:[HTMLElement, HTMLElement, HTMLElement, HTMLElement]) => {
   washPanels(panels)
   
-  panelTouchSide(panels[0], { top, left })
-  panelTouchSide(panels[1], { top, right })
-  panelTouchSide(panels[2], { bottom, left })
-  panelTouchSide(panels[3], { right, bottom })
+  panels.forEach((panel, idx) => addTouchSideClasses(panel, idx, FOUR_PANEL))
 
   const majorContainer = makeRow(panels[0], panels[1])
   const minorContainer = makeRow(panels[2], panels[3])
@@ -96,7 +141,7 @@ export const getFourPanel = (panels:[HTMLElement, HTMLElement, HTMLElement, HTML
 export const getSinglePanel = (panels:[HTMLElement, HTMLElement, HTMLElement, HTMLElement]) => {
   washPanels(panels)
   
-  panelTouchSide(panels[0], { top, left, bottom, right })
+  panels.forEach((panel, idx) => addTouchSideClasses(panel, idx, SINGLE_PANEL))
 
   const majorContainer = makeRow(panels[0])
   const minorContainer = makeRow(panels[1], panels[2], panels[3])
@@ -106,5 +151,11 @@ export const getSinglePanel = (panels:[HTMLElement, HTMLElement, HTMLElement, HT
 
   minorContainer.className = ''
   minorContainer.style.height = '0px'
-  return makeCol(majorContainer, minorContainer)
-}
\ No newline at end of file
+  return makeRow(majorContainer, minorContainer)
+}
+
+export const isIdentityQuat = ori => Math.abs(ori[0]) < 1e-6
+  && Math.abs(ori[1]) < 1e-6
+  && Math.abs(ori[2]) < 1e-6
+  && Math.abs(ori[3] - 1) < 1e-6
+  
\ No newline at end of file
diff --git a/src/ui/ui.module.ts b/src/ui/ui.module.ts
index 075cf4f3c831b324c6b9f9ff33568af7b83cd35f..e0582147e81c8dd9274aa6fda046f1e2eb0d868e 100644
--- a/src/ui/ui.module.ts
+++ b/src/ui/ui.module.ts
@@ -61,6 +61,9 @@ import { PluginBtnFabColorPipe } from "src/util/pipes/pluginBtnFabColor.pipe";
 import { KgSearchBtnColorPipe } from "src/util/pipes/kgSearchBtnColor.pipe";
 import { TemplateParcellationHasMoreInfo } from "src/util/pipes/templateParcellationHasMoreInfo.pipe";
 import { HumanReadableFileSizePipe } from "src/util/pipes/humanReadableFileSize.pipe";
+import { MaximmisePanelButton } from "./nehubaContainer/maximisePanelButton/maximisePanelButton.component";
+import { TouchSideClass } from "./nehubaContainer/touchSideClass.directive";
+import { ReorderPanelIndexPipe } from "./nehubaContainer/reorderPanelIndex.pipe";
 
 
 @NgModule({
@@ -104,6 +107,7 @@ import { HumanReadableFileSizePipe } from "src/util/pipes/humanReadableFileSize.
     CurrentLayout,
     ViewerStateController,
     RegionTextSearchAutocomplete,
+    MaximmisePanelButton,
 
     /* pipes */
     GroupDatasetByRegion,
@@ -129,10 +133,12 @@ import { HumanReadableFileSizePipe } from "src/util/pipes/humanReadableFileSize.
     SavedRegionsSelectionBtnDisabledPipe,
     TemplateParcellationHasMoreInfo,
     HumanReadableFileSizePipe,
+    ReorderPanelIndexPipe,
 
     /* directive */
     DownloadDirective,
-    ShowToastDirective
+    ShowToastDirective,
+    TouchSideClass
   ],
   entryComponents : [