diff --git a/src/client/actions/index.js b/src/client/actions/index.js
index cf8abb8b436b4c2e3be86a8bffae5db0cd37fbaa..7156dcd066f089aae0b8a2a910ac04b0983a3db8 100644
--- a/src/client/actions/index.js
+++ b/src/client/actions/index.js
@@ -26,7 +26,9 @@ export const UPDATE_FACET_VALUES = 'UPDATE_FACET_VALUES'
 export const UPDATE_FACET_VALUES_CONSTRAIN_SELF = 'UPDATE_FACET_VALUES_CONSTRAIN_SELF'
 export const UPDATE_FACET_OPTION = 'UPDATE_FACET_OPTION'
 export const UPDATE_CLIENT_SIDE_FILTER = 'UPDATE_CLIENT_SIDE_FILTER'
+export const UPDATE_MAP_BOUNDS = 'UPDATE_MAP_BOUNDS'
 export const FETCH_GEOJSON_LAYERS = 'FETCH_GEOJSON_LAYERS'
+export const FETCH_GEOJSON_LAYERS_BACKEND = 'FETCH_GEOJSON_LAYERS_BACKEND'
 export const UPDATE_GEOJSON_LAYERS = 'UPDATE_GEOJSON_LAYERS'
 export const OPEN_MARKER_POPUP = 'OPEN_MARKER_POPUP'
 export const SHOW_ERROR = 'SHOW_ERROR'
@@ -36,6 +38,14 @@ export const LOAD_LOCALES = 'LOAD_LOCALES'
 export const LOAD_LOCALES_FAILED = 'LOAD_LOCALES_FAILED'
 export const UPDATE_LOCALE = 'UPDATE_LOCALE'
 export const ANIMATE_MAP = 'ANIMATE_MAP'
+export const CLIENT_FS_UPDATE_QUERY = 'CLIENT_FS_UPDATE_QUERY'
+export const CLIENT_FS_TOGGLE_DATASET = 'CLIENT_FS_TOGGLE_DATASET'
+export const CLIENT_FS_FETCH_RESULTS = 'CLIENT_FS_FETCH_RESULTS'
+export const CLIENT_FS_FETCH_RESULTS_FAILED = 'CLIENT_FS_FETCH_RESULTS_FAILED'
+export const CLIENT_FS_UPDATE_RESULTS = 'CLIENT_FS_UPDATE_RESULTS'
+export const CLIENT_FS_CLEAR_RESULTS = 'CLIENT_FS_CLEAR_RESULTS'
+export const CLIENT_FS_UPDATE_FACET = 'CLIENT_FS_UPDATE_FACET'
+export const CLIENT_FS_SORT_RESULTS = 'CLIENT_FS_SORT_RESULTS'
 
 export const fetchPaginatedResults = (resultClass, facetClass, sortBy) => ({
   type: FETCH_PAGINATED_RESULTS,
@@ -251,12 +261,57 @@ export const animateMap = value => ({
   type: ANIMATE_MAP,
   value
 })
+export const updateMapBounds = bounds => ({
+  type: UPDATE_MAP_BOUNDS,
+  bounds
+})
 export const fetchGeoJSONLayers = ({ layerIDs, bounds }) => ({
   type: FETCH_GEOJSON_LAYERS,
   layerIDs,
   bounds
 })
+export const fetchGeoJSONLayersBackend = ({ layerIDs, bounds }) => ({
+  type: FETCH_GEOJSON_LAYERS_BACKEND,
+  layerIDs,
+  bounds
+})
 export const updateGeoJSONLayers = ({ payload }) => ({
   type: UPDATE_GEOJSON_LAYERS,
   payload
 })
+export const clientFSUpdateQuery = query => ({
+  type: CLIENT_FS_UPDATE_QUERY,
+  query
+})
+export const clientFSToggleDataset = dataset => ({
+  type: CLIENT_FS_TOGGLE_DATASET,
+  dataset
+})
+
+export const clientFSFetchResults = ({ jenaIndex, query }) => ({
+  type: CLIENT_FS_FETCH_RESULTS,
+  jenaIndex,
+  query
+})
+export const clientFSFetchResultsFailed = error => ({
+  type: CLIENT_FS_FETCH_RESULTS_FAILED,
+  error
+})
+export const clientFSUpdateResults = ({ results, jenaIndex }) => ({
+  type: CLIENT_FS_UPDATE_RESULTS,
+  results,
+  jenaIndex
+})
+export const clientFSClearResults = () => ({
+  type: CLIENT_FS_CLEAR_RESULTS
+})
+export const clientFSUpdateFacet = ({ facetID, value, latestValues }) => ({
+  type: CLIENT_FS_UPDATE_FACET,
+  facetID,
+  value,
+  latestValues
+})
+export const clientFSSortResults = options => ({
+  type: CLIENT_FS_SORT_RESULTS,
+  options
+})
diff --git a/src/client/epics/index.js b/src/client/epics/index.js
index 9a7213c64eef552c73a9939d49ef8b31f0f9885f..5c5943ae020ad6dd40e9f12da3e508227b6b53f9 100644
--- a/src/client/epics/index.js
+++ b/src/client/epics/index.js
@@ -14,7 +14,7 @@ import intl from 'react-intl-universal'
 import localeEN from '../translations/sampo/localeEN'
 import localeFI from '../translations/sampo/localeFI'
 import localeSV from '../translations/sampo/localeSV'
-import { stateToUrl, handleAxiosError } from '../helpers/helpers'
+import { stateToUrl, handleAxiosError, pickSelectedDatasets, boundsToValues } from '../helpers/helpers'
 import querystring from 'querystring'
 import {
   FETCH_RESULT_COUNT,
@@ -32,16 +32,21 @@ import {
   FETCH_SIMILAR_DOCUMENTS_BY_ID_FAILED,
   FETCH_FACET_FAILED,
   FETCH_GEOJSON_LAYERS,
+  FETCH_GEOJSON_LAYERS_BACKEND,
+  CLIENT_FS_FETCH_RESULTS,
+  CLIENT_FS_FETCH_RESULTS_FAILED,
   LOAD_LOCALES,
   updateResultCount,
   updatePaginatedResults,
   updateResults,
+  clientFSUpdateResults,
   updateInstance,
   updateInstanceRelatedData,
   updateFacetValues,
   updateFacetValuesConstrainSelf,
   updateLocale,
-  updateGeoJSONLayers
+  updateGeoJSONLayers,
+  SHOW_ERROR
 } from '../actions'
 import {
   rootUrl,
@@ -133,6 +138,37 @@ const fetchResultsEpic = (action$, state$) => action$.pipe(
   })
 )
 
+const clientFSFetchResultsEpic = (action$, state$) => action$.pipe(
+  ofType(CLIENT_FS_FETCH_RESULTS),
+  withLatestFrom(state$),
+  debounceTime(500),
+  switchMap(([action, state]) => {
+    const { jenaIndex } = action
+    const selectedDatasets = pickSelectedDatasets(state.clientSideFacetedSearch.datasets)
+    const dsParams = selectedDatasets.map(ds => `dataset=${ds}`).join('&')
+    let requestUrl
+    if (action.jenaIndex === 'text') {
+      requestUrl = `${apiUrl}federatedSearch?q=${action.query}&${dsParams}`
+    } else if (action.jenaIndex === 'spatial') {
+      const { latMin, longMin, latMax, longMax } = state.leafletMap
+      requestUrl = `${apiUrl}federatedSearch?latMin=${latMin}&longMin=${longMin}&latMax=${latMax}&longMax=${longMax}&${dsParams}`
+    }
+    return ajax.getJSON(requestUrl).pipe(
+      map(response => clientFSUpdateResults({
+        results: response,
+        jenaIndex
+      })),
+      catchError(error => of({
+        type: CLIENT_FS_FETCH_RESULTS_FAILED,
+        error: error,
+        message: {
+          text: backendErrorText,
+          title: 'Error'
+        }
+      }))
+    )
+  })
+)
 const fetchResultCountEpic = (action$, state$) => action$.pipe(
   ofType(FETCH_RESULT_COUNT),
   withLatestFrom(state$),
@@ -335,7 +371,37 @@ const fetchSimilarDocumentsEpic = (action$, state$) => action$.pipe(
   })
 )
 
-const fetchGeoJSONLayers = action$ => action$.pipe(
+const fetchGeoJSONLayersBackendEpic = (action$, state$) => action$.pipe(
+  ofType(FETCH_GEOJSON_LAYERS_BACKEND),
+  withLatestFrom(state$),
+  mergeMap(([action]) => {
+    const { layerIDs, bounds } = action
+    const { latMin, longMin, latMax, longMax } = boundsToValues(bounds)
+    const params = {
+      layerID: layerIDs,
+      latMin,
+      longMin,
+      latMax,
+      longMax
+    }
+    const requestUrl = `${apiUrl}wfs?${querystring.stringify(params)}`
+    return ajax.getJSON(requestUrl).pipe(
+      map(res => updateGeoJSONLayers({
+        payload: res
+      })),
+      catchError(error => of({
+        type: SHOW_ERROR,
+        error: error,
+        message: {
+          text: backendErrorText,
+          title: 'Error'
+        }
+      }))
+    )
+  })
+)
+
+const fetchGeoJSONLayersEpic = action$ => action$.pipe(
   ofType(FETCH_GEOJSON_LAYERS),
   mergeMap(async action => {
     const { layerIDs, bounds } = action
@@ -346,16 +412,19 @@ const fetchGeoJSONLayers = action$ => action$.pipe(
 
 const fetchGeoJSONLayer = async (layerID, bounds) => {
   const baseUrl = 'http://kartta.nba.fi/arcgis/services/WFS/MV_Kulttuuriymparisto/MapServer/WFSServer'
-  const boundsStr =
-    `${bounds._southWest.lng},${bounds._southWest.lat},${bounds._northEast.lng},${bounds._northEast.lat}`
+  // const baseUrl = 'http://avaa.tdata.fi/geoserver/kotus/ows'
+  // const baseUrl = 'http://avaa.tdata.fi/geoserver/paituli/wfs'
+  // const boundsStr =
+  //   `${bounds._southWest.lng},${bounds._southWest.lat},${bounds._northEast.lng},${bounds._northEast.lat}`
   const mapServerParams = {
     request: 'GetFeature',
     service: 'WFS',
     version: '2.0.0',
     typeName: layerID,
     srsName: 'EPSG:4326',
-    outputFormat: 'geojson',
-    bbox: boundsStr
+    // outputFormat: 'geojson'
+    outputFormat: 'application/json'
+    // bbox: boundsStr
   }
   const url = `${baseUrl}?${querystring.stringify(mapServerParams)}`
   try {
@@ -372,6 +441,7 @@ const fetchGeoJSONLayer = async (layerID, bounds) => {
 const rootEpic = combineEpics(
   fetchPaginatedResultsEpic,
   fetchResultsEpic,
+  clientFSFetchResultsEpic,
   fetchResultCountEpic,
   fetchResultsClientSideEpic,
   fetchByURIEpic,
@@ -379,7 +449,8 @@ const rootEpic = combineEpics(
   fetchFacetConstrainSelfEpic,
   loadLocalesEpic,
   fetchSimilarDocumentsEpic,
-  fetchGeoJSONLayers
+  fetchGeoJSONLayersEpic,
+  fetchGeoJSONLayersBackendEpic
 )
 
 export default rootEpic
diff --git a/src/client/helpers/helpers.js b/src/client/helpers/helpers.js
index a88689d64e8346e80b9965f23e651d49f7fe0712..c8176fd027522e99a3ab71abb4ae0f873aa5e69e 100644
--- a/src/client/helpers/helpers.js
+++ b/src/client/helpers/helpers.js
@@ -68,7 +68,7 @@ export const urlToState = ({ initialState, queryString }) => {
   return params
 }
 
-const boundsToValues = bounds => {
+export const boundsToValues = bounds => {
   const latMin = bounds._southWest.lat
   const longMin = bounds._southWest.lng
   const latMax = bounds._northEast.lat
@@ -99,3 +99,13 @@ export const handleAxiosError = error => {
   }
   console.log(error.config)
 }
+
+export const pickSelectedDatasets = datasets => {
+  const selected = []
+  Object.keys(datasets).map(key => {
+    if (datasets[key].selected) {
+      selected.push(key)
+    }
+  })
+  return selected
+}
diff --git a/src/client/reducers/clientSideFacetedSearch.js b/src/client/reducers/clientSideFacetedSearch.js
deleted file mode 100644
index 3e9362b4bee778cff2b794ed105bf5c2de360302..0000000000000000000000000000000000000000
--- a/src/client/reducers/clientSideFacetedSearch.js
+++ /dev/null
@@ -1,90 +0,0 @@
-import {
-  FETCH_RESULTS_CLIENT_SIDE,
-  UPDATE_RESULTS,
-  CLEAR_RESULTS,
-  UPDATE_CLIENT_SIDE_FILTER,
-  SORT_RESULTS
-} from '../actions'
-
-export const INITIAL_STATE = {
-  query: '',
-  results: [],
-  latestFilter: {
-    id: ''
-  },
-  latestFilterValues: [],
-  resultsFilter: {
-    prefLabel: new Set(),
-    type: new Set()
-  },
-  sortBy: 'prefLabel',
-  sortDirection: 'asc',
-  // groupBy: 'broaderTypeLabel',
-  // groupByLabel: 'Paikanlaji',
-  textResultsFetching: false,
-  spatialResultsFetching: false
-}
-
-const clientSideFacetedSearch = (state = INITIAL_STATE, action) => {
-  if (action.resultClass === 'all') {
-    switch (action.type) {
-      case FETCH_RESULTS_CLIENT_SIDE:
-        return {
-          ...state,
-          [`${action.jenaIndex}ResultsFetching`]: true
-        }
-      case UPDATE_RESULTS:
-        return {
-          ...state,
-          query: action.query,
-          results: action.data,
-          [`${action.jenaIndex}ResultsFetching`]: false
-        }
-      case CLEAR_RESULTS:
-        return {
-          ...state,
-          results: null,
-          fetchingResults: false,
-          query: '',
-          resultsFilter: {
-            prefLabel: new Set(),
-            type: new Set()
-          }
-        }
-      case UPDATE_CLIENT_SIDE_FILTER:
-        return updateResultsFilter(state, action)
-      case SORT_RESULTS:
-        return {
-          ...state,
-          sortBy: action.options.sortBy,
-          sortDirection: action.options.sortDirection
-        }
-      default:
-        return state
-    }
-  } else return state
-}
-
-const updateResultsFilter = (state, action) => {
-  const { property, value, latestValues } = action.filterObj
-  const nSet = state.resultsFilter[property]
-  if (nSet.has(value)) {
-    nSet.delete(value)
-  } else {
-    nSet.add(value)
-  }
-  const newFilter = {
-    ...state.resultsFilter,
-    [property]: nSet
-  }
-  return {
-    ...state,
-    resultsFilter: newFilter,
-    latestFilter: {
-      id: property
-    },
-    latestFilterValues: latestValues
-  }
-}
-
-export default clientSideFacetedSearch
diff --git a/src/client/reducers/index.js b/src/client/reducers/index.js
index a46c34160b7ff925f7dfb014ba9dc0eb06a03aa0..b9c2330e153005f8f73cc31075ccd471e3b11ea5 100644
--- a/src/client/reducers/index.js
+++ b/src/client/reducers/index.js
@@ -1,10 +1,12 @@
 import { combineReducers } from 'redux'
 import { reducer as toastrReducer } from 'react-redux-toastr'
+// general reducers:
 import error from './error'
 import options from './options'
 import animation from './animation'
 import leafletMapLayers from './leafletMapLayers'
-import clientSideFacetedSearch from './clientSideFacetedSearch'
+// portal spefic reducers:
+import clientSideFacetedSearch from './sampo/clientSideFacetedSearch'
 import perspective1 from './sampo/perspective1'
 import perspective2 from './sampo/perspective2'
 import perspective3 from './sampo/perspective3'
diff --git a/src/client/reducers/sampo/clientSideFacetedSearch.js b/src/client/reducers/sampo/clientSideFacetedSearch.js
new file mode 100644
index 0000000000000000000000000000000000000000..2f4b8f86f20450fa959e3152503b13099fc8654e
--- /dev/null
+++ b/src/client/reducers/sampo/clientSideFacetedSearch.js
@@ -0,0 +1,181 @@
+import {
+  CLIENT_FS_UPDATE_QUERY,
+  CLIENT_FS_TOGGLE_DATASET,
+  CLIENT_FS_FETCH_RESULTS,
+  CLIENT_FS_FETCH_RESULTS_FAILED,
+  CLIENT_FS_UPDATE_RESULTS,
+  CLIENT_FS_CLEAR_RESULTS,
+  CLIENT_FS_UPDATE_FACET,
+  CLIENT_FS_SORT_RESULTS
+} from '../../actions'
+
+export const INITIAL_STATE = {
+  query: '',
+  datasets: {
+    kotus: { selected: true },
+    pnr: { selected: true },
+    warsa_karelian_places: { selected: false },
+    tgn: { selected: false }
+  },
+  results: null,
+  facets: {
+    datasetSelector: {
+      facetID: 'datasetSelector',
+      filterType: 'datasetSelector'
+    },
+    prefLabel: {
+      facetID: 'prefLabel',
+      filterType: 'clientFSLiteral',
+      selectionsSet: new Set(),
+      isFetching: false,
+      searchField: true,
+      containerClass: 'ten',
+      type: 'hierarchical'
+    },
+    broaderTypeLabel: {
+      facetID: 'broaderTypeLabel',
+      filterType: 'clientFSLiteral',
+      selectionsSet: new Set(),
+      isFetching: false,
+      searchField: true,
+      containerClass: 'ten',
+      type: 'hierarchical'
+    },
+    broaderAreaLabel: {
+      facetID: 'broaderAreaLabel',
+      filterType: 'clientFSLiteral',
+      selectionsSet: new Set(),
+      isFetching: false,
+      searchField: true,
+      containerClass: 'ten',
+      type: 'hierarchical'
+    },
+    modifier: {
+      facetID: 'modifier',
+      filterType: 'clientFSLiteral',
+      selectionsSet: new Set(),
+      isFetching: false,
+      searchField: true,
+      containerClass: 'ten',
+      type: 'hierarchical'
+    },
+    basicElement: {
+      facetID: 'basicElement',
+      filterType: 'clientFSLiteral',
+      selectionsSet: new Set(),
+      isFetching: false,
+      searchField: true,
+      containerClass: 'ten',
+      type: 'hierarchical'
+    },
+    collectionYear: {
+      facetID: 'collectionYear',
+      filterType: 'clientFSLiteral',
+      selectionsSet: new Set(),
+      isFetching: false,
+      searchField: true,
+      containerClass: 'ten',
+      type: 'hierarchical'
+    },
+    source: {
+      facetID: 'source',
+      filterType: 'clientFSLiteral',
+      selectionsSet: new Set(),
+      isFetching: false,
+      searchField: false,
+      containerClass: 'three',
+      type: 'hierarchical'
+    }
+  },
+  lastlyUpdatedFacet: null,
+  facetUpdateID: 0,
+  sortBy: 'broaderAreaLabel',
+  sortDirection: 'asc',
+  groupBy: 'broaderTypeLabel',
+  groupByLabel: 'Paikanlaji',
+  textResultsFetching: false,
+  spatialResultsFetching: false
+}
+
+const clientSideFacetedSearch = (state = INITIAL_STATE, action) => {
+  switch (action.type) {
+    case CLIENT_FS_UPDATE_QUERY:
+      return { ...state, query: action.query || '' }
+    case CLIENT_FS_TOGGLE_DATASET:
+      return {
+        ...state,
+        suggestions: [],
+        results: null,
+        datasets: {
+          ...state.datasets,
+          [action.dataset]: {
+            ...state.datasets[action.dataset],
+            selected: !state.datasets[action.dataset].selected
+          }
+        }
+      }
+    case CLIENT_FS_FETCH_RESULTS:
+      return {
+        ...state,
+        [`${action.jenaIndex}ResultsFetching`]: true
+      }
+    case CLIENT_FS_FETCH_RESULTS_FAILED:
+      return {
+        ...state,
+        textResultsFetching: false,
+        spatialResultsFetching: false
+      }
+    case CLIENT_FS_CLEAR_RESULTS:
+      return {
+        ...state,
+        results: null,
+        fetchingResults: false,
+        query: INITIAL_STATE.query,
+        facets: INITIAL_STATE.facets
+      }
+    case CLIENT_FS_UPDATE_RESULTS:
+      return {
+        ...state,
+        results: action.results,
+        [`${action.jenaIndex}ResultsFetching`]: false
+      }
+    case CLIENT_FS_UPDATE_FACET:
+      return clientFSUpdateFacet(state, action)
+    case CLIENT_FS_SORT_RESULTS:
+      return {
+        ...state,
+        sortBy: action.options.sortBy,
+        sortDirection: action.options.sortDirection
+      }
+    default:
+      return state
+  }
+}
+
+const clientFSUpdateFacet = (state, action) => {
+  const { facetID, value, latestValues } = action
+  const newSelectionsSet = state.facets[facetID].selectionsSet
+  if (newSelectionsSet.has(value)) {
+    newSelectionsSet.delete(value)
+  } else {
+    newSelectionsSet.add(value)
+  }
+  const updatedFacets = {
+    ...state.facets,
+    [facetID]: {
+      ...state.facets[facetID],
+      selectionsSet: newSelectionsSet
+    }
+  }
+  return {
+    ...state,
+    facetUpdateID: ++state.facetUpdateID,
+    facets: updatedFacets,
+    lastlyUpdatedFacet: {
+      facetID: facetID,
+      values: latestValues
+    }
+  }
+}
+
+export default clientSideFacetedSearch
diff --git a/src/client/translations/sampo/localeEN.js b/src/client/translations/sampo/localeEN.js
index f50dd2e43289cfe2146aa442b214a9bc6e089f2e..3edab969ad228d24f5c5d27ec4469c554fe4053b 100644
--- a/src/client/translations/sampo/localeEN.js
+++ b/src/client/translations/sampo/localeEN.js
@@ -62,7 +62,7 @@ export default {
   leafletMap: {
     basemaps: {
       mapbox: {
-        'light-v10': 'MapBox Light'
+        'light-v10': 'Mapbox Light'
       },
       googleRoadmap: 'Google Maps',
       topographicalMapNLS: 'Topographical map (National Land Survey of Finland)',