🎨 style(new!): add new ui vue.js

Signed-off-by: Abhiraj Roy <157954129+iconized@users.noreply.github.com>
This commit is contained in:
Abhiraj Roy
2024-06-05 04:26:16 +05:30
parent 4878325171
commit 5f4ddc3090
110 changed files with 9442 additions and 0 deletions

27
pages/README.md Normal file
View File

@@ -0,0 +1,27 @@
# `pages/` [Directory](https://nuxt.com/docs/getting-started/views#pages)
Pages represent views use for each specific route pattern. Every file in the `pages/` directory represents a different route displaying its content.
To use pages, create pages/index.vue file and add `<NuxtPage />` component to the *app.vue* (or remove *app.vue* for default entry). You can now create more pages and their corresponding routes by adding new files in the `pages/` directory.
*pages/index.vue*
````html
<template>
<div>
<h1>Welcome to the homepage</h1>
<AppAlert>
This is an auto-imported component
</AppAlert>
</div>
</template>
````
*pages/about.vue*
````html
<template>
<section>
<p>This page will be displayed at the /about route.</p>
</section>
</template>
````

334
pages/about-me.vue Normal file
View File

@@ -0,0 +1,334 @@
<template>
<main v-if="!loading" id="about-me" class="page">
<div id="mobile-page-title">
<h2>_about-me</h2>
</div>
<div id="page-menu" class="w-full flex">
<!-- DESKTOP section icons -->
<div id="sections">
<div id="section-icon" v-for="section in config.dev.about.sections" :key="section.title" :class="{ active: isSectionActive(section.title)}">
<img :id="'section-icon-' + section.title" :src="section.icon" :alt="section.title + '-section'" @click="focusCurrentSection(section)">
</div>
</div>
<!-- focused section content -->
<div id="section-content" class="hidden lg:block w-full h-full border-right">
<!-- title -->
<div id="section-content-title" class="hidden lg:flex items-center min-w-full">
<img id="section-arrow-menu" src="/icons/arrow.svg" alt="" class="section-arrow mx-3 open">
<p v-html="config.dev.about.sections[currentSection].title" class="font-fira_regular text-white text-sm"></p>
</div>
<!-- folders -->
<div>
<div v-for="(folder, key, index) in config.dev.about.sections[currentSection].info" :key="key" class="grid grid-cols-2 items-center my-2 font-fira_regular text-menu-text" @click="focusCurrentFolder(folder)">
<div class="flex col-span-2 hover:text-white hover:cursor-pointer">
<img id="diple" src="/icons/diple.svg" alt="" :class="{ open: isOpen(folder.title)}">
<img :src="'/icons/folder' + (index+1) + '.svg'" alt="" class="mr-3">
<p :id="folder.title" v-html="key" :class="{ active: isActive(folder.title)}"></p>
</div>
<div v-if="folder.files !== undefined" class="col-span-2">
<div v-for="(file, key) in folder.files" :key="key" class="hover:text-white hover:cursor-pointer flex my-2">
<img src="/icons/markdown.svg" alt="" class="ml-8 mr-3"/>
<p >{{ key }}</p>
</div>
</div>
</div>
</div>
<!-- contact -->
<div id="section-content-title-contact" class="flex items-center min-w-full border-top">
<img id="section-arrow-menu" src="/icons/arrow.svg" alt="" class="section-arrow mx-3 open">
<p v-html="config.dev.contacts.direct.title" class="font-fira_regular text-white text-sm"></p>
</div>
<div id="contact-sources" class="hidden lg:flex lg:flex-col my-2">
<div v-for="(source, key) in config.dev.contacts.direct.sources" :key="key" class="flex items-center mb-2">
<img :src="'/icons/' + key + '.svg'" alt="" class="mx-4">
<a v-html="source" href="/" class="font-fira_retina text-menu-text hover:text-white"></a>
</div>
</div>
</div>
<!-- mobile -->
<div id="section-content" class="lg:hidden w-full font-fira_regular">
<div v-for="section in config.dev.about.sections" :key="section.title">
<!-- section title (mobile) -->
<div :key="section.title" :src="section.icon" id="section-content-title" class="flex lg:hidden mb-1" @click="focusCurrentSection(section)">
<img src="/icons/arrow.svg" :id="'section-arrow-' + section.title" alt="" class="section-arrow">
<p v-html="section.title" class=" text-white text-sm"></p>
</div>
<!-- folders -->
<div :id="'folders-' + section.title" class="hidden"> <!-- <div :id="'folders-' + section.title" :class="currentSection == section.title ? 'block' : 'hidden'"> -->
<div v-for="(folder, key, index) in config.dev.about.sections[section.title].info" :key="key" class="grid grid-cols-2 items-center my-2 font-fira_regular text-menu-text hover:text-white hover:cursor-pointer" @click="focusCurrentFolder(folder)">
<div class="flex col-span-2">
<img id="diple" src="/icons/diple.svg">
<img :src="'icons/folder' + (index+1) + '.svg'" alt="" class="mr-3">
<p :id="folder.title" v-html="key" :class="{ active: isActive(folder.title)}"></p>
</div>
<div v-if="folder.files !== undefined" class="col-span-2">
<div v-for="(file, key) in folder.files" :key="key" class="hover:text-white hover:cursor-pointer flex my-2">
<img src="/icons/markdown.svg" alt="" class="ml-8 mr-3"/>
<p >{{ key }}</p>
</div>
</div>
</div>
</div>
</div>
<!-- section content title -->
<div id="section-content-title" class="flex items-center min-w-full" @click="showContacts()">
<img src="/icons/arrow.svg" alt="" id="section-arrow" class="section-arrow">
<p v-html="config.dev.contacts.direct.title" class="font-fira_regular text-white text-sm"></p>
</div>
<!-- section content folders -->
<div id="contacts" class="hidden">
<div v-for="(source, key) in config.dev.contacts.direct.sources" :key="key" class="flex items-center my-2">
<img :src="'/icons/' + key + '.svg'" alt="">
<a v-html="source" href="/" class="font-fira_retina text-menu-text hover:text-white ml-4"></a>
</div>
</div>
</div>
</div>
<!-- MENU END -->
<!-- content -->
<div class="flex flex-col lg:grid lg:grid-cols-2 h-full w-full">
<div id="left" class="w-full flex flex-col border-right">
<!-- windows tab desktop -->
<div class="tab-height w-full hidden lg:flex border-bot items-center">
<div class="flex items-center border-right h-full">
<p v-html="config.dev.about.sections[currentSection].title" class="font-fira_regular text-menu-text text-sm px-3"></p>
<img src="/icons/close.svg" alt="" class="mx-3">
</div>
</div>
<!-- windows tab mobile -->
<div id="tab-mobile" class="flex lg:hidden font-fira_retina">
<span class="text-white">// </span>
<h3 v-html="config.dev.about.sections[currentSection].title" class="text-white px-2"></h3>
<span class="text-menu-text"> / </span>
<h3 v-html="config.dev.about.sections[currentSection].info[folder].title" class="text-menu-text pl-2"></h3>
</div>
<!-- text -->
<div id="commented-text" class="flex h-full w-full lg:border-right overflow-hidden">
<div class="w-full h-full ml-5 mr-10 lg:my-5 overflow-scroll">
<CommentedText :text="config.dev.about.sections[currentSection].info[folder].description" />
</div>
<!-- scroll bar -->
<div id="scroll-bar" class="h-full border-left hidden lg:flex justify-center py-1">
<div id="scroll">
</div>
</div>
</div>
</div>
<div id="right" class="max-w-full flex flex-col">
<!-- windows tab -->
<div class="tab-height w-full h-full hidden lg:flex border-bot items-center">
</div>
<!-- windows tab mobile -->
<div class="tab-height w-full h-full flex-none lg:hidden items-center">
</div>
<div id="gists-content" class="flex">
<div id="gists" class="flex flex-col lg:px-6 lg:py-4 w-full overflow-hidden">
<!-- title -->
<h3 class="text-white lg:text-menu-text mb-4 text-sm">// Code snippet showcase:</h3>
<div class="flex flex-col overflow-scroll">
<!-- snippets -->
<GistSnippet data-aos="fade-down" v-for="(gist, key) in config.public.dev.gists" :key="key" :id="gist" />
</div>
</div>
<!-- scroll bar -->
<div id="scroll-bar" class="h-full border-left hidden lg:flex justify-center py-1">
<div id="scroll"></div>
</div>
</div>
</div>
</div>
</main>
</template>
<style>
#sections {
width: 5rem; /* 80px */
height: 100%;
display: none;
border-right: 1px solid #1E2D3D;
}
/* LG */
@media (min-width: 1024px) {
#sections {
display: block;
}
}
#section-icon {
@apply my-6 hover:cursor-pointer flex justify-center;
opacity: 0.4;
}
#section-icon.active {
opacity: 1;
}
#section-icon:hover {
opacity: 1;
}
.tab-height {
min-height: 35px;
max-height: 35px;
}
#tab-mobile {
padding: 25px 20px 0px 25px;
align-items: flex-end;
}
#scroll-bar{
width: 20px;
}
#scroll {
width: 14px;
height: 7px;
background-color: #607B96;
}
#diple {
@apply mx-3 w-2 max-w-fit;
}
.open {
transform: rotate(90deg);
}
.active {
color:white;
}
#right, #left {
height: 100%;
overflow: hidden;
}
#gists-content {
height: 100%;
overflow: hidden;
}
@media (max-width: 1024px) {
#gists-content {
height: 100%;
padding: 0px 25px;
overflow: hidden;
}
#about {
min-height: stretch;
}
}
.section-arrow {
transition: 0.1s;
}
#section-content #contacts {
padding: 0px 25px;
}
</style>
<script>
export default {
data() {
return {
currentSection: 'personal-info',
folder: 'bio',
loading: true,
}
},
/**
* In setup we can define the data we want to use in the component before the component is created.
*/
setup() {
const config = useRuntimeConfig()
return {
config
}
},
computed: {
// Set active class to current page link
isActive() {
return folder => this.folder === folder;
},
isSectionActive() {
return section => this.currentSection === section;
},
isOpen() {
return folder => this.folder === folder;
},
},
methods: {
focusCurrentSection(section) {
this.currentSection = section.title
this.folder = Object.keys(section.info)[0]
document.getElementById('folders-' + section.title).classList.toggle('hidden') // show folders
document.getElementById('section-arrow-' + section.title).classList.toggle('rotate-90'); // rotate arrow
},
focusCurrentFolder(folder) {
this.folder = folder.title
// handle if folder belongs to the current section. It happens when you click on a folder from a different section in mobile view.
this.currentSection = this.config.dev.about.sections[this.currentSection].info[folder.title] ? this.currentSection : Object.keys(this.config.dev.about.sections).find(section => this.config.dev.about.sections[section].info[folder.title])
},
/**
* TODO: Hay que crear un método para que cuando se haga click en un folder, se muestren los archivos que contiene. Y si se hace click en un archivo, se muestre el contenido del archivo.
* TODO: Además de girar el icono del diple.
*/
toggleFiles() {
document.getElementById('file-' + this.folder).classList.toggle('hidden')
},
/* Mobile */
showContacts() {
document.getElementById('contacts').classList.toggle('hidden')
document.getElementById('section-arrow').classList.toggle('rotate-90'); // rotate arrow
},
},
mounted(){
this.loading = false
}
}
</script>

213
pages/contact-me.vue Normal file
View File

@@ -0,0 +1,213 @@
<template>
<main id="contact-me" class="page">
<div id="mobile-page-title">
<h2>_contact-me</h2>
</div>
<div id="page-menu" class="w-full h-full flex flex-col border-right">
<!-- contacts -->
<div id="contacts" class="submenu">
<div class="title" @click="open('contacts')">
<img class="arrow" src="/icons/arrow.svg">
<h3>
contacts
</h3>
</div>
<div id="links">
<div v-for="(source, key) in contact.direct.sources" :key="key" class="link">
<img :src="'/icons/' + key + '.svg'">
<a v-html="source" href="/" class="font-fira_retina text-menu-text hover:text-white"></a>
</div>
</div>
</div>
<!-- find me also in -->
<div id="find-me-in" class="submenu border-top">
<div class="title" @click="open('find-me-in')">
<img class="arrow" src="/icons/arrow.svg">
<h3>
find-me-also-in
</h3>
</div>
<div id="links">
<div v-for="(source, key) in contact.find_me_also_in.sources" :key="key" class="link">
<img src="/icons/link.svg">
<a :href="source.url + source.user" class="font-fira_retina text-menu-text hover:text-white" target="_blank">{{ source.title }}</a>
</div>
</div>
</div>
</div>
<div class="flex flex-col w-full">
<!-- windows tab -->
<div class="tab-height w-full hidden lg:flex border-right border-bot items-center">
<div class="flex items-center border-right h-full">
<p class="font-fira_regular text-menu-text text-sm px-3">contacts</p>
<img src="/icons/close.svg" alt="" class="m-3">
</div>
</div>
<!-- main -->
<div class="flex lg:grid lg:grid-cols-2 h-full w-full">
<div id="left" class="h-full w-full flex flex-col border-right items-center">
<ContactForm :name="name" :email="email" :message="message" />
</div>
<div id="right" class="h-full w-full hidden lg:flex">
<div class="form-content">
<FormContentCode :name="name" :email="email" :message="message" />
</div>
<!-- scroll bar -->
<div id="scroll-bar" class="h-full border-left flex justify-center py-1">
<div id="scroll"></div>
</div>
</div>
</div>
</div>
</main>
</template>
<script>
export default {
data() {
return {
name: '',
email: '',
message: '',
}
},
setup() {
const contact = useRuntimeConfig().dev.contacts
return {
contact
}
},
methods: {
open(elementId) {
const element = document.getElementById(elementId);
const arrow = element.querySelector('.arrow');
const links = element.querySelector('#links');
if (links.style.display === 'block') {
links.style.display = 'none';
arrow.style.transform = 'rotate(0deg)';
} else {
links.style.display = 'block';
arrow.style.transform = 'rotate(90deg)';
}
}
},
mounted(){
const nameInput = document.getElementById('name-input');
const emailInput = document.getElementById('email-input');
const messageInput = document.getElementById('message-input');
nameInput.addEventListener('input', (event) => {
const nameValue = document.getElementById('name-value')
nameValue.innerHTML = event.target.value;
})
emailInput.addEventListener('input', (event) => {
const emailValue = document.getElementById('email-value')
emailValue.innerHTML = event.target.value;
})
messageInput.addEventListener('input', (event) => {
const messageValue = document.getElementById('message-value')
messageValue.innerHTML = event.target.value;
})
/**
* * Close all submenus
* ! This is a temporary solution.
* ! This is needed because when the page is loaded, height style on #links are not applied.
*/
const links = document.getElementsByClassName('submenu');
for (let i = 0; i < links.length; i++) {
if(window.innerWidth > 1024){
links[i].querySelector("#links").style.display = "block";
links[i].querySelector(".arrow").style.transform = "rotate(90deg)";
} else {
links[i].querySelector("#links").style.display = "none";
}
}
},
}
</script>
<style>
.arrow {
transition: 0.1s;
margin-right: 10px;
width: 9px;
height: 9px;
}
.submenu {
display: flex;
flex-direction: column;
}
.submenu .title h3 {
@apply font-fira_regular;
color: white;
font-size: 16px;
}
.link {
display: flex;
align-items: center;
padding: 4px 25px;
}
.link img {
width: 16px;
height: 16px;
margin-right: 10px;
}
#links {
padding: 10px 0px;
}
.form-content {
padding: 75px 50px 0px 75px;
width: 100%;
height: 100%;
overflow-y: auto;
font-size: 15px;
}
@media (min-width: 1024px) {
.submenu .title {
display: flex;
align-items: center;
border-bottom: 1px solid #1E2D3D;
padding: 0px 25px;
height: 35px;
padding: 0px 25px;
}
.submenu .title:hover {
cursor: pointer;
}
.submenu .title h3 {
font-size: 14px;
}
}
</style>

335
pages/index.vue Normal file
View File

@@ -0,0 +1,335 @@
<template>
<main v-if="!loading" id="hello">
<!-- gradients -->
<div class="css-blurry-gradient-blue"></div>
<div class="css-blurry-gradient-green"></div>
<section class="hero">
<div class="head">
<span>
Hi all, I am
</span>
<h1>{{ config.dev.name }}</h1>
<span class="diple flex">
>&nbsp;
<h2 class="line-1 anim-typewriter max-w-fit"> {{ config.dev.role }} </h2>
</span>
</div>
<div id="info">
<span class="action">
// complete the game to continue
</span>
<span :class="{hide: isMobile}">
// you can also see it on my Github page
</span>
<span :class="{hide: !isMobile}">
// find my profile on Github:
</span>
<p class="code">
<span class="identifier">
const
</span>
<span class="variable-name">
githubLink
</span>
<span class="operator">
=
</span>
<a class="string" :href="'https://github.com/' + config.public.dev.contacts.social.github.user">
"https://github.com/{{ config.public.dev.contacts.social.github.user }}"
</a>
</p>
</div>
</section>
<section data-aos="fade-up" class="game" v-if="!isMobile">
<SnakeGame />
</section>
</main>
</template>
<script setup>
import { ref, onMounted, onBeforeUnmount } from 'vue'
const config = useRuntimeConfig()
const isMobile = ref(false)
const loading = ref(false)
onMounted(() => {
if (window.innerWidth <= 1024) isMobile.value = true
window.addEventListener('resize', handleResize)
})
onBeforeUnmount(() => {
window.removeEventListener('resize', handleResize)
})
function handleResize() {
if (window.innerWidth <= 1024) {
isMobile.value = true
} else {
isMobile.value = false
}
}
</script>
<style scoped>
#hello {
display: flex;
height: 100%;
width: 100%;
flex: 1 1 auto;
padding-left: 275px;
overflow: hidden;
}
.hero {
width: 100%;
justify-content: center;
}
.game {
display: flex;
flex-direction: column;
width: 100%;
height: 100%;
justify-content: center;
/* align-items: center; */
z-index: 20;
}
#hello .hero {
display: flex;
flex-direction: column;
/* display: grid;
grid-template-columns: repeat(12, minmax(0, 1fr)); */
margin: 0rem;
}
#hello .head span {
font-size: 18px;
line-height: 1;
color: #E5E9F0;
font-family: 'Fira Code Retina';
}
#hello .head h1 {
font-size: 58px;
line-height: 1;
color: #E5E9F0;
font-family: 'Fira Code Regular';
padding-top: 1rem; /* 16px */
padding-bottom: 1rem; /* 16px */
}
#hello .head h2, #hello .head .diple {
font-size: 32px;
line-height: 1;
color: #4D5BCE;
font-family: 'Fira Code Retina';
}
.head {
padding-bottom: 3rem;
}
#info {
display: flex;
flex-direction: column;
}
#info > span {
font-size: 14px;
line-height: 1;
color: #607B96;
font-family: 'Fira Code Retina';
padding-bottom: 1rem; /* 16px */
}
.code {
font-family: 'Fira Code Medium';
color: #E5E9F0;
}
.code .identifier {
color: #4D5BCE;
}
.code .variable-name {
color: #43D9AD;
}
.code .operator {
color: white;
}
.code .string {
color: #E99287;
text-decoration-line: underline;
text-underline-offset: 4px;
}
#info {
padding-block: 2.5rem;
}
#info .action {
display: flex
}
.hide {
display: none;
}
.css-blurry-gradient-blue {
position: fixed;
bottom: 25%;
right: 5%;
width: 300px;
height: 300px;
border-radius: 0% 0% 50% 50%;
rotate: 10deg;
filter: blur(70px);
background: radial-gradient(circle at 50% 50%,rgba(77, 91, 206, 1), rgba(76, 0, 255, 0));
opacity: 0.5;
z-index: 10;
}
.css-blurry-gradient-green {
position: absolute;
top: 20%;
right: 30%;
width: 300px;
height: 300px;
border-radius: 0% 50% 0% 50%;
filter: blur(70px);
background: radial-gradient(circle at 50% 50%,rgba(67, 217, 173, 1), rgba(76, 0, 255, 0));
opacity: 0.5;
z-index: 10;
}
#info {
font-size: 14px;
}
/* Typewrite Animation */
.line-1 {
width: fit-content;
border-right: 3px solid rgba(255,255,255,.75);
white-space: nowrap;
overflow: hidden;
padding-right: 2px;
}
.anim-typewriter{
animation: typewriter 3.5s steps(40) 1s 1 normal both,
blinkTextCursor 800ms steps(40) infinite normal;
}
@keyframes typewriter{
from{width: 0;}
to{width: 100%;}
}
@keyframes blinkTextCursor{
from{border-right-color: rgba(255,255,255,.75);}
to{border-right-color: transparent;}
}
/* mobile */
@media (max-width: 768px) {
#hello {
padding-left: 0;
}
#hello .hero {
display: flex;
flex-direction: column;
justify-content: space-between;
margin: 1.75rem; /* 28px */
}
.head {
padding-top: 4rem; /* 40px */
}
#hello .head h2, #hello .head .diple {
font-size: 20px;
color: #43D9AD;
}
#info .action {
display: none;
}
}
/* tablet */
@media (min-width: 768px) and (max-width: 1024px) {
#hello {
padding-left: 0;
}
#hello .hero {
display: flex;
flex-direction: column;
justify-content: center;
margin: 1.75rem; /* 28px */
}
.head {
padding-top: 4rem; /* 40px */
}
}
@media (min-width: 1024px) and (max-width: 1320px) {
#hello {
padding-left: 135px;
}
}
/* LG */
@media (min-width: 1024px) {
.css-blurry-gradient-blue {
position: fixed;
bottom: 10%;
right: 10%;
width: 500px;
height: 500px;
opacity: 0.7;
border-radius: 100% 50% 100% 0%;
}
.css-blurry-gradient-green {
position: fixed;
top: 10%;
right: 35%;
filter: blur(100px);
rotate: 10deg;
width: 400px;
height: 400px;
opacity: 0.5;
border-radius: 100% 0% 0% 0%;
rotate: 20deg;
}
}
@media (min-width: 1920px){
#hello {
padding-left: 310px;
}
#hello .head h1 {
font-size: 62px;
}
}
</style>

224
pages/projects.vue Normal file
View File

@@ -0,0 +1,224 @@
<template>
<main class="flex flex-col flex-auto lg:flex-row overflow-hidden">
<div id="mobile-page-title">
<h2>_projects</h2>
</div>
<!-- section title (mobile) -->
<div id="section-content-title" class="flex lg:hidden" @click="showFilters = !showFilters">
<img :class="showFilters ? 'section-arrow rotate-90' : 'section-arrow'" src="/icons/arrow.svg">
<span class="font-fira_regular text-white text-sm">projects</span>
</div>
<div v-if="showFilters" id="filter-menu" class="w-full flex-col border-right font-fira_regular text-menu-text lg:flex">
<!-- title -->
<div id="section-content-title" class="hidden lg:flex items-center min-w-full">
<img id="section-arrow-menu" src="/icons/arrow.svg" alt="" class="section-arrow mx-3">
<p class="font-fira_regular text-white text-sm">projects</p>
</div>
<!-- filter menu -->
<nav id="filters" class="w-full flex-col">
<div v-for="tech in techs" :key="tech" class="flex items-center py-2">
<input type="checkbox" :id="tech" @click="filterProjects(tech)">
<img :id="'icon-tech-' + tech" :src="'/icons/techs/' + tech + '.svg'" alt="" class="tech-icon w-5 h-5 mx-4">
<span :id="'title-tech-' + tech">{{ tech }}</span>
</div>
</nav>
</div>
<!-- content -->
<div class="flex flex-col w-full overflow-hidden">
<!-- windows tab -->
<div class="tab-height w-full hidden lg:flex border-bot items-center">
<div class="flex items-center border-right h-full">
<p v-for="filter in filters" :key="filter" class="font-fira_regular text-menu-text text-sm px-3">{{ filter }};</p>
<img src="/icons/close.svg" alt="" class="m-3">
</div>
</div>
<!-- windows tab mobile -->
<div id="tab" class="flex lg:hidden items-center">
<span class="text-white"> // </span>
<p class="font-fira_regular text-white text-sm px-3">projects</p>
<span class="text-menu-text"> / </span>
<p v-for="filter in filters" :key="filter" class="font-fira_regular text-menu-text text-sm px-3">{{ filter }};</p>
</div>
<!-- projects -->
<div id="projects-case" class="grid grid-cols-1 lg:grid-cols-2 max-w-full h-full overflow-scroll lg:self-center">
<div id="not-found" class="hidden flex flex-col font-fira_retina text-menu-text my-5 h-full justify-center items-center">
<span class="flex justify-center text-4xl pb-3">
X__X
</span>
<span class="text-white flex justify-center text-xl">
No matching projects
</span>
<span class="flex justify-center">
for these technologies
</span>
</div>
<project-card
v-for="(project, key, index) in projects"
:key="key"
:index="index"
:project="project"
/>
</div>
</div>
</main>
</template>
<script setup>
import { ref } from 'vue'
const config = useRuntimeConfig()
const techs = ['React', 'HTML', 'CSS', 'Vue', 'Angular', 'Gatsby', 'Flutter']
const filters = ref(['all'])
const showFilters = ref(true)
const projects = ref(config.public.dev.projects)
function filterProjects(tech) {
document.getElementById('icon-tech-' + tech).classList.toggle('active')
document.getElementById('title-tech-' + tech).classList.toggle('active')
const check = document.getElementById(tech)
if (check.checked) {
filters.value = filters.value.filter((item) => item !== 'all')
filters.value.push(tech)
} else {
filters.value = filters.value.filter((item) => item !== tech)
filters.value.length === 0 ? filters.value.push('all') : null
}
filters.value[0] == 'all' ? projects.value = config.public.dev.projects : projects.value = filterProjectsBy(filters.value)
if (projects.value.length === 0) {
document.getElementById('projects-case').classList.remove('grid')
document.getElementById('not-found').classList.remove('hidden')
} else {
document.getElementById('projects-case').classList.add('grid')
document.getElementById('not-found').classList.add('hidden')
}
}
function filterProjectsBy(filters) {
const projectArray = Object.values(config.public.dev.projects)
return projectArray.filter(project => {
return filters.some(filter => project.tech.includes(filter))
})
}
</script>
<style>
#filters {
padding: 10px 25px;
}
#tab {
padding: 25px 25px 5px;
flex-wrap: wrap;
}
.tech-icon {
opacity: 0.4;
}
.tech-icon.active {
opacity: 1;
}
#title-tech.active {
color: white;
}
#view-button {
background-color: #1C2B3A;
}
#view-button:hover {
background-color: #263B50;
}
input[type="checkbox"] {
appearance: none;
background-color: transparent;
width: 1.15em;
height: 1.15em;
border: 2px solid currentColor;
border-radius: 0.15em;
margin-top: 1px;
}
input[type="checkbox"]:checked {
background-color: currentColor;
background-image: url("data:image/svg+xml;utf8,<svg width='13' height='10' viewBox='0 0 13 10' fill='none' xmlns='http://www.w3.org/2000/svg'><path d='M5.38587 7.2802L11.9718 0.693573L12.9856 1.70668L5.38587 9.30641L0.826172 4.74671L1.83928 3.73361L5.38587 7.2802Z' fill='white'/></svg>");
background-repeat: no-repeat;
background-position: center;
}
input[type="checkbox"]:checked:hover {
box-shadow: #607b968b 0px 0px 0px 2px;
}
input[type="checkbox"]:not(:checked) {
border-color: currentColor;
}
input[type="checkbox"]:hover {
cursor: pointer;
background-color: currentColor;
background-image: url("data:image/svg+xml;utf8,<svg width='13' height='10' viewBox='0 0 13 10' fill='none' xmlns='http://www.w3.org/2000/svg'><path d='M5.38587 7.2802L11.9718 0.693573L12.9856 1.70668L5.38587 9.30641L0.826172 4.74671L1.83928 3.73361L5.38587 7.2802Z' fill='white'/></svg>");
background-repeat: no-repeat;
background-position: center;
box-shadow: #607b968b 0px 0px 0px 2px;
}
input[type="checkbox"]:hover:not(:checked) {
cursor: pointer;
background-color: rgba(0, 0, 0, 0.1);
background-image: none;
box-shadow: #607b968b 0px 0px 0px 2px;
}
input[type="checkbox"]:focus {
box-shadow: none;
}
@media (max-width: 768px) {
#projects-case {
padding: 0px 25px 40px;
}
}
@media (min-width: 768px) {
#projects-case {
grid-template-columns: repeat(2, minmax(0, 1fr));
padding: 50px 50px 40px;
}
}
@media (min-width: 1350px) {
#projects-case {
grid-template-columns: repeat(3, minmax(0, 1fr));
padding: 50px 80px 40px;
/* padding: 100px 100px 40px; */
}
}
@keyframes animateToBottom {
from {
transform: translate3d(0, -200px, 0);
}
to {
transform: translate3d(0, 10px, 0);
}
}
</style>