summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJules Laplace <julescarbon@gmail.com>2020-06-30 20:11:14 +0200
committerJules Laplace <julescarbon@gmail.com>2020-06-30 20:11:14 +0200
commit250527589e003420a84f36c4191d2e574f1ad28c (patch)
treec8ae5ae829c8de1427c2cbb83178cb6c85f71eb7
parent7bc1723499503800cbdd446b27e202898fc32b9e (diff)
seeking and zooming the waveform. playing the audio
-rw-r--r--animism-align/frontend/actions.js2
-rw-r--r--animism-align/frontend/store.js2
-rw-r--r--animism-align/frontend/types.js4
-rw-r--r--animism-align/frontend/views/align/align.actions.js7
-rw-r--r--animism-align/frontend/views/align/align.css24
-rw-r--r--animism-align/frontend/views/align/align.reducer.js19
-rw-r--r--animism-align/frontend/views/align/components/cursor.component.js2
-rw-r--r--animism-align/frontend/views/align/components/playButton.component.js31
-rw-r--r--animism-align/frontend/views/align/components/playCursor.component.js43
-rw-r--r--animism-align/frontend/views/align/components/timeline.component.js83
-rw-r--r--animism-align/frontend/views/align/components/waveform.component.js2
-rw-r--r--animism-align/frontend/views/audio/audio.actions.js40
-rw-r--r--animism-align/frontend/views/audio/audio.reducer.js30
-rw-r--r--animism-align/static/img/elements/close.svg15
-rw-r--r--animism-align/static/img/elements/e-flux-logo.svg42
-rwxr-xr-xanimism-align/static/img/elements/icons_arrow-down.svg6
-rwxr-xr-xanimism-align/static/img/elements/icons_arrow-left.svg6
-rwxr-xr-xanimism-align/static/img/elements/icons_arrow-right.svg6
-rwxr-xr-xanimism-align/static/img/elements/icons_arrow-up.svg6
-rwxr-xr-xanimism-align/static/img/elements/icons_audio.svg7
-rwxr-xr-xanimism-align/static/img/elements/icons_pause.svg6
-rwxr-xr-xanimism-align/static/img/elements/icons_play.svg6
-rwxr-xr-xanimism-align/static/img/elements/icons_volume.svg7
-rwxr-xr-xanimism-align/static/img/elements/icons_zoom-plus.svg8
-rwxr-xr-xanimism-align/static/img/elements/icons_zoom-x.svg10
-rw-r--r--animism-align/static/img/elements/menu.svg19
-rw-r--r--animism-align/static/img/elements/search.svg17
-rwxr-xr-xanimism-align/static/img/icons_pause_white.svg6
-rwxr-xr-xanimism-align/static/img/icons_play_white.svg6
29 files changed, 424 insertions, 38 deletions
diff --git a/animism-align/frontend/actions.js b/animism-align/frontend/actions.js
index 6ce2d02..1b5f15e 100644
--- a/animism-align/frontend/actions.js
+++ b/animism-align/frontend/actions.js
@@ -1,6 +1,7 @@
import { bindActionCreators } from 'redux'
import { actions as crudActions } from './api'
+import * as audioActions from './views/audio/audio.actions'
import * as alignActions from './views/align/align.actions'
import * as siteActions from './views/site/site.actions'
@@ -12,6 +13,7 @@ export default
.concat([
['site', siteActions],
['align', alignActions],
+ ['audio', audioActions],
])
.map(p => [p[0], bindActionCreators(p[1], store.dispatch)])
.concat([
diff --git a/animism-align/frontend/store.js b/animism-align/frontend/store.js
index 871d72c..accf1b1 100644
--- a/animism-align/frontend/store.js
+++ b/animism-align/frontend/store.js
@@ -7,6 +7,7 @@ import thunk from 'redux-thunk'
import uploadReducer from './views/upload/upload.reducer'
import alignReducer from './views/align/align.reducer'
+import audioReducer from './views/audio/audio.reducer'
import paragraphReducer from './views/paragraph/paragraph.reducer'
import timestampReducer from './views/timestamp/timestamp.reducer'
import siteReducer from './views/site/site.reducer'
@@ -19,6 +20,7 @@ const createRootReducer = history => (
site: siteReducer,
upload: uploadReducer,
align: alignReducer,
+ audio: audioReducer,
paragraph: paragraphReducer,
timestamp: timestampReducer,
// collection: collectionReducer,
diff --git a/animism-align/frontend/types.js b/animism-align/frontend/types.js
index d38cd68..9d16a4b 100644
--- a/animism-align/frontend/types.js
+++ b/animism-align/frontend/types.js
@@ -11,6 +11,10 @@ export const align = crud_type('align', [
'set_display_setting',
])
+export const audio = with_type('audio', [
+ 'play', 'pause', 'update_time',
+])
+
export const site = with_type('site', [
])
diff --git a/animism-align/frontend/views/align/align.actions.js b/animism-align/frontend/views/align/align.actions.js
index 7d8cbd1..2e1c795 100644
--- a/animism-align/frontend/views/align/align.actions.js
+++ b/animism-align/frontend/views/align/align.actions.js
@@ -1,8 +1,9 @@
import * as types from '../../types'
-import { store, history } from '../../store'
+import { store, history, dispatch } from '../../store'
import { api, post, pad, preloadImage } from '../../util'
import actions from '../../actions'
import { session } from '../../session'
+import throttle from 'lodash.throttle'
import { ZOOM_STEPS } from './constants'
@@ -16,6 +17,10 @@ export const setZoom = zoom => dispatch => {
}
}
+export const throttledSetZoom = throttle(zoom => dispatch => {
+ setZoom(zoom)(dispatch)
+}, 250, { leading: true })
+
export const setCursor = cursor_ts => dispatch => (
dispatch({ type: types.align.set_display_setting, key: 'cursor_ts', value: cursor_ts })
)
diff --git a/animism-align/frontend/views/align/align.css b/animism-align/frontend/views/align/align.css
index 153c481..38a1c8b 100644
--- a/animism-align/frontend/views/align/align.css
+++ b/animism-align/frontend/views/align/align.css
@@ -44,18 +44,40 @@ canvas {
width: 100%;
position: absolute;
left: 0;
+ pointer-events: none;
}
.timeline .cursor .line {
width: 100%;
height: 1px;
background: #00f;
}
+.timeline .cursor.playCursor .line {
+ background: #ddd;
+}
.timeline .cursor .tickLabel {
position: absolute;
+ pointer-events: none;
right: 6px;
font-size: 12px;
width: 40px;
margin-top: -7px;
text-align: right;
text-shadow: 0 0 2px #000, 0 0 2px #000, 0 0 2px #000;
-} \ No newline at end of file
+}
+.playButton {
+ position: absolute;
+ top: 0; left: 0;
+ width: 3rem; height: 3rem;
+ padding: 1rem;
+ background: #000;
+ cursor: pointer;
+ background-size: contain;
+ background-repeat: no-repeat;
+ background-position: center;
+}
+.playButton.playing {
+ background-image: url('/static/img/icons_pause_white.svg');
+}
+.playButton.paused {
+ background-image: url('/static/img/icons_play_white.svg');
+}
diff --git a/animism-align/frontend/views/align/align.reducer.js b/animism-align/frontend/views/align/align.reducer.js
index f616dc2..5640cde 100644
--- a/animism-align/frontend/views/align/align.reducer.js
+++ b/animism-align/frontend/views/align/align.reducer.js
@@ -1,8 +1,6 @@
import * as types from '../../types'
import { session, getDefault, getDefaultInt } from '../../session'
-import { crudState, crudReducer } from '../../api/crud.reducer'
-
const initialState = {
timeline: {
cursor_ts: -1,
@@ -18,14 +16,15 @@ export default function alignReducer(state = initialState, action) {
// console.log(action.type, action)
switch (action.type) {
case types.peaks.loaded:
- return {
- ...state,
- timeline: {
- ...state.timeline,
- duration: action.data.length / 20,
- }
- }
-
+ console.log('peaks duration:', action.data.length / 20)
+ return state
+ // return {
+ // ...state,
+ // timeline: {
+ // ...state.timeline,
+ // duration: action.data.length / 20,
+ // }
+ // }
case types.align.set_display_setting:
return {
...state,
diff --git a/animism-align/frontend/views/align/components/cursor.component.js b/animism-align/frontend/views/align/components/cursor.component.js
index c92f807..a5ad438 100644
--- a/animism-align/frontend/views/align/components/cursor.component.js
+++ b/animism-align/frontend/views/align/components/cursor.component.js
@@ -1,7 +1,7 @@
import React, { Component } from 'react'
import { ZOOM_STEPS } from '../constants'
-import { clamp, timestamp } from '../../../util'
+import { timestamp } from '../../../util'
const Cursor = ({ timeline }) => {
const { start_ts, zoom, cursor_ts, duration } = timeline
diff --git a/animism-align/frontend/views/align/components/playButton.component.js b/animism-align/frontend/views/align/components/playButton.component.js
new file mode 100644
index 0000000..fcb1422
--- /dev/null
+++ b/animism-align/frontend/views/align/components/playButton.component.js
@@ -0,0 +1,31 @@
+import React, { Component } from 'react'
+// import { Link } from 'react-router-dom'
+import { bindActionCreators } from 'redux'
+import { connect } from 'react-redux'
+
+import actions from '../../../actions'
+// import * as alignActions from '../align.actions'
+
+import { ZOOM_STEPS } from '../constants'
+import { clamp } from '../../../util'
+
+const PlayButton = ({ audio }) => {
+ return (
+ <div
+ className={audio.playing ? 'playButton playing' : 'playButton paused'}
+ onClick={() => {
+ audio.playing ? actions.audio.pause() : actions.audio.play()
+ }}
+ />
+ )
+}
+
+const mapStateToProps = state => ({
+ audio: state.audio,
+})
+
+const mapDispatchToProps = dispatch => ({
+ // alignActions: bindActionCreators({ ...alignActions }, dispatch),
+})
+
+export default connect(mapStateToProps, mapDispatchToProps)(PlayButton)
diff --git a/animism-align/frontend/views/align/components/playCursor.component.js b/animism-align/frontend/views/align/components/playCursor.component.js
new file mode 100644
index 0000000..f191bb1
--- /dev/null
+++ b/animism-align/frontend/views/align/components/playCursor.component.js
@@ -0,0 +1,43 @@
+import React, { Component } from 'react'
+// import { Link } from 'react-router-dom'
+import { bindActionCreators } from 'redux'
+import { connect } from 'react-redux'
+
+import { ZOOM_STEPS } from '../constants'
+import { timestamp } from '../../../util'
+
+const PlayCursor = ({ timeline, audio }) => {
+ const { start_ts, zoom, duration } = timeline
+ const { play_ts } = audio
+ const secondsPerPixel = ZOOM_STEPS[zoom] * 0.1
+ const y = (play_ts - start_ts) / secondsPerPixel
+ // console.log(play_ts, y)
+ return (
+ <div
+ className='cursor playCursor'
+ style={{
+ top: y,
+ }}
+ >
+ <div className='line' />
+ </div>
+ )
+}
+
+/*
+ <div className='tickLabel'>
+ {timestamp(cursor_ts, 1)}
+ </div>
+
+*/
+
+const mapStateToProps = state => ({
+ timeline: state.align.timeline,
+ audio: state.audio,
+})
+
+const mapDispatchToProps = dispatch => ({
+ // alignActions: bindActionCreators({ ...alignActions }, dispatch),
+})
+
+export default connect(mapStateToProps, mapDispatchToProps)(PlayCursor)
diff --git a/animism-align/frontend/views/align/components/timeline.component.js b/animism-align/frontend/views/align/components/timeline.component.js
index 86175cf..3b12cb5 100644
--- a/animism-align/frontend/views/align/components/timeline.component.js
+++ b/animism-align/frontend/views/align/components/timeline.component.js
@@ -9,6 +9,8 @@ import actions from '../../../actions'
import Waveform from './waveform.component'
import Ticks from './ticks.component'
import Cursor from './cursor.component'
+import PlayButton from './playButton.component'
+import PlayCursor from './playCursor.component'
import { ZOOM_STEPS } from '../constants'
import { clamp } from '../../../util'
@@ -19,6 +21,7 @@ class Timeline extends Component {
this.handleKeydown = this.handleKeydown.bind(this)
this.handleMouseMove = this.handleMouseMove.bind(this)
this.handleWheel = this.handleWheel.bind(this)
+ this.handleClick = this.handleClick.bind(this)
}
componentDidMount() {
this.bind()
@@ -33,10 +36,31 @@ class Timeline extends Component {
document.removeEventListener('keydown', this.handleKeydown)
}
handleKeydown(e) {
- if (e.shiftKey && e.keyCode === 189) {
- actions.align.setZoom(this.props.timeline.zoom - 1)
- } else if (e.shiftKey && e.keyCode === 187) {
- actions.align.setZoom(this.props.timeline.zoom + 1)
+ if (document.activeElement !== document.body) {
+ return
+ }
+ if (e.shiftKey) {
+ switch (e.keyCode) {
+ case 187: // plus
+ actions.align.setZoom(this.props.timeline.zoom - 1)
+ break
+ case 189: // minus
+ actions.align.setZoom(this.props.timeline.zoom + 1)
+ break
+ }
+ } else {
+ console.log(e.keyCode)
+ switch (e.keyCode) {
+ case 32: // spacebar
+ actions.audio.toggle()
+ break
+ case 38: // up
+ actions.audio.jump(- ZOOM_STEPS[this.props.timeline.zoom] * 0.1)
+ break
+ case 40: // down
+ actions.audio.jump(ZOOM_STEPS[this.props.timeline.zoom] * 0.1)
+ break
+ }
}
}
handleWheel(e) {
@@ -45,28 +69,26 @@ class Timeline extends Component {
let widthTimeDuration = window.innerHeight * secondsPerPixel // secs per pixel
start_ts = clamp(start_ts + e.deltaY * ZOOM_STEPS[zoom], 0, Math.max(0, duration - widthTimeDuration / 2))
- console.log(start_ts)
- actions.align.setScrollPosition(start_ts)
+ if (e.shiftKey) {
+ if (Math.abs(e.deltaY) < 2) return
+ if (e.deltaY > 0) {
+ actions.align.throttledSetZoom(this.props.timeline.zoom + 1)
+ } else {
+ actions.align.throttledSetZoom(this.props.timeline.zoom - 1)
+ }
+ } else if (e.altKey) {
+ actions.audio.seek(start_ts)
+ } else {
+ actions.align.setScrollPosition(start_ts)
+ }
}
handleMouseMove(e) {
- const { start_ts, zoom, duration } = this.props.timeline
- const y = e.pageY
- const height = window.innerHeight
-
- let secondsPerPixel = ZOOM_STEPS[zoom] * 0.1
- let widthTimeDuration = height * secondsPerPixel
-
- let timeMin = start_ts
- let timeMax = Math.min(start_ts + widthTimeDuration, duration)
- let timeWidth = timeMax - timeMin
-
- let cursor_ts = y * secondsPerPixel + start_ts
- cursor_ts = clamp(cursor_ts, 0, timeMax)
-
+ const cursor_ts = positionToTime(e.pageY, this.props.timeline)
actions.align.setCursor(cursor_ts)
-
- // let pixelMin = timeMin / secondsPerPixel
- // const ts =
+ }
+ handleClick(e) {
+ const play_ts = positionToTime(e.pageY, this.props.timeline)
+ actions.audio.seek(play_ts)
}
render() {
return (
@@ -75,14 +97,27 @@ class Timeline extends Component {
onWheel={this.handleWheel}
onMouseMove={this.handleMouseMove}
>
- <Waveform />
+ <Waveform onClick={this.handleClick} />
<Ticks timeline={this.props.timeline} />
<Cursor timeline={this.props.timeline} />
+ <PlayCursor />
+ <PlayButton />
</div>
)
}
}
+const positionToTime = (y, { start_ts, zoom, duration }) => {
+ const secondsPerPixel = ZOOM_STEPS[zoom] * 0.1
+ const widthTimeDuration = window.innerHeight * secondsPerPixel
+
+ const timeMin = start_ts
+ const timeMax = Math.min(start_ts + widthTimeDuration, duration)
+ const timeWidth = timeMax - timeMin
+
+ return clamp(y * secondsPerPixel + start_ts, 0, timeMax)
+}
+
const mapStateToProps = state => ({
timeline: state.align.timeline,
})
diff --git a/animism-align/frontend/views/align/components/waveform.component.js b/animism-align/frontend/views/align/components/waveform.component.js
index 4cd9963..e454623 100644
--- a/animism-align/frontend/views/align/components/waveform.component.js
+++ b/animism-align/frontend/views/align/components/waveform.component.js
@@ -83,7 +83,7 @@ class Waveform extends Component {
}
render() {
return (
- <canvas ref={this.canvasRef} />
+ <canvas ref={this.canvasRef} onClick={this.props.onClick} />
)
}
}
diff --git a/animism-align/frontend/views/audio/audio.actions.js b/animism-align/frontend/views/audio/audio.actions.js
new file mode 100644
index 0000000..963d8b0
--- /dev/null
+++ b/animism-align/frontend/views/audio/audio.actions.js
@@ -0,0 +1,40 @@
+import * as types from '../../types'
+import { store, history, dispatch } from '../../store'
+import actions from '../../actions'
+import { session } from '../../session'
+
+const audioPlayer = document.createElement('audio')
+audioPlayer.src = '/static/data_store/peaks/animismA200620.mp3'
+audioPlayer.addEventListener('loadedmetadata', () => {
+ console.log('audio duration:', audioPlayer.duration)
+ dispatch({ type: types.align.set_display_setting, key: 'duration', value: audioPlayer.duration })
+})
+audioPlayer.addEventListener('play', () => {
+ dispatch({ type: types.audio.play })
+})
+audioPlayer.addEventListener('pause', () => {
+ dispatch({ type: types.audio.pause })
+})
+audioPlayer.addEventListener('timeupdate', () => {
+ dispatch({ type: types.audio.update_time, play_ts: audioPlayer.currentTime })
+})
+
+export const play = () => dispatch => {
+ audioPlayer.play()
+}
+export const pause = () => dispatch => {
+ audioPlayer.pause()
+}
+export const seek = play_ts => dispatch => {
+ audioPlayer.currentTime = play_ts
+}
+export const jump = delta_ts => dispatch => {
+ audioPlayer.currentTime += delta_ts
+}
+export const toggle = () => dispatch => {
+ if (store.getState().audio.playing) {
+ pause()(dispatch)
+ } else {
+ play()(dispatch)
+ }
+} \ No newline at end of file
diff --git a/animism-align/frontend/views/audio/audio.reducer.js b/animism-align/frontend/views/audio/audio.reducer.js
new file mode 100644
index 0000000..e37933d
--- /dev/null
+++ b/animism-align/frontend/views/audio/audio.reducer.js
@@ -0,0 +1,30 @@
+import * as types from '../../types'
+import { session, getDefault, getDefaultInt } from '../../session'
+
+const initialState = {
+ playing: false,
+ play_ts: 0,
+}
+
+export default function alignReducer(state = initialState, action) {
+ // console.log(action.type, action)
+ switch (action.type) {
+ case types.audio.play:
+ return {
+ ...state,
+ playing: true,
+ }
+ case types.audio.pause:
+ return {
+ ...state,
+ playing: false,
+ }
+ case types.audio.update_time:
+ return {
+ ...state,
+ play_ts: action.play_ts,
+ }
+ default:
+ return state
+ }
+}
diff --git a/animism-align/static/img/elements/close.svg b/animism-align/static/img/elements/close.svg
new file mode 100644
index 0000000..a6141d9
--- /dev/null
+++ b/animism-align/static/img/elements/close.svg
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 19.2.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ viewBox="0 0 22.3 22.3" enable-background="new 0 0 22.3 22.3" xml:space="preserve">
+<g>
+ <defs>
+ <rect id="SVGID_1_" width="22.3" height="22.3"/>
+ </defs>
+ <clipPath id="SVGID_2_">
+ <use xlink:href="#SVGID_1_" overflow="visible"/>
+ </clipPath>
+ <line clip-path="url(#SVGID_2_)" fill="none" stroke="#010101" stroke-width="2" x1="0.7" y1="21.6" x2="21.6" y2="0.7"/>
+ <line clip-path="url(#SVGID_2_)" fill="none" stroke="#010101" stroke-width="2" x1="0.7" y1="0.7" x2="21.6" y2="21.6"/>
+</g>
+</svg>
diff --git a/animism-align/static/img/elements/e-flux-logo.svg b/animism-align/static/img/elements/e-flux-logo.svg
new file mode 100644
index 0000000..aa0809f
--- /dev/null
+++ b/animism-align/static/img/elements/e-flux-logo.svg
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 19.2.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ viewBox="0 0 218.3 65" style="enable-background:new 0 0 218.3 65;" xml:space="preserve">
+<style type="text/css">
+ .st0{display:none;}
+ .st1{display:inline;fill:none;stroke:#000000;}
+</style>
+<g id="Layer_2" class="st0">
+ <circle class="st1" cx="-450.5" cy="-145" r="306"/>
+ <circle class="st1" cx="81.5" cy="226.3" r="91.8"/>
+ <circle class="st1" cx="-198.4" cy="357.6" r="91.8"/>
+ <circle class="st1" cx="273.2" cy="190.3" r="91.8"/>
+ <circle class="st1" cx="440.5" cy="288.4" r="91.8"/>
+ <circle class="st1" cx="-129.4" cy="591.6" r="91.8"/>
+ <circle class="st1" cx="226.7" cy="371" r="91.8"/>
+ <circle class="st1" cx="368.8" cy="517" r="91.8"/>
+ <line class="st1" x1="483.5" y1="29" x2="489.5" y2="29"/>
+ <line class="st1" x1="-63.8" y1="79" x2="511.5" y2="662"/>
+ <line class="st1" x1="512.5" y1="373" x2="225.5" y2="663"/>
+ <line class="st1" x1="225.5" y1="85.7" x2="-64.5" y2="371"/>
+ <line class="st1" x1="225.5" y1="371" x2="226.7" y2="371"/>
+ <line class="st1" x1="225.5" y1="371" x2="514.8" y2="87"/>
+</g>
+<g id="Layer_4">
+ <g>
+ <path d="M16.7,51.2c1.7,1.7,4.3,2.5,7.5,2.5c2.4,0,4.4-0.6,6.1-1.8c1.7-1.2,2.7-2.4,3.1-3.8h10.3c-1.6,5.1-4.2,8.8-7.5,10.9
+ c-3.4,2.2-7.5,3.3-12.3,3.3c-3.3,0-6.3-0.5-9-1.6s-5-2.6-6.8-4.5c-1.9-2-3.3-4.3-4.3-7s-1.5-5.7-1.5-9c0-3.2,0.5-6.1,1.6-8.8
+ c1-2.7,2.5-5.1,4.4-7.1s4.2-3.6,6.9-4.7c2.7-1.1,5.6-1.7,8.8-1.7c3.6,0,6.8,0.7,9.4,2.1c2.7,1.4,4.9,3.3,6.6,5.6
+ c1.7,2.4,3,5,3.7,8c0.8,3,1,6.2,0.8,9.4H13.9C14,46.8,15,49.5,16.7,51.2z M29.9,29c-1.4-1.5-3.5-2.3-6.4-2.3
+ c-1.9,0-3.4,0.3-4.6,0.9s-2.2,1.4-3,2.3c-0.7,0.9-1.3,1.9-1.6,2.9c-0.3,1-0.5,2-0.5,2.8h18.9C32.3,32.7,31.3,30.5,29.9,29z"/>
+ <path d="M51.8,36.5h20.5v4.7H51.8V36.5z"/>
+ <path d="M77.2,26.8V19h7v-3.3c0-3.8,1.2-6.8,3.5-9.2c2.4-2.4,5.9-3.6,10.7-3.6c1,0,2.1,0,3.1,0.1s2.1,0.1,3,0.2V12
+ c-1.4-0.2-2.8-0.3-4.3-0.3c-1.6,0-2.7,0.4-3.4,1.1c-0.7,0.7-1,1.9-1,3.7V19h8v7.8h-8v34.6H84.2V26.8H77.2z"/>
+ <path d="M120.9,2.9v58.5h-11.7V2.9H120.9z"/>
+ <path d="M158.8,61.5v-5.9h-0.2c-1.5,2.5-3.4,4.2-5.8,5.3c-2.4,1.1-4.8,1.6-7.2,1.6c-3.1,0-5.7-0.4-7.7-1.2s-3.6-2-4.7-3.5
+ c-1.1-1.5-2-3.3-2.4-5.5c-0.5-2.2-0.7-4.6-0.7-7.2V19h11.7v24c0,3.5,0.5,6.1,1.6,7.9c1.1,1.7,3,2.6,5.8,2.6c3.2,0,5.5-0.9,6.9-2.8
+ c1.4-1.9,2.1-5,2.1-9.3V19h11.7v42.5H158.8z"/>
+ <path d="M175.6,19h13.3l7.5,11.1l7.4-11.1h12.9l-13.9,19.9l15.7,22.6H205l-8.9-13.4l-8.9,13.4h-13l15.3-22.3L175.6,19z"/>
+ </g>
+</g>
+</svg>
diff --git a/animism-align/static/img/elements/icons_arrow-down.svg b/animism-align/static/img/elements/icons_arrow-down.svg
new file mode 100755
index 0000000..b800b63
--- /dev/null
+++ b/animism-align/static/img/elements/icons_arrow-down.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 24.1.2, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ viewBox="0 0 40 40" style="enable-background:new 0 0 40 40;" xml:space="preserve">
+<polygon points="10.06,16.14 9.51,16.98 20,23.86 30.49,16.98 29.94,16.14 20,22.66 "/>
+</svg>
diff --git a/animism-align/static/img/elements/icons_arrow-left.svg b/animism-align/static/img/elements/icons_arrow-left.svg
new file mode 100755
index 0000000..ffc7dd0
--- /dev/null
+++ b/animism-align/static/img/elements/icons_arrow-left.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 24.1.2, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ viewBox="0 0 40 40" style="enable-background:new 0 0 40 40;" xml:space="preserve">
+<polygon points="23.02,30.49 16.14,20 23.02,9.51 23.86,10.06 17.34,20 23.86,29.94 "/>
+</svg>
diff --git a/animism-align/static/img/elements/icons_arrow-right.svg b/animism-align/static/img/elements/icons_arrow-right.svg
new file mode 100755
index 0000000..b0807cb
--- /dev/null
+++ b/animism-align/static/img/elements/icons_arrow-right.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 24.1.2, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ viewBox="0 0 40 40" style="enable-background:new 0 0 40 40;" xml:space="preserve">
+<polygon points="16.98,30.49 16.14,29.94 22.66,20 16.14,10.06 16.98,9.51 23.86,20 "/>
+</svg>
diff --git a/animism-align/static/img/elements/icons_arrow-up.svg b/animism-align/static/img/elements/icons_arrow-up.svg
new file mode 100755
index 0000000..1a6c776
--- /dev/null
+++ b/animism-align/static/img/elements/icons_arrow-up.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 24.1.2, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ viewBox="0 0 40 40" style="enable-background:new 0 0 40 40;" xml:space="preserve">
+<polygon points="10.06,23.86 9.51,23.02 20,16.14 30.49,23.02 29.94,23.86 20,17.34 "/>
+</svg>
diff --git a/animism-align/static/img/elements/icons_audio.svg b/animism-align/static/img/elements/icons_audio.svg
new file mode 100755
index 0000000..e9e4bd4
--- /dev/null
+++ b/animism-align/static/img/elements/icons_audio.svg
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 24.1.2, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ viewBox="0 0 40 40" style="enable-background:new 0 0 40 40;" xml:space="preserve">
+<path d="M29.11,20.37c0,2.48-1.21,4.74-3.23,6.04l-0.57-0.89c1.71-1.1,2.74-3.03,2.74-5.15c0-2.11-1.06-4.09-2.77-5.18l0.57-0.9
+ C27.89,15.59,29.11,17.86,29.11,20.37z M10.89,15.5v9h5.02l5.4,4.38V11.12l-5.4,4.38H10.89z"/>
+</svg>
diff --git a/animism-align/static/img/elements/icons_pause.svg b/animism-align/static/img/elements/icons_pause.svg
new file mode 100755
index 0000000..c281fd1
--- /dev/null
+++ b/animism-align/static/img/elements/icons_pause.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 24.1.2, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ viewBox="0 0 40 40" style="enable-background:new 0 0 40 40;" xml:space="preserve">
+<path d="M14.34,12.5h4.16v15h-4.16V12.5z M21.5,27.5h4.17v-15H21.5V27.5z"/>
+</svg>
diff --git a/animism-align/static/img/elements/icons_play.svg b/animism-align/static/img/elements/icons_play.svg
new file mode 100755
index 0000000..4435cb6
--- /dev/null
+++ b/animism-align/static/img/elements/icons_play.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 24.1.2, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ viewBox="0 0 40 40" style="enable-background:new 0 0 40 40;" xml:space="preserve">
+<polygon points="12.5,11 12.5,29 27.5,20 "/>
+</svg>
diff --git a/animism-align/static/img/elements/icons_volume.svg b/animism-align/static/img/elements/icons_volume.svg
new file mode 100755
index 0000000..b614500
--- /dev/null
+++ b/animism-align/static/img/elements/icons_volume.svg
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 24.1.2, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ viewBox="0 0 40 40" style="enable-background:new 0 0 40 40;" xml:space="preserve">
+<path d="M5,25h4.17l0,2.5H5V25z M15.33,27.5h4.17V20h-4.17V27.5z M20.5,27.5h4.17v-10H20.5V27.5z M25.67,27.5h4.17V15h-4.17V27.5z
+ M30.83,27.5H35v-15h-4.17V27.5z M10.17,27.5h4.17v-5h-4.17V27.5z"/>
+</svg>
diff --git a/animism-align/static/img/elements/icons_zoom-plus.svg b/animism-align/static/img/elements/icons_zoom-plus.svg
new file mode 100755
index 0000000..22357ef
--- /dev/null
+++ b/animism-align/static/img/elements/icons_zoom-plus.svg
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 24.1.2, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ viewBox="0 0 40 40" style="enable-background:new 0 0 40 40;" xml:space="preserve">
+<path d="M20,35.5c-8.55,0-15.5-6.95-15.5-15.5c0-8.55,6.95-15.5,15.5-15.5S35.5,11.45,35.5,20C35.5,28.55,28.55,35.5,20,35.5z
+ M20,5.5C12,5.5,5.5,12,5.5,20c0,8,6.5,14.5,14.5,14.5S34.5,28,34.5,20C34.5,12,28,5.5,20,5.5z M27.5,19.5h-7v-7h-1v7h-7v1h7v7h1v-7
+ h7V19.5z"/>
+</svg>
diff --git a/animism-align/static/img/elements/icons_zoom-x.svg b/animism-align/static/img/elements/icons_zoom-x.svg
new file mode 100755
index 0000000..126a582
--- /dev/null
+++ b/animism-align/static/img/elements/icons_zoom-x.svg
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 24.1.2, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ viewBox="0 0 40 40" style="enable-background:new 0 0 40 40;" xml:space="preserve">
+<path d="M30.96,9.04c-6.05-6.05-15.88-6.04-21.93,0c-6.04,6.04-6.04,15.88,0,21.93c3.02,3.02,6.99,4.53,10.96,4.53
+ s7.94-1.51,10.96-4.53C37.01,24.92,37.01,15.08,30.96,9.04z M30.26,30.26C27.52,32.99,23.87,34.5,20,34.5s-7.52-1.51-10.26-4.25
+ c-5.66-5.66-5.66-14.86,0-20.51C12.57,6.92,16.29,5.5,20,5.5s7.43,1.41,10.26,4.24C33,12.48,34.5,16.13,34.5,20
+ S33,27.52,30.26,30.26z M25.66,15.05L20.71,20l4.94,4.94l-0.71,0.71L20,20.71l-4.94,4.94l-0.71-0.71L19.29,20l-4.95-4.95l0.71-0.71
+ L20,19.29l4.95-4.95L25.66,15.05z"/>
+</svg>
diff --git a/animism-align/static/img/elements/menu.svg b/animism-align/static/img/elements/menu.svg
new file mode 100644
index 0000000..98493da
--- /dev/null
+++ b/animism-align/static/img/elements/menu.svg
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 19.2.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ viewBox="0 0 30 21" style="enable-background:new 0 0 30 21;" xml:space="preserve">
+<style type="text/css">
+ .st0{clip-path:url(#SVGID_2_);fill:none;stroke:#010101;stroke-width:2;}
+</style>
+<g>
+ <defs>
+ <rect id="SVGID_1_" width="30" height="21"/>
+ </defs>
+ <clipPath id="SVGID_2_">
+ <use xlink:href="#SVGID_1_" style="overflow:visible;"/>
+ </clipPath>
+ <line class="st0" x1="0" y1="1" x2="30" y2="1"/>
+ <line class="st0" x1="0" y1="10.5" x2="30" y2="10.5"/>
+ <line class="st0" x1="0" y1="20" x2="30" y2="20"/>
+</g>
+</svg>
diff --git a/animism-align/static/img/elements/search.svg b/animism-align/static/img/elements/search.svg
new file mode 100644
index 0000000..8920f82
--- /dev/null
+++ b/animism-align/static/img/elements/search.svg
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 19.2.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ viewBox="0 0 32.4 32.8" enable-background="new 0 0 32.4 32.8" xml:space="preserve">
+<g>
+ <defs>
+ <rect id="SVGID_1_" x="1.9" y="2.4" width="29.5" height="29.4"/>
+ </defs>
+ <clipPath id="SVGID_2_">
+ <use xlink:href="#SVGID_1_" overflow="visible"/>
+ </clipPath>
+ <path clip-path="url(#SVGID_2_)" fill="#FFFFFF" d="M13.1,23.7c5.6,0,10.1-4.5,10.1-10.1c0-5.6-4.5-10.1-10.1-10.1S2.9,8,2.9,13.6
+ C2.9,19.2,7.5,23.7,13.1,23.7"/>
+ <circle clip-path="url(#SVGID_2_)" fill="none" stroke="#000000" stroke-width="2" cx="13.1" cy="13.6" r="10.1"/>
+ <line clip-path="url(#SVGID_2_)" fill="none" stroke="#010101" stroke-width="2" x1="20.3" y1="20.6" x2="30.7" y2="31.1"/>
+</g>
+</svg>
diff --git a/animism-align/static/img/icons_pause_white.svg b/animism-align/static/img/icons_pause_white.svg
new file mode 100755
index 0000000..59c7e60
--- /dev/null
+++ b/animism-align/static/img/icons_pause_white.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 24.1.2, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ viewBox="0 0 40 40" style="enable-background:new 0 0 40 40;" xml:space="preserve">
+<path fill='#ffffff' d="M14.34,12.5h4.16v15h-4.16V12.5z M21.5,27.5h4.17v-15H21.5V27.5z"/>
+</svg>
diff --git a/animism-align/static/img/icons_play_white.svg b/animism-align/static/img/icons_play_white.svg
new file mode 100755
index 0000000..78ff002
--- /dev/null
+++ b/animism-align/static/img/icons_play_white.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 24.1.2, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ viewBox="0 0 40 40" style="enable-background:new 0 0 40 40;" xml:space="preserve">
+<polygon fill='#ffffff' points="12.5,11 12.5,29 27.5,20 "/>
+</svg>