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

+ +
+

João Silva, 2023

+
+ + + + + + + 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