diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..b58b603
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,5 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Editor-based HTTP Client requests
+/httpRequests/
diff --git a/.idea/.name b/.idea/.name
new file mode 100644
index 0000000..e8e89e7
--- /dev/null
+++ b/.idea/.name
@@ -0,0 +1 @@
+Onde andas
\ No newline at end of file
diff --git a/.idea/Onde andas.iml b/.idea/Onde andas.iml
new file mode 100644
index 0000000..6b49a13
--- /dev/null
+++ b/.idea/Onde andas.iml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/jsLibraryMappings.xml b/.idea/jsLibraryMappings.xml
new file mode 100644
index 0000000..03c98bb
--- /dev/null
+++ b/.idea/jsLibraryMappings.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000..89d267e
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..35eb1dd
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..6d31e2c
--- /dev/null
+++ b/README.md
@@ -0,0 +1,3 @@
+# R3ISilva.github.io
+Tell me where you've been and I'll tell you who you are.
+Autobiography made out of interactive soundscapes.
diff --git a/audio/bar/anim.wav b/audio/bar/anim.wav
new file mode 100644
index 0000000..cf41b9d
Binary files /dev/null and b/audio/bar/anim.wav differ
diff --git a/audio/bar/hover.wav b/audio/bar/hover.wav
new file mode 100644
index 0000000..58ea296
Binary files /dev/null and b/audio/bar/hover.wav differ
diff --git a/audio/favorite_spot/anim.wav b/audio/favorite_spot/anim.wav
new file mode 100644
index 0000000..19c4d02
Binary files /dev/null and b/audio/favorite_spot/anim.wav differ
diff --git a/audio/favorite_spot/hover.wav b/audio/favorite_spot/hover.wav
new file mode 100644
index 0000000..7423c1b
Binary files /dev/null and b/audio/favorite_spot/hover.wav differ
diff --git a/audio/home/anim.wav b/audio/home/anim.wav
new file mode 100644
index 0000000..ae1d4b6
Binary files /dev/null and b/audio/home/anim.wav differ
diff --git a/audio/home/hover.wav b/audio/home/hover.wav
new file mode 100644
index 0000000..ed298c0
Binary files /dev/null and b/audio/home/hover.wav differ
diff --git a/audio/studio/anim.wav b/audio/studio/anim.wav
new file mode 100644
index 0000000..6150e1b
Binary files /dev/null and b/audio/studio/anim.wav differ
diff --git a/audio/studio/hover.wav b/audio/studio/hover.wav
new file mode 100644
index 0000000..1e4fc8b
Binary files /dev/null and b/audio/studio/hover.wav differ
diff --git a/audio/work/anim.wav b/audio/work/anim.wav
new file mode 100644
index 0000000..925c943
Binary files /dev/null and b/audio/work/anim.wav differ
diff --git a/audio/work/hover.wav b/audio/work/hover.wav
new file mode 100644
index 0000000..33e4ec6
Binary files /dev/null and b/audio/work/hover.wav differ
diff --git a/audio/zone/anim.wav b/audio/zone/anim.wav
new file mode 100644
index 0000000..50679e0
Binary files /dev/null and b/audio/zone/anim.wav differ
diff --git a/audio/zone/hover.wav b/audio/zone/hover.wav
new file mode 100644
index 0000000..90c3071
Binary files /dev/null and b/audio/zone/hover.wav differ
diff --git a/index.html b/index.html
new file mode 100644
index 0000000..922e9db
--- /dev/null
+++ b/index.html
@@ -0,0 +1,25 @@
+
+
+
+
+ PSI1
+
+
+
+
+
+
+
+
+ click to start
+
+
+
+
+
+
+
+
+
diff --git a/scripts/audio.js b/scripts/audio.js
new file mode 100644
index 0000000..99c5d7e
--- /dev/null
+++ b/scripts/audio.js
@@ -0,0 +1,50 @@
+class GraphAudio {
+
+ constructor(backgroundAudio) {
+ this.audios = new Map()
+ this.addAudio(backgroundAudio, backgroundAudio)
+ this.backgroundAudio = backgroundAudio
+ this.currentPlayingID = undefined
+ }
+
+ addAudio(id, src) {
+ if (this.audios.has(id)) return
+ const audio = {
+ howl: new Howl({
+ src: '../audio/' + src,
+ loop: true,
+ volume: 0
+ }),
+ id: undefined
+ }
+ this.audios.set(id, audio)
+ audio.howl.once('load', () => {
+ audio.id = audio.howl.play()
+ })
+ return audio
+ }
+
+ playAudio(id) {
+ const audio = this.audios.get(id)
+ if (audio === undefined) throw new Error("Audio '" + id + "' not found")
+
+ // Same audio, return
+ if (this.currentPlayingID !== undefined && audio.id === this.currentPlayingID) return
+
+ // Pause current sound
+ if (this.currentPlayingID !== undefined) {
+ this.stopAudio(this.currentPlayingID)
+ }
+
+ // Play new sound
+ audio.howl.fade(audio.howl.volume(audio.id), 1.0, 500, audio.id)
+ this.currentPlayingID = id
+ }
+
+ stopAudio(id) {
+ let audio = this.audios.get(id)
+ if (audio === undefined) throw new Error("Audio '" + this.currentPlayingID + "' not found")
+
+ audio.howl.fade(audio.howl.volume(audio.id), 0, 500, audio.id)
+ }
+}
\ No newline at end of file
diff --git a/scripts/config.js b/scripts/config.js
new file mode 100644
index 0000000..147188a
--- /dev/null
+++ b/scripts/config.js
@@ -0,0 +1,171 @@
+const graphConfig = {
+ title: "Tell me where you've been and I'll tell you who you are",
+ circlesContainerId: 'graph',
+ linesContainerId: 'lines',
+ backgroundAudio: 'zone/hover.wav',
+ mainCircle: 'zone',
+ circles: {
+ zone: {
+ text: 'ZONE',
+ size: 'normal',
+ priority: 0,
+ prerequisites: [],
+ lines: [
+ { adjacentCircle: 'work', isDashed: false },
+ { adjacentCircle: 'home', isDashed: false },
+ { adjacentCircle: 'studio', isDashed: false },
+ { adjacentCircle: 'bar', isDashed: false },
+ { adjacentCircle: 'favorite_spot', isDashed: false }
+ ],
+ animation: {
+ audio: 'zone/anim.wav',
+ steps: [
+ {
+ text: 'ZONE, the only non-physical soundscape',
+ duration: 1000
+ },
+ {
+ text: 'A blend of soundscapes',
+ duration: 1000
+ }
+ ]
+ }
+ },
+ work: {
+ text: 'WORK',
+ size: 'normal',
+ hoverAudio: 'work/hover.wav',
+ priority: 1,
+ prerequisites: ['zone'],
+ lines: [
+ { adjacentCircle: 'productivity', isDashed: true }
+ ],
+ animation: {
+ audio: 'work/anim.wav',
+ steps: [
+ {
+ text: 'The place to sustain other places',
+ duration: 1000
+ }
+ ]
+ }
+ },
+ studio: {
+ text: 'STUDIO',
+ size: 'normal',
+ hoverAudio: 'studio/hover.wav',
+ priority: 1,
+ prerequisites: ['zone'],
+ lines: [
+ { adjacentCircle: 'productivity', isDashed: true },
+ { adjacentCircle: 'friends', isDashed: true }
+ ],
+ animation: {
+ audio: 'studio/anim.wav',
+ steps: [
+ {
+ text: 'A place to work, create, and have fun,',
+ duration: 1000
+ },
+ {
+ text: 'a good place for a good amount of productivity and leisure',
+ duration: 1000
+ }
+ ]
+ }
+ },
+ home: {
+ text: 'HOME',
+ size: 'normal',
+ hoverAudio: 'home/hover.wav',
+ priority: 1,
+ prerequisites: ['zone'],
+ lines: [
+ { adjacentCircle: 'family', isDashed: true },
+ { adjacentCircle: 'productivity', isDashed: true },
+ { adjacentCircle: 'food', isDashed: true },
+ { adjacentCircle: 'friends', isDashed: true }
+ ],
+ animation: {
+ audio: 'home/anim.wav',
+ steps: [
+ {
+ text: "A place where I'm myself the most,",
+ duration: 1000
+ },
+ {
+ text: "at 21 years old, I've spent ~100 000 hours at home.",
+ duration: 1000
+ }
+ ]
+ }
+ },
+ bar: {
+ text: 'BAR',
+ size: 'normal',
+ hoverAudio: 'bar/hover.wav',
+ priority: 1,
+ prerequisites: ['zone'],
+ lines: [
+ { adjacentCircle: 'food', isDashed: true },
+ { adjacentCircle: 'friends', isDashed: true }
+ ],
+ animation: {
+ audio: 'bar/anim.wav',
+ steps: [
+ {
+ text: 'A place to relax and brainstorm',
+ duration: 1000
+ }
+ ]
+ },
+ },
+ favorite_spot: {
+ text: 'FAVORITE SPOT',
+ size: 'normal',
+ hoverAudio: 'favorite_spot/hover.wav',
+ priority: 1,
+ prerequisites: ['zone'],
+ lines: [
+ { adjacentCircle: 'friends', isDashed: true }
+ ],
+ animation: {
+ audio: 'favorite_spot/anim.wav',
+ steps: [
+ {
+ text: 'A place to forget everything,',
+ duration: 1000
+ },
+ {
+ text: 'and experience the sound and landscape',
+ duration: 1000
+ }
+ ]
+ }
+ },
+ family: {
+ text: 'FAMILY',
+ size: 'small',
+ priority: 2,
+ prerequisites: ['home']
+ },
+ friends: {
+ text: 'FRIENDS',
+ size: 'small',
+ priority: 2,
+ prerequisites: ['home', 'studio', 'bar', 'favorite_spot']
+ },
+ food: {
+ text: 'FOOD',
+ size: 'small',
+ priority: 2,
+ prerequisites: ['home', 'bar']
+ },
+ productivity: {
+ text: 'PRODUCTIVITY',
+ size: 'small',
+ priority: 2,
+ prerequisites: ['work', 'studio', 'home']
+ }
+ }
+}
\ No newline at end of file
diff --git a/scripts/graph.js b/scripts/graph.js
new file mode 100644
index 0000000..f772ff3
--- /dev/null
+++ b/scripts/graph.js
@@ -0,0 +1,168 @@
+// Simple ordered graph implementation
+class Graph {
+
+ constructor(size) {
+ this.size = size
+ this.adjacencyList = new Map()
+ }
+
+ addVertex(vertex) {
+ this.adjacencyList.set(vertex, [])
+ return this.adjacencyList.get(vertex)
+ }
+
+ addEdge(startVertex, endVertex) {
+ this.adjacencyList.get(startVertex).push(endVertex)
+ this.adjacencyList.get(endVertex).push(startVertex)
+ }
+
+ forEach(callback) {
+ for (const vertex of this.adjacencyList.keys()) {
+ callback(vertex)
+ }
+ }
+
+ getAdjacentVertices(vertex) {
+ return this.adjacencyList.get(vertex)
+ }
+}
+
+// Implementation using custom data models (Container and Line) and features such as: audio playback control,
+// node prerequisites and event listening controls.
+class SoundGraph extends Graph {
+
+ constructor(config) {
+ if (config === undefined) throw new Error('No graph configuration provided')
+ if (config.circles === undefined) throw new Error('No circles found in graph configuration (size = 0)')
+ const size = Object.keys(config.circles).length
+ super(size);
+
+ this.config = config
+ this.lines = []
+ this.containers = []
+ }
+
+ init() {
+ const circlesContainer = document.getElementById(this.config.circlesContainerId)
+ if (circlesContainer === null) throw new Error('No container for circles found')
+ this.circlesContainer = circlesContainer
+
+ const linesContainer = document.getElementById(this.config.linesContainerId)
+ if (linesContainer === null) throw new Error('No container for lines found')
+ this.linesContainer = linesContainer
+
+ // Create containers
+ const circleEntries = Object.entries(this.config.circles)
+ for (const entry of circleEntries) {
+ const id = entry[0]
+ const config = entry[1]
+ this.addContainer(id, config)
+ }
+
+ // Add prerequisites
+ for (const container of this.containers) {
+ if (container.config.prerequisites !== undefined && container.config.prerequisites.length > 0) {
+ for (const prerequisite of container.config.prerequisites) {
+ container.prerequisites.push(this.getContainer(prerequisite))
+ }
+ }
+ }
+
+ // Create lines
+ for (const entry of circleEntries) {
+ const id = entry[0]
+ const config = entry[1]
+ if (config.lines !== undefined && config.lines.length > 0) {
+ for (const lineConfig of config.lines) {
+ const startContainer = this.getContainer(id)
+ const endContainer = this.getContainer(lineConfig.adjacentCircle)
+ this.addLine(startContainer, endContainer, lineConfig)
+ }
+ }
+ }
+
+ graphAudio.playAudio(this.config.backgroundAudio)
+ }
+
+ draw() {
+ for (const line of this.lines) {
+ line.draw()
+ }
+ }
+
+ addContainer(id, config) {
+ const container = new Container(id, config)
+
+ super.addVertex(container)
+ this.containers.push(container)
+ container.dom = this.circlesContainer.appendChild(container.dom)
+
+ return container
+ }
+
+ addLine(startContainer, endContainer, config) {
+ const line = new Line(startContainer, endContainer, config)
+
+ super.addEdge(startContainer, endContainer)
+ this.lines.push(line)
+
+ if (line.config.isDashed) {
+ this.linesContainer.prepend(line.mask)
+ line.mask = document.getElementById(line.id + '-mask')
+ }
+ this.linesContainer.prepend(line.dom)
+ line.dom = document.getElementById(line.id)
+
+
+ return line
+ }
+
+ getContainer(id) {
+ const container = this.containers.find(container => container.id === id)
+
+ if (container === undefined) throw new Error("Container '" + id + "' not found")
+
+ return container
+ }
+
+ meetsPrerequisites(container) {
+ return container.prerequisites.every((prerequisite) => prerequisite.isRevealed)
+ }
+
+ getAdjacentContainers(container) {
+ return super.getAdjacentVertices(container)
+ }
+
+ getLinesTo(container) {
+ return container.lines.filter((line) => line.id.endsWith(`-${container.id}`))
+ }
+
+ getLinesFrom(container) {
+ return container.lines.filter((line) => line.id.startsWith(`${container.id}-`))
+ }
+
+ getLinesFromTo(startContainer, endContainer) {
+ return this.lines.filter(line => line.id === `${startContainer.id}-${endContainer.id}`)
+ }
+
+ getPossibleAdjacentContainers(container) {
+ return this.getAdjacentContainers(container).filter((adjContainer) => {
+ return !adjContainer.container.isRevealed && adjContainer.container.prerequisites.every((prerequisite) => prerequisite.isRevealed)
+ })
+ }
+
+ getUnrevealedAdjacentContainers(container) {
+ return this.getAdjacentContainers(container).filter((adjContainer) => !adjContainer.container.isRevealed)
+ }
+
+ setAnimation(container, state) {
+ const otherContainers = this.containers.filter(_container => _container.isRevealed && _container !== container)
+
+ // Is in animation state
+ if (state) {
+ otherContainers.forEach(_container => _container.dom.style.visibility = 'hidden')
+ } else {
+ otherContainers.forEach(_container => _container.dom.style.visibility = 'visible')
+ }
+ }
+}
\ No newline at end of file
diff --git a/scripts/model.js b/scripts/model.js
new file mode 100644
index 0000000..391584b
--- /dev/null
+++ b/scripts/model.js
@@ -0,0 +1,286 @@
+class Container {
+ constructor(id, config) {
+ this.id = id
+ this.config = config
+ this.isRevealed = false
+ this.lines = []
+ this.prerequisites = []
+
+ this.dom = this.createDOM()
+
+ this.setupAudio()
+ }
+
+ createDOM() {
+ const container = document.createElement('div')
+ container.id = this.id
+ container.classList.add('container')
+ container.classList.add(this.config.size)
+ container.style.visibility = 'hidden'
+
+ const circle = document.createElement('div')
+ circle.classList.add('circle')
+ circle.style.opacity = '0'
+
+ const text = document.createElement('p')
+ text.classList.add('circle-text')
+ text.innerText = this.config.text
+ text.style.opacity = '0'
+
+ this.circle = container.appendChild(circle)
+ this.text = container.appendChild(text)
+ return container
+ }
+
+ reveal() {
+ anime.remove(this.dom)
+ anime.remove(this.circle)
+ anime.remove(this.text)
+ this.dom.style.visibility = 'visible'
+ const tl = anime.timeline()
+ .add({
+ targets: this.circle,
+ easing: 'linear',
+ opacity: 1,
+ duration: 1000
+ })
+ .add({
+ targets: this.text,
+ easing: 'linear',
+ opacity: 1,
+ duration: 500,
+ complete: () => {
+ this.isRevealed = true
+ this.setHover(true)
+ }
+ })
+ return tl.finished
+ }
+
+ startAnimation(callback) {
+ this.circle.removeEventListener('click', this.animationHandler)
+ this.setHover(false)
+ const steps = this.config.animation.steps
+ animationText.style.visibility = 'visible'
+ graphAudio.playAudio(this.config.animation.audio)
+
+ const animate = (stepCount) => {
+ this.circle.onmousemove = () => {}
+ const step = steps[stepCount]
+ let duration = 0
+ if (step !== undefined)
+ duration = (step.duration === undefined) ? 0 : Number(step.duration)
+ switch (stepCount) {
+ case 0: // First step
+ animationText.innerText = step.text
+ anime.remove(this.circle)
+ anime.remove(animationText)
+ anime.timeline()
+ .add({
+ targets: this.circle,
+ duration: 0,
+ opacity: 1
+ })
+ .add({
+ targets: this.circle,
+ easing: 'easeInOutElastic(1, .6)',
+ scale: 200,
+ duration: 500
+ })
+ .add({
+ targets: animationText,
+ opacity: 1,
+ duration: 500
+ })
+ .finished.then(() => {
+ setTimeout(() => {
+ window.onmousemove = () => {
+ window.onmousemove = () => {}
+ animate(++stepCount)
+ }
+ }, duration)
+ })
+ break
+
+ case steps.length: // Last step
+ anime.remove(animationText)
+ anime.timeline()
+ .add({
+ targets: animationText,
+ opacity: 0,
+ duration: 500,
+ complete: () => animationText.style.visibility = 'hidden'
+ })
+ .add({
+ targets: this.circle,
+ easing: 'easeOutQuad',
+ scale: 1,
+ duration: 500
+ })
+ .finished.then(() => {
+ graphAudio.playAudio(graph.config.backgroundAudio)
+ this.setHover(true)
+ if (callback !== undefined && typeof callback === 'function') callback()
+ else this.defaultAnimationCallback()
+ })
+ break
+
+ default: // Middle steps
+ anime.remove(animationText)
+ anime.timeline({
+ easing: 'easeOutQuad',
+ duration: 1000
+ })
+ .add({
+ targets: animationText,
+ opacity: 0,
+ complete: () => animationText.innerText = step.text
+ })
+ .add({
+ targets: animationText,
+ opacity: 1
+ })
+ .finished.then(() => {
+ setTimeout(() => {
+ window.onmousemove = () => {
+ window.onmousemove = () => {}
+ animate(++stepCount)
+ }
+ }, duration)
+ })
+ break
+ }
+ }
+
+ animate(0)
+ }
+
+ defaultAnimationCallback() {
+ this.animationHandler = () => this.startAnimation()
+ this.circle.addEventListener('click', this.animationHandler)
+ }
+
+ setHover(state) {
+ const audioID = this.config.hoverAudio
+ if (this.config.hoverAudio !== undefined) {
+ if (this.playHoverAudioHandler === undefined) this.playHoverAudioHandler = () => graphAudio.playAudio(audioID)
+ if (this.stopHoverAudioHandler === undefined) this.stopHoverAudioHandler = () => graphAudio.playAudio(graph.config.backgroundAudio)
+ if (state) {
+ this.circle.addEventListener('mouseenter', this.playHoverAudioHandler)
+ this.circle.addEventListener('mouseleave', this.stopHoverAudioHandler)
+ } else {
+ this.circle.removeEventListener('mouseenter', this.playHoverAudioHandler)
+ this.circle.removeEventListener('mouseleave', this.stopHoverAudioHandler)
+ }
+ }
+ this.circle.classList.toggle('circle-hover', state)
+ }
+
+ setupAudio() {
+ if (this.config.hoverAudio !== undefined) {
+ graphAudio.addAudio(this.config.hoverAudio, this.config.hoverAudio)
+ }
+
+ if (this.config.animation === undefined ||
+ this.config.animation.steps === undefined ||
+ this.config.animation.steps.length === 0)
+ return
+
+ if (this.config.animation.audio !== undefined) {
+ graphAudio.addAudio(this.config.animation.audio, this.config.animation.audio)
+ }
+ }
+
+ setupAnimation(callback) {
+ if (this.config.animation === undefined || this.config.animation.steps === undefined || this.config.animation.steps.length === 0) return
+
+ this.animationHandler = () => this.startAnimation(callback)
+ this.circle.addEventListener('click', this.animationHandler)
+ }
+}
+
+class Line {
+ constructor(startContainer, endContainer, config) {
+ this.id = startContainer.id + '-' + endContainer.id
+ this.startContainer = startContainer
+ this.endContainer = endContainer
+ this.config = config
+ this.isRevealed = false
+
+ const [lineDOM, maskDOM] = this.createDOM()
+ this.dom = lineDOM
+ this.mask = maskDOM
+
+ startContainer.lines.push(this)
+ endContainer.lines.push(this)
+ }
+
+ createDOM() {
+ const namespaceURL = "http://www.w3.org/2000/svg"
+ const line = document.createElementNS(namespaceURL,'line')
+ if (this.config.isDashed) line.classList.add('dashed')
+ line.id = this.id
+ line.style.opacity = '0'
+
+ let mask = undefined
+ if (this.config.isDashed) {
+ mask = document.createElementNS(namespaceURL,'line')
+ mask.classList.add('dashed-mask')
+ mask.id = this.id + '-mask'
+ }
+
+ return [line, mask]
+ }
+
+ reveal() {
+ anime.remove(this.dom)
+ const tl = anime.timeline({
+ targets: this.dom,
+ easing: 'easeInOutQuad'
+ })
+ .add({
+ opacity: 1,
+ duration: 0
+ })
+ .add({
+ strokeDashoffset: [anime.setDashoffset, 0],
+ duration: 1000,
+ complete: () => {
+ this.isRevealed = true
+ }
+ })
+ return tl.finished
+ }
+
+ draw() {
+ const startCircle = this.startContainer.circle
+ const endCircle = this.endContainer.circle
+ Line.draw(this.dom, startCircle, endCircle)
+ if (this.mask !== undefined) Line.draw(this.mask, startCircle, endCircle)
+ }
+
+ static draw(target, startCircle, endCircle) {
+ const startElementWidth = startCircle.getBoundingClientRect().width
+ const startElementHeight = startCircle.getBoundingClientRect().height
+ const endElementWidth = endCircle.getBoundingClientRect().width
+ const endElementHeight = endCircle.getBoundingClientRect().height
+ const startRect = startCircle.getBoundingClientRect()
+ const startOffset = {
+ left: startRect.left + window.scrollX,
+ top: startRect.top + window.scrollY
+ }
+ const endRect = endCircle.getBoundingClientRect()
+ const endOffset = {
+ left: endRect.left + window.scrollX,
+ top: endRect.top + window.scrollY
+ }
+ const x1 = startOffset.left + startElementWidth / 2
+ const y1 = startOffset.top + startElementHeight / 2
+ const x2 = endOffset.left + endElementWidth / 2
+ const y2 = endOffset.top + endElementHeight / 2
+ target.x1.baseVal.value = x1
+ target.x2.baseVal.value = x2
+ target.y1.baseVal.value = y1
+ target.y2.baseVal.value = y2
+ }
+}
\ No newline at end of file
diff --git a/scripts/script.js b/scripts/script.js
new file mode 100644
index 0000000..27f606d
--- /dev/null
+++ b/scripts/script.js
@@ -0,0 +1,146 @@
+const graph = new SoundGraph(graphConfig)
+const animationText = document.getElementById('animation-text')
+const graphAudio = new GraphAudio(graph.config.backgroundAudio)
+let mainContainer
+
+function revealNext() {
+ const adjContainers = graph.getAdjacentContainers(mainContainer)
+ const nextContainers = adjContainers.filter(adjContainer => adjContainer.config.priority === 1 && !adjContainer.isRevealed)
+ if (nextContainers.length !== 0) {
+ const randomIdx = Math.floor(Math.random() * nextContainers.length)
+ const nextContainer = nextContainers[randomIdx]
+ nextContainer.reveal()
+ graph.getLinesFromTo(mainContainer, nextContainer).forEach(line => line.reveal())
+ nextContainer.setupAnimation(() => {
+ revealNext(nextContainer)
+ nextContainer.setupAnimation()
+ })
+ }
+ const smallContainers = graph.containers.filter(_container => _container.isRevealed === false && _container.config.priority === 2 && graph.meetsPrerequisites(_container))
+ for (const smallContainer of smallContainers) {
+ smallContainer.reveal()
+ graph.getLinesTo(smallContainer).forEach(line => line.reveal())
+ }
+}
+
+const handler = () => {
+ window.removeEventListener('click', handler)
+ anime({
+ targets: animationText,
+ easing: 'linear',
+ opacity: 0,
+ duration: 2000,
+ complete: () => {
+ animationText.style.visibility = 'hidden'
+ animationText.style.color = 'black'
+ }
+ })
+
+ graph.init()
+ mainContainer = graph.getContainer('zone')
+
+ graph.draw()
+ window.onresize = () => graph.draw()
+
+ init()
+}
+
+window.onload = () => window.addEventListener('click', handler)
+
+function init() {
+ const cancelHoverHandler = () => {
+ anime.remove(mainContainer.circle)
+ anime.remove(animationText)
+ const duration = 500
+ anime.timeline({
+ easing: 'easeInQuad',
+ duration: duration
+ })
+ .add({
+ targets: animationText,
+ opacity: 0
+ })
+ .add({
+ targets: mainContainer.circle,
+ scale: 1
+ },
+ '-=' + duration)
+ }
+
+ const hoverHandler = () => {
+ anime.remove(mainContainer.circle)
+ anime.remove(animationText)
+ anime.timeline({
+ easing: 'easeOutQuad',
+ duration: 1000
+ })
+ .add({
+ targets: mainContainer.circle,
+ scale: 3
+ })
+ .add({
+ targets: animationText,
+ easing: 'linear',
+ opacity: 1,
+ complete: () => {
+ mainContainer.circle.removeEventListener('mouseenter', hoverHandler)
+ mainContainer.circle.removeEventListener('mouseleave', cancelHoverHandler)
+ bridge()
+ }
+ })
+ }
+
+ anime.remove(animationText)
+ mainContainer.dom.style.visibility = 'visible'
+ anime.timeline({
+ easing: 'linear',
+ duration: 2000
+ })
+ .add({
+ targets: animationText,
+ opacity: 0,
+ complete: () => animationText.innerText = graph.config.title
+ })
+ .add({
+ targets: mainContainer.circle,
+ opacity: 1
+ }).finished.then(() => {
+ mainContainer.isRevealed = true
+ mainContainer.circle.addEventListener('mouseenter', hoverHandler)
+ mainContainer.circle.addEventListener('mouseleave', cancelHoverHandler)
+ })
+}
+
+function bridge() {
+ const tl = anime.timeline({
+ easing: 'easeOutQuad',
+ duration: 1000
+ })
+ .add({
+ targets: animationText,
+ opacity: [1, 0],
+ complete: () => {
+ animationText.style.visibility = 'hidden'
+ animationText.style.top = '50%'
+ animationText.style.color = 'black'
+ }
+ }, 2000)
+ .add({
+ targets: mainContainer.circle,
+ scale: [3, 1]
+ })
+ .add({
+ targets: mainContainer.text,
+ opacity: 1,
+ delay: 500
+ })
+
+ tl.finished.then(() => {
+ mainContainer.reveal()
+
+ mainContainer.setupAnimation(() => {
+ revealNext(mainContainer)
+ mainContainer.setupAnimation()
+ })
+ })
+}
\ No newline at end of file
diff --git a/styles.css b/styles.css
new file mode 100644
index 0000000..a4cc048
--- /dev/null
+++ b/styles.css
@@ -0,0 +1,204 @@
+/*region general*/
+html, body {
+ width: 100%;
+ height: 100%;
+ padding: 0;
+ margin: 0;
+ overflow: clip;
+}
+
+body {
+ background-color: black;
+}
+
+#animation-text {
+ font-family: 'Montserrat', sans-serif;
+ font-size: 50px;
+ text-align: center;
+ color: white;
+ z-index: 1;
+
+ position: absolute;
+ bottom: 1%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+
+ display: block;
+}
+/*endregion*/
+
+/*region container*/
+.container {
+ position: absolute;
+ display: flex;
+ align-items: center;
+ flex-direction: column;
+ transform: translate(-50%, -50%);
+}
+
+.circle {
+ display: block;
+
+ border-radius: 50%;
+ background-color: white;
+}
+
+.circle-hover {
+ scale: 1.0;
+ transition: scale 0.2s linear;
+}
+
+.circle-hover:hover {
+ scale: 1.2;
+}
+
+.normal > .circle {
+ height: 50px;
+ width: 50px;
+}
+
+.small > .circle {
+ height: 17px;
+ width: 17px;
+}
+
+.circle-text {
+ font-family: 'Montserrat', sans-serif;
+ font-weight: bold;
+ letter-spacing: 3px;
+ text-align: center;
+ color: white;
+
+ display: inline-block;
+}
+
+.normal > .circle-text {
+ font-size: 16px;
+ margin-top: 5px;
+}
+
+.small > .circle-text {
+ font-size: 10px;
+ margin-top: 10px;
+}
+/*endregion*/
+
+/*region container coordinates*/
+#family {
+ top: 28%;
+ left: 10%;
+}
+
+#work {
+ top: 20%;
+ left: 40%;
+}
+
+#productivity {
+ top: 28%;
+ left: 55%;
+}
+
+#home {
+ top: 50%;
+ left: 20%;
+}
+
+#zone {
+ top: 50%;
+ left: 50%;
+}
+
+#food {
+ top: 70%;
+ left: 25%;
+}
+
+#studio {
+ top: 25%;
+ left: 80%;
+}
+
+#bar {
+ top: 80%;
+ left: 40%;
+}
+
+#friends {
+ top: 75%;
+ left: 68%;
+}
+
+#favorite_spot {
+ top: 70%;
+ left: 85%;
+ width: 100px;
+ height: auto;
+}
+/*endregion*/
+
+/*region line*/
+line {
+ stroke-width: 10px;
+ stroke: rgb(65, 65, 65);
+ opacity: 0;
+}
+
+.dashed {
+ fill: none;
+ stroke: rgba(255, 255, 255, 0.2);
+ stroke-linejoin: round;
+ stroke-miterlimit: 10;
+ stroke-width: 5px;
+}
+
+.dashed-mask {
+ stroke: black;
+ stroke-width: 5px;
+ stroke-linejoin: round;
+ stroke-miterlimit: 10;
+ stroke-dasharray: 10;
+ opacity: 1;
+}
+/*endregion*/
+
+/*region user*/
+.nome {
+ font-family: 'Montserrat', sans-serif;
+ font-size: 20px;
+ font-weight: bold;
+ letter-spacing: 3px;
+ text-align: center;
+ color: white;
+ opacity: 100%;
+ position: absolute;
+ display: flex;
+ align-items: center;
+ flex-direction: column;
+ bottom: 100px;
+ right: 5%;
+ width: 10%;
+ height: auto;
+ max-height: 230px;
+ overflow: hidden;
+ text-align: right;
+}
+
+.logo {
+ position: absolute;
+ display: flex;
+ align-items: center;
+ flex-direction: column;
+ bottom: 100px;
+ left: 5%;
+ width: 10%;
+ height: auto;
+ max-height: 230px;
+ overflow: hidden;
+}
+
+img {
+ width: 100%;
+ height: auto;
+}
+/*endregion*/
\ No newline at end of file