diff --git a/deploy/assets/images/atlas-selection/freesurfer.png b/deploy/assets/images/atlas-selection/freesurfer.png
new file mode 100644
index 0000000000000000000000000000000000000000..3f85d87f42345c3487e8548fa2ab91f406b0c67c
Binary files /dev/null and b/deploy/assets/images/atlas-selection/freesurfer.png differ
diff --git a/deploy/atlas/index.js b/deploy/atlas/index.js
index 08d79085e0bdde4e6c8db147853d297d4cc1fad7..eb96d883cff1593df8dfd59cdeeae8b235567342 100644
--- a/deploy/atlas/index.js
+++ b/deploy/atlas/index.js
@@ -55,6 +55,8 @@ const previewImageFIleNameMap = new Map([
   ['minds/core/parcellationatlas/v1.0.0/2449a7f0-6dd0-4b5a-8f1e-aec0db03679d', 'waxholm-v2.png'],
   ['minds/core/parcellationatlas/v1.0.0/11017b35-7056-4593-baad-3934d211daba', 'waxholm-v1.png'],
   ['juelich/iav/atlas/v1.0.0/79cbeaa4ee96d5d3dfe2876e9f74b3dc3d3ffb84304fb9b965b1776563a1069c', 'short-bundle-hcp.png'],
+  ['julich/tmp/referencespace/freesurfer', 'freesurfer.png'],
+  ['julich/tmp/parcellation/freesurfer-test-parc', 'freesurfer.png'],
 ])
 
 router.get('/preview', (req, res) => {
diff --git a/deploy/csp/index.js b/deploy/csp/index.js
index 8f664fae9c0b7f8d01e75bef88c610556791873f..3766830a9e7cf71087e9d92ed1976acf4c6e41fc 100644
--- a/deploy/csp/index.js
+++ b/deploy/csp/index.js
@@ -118,8 +118,9 @@ module.exports = (app) => {
           'cdn.jsdelivr.net/npm/vue@2.5.16/', // plugin load external lib -> vue 2
           'cdn.jsdelivr.net/npm/preact@8.4.2/', // plugin load external lib -> preact
           'unpkg.com/react@16/umd/', // plugin load external lib -> react
-          'unpkg.com/kg-dataset-previewer@1.1.5/', // preview component
+          'unpkg.com/kg-dataset-previewer@1.2.0/', // preview component
           'cdnjs.cloudflare.com/ajax/libs/mathjax/', // math jax
+          'https://unpkg.com/three-surfer@0.0.1/dist/bundle.js', // for threeSurfer (freesurfer support in browser)
           (req, res) => res.locals.nonce ? `'nonce-${res.locals.nonce}'` : null,
           ...SCRIPT_SRC,
           ...WHITE_LIST_SRC,
diff --git a/deploy/templates/query.js b/deploy/templates/query.js
index b7f6f4589e91338cc95af93cd190fa414a9bda2e..9dbbafb4b1c9bbd3d6dc80b97d9128dec7b0532b 100644
--- a/deploy/templates/query.js
+++ b/deploy/templates/query.js
@@ -13,7 +13,8 @@ exports.getAllTemplates = () => new Promise((resolve, reject) => {
     'colin',
     'MNI152',
     'waxholmRatV2_0',
-    'allenMouse'
+    'allenMouse',
+    'freesurfer'
   ]
   resolve(templates)
 })
diff --git a/src/index.html b/src/index.html
index 7cc1d11aea631bb48552a57baf1f12ae9769ecb7..5c9eccb2f215d24f7c1c8fd7d3328f17a9345845 100644
--- a/src/index.html
+++ b/src/index.html
@@ -14,6 +14,7 @@
   
   <script src="https://unpkg.com/kg-dataset-previewer@1.2.0/dist/kg-dataset-previewer/kg-dataset-previewer.js" defer>
   </script>
+  <script src="https://unpkg.com/three-surfer@0.0.1/dist/bundle.js" defer></script>
 
   <title>Interactive Atlas Viewer</title>
   <script type="application/ld+json">
diff --git a/src/res/ext/atlas/atlas_multiLevelHuman.json b/src/res/ext/atlas/atlas_multiLevelHuman.json
index 36d9d008da84e634321e63595bf02760502602ac..1922fe0af4ecf4259d9212580e09d25ce9920818 100644
--- a/src/res/ext/atlas/atlas_multiLevelHuman.json
+++ b/src/res/ext/atlas/atlas_multiLevelHuman.json
@@ -82,9 +82,27 @@
           "@id": "juelich/iav/atlas/v1.0.0/4"
         }
       ]
+    },
+    {
+      "@id": "julich/tmp/referencespace/freesurfer",
+      "name": "Freesurfer atlas test tmpl",
+      "availableIn": [
+        {
+          "@id": "julich/tmp/parcellation/freesurfer-test-parc"
+        }
+      ]
     }
   ],
   "parcellations": [
+    {
+      "@id": "julich/tmp/parcellation/freesurfer-test-parc",
+      "name": "Freesurfer atlas parc test",
+      "availableIn": [
+        {
+          "@id": "julich/tmp/referencespace/freesurfer"
+        }
+      ]
+    },
     {
       "@id": "juelich/iav/atlas/v1.0.0/8",
       "name": "Cytoarchitectonic Maps - v1.18",
diff --git a/src/res/ext/freesurfer.json b/src/res/ext/freesurfer.json
new file mode 100644
index 0000000000000000000000000000000000000000..3c3ef997570df51842400e6397a3952db945e1db
--- /dev/null
+++ b/src/res/ext/freesurfer.json
@@ -0,0 +1,152 @@
+{
+  "name": "Freesurfer Test Tmpl",
+  "@id": "julich/tmp/referencespace/freesurfer",
+  "@type": "julich/tmp/referencespace",
+  "useTheme": "dark",
+  "three-surfer": {
+    "@context": {
+      "rootUrl": "https://neuroglancer-dev.humanbrainproject.eu/precomputed/freesurfer/20210305",
+      "fsa_fsa": "/fsaverage/fsaverage",
+      "fsa_fsa6": "/fsaverage/fsaverage6",
+      "label_fsa_lh": "https://neuroglancer-dev.humanbrainproject.eu/precomputed/freesurfer/20210305/julichbrain_labels/lh.JulichBrain_MPMAtlas_l_N10_nlin2Stdicbm152asym2009c_publicDOI_83fb39b2811305777db0eb80a0fc8b53.allSub_RF_ANTs_MNI152_orig_to_fsaverage.gii",
+      "label_fsa_rh": "https://neuroglancer-dev.humanbrainproject.eu/precomputed/freesurfer/20210305/julichbrain_labels/rh.JulichBrain_MPMAtlas_r_N10_nlin2Stdicbm152asym2009c_publicDOI_172e93a5bec140c111ac862268f0d046.allSub_RF_ANTs_MNI152_orig_to_fsaverage.gii",
+      "label_fsa6_lh": "https://neuroglancer-dev.humanbrainproject.eu/precomputed/freesurfer/20210305/julichbrain_labels/lh.JulichBrain_MPMAtlas_l_N10_nlin2Stdicbm152asym2009c_publicDOI_83fb39b2811305777db0eb80a0fc8b53.BV_MNI152_orig_to_fsaverage6.gii",
+      "label_fsa6_rh": "https://neuroglancer-dev.humanbrainproject.eu/precomputed/freesurfer/20210305/julichbrain_labels/rh.JulichBrain_MPMAtlas_r_N10_nlin2Stdicbm152asym2009c_publicDOI_172e93a5bec140c111ac862268f0d046.BV_MNI152_orig_to_fsaverage6.gii",
+      "label_bv_hcp32k_lh": "https://neuroglancer-dev.humanbrainproject.eu/precomputed/freesurfer/20210305/julichbrain_labels/lh.JulichBrain_MPMAtlas_l_N10_nlin2Stdicbm152asym2009c_publicDOI_83fb39b2811305777db0eb80a0fc8b53.allSub_RF_ANTs_MNI152_orig_to_hcp32k.gii",
+      "label_bv_hcp32k_rh": "https://neuroglancer-dev.humanbrainproject.eu/precomputed/freesurfer/20210305/julichbrain_labels/rh.JulichBrain_MPMAtlas_r_N10_nlin2Stdicbm152asym2009c_publicDOI_172e93a5bec140c111ac862268f0d046.allSub_RF_ANTs_MNI152_orig_to_hcp32k.gii",
+      "label_ants_hcp32k_lh": "https://neuroglancer-dev.humanbrainproject.eu/precomputed/freesurfer/20210305/julichbrain_labels/lh.JulichBrain_MPMAtlas_l_N10_nlin2Stdicbm152asym2009c_publicDOI_83fb39b2811305777db0eb80a0fc8b53.BV_MNI152_orig_to_hcp32k.gii",
+      "label_ants_hcp32k_rh": "https://neuroglancer-dev.humanbrainproject.eu/precomputed/freesurfer/20210305/julichbrain_labels/rh.JulichBrain_MPMAtlas_r_N10_nlin2Stdicbm152asym2009c_publicDOI_172e93a5bec140c111ac862268f0d046.BV_MNI152_orig_to_hcp32k.gii"
+    },
+    "modes": [
+      {
+        "name": "fsaverage/fsaverage/pial",
+        "meshes": [
+          {
+            "mesh": "rootUrl:fsa_fsa:/lh.pial.gii",
+            "colormap": "label_fsa_lh:"
+          },
+          {
+            "mesh": "rootUrl:fsa_fsa:/rh.pial.gii",
+            "colormap": "label_fsa_rh:"
+          }
+        ]
+      },
+      {
+        "name": "fsaverage/fsaverage/curv",
+        "meshes": [
+          {
+            "mesh": "rootUrl:fsa_fsa:/lh.curv.gii",
+            "colormap": "label_fsa_lh:"
+          },
+          {
+            "mesh": "rootUrl:fsa_fsa:/rh.curv.gii",
+            "colormap": "label_fsa_rh:"
+          }
+        ]
+      },
+      {
+        "name": "fsaverage/fsaverage/white",
+        "meshes": [
+          {
+            "mesh": "rootUrl:fsa_fsa:/lh.white.gii",
+            "colormap": "label_fsa_lh:"
+          },
+          {
+            "mesh": "rootUrl:fsa_fsa:/rh.white.gii",
+            "colormap": "label_fsa_rh:"
+          }
+        ]
+      },
+      {
+        "name": "fsaverage/fsaverage/inflated",
+        "meshes": [
+          {
+            "mesh": "rootUrl:fsa_fsa:/lh.inflated.gii",
+            "colormap": "label_fsa_lh:"
+          },
+          {
+            "mesh": "rootUrl:fsa_fsa:/rh.inflated.gii",
+            "colormap": "label_fsa_rh:"
+          }
+        ]
+      },
+      {
+        "name": "fsaverage/fsaverage6/pial",
+        "meshes": [
+          {
+            "mesh": "rootUrl:fsa_fsa6:/lh.pial.gii",
+            "colormap": "label_fsa6_lh:"
+          },
+          {
+            "mesh": "rootUrl:fsa_fsa6:/rh.pial.gii",
+            "colormap": "label_fsa6_rh:"
+          }
+        ]
+      },
+      {
+        "name": "fsaverage/fsaverage6/curv",
+        "meshes": [
+          {
+            "mesh": "rootUrl:fsa_fsa6:/lh.curv.gii",
+            "colormap": "label_fsa6_lh:"
+          },
+          {
+            "mesh": "rootUrl:fsa_fsa6:/rh.curv.gii",
+            "colormap": "label_fsa6_rh:"
+          }
+        ]
+      },
+      {
+        "name": "fsaverage/fsaverage6/white",
+        "meshes": [
+          {
+            "mesh": "rootUrl:fsa_fsa6:/lh.white.gii",
+            "colormap": "label_fsa6_lh:"
+          },
+          {
+            "mesh": "rootUrl:fsa_fsa6:/rh.white.gii",
+            "colormap": "label_fsa6_rh:"
+          }
+        ]
+      },
+      {
+        "name": "fsaverage/fsaverage6/inflated",
+        "meshes": [
+          {
+            "mesh": "rootUrl:fsa_fsa6:/lh.inflated.gii",
+            "colormap": "label_fsa6_lh:"
+          },
+          {
+            "mesh": "rootUrl:fsa_fsa6:/rh.inflated.gii",
+            "colormap": "label_fsa6_rh:"
+          }
+        ]
+      },
+      {
+        "name": "fsaverage/fsaverage6/sphere",
+        "meshes": [
+          {
+            "mesh": "rootUrl:fsa_fsa6:/lh.sphere.gii",
+            "colormap": "label_fsa6_lh:"
+          },
+          {
+            "mesh": "rootUrl:fsa_fsa6:/rh.sphere.gii",
+            "colormap": "label_fsa6_rh:"
+          }
+        ]
+      }
+    ]
+  },
+  "parcellations": [
+    {
+      "@id": "julich/tmp/parcellation/freesurfer-test-parc",
+      "@type": "julich/tmp/parcellation",
+      "name": "Freesurfer Test Parc",
+      "properties": {
+        "name": "Freesurfer Test Parc",
+        "descrption": "This is a test parc for free surfer"
+      },
+      "regions": []
+    }
+  ]
+}
diff --git a/src/util/pureConstant.service.ts b/src/util/pureConstant.service.ts
index d8a4d64b05eaafe1de3f497b1f45ff5776b59545..8e588cd496e1375733a0d16847e7d4e15dc32f52 100644
--- a/src/util/pureConstant.service.ts
+++ b/src/util/pureConstant.service.ts
@@ -1,6 +1,6 @@
 import { Injectable, OnDestroy } from "@angular/core";
 import { Store, select } from "@ngrx/store";
-import { Observable, merge, Subscription, of, throwError, forkJoin, fromEvent, combineLatest, timer } from "rxjs";
+import { Observable, merge, Subscription, of, forkJoin, fromEvent, combineLatest, timer } from "rxjs";
 import { viewerConfigSelectorUseMobileUi } from "src/services/state/viewerConfig.store.helper";
 import { shareReplay, tap, scan, catchError, filter, switchMap, map, take, switchMapTo } from "rxjs/operators";
 import { HttpClient } from "@angular/common/http";
@@ -34,17 +34,20 @@ export class PureContantService implements OnDestroy{
 
   private fetchTemplate = (templateUrl) => this.http.get(`${this.backendUrl}${templateUrl}`, { responseType: 'json' }).pipe(
     switchMap((template: any) => {
-      if (template.nehubaConfig) { return of(template) }
-      if (template.nehubaConfigURL) { return this.http.get(`${this.backendUrl}${template.nehubaConfigURL}`, { responseType: 'json' }).pipe(
-        map(nehubaConfig => {
-          return {
-            ...template,
-            nehubaConfig,
-          }
-        }),
-      )
+      if (template.nehubaConfig) {
+        return of(template)
+      }
+      if (template.nehubaConfigURL) {
+        return this.http.get(`${this.backendUrl}${template.nehubaConfigURL}`, { responseType: 'json' }).pipe(
+          map(nehubaConfig => {
+            return {
+              ...template,
+              nehubaConfig,
+            }
+          }),
+        )
       }
-      throwError('neither nehubaConfig nor nehubaConfigURL defined')
+      return of(template)
     }),
   )
 
diff --git a/src/viewerModule/constants.ts b/src/viewerModule/constants.ts
index 93d2e60eb3f72c1a70111b02b238953a847b41f6..d528f4e6212b3789d080b5641463b4a1b6c4d8a4 100644
--- a/src/viewerModule/constants.ts
+++ b/src/viewerModule/constants.ts
@@ -1,4 +1,6 @@
 import { InjectionToken } from "@angular/core";
 import { Observable } from "rxjs";
 
-export const VIEWERMODULE_DARKTHEME = new InjectionToken<Observable<boolean>>('VIEWERMODULE_DARKTHEME')
\ No newline at end of file
+export type TSupportedViewer = 'nehuba' | 'threeSurfer' | null
+
+export const VIEWERMODULE_DARKTHEME = new InjectionToken<Observable<boolean>>('VIEWERMODULE_DARKTHEME')
diff --git a/src/viewerModule/module.ts b/src/viewerModule/module.ts
index 3c82d55e193ced3cf69a604be5ff681b1ae6b15d..cec938a068e649b42055bd79dd15509736610df1 100644
--- a/src/viewerModule/module.ts
+++ b/src/viewerModule/module.ts
@@ -15,6 +15,7 @@ import { TopMenuModule } from "src/ui/topMenu/module";
 import { UtilModule } from "src/util";
 import { VIEWERMODULE_DARKTHEME } from "./constants";
 import { NehubaModule } from "./nehuba";
+import { ThreeSurferModule } from "./threeSurfer";
 import { RegionAccordionTooltipTextPipe } from "./util/regionAccordionTooltipText.pipe";
 import { ViewerCmp } from "./viewerCmp/viewerCmp.component";
 
@@ -22,6 +23,7 @@ import { ViewerCmp } from "./viewerCmp/viewerCmp.component";
   imports: [
     CommonModule,
     NehubaModule,
+    ThreeSurferModule,
     LayoutModule,
     DatabrowserModule,
     AtlasCmpUiSelectorsModule,
diff --git a/src/viewerModule/nehuba/nehubaViewerGlue/nehubaViewerGlue.component.ts b/src/viewerModule/nehuba/nehubaViewerGlue/nehubaViewerGlue.component.ts
index 25861a93495e4fe93775e5610b2088cdd281d449..baaf1b2ad043fc8b622f7c231b627b1aa4c642a5 100644
--- a/src/viewerModule/nehuba/nehubaViewerGlue/nehubaViewerGlue.component.ts
+++ b/src/viewerModule/nehuba/nehubaViewerGlue/nehubaViewerGlue.component.ts
@@ -1,4 +1,4 @@
-import { Component, ElementRef, Inject, Input, OnChanges, OnDestroy, Optional, SimpleChanges, ViewChild } from "@angular/core";
+import { Component, ElementRef, EventEmitter, Inject, Input, OnChanges, OnDestroy, Optional, Output, SimpleChanges, ViewChild } from "@angular/core";
 import { select, Store } from "@ngrx/store";
 import { asyncScheduler, combineLatest, fromEvent, interval, merge, Observable, of, Subject, timer } from "rxjs";
 import { ngViewerActionAddNgLayer, ngViewerActionRemoveNgLayer, ngViewerActionSetPerspOctantRemoval, ngViewerActionToggleMax } from "src/services/state/ngViewerState/actions";
@@ -52,11 +52,7 @@ export class NehubaGlueCmp implements IViewer, OnChanges, OnDestroy{
   @ViewChild(MouseHoverDirective, { static: true })
   private mouseoverDirective: MouseHoverDirective
 
-  public viewerEvents$ = new Subject<TViewerEvent>()
-  public viewerLoaded$ = this.viewerEvents$.pipe(
-    filter(ev => ev.type === 'VIEWERLOADED'),
-    map(ev => ev.data)
-  )
+  public viewerLoaded: boolean = false
 
   private onhoverSegments = []
   private onDestroyCb: Function[] = []
@@ -259,6 +255,9 @@ export class NehubaGlueCmp implements IViewer, OnChanges, OnDestroy{
     }))
   }
 
+  @Output()
+  public viewerEvent = new EventEmitter<TViewerEvent>()
+
   constructor(
     private store$: Store<any>,
     private el: ElementRef,
@@ -267,7 +266,7 @@ export class NehubaGlueCmp implements IViewer, OnChanges, OnDestroy{
     @Optional() @Inject(API_SERVICE_SET_VIEWER_HANDLE_TOKEN) setViewerHandle: TSetViewerHandle,
     @Optional() @Inject(NEHUBA_INSTANCE_INJTKN) nehubaViewer$: Subject<NehubaViewerUnit>,
   ){
-    this.viewerEvents$.next({
+    this.viewerEvent.emit({
       type: 'MOUSEOVER_ANNOTATION',
       data: {}
     })
@@ -696,10 +695,11 @@ export class NehubaGlueCmp implements IViewer, OnChanges, OnDestroy{
   }
 
   handleViewerLoadedEvent(flag: boolean) {
-    this.viewerEvents$.next({
+    this.viewerEvent.emit({
       type: 'VIEWERLOADED',
       data: flag
     })
+    this.viewerLoaded = flag
   }
 
   private selectHoveredRegion(ev: any, next: Function){
diff --git a/src/viewerModule/nehuba/nehubaViewerGlue/nehubaViewerGlue.template.html b/src/viewerModule/nehuba/nehubaViewerGlue/nehubaViewerGlue.template.html
index ac4241ecfea6d528df7a71ed6fe7c83eea21152a..65057b2b5f6b841514f2619dc063b96fe136afe7 100644
--- a/src/viewerModule/nehuba/nehubaViewerGlue/nehubaViewerGlue.template.html
+++ b/src/viewerModule/nehuba/nehubaViewerGlue/nehubaViewerGlue.template.html
@@ -15,7 +15,7 @@
   </div>
 </div>
 
-<current-layout *ngIf="viewerLoaded$ | async" class="position-absolute w-100 h-100 d-block pe-none top-0 left-0">
+<current-layout *ngIf="viewerLoaded" class="position-absolute w-100 h-100 d-block pe-none top-0 left-0">
   <div class="w-100 h-100 position-relative" cell-i>
     <ng-content *ngTemplateOutlet="ngPanelOverlayTmpl; context: { panelIndex: panelOrder$ | async | getNthElement : 0 | parseAsNumber }"></ng-content>
   </div>
diff --git a/src/viewerModule/threeSurfer/index.ts b/src/viewerModule/threeSurfer/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..748c74827ac5b56cf13833e1133129b42da3d85c
--- /dev/null
+++ b/src/viewerModule/threeSurfer/index.ts
@@ -0,0 +1 @@
+export { ThreeSurferModule } from './module'
diff --git a/src/viewerModule/threeSurfer/module.ts b/src/viewerModule/threeSurfer/module.ts
new file mode 100644
index 0000000000000000000000000000000000000000..fcc19bc37b1cb77bf04e0749534c2e8730715a96
--- /dev/null
+++ b/src/viewerModule/threeSurfer/module.ts
@@ -0,0 +1,21 @@
+import { CommonModule } from "@angular/common";
+import { NgModule } from "@angular/core";
+import { AngularMaterialModule } from "src/ui/sharedModules/angularMaterial.module";
+import { ThreeSurferGlueCmp } from "./threeSurferGlue/threeSurfer.component";
+import { ThreeSurferViewerConfig } from "./tsViewerConfig/tsViewerConfig.component";
+
+@NgModule({
+  imports: [
+    CommonModule,
+    AngularMaterialModule,
+  ],
+  declarations: [
+    ThreeSurferGlueCmp,
+    ThreeSurferViewerConfig,
+  ],
+  exports: [
+    ThreeSurferGlueCmp,
+  ]
+})
+
+export class ThreeSurferModule{}
diff --git a/src/viewerModule/threeSurfer/threeSurferGlue/threeSurfer.component.ts b/src/viewerModule/threeSurfer/threeSurferGlue/threeSurfer.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..f47d5d37e52e0db185950173bba00f97c772040f
--- /dev/null
+++ b/src/viewerModule/threeSurfer/threeSurferGlue/threeSurfer.component.ts
@@ -0,0 +1,106 @@
+import { Component, Input, Output, EventEmitter, ElementRef, OnChanges, OnDestroy, AfterViewInit, ViewChild } from "@angular/core";
+import { IViewer, TViewerEvent } from "src/viewerModule/viewer.interface";
+import { TThreeSurferConfig, TThreeSurferMode } from "../types";
+import { IContext, parseContext } from "../util";
+
+@Component({
+  selector: 'three-surfer-glue-cmp',
+  templateUrl: './threeSurfer.template.html',
+  styleUrls: [
+    './threeSurfer.style.css'
+  ]
+})
+
+export class ThreeSurferGlueCmp implements IViewer, OnChanges, AfterViewInit, OnDestroy {
+
+  @Input()
+  selectedTemplate: any
+
+  @Input()
+  selectedParcellation: any
+  
+  @Output()
+  viewerEvent = new EventEmitter<TViewerEvent>()
+
+  private domEl: HTMLElement
+  private config: TThreeSurferConfig
+  public modes: TThreeSurferMode[] = []
+  public selectedMode: string
+
+  constructor(
+    private el: ElementRef
+  ){
+    this.domEl = this.el.nativeElement
+  }
+
+  tsRef: any
+  loadedMeshes: any[] = []
+
+  private unloadAllMeshes() {
+    while(this.loadedMeshes.length > 0) {
+      const m = this.loadedMeshes.pop()
+      this.tsRef.unloadMesh(m)
+    }
+  }
+
+  public async loadMode(mode: TThreeSurferMode) {
+    
+    this.unloadAllMeshes()
+
+    this.selectedMode = mode.name
+    const { meshes } = mode
+    for (const singleMesh of meshes) {
+      const { mesh, colormap } = singleMesh
+      
+      const tsM = await this.tsRef.loadMesh(
+        parseContext(mesh, [this.config['@context']])
+      )
+      this.loadedMeshes.push(tsM)
+      const tsC = await this.tsRef.loadColormap(
+        parseContext(colormap, [this.config['@context']])
+      )
+      const colorIdx = tsC[0].getData()
+      this.tsRef.applyColorMap(tsM, colorIdx)
+    }
+  }
+
+  ngOnChanges(){
+    if (this.selectedTemplate) {
+      
+      this.config = this.selectedTemplate['three-surfer']
+      // somehow curv ... cannot be parsed properly by gifti parser... something about points missing
+      this.modes = this.config.modes.filter(m => !/curv/.test(m.name))
+      if (!this.tsRef) {
+        this.tsRef = new (window as any).ThreeSurfer(this.domEl, {highlightHovered: true})
+        this.onDestroyCb.push(
+          () => {
+            this.tsRef.dispose()
+            this.tsRef = null
+          }
+        )
+      }
+      // load mode0 by default
+      this.loadMode(this.config.modes[0])
+
+      this.viewerEvent.emit({
+        type: 'VIEWERLOADED',
+        data: true
+      })
+    }
+  }
+  ngAfterViewInit(){
+    const customEvHandler = (ev: CustomEvent) => {
+      // console.log('ev listener', ev.detail.colormap?.verticesValue)
+    }
+    this.domEl.addEventListener((window as any).CUSTOM_EVENTNAME, customEvHandler)
+    this.onDestroyCb.push(
+      () => this.domEl.removeEventListener((window as any).CUSTOM_EVENTNAME, customEvHandler)
+    )
+  }
+
+  private onDestroyCb: (() => void) [] = []
+
+  ngOnDestroy() {
+    while (this.onDestroyCb.length > 0) this.onDestroyCb.pop()()
+  }
+}
diff --git a/src/viewerModule/threeSurfer/threeSurferGlue/threeSurfer.style.css b/src/viewerModule/threeSurfer/threeSurferGlue/threeSurfer.style.css
new file mode 100644
index 0000000000000000000000000000000000000000..ada443b1cc718f3681d2521af88812ef4924c8ce
--- /dev/null
+++ b/src/viewerModule/threeSurfer/threeSurferGlue/threeSurfer.style.css
@@ -0,0 +1,21 @@
+:host
+{
+  display: block;
+  height: 100%;
+  width: 100%;
+}
+
+:host > div
+{
+  display: block;
+  height: 100%;
+  width: 100%;
+}
+
+button[mat-icon-button]
+{
+  z-index: 1;
+  position: fixed;
+  bottom: 1rem;
+  right: 1rem;
+}
diff --git a/src/viewerModule/threeSurfer/threeSurferGlue/threeSurfer.template.html b/src/viewerModule/threeSurfer/threeSurferGlue/threeSurfer.template.html
new file mode 100644
index 0000000000000000000000000000000000000000..1023bb635ee52e510c6065b9b127cc6d962383c8
--- /dev/null
+++ b/src/viewerModule/threeSurfer/threeSurferGlue/threeSurfer.template.html
@@ -0,0 +1,21 @@
+<button mat-icon-button
+  color="primary"
+  class="pe-all"
+  [matMenuTriggerFor]="fsModeSelMenu">
+  <i class="fas fa-bars"></i>
+</button>
+
+<mat-menu #fsModeSelMenu="matMenu">
+  <button *ngFor="let mode of modes"
+    mat-menu-item
+    (click)="loadMode(mode)"
+    color="primary">
+    <mat-icon
+      fontSet="fas"
+      [fontIcon]="mode.name === selectedMode ? 'fa-circle' : 'fa-none'">
+    </mat-icon>
+    <span>
+      {{ mode.name }}
+    </span>
+  </button>
+</mat-menu>
diff --git a/src/viewerModule/threeSurfer/tsViewerConfig/tsViewerConfig.component.ts b/src/viewerModule/threeSurfer/tsViewerConfig/tsViewerConfig.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..d59b43509047e1853dca0e9be80c6753f8ba65b9
--- /dev/null
+++ b/src/viewerModule/threeSurfer/tsViewerConfig/tsViewerConfig.component.ts
@@ -0,0 +1,13 @@
+import { Component } from "@angular/core";
+
+@Component({
+  selector: 'three-surfer-viewer-config',
+  templateUrl: './tsViewerConfig.template.html',
+  styleUrls: [
+    './tsViewerConfig.style.css'
+  ]
+})
+
+export class ThreeSurferViewerConfig {
+  
+}
diff --git a/src/viewerModule/threeSurfer/tsViewerConfig/tsViewerConfig.style.css b/src/viewerModule/threeSurfer/tsViewerConfig/tsViewerConfig.style.css
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/src/viewerModule/threeSurfer/tsViewerConfig/tsViewerConfig.template.html b/src/viewerModule/threeSurfer/tsViewerConfig/tsViewerConfig.template.html
new file mode 100644
index 0000000000000000000000000000000000000000..578678e805ef42854e97e7d420f8146f584d0af5
--- /dev/null
+++ b/src/viewerModule/threeSurfer/tsViewerConfig/tsViewerConfig.template.html
@@ -0,0 +1 @@
+tsviewer config
diff --git a/src/viewerModule/threeSurfer/types.ts b/src/viewerModule/threeSurfer/types.ts
new file mode 100644
index 0000000000000000000000000000000000000000..62fe903442c2764d824db6d0be0ef0e25bcd3245
--- /dev/null
+++ b/src/viewerModule/threeSurfer/types.ts
@@ -0,0 +1,16 @@
+import { IContext } from './util'
+
+export type TThreeSurferMesh = {
+  colormap: string
+  mesh: string
+}
+
+export type TThreeSurferMode = {
+  name: string
+  meshes: TThreeSurferMesh[]
+}
+
+export type TThreeSurferConfig = {
+  ['@context']: IContext
+  modes: TThreeSurferMode[]
+}
diff --git a/src/viewerModule/threeSurfer/util.ts b/src/viewerModule/threeSurfer/util.ts
new file mode 100644
index 0000000000000000000000000000000000000000..34a0d85065cf7104d57faf08790dd943804f7b26
--- /dev/null
+++ b/src/viewerModule/threeSurfer/util.ts
@@ -0,0 +1,14 @@
+export interface IContext {
+  [key: string]: string
+}
+
+export function parseContext(input: string, contexts: IContext[]){
+  let output = input
+  for (const context of contexts) {
+    for (const key in context) {
+      const re = new RegExp(`${key}:`, 'g')
+      output = output.replace(re, context[key])
+    }
+  }
+  return output
+}
diff --git a/src/viewerModule/viewer.interface.ts b/src/viewerModule/viewer.interface.ts
index 4e4eada8942a3b563f036b4566b73e42be452d3e..fdd5600b3a67aafb26c06335c4ec93d5742c0bb5 100644
--- a/src/viewerModule/viewer.interface.ts
+++ b/src/viewerModule/viewer.interface.ts
@@ -1,4 +1,4 @@
-import { Observable } from "rxjs";
+import { EventEmitter } from "@angular/core";
 
 type TLayersColorMap = Map<string, Map<number, { red: number, green: number, blue: number }>>
 
@@ -44,5 +44,5 @@ export type IViewer = {
   selectedTemplate: any
   selectedParcellation: any
   viewerCtrlHandler?: IViewerCtrl
-  viewerEvents$: Observable<TViewerEvent>
+  viewerEvent: EventEmitter<TViewerEvent>
 }
\ No newline at end of file
diff --git a/src/viewerModule/viewerCmp/viewerCmp.component.ts b/src/viewerModule/viewerCmp/viewerCmp.component.ts
index 7d9e95ff855deb3f68647705a6cff34ad9de1487..6eda39c0b01bdec097d2c0abaa1345ebe160f96a 100644
--- a/src/viewerModule/viewerCmp/viewerCmp.component.ts
+++ b/src/viewerModule/viewerCmp/viewerCmp.component.ts
@@ -1,11 +1,9 @@
-import { AfterViewInit, Component, Inject, Input, OnDestroy, Optional, ViewChild } from "@angular/core";
+import { Component, Inject, Input, OnDestroy, Optional, ViewChild } from "@angular/core";
 import { select, Store } from "@ngrx/store";
 import { combineLatest, Observable, Subject, Subscription } from "rxjs";
 import { distinctUntilChanged, filter, map, startWith } from "rxjs/operators";
 import { viewerStateHelperSelectParcellationWithId, viewerStateRemoveAdditionalLayer, viewerStateSetSelectedRegions } from "src/services/state/viewerState/actions";
 import { viewerStateContextedSelectedRegionsSelector, viewerStateGetOverlayingAdditionalParcellations, viewerStateParcVersionSelector, viewerStateSelectedParcellationSelector,  viewerStateSelectedTemplateSelector, viewerStateStandAloneVolumes } from "src/services/state/viewerState/selectors"
-import { NehubaGlueCmp } from "../nehuba";
-import { IViewer } from "../viewer.interface";
 import { CONST, ARIA_LABELS } from 'common/constants'
 import { ngViewerActionClearView } from "src/services/state/ngViewerState/actions";
 import { ngViewerSelectorClearViewEntries } from "src/services/state/ngViewerState/selectors";
@@ -13,6 +11,7 @@ import { uiActionHideAllDatasets, uiActionHideDatasetWithId } from "src/services
 import { REGION_OF_INTEREST } from "src/util/interfaces";
 import { animate, state, style, transition, trigger } from "@angular/animations";
 import { SwitchDirective } from "src/util/directives/switch.directive";
+import { TSupportedViewer } from "../constants";
 
 @Component({
   selector: 'iav-cmp-viewer-container',
@@ -67,12 +66,11 @@ import { SwitchDirective } from "src/util/directives/switch.directive";
   ]
 })
 
-export class ViewerCmp implements OnDestroy, AfterViewInit{
+export class ViewerCmp implements OnDestroy {
 
   public CONST = CONST
   public ARIA_LABELS = ARIA_LABELS
 
-  @ViewChild(NehubaGlueCmp) viewerCmp: IViewer
   @ViewChild('sideNavTopSwitch', { static: true })
   private sidenavTopSwitch: SwitchDirective
 
@@ -98,6 +96,15 @@ export class ViewerCmp implements OnDestroy, AfterViewInit{
     distinctUntilChanged(),
   )
 
+  public useViewer$: Observable<TSupportedViewer> = this.templateSelected$.pipe(
+    map(t => {
+      if (!t) return null
+      if (!!t['nehubaConfigURL'] || !!t['nehubaConfig']) return 'nehuba'
+      if (!!t['three-surfer']) return 'threeSurfer'
+      return null
+    })
+  )
+
   public isStandaloneVolumes$ = this.store$.pipe(
     select(viewerStateStandAloneVolumes),
     map(v => v.length > 0)
@@ -156,16 +163,6 @@ export class ViewerCmp implements OnDestroy, AfterViewInit{
     )
   }
 
-  ngAfterViewInit(){
-    this.subscriptions.push(
-      this.viewerCmp.viewerEvents$.pipe(
-        filter(ev => ev.type === 'VIEWERLOADED'),
-      ).subscribe(ev => {
-        this.viewerLoaded = ev.data
-      })
-    )
-  }
-
   ngOnDestroy() {
     while (this.subscriptions.length) this.subscriptions.pop().unsubscribe()
   }
@@ -229,4 +226,4 @@ export class ViewerCmp implements OnDestroy, AfterViewInit{
         : uiActionHideAllDatasets()
     )
   }
-}
\ No newline at end of file
+}
diff --git a/src/viewerModule/viewerCmp/viewerCmp.template.html b/src/viewerModule/viewerCmp/viewerCmp.template.html
index 95052becdcfeccc8ba2e4d163b8ec643d81dc302..215f0459a933cc6b1833d639b734886414bab756 100644
--- a/src/viewerModule/viewerCmp/viewerCmp.template.html
+++ b/src/viewerModule/viewerCmp/viewerCmp.template.html
@@ -239,11 +239,26 @@
       </ui-splashscreen>
       <div class="h-100 w-100 overflow-hidden position-relative"
         [ngClass]="{'pe-none': !viewerLoaded}">
-        <iav-cmp-viewer-nehuba-glue class="d-block w-100 h-100 position-absolute left-0 top-0"
-          [selectedTemplate]="templateSelected$ | async"
-          [selectedParcellation]="parcellationSelected$ | async"
-          #iavCmpViewerNehubaGlue="iavCmpViewerNehubaGlue">
-        </iav-cmp-viewer-nehuba-glue>
+
+        <ng-container [ngSwitch]="useViewer$ | async">
+
+          <iav-cmp-viewer-nehuba-glue class="d-block w-100 h-100 position-absolute left-0 top-0"
+            *ngSwitchCase="'nehuba'"
+            (viewerEvent)="viewerLoaded = $event.data"
+            [selectedTemplate]="templateSelected$ | async"
+            [selectedParcellation]="parcellationSelected$ | async"
+            #iavCmpViewerNehubaGlue="iavCmpViewerNehubaGlue">
+          </iav-cmp-viewer-nehuba-glue>
+
+          <three-surfer-glue-cmp class="d-block w-100 h-100 position-absolute left-0 top-0"
+            *ngSwitchCase="'threeSurfer'"
+            (viewerEvent)="viewerLoaded = $event.data"
+            [selectedTemplate]="templateSelected$ | async"
+            [selectedParcellation]="parcellationSelected$ | async">
+          </three-surfer-glue-cmp>
+
+          <div *ngSwitchDefault>Template not supported by any of the viewers</div>
+        </ng-container>
 
       </div>
     </div>