project page need more but nice

This commit is contained in:
nessar committed 2023-03-27 04:31:38 +02:00
1 parent 8c410cacf8
commit 77cb652917
7 files changed
+222 -129

No files matched your search

+1 -1
View File
@@ -10,7 +10,7 @@
<body style="overflow-x: hidden;">
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
<script type="module" src="src/assets/ts/main.ts"></script>
</body>
</html>
+4
View File
@@ -39,6 +39,10 @@ export default defineComponent({
</template>
<style scoped>
main {
display: grid;
}
.links ol {
list-style: none;
display: flex;
+22 -6
View File
@@ -1,5 +1,5 @@
[
{
"projects": {
"name": "test1",
"year": 2021,
"type": "School Project",
@@ -14,9 +14,25 @@
"github": "https://www.github.com",
"release": "https://www.release.com"
},
"images": {
"main": "https://www.google.com",
"alt": "https://www.google.com"
}
}
"logo": "https://www.google.com",
"video": "https://www.youtube.com"
},
{
"name": "test2",
"year": 2010,
"type": "School Project",
"desc": "This is a super testing project",
"tech": [
"HTML",
"CSS",
"JavaScript"
],
"links": {
"title": "https://www.google.com",
"github": "https://www.github.com",
"release": "https://www.release.com"
},
"logo": "https://www.google.com",
"video": "https://www.youtube.com"
}
]
+1 -1
View File
@@ -1,6 +1,6 @@
import { createApp } from "vue";
import Particles from "vue3-particles";
import App from "./App.vue";
import App from "@/App.vue";
import "@/assets/css/main.css";
+10
View File
@@ -0,0 +1,10 @@
export interface Project {
name: string;
year: number;
type: string;
desc: string;
tech: string[];
links: { [key: string]: string };
logo: string;
video: string;
}
+91 -3
View File
@@ -1,11 +1,99 @@
<template>
<div>
<div class="project">
<h1>{{ project.name }}</h1>
<div class="info">
<p>{{ project.desc }}</p>
<video>
<source :src="project.video" type="video/mp4" />
</video>
</div>
</div>
</template>
<script setup lang="ts">
import { defineComponent } from 'vue';
import type { Project } from '@/assets/ts/project';
</script>
<script lang="ts">
export default defineComponent({
name: 'project',
props: {
project: {
type: Object as () => Project,
required: true,
},
fullscreen: {
type: Boolean,
required: true,
},
color: {
type: String,
required: true,
},
},
data() {
return {
width: '80vw',
height: '80vh',
};
},
watch: {
fullscreen: function (val) {
if (val) {
this.width = '100vw';
this.height = '100vh';
} else {
this.width = '80vw';
this.height = '80vh';
}
},
},
});
</script>
<style scoped></style>
<style scoped>
.project {
width: v-bind(width);
height: v-bind(height);
background-color: v-bind(color);
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
transition: width 0.75s, height 0.75s;
}
h1 {
color: black;
font-size: 2.5em;
}
p {
color: black;
font-size: 1.5em;
}
video {
width: 100%;
height: 100%;
}
.info {
width: 50%;
font-family: montserrat_regular;
display: flex;
align-items: center;
justify-content: center;
flex-direction: row;
}
.info p {
background-color: gray;
padding: 25px;
border-radius: 15px;
margin-right: -35px;
}
</style>
+93 -118
View File
@@ -1,67 +1,102 @@
<template>
<div class="spacer timeline" />
<div class="timeline" id="timeline">
<div class="spacer bg" />
<a id="projects" />
<div id="animStart" />
<div class="transform" id="transform">
<div class="circle" />
<div class="connector" />
<div class="project">
<div class="infos">
<h1>titre</h1>
<p>descriptiondescriptiondescriptiondescriptiondescriptiondescriptiondescriptiriptiondescriptiondescriptiondescriptiondescriptiondescriptiondescription
</p>
<div class="button">
<a href="test">test</a>
</div>
</div>
<video>
<source src="@/assets/videos/test.mp4" type="video/mp4" />
</video>
<div class="timeLineOuter bg" id="timeLineOuter">
<div id="timelineSticky">
<div class="timeline bg" id="timeline">
<project v-for="(p, id) in projects" :project="p" :fullscreen="fullscreen"
:color="COLORS[id % COLORS.length]" />
</div>
</div>
</div>
<div id="animStop" />
</template>
<script setup lang="ts">
import { defineComponent } from 'vue';
import type { Project } from '@/assets/ts/project';
import project from './project.vue';
</script>
<script lang="ts">
const enum STATUS {
BEFOREVIEWPORT,
INVIEWPORT,
AFTERVIEWPORT,
}
const COLORS = ["#DDF9C1", "#F8DDA4", "#F9A03F", "#D45113", "#813405"]
export default defineComponent({
name: 'timeline',
created() {
window.addEventListener('scroll', this.handleScroll);
fetch("src/assets/json/projects.json")
.then(response => response.json())
.then((data: Project[]) => {
this.projects = data;
this.nbProjects = data.length;
})
.catch((error) => {
console.error('Error:', error);
});
},
data() {
return {
nbProjects: 0,
projects: [] as Project[],
fullscreen: false,
gap: "20vw"
};
},
methods: {
handleScroll(event: Event) {
if (this.activateAnimation()) {
const timeline = document.getElementById('timeline')!.getBoundingClientRect();
if (!timeline) return;
const position = timeline.top;
document.getElementById('transform')?.setAttribute('style', `transform: translateY(calc(-50vh + ${-position}px)) rotate(${-position}deg);`);
const timelineSticky = document.getElementById('timelineSticky');
const timeline = document.getElementById('timeline');
const timeLineOuter = document.getElementById('timeLineOuter');
if (!timelineSticky || !timeline || !timeLineOuter) return;
const rect = timeLineOuter.getBoundingClientRect();
const inViewPort = this.isTimelineInViewport();
switch (inViewPort) {
case STATUS.BEFOREVIEWPORT:
this.fullscreen = false
this.gap = "20vw"
timelineSticky.style.position = 'absolute';
timelineSticky.style.top = '10vh';
timelineSticky.style.bottom = '';
timeline.style.transform = `translateX(0px)`;
break;
case STATUS.INVIEWPORT:
this.fullscreen = true
this.gap = ""
timelineSticky.style.position = 'fixed';
timelineSticky.style.top = '10vh';
timelineSticky.style.bottom = '10vh';
// translate the timeline to the right by the amount of scroll
timeline.style.transform = `translateX(${-rect.top}px)`;
break;
case STATUS.AFTERVIEWPORT:
this.fullscreen = false
this.gap = "20vw"
timelineSticky.style.position = 'absolute';
timelineSticky.style.top = '';
timelineSticky.style.bottom = '10vh';
const widthByProject = timeline.getBoundingClientRect().width / this.nbProjects;
timeline.style.transform = `translateX(${widthByProject * (this.nbProjects - 1)}px)`;
break;
}
// else {
// document.getElementById('transform')?.setAttribute('style', `transform: translateY(-50vh) rotate(0deg);`);
// }
timelineSticky.style.width = '100vw';
timelineSticky.style.height = '80vh';
},
activateAnimation() {
let start = document.getElementById('animStart')?.getBoundingClientRect();
let stop = document.getElementById('animStop')?.getBoundingClientRect();
if (!start || !stop) return false;
if (stop.top >= 0 && stop.bottom <= (window.innerHeight || document.documentElement.clientHeight))
return false;
else {
if (start.top >= 0)
return false;
else
return true;
}
isTimelineInViewport(): STATUS {
const timeLineOuter = document.getElementById('timeLineOuter');
if (!timeLineOuter) return STATUS.BEFOREVIEWPORT;
const rect = timeLineOuter.getBoundingClientRect();
if (rect.top < 0 && rect.bottom > window.innerHeight)
return STATUS.INVIEWPORT;
if (rect.top > 0)
return STATUS.BEFOREVIEWPORT;
return STATUS.AFTERVIEWPORT;
},
},
})
@@ -72,89 +107,29 @@ export default defineComponent({
height: 20vh !important;
}
.bg {
background-color: #292929;
}
.timeLineOuter {
position: relative;
width: 100vw;
height: calc(100vw * v-bind(nbProjects - 1) + 100vh);
}
.timeline {
background: linear-gradient(to right, #000000, #292929, #000000);
height: 1000vh;
}
.transform {
width: calc(100vw*v-bind(nbProjects));
height: 80Vh;
display: flex;
align-items: center;
justify-content: center;
flex-direction: row;
height: 200vh;
width: 200vw;
margin-left: -100vw;
transform: translateY(-50vh);
margin-bottom: -100vh;
}
.circle {
display: flex;
justify-content: center;
align-items: center;
border-radius: 50%;
background-color: transparent;
flex-direction: row-reverse;
flex-wrap: nowrap;
position: absolute;
z-index: 1;
border: 5vw groove white;
}
.project {
padding: 15px;
display: flex;
align-items: center;
justify-content: left;
flex-direction: column;
border: 5px solid white;
border-radius: 25px;
width: 70vw;
/* margin: 10vh 5vw 0 15vw; */
filter: drop-shadow(0px 0px 20px rgba(255, 255, 255, 0.5));
}
.connector {
margin-left: 110vw;
height: 5px;
width: 10vw;
background-color: rgb(255, 255, 255);
z-index: 3;
}
video {
width: 100%;
height: 100%;
z-index: 1;
overflow: hidden;
border-radius: 15px;
}
.project .infos {
width: 80%;
z-index: 2;
}
/* Styles for screens taller than wide */
@media screen and (orientation: portrait) {
.circle {
width: 30vw;
height: 30vw;
}
.infos {
background-color: rgb(29, 27, 41);
margin-bottom: -25px;
border-radius: 25px;
padding: 25px;
word-break: break-all;
}
}
/* Styles for screens wider than tall */
@media screen and (orientation: landscape) {
.circle {
width: 80vh;
height: 80vh;
}
left: calc(-100vw*v-bind(nbProjects - 1));
right: 0;
will-change: transform;
transition: transform 0.1s;
gap: v-bind(gap)
}
</style>