feat: search engine timeline view

This commit is contained in:
Adrien Marquès 2022-10-04 13:06:09 +02:00
parent 4dddc7973e
commit 52b45583c6
Signed by: xdrm-brackets
GPG Key ID: D75243CA236D825E
14 changed files with 757 additions and 729 deletions

View File

@ -1,19 +1,19 @@
<template>
<div id="app">
<Home/>
<Skills/>
<Timeline/>
</div>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import Home from './components/Home.vue';
import Skills from './components/Skills.vue';
import Timeline from './components/Timeline.vue';
@Component({
components: {
Home,
Skills,
Timeline,
},
})
export default class App extends Vue {
@ -30,8 +30,9 @@ export default class App extends Vue {
min-height: 100%;
height: auto;
padding-bottom: 4vw;
font-size: 1rem;
font-family: 'Source Sans Pro';
flex-flow: column nowrap;
overflow: hidden;

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="4.2333mm" height="4.2333mm" version="1.1" viewBox="0 0 4.2333 4.2333" xmlns="http://www.w3.org/2000/svg">
<g transform="translate(-139.71 -124.82)">
<g transform="matrix(.49451 0 0 .49451 38.616 -321.26)">
<g transform="matrix(.64049 0 0 .64049 75.033 338.39)">
<g transform="matrix(1.3569 0 0 1.3569 -74.479 -286.98)">
<circle cx="208.71" cy="865.03" r="4.9253" fill="#292a2e" style="paint-order:stroke fill markers"/>
<path d="m207.85 862.56a0.33661 0.33661 0 0 0-0.31641 0.18946l-0.96289 1.9766h-0.59961a0.33657 0.33657 0 0 0-0.33789 0.33594 0.33657 0.33657 0 0 0 0.33789 0.33789h0.81055a0.33661 0.33661 0 0 0 0.30273-0.18946l0.71875-1.4746 1.3867 3.543a0.33661 0.33661 0 0 0 0.61133 0.0332l0.99804-1.9121h0.64649a0.33657 0.33657 0 0 0 0.33594-0.33789 0.33657 0.33657 0 0 0-0.33594-0.33594h-0.84961a0.33661 0.33661 0 0 0-0.29883 0.18164l-0.75 1.4375-1.3984-3.5703a0.33661 0.33661 0 0 0-0.29882-0.21485z" color="#000000" fill="#9b9ea1" style="paint-order:stroke fill markers"/>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="4.2333mm" height="4.2333mm" version="1.1" viewBox="0 0 4.2333 4.2333" xmlns="http://www.w3.org/2000/svg">
<g transform="translate(-139.71 -124.82)">
<g transform="matrix(.49451 0 0 .49451 38.616 -321.26)">
<g transform="matrix(.64049 0 0 .64049 75.033 338.39)">
<g transform="matrix(1.3569 0 0 1.3569 -74.479 -286.98)">
<circle cx="208.71" cy="865.03" r="4.9253" fill="#292a2e" style="paint-order:stroke fill markers"/>
</g>
</g>
<path d="m208.16 907.55-0.20732 0.20589c-0.17972 0.17813-0.47201 0.17829-0.65163 0-0.0864-0.0857-0.13378-0.19948-0.13378-0.32046 0-0.12097 0.0474-0.23483 0.13373-0.32051l0.76312-0.75713c0.15816-0.15683 0.45567-0.38776 0.67252-0.1727 0.0995 0.0988 0.26023 0.0982 0.35903-2e-3 0.0988-0.0995 0.0982-0.26023-2e-3 -0.35897-0.3686-0.36579-0.91339-0.29819-1.3879 0.17259l-0.76317 0.75718c-0.1831 0.18177-0.28389 0.42351-0.28389 0.68082 0 0.25732 0.10079 0.49901 0.28394 0.68077 0.18843 0.18694 0.43585 0.28036 0.68338 0.28036 0.24758 0 0.4951-0.0934 0.68363-0.28046l0.20748-0.20594c0.0995-0.0987 0.10003-0.25946 8.7e-4 -0.35898-0.0987-0.0994-0.25956-0.1-0.35907-8.8e-4zm2.3127-2.9412c-0.39591-0.39282-0.94946-0.41413-1.316-0.0505l-0.25844 0.25654c-0.0995 0.0988-0.10012 0.25946-2e-3 0.35897 0.0989 0.0995 0.25957 0.10008 0.35909 2e-3l0.25833-0.25638c0.18985-0.18853 0.43846-0.11038 0.60061 0.0505 0.0865 0.0857 0.13393 0.19954 0.13393 0.32051 0 0.12103-0.0475 0.23488-0.13383 0.32056l-0.81418 0.80769c-0.37229 0.36937-0.54694 0.196-0.62145 0.12205-0.0995-0.0988-0.26019-0.0982-0.35904 2e-3 -0.0988 0.0995-0.0982 0.26023 2e-3 0.35897 0.17091 0.16963 0.36599 0.25372 0.5705 0.25372 0.2504 0 0.51488-0.12609 0.76636-0.37576l0.81418-0.80769c0.18305-0.18177 0.28394-0.42355 0.28394-0.68087 0-0.25721-0.10089-0.499-0.28404-0.68087z" fill="#9b9ea1"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="4.2333mm" height="4.2333mm" version="1.1" viewBox="0 0 4.2333 4.2333" xmlns="http://www.w3.org/2000/svg">
<g transform="translate(-139.71 -124.82)">
<g transform="matrix(.49451 0 0 .49451 38.616 -321.26)">
<g transform="matrix(.64049 0 0 .64049 75.033 338.39)">
<g transform="matrix(1.3569 0 0 1.3569 -74.479 -286.98)">
<circle cx="208.71" cy="865.03" r="4.9253" fill="#292a2e" style="paint-order:stroke fill markers"/>
</g>
</g>
<path d="m210.17 904.55h-2.9336c-0.40336 0-0.73339 0.33003-0.73339 0.7334v1.8335c0 0.40337 0.33003 0.73339 0.73339 0.73339v0.55005c0 0.16502 0.20169 0.23836 0.3117 0.12835l0.67839-0.6784h1.9435c0.40336 0 0.73339-0.33002 0.73339-0.73339v-1.8335c0-0.40337-0.33003-0.7334-0.73339-0.7334zm-1.1001 2.2002h-1.1001c-0.11001 0-0.18336-0.0733-0.18336-0.18335 0-0.11001 0.0734-0.18334 0.18336-0.18334h1.1001c0.11001 0 0.18335 0.0733 0.18335 0.18334 0 0.11001-0.0733 0.18335-0.18335 0.18335zm0.36669-0.7334h-1.4668c-0.11001 0-0.18336-0.0733-0.18336-0.18334 0-0.11001 0.0734-0.18336 0.18336-0.18336h1.4668c0.11001 0 0.18336 0.0734 0.18336 0.18336 0 0.11001-0.0734 0.18334-0.18336 0.18334z" fill="#9b9ea1"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="4.2333mm" height="4.2333mm" version="1.1" viewBox="0 0 4.2333 4.2333" xmlns="http://www.w3.org/2000/svg">
<g transform="translate(-55.204 -97.322)">
<g transform="matrix(.42975 0 0 .42975 -32.37 -272.31)">
<circle cx="208.71" cy="865.03" r="4.9253" fill="#2c55cf" style="paint-order:stroke fill markers"/>
<path d="m209.6 862.95c0-0.0945-0.0733-0.29631-0.29592-0.29631-0.12969 0-0.24927 0.0842-0.28656 0.21504l-1.1874 4.156c-8e-3 0.0272-0.0115 0.0547-0.0115 0.0816-8e-3 0.0957 0.0745 0.29704 0.29715 0.29704 0.12913 0 0.24797-0.0849 0.28526-0.21541l1.1874-4.156c8e-3 -0.0276 0.0116-0.055 0.0116-0.082zm-2.2264 1.0312c0-0.16958-0.13869-0.29686-0.29686-0.29686-0.076 0-0.15195 0.029-0.20984 0.087l-1.039 1.039c-0.058 0.0661-0.087 0.14216-0.087 0.20987 0 0.0677 0.029 0.16049 0.087 0.21801l1.039 1.039c0.0577 0.0584 0.13382 0.0789 0.20988 0.0789 0.15817 0 0.29686-0.12737 0.29686-0.29686 0-0.076-0.029-0.15195-0.087-0.20993l-0.82911-0.82907 0.82916-0.82915c0.0582-0.0503 0.0869-0.12542 0.0869-0.20984zm4.3044 1.039c0-0.076-0.029-0.15195-0.087-0.20993l-1.039-1.039c-0.0577-0.0498-0.13382-0.0869-0.20989-0.0869-0.15816 0-0.29685 0.12737-0.29685 0.29686 0 0.076 0.029 0.15195 0.087 0.20993l0.82915 0.82916-0.82915 0.82916c-0.0582 0.066-0.087 0.14202-0.087 0.20974 0 0.16958 0.13869 0.29686 0.29685 0.29686 0.076 0 0.15196-0.029 0.20985-0.087l1.039-1.039c0.0583-0.0494 0.087-0.12546 0.087-0.20988z" fill="#fff"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="4.2333mm" height="4.2333mm" version="1.1" viewBox="0 0 4.2333 4.2333" xmlns="http://www.w3.org/2000/svg">
<g transform="translate(-139.71 -124.82)">
<g transform="matrix(.31672 0 0 .31672 75.72 -153.92)">
<g transform="matrix(1.3569 0 0 1.3569 -74.479 -286.98)">
<circle cx="208.71" cy="865.03" r="4.9253" fill="#292a2e" style="paint-order:stroke fill markers"/>
</g>
<path d="m211.76 887.29-0.13917-0.0804c-0.16563-0.0955-0.26458-0.26696-0.26458-0.45826 0-0.19129 0.099-0.36274 0.26458-0.45826l0.13917-0.0804c0.37994-0.2196 0.50985-0.70432 0.29051-1.0843l-0.26458-0.45826c-0.21881-0.37915-0.70538-0.50932-1.0843-0.29051l-0.13917 0.0802c-0.16563 0.0958-0.36381 0.0958-0.52917 0-0.16563-0.0958-0.26458-0.26697-0.26458-0.45826v-0.1606c0-0.43762-0.35613-0.79375-0.79375-0.79375h-0.52917c-0.43762 0-0.79375 0.35613-0.79375 0.79375v0.16086c0 0.1913-0.099 0.36248-0.26458 0.45826-0.16563 0.0955-0.36354 0.0958-0.52917 0l-0.13917-0.0804c-0.37888-0.21881-0.86545-0.0886-1.0845 0.29051l-0.26458 0.45826c-0.21934 0.37994-0.0894 0.86492 0.29051 1.0843l0.13944 0.0804c0.16563 0.0955 0.26458 0.26697 0.26458 0.45826 0 0.1913-0.099 0.36275-0.26458 0.45826l-0.13917 0.0804c-0.37995 0.21934-0.50986 0.70433-0.29052 1.0843l0.26459 0.45826c0.21907 0.37914 0.70564 0.50932 1.0843 0.29051l0.13917-0.0802c0.16563-0.096 0.36354-0.0955 0.52917 0 0.16563 0.0958 0.26458 0.26696 0.26458 0.45826v0.1606c0 0.43762 0.35613 0.79375 0.79375 0.79375h0.52917c0.43762 0 0.79375-0.35613 0.79375-0.79375v-0.16087c0-0.19129 0.099-0.36248 0.26458-0.45826 0.16536-0.0955 0.36354-0.0958 0.52917 0l0.13917 0.0804c0.37888 0.21854 0.86545 0.0884 1.0843-0.29051l0.26458-0.45826c0.21934-0.37994 0.0894-0.86493-0.29051-1.0843zm-3.0496 0.78423c-0.72945 0-1.3229-0.59346-1.3229-1.3229 0-0.72945 0.59346-1.3229 1.3229-1.3229 0.72946 0 1.3229 0.59346 1.3229 1.3229 0 0.72946-0.59346 1.3229-1.3229 1.3229z" fill="#9b9ea1"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="4.2333mm" height="4.2333mm" version="1.1" viewBox="0 0 4.2333 4.2333" xmlns="http://www.w3.org/2000/svg">
<g transform="translate(-139.71 -124.82)">
<g transform="matrix(.49451 0 0 .49451 38.616 -321.26)">
<g transform="matrix(.64049 0 0 .64049 75.033 338.39)">
<g transform="matrix(1.3569 0 0 1.3569 -74.479 -286.98)">
<circle cx="208.71" cy="865.03" r="4.9253" fill="#292a2e" style="paint-order:stroke fill markers"/>
</g>
</g>
<path d="m210.25 905.42a0.61713 0.61713 0 1 0-0.92831 0.53266c-0.0221 0.28041-0.19362 0.35678-0.7232 0.4669-0.17126 0.0355-0.34646 0.0718-0.50827 0.13432v-1.2169a0.61713 0.61713 0 1 0-0.61713 0v2.0174a0.61713 0.61713 0 1 0 0.62118 2e-3c0.023-0.17357 0.15429-0.23461 0.62929-0.33287 0.2645-0.0547 0.53787-0.11128 0.76929-0.25987 0.27964-0.17916 0.42939-0.45109 0.44704-0.80892a0.61713 0.61713 0 0 0 0.31011-0.53508zm-2.4685-0.9257a0.30857 0.30857 0 1 1-0.30856 0.30857 0.30857 0.30857 0 0 1 0.30856-0.30857zm0 3.7028a0.30857 0.30857 0 1 1 0.30857-0.30856 0.30857 0.30857 0 0 1-0.30857 0.30856zm1.8514-2.4685a0.30857 0.30857 0 1 1 0.30857-0.30857 0.30857 0.30857 0 0 1-0.30857 0.30857z" fill="#9b9ea1"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -1,142 +0,0 @@
<template>
<div id='banner1'>
<div class='border-card'></div>
<div class='content'>
<h3>Hello, I'm</h3>
<h1 class='name'>
{{ model.firstname.toUpperCase() }}
{{ model.lastname.toUpperCase() }}
</h1>
<h2 class='headline'>{{ model.headline.toUpperCase() }}</h2>
</div>
</div>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import BannerModel from '../model/banner';
@Component({})
export default class Banner1 extends Vue {
private model: any = BannerModel;
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped lang="scss">
@import '../global.scss';
$banner-size: 100vh;
$bg-color: #ededed;
// MEDIA QUERIES
// optimize vertical space
$mq-vert: 590px;
#banner1 {
flex: #{$banner-size * 0.999} 0 0;
display: flex;
position: relative;
top: 0;
left: 0;
width: 100%;
height: 100%;
flex-flow: row wrap;
justify-content: flex-start;
align-items: center;
color: #3d3d3d;
font-size: 1rem;
font-family: 'Source Sans Pro';
font-weight: 400;
white-space: nowrap;
background: $bg-color;
}
.border-card {
display: block;
position: absolute;
top: -20vw;
left: 70vw;
width: 60vw;
height: 60vw;
background: linear-gradient(45deg, #1a1a1a, #292929);
border-radius: 3rem;
transform: rotate(35deg);
}
.content {
flex: auto 0 1;
display: flex;
position: relative;
flex-flow: column nowrap;
margin: 0 10rem;
margin-bottom: 6rem;
h1 {
font-size: 4em;
font-weight: 700;
}
h2 {
font-size: 2.2em;
font-weight: 400;
}
h3 {
font-size: 2em;
font-weight: 400;
}
.name {
display: inline-block;
}
}
// not enough vertical space
@media screen and (max-height: 615px){
#banner1 {
align-items: flex-start;
.content {
margin-top: 2rem;
}
}
}
// not enough horizontal space
@media screen and (max-width: 740px){
#banner1 {
font-size: 2vw;
align-items: center;
justify-content: center;
.content {
margin: 0 2rem;
}
}
}
// not enough vertical space
@media screen and (max-height: 615px){
#banner1 {
font-size: 1vw;
}
}
// border-card overlaps with the text
@media screen and (max-width: 1010px){
.border-card {
left: 80vw;
}
}
</style>

View File

@ -1,203 +0,0 @@
<template>
<div id='banner2'>
<div class='content'>
<h2>Hello, I'm a <u>freelance developer</u></h2>
<br>
<br>
<h3>
I make your ideas into sites and
applications that work and <u>thrive</u>
out of the box.
</h3>
</div>
<div class='images'>
<div class='circle top'></div>
<div class='circle bottom'></div>
<div class='laptop'></div>
</div>
</div>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import BannerModel from '../model/banner';
@Component({})
export default class Banner2 extends Vue {
private model: any = BannerModel;
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped lang="scss">
@import '../global.scss';
$banner-size: 100vh;
$bg-color: #0e0e10;
// MEDIA QUERIES
// optimize vertical space
$mq-vert: 590px;
#banner2 {
flex: #{$banner-size * 0.999} 0 0;
display: flex;
position: relative;
top: 0;
left: 0;
width: 100%;
height: 100%;
flex-flow: row nowrap;
justify-content: flex-start;
align-items: center;
color: #fff;
font-size: 1rem;
font-family: 'Source Sans Pro';
font-weight: 400;
white-space: nowrap;
background: $bg-color;
}
.content {
display: block;
position: relative;
width: 37%;
white-space: normal;
margin: 0 10rem;
margin-bottom: 6rem;
h2, h3 {
display: inline;
}
u {
display: inline-block;
white-space: nowrap;
}
h2 {
font-size: 5em;
font-weight: 400;
}
h3 {
font-size: 3em;
font-weight: 400;
}
u {
position: relative;
font-weight: 800;
text-decoration: none;
z-index: 0;
@keyframes grow-right {
from{ width: 0%; }
to{ width: 102%; }
}
&:before {
content: '';
display: block;
position: absolute;
top: 70%;
left: -1%;
width: 0%;
height: .2em;
background: #5768f8;
z-index: -1;
animation: .2s .5s ease-in-out grow-right forwards;
}
}
h3 u:before {
animation-delay: 1s;
}
}
.images {
flex: auto 1 1;
position: relative;
width: 50vw;
height: 50vh;
background: radial-gradient(rgba(87, 104, 248, 0.5) 0%, transparent 70%);
}
@keyframes scale-up {
0% { transform: scale(0); }
100% { transform: scale(1); }
}
// gradient circles around the leftmost center point
$focus-point-x: 50%;
$focus-point-y: 50%;
.circle,
.circle.bottom {
display: block;
position: absolute;
top: calc( #{$focus-point-y} - 5vw );
left: calc( #{$focus-point-x} - 5vw );
width: 50vw;
height: 50vw;
border-radius: 50% / 50%;
background: linear-gradient(90deg, #6063f8, #6431f5);
transform: scale(0);
animation: .2s 1.7s ease-in-out scale-up forwards;
}
.circle.top {
top: calc( #{$focus-point-y} - 20vw );
left: calc( #{$focus-point-x} - 20vw );
width: 20vw;
height: 20vw;
background: linear-gradient(90deg, #3d4ba3, #57f2f8);
animation-delay: 1s;
}
.laptop {
position: absolute;
top: $focus-point-y;
left: $focus-point-x;
width: 40vw;
height: 40vw;
transform: translateX(-50%) translateY(-50%);
background: url('../assets/laptop.svg');
background-size: contain;
background-repeat: no-repeat;
}
// not enough horizontal space
@media screen and (max-width: 1000px){
#banner2 {
font-size: 1vw;
}
}
// not enough vertical space
@media screen and (max-height: 615px){
#banner2 {
font-size: .8vw;
}
}
</style>

View File

@ -4,44 +4,6 @@
<div class='wave'></div>
<div class='wave w2'></div>
<div id='banner-container'>
<Banner1 v-if='theme == `black_white`' />
<Banner2 v-if='theme == `blue`' />
<Banner2 v-if='theme == `orange`' />
</div>
<form class='themes' @change="onThemeSelected" ref='theme_form'>
<h2>THEMES</h2>
<template v-for='t in themes'>
<input :key='`${t.name}-input`' type='radio' name='theme' :id='t.name' :value='t.name' :checked='t.default'/>
<label :key='`${t.name}-label`' :for='t.name'>
<svg width="16" height="16" version="1.1" viewBox="0 0 4.2333 4.2333" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id='blue-gradient'>
<stop offset='0' style='stop-color: var(--color0); stop-opacity: 1'/>
<stop offset='1' style='stop-color: var(--color1); stop-opacity: 1'/>
</linearGradient>
<linearGradient id='orange-gradient'>
<stop offset='0' style='stop-color: var(--color0); stop-opacity: 1'/>
<stop offset='1' style='stop-color: var(--color1); stop-opacity: 1'/>
</linearGradient>
<linearGradient id='green-gradient'>
<stop offset='0' style='stop-color: var(--color0); stop-opacity: 1'/>
<stop offset='1' style='stop-color: var(--color1); stop-opacity: 1'/>
</linearGradient>
</defs>
<g transform="translate(-157.14 -77.615)">
<g transform="matrix(0 -.11388 -.11388 0 185.25 96.78)" stroke="#1a1a1a" stroke-width="11.855">
<path d="m168.29 228.24c0 10.195-8.3886 18.587-18.585 18.587s-18.59-8.3921-18.59-18.587c0-10.195 8.3931-18.587 18.59-18.587s18.585 8.3921 18.585 18.587zm-5.9273 0c0-6.9441-5.7129-12.661-12.658-12.661s-12.662 5.7168-12.662 12.661c0 6.9441 5.7175 12.661 12.662 12.661s12.658-5.7168 12.658-12.661z" color="#000000" fill="#1a1a1a" stroke="none" style="paint-order:stroke fill markers"/>
<circle cx="149.71" cy="228.24" r="6.4803" fill="#1a1a1a" stroke="none" style="paint-order:stroke fill markers"/>
</g>
</g>
</svg>
</label>
</template>
</form>
</div>
</template>
@ -49,26 +11,13 @@
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import { Themes, Theme } from '@/model/themes';
import Banner1 from '@/components/Banner1.vue';
import Banner2 from '@/components/Banner2.vue';
@Component({
components: {
Banner1,
Banner2,
},
components: {},
})
export default class Home extends Vue {
private themes: Theme[] = Themes;
private theme: string = Themes[0].name;
private onThemeSelected() {
const fd = new FormData(this.$refs.theme_form as HTMLFormElement);
console.log(fd);
this.theme = fd.get('theme') as string;
console.log(this.theme);
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
@ -78,17 +27,6 @@
$wave-height: 6.3rem;
// horizontal spacing between theme chips
$theme-chip-spacing: 1rem;
// full size of a theme chip
$theme-chip-size: 1.8rem;
// empty space inside the chip (relative to the chip size)
$theme-chip-void: 1.2rem;
// size of the inner (checked) circle (relative to the chip void)
$theme-chip-inner: .6rem;
#home {
position: relative;
top: 0;
@ -96,7 +34,7 @@
width: $width;
height: $height;
background: #ededed;
background: #07142d;
}
@keyframes wave-x {
@ -127,98 +65,4 @@
z-index: 900;
}
.themes {
display: flex;
position: relative;
top: calc( #{$height} - #{$wave-height} );
width: $width;
flex-flow: row wrap;
justify-content: center;
align-items: center;
transform: translateY(-100%);
input[type=radio]{
display: none;
}
h2 {
flex: 100%;
text-align: center;
margin-bottom: 1rem;
}
& > label {
display: flex;
position: relative;
width: $theme-chip-size;
height: $theme-chip-size;
justify-content: center;
align-items: center;
margin: 0 #{$theme-chip-spacing};
cursor: pointer;
transition: transform .2s ease-in;
svg {
width: 100%;
height: 100%;
}
svg circle {
opacity: 0;
transition: opacity .1s ease-in-out;
}
}
label:nth-child(2) { margin-left: 0; }
label:last-child { margin-right: 0; }
input:checked + label svg circle {
opacity: 1;
}
// define our custom svg gradients
#blue-gradient {
--color0: #3d4ba3;
--color1: #5768f8;
}
#orange-gradient {
--color0: #fcb67b;
--color1: #c54556;
}
#green-gradient {
--color0: #00d89f;
--color1: #00a2d8;
}
input[value='black_white'] + label {
circle, path{ fill: #1a1a1a; }
}
input[value='blue'] + label {
circle, path{ fill: url(#blue-gradient); }
}
input[value='forest'] + label {
circle, path{ fill: url(#orange-gradient); }
}
input[value='glass'] + label {
circle, path{ fill: url(#green-gradient); }
}
z-index: 1000;
}
#banner-container {
position: absolute;
top: 0;
left: 0;
width: $width;
height: $height;
}
</style>

View File

@ -1,183 +0,0 @@
<template>
<div id='skills'>
<div class='skillset' v-for='(skillset, si) of skills' :key='si'>
<div class='title'>
{{ skillset.title.toUpperCase() }}
</div>
<div class='subtitle'>
<span v-for='keyword of skillset.keywords' :key='keyword'>{{ keyword }}</span>
</div>
<div class='stack'>
<template v-for='(skill,i) of skillset.skills'>
<a class='label' :key='`${i}-link`' :href='skill.link' v-html='skill.label'></a>
<span class='level' :key='`${i}-span`'>
<span :style='{ width: (skill.level*100)+"%" }'></span>
</span>
</template>
</div>
</div>
</div>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import SkillsModel from '../model/skills';
@Component
export default class Skills extends Vue {
private skills = SkillsModel;
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped lang="scss">
@import '../global';
#skills {
flex: auto 0 1;
display: flex;
position: relative;
margin: 0 5%;
margin-top: 3em;
flex-flow: row wrap;
justify-content: space-between;
align-items: flex-start;
.skillset {
flex: 30%-(2*3%);
display: flex;
position: relative;
margin: 0 3%;
flex-flow: column nowrap;
justify-content: flex-start;
align-items: center;
.title {
flex: auto 0 1;
display: block;
position: relative;
padding: .2em 1em;
margin-bottom: .2em;
font-size: 1.5rem;
font-weight: normal;
border-bottom: 2px solid #ccc;
}
.subtitle {
flex: auto 0 1;
display: flex;
position: relative;
flex-flow: row nowrap;
justify-content: space-between;
align-items: center;
color: #ccc;
font-size: 1rem;
font-weight: normal;
span{
padding-left: .5em;
&:not(:last-child):after{
content: ' / ';
color: #aaa;
}
}
}
.stack {
flex: auto;
display: flex;
position: relative;
margin: 1rem;
min-width: 15rem;
flex-flow: row wrap;
justify-content: space-between;
align-items: center;
.label{
flex: 60%;
display: inline-block;
position: relative;
padding: .2rem 0;
font-size: 1.1rem;
font-weight: normal;
}
.level{
flex: 40%;
display: block;
position: relative;
padding: .2rem 0;
height: .5rem;
border-radius: .25rem / .25rem;
overflow: hidden;
background-color: #e6e6e6;
@keyframes fillIn {
from { width: 50%; }
}
span {
display: block;
position: absolute;
top: 0;
left: 0;
width: 0;
height: 100%;
background-size: 100% 100%;
animation: 1s ease fillIn;
}
}
}
}
.skillset:nth-of-type(3n+1) .stack .level span{
@include gradient1;
}
// .skillset:nth-of-type(3n+1),
// .skillset:nth-of-type(3n+3) {
// margin-top: 5rem;
// }
.skillset:nth-of-type(3n+2) .stack .level span{
@include gradient2;
}
.skillset:nth-of-type(3n+3) .stack .level span{
@include gradient3;
}
}
</style>
<style lang="scss">
#skills .skillset .stack .label i {
font-style: normal;
opacity: 0.3;
}
</style>

317
src/components/Timeline.vue Normal file
View File

@ -0,0 +1,317 @@
<template>
<div id='timeline'>
<template class='project' v-for='(proj) of projects'>
<div :key="'start-'+proj.name" class='start'>
{{ proj.started_at | short_date }}
</div>
<img :key="'name-icon-'+proj.name" class='name-icon' src='../assets/timeline/project.svg'/>
<div :key="'name-'+proj.name" class='name'>
Created <b>{{ proj.name }}</b> <span>{{ proj.started_at | date_diff }} ago</span>
</div>
<img :key="'skill-icon-'+proj.name" class='skill-icon' src='../assets/timeline/skills.svg' />
<div :key="'skillset-'+proj.name" class='skillset'>
<span v-for='(skill) of proj.skills' :key='skill' v-html='skills[skill].name'></span>
</div>
<img :key="'desc-icon-'+proj.name" class='desc-icon' src='../assets/timeline/info.svg' />
<div :key="'desc-'+proj.name" class='desc' v-html='proj.info'></div>
<template v-if='proj.source != null'>
<img :key="'src-icon-'+proj.name" class='src-icon' src='../assets/timeline/src.svg' />
<div :key="'src-'+proj.name" class='src' >
Hosted at <a :href='proj.source.link'>{{ proj.source.name }}</a> <span>({{ proj.commits }} commits)</span>
</div>
</template>
<template v-if='proj.doc != null'>
<img :key="'doc-icon-'+proj.name" class='doc-icon' src='../assets/timeline/doc.svg' />
<div :key="'doc-'+proj.name" class='doc'>
Documentation at <a :href='proj.doc.link'>{{ proj.doc.name }}</a>
</div>
</template>
<img :key="'end-icon-'+proj.name" class='end-icon' src='../assets/timeline/activity.svg' />
<div :key="'end-'+proj.name" class='end'>
<template v-if='proj.stopped_at != null'>
Project stopped in {{ proj.stopped_at | short_date }} <span>{{ proj.stopped_at | date_diff }} ago</span>
</template>
<template v-else>
Project still active
</template>
</div>
</template>
</div>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import { Projects, Project } from '../model/projects';
import { Skills, tSkills } from '../model/skills';
function pluralize(n: number, s: string): string {
n = Math.floor(Math.abs(n));
const plural = (n > 1);
switch(s){
case 'second':
return plural ? `${n} seconds` : `1 second`;
case 'minute':
return plural ? `${n} minutes` : `1 minute`;
case 'day':
return plural ? `${n} days` : `1 day`;
case 'month':
return plural ? `${n} months` : `1 month`;
case 'year':
return plural ? `${n} years` : `1 year`;
}
return "";
}
@Component({
filters: {
short_date: function(date: Date): string {
return date.toLocaleDateString('en-US', { month: 'short', year: 'numeric' });
},
date_diff: function(date: Date): string {
const minute = 60*1000;
const hour = 60*minute;
const day = 24*hour;
const month = 30*day;
const year = 365*day;
let now = new Date();
let diff = now.getTime() - date.getTime();
if( diff < 0 ){
return "sometime";
}
if( diff < minute ){
return pluralize(diff, 'second');
}
if( diff < hour ){
return pluralize(diff/minute, 'minute');
}
if( diff < day ){
return pluralize(diff/hour, 'hour');
}
if( diff < month ){
return pluralize(diff/day, 'day');
}
if( diff < year ){
return pluralize(diff/month, 'month');
}
return pluralize(diff/year, 'year');
}
}
})
export default class Timeline extends Vue {
private projects: Project[] = Projects;
private skills: tSkills = Skills;
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped lang="scss">
@import '../global';
$icon-width: 2.3rem;
$space-width: 1rem;
#timeline {
display: grid;
position: relative;
width: 100vw;
min-height: 100vh;
grid-template-columns: auto $icon-width $icon-width $space-width auto;
grid-gap: .5em;
font-size: 1.6rem;
font-weight: 400;
color: #fff;
background: #161619;
padding: 0 5rem;
padding-top: 2em;
.start { grid-column: 1 / 2; }
.name { grid-column: 3 / 6; }
.skillset, .desc, .src, .doc, .end {
grid-column: 5 / 6;
}
.name-icon { grid-column: 2 / 3; }
.skill-icon, .desc-icon, .src-icon, .doc-icon, .end-icon {
grid-column: 3 / 4;
}
.skill-icon, .desc-icon, .src-icon, .doc-icon, .end-icon {
position: relative;
width: #{$icon-width + .3rem};
height: #{$icon-width + .3rem};
margin-top: .3rem;
background: #161619;
}
.src-icon, .doc-icon, .end-icon {
position: relative;
width: #{$icon-width + .1rem};
height: #{$icon-width + .1rem};
margin-top: .1rem;
background: #161619;
}
.start {
font-size: .8em;
color: #535359;
padding-top: .3em;
}
.name-icon {
position: relative;
width: #{$icon-width + .2rem};
height: #{$icon-width + .2rem};
padding-top: -.2rem;
background: #161619;
}
.name, .end {
b {
color: #2c55cf;
}
span {
position: relative;
font-size: .8em;
color: #4d4d4d;
margin-left: .5em;
&:before {
content: '';
display: inline-block;
position: relative;
margin-bottom: .15em;
margin-right: .5em;
width: .3em;
height: .3em;
background: #606060;
border-radius: 50% / 50%;
}
}
}
.name {
margin-bottom: 1em;
}
.skillset {
display: flex;
flex-flow: row wrap;
justify-content: flex-start;
align-items: flex-start;
span {
font-size: .8em;
padding: .4em .6em;
margin-right: .4em;
margin-bottom: .4em;
color: #fff;
background: #28282d;
border-radius: .4em / .4em;
user-select: none;
}
}
.desc {
display: block;
position: relative;
width: 50vw;
font-size: .8em;
padding: 1em;
padding-bottom: 2em;
margin-bottom: 1.5em;
background: #28282d;
border: .1rem solid #2f2f33;
border-radius: .4em / .4em;
z-index: 100;
transform-style: preserve-3d;
&:before, &:after {
content: '';
display: block;
position: absolute;
top: .6em;
left: .6em;
width: 100%;
height: 100%;
background: #202024;
border: .1rem solid #2f2f33;
border-radius: .4em / .4em;
transform: translateZ(-1px);
}
&:before {
top: 1.2em;
left: 1.2em;
}
}
.src, .doc {
color: #999999;
a {
display: inline-block;
position: relative;
color: #fff;
&:after {
content: '';
display: block;
position: absolute;
margin-left: 0;
width: 100%;
height: .15rem;
background: #3333be;
}
}
span {
margin-left: .5em;
color: #4d4d4d;
}
}
.end {
margin-bottom: 2em;
}
}
</style>

73
src/model/projects.ts Normal file
View File

@ -0,0 +1,73 @@
import { tID as s } from './skills';
export interface Link {
name: string;
link: string;
}
export interface Project {
name: string;
skills: s[];
client: string|null;
started_at: Date;
stopped_at: Date|null;
info: string;
source: Link|null;
doc: Link|null;
commits: number;
}
export const Projects: Project[] = [
{
name: 'Angular module system',
client: 'Marlink',
skills: [s.Angular, s.RnD, s.Concurrency, s.Html, s.Css, s.Inkscape, s.UIUX, s.Ts, s.Js, s.Ajax, s.Cordova, s.Bash, s.Git, s.Rest, s.Rpm, s.Vue, s.Web, s.Docker],
started_at: new Date(2019, 10, 26),
stopped_at: new Date(2021, 4, 11),
info: 'Prototype for a front-end Angular modular system. I technically and visually designed a kind of app store. Hacked angular to allow injecting custom pages and code on-the-go.',
source: null, doc: null,
commits: 0, // ???
},
{
name: 'EarthMap',
client: 'Collins Aerospace',
skills: [s.Qt, s.RnD, s.Cpp, s.Concurrency, s.Git, s.Css, s.Inkscape, s.UIUX],
started_at: new Date(2019, 10, 26),
stopped_at: new Date(2021, 4, 11),
info: 'TODO',
source: null, doc: null,
commits: 840,
},
{
name: 'ADSBOnPED',
client: 'Collins Aerospace',
skills: [s.Qt, s.RnD, s.Cpp, s.Concurrency, s.Git, s.Css, s.Bash, s.Linux, s.Inkscape, s.UIUX],
started_at: new Date(2020, 7, 16),
stopped_at: null,
info: 'TODO',
source: null, doc: null,
commits: 1897 + 62,
},
{
name: 'EasyCom',
client: 'Medwin | Vygon',
skills: [s.Go, s.Rest, s.Concurrency, s.Git, s.Bash, s.Linux, s.Docker, s.Crypto, s.Postgres, s.Opti, s.Websocket, s.Inkscape, s.UIUX],
started_at: new Date(2021, 1, 6),
stopped_at: new Date(2022, 11, 1),
info: 'TODO',
source: null, doc: null,
commits: 1260+90 + 41+1 + 12+1 + 14,
},
{
name: 'aicra',
client: null,
skills: [s.Go, s.Web, s.Rest, s.OpenSource, s.Git, s.Inkscape],
started_at: new Date(2018, 5, 19),
stopped_at: null, // still active
info: 'Started as a personal go library to ease REST API development. The main goal is to greatly enhance maintainability by providing a single configuration file that describes every endpoint of the API. Everything that can be automated is while staying idiomatic to go. Aicra automates routing, parameter validation and extraction, permission management, response output formatting. I then used it for a professional project in the medical field after corporate evaluation and audit.',
source: { name: 'github.com/xdrm-io/aicra', link: 'https://github.com/xdrm-io/aicra' },
doc: { name: 'pkg.go.dev/github.com/xdrm-io/aicra', link: 'https://pkg.go.dev/github.com/xdrm-io/aicra' },
commits: 535,
},
];

View File

@ -1,41 +1,290 @@
export default [
{
title: 'Web',
keywords: [ 'languages', 'technologies' ],
skills: [
{ label: 'MariaDB', link: 'https://mariadb.org/', level: 0.6 },
{ label: 'Postgres', link: 'https://postgresql.org/', level: 0.8 },
{ label: 'MongoDB', link: 'https://mongodb.com/', level: 0.5 },
{ label: 'Vue <i>(.js)</i>', link: 'https://vuejs.org/', level: 0.8 },
{ label: 'Angular <i>(7+)</i>', link: 'https://angular.io/', level: 0.3 },
{ label: 'WebGL', link: 'https://www.khronos.org/webgl/', level: 0.3 },
{ label: 'Audio API', link: 'https://webaudio.github.io/web-audio-api/', level: 0.5 },
{ label: 'Websocket', link: 'https://tools.ietf.org/html/rfc6455', level: 1 },
],
export enum tID {
MariaDB,
Postgres,
Mongo,
Vue,
Angular,
Parcel,
Cordova,
Webpack,
WebGL,
AudioAPI,
Websocket,
Docker,
Bash,
Linux,
Systemd,
Git,
Rpm,
RaspBerry,
Arduino,
Php,
Html,
Css,
Js,
Ajax,
Ts,
C,
Cpp,
Python,
Go,
Qt,
OpenSource,
Electronics,
Web,
Rest,
Crypto,
ImageProcessing,
AI,
DeepLearning,
NeuralNetwork,
Opti,
Sockets,
Concurrency,
UIUX,
Inkscape,
RnD,
}
interface tSkill {
icon: string;
name: string;
link: string;
}
export type tSkills = { [id in tID]: tSkill };
export const Skills: tSkills = {
[tID.MariaDB]: {
name: 'MariaDB',
link: 'https://mariadb.org',
icon: 'skills/mariadb.svg',
},
{
title: 'Sys',
keywords: ['languages', 'technologies'],
skills: [
{ label: 'Linux/bash', link: 'https://www.gnu.org/software/bash/', level: 1 },
{ label: 'docker', link: 'https://www.docker.com/', level: 0.8 },
{ label: 'systemd', link: 'https://freedesktop.org/wiki/Software/systemd/', level: 1 },
{ label: 'git', link: 'https://git-scm.com/', level: 0.8 },
{ label: 'raspberry', link: 'https://www.raspberrypi.org/', level: 0.8 },
{ label: 'arduino', link: 'https://www.arduino.cc/', level: 0.5 },
{ label: 'RPM packaging', link: 'https://rpm.org/', level: 0.7 },
],
[tID.Postgres]: {
name: 'PostgreSQL',
link: 'https://postgresql.org',
icon: 'skills/postgres.svg',
},
{
title: 'Misc',
keywords: ['frameworks', 'api', 'dependencies'],
skills: [
{ label: 'PHP', link: 'https://www.php.net/', level: 0.9 },
{ label: 'HTML<i>/</i>CSS', link: 'https://www.w3.org/standards/webdesign/htmlcss', level: 1 },
{ label: 'Typescript', link: 'https://www.typescriptlang.org/', level: 0.6 },
{ label: 'C<i>/</i>C++', link: 'https://isocpp.org/', level: 0.8 },
{ label: 'Python', link: 'https://www.python.org/', level: 0.3 },
{ label: 'go', link: 'https://golang.org/', level: 1 },
],
[tID.Mongo]: {
name: 'MongoDB',
link: 'https://mongodb.com',
icon: 'skills/mongo.svg',
},
];
[tID.Vue]: {
name: 'Vue <i>(.js)</i>',
link: 'https://vuejs.org',
icon: 'skills/vue.svg',
},
[tID.Angular]: {
name: 'Angular <i>(7+)</i>',
link: 'https://angular.io',
icon: 'skills/angular.svg',
},
[tID.Parcel]: {
name: 'Parcel',
link: 'https://parceljs.org/',
icon: 'skills/parcel.svg',
},
[tID.Cordova]: {
name: 'Apache Cordova',
link: 'https://cordova.apache.org/',
icon: 'skills/cordova.svg',
},
[tID.Webpack]: {
name: 'Webpack',
link: 'https://webpack.js.org/',
icon: 'skills/webpack.svg',
},
[tID.WebGL]: {
name: 'WebGL',
link: 'https://www.khronos.org/webgl/',
icon: 'skills/web-gl.svg',
},
[tID.AudioAPI]: {
name: 'Audio API',
link: 'https://webaudio.github.io/web-audio-api/',
icon: 'skills/audio-api.svg',
},
[tID.Websocket]: {
name: 'Websocket',
link: 'https://tools.ietf.org/html/rfc6455',
icon: 'skills/websocket.svg',
},
[tID.Docker]: {
name: 'Docker',
link: 'https://docker.com',
icon: 'skills/docker.svg',
},
[tID.Bash]: {
name: 'bash',
link: 'https://www.gnu.org/software/bash/',
icon: 'skills/bash.svg',
},
[tID.Linux]: {
name: 'GNU/Linux',
link: 'https://www.linux.org',
icon: 'skills/linux.svg',
},
[tID.Systemd]: {
name: 'systemd',
link: 'https://freedesktop.org/wiki/Software/systemd/',
icon: 'skills/systemd.svg',
},
[tID.Git]: {
name: 'Git',
link: 'https://git-scm.com/',
icon: 'skills/git.svg',
},
[tID.Rpm]: {
name: 'RPM packaging',
link: 'https://rpm.org/',
icon: 'skills/rpm.svg',
},
[tID.RaspBerry]: {
name: 'Raspberry',
link: 'https://raspberrypi.org',
icon: 'skills/raspberry.svg',
},
[tID.Arduino]: {
name: 'Arduino',
link: 'https://arduino.cc',
icon: 'skills/arduino.svg',
},
[tID.Php]: {
name: 'PHP',
link: 'https://www.php.net',
icon: 'skills/php.svg',
},
[tID.Html]: {
name: 'HTML5',
link: 'https://www.w3.org/standards/webdesign/htmlcss',
icon: 'skills/html.svg',
},
[tID.Css]: {
name: 'CSS3',
link: 'https://www.w3.org/standards/webdesign/htmlcss',
icon: 'skills/css.svg',
},
[tID.Js]: {
name: 'Javascript',
link: 'http://www.ecma-international.org/publications-and-standards/standards/ecma-262/',
icon: 'skills/js.svg',
},
[tID.Ajax]: {
name: 'AJAX',
link: 'https://www.w3schools.com/xml/ajax_intro.asp',
icon: 'skills/ajax.svg',
},
[tID.Ts]: {
name: 'Typescript',
link: 'https://www.typescript.org/',
icon: 'skills/ts.svg',
},
[tID.C]: {
name: 'C (lang)',
link: 'https://www.open-std.org/jtc1/sc22/wg14/',
icon: 'skills/c.svg',
},
[tID.Cpp]: {
name: 'C++',
link: 'https://isocpp.org/',
icon: 'skills/cpp.svg',
},
[tID.Python]: {
name: 'Python',
link: 'https://python.org/',
icon: 'skills/python.svg',
},
[tID.Go]: {
name: 'Go (lang)',
link: 'https://go.dev',
icon: 'skills/go.svg',
},
[tID.Qt]: {
name: 'Qt',
link: 'https://qt.io',
icon: 'skills/qt.svg',
},
[tID.OpenSource]: {
name: 'Open-source',
link: 'https://opensource.org/',
icon: 'skills/open-source.svg',
},
[tID.Electronics]: {
name: 'Electronics',
link: 'https://en.wikipedia.org/wiki/Electronics',
icon: 'skills/electronics.svg',
},
[tID.Web]: {
name: 'Web',
link: 'https://en.wikipedia.org/wiki/World_Wide_Web',
icon: 'skills/web.svg',
},
[tID.Rest]: {
name: 'REST',
link: 'https://en.wikipedia.org/wiki/Representational_state_transfer',
icon: 'skills/rest.svg',
},
[tID.Crypto]: {
name: 'Cryptography',
link: 'https://en.wikipedia.org/wiki/Cryptography',
icon: 'skills/crypto.svg',
},
[tID.ImageProcessing]: {
name: 'Image processing',
link: 'https://en.wikipedia.org/wiki/Digital_image_processing',
icon: 'skills/image-processing.svg',
},
[tID.AI]: {
name: 'Artificial Intelligence',
link: 'https://en.wikipedia.org/wiki/Artificial_intelligence',
icon: 'skills/ai.svg',
},
[tID.DeepLearning]: {
name: 'Deep Learning',
link: 'https://en.wikipedia.org/wiki/Deep_learning',
icon: 'skills/deep-learning.svg',
},
[tID.NeuralNetwork]: {
name: 'Neural Networks',
link: 'https://en.wikipedia.org/wiki/Artificial_neural_network',
icon: 'skills/neural-network.svg',
},
[tID.Opti]: {
name: 'Program optimization',
link: 'https://en.wikipedia.org/wiki/Program_optimization',
icon: 'skills/opti.svg',
},
[tID.Sockets]: {
name: 'Sockets',
link: 'https://en.wikipedia.org/wiki/Computer_network_programming',
icon: 'skills/sockets.svg',
},
[tID.Concurrency]: {
name: 'Concurrent programming',
link: 'https://en.wikipedia.org/wiki/Concurrent_computing',
icon: 'skills/concurrency.svg',
},
[tID.UIUX]: {
name: 'UI/UX',
link: 'https://en.wikipedia.org/wiki/UX',
icon: 'skills/ux.svg',
},
[tID.Inkscape]: {
name: 'Inkscape',
link: 'https://inkscape.org/',
icon: 'skills/inkscape.svg',
},
[tID.RnD]: {
name: 'R&D',
link: 'https://en.wikipedia.org/wiki/R&D',
icon: 'skills/rnd.svg',
},
};