diff --git a/LICENSE b/LICENSE
index 85ec0235b4efad3ca03f3c51a63ec5458ad7e268..824a4fbde8468411db007a66412dd348e156bd3a 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
 MIT License
 
-Copyright (c) 2020 Semantic Computing Research Group (SeCo)
+Copyright (c) 2021 Semantic Computing Research Group (SeCo)
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
diff --git a/src/client/actions/index.js b/src/client/actions/index.js
index d496cc5df43bf277681185f0603b2796a3d3a187..fb16620bcff578e88d2fa459a6cd940f3b55a557 100644
--- a/src/client/actions/index.js
+++ b/src/client/actions/index.js
@@ -387,8 +387,9 @@ export const clientFSSortResults = options => ({
   type: CLIENT_FS_SORT_RESULTS,
   options
 })
-export const fetchKnowledgeGraphMetadata = ({ resultClass }) => ({
+export const fetchKnowledgeGraphMetadata = ({ perspectiveID, resultClass }) => ({
   type: FETCH_KNOWLEDGE_GRAPH_METADATA,
+  perspectiveID,
   resultClass
 })
 export const fetchKnowledgeGraphMetadataFailded = (resultClass, error, message) => ({
diff --git a/src/client/components/App.js b/src/client/components/App.js
index 074e05d723f0e2fd9c316af7e64d97a300af4113..15d5caeae448e4859362c3564910734ef59a897e 100644
--- a/src/client/components/App.js
+++ b/src/client/components/App.js
@@ -1,14 +1,16 @@
 import React from 'react'
 import { MuiThemeProvider, createTheme } from '@material-ui/core/styles'
 import SemanticPortal from '../containers/SemanticPortal'
+import portalConfig from '../../configs/portalConfig.json'
+const { colorPalette } = portalConfig.layoutConfig
 
 const theme = createTheme({
   palette: {
     primary: {
-      main: '#212121'
+      main: colorPalette.primary.main
     },
     secondary: {
-      main: '#EB1806'
+      main: colorPalette.secondary.main
     }
   },
   overrides: {
diff --git a/src/client/components/facet_results/LeafletMap.js b/src/client/components/facet_results/LeafletMap.js
index f257b9b0df464f629c406b67c1f0669a134f4a1a..55e09216ae765352af916597e14c1a1b3ff0eb63 100644
--- a/src/client/components/facet_results/LeafletMap.js
+++ b/src/client/components/facet_results/LeafletMap.js
@@ -117,6 +117,7 @@ class LeafletMap extends React.Component {
     if (this.props.mapMode &&
       (this.props.pageType === 'facetResults' || this.props.pageType === 'instancePage')) {
       this.props.fetchResults({
+        perspectiveID: this.props.perspectiveConfig.id,
         resultClass: this.props.resultClass,
         facetClass: this.props.facetClass,
         sortBy: null,
@@ -166,6 +167,7 @@ class LeafletMap extends React.Component {
     // check if filters have changed
     if (has(prevProps, 'facetUpdateID') && prevProps.facetUpdateID !== this.props.facetUpdateID) {
       this.props.fetchResults({
+        perspectiveID: this.props.perspectiveConfig.id,
         resultClass: this.props.resultClass,
         facetClass: this.props.facetClass,
         sortBy: null,
@@ -181,6 +183,7 @@ class LeafletMap extends React.Component {
     // check if map mode has changed
     if (prevState.mapMode !== this.state.mapMode) {
       this.props.fetchResults({
+        perspectiveID: this.props.perspectiveConfig.id,
         resultClass: this.props.resultClass,
         facetClass: this.props.facetClass,
         sortBy: null
diff --git a/src/client/components/facet_results/ReactVirtualizedList.js b/src/client/components/facet_results/ReactVirtualizedList.js
new file mode 100644
index 0000000000000000000000000000000000000000..8dd0cf597c18c712d29be51bc7f63a78c246ef90
--- /dev/null
+++ b/src/client/components/facet_results/ReactVirtualizedList.js
@@ -0,0 +1,194 @@
+import React, { useEffect } from 'react'
+import { List, AutoSizer } from 'react-virtualized'
+import { makeStyles } from '@material-ui/core/styles'
+import Card from '@material-ui/core/Card'
+import CardActionArea from '@material-ui/core/CardActionArea'
+import CardContent from '@material-ui/core/CardContent'
+import CardMedia from '@material-ui/core/CardMedia'
+import Typography from '@material-ui/core/Typography'
+import intl from 'react-intl-universal'
+import { Link } from 'react-router-dom'
+import CircularProgress from '@material-ui/core/CircularProgress'
+import purple from '@material-ui/core/colors/purple'
+
+const useStyles = makeStyles(theme => ({
+  root: props => {
+    return {
+      marginTop: theme.spacing(1),
+      maxWidth: 350,
+      height: window.innerHeight - props.layoutConfig.topBar.reducedHeight - props.layoutConfig.tabHeight - 139,
+      [theme.breakpoints.up(600)]: {
+        height: window.innerHeight - props.layoutConfig.topBar.reducedHeight - props.layoutConfig.tabHeight - 256
+      },
+      [theme.breakpoints.up(props.layoutConfig.hundredPercentHeightBreakPoint)]: {
+        height: window.innerHeight - props.layoutConfig.topBar.reducedHeight - props.layoutConfig.tabHeight - 178
+      },
+      [theme.breakpoints.up(1100)]: {
+        height: window.innerHeight - props.layoutConfig.topBar.reducedHeight - props.layoutConfig.tabHeight - 196
+      },
+      [theme.breakpoints.up(props.layoutConfig.reducedHeightBreakpoint)]: {
+        height: window.innerHeight - props.layoutConfig.topBar.reducedHeight - props.layoutConfig.tabHeight - 265
+      },
+      fontFamily: 'Roboto'
+    }
+  },
+  list: {
+    [theme.breakpoints.up('md')]: {
+      paddingRight: 4
+    }
+  },
+  link: {
+    textDecoration: 'none'
+  },
+  progressContainer: {
+    width: '100%',
+    height: 600,
+    [theme.breakpoints.up('md')]: {
+      height: 'calc(100% - 80px)'
+    },
+    display: 'flex',
+    alignItems: 'center',
+    justifyContent: 'center'
+  }
+}))
+
+const ReactVirtualizedList = props => {
+  const classes = useStyles(props)
+  const { results } = props.perspectiveState
+
+  useEffect(() => {
+    props.fetchResults({
+      resultClass: props.resultClass,
+      facetClass: props.facetClass
+    })
+  }, [])
+
+  useEffect(() => {
+    const { facetUpdateID } = props
+    if (facetUpdateID > 0) {
+      props.fetchResults({
+        resultClass: props.resultClass,
+        facetClass: props.facetClass
+      })
+    }
+  }, [props.facetUpdateID])
+
+  const rowRenderer = ({
+    key, // Unique key within array of rows
+    index, // Index of row within collection
+    isScrolling, // The List is currently being scrolled
+    isVisible, // This row is visible within the List (eg it is not an overscanned row)
+    style // Style object to be applied to row (to position it)
+  }) => {
+    const data = props.perspectiveState.results[index]
+    let image = null
+    if (data.imageURL) {
+      const { imageURL } = data
+      image = imageURL.includes(', ') ? imageURL.split(', ')[0] : imageURL
+    }
+    return (
+      <div className={classes.rowRoot} key={key} style={style}>
+        <Link className={classes.link} to={data.dataProviderUrl}>
+          <Card>
+            <CardActionArea>
+              {image &&
+                <CardMedia
+                  component='img'
+                  alt='Kuva löydöstä'
+                  height='140'
+                  image={image}
+                  title='Kuva löydöstä'
+                />}
+              <CardContent>
+                <Typography gutterBottom variant='h5' component='h2'>
+                  {data.findName}
+                </Typography>
+                <Typography variant='body2' color='textSecondary' component='p'>
+                  <strong>{intl.get('perspectives.finds.properties.objectType.label')}: </strong>
+                  {data.objectType}
+                </Typography>
+                <Typography variant='body2' color='textSecondary' component='p'>
+                  <strong>{intl.get('perspectives.finds.properties.material.label')}: </strong>
+                  {data.material}
+                </Typography>
+                <Typography variant='body2' color='textSecondary' component='p'>
+                  <strong>{intl.get('perspectives.finds.properties.period.label')}: </strong>
+                  {data.period}
+                </Typography>
+                <Typography variant='body2' color='textSecondary' component='p'>
+                  <strong>{intl.get('perspectives.finds.properties.municipality.label')}: </strong>
+                  {data.municipality}
+                </Typography>
+              </CardContent>
+            </CardActionArea>
+          </Card>
+        </Link>
+
+      </div>
+    )
+  }
+
+  const getRowHeight = ({ index }) => {
+    const data = props.perspectiveState.results[index]
+    let height = 300
+    if (!data.imageURL) {
+      height -= 140
+    }
+    if (data.findName.length > 26) {
+      height += 32
+    }
+    if (data.findName.length > 40) {
+      height += 54
+    }
+    if (data.period) {
+      const limit = window.innerWidth < 328 ? 25 : 34
+      if (data.period.length > limit) {
+        height += 20
+      }
+    }
+    return height
+  }
+
+  const validResults = () => {
+    const { results, resultClass } = props.perspectiveState
+    if (resultClass !== props.resultClass) { return false }
+    if (results == null) { return false }
+    if (results.length < 1) { return false }
+    return true
+  }
+
+  // if (props.perspectiveState.results) {
+  //   props.perspectiveState.results.map(r => {
+  //     if (r.period && r.period.length > 33) {
+  //       console.log(r)
+  //     }
+  //   })
+  // }
+
+  return (
+    <div className={classes.root}>
+      {(!validResults() || props.perspectiveState.results.fetching)
+        ? (
+          <div className={classes.progressContainer}>
+            <CircularProgress style={{ color: purple[500] }} thickness={5} />
+          </div>
+          )
+        : (
+          <AutoSizer>
+            {({ height, width }) => (
+              <List
+                className={classes.list}
+                height={height}
+                width={width}
+                rowCount={results.length}
+                rowHeight={getRowHeight}
+                rowRenderer={rowRenderer}
+              />
+            )}
+          </AutoSizer>
+          )}
+    </div>
+  )
+}
+
+export default ReactVirtualizedList
diff --git a/src/client/components/facet_results/ResultClassRoute.js b/src/client/components/facet_results/ResultClassRoute.js
index cb944843df64dd36f2b85f1c45dae7926ffbb7d8..31b2f0137d240f1b385e20df4e8e6da388e1babf 100644
--- a/src/client/components/facet_results/ResultClassRoute.js
+++ b/src/client/components/facet_results/ResultClassRoute.js
@@ -9,6 +9,8 @@ const LeafletMap = lazy(() => import('./LeafletMap'))
 const Deck = lazy(() => import('./Deck'))
 const ApexCharts = lazy(() => import('./ApexCharts'))
 const Network = lazy(() => import('./Network'))
+const VideoPage = lazy(() => import('../main_layout/VideoPage'))
+const WordCloud = lazy(() => import('../main_layout/WordCloud'))
 // const BarChartRace = lazy(() => import('../../facet_results/BarChartRace'))
 const ExportCSV = lazy(() => import('./ExportCSV'))
 const Export = lazy(() => import('./Export'))
@@ -86,19 +88,36 @@ const ResultClassRoute = props => {
       )
       break
     case 'InstancePageTable': {
+      const properties = resultClassConfig.properties
+        ? resultClassConfig.properties
+        : getVisibleRows(perspectiveState)
+      let instanceTableProps = {
+        portalConfig,
+        perspectiveConfig: perspective,
+        layoutConfig,
+        resultClass,
+        fetchResults: props.fetchResults,
+        properties,
+        screenSize
+      }
+      if (resultClassConfig.fetchResultsWhenMounted) {
+        instanceTableProps = {
+          ...instanceTableProps,
+          fetchResultsWhenMounted: true,
+          data: perspectiveState.results ? perspectiveState.results[0] : null,
+          uri: perspectiveState.instanceTableData.id,
+          resultUpdateID: perspectiveState.resultUpdateID
+        }
+      } else {
+        instanceTableProps = {
+          ...instanceTableProps,
+          data: perspectiveState.instanceTableData
+        }
+      }
       routeComponent = (
         <Route
           path={path}
-          render={routeProps =>
-            <InstancePageTable
-              portalConfig={portalConfig}
-              perspectiveConfig={perspective}
-              resultClass={props.defaultResultClass}
-              data={perspectiveState.instanceTableData}
-              properties={getVisibleRows(perspectiveState)}
-              screenSize={screenSize}
-              layoutConfig={layoutConfig}
-            />}
+          render={routeProps => <InstancePageTable {...instanceTableProps} />}
         />
       )
       break
@@ -157,6 +176,9 @@ const ResultClassRoute = props => {
           updateFacetOption: props.updateFacetOption
         }
       }
+      if (pageType === 'instancePage') {
+        leafletProps.uri = perspectiveState.instanceTableData.id
+      }
       routeComponent = (
         <Route
           path={path}
@@ -233,7 +255,8 @@ const ResultClassRoute = props => {
         fill,
         createChartData,
         doNotRenderOnMount = false,
-        dropdownForResultClasses = false
+        dropdownForResultClasses = false,
+        dropdownForChartTypes = false
       } = resultClassConfig
       const apexProps = {
         portalConfig,
@@ -264,6 +287,17 @@ const ResultClassRoute = props => {
         apexProps.resultClasses = resultClassConfig.resultClasses
         apexProps.dropdownForResultClasses = true
       }
+      if (dropdownForChartTypes && has(resultClassConfig, 'chartTypes')) {
+        const { chartTypes } = resultClassConfig
+        const newChartTypes = chartTypes.map(chartType => {
+          return {
+            id: chartType.id,
+            createChartData: props.apexChartsConfig[chartType.createChartData]
+          }
+        })
+        apexProps.chartTypes = newChartTypes
+        apexProps.dropdownForChartTypes = true
+      }
       routeComponent = (
         <Route
           path={path}
@@ -318,6 +352,42 @@ const ResultClassRoute = props => {
       )
       break
     }
+    case 'VideoPage': {
+      const videoPageProps = {
+        portalConfig,
+        perspectiveConfig: perspective,
+        layoutConfig,
+        screenSize,
+        resultClass,
+        perspectiveState,
+        properties: getVisibleRows(perspectiveState),
+        localID: props.localID,
+        routeProps: props.routeProps,
+        videoPlayerState: props.videoPlayerState,
+        updateVideoPlayerTime: props.updateVideoPlayerTime
+      }
+      routeComponent = (
+        <Route
+          path={path}
+          render={() =>
+            <VideoPage {...videoPageProps} />}
+        />
+      )
+      break
+    }
+    case 'WordCloud': {
+      const wordCloudProps = {
+        data: perspectiveState.instanceTableData[resultClassConfig.wordCloudProperty]
+      }
+      routeComponent = (
+        <Route
+          path={path}
+          render={() =>
+            <WordCloud {...wordCloudProps} />}
+        />
+      )
+      break
+    }
     case 'Export': {
       const { pageType = 'facetResults' } = resultClassConfig
       const exportResultClass = resultClassConfig.resultClass
diff --git a/src/client/components/main_layout/InstancePage.js b/src/client/components/main_layout/InstancePage.js
index 9ab80038559d4ad4f359450193909223b8f46a1b..0dc9b2a0e4b87ac30e9d87141871527cb8a1f89a 100644
--- a/src/client/components/main_layout/InstancePage.js
+++ b/src/client/components/main_layout/InstancePage.js
@@ -159,6 +159,7 @@ class InstancePage extends React.Component {
                     defaultResultClass={resultClass}
                     resultClass={instancePageResultClass}
                     resultClassConfig={resultClassConfig}
+                    localID={this.state.localID}
                     {...this.props}
                   />
                 )
diff --git a/src/client/components/main_layout/InstancePageTable.js b/src/client/components/main_layout/InstancePageTable.js
index 7c7525631098a1403c330d1624b3bc2ca0aebbca..dd7c73ee59d43ce5b31a7a6008160f84dac5c3bf 100644
--- a/src/client/components/main_layout/InstancePageTable.js
+++ b/src/client/components/main_layout/InstancePageTable.js
@@ -87,7 +87,8 @@ class InstancePageTable extends React.Component {
   componentDidMount = () => {
     if (this.props.fetchResultsWhenMounted) {
       this.props.fetchResults({
-        resultClass: this.props.resultClassVariant,
+        perspectiveID: this.props.perspectiveConfig.id,
+        resultClass: this.props.resultClass,
         facetClass: this.props.facetClass,
         uri: this.props.uri
       })
@@ -123,15 +124,16 @@ class InstancePageTable extends React.Component {
   }
 
   render = () => {
-    const { classes, data, resultClass, properties, screenSize } = this.props
+    const { classes, data, properties, screenSize, perspectiveConfig } = this.props
+    const perspectiveID = perspectiveConfig.id
     return (
       <>
         {data &&
           <Table className={classes.instanceTable} size='small'>
             <TableBody>
               {properties.map(row => {
-                const label = intl.get(`perspectives.${resultClass}.properties.${row.id}.label`)
-                const description = intl.get(`perspectives.${resultClass}.properties.${row.id}.description`)
+                const label = intl.get(`perspectives.${perspectiveID}.properties.${row.id}.label`)
+                const description = intl.get(`perspectives.${perspectiveID}.properties.${row.id}.description`)
                 const {
                   id, valueType, makeLink, externalLink, sortValues, sortBy, numberedList, minWidth,
                   linkAsButton, collapsedMaxWords, showSource, sourceExternalLink, renderAsHTML, HTMLParserTask
diff --git a/src/client/components/main_layout/KnowledgeGraphMetadataTable.js b/src/client/components/main_layout/KnowledgeGraphMetadataTable.js
index eb34d761446158b386f8a2f2039280c8b238dbe3..58822256e0b362db83119c1a6924a2cdb496c6e6 100644
--- a/src/client/components/main_layout/KnowledgeGraphMetadataTable.js
+++ b/src/client/components/main_layout/KnowledgeGraphMetadataTable.js
@@ -24,8 +24,11 @@ const KnowledgeGraphMetadataTable = props => {
 
   useEffect(() => {
     if (props.fetchKnowledgeGraphMetadata) {
-      const { resultClass } = props
-      props.fetchKnowledgeGraphMetadata({ resultClass })
+      const { perspectiveID, resultClass } = props
+      props.fetchKnowledgeGraphMetadata({
+        perspectiveID,
+        resultClass
+      })
     }
   }, [])
 
diff --git a/src/client/components/main_layout/MuiIcon.js b/src/client/components/main_layout/MuiIcon.js
index ea108b95244d03c6e09a6e162648deb7b911afbc..53c3bb867b3c0ac62f98bcf18da7146269f8bfd9 100644
--- a/src/client/components/main_layout/MuiIcon.js
+++ b/src/client/components/main_layout/MuiIcon.js
@@ -10,7 +10,9 @@ import {
   CloudDownload,
   BubbleChart,
   ShowChart,
-  FormatAlignJustify
+  FormatAlignJustify,
+  ClearAll,
+  OndemandVideo
 } from '@material-ui/icons'
 import has from 'lodash'
 
@@ -26,7 +28,9 @@ const MuiIcon = props => {
     CloudDownload: CloudDownload,
     BubbleChart: BubbleChart,
     ShowChart: ShowChart,
-    FormatAlignJustify: FormatAlignJustify
+    FormatAlignJustify: FormatAlignJustify,
+    ClearAll: ClearAll,
+    OndemandVideo: OndemandVideo
   }
   if (has(MuiIcons, props.iconName)) {
     const MuiIconComponent = MuiIcons[props.iconName]
diff --git a/src/client/components/main_layout/PerspectiveTabs.js b/src/client/components/main_layout/PerspectiveTabs.js
index be0357046560b8bdba9cfb3c3da095789aa3a38f..0773cd6409e7efe959ff721ca70c5880b269acee 100644
--- a/src/client/components/main_layout/PerspectiveTabs.js
+++ b/src/client/components/main_layout/PerspectiveTabs.js
@@ -1,6 +1,7 @@
 import React from 'react'
 import PropTypes from 'prop-types'
 import { withStyles } from '@material-ui/core/styles'
+import Grow from '@material-ui/core/Grow'
 import Tabs from '@material-ui/core/Tabs'
 import Tab from '@material-ui/core/Tab'
 import { Link } from 'react-router-dom'
@@ -38,6 +39,12 @@ class PerspectiveTabs extends React.Component {
     if (newPath !== oldPath) {
       this.setState({ value: this.pathnameToValue(newPath) })
     }
+
+    // Fix tabs indicator not showing on first load
+    // https://stackoverflow.com/a/61205108
+    const evt = document.createEvent('UIEvents')
+    evt.initUIEvent('resize', true, false, window, 0)
+    window.dispatchEvent(evt)
   }
 
   pathnameToValue = pathname => {
@@ -62,30 +69,34 @@ class PerspectiveTabs extends React.Component {
     const scrollButtons = tabs.length > 3 ? 'auto' : 'on'
     return (
       <Paper className={classes.root}>
-        <Tabs
-          value={this.state.value}
-          onChange={this.handleChange}
-          indicatorColor='secondary'
-          textColor='secondary'
-          variant={variant}
-          scrollButtons={scrollButtons}
-        >
-          {tabs.map(tab =>
-            <Tab
-              classes={{
-                root: classes.tabRoot,
-                labelIcon: classes.tabLabelIcon,
-                wrapper: classes.tabWrapper
-              }}
-              key={tab.id}
-              icon={tab.icon}
-              label={intl.get(`tabs.${tab.id}`)}
-              component={Link}
-              to={tab.id}
-              wrapped
-            />
-          )}
-        </Tabs>
+        <Grow in>
+          <Tabs
+            value={this.state.value}
+            onChange={this.handleChange}
+            indicatorColor='secondary'
+            textColor='secondary'
+            variant={variant}
+            scrollButtons={scrollButtons}
+          >
+            {tabs.map(tab =>
+              <Tab
+                classes={{
+                  root: classes.tabRoot,
+                  labelIcon: classes.tabLabelIcon,
+                  wrapper: classes.tabWrapper
+                }}
+                key={tab.id}
+                icon={tab.icon}
+                label={intl.get(`tabs.${tab.id}`)}
+                component={Link}
+                to={tab.id}
+                wrapped
+              />
+            )}
+          </Tabs>
+
+        </Grow>
+
       </Paper>
     )
   }
diff --git a/src/client/components/main_layout/TopBarInfoButton.js b/src/client/components/main_layout/TopBarInfoButton.js
index c7f21692272d816ce433d73a8703e092a740a260..dd34179ccdef26664fdd58b0db76dda03965446d 100644
--- a/src/client/components/main_layout/TopBarInfoButton.js
+++ b/src/client/components/main_layout/TopBarInfoButton.js
@@ -33,8 +33,41 @@ class TopBarInfoButton extends React.Component {
     this.setState({ anchorEl: null })
   };
 
-  render () {
+  renderInfoItem = item => {
     const { classes } = this.props
+    let jsx
+    if (item.externalLink) {
+      jsx = (
+        <a
+          className={classes.link}
+          key={item.id}
+          href={intl.get(`topBar.info.${item.translatedUrl}`)}
+          target='_blank'
+          rel='noopener noreferrer'
+        >
+          <MenuItem onClick={this.handleInfoMenuClose}>
+            {intl.get(`topBar.info.${item.translatedText}`)}
+          </MenuItem>
+        </a>
+      )
+    } else {
+      jsx = (
+        <MenuItem
+          key={item.id}
+          component={this.AdapterLink}
+          to={`${this.props.rootUrl}${item.internalLink}`}
+          onClick={this.handleInfoMenuClose}
+        >
+          {intl.get(`topBar.info.${item.translatedText}`)}
+        </MenuItem>
+      )
+    }
+    return jsx
+  }
+
+  render () {
+    const { classes, layoutConfig } = this.props
+    const { infoDropdown } = layoutConfig.topBar
     return (
       <>
         <Button
@@ -60,25 +93,7 @@ class TopBarInfoButton extends React.Component {
           open={Boolean(this.state.anchorEl)}
           onClose={this.handleInfoMenuClose}
         >
-          <MenuItem
-            key={0}
-            component={this.AdapterLink}
-            to={`${this.props.rootUrl}/about`}
-            onClick={this.handleInfoMenuClose}
-          >
-            {intl.get('topBar.info.aboutThePortal')}
-          </MenuItem>
-          <a
-            className={classes.link}
-            key={1}
-            href={intl.get('topBar.info.blogUrl')}
-            target='_blank'
-            rel='noopener noreferrer'
-          >
-            <MenuItem onClick={this.handleInfoMenuClose}>
-              {intl.get('topBar.info.blog')}
-            </MenuItem>
-          </a>
+          {infoDropdown.map(item => this.renderInfoItem(item))}
         </Menu>
       </>
     )
diff --git a/src/client/components/main_layout/VideoPage.js b/src/client/components/main_layout/VideoPage.js
new file mode 100644
index 0000000000000000000000000000000000000000..1c357cb2985e54a2ffc38dd768cca0c19195b461
--- /dev/null
+++ b/src/client/components/main_layout/VideoPage.js
@@ -0,0 +1,150 @@
+import React from 'react'
+import { makeStyles } from '@material-ui/core/styles'
+import Paper from '@material-ui/core/Paper'
+import Grid from '@material-ui/core/Grid'
+import { Typography } from '@material-ui/core'
+import InstancePageTable from './InstancePageTable'
+import Player from './Player'
+import VideoTableOfContents from './VideoTableOfContents'
+import { has } from 'lodash'
+
+const useStyles = makeStyles(theme => ({
+  root: {
+    width: '100%',
+    height: '100%',
+    display: 'flex',
+    justifyContent: 'center',
+    fontFamily: 'Roboto, Helvetica, Arial, sans-serif',
+    backgroundColor: '#bdbdbd'
+  },
+  mainContainer: props => ({
+    margin: 0,
+    maxWidth: 1100,
+    // minHeight: 1100,
+    marginTop: theme.spacing(1),
+    // flexWrap: 'wrap-reverse',
+    [theme.breakpoints.up(props.layoutConfig.hundredPercentHeightBreakPoint)]: {
+      height: `calc(100% - ${theme.spacing(2.5)}px)`
+    }
+  }),
+  gridItem: props => ({
+    [theme.breakpoints.up(props.layoutConfig.hundredPercentHeightBreakPoint)]: {
+      height: '100%'
+    },
+    paddingTop: '0px !important',
+    paddingBottom: '0px !important'
+  }),
+  tableOfContents: props => ({
+    padding: theme.spacing(2),
+    overflow: 'auto',
+    top: theme.spacing(0.5),
+    [theme.breakpoints.up(props.layoutConfig.hundredPercentHeightBreakPoint)]: {
+      height: 'calc(100% - 32px)'
+    }
+  }),
+  videoPlayerContainer: props => ({
+    [theme.breakpoints.up(props.layoutConfig.hundredPercentHeightBreakPoint)]: {
+      height: '60%'
+    },
+    [theme.breakpoints.down('sm')]: {
+      maxWidth: 500
+    },
+    overflow: 'auto',
+    marginBottom: theme.spacing(1),
+    display: 'flex',
+    alignItems: 'center'
+  }),
+  tableContainer: props => ({
+    marginBottom: theme.spacing(1),
+    [theme.breakpoints.up(props.layoutConfig.hundredPercentHeightBreakPoint)]: {
+      height: `calc(40% - ${theme.spacing(1)}px)`,
+      overflow: 'auto'
+    }
+  }),
+  wordCloud: props => ({
+    marginTop: theme.spacing(1),
+    padding: theme.spacing(2),
+    overflow: 'auto',
+    height: 200,
+    display: 'none',
+    [theme.breakpoints.up(props.layoutConfig.hundredPercentHeightBreakPoint)]: {
+      height: '40%',
+      display: 'block'
+    }
+  }),
+  wordCloudContainer: {
+    width: '100%'
+  },
+  tooltip: {
+    maxWidth: 500
+  },
+  tooltipContent: {
+    padding: theme.spacing(1)
+  },
+  tooltipList: {
+    listStylePosition: 'inside',
+    paddingLeft: 0
+  }
+}))
+
+const VideoPage = props => {
+  const classes = useStyles(props)
+  const { instanceTableData } = props.perspectiveState
+  const { portalConfig, perspectiveConfig, localID, resultClass, screenSize, layoutConfig } = props
+  let { properties } = props
+
+  const readyToRenderVideoPlayer = () => {
+    return `http://ldf.fi/warmemoirsampo/${localID}` === instanceTableData.id &&
+        has(instanceTableData, 'youTubeID')
+  }
+
+  if (!has(instanceTableData, 'warsaPage')) {
+    properties = properties.filter(prop => prop.id !== 'warsaPage')
+  }
+
+  return (
+    <div className={classes.root}>
+      <Grid className={classes.mainContainer} container spacing={1}>
+        <Grid className={classes.gridItem} item xs={12} sm={12} md={7}>
+          <Paper className={classes.videoPlayerContainer}>
+            {readyToRenderVideoPlayer() &&
+              <Player
+                resultClass={props.resultClass}
+                data={instanceTableData}
+                routeProps={props.routeProps}
+                videoPlayerState={props.videoPlayerState}
+                updateVideoPlayerTime={props.updateVideoPlayerTime}
+              />}
+          </Paper>
+          <Paper className={classes.tableContainer}>
+            <InstancePageTable
+              portalConfig={portalConfig}
+              perspectiveConfig={perspectiveConfig}
+              resultClass={resultClass}
+              data={instanceTableData}
+              properties={properties}
+              screenSize={screenSize}
+              layoutConfig={layoutConfig}
+            />
+          </Paper>
+        </Grid>
+        <Grid className={classes.gridItem} item xs={12} sm={12} md={5}>
+          <Paper className={classes.tableOfContents}>
+            <Typography variant='h6' component='h2'>Sisällysluettelo</Typography>
+            {has(instanceTableData, 'timeSlice') &&
+              <VideoTableOfContents
+                instanceTableData={instanceTableData}
+                toc={instanceTableData.timeSlice}
+                textFormat='plain-text-from-text-slice'
+                // textFormat='annotated-html-from-text-slice'
+                // textFormat='annotated-html-from-time-slice'
+                videoPlayerState={props.videoPlayerState}
+              />}
+          </Paper>
+        </Grid>
+      </Grid>
+    </div>
+  )
+}
+
+export default VideoPage
diff --git a/src/client/components/main_layout/VideoTableOfContents.js b/src/client/components/main_layout/VideoTableOfContents.js
new file mode 100644
index 0000000000000000000000000000000000000000..6e7d2dc7935970dced6ce29c2cb74fe06a083d74
--- /dev/null
+++ b/src/client/components/main_layout/VideoTableOfContents.js
@@ -0,0 +1,400 @@
+import React from 'react'
+import { withStyles } from '@material-ui/core/styles'
+import Accordion from '@material-ui/core/Accordion'
+import AccordionDetails from '@material-ui/core/AccordionDetails'
+import AccordionSummary from '@material-ui/core/AccordionSummary'
+import Typography from '@material-ui/core/Typography'
+import ExpandMoreIcon from '@material-ui/icons/ExpandMore'
+import Divider from '@material-ui/core/Divider'
+import Tooltip from '@material-ui/core/Tooltip'
+import { Link } from 'react-router-dom'
+import { has } from 'lodash'
+import parse from 'html-react-parser'
+import { arrayToObject } from '../../helpers/helpers'
+
+const styles = theme => ({
+  root: {
+    width: '100%',
+    marginTop: theme.spacing(1)
+  },
+  heading: {
+    fontSize: theme.typography.pxToRem(15),
+    flexBasis: '33.33%',
+    flexShrink: 0
+  },
+  secondaryHeadingContainer: {
+    display: 'flex',
+    alignItems: 'center'
+  },
+  secondaryHeading: {
+    fontSize: theme.typography.pxToRem(12),
+    color: theme.palette.text.secondary
+  },
+  timeLink: {
+    marginRight: theme.spacing(1)
+  },
+  activeAccordion: {
+    border: '2px solid red'
+  },
+  accordionDetailsRoot: {
+    flexDirection: 'column'
+  },
+  tocSubHeading: {
+    marginTop: theme.spacing(1)
+  },
+  tooltip: {
+    maxWidth: 500
+  },
+  tooltipContent: {
+    padding: theme.spacing(1)
+  }
+})
+
+class VideoTableOfContents extends React.Component {
+  constructor (props) {
+    super(props)
+    const {
+      mentionedPlace,
+      mentionedPerson,
+      mentionedOrganization,
+      mentionedUnit,
+      mentionedEvent,
+      mentionedProduct
+    } = props.instanceTableData
+    let namedEntities = [
+      ...(Array.isArray(mentionedPlace)
+        ? mentionedPlace
+        : [mentionedPlace]),
+      ...(Array.isArray(mentionedPerson)
+        ? mentionedPerson
+        : [mentionedPerson]),
+      ...(Array.isArray(mentionedOrganization)
+        ? mentionedOrganization
+        : [mentionedOrganization]),
+      ...(Array.isArray(mentionedUnit)
+        ? mentionedUnit
+        : [mentionedUnit]),
+      ...(Array.isArray(mentionedEvent)
+        ? mentionedEvent
+        : [mentionedEvent]),
+      ...(Array.isArray(mentionedProduct)
+        ? mentionedProduct
+        : [mentionedProduct])
+    ]
+    if (namedEntities !== null) {
+      namedEntities = arrayToObject({
+        array: namedEntities,
+        keyField: 'id'
+      })
+    }
+    this.state = {
+      expandedSet: new Set([]),
+      currentPart: null,
+      namedEntities
+    }
+  }
+
+  componentDidUpdate = (prevProps, prevState) => {
+    const currentPart = this.getCurrentPart()
+    if (this.props.videoPlayerState.videoPlayerTime !== prevProps.videoPlayerState.videoPlayerTime) {
+      this.setState({ currentPart })
+    }
+  }
+
+  getCurrentPart = () => {
+    const { videoPlayerTime } = this.props.videoPlayerState
+    let currentPart = null
+    let toc_ = this.props.toc
+    if (!Array.isArray(toc_)) {
+      toc_ = [this.props.toc]
+    }
+    for (const part of toc_) {
+      if (part.beginTimeInSeconds <= videoPlayerTime && part.endTimeInSeconds > videoPlayerTime) {
+        currentPart = part
+        break // there are errors in timecodes, choose only the first part that fits the condition
+      }
+    }
+    return currentPart
+  }
+
+  renderTooltip = (domNode, namedEntityID) => {
+    let tooltipContent = namedEntityID
+    if (has(this.state.namedEntities, namedEntityID)) {
+      const entity = this.state.namedEntities[namedEntityID]
+      const tooltipHeading = has(entity, 'wikipediaLink')
+        ? (
+          <p>
+            <a href={entity.wikipediaLink} target='_blank' rel='noopener noreferrer'>
+              {entity.prefLabel} (Wikipedia)
+            </a>
+          </p>
+          )
+        : (<p>{entity.prefLabel} (Wikipedia)</p>)
+      tooltipContent = (
+        <div className={this.props.classes.tooltipContent}>
+          {tooltipHeading}
+          <p>
+            {entity.description}
+          </p>
+        </div>
+      )
+    }
+    return (
+      <Tooltip
+        title={tooltipContent}
+        interactive
+        placement='top'
+        arrow
+        classes={{
+          tooltip: this.props.classes.tooltip
+        }}
+      >
+        <span
+          style={{
+            textDecoration: 'underline',
+            cursor: 'pointer'
+          }}
+        >
+          {domNode.children[0].data}
+        </span>
+      </Tooltip>
+    )
+  }
+
+  renderLink = (domNode, namedEntityID) => {
+    if (has(this.state.namedEntities, namedEntityID)) {
+      const entity = this.state.namedEntities[namedEntityID]
+      return (
+        <Link to={entity.dataProviderUrl}>
+          {domNode.children[0].data}
+        </Link>
+      )
+    } else {
+      return (
+        <>
+          {domNode.children[0].data}
+        </>
+      )
+    }
+  }
+
+  parseHTMLTextSlice = slice => {
+    const html = parse(slice.annotatedTextContent, {
+      replace: domNode => {
+        if (domNode.type === 'tag' && domNode.name === 'span' &&
+        has(domNode.attribs, 'data-link')) {
+          const namedEntityID = domNode.attribs['data-link']
+          return this.renderTooltip(domNode, namedEntityID)
+        }
+      }
+    })
+    return (
+      <li key={slice.order}>{html}</li>
+    )
+  }
+
+  parseHTMLTimeSlice = annotatedTextContent => {
+    const html = parse(annotatedTextContent, {
+      replace: domNode => {
+        if (domNode.type === 'tag' && domNode.name === 'span' &&
+        has(domNode.attribs, 'data-uri')) {
+          const namedEntityID = domNode.attribs['data-uri']
+          return this.renderLink(domNode, namedEntityID)
+        }
+      }
+    })
+    return (
+      <p>
+        {html}
+      </p>
+    )
+  }
+
+  handleAccordionOnChange = rowID => () => {
+    const { expandedSet } = this.state
+    if (expandedSet.has(rowID)) {
+      expandedSet.delete(rowID)
+    } else {
+      expandedSet.add(rowID)
+    }
+    this.setState({ expandedSet })
+  }
+
+  render () {
+    const { classes, toc, textFormat } = this.props
+    const { expandedSet } = this.state
+    let toc_ = toc
+    if (!Array.isArray(toc)) {
+      toc_ = [toc]
+    }
+    return (
+      <div className={classes.root}>
+        {toc_.map(row => {
+          const rowID = row.order
+          let isCurrent = false
+          if (this.state.currentPart && rowID === this.state.currentPart.order) {
+            isCurrent = true
+          }
+          const expanded = expandedSet.has(rowID) || isCurrent
+          const hasPlaceLinks = has(row, 'mentionedPlace')
+          const hasPersonLinks = has(row, 'mentionedPerson')
+          const hasUnitLinks = has(row, 'mentionedUnit')
+          const hasOrganizationLinks = has(row, 'mentionedOrganization')
+          const hasEventLinks = has(row, 'mentionedEvent')
+          const hasProductLinks = has(row, 'mentionedProduct')
+          const hasNamedEntityLinks =
+            hasPlaceLinks ||
+            hasPersonLinks ||
+            hasUnitLinks ||
+            hasOrganizationLinks ||
+            hasEventLinks ||
+            hasProductLinks
+          const hasTextSlices = has(row, 'textSlice')
+          if (hasPlaceLinks) {
+            if (Array.isArray(row.mentionedPlace)) {
+              row.mentionedPlace.forEach(place => {
+                if (Array.isArray(place.prefLabel)) {
+                  place.prefLabel = place.prefLabel[0]
+                }
+              })
+              row.mentionedPlace.sort((a, b) => a.prefLabel.localeCompare(b.prefLabel))
+            }
+          }
+          const timeSliceHasAnnotatedTextContent = has(row, 'annotatedTextContent')
+          return (
+            <Accordion
+              className={isCurrent ? classes.activeAccordion : null}
+              key={rowID}
+              expanded={expanded}
+              onChange={this.handleAccordionOnChange(rowID)}
+            >
+              <AccordionSummary
+                style={{
+                  root: {
+                    '&$expanded': { minHeight: 15 }
+                  },
+                  content: {
+                    '&$expanded': { marginBottom: 0 }
+                  }
+                }}
+                expandIcon={<ExpandMoreIcon />}
+                IconButtonProps={{
+                  disabled: isCurrent
+                }}
+                aria-label='Expand'
+                aria-controls={`${rowID}-content`}
+                id={`${rowID}-header`}
+              >
+                <Link
+                  className={classes.timeLink}
+                  to={{ hash: row.beginTimeInSeconds }}
+                  replace
+                  onClick={event => {
+                    if (expanded) {
+                      event.stopPropagation()
+                    }
+                  }}
+                  onFocus={event => event.stopPropagation()}
+                >
+                  <Typography className={classes.heading}>
+                    {row.beginTimeLabel}
+                  </Typography>
+                </Link>
+                {!expanded &&
+                  <div className={classes.secondaryHeadingContainer}>
+                    <Typography className={classes.secondaryHeading}>{row.prefLabel}</Typography>
+                  </div>}
+
+              </AccordionSummary>
+              <AccordionDetails
+                classes={{
+                  root: classes.accordionDetailsRoot
+                }}
+              >
+                <Typography>Haastattelijan muistiinpanot</Typography>
+                {textFormat === 'plain-text-from-text-slice' && hasTextSlices &&
+                  <ul>
+                    {Array.isArray(row.textSlice)
+                      ? row.textSlice.map(slice => <li key={slice.order}>{slice.textContent}</li>)
+                      : <li key={row.textSlice.order}>{row.textSlice.textContent}</li>}
+                  </ul>}
+                {textFormat === 'annotated-html-from-text-slice' && hasTextSlices &&
+                  <ul>
+                    {Array.isArray(row.textSlice)
+                      ? row.textSlice.map(slice => this.parseHTMLTextSlice(slice))
+                      : this.parseHTMLTextSlice(row.textSlice)}
+                  </ul>}
+                {textFormat === 'annotated-html-from-time-slice' && timeSliceHasAnnotatedTextContent &&
+                  this.parseHTMLTimeSlice(row.annotatedTextContent)}
+                {hasNamedEntityLinks &&
+                  <>
+                    <Divider />
+                    <Typography className={classes.tocSubHeading}>Automaattisesti tunnistetut</Typography>
+                    <ul>
+                      {hasPlaceLinks &&
+                        <li>paikat
+                          <ul>
+                            {Array.isArray(row.mentionedPlace)
+                              ? row.mentionedPlace.map(place =>
+                                <li key={place.id}><Link to={place.dataProviderUrl}>{place.prefLabel}</Link></li>)
+                              : <li key={row.mentionedPlace.id}><Link to={row.mentionedPlace.dataProviderUrl}>{row.mentionedPlace.prefLabel}</Link></li>}
+                          </ul>
+                        </li>}
+                      {hasPersonLinks &&
+                        <li>henkilöt
+                          <ul>
+                            {Array.isArray(row.mentionedPerson)
+                              ? row.mentionedPerson.map(person =>
+                                <li key={person.id}><Link to={person.dataProviderUrl}>{person.prefLabel}</Link></li>)
+                              : <li key={row.mentionedPerson.id}><Link to={row.mentionedPerson.dataProviderUrl}>{row.mentionedPerson.prefLabel}</Link></li>}
+                          </ul>
+                        </li>}
+                      {hasUnitLinks &&
+                        <li>joukko-osastot
+                          <ul>
+                            {Array.isArray(row.mentionedUnit)
+                              ? row.mentionedUnit.map(unit =>
+                                <li key={unit.id}><Link to={unit.dataProviderUrl}>{unit.prefLabel}</Link></li>)
+                              : <li key={row.mentionedUnit.id}><Link to={row.mentionedUnit.dataProviderUrl}>{row.mentionedUnit.prefLabel}</Link></li>}
+                          </ul>
+                        </li>}
+                      {hasOrganizationLinks &&
+                        <li>organisaatiot
+                          <ul>
+                            {Array.isArray(row.mentionedOrganization)
+                              ? row.mentionedOrganization.map(organization =>
+                                <li key={organization.id}><Link to={organization.dataProviderUrl}>{organization.prefLabel}</Link></li>)
+                              : <li key={row.mentionedOrganization.id}><Link to={row.mentionedOrganization.dataProviderUrl}>{row.mentionedOrganization.prefLabel}</Link></li>}
+                          </ul>
+                        </li>}
+                      {hasEventLinks &&
+                        <li>tapahtumat
+                          <ul>
+                            {Array.isArray(row.mentionedEvent)
+                              ? row.mentionedEvent.map(event =>
+                                <li key={event.id}><Link to={event.dataProviderUrl}>{event.prefLabel}</Link></li>)
+                              : <li key={row.mentionedEvent.id}><Link to={row.mentionedEvent.dataProviderUrl}>{row.mentionedEvent.prefLabel}</Link></li>}
+                          </ul>
+                        </li>}
+                      {hasProductLinks &&
+                        <li>nimikkeet
+                          <ul>
+                            {Array.isArray(row.mentionedProduct)
+                              ? row.mentionedProduct.map(product =>
+                                <li key={product.id}><Link to={product.dataProviderUrl}>{product.prefLabel}</Link></li>)
+                              : <li key={row.mentionedProduct.id}><Link to={row.mentionedProduct.dataProviderUrl}>{row.mentionedProduct.prefLabel}</Link></li>}
+                          </ul>
+                        </li>}
+                    </ul>
+                  </>}
+              </AccordionDetails>
+            </Accordion>
+          )
+        }
+        )}
+      </div>
+    )
+  }
+}
+
+export default withStyles(styles)(VideoTableOfContents)
diff --git a/src/client/components/main_layout/WordCloud.js b/src/client/components/main_layout/WordCloud.js
new file mode 100644
index 0000000000000000000000000000000000000000..284335c82a8146060b2861f440d3ca89b893c36c
--- /dev/null
+++ b/src/client/components/main_layout/WordCloud.js
@@ -0,0 +1,67 @@
+import React from 'react'
+import ReactWordcloud from 'react-wordcloud'
+import { makeStyles } from '@material-ui/core/styles'
+import Paper from '@material-ui/core/Paper'
+import 'tippy.js/dist/tippy.css'
+import 'tippy.js/animations/scale.css'
+
+const options = {
+  rotations: 0,
+  fontSizes: [14, 60],
+  deterministic: true
+}
+
+const useStyles = makeStyles(theme => ({
+  wordCloudOuterContainer: props => ({
+    height: '100%',
+    display: 'flex',
+    alignItems: 'center',
+    justifyContent: 'center',
+    backgroundColor: '#bdbdbd'
+  }),
+  wordCloudInnerContainer: props => ({
+    width: '100%',
+    height: '100%',
+    [theme.breakpoints.down('md')]: {
+      minHeight: 400,
+      overflow: 'auto'
+    },
+    [theme.breakpoints.up('lg')]: {
+      width: '50%',
+      height: '50%'
+    }
+  })
+}))
+
+const Wordcloud = props => {
+  const { data, maxWords } = props
+  const classes = useStyles(props)
+
+  if (data == null) {
+    return (<></>)
+  }
+
+  data.forEach(el => {
+    el.weight = +el.weight
+  })
+
+  // sort without mutating the original array
+  let words = [...data].sort((a, b) => b.weight - a.weight)
+  if (words.length > maxWords) {
+    words.splice(maxWords)
+  }
+  words = words.map(item => ({ text: item.prefLabel, value: item.weight }))
+
+  return (
+    <div className={classes.wordCloudOuterContainer}>
+      <Paper className={classes.wordCloudInnerContainer}>
+        <ReactWordcloud
+          options={options}
+          words={words}
+        />
+      </Paper>
+    </div>
+  )
+}
+
+export default Wordcloud
diff --git a/src/client/containers/SemanticPortal.js b/src/client/containers/SemanticPortal.js
index 388278969de0452ae278bed7cf7b0f42506f7d71..2f1095bae23125278f5b0242454b8ef75ff83935 100644
--- a/src/client/containers/SemanticPortal.js
+++ b/src/client/containers/SemanticPortal.js
@@ -38,6 +38,7 @@ import {
   updatePerspectiveHeaderExpanded,
   loadLocales,
   animateMap,
+  updateVideoPlayerTime,
   clientFSToggleDataset,
   clientFSFetchResults,
   clientFSSortResults,
@@ -86,13 +87,13 @@ const FullTextSearch = lazy(() => import('../components/main_layout/FullTextSear
 const FacetBar = lazy(() => import('../components/facet_bar/FacetBar'))
 const FacetResults = lazy(() => import('../components/facet_results/FacetResults'))
 const FederatedResults = lazy(() => import('../components/facet_results/FederatedResults'))
+const KnowledgeGraphMetadataTable = lazy(() => import('../components/main_layout/KnowledgeGraphMetadataTable'))
 // ** General components end **
 
 // ** Import portal specific components **
 const Main = lazy(() => import(`../components/perspectives/${portalID}/Main`))
 const MainClientFS = lazy(() => import(`../components/perspectives/${portalID}/MainClientFS`))
 const Footer = lazy(() => import(`../components/perspectives/${portalID}/Footer`))
-const KnowledgeGraphMetadataTable = lazy(() => import(`../components/perspectives/${portalID}/KnowledgeGraphMetadataTable`))
 // ** Portal specific components end **
 
 const useStyles = makeStyles(theme => ({
@@ -472,6 +473,8 @@ const SemanticPortal = props => {
                                   perspective={perspective}
                                   animationValue={props.animationValue}
                                   animateMap={props.animateMap}
+                                  videoPlayerState={props.videoPlayer}
+                                  updateVideoPlayerTime={props.updateVideoPlayerTime}
                                   screenSize={screenSize}
                                   rootUrl={rootUrlWithLang}
                                   apexChartsConfig={apexChartsConfig}
@@ -542,6 +545,8 @@ const SemanticPortal = props => {
                             perspective={perspective}
                             animationValue={props.animationValue}
                             animateMap={props.animateMap}
+                            videoPlayerState={props.videoPlayer}
+                            updateVideoPlayerTime={props.updateVideoPlayerTime}
                             screenSize={screenSize}
                             rootUrl={rootUrlWithLang}
                             apexChartsConfig={apexChartsConfig}
@@ -631,6 +636,7 @@ const SemanticPortal = props => {
                   <KnowledgeGraphMetadataTable
                     portalConfig={portalConfig}
                     layoutConfig={layoutConfig}
+                    perspectiveID='perspective1'
                     resultClass='perspective1KnowledgeGraphMetadata'
                     fetchKnowledgeGraphMetadata={props.fetchKnowledgeGraphMetadata}
                     knowledgeGraphMetadata={props.perspective1.knowledgeGraphMetadata}
@@ -676,6 +682,7 @@ const mapStateToProps = state => {
   stateToProps.leafletMap = state.leafletMap
   stateToProps.fullTextSearch = state.fullTextSearch
   stateToProps.animationValue = state.animation.value
+  stateToProps.videoPlayer = state.videoPlayer
   stateToProps.options = state.options
   stateToProps.error = state.error
   return stateToProps
@@ -706,6 +713,7 @@ const mapDispatchToProps = ({
   updatePerspectiveHeaderExpanded,
   loadLocales,
   animateMap,
+  updateVideoPlayerTime,
   clientFSToggleDataset,
   clientFSFetchResults,
   clientFSClearResults,
diff --git a/src/client/epics/index.js b/src/client/epics/index.js
index c260227e00d19dce2084c8d48a58d751f0b961d1..d45b897a7c90e643f2fdb22608d5e5001ecc13c0 100644
--- a/src/client/epics/index.js
+++ b/src/client/epics/index.js
@@ -531,7 +531,7 @@ const fetchKnowledgeGraphMetadataEpic = (action$, state$) => action$.pipe(
   ofType(FETCH_KNOWLEDGE_GRAPH_METADATA),
   withLatestFrom(state$),
   mergeMap(([action]) => {
-    const requestUrl = `${apiUrl}/void/${action.resultClass}`
+    const requestUrl = `${apiUrl}/void/${action.perspectiveID}/${action.resultClass}`
     return ajax({
       url: requestUrl,
       method: 'GET'
diff --git a/src/client/helpers/helpers.js b/src/client/helpers/helpers.js
index 338f29b26745ade2030dde1ee57035cdf6395891..1e14944d6da2f68c05c479f8b83bce0a28e0183c 100644
--- a/src/client/helpers/helpers.js
+++ b/src/client/helpers/helpers.js
@@ -19,7 +19,9 @@ export const stateToUrl = ({
   limit = null,
   optimize = null,
   fromID = null,
-  toID = null
+  toID = null,
+  period = null,
+  province = null
 }) => {
   const params = {}
   if (perspectiveID !== null) { params.perspectiveID = perspectiveID }
@@ -36,6 +38,8 @@ export const stateToUrl = ({
   if (optimize !== null) { params.optimize = optimize }
   if (fromID !== null) { params.fromID = fromID }
   if (toID !== null) { params.toID = toID }
+  if (period !== null) { params.period = period }
+  if (province !== null) { params.province = province }
   if (facets !== null) {
     const constraints = []
     for (const [key, value] of Object.entries(facets)) {
@@ -195,6 +199,10 @@ export const processPortalConfig = async portalConfig => {
     mapboxConfig.mapboxAccessToken = mapboxAccessToken
   }
   layoutConfig.mainPage.bannerBackround = bannerBackround.replace('<BANNER_IMAGE_URL', bannerImageURL)
+  if (layoutConfig.topBar.logoImage) {
+    const { default: image } = await import(/* webpackMode: "eager" */ `../img/${layoutConfig.topBar.logoImage}`)
+    layoutConfig.topBar.logoImage = image
+  }
 }
 
 export const createPerspectiveConfig = async ({ portalID, searchPerspectives }) => {
diff --git a/src/client/reducers/general/videoPlayer.js b/src/client/reducers/general/videoPlayer.js
new file mode 100644
index 0000000000000000000000000000000000000000..9233d9e7131e2643d59d862653ec19ad42b33ce1
--- /dev/null
+++ b/src/client/reducers/general/videoPlayer.js
@@ -0,0 +1,14 @@
+import { UPDATE_VIDEO_PLAYER_TIME } from '../../actions'
+
+export const INITIAL_STATE = {
+  videoPlayerTime: null
+}
+
+const videoPlayer = (state = INITIAL_STATE, action) => {
+  if (action.type === UPDATE_VIDEO_PLAYER_TIME) {
+    state = { ...state, videoPlayerTime: action.value }
+  }
+  return state
+}
+
+export default videoPlayer
diff --git a/src/client/translations/sampo/localeEN.json b/src/client/translations/sampo/localeEN.json
index f06473d21aee468f97618bccb77a5b4e1d9b6407..bc33f3cfac31ed3997f710297280b0460fbd3aac 100644
--- a/src/client/translations/sampo/localeEN.json
+++ b/src/client/translations/sampo/localeEN.json
@@ -23,7 +23,7 @@
     "info": {
       "info": "Info",
       "blog": "Project blog",
-      "blogUrl": "https://seco.cs.aalto.fi",
+      "blogUrl": "https://seco.cs.aalto.fi/tools/sampo-ui/",
       "aboutThePortal": "About the Portal"
     },
     "searchBarPlaceHolder": "Search all content",
diff --git a/src/configs/portalConfig.json b/src/configs/portalConfig.json
index b65a32f2cd412d39f652f3d1dded5f2b150dad17..30b6dc74227a9365457e68ec4def59531dcf6a43 100644
--- a/src/configs/portalConfig.json
+++ b/src/configs/portalConfig.json
@@ -44,6 +44,14 @@
         "sitemapInstancePageQuery": "sitemapInstancePageQuery"
     },
     "layoutConfig": {
+        "colorPalette": {
+            "primary": {
+                "main": "#212121"
+            },
+            "secondary": {
+                "main": "#EB1806"
+            }
+        },
         "hundredPercentHeightBreakPoint": "md",
         "reducedHeightBreakpoint": "xl",
         "tabHeight": 58,
@@ -52,9 +60,22 @@
         "topBar": {
             "showLanguageButton": true,
             "feedbackLink": "https://link.webropolsurveys.com/...",
-            "reducedHeight": 48,
+            "reducedHeight": 44,
             "defaultHeight": 64,
-            "mobileMenuBreakpoint": 1360
+            "mobileMenuBreakpoint": 1360,
+            "infoDropdown": [
+                {
+                    "id": "about",
+                    "translatedText": "aboutThePortal",
+                    "internalLink": "/about"
+                },
+                {
+                    "id": "blog",
+                    "externalLink": true,
+                    "translatedUrl": "blogUrl",
+                    "translatedText": "blog"
+                }
+            ]
         },
         "mainPage": {
             "bannerImage": "main_page/mmm-banner.jpg",
diff --git a/src/configs/sampo/search_perspectives/perspective1.json b/src/configs/sampo/search_perspectives/perspective1.json
index 3b01432782450fee1c62b9cc0daad7d2fa9b5b2c..53152059e441fcc1e7234ea2633f7c523e29dc15 100644
--- a/src/configs/sampo/search_perspectives/perspective1.json
+++ b/src/configs/sampo/search_perspectives/perspective1.json
@@ -52,8 +52,12 @@
             "component": "Export",
             "tabPath": "export",
             "tabIcon": "CloudDownload",
-            "resultClass": "perspective2",
-            "facetClass": "perspective2"
+            "resultClass": "perspective1",
+            "facetClass": "perspective1"
+        },
+        "perspective1KnowledgeGraphMetadata": {
+            "sparqlQuery": "knowledgeGraphMetadataQuery",
+            "resultMapper": "makeObjectList"
         }
     },
     "properties": [
diff --git a/src/configs/sampo/search_perspectives/perspective2.json b/src/configs/sampo/search_perspectives/perspective2.json
index 5a94f67aa9881e2d993f8188b19c5a3e5e543c65..72c1ac89f9ff9d2f570ce8bf0bf9c73b4a25c1d0 100644
--- a/src/configs/sampo/search_perspectives/perspective2.json
+++ b/src/configs/sampo/search_perspectives/perspective2.json
@@ -68,7 +68,7 @@
             "tabPath": "production_places",
             "tabIcon": "AddLocation",
             "sparqlQuery": "productionPlacesQuery",
-            "facetClass": "perspective1",
+            "facetClass": "perspective2",
             "filterTarget": "manuscripts",
             "resultMapper": "mapPlaces",
             "facetID": "productionPlace",
@@ -85,7 +85,7 @@
             "tabPath": "production_places_heatmap",
             "tabIcon": "AddLocation",
             "sparqlQuery": "productionPlacesQuery",
-            "facetClass": "perspective1",
+            "facetClass": "perspective2",
             "filterTarget": "manuscripts",
             "resultMapper": "mapPlaces",
             "layerType": "heatmapLayer"
@@ -96,7 +96,7 @@
             "tabPath": "last_known_locations",
             "tabIcon": "LocationOn",
             "sparqlQuery": "lastKnownLocationsQuery",
-            "facetClass": "perspective1",
+            "facetClass": "perspective2",
             "filterTarget": "manuscripts",
             "resultMapper": "mapPlaces",
             "facetID": "lastKnownLocation",
@@ -111,7 +111,7 @@
             "tabPath": "migrations",
             "tabIcon": "Redo",
             "sparqlQuery": "migrationsQuery",
-            "facetClass": "perspective1",
+            "facetClass": "perspective2",
             "filterTarget": "manuscript",
             "resultMapper": "makeObjectList",
             "layerType": "arcLayer",
@@ -138,7 +138,7 @@
             "tabPath": "production_dates",
             "tabIcon": "ShowChart",
             "sparqlQuery": "productionsByDecadeQuery",
-            "facetClass": "perspective1",
+            "facetClass": "perspective2",
             "filterTarget": "instance",
             "resultMapper": "mapLineChart",
             "resultMapperConfig": {
@@ -157,7 +157,7 @@
         },
         "productionsByDecadeAndCountry": {
             "sparqlQuery": "productionsByDecadeAndCountryQuery",
-            "facetClass": "perspective1",
+            "facetClass": "perspective2",
             "filterTarget": "manuscript",
             "resultMapper": "makeObjectList",
             "postprocess": {
@@ -173,7 +173,7 @@
             "tabPath": "event_dates",
             "tabIcon": "AddLocation",
             "sparqlQuery": "eventsByDecadeQuery",
-            "facetClass": "perspective1",
+            "facetClass": "perspective2",
             "filterTarget": "manuscript",
             "resultMapper": "mapMultipleLineChart",
             "resultMapperConfig": {
@@ -212,7 +212,7 @@
             "tabPath": "network",
             "tabIcon": "BubbleChart",
             "sparqlQuery": "manuscriptFacetResultsNetworkLinksQuery",
-            "facetClass": "perspective1",
+            "facetClass": "perspective2",
             "sparqlQueryNodes": "manuscriptNetworkNodesQuery",
             "filterTarget": "manuscript",
             "useNetworkAPI": true,
@@ -227,12 +227,8 @@
             "component": "Export",
             "tabPath": "export",
             "tabIcon": "CloudDownload",
-            "resultClass": "perspective1",
-            "facetClass": "perspective1"
-        },
-        "perspective1KnowledgeGraphMetadata": {
-            "sparqlQuery": "knowledgeGraphMetadataQuery",
-            "resultMapper": "makeObjectList"
+            "resultClass": "perspective2",
+            "facetClass": "perspective2"
         },
         "jenaText": {
             "propertiesQueryBlock": "fullTextSearchProperties"
diff --git a/src/server/index.js b/src/server/index.js
index 1f0f980064e3fd1c5e5e6dc548290863ba5859a4..7d52b9bab025f7947a831517cd692f8faf52733f 100644
--- a/src/server/index.js
+++ b/src/server/index.js
@@ -366,12 +366,13 @@ app.get(`${apiPath}/wfs`, async (req, res, next) => {
   }
 })
 
-app.get(`${apiPath}/void/:resultClass`, async (req, res, next) => {
+app.get(`${apiPath}/void/:perspectiveID/:resultClass`, async (req, res, next) => {
   const { params } = req
   try {
     const backendSearchConfig = await createBackendSearchConfig()
     const data = await getAllResults({
       backendSearchConfig,
+      perspectiveID: params.perspectiveID,
       resultClass: params.resultClass,
       resultFormat: 'json'
     })
diff --git a/src/server/openapi.yaml b/src/server/openapi.yaml
index 01f8cf31e5e06cf7d7f5eda8c36ebd1e0b430265..9579a7b8e8346e58a86ab296a0c667934dd2ece0 100644
--- a/src/server/openapi.yaml
+++ b/src/server/openapi.yaml
@@ -556,10 +556,16 @@ paths:
             application/json:
               schema:
                 type: object
-  /void/{resultClass}:
+  /void/{perspectiveID}/{resultClass}:
     get:
       summary: Retrieve a VoID description
       parameters:
+        - in: path
+          name: perspectiveID
+          schema: 
+            type: string
+            example: perspective1
+          required: true
         - in: path
           name: resultClass
           schema: 
diff --git a/src/server/sparql/Filters.js b/src/server/sparql/Filters.js
index b0adc34e536efe9c7d6ce1eefac728d649290529..56e2cf661c19a601530066468d646c3a42c82c7b 100644
--- a/src/server/sparql/Filters.js
+++ b/src/server/sparql/Filters.js
@@ -95,11 +95,17 @@ const generateTextFilter = ({
 }) => {
   const facetConfig = backendSearchConfig[facetClass].facets[facetID]
   let filterStr = ''
+  let queryObject
+  if (facetConfig.textQueryProperty) {
+    queryObject = `(${facetConfig.textQueryProperty} '${queryString}')`
+  } else {
+    queryObject = `'${queryString}'`
+  }
   if (!has(facetConfig, 'textQueryPredicate')) {
-    filterStr = `?${filterTarget} text:query (${facetConfig.textQueryProperty} '${queryString}') .`
+    filterStr = `?${filterTarget} text:query ${queryObject} .`
   } else {
     filterStr = `
-      ?textQueryTarget text:query (${facetConfig.textQueryProperty} '${queryString}') .
+      ?textQueryTarget text:query ${queryObject} .
       ?${filterTarget} ${facetConfig.textQueryPredicate} ?textQueryTarget .
 
     `
diff --git a/src/server/sparql/Mappers.js b/src/server/sparql/Mappers.js
index a927f3ebd923c943a20fa3205378190ff19e5765..04dc9b6ea851f2843b2ddb166c2686443d735ad0 100644
--- a/src/server/sparql/Mappers.js
+++ b/src/server/sparql/Mappers.js
@@ -358,6 +358,29 @@ const getChoroplethMapColor = ({ value, clusters }) => {
   return heatmapColor
 }
 
+export const createPaddedTimeCodes = ({ data, config }) => {
+  data.forEach(item => {
+    let target = item[config.target]
+    if (!Array.isArray(target)) {
+      target = [target]
+    }
+    target.forEach(targetItem => {
+      const { hours, minutes, seconds } = targetItem
+      if (hours == null || minutes == null || seconds == null) {
+        // console.log(targetItem)
+      } else {
+        const paddedTimecode = createPaddedTimeCode({ hours, minutes, seconds })
+        targetItem[config.timeCodeProperty] = paddedTimecode
+      }
+    })
+  })
+  return data
+}
+
+const createPaddedTimeCode = ({ hours, minutes, seconds }) => {
+  return `${hours}:${minutes.padStart(2, '0')}:${seconds.padStart(2, '0')}`
+}
+
 // const NS_PER_SEC = 1e9
 // const MS_PER_NS = 1e-6
 
diff --git a/src/server/sparql/sampo/sparql_queries/SparqlQueriesPerspective1.js b/src/server/sparql/sampo/sparql_queries/SparqlQueriesPerspective1.js
index ead7630663faee7b8ab1bde5159ae96c5cb495f3..19e11062a735c743c558769e84d51f28f8857236 100644
--- a/src/server/sparql/sampo/sparql_queries/SparqlQueriesPerspective1.js
+++ b/src/server/sparql/sampo/sparql_queries/SparqlQueriesPerspective1.js
@@ -1,4 +1,4 @@
-const perspectiveID = 'perspective2'
+const perspectiveID = 'perspective1'
 
 export const workProperties = `
     {
@@ -59,3 +59,18 @@ export const workProperties = `
       OPTIONAL { ?productionTimespan__id crm:P82b_end_of_the_end ?productionTimespan__end }
     }
 `
+
+export const knowledgeGraphMetadataQuery = `
+  SELECT * 
+  WHERE {
+    ?id a sd:Dataset ;
+        dct:title ?title ;
+        dct:publisher ?publisher ;
+        dct:rightsHolder ?rightsHolder ;
+        dct:modified ?modified ;
+        dct:source ?databaseDump__id .
+    ?databaseDump__id skos:prefLabel ?databaseDump__prefLabel ;
+                      mmm-schema:data_provider_url ?databaseDump__dataProviderUrl ;
+                      dct:modified ?databaseDump__modified .
+  }
+`
diff --git a/src/server/sparql/sampo/sparql_queries/SparqlQueriesPerspective2.js b/src/server/sparql/sampo/sparql_queries/SparqlQueriesPerspective2.js
index 97dd7838f30c2d01dca9f17eae1997239f94629c..56ed0c10cc9c188663509f315e54344682e662ba 100644
--- a/src/server/sparql/sampo/sparql_queries/SparqlQueriesPerspective2.js
+++ b/src/server/sparql/sampo/sparql_queries/SparqlQueriesPerspective2.js
@@ -1,4 +1,4 @@
-const perspectiveID = 'perspective1'
+const perspectiveID = 'perspective2'
 
 export const manuscriptPropertiesInstancePage =
 `   {
@@ -582,21 +582,6 @@ export const eventsByDecadeQuery = `
   ORDER BY ?category
 `
 
-export const knowledgeGraphMetadataQuery = `
-  SELECT * 
-  WHERE {
-    ?id a sd:Dataset ;
-        dct:title ?title ;
-        dct:publisher ?publisher ;
-        dct:rightsHolder ?rightsHolder ;
-        dct:modified ?modified ;
-        dct:source ?databaseDump__id .
-    ?databaseDump__id skos:prefLabel ?databaseDump__prefLabel ;
-                      mmm-schema:data_provider_url ?databaseDump__dataProviderUrl ;
-                      dct:modified ?databaseDump__modified .
-  }
-`
-
 export const choroplethQuery = `
   SELECT ?id ?prefLabel ?polygon (count(?death) as ?instanceCount) 
   WHERE {