project page need more but nice
This commit is contained in:
1 parent
8c410cacf8
commit
77cb652917
7 files changed
+223
-130
No files matched your search
+1
-1
@@ -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>
|
||||
@@ -39,6 +39,10 @@ export default defineComponent({
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
main {
|
||||
display: grid;
|
||||
}
|
||||
|
||||
.links ol {
|
||||
list-style: none;
|
||||
display: flex;
|
||||
|
||||
@@ -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,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";
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
@@ -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>
|
||||
Reference in new issue
Block a user