feat: final refactor of sections
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
import BrandLogo from '@components/ui/logos/BrandLogo.astro';
|
import BrandLogo from '@components/ui/logos/BrandLogo.astro';
|
||||||
import ThemeToggle from '@components/buttons/ThemeToggle.astro';
|
import ThemeToggleButton from '@components/buttons/ThemeToggleButton.astro';
|
||||||
import { NavigationLinks } from '@/config';
|
import { NavigationLinks } from '@/config';
|
||||||
|
|
||||||
const pathname = new URL(Astro.request.url).pathname;
|
const pathname = new URL(Astro.request.url).pathname;
|
||||||
@@ -90,7 +90,7 @@ const currentPath = pathname.slice(1);
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
<span class="md:inline-block">
|
<span class="md:inline-block">
|
||||||
<ThemeToggle />
|
<ThemeToggleButton />
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
---
|
---
|
||||||
import GoLinkPrimary from '@components/buttons/GoLinkPrimary.astro';
|
import GoLinkPrimaryButton from '@components/buttons/GoLinkPrimaryButton.astro';
|
||||||
import Image from '@components/ui/images/Image.astro';
|
import Image from '@components/ui/images/Image.astro';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -39,6 +39,6 @@ const { title, subTitle, btnExists, btnTitle, btnURL, img, imgAlt } = Astro.prop
|
|||||||
>
|
>
|
||||||
{subTitle}
|
{subTitle}
|
||||||
</p>
|
</p>
|
||||||
{btnExists ? <GoLinkPrimary title={btnTitle} url={btnURL} /> : null}
|
{btnExists ? <GoLinkPrimaryButton title={btnTitle} url={btnURL} /> : null}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
---
|
---
|
||||||
import GoLinkPrimary from '@components/buttons/GoLinkPrimary.astro';
|
import GoLinkPrimaryButton from '@components/buttons/GoLinkPrimaryButton.astro';
|
||||||
import Image from '@components/ui/images/Image.astro';
|
import Image from '@components/ui/images/Image.astro';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -43,7 +43,7 @@ const {
|
|||||||
>
|
>
|
||||||
{subTitle}
|
{subTitle}
|
||||||
</p>
|
</p>
|
||||||
{btnExists ? <GoLinkPrimary title={btnTitle} url={btnURL} /> : null}
|
{btnExists ? <GoLinkPrimaryButton title={btnTitle} url={btnURL} /> : null}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ const certificates = ((await directus.request(
|
|||||||
Education
|
Education
|
||||||
</h3>
|
</h3>
|
||||||
<div class="mx-8">
|
<div class="mx-8">
|
||||||
<h4 class="smooth-reveal card-text-header-minor pt-5 ">
|
<h4 class="smooth-reveal card-text-header-minor pt-5">
|
||||||
College
|
College
|
||||||
</h4>
|
</h4>
|
||||||
<div class="grid md:grid-cols-2 sm:grid-cols-1 gap-4 py-3">
|
<div class="grid md:grid-cols-2 sm:grid-cols-1 gap-4 py-3">
|
||||||
|
|||||||
@@ -1,152 +0,0 @@
|
|||||||
---
|
|
||||||
import { Icon } from 'astro-icon/components';
|
|
||||||
import { readItems } from '@directus/sdk';
|
|
||||||
|
|
||||||
import type { Experience } from '@lib/directusTypes';
|
|
||||||
|
|
||||||
import directus from '@lib/directus';
|
|
||||||
|
|
||||||
const experiences = await directus.request(
|
|
||||||
readItems('site_experience', {
|
|
||||||
fields: ['*'],
|
|
||||||
sort: ['-endDate'],
|
|
||||||
})
|
|
||||||
);
|
|
||||||
---
|
|
||||||
|
|
||||||
<section
|
|
||||||
class:list={['flex flex-col gap-8', Astro.props.className]}
|
|
||||||
|
|
||||||
>
|
|
||||||
<h3 class="relative flex w-full items-center gap-3 pb-10 text-5xl text-neutral-800 dark:text-neutral-200">Experience</h3>
|
|
||||||
<ul class="ml-8 w-full flex flex-col">
|
|
||||||
{
|
|
||||||
experiences.map(
|
|
||||||
(experience: Experience) => {
|
|
||||||
const startYear = new Date(experience.startDate).getFullYear();
|
|
||||||
const endYear = experience.endDate != null ? new Date(experience.endDate).getFullYear() : 'Present';
|
|
||||||
|
|
||||||
return (
|
|
||||||
<li class="relative">
|
|
||||||
<div class="group smooth-reveal relative grid pb-1 transition-all sm:grid-cols-18 sm:gap-8 md:gap-6 lg:hover:!opacity-100">
|
|
||||||
<header class="relative mt-1 text-lg font-semibold sm:col-span-3 text-neutral-800 dark:text-neutral-200">
|
|
||||||
<time datetime={experience.startDate} data-title={experience.startDate}>
|
|
||||||
{startYear}
|
|
||||||
</time>{' '}
|
|
||||||
-{' '}
|
|
||||||
<time datetime={experience.endDate} data-title={experience.endDate}>
|
|
||||||
{endYear}
|
|
||||||
</time>
|
|
||||||
</header>
|
|
||||||
<div class="relative flex flex-col pb-6 before:absolute before:mt-8 before:-ml-6 before:h-full before:w-px before:bg-stone-400 sm:col-span-12">
|
|
||||||
<div class="absolute mt-4 h-2 w-2 -translate-x-[1.71rem] rounded-full bg-stone-400" />
|
|
||||||
<h3>
|
|
||||||
<div
|
|
||||||
class="inline-flex items-center text-2xl leading-tight font-semibold"
|
|
||||||
aria-label="{position} - {company}"
|
|
||||||
>
|
|
||||||
<span class="text-neutral-800 dark:text-neutral-200">
|
|
||||||
{experience.position} <span>@</span>
|
|
||||||
{experience.url ? (
|
|
||||||
<a
|
|
||||||
class="hover:text-steel dark:hover:text-bermuda"
|
|
||||||
href={experience.url}
|
|
||||||
title={`Ver ${experience.name}`}
|
|
||||||
target="_blank"
|
|
||||||
>
|
|
||||||
{experience.name}
|
|
||||||
</a>
|
|
||||||
) : (
|
|
||||||
<span>{experience.name}</span>
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</h3>
|
|
||||||
{(experience.location || experience.location_type) && (
|
|
||||||
<div class="text-sm text-neutral-600 dark:text-neutral-400">
|
|
||||||
{experience.location} {experience.location && experience.location_type && '-'} {experience.location_type}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div class="text-md mt-4 flex flex-col gap-4" x-data="{ expanded: false }">
|
|
||||||
{experience.summary && (
|
|
||||||
<div class="flex flex-col gap-1">
|
|
||||||
<h4 class="font-semibold text-neutral-800 dark:text-neutral-200">Summary:</h4>
|
|
||||||
<ul class="flex list-disc flex-col gap-2 text-neutral-700 dark:text-neutral-400 [&>li]:ml-4">
|
|
||||||
{Array.isArray(experience.summary) ? (
|
|
||||||
experience.summary.map((item) => ({ item }))
|
|
||||||
) : (
|
|
||||||
<li class="marker:text-steel dark:marker:text-bermuda">{experience.summary}</li>
|
|
||||||
)}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{(experience.responsibilities || experience.achievements) && (
|
|
||||||
<div class="relative flex flex-col gap-4 max-sm:!h-auto md:after:absolute md:after:bottom-0 md:after:h-12 md:after:w-full md:after:bg-gradient-to-t md:after:from-neutral-200 dark:md:after:from-stone-700 md:after:content-[''] " :class="expanded ? 'after:hidden' : ''" x-show="expanded" x-collapse.min.50px>
|
|
||||||
{experience.responsibilities && (
|
|
||||||
<div class="flex flex-col gap-1">
|
|
||||||
<h4 class="font-semibold text-neutral-800 dark:text-neutral-200">Responsibilities:</h4>
|
|
||||||
<ul class="text-neutral-700 dark:text-neutral-400 [&>li]:ml-4 flex list-disc flex-col gap-2">
|
|
||||||
{experience.responsibilities.map(responsibility => (
|
|
||||||
<li class="marker:text-steel dark:marker:text-bermuda">{responsibility}</li>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{experience.achievements && (
|
|
||||||
<div class="flex flex-col gap-1">
|
|
||||||
<h4 class="font-semibold text-neutral-800 dark:text-neutral-200">Achievements:</h4>
|
|
||||||
<ul class="text-neutral-700 dark:text-neutral-400 [&>li]:ml-4 flex list-disc flex-col gap-2">
|
|
||||||
{experience.achievements.map(achievement => (
|
|
||||||
<li class="marker:text-steel dark:marker:text-bermuda">{achievement}</li>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button @click="expanded = ! expanded" class="group/more w-fit cursor-pointer items-center justify-center gap-1.5 text-xs underline text-neutral-700 dark:text-neutral-300 hover:text-neutral-900 dark:hover:text-neutral-400 transition-all flex">
|
|
||||||
<span x-text="expanded ? 'Show less' : 'Show more'">Show more</span>
|
|
||||||
<svg
|
|
||||||
class="h-4 w-4 duration-200 ease-out group-hover/more:translate-y-0.5"
|
|
||||||
:class="{ 'rotate-180': expanded }"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
fill="none"
|
|
||||||
stroke="currentColor"
|
|
||||||
stroke-width="2"
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
>
|
|
||||||
<polyline points="6 9 12 15 18 9" />
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<ul class="flex print:hidden flex-wrap gap-2" aria-label="Technologies used">
|
|
||||||
{experience.skills && experience.skills.map(skill => {
|
|
||||||
const iconName = skill.toLowerCase();
|
|
||||||
return (
|
|
||||||
<li class="bg-steel/20 border-steel/20 text-neutral-800 dark:bg-bermuda/20 dark:border-bermuda/20 dark:text-neutral-200 flex gap-1 items-center border-solid border rounded-md px-2 py-0.5 text-xs">
|
|
||||||
<Icon name={`mdi:${iconName}`} /> <span>{skill}</span>
|
|
||||||
</li>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</ul>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
</ul>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<!-- Alpine Plugins -->
|
|
||||||
<script defer src="https://cdn.jsdelivr.net/npm/@alpinejs/collapse@3.x.x/dist/cdn.min.js"></script>
|
|
||||||
|
|
||||||
<!-- Alpine Core -->
|
|
||||||
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
|
|
||||||
159
src/components/sections/ExperienceSection.astro
Normal file
159
src/components/sections/ExperienceSection.astro
Normal file
@@ -0,0 +1,159 @@
|
|||||||
|
---
|
||||||
|
import { Icon } from 'astro-icon/components';
|
||||||
|
import { readItems } from '@directus/sdk';
|
||||||
|
|
||||||
|
import type { Experience } from '@lib/directusTypes';
|
||||||
|
|
||||||
|
import directus from '@lib/directus';
|
||||||
|
|
||||||
|
const experiences = ((await directus.request(
|
||||||
|
readItems('site_experience'as any, {
|
||||||
|
fields: ['*'],
|
||||||
|
sort: ['-endDate'],
|
||||||
|
})
|
||||||
|
)) as unknown) as Experience[];
|
||||||
|
---
|
||||||
|
|
||||||
|
<section class:list={['flex flex-col gap-8', Astro.props.className]}>
|
||||||
|
<h3 class="smooth-reveal card-text-header flex relative items-center w-full gap-3 pb-10">
|
||||||
|
Experience
|
||||||
|
</h3>
|
||||||
|
<ul class="ml-8 w-full flex flex-col">
|
||||||
|
{experiences.map((experience: Experience) => {
|
||||||
|
const startYear = new Date(experience.startDate).getFullYear();
|
||||||
|
const endYear = experience.endDate != null ? new Date(experience.endDate).getFullYear() : 'Present';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<li class="relative">
|
||||||
|
<div class="smooth-reveal group relative grid sm:grid-cols-18 sm:gap-8 md:gap-6 pb-16">
|
||||||
|
<header class="relative sm:col-span-3 text-header font-semibold text-lg mt-1">
|
||||||
|
<time datetime={experience.startDate} data-title={experience.startDate}>
|
||||||
|
{startYear}
|
||||||
|
</time>
|
||||||
|
{' '}-{' '}
|
||||||
|
<time datetime={experience.endDate} data-title={experience.endDate}>
|
||||||
|
{endYear}
|
||||||
|
</time>
|
||||||
|
</header>
|
||||||
|
<div class="relative flex flex-col sm:col-span-12 pb-6">
|
||||||
|
<div class="absolute bg-stone-400 -translate-x-[1.71rem] rounded-full h-2 w-2 mt-4"/>
|
||||||
|
<h3>
|
||||||
|
<div
|
||||||
|
class="inline-flex items-center text-2xl leading-tight font-semibold"
|
||||||
|
aria-label="{position} - {company}"
|
||||||
|
>
|
||||||
|
<span class="text-header">
|
||||||
|
{experience.position} <span>@</span>
|
||||||
|
{experience.url ? (
|
||||||
|
<a
|
||||||
|
class="hover:text-main"
|
||||||
|
href={experience.url}
|
||||||
|
title={`Ver ${experience.name}`}
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
{experience.name}
|
||||||
|
</a>
|
||||||
|
) : (
|
||||||
|
<span>{experience.name}</span>
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</h3>
|
||||||
|
{(experience.location || experience.location_type) && (
|
||||||
|
<div class="text-secondary text-sm">
|
||||||
|
{experience.location} {experience.location && experience.location_type && '-'} {experience.location_type}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div class="text-md mt-4 flex flex-col gap-4" x-data="{ expanded: false }">
|
||||||
|
{experience.summary && (
|
||||||
|
<div class="flex flex-col gap-1">
|
||||||
|
<h4 class="text-header font-semibold">
|
||||||
|
Summary:
|
||||||
|
</h4>
|
||||||
|
<ul class="flex flex-col text-primary list-disc gap-2 [&>li]:ml-4">
|
||||||
|
<li class="marker:text-main">
|
||||||
|
{experience.summary}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{(experience.responsibilities || experience.achievements) && (
|
||||||
|
<div class="relative flex flex-col gap-4 max-sm:h-auto! md:after:absolute md:after:bottom-0 md:after:h-12 md:after:w-full md:after:bg-gradient-to-t md:after:from-neutral-200 dark:md:after:from-stone-700 md:after:content-[''] " :class="expanded ? 'after:hidden' : ''" x-show="expanded" x-collapse.min.50px>
|
||||||
|
{experience.responsibilities && (
|
||||||
|
<div class="flex flex-col gap-1">
|
||||||
|
<h4 class="text-header font-semibold">
|
||||||
|
Responsibilities:
|
||||||
|
</h4>
|
||||||
|
<ul class="flex flex-col text-primary list-disc gap-2 [&>li]:ml-4">
|
||||||
|
{experience.responsibilities.map(responsibility => (
|
||||||
|
<li class="marker:text-main">
|
||||||
|
{responsibility}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{experience.achievements && (
|
||||||
|
<div class="flex flex-col gap-1">
|
||||||
|
<h4 class="text-header font-semibold">
|
||||||
|
Achievements:
|
||||||
|
</h4>
|
||||||
|
<ul class="flex flex-col text-primary list-disc gap-2 [&>li]:ml-4">
|
||||||
|
{experience.achievements.map(achievement => (
|
||||||
|
<li class="marker:text-main">
|
||||||
|
{achievement}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button @click="expanded = ! expanded" class="group/more flex items-center justify-center text-primary hover:text-primary-hover text-xs underline transition-all gap-1.5 w-fit cursor-pointer">
|
||||||
|
<span x-text="expanded ? 'Show less' : 'Show more'">
|
||||||
|
Show more
|
||||||
|
</span>
|
||||||
|
<svg
|
||||||
|
class="group-hover/more:translate-y-0.5 ease-out duration-300 h-4 w-4"
|
||||||
|
:class="{ 'rotate-180': expanded }"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="2"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
>
|
||||||
|
<polyline points="6 9 12 15 18 9" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<ul
|
||||||
|
class="flex print:hidden flex-wrap gap-2"
|
||||||
|
aria-label="Technologies used"
|
||||||
|
>
|
||||||
|
{experience.skills && experience.skills.map(skill => {
|
||||||
|
const iconName = skill.toLowerCase();
|
||||||
|
const skillName = skill.split(':')[1].replace(/^language-/, '').replace(/-/g, ' ').replace(/\b\w/g, (l) => l.toUpperCase());
|
||||||
|
return (
|
||||||
|
<li class="flex items-center bg-steel/20 dark:bg-bermuda/20 text-neutral-800 dark:text-neutral-200 text-xs rounded-md border border-solid border-steel/20 dark:border-bermuda/20 gap-1 px-2 py-0.5">
|
||||||
|
<Icon name={`${iconName}`} class="h-4 w-4" /> <span>{skillName}</span>
|
||||||
|
</li>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</ul>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- Alpine Plugins -->
|
||||||
|
<script defer src="https://cdn.jsdelivr.net/npm/@alpinejs/collapse@3.x.x/dist/cdn.min.js"></script>
|
||||||
|
|
||||||
|
<!-- Alpine Core -->
|
||||||
|
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
---
|
---
|
||||||
import GoLinkPrimary from '@components/buttons/GoLinkPrimary.astro';
|
import GoLinkPrimaryButton from '@components/buttons/GoLinkPrimaryButton.astro';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
title: string;
|
title: string;
|
||||||
@@ -23,7 +23,7 @@ const { title, subTitle, btnExists, btnTitle, btnURL } = Astro.props;
|
|||||||
</p>
|
</p>
|
||||||
{btnExists ? (
|
{btnExists ? (
|
||||||
<div class="smooth-reveal mt-4 md:mt-8">
|
<div class="smooth-reveal mt-4 md:mt-8">
|
||||||
<GoLinkPrimary title={btnTitle} url={btnURL}/>
|
<GoLinkPrimaryButton title={btnTitle} url={btnURL}/>
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
import GoLinkPrimary from '@components/buttons/GoLinkPrimary.astro';
|
import GoLinkPrimaryButton from '@components/buttons/GoLinkPrimaryButton.astro';
|
||||||
import GoLinkSecondary from '@components/buttons/GoLinkSecondary.astro';
|
import GoLinkSecondaryButton from '@components/buttons/GoLinkSecondaryButton.astro';
|
||||||
import Image from '@components/ui/images/Image.astro';
|
import Image from '@components/ui/images/Image.astro';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -31,8 +31,8 @@ const roundedClasses = Astro.props.rounded ? "rounded-xl" : null;
|
|||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
<div class="smooth-reveal grid sm:inline-flex mt-7 w-full gap-3">
|
<div class="smooth-reveal grid sm:inline-flex mt-7 w-full gap-3">
|
||||||
{primaryBtn && <GoLinkPrimary title={primaryBtn} url={primaryBtnURL} />}
|
{primaryBtn && <GoLinkPrimaryButton title={primaryBtn} url={primaryBtnURL} />}
|
||||||
{secondaryBtn && <GoLinkSecondary title={secondaryBtn} url={secondaryBtnURL} />}
|
{secondaryBtn && <GoLinkSecondaryButton title={secondaryBtn} url={secondaryBtnURL} />}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
---
|
---
|
||||||
import { readItems } from '@directus/sdk';
|
import { readItems } from '@directus/sdk';
|
||||||
|
|
||||||
import directus from '@lib/directus';
|
|
||||||
import type { Post } from '@lib/directusTypes';
|
import type { Post } from '@lib/directusTypes';
|
||||||
|
|
||||||
|
import directus from '@lib/directus';
|
||||||
import BlogCard from '@components/blog/BlogCard.astro';
|
import BlogCard from '@components/blog/BlogCard.astro';
|
||||||
|
|
||||||
const posts = await directus.request(
|
const posts = await directus.request(
|
||||||
@@ -20,11 +21,14 @@ const recentPosts = posts
|
|||||||
|
|
||||||
<section class="mx-auto mb-20 max-w-340 px-4 py-10 sm:px-6 lg:px-8 lg:py-14 2xl:max-w-full">
|
<section class="mx-auto mb-20 max-w-340 px-4 py-10 sm:px-6 lg:px-8 lg:py-14 2xl:max-w-full">
|
||||||
<div class="mx-auto mb-10 max-w-2xl text-center lg:mb-14">
|
<div class="mx-auto mb-10 max-w-2xl text-center lg:mb-14">
|
||||||
<h1
|
<h1 class="smooth-reveal card-text-header block">
|
||||||
class="smooth-reveal block text-4xl font-bold text-neutral-800 md:text-5xl md:leading-tight lg:text-5xl dark:text-neutral-200"
|
|
||||||
>
|
|
||||||
Latest Posts
|
Latest Posts
|
||||||
</h1>
|
</h1>
|
||||||
|
<div class="smooth-reveal mx-auto mt-5 max-w-3xl text-center">
|
||||||
|
<span class="card-text-header-description">
|
||||||
|
Checkout my most recent thoughts here
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid gap-6 sm:grid-cols-2 lg:grid-cols-3">
|
<div class="grid gap-6 sm:grid-cols-2 lg:grid-cols-3">
|
||||||
{recentPosts.map((b) => <BlogCard post={b} />)}
|
{recentPosts.map((b) => <BlogCard post={b} />)}
|
||||||
@@ -6,86 +6,63 @@ import type { Skill } from '@lib/directusTypes';
|
|||||||
|
|
||||||
import directus from '@lib/directus';
|
import directus from '@lib/directus';
|
||||||
|
|
||||||
const skills = await directus.request(
|
const skills = ((await directus.request(
|
||||||
readItems('site_skills', {
|
readItems('site_skills' as any, {
|
||||||
fields: ['*'],
|
fields: ['*'],
|
||||||
sort: ['-date_created'],
|
sort: ['-date_created'],
|
||||||
})
|
})
|
||||||
);
|
)) as unknown) as Skill[];
|
||||||
|
|
||||||
const baseClasses = 'mx-2 min-w-[220px] sm:mx-4 sm:min-w-[280px]';
|
|
||||||
const borderClasses =
|
|
||||||
'border border-neutral-100 hover:border-neutral-200 dark:border-stone-500/20 dark:hover:border-neutral-800';
|
|
||||||
const bgColorClasses = 'bg-neutral-100/80 dark:bg-neutral-800/60 dark:hover:bg-neutral-800/90';
|
|
||||||
const hoverClasses = 'hover:-translate-y-2 hover:scale-105 ';
|
|
||||||
const shadowClasses = 'shadow-xs hover:shadow-lg';
|
|
||||||
---
|
---
|
||||||
|
|
||||||
<section class:list={['flex flex-col gap-4', Astro.props.className]}>
|
<section class:list={['flex flex-col gap-4', Astro.props.className]}>
|
||||||
<h3
|
<h3 class="smooth-reveal card-text-header flex relative items-center w-full gap-3 pb-5">
|
||||||
class="relative flex w-full items-center gap-3 pb-4 text-5xl text-neutral-800 dark:text-neutral-200"
|
|
||||||
>
|
|
||||||
Skills
|
Skills
|
||||||
</h3>
|
</h3>
|
||||||
<div class="">
|
<div class="">
|
||||||
<div class="tech-stack-slider relative overflow-hidden py-4 sm:py-8">
|
<div class="tech-stack-slider relative overflow-hidden py-4 sm:py-8">
|
||||||
<!-- Main slider container -->
|
<!-- Main slider container -->
|
||||||
<div class="slider-track animate-slide flex">
|
<div class="slider-track animate-slide flex">
|
||||||
{
|
{[...skills, ...skills, ...skills].map((skill: Skill) => {
|
||||||
[...skills, ...skills, ...skills].map((skill: Skill) => {
|
return (
|
||||||
return (
|
<div class="skill-card card-base transform hover:-translate-y-2 hover:scale-105 transition-all duration-300 mx-2 min-w-55 sm:mx-4 sm:min-w-70">
|
||||||
<div
|
<div class="p-4 sm:p-6">
|
||||||
class={`skill-card transform rounded-xl transition-all duration-300 ${baseClasses} ${borderClasses} ${bgColorClasses} ${hoverClasses} ${shadowClasses}`}
|
<div class="flex items-center justify-between mb-4 sm:mb-6">
|
||||||
>
|
<div class="flex items-center gap-2 sm:gap-4">
|
||||||
<div class="p-4 sm:p-6">
|
<div class="flex items-center justify-center rounded-lg text-primary">
|
||||||
<div class="mb-4 flex items-center justify-between sm:mb-6">
|
<Icon name={skill.icon} class="h-8 w-8 sm:h-12 sm:w-12" />
|
||||||
<div class="flex items-center gap-2 sm:gap-4">
|
|
||||||
<div class="flex transform items-center justify-center rounded-lg text-neutral-800 transition-transform group-hover:rotate-12 dark:text-neutral-200">
|
|
||||||
<Icon name={skill.icon} class="h-8 w-8 sm:h-12 sm:w-12" />
|
|
||||||
</div>
|
|
||||||
<h3 class="text-base font-semibold text-neutral-900 sm:text-xl dark:text-neutral-100">
|
|
||||||
{skill.title}
|
|
||||||
</h3>
|
|
||||||
</div>
|
|
||||||
<span class="rounded-full bg-neutral-200 px-2 py-0.5 font-mono text-xs text-neutral-700 sm:px-2.5 sm:py-1 sm:text-sm dark:bg-neutral-800 dark:text-neutral-300">
|
|
||||||
{skill.level}%
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="relative h-1.5 w-full overflow-hidden rounded-full bg-stone-500/20 sm:h-2 dark:bg-stone-500/20">
|
|
||||||
<div
|
|
||||||
class="progress-bar-animate from-steel via-bermuda to-steel absolute top-0 left-0 h-full rounded-full bg-linear-to-r transition-all duration-1000"
|
|
||||||
style={`width: ${skill.level}%`}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mt-1 flex justify-between font-mono text-[10px] text-neutral-600 sm:mt-2 sm:text-xs dark:text-neutral-400">
|
|
||||||
<span>Beginner</span>
|
|
||||||
<span>Advanced</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
<h3 class="text-neutral-900 dark:text-neutral-100 text-base font-semibold sm:text-xl">
|
||||||
|
{skill.title}
|
||||||
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
);
|
<span class=" bg-neutral-200 dark:bg-neutral-800 text-neutral-700 dark:text-neutral-300 font-mono text-xs sm:text-sm rounded-full px-2 sm:px-2.5 py-0.5 sm:py-1">
|
||||||
})
|
{skill.level}%
|
||||||
}
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="relative bg-stone-500/20 dark:bg-stone-500/20 rounded-full h-1.5 sm:h-2 w-full overflow-hidden">
|
||||||
|
<div
|
||||||
|
class="progress-bar-animate bg-linear-to-r from-steel via-bermuda to-steel absolute top-0 left-0 h-full rounded-full transition-all duration-1000"
|
||||||
|
style={`width: ${skill.level}%`}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-between text-secondary font-mono text-[10px] mt-1 sm:mt-2 sm:text-xs">
|
||||||
|
<span>Beginner</span>
|
||||||
|
<span>Advanced</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Gradient overlays -->
|
<!-- Gradient overlays -->
|
||||||
<div
|
<div class="bg-linear-to-r from-neutral-200 to-transparent dark:from-stone-700 absolute top-0 bottom-0 left-0 z-10 w-12 sm:w-24"/>
|
||||||
class="absolute top-0 bottom-0 left-0 z-10 w-12 bg-linear-to-r from-neutral-200 to-transparent sm:w-24 dark:from-stone-700"
|
<div class="bg-linear-to-l from-neutral-200 to-transparent dark:from-stone-700 absolute top-0 bottom-0 right-0 z-10 w-12 sm:w-24"/>
|
||||||
>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="absolute top-0 right-0 bottom-0 z-10 w-12 bg-linear-to-l from-neutral-200 to-transparent sm:w-24 dark:from-stone-700"
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
document.addEventListener('astro:page-load', () => {
|
document.addEventListener('astro:page-load', () => {
|
||||||
// Create infinite scrolling effect
|
|
||||||
function setupInfiniteScroll() {
|
function setupInfiniteScroll() {
|
||||||
const cards = document.querySelectorAll('.skill-card');
|
const cards = document.querySelectorAll('.skill-card');
|
||||||
if (!cards.length) return;
|
if (!cards.length) return;
|
||||||
@@ -93,7 +70,6 @@ const shadowClasses = 'shadow-xs hover:shadow-lg';
|
|||||||
|
|
||||||
setupInfiniteScroll();
|
setupInfiniteScroll();
|
||||||
|
|
||||||
// Add hover effects to cards - only on non-touch devices
|
|
||||||
const isTouchDevice = 'ontouchstart' in window || navigator.maxTouchPoints > 0;
|
const isTouchDevice = 'ontouchstart' in window || navigator.maxTouchPoints > 0;
|
||||||
const cards = document.querySelectorAll('.skill-card');
|
const cards = document.querySelectorAll('.skill-card');
|
||||||
|
|
||||||
@@ -144,7 +120,7 @@ const shadowClasses = 'shadow-xs hover:shadow-lg';
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
/* Tech Stack Slider */
|
/* Specific css to enable sliding effect */
|
||||||
.slider-track {
|
.slider-track {
|
||||||
width: fit-content;
|
width: fit-content;
|
||||||
animation: scroll 40s linear infinite;
|
animation: scroll 40s linear infinite;
|
||||||
@@ -155,7 +131,7 @@ const shadowClasses = 'shadow-xs hover:shadow-lg';
|
|||||||
transform: translateX(0);
|
transform: translateX(0);
|
||||||
}
|
}
|
||||||
100% {
|
100% {
|
||||||
transform: translateX(calc(-220px * 6 - 16px * 6)); /* Card width + margin for mobile */
|
transform: translateX(calc(-220px * 6 - 16px * 6));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -169,7 +145,7 @@ const shadowClasses = 'shadow-xs hover:shadow-lg';
|
|||||||
transform: translateX(0);
|
transform: translateX(0);
|
||||||
}
|
}
|
||||||
100% {
|
100% {
|
||||||
transform: translateX(calc(-280px * 6 - 32px * 6)); /* Card width + margin for desktop */
|
transform: translateX(calc(-280px * 6 - 32px * 6));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -17,7 +17,6 @@ const { forecastDays, error } = await getFiveDayForecast(latitude, longitude, ti
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{error ? (
|
{error ? (
|
||||||
<div class="card-base p-10 text-accent text-center">
|
<div class="card-base p-10 text-accent text-center">
|
||||||
{error}
|
{error}
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ import { readSingleton } from '@directus/sdk';
|
|||||||
|
|
||||||
import directus from '@lib/directus';
|
import directus from '@lib/directus';
|
||||||
import BaseLayout from '@layouts/BaseLayout.astro';
|
import BaseLayout from '@layouts/BaseLayout.astro';
|
||||||
import GoBack from '@/components/buttons/GoBack.astro';
|
import GoBackButton from '@/components/buttons/GoBackButton.astro';
|
||||||
import GoHome from '@/components/buttons/GoHome.astro';
|
import GoHomeButton from '@/components/buttons/GoHomeButton.astro';
|
||||||
|
|
||||||
const global = await directus.request(readSingleton('site_global'));
|
const global = await directus.request(readSingleton('site_global'));
|
||||||
---
|
---
|
||||||
@@ -61,8 +61,8 @@ const global = await directus.request(readSingleton('site_global'));
|
|||||||
<div
|
<div
|
||||||
class="smooth-reveal mt-10 flex flex-col items-center justify-center gap-4 sm:flex-row"
|
class="smooth-reveal mt-10 flex flex-col items-center justify-center gap-4 sm:flex-row"
|
||||||
>
|
>
|
||||||
<GoBack/>
|
<GoBackButton/>
|
||||||
<GoHome url={global.site_url} />
|
<GoHomeButton url={global.site_url} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -4,10 +4,10 @@ import { readSingleton } from '@directus/sdk';
|
|||||||
import directus from '@lib/directus';
|
import directus from '@lib/directus';
|
||||||
import BaseLayout from '@layouts/BaseLayout.astro';
|
import BaseLayout from '@layouts/BaseLayout.astro';
|
||||||
import HeroSection from '@components/sections/HeroSection.astro';
|
import HeroSection from '@components/sections/HeroSection.astro';
|
||||||
import Experience from '@components/sections/Experience.astro';
|
import ExperienceSection from '@components/sections/ExperienceSection.astro';
|
||||||
import EducationSection from '@components/sections/EducationSection.astro';
|
import EducationSection from '@components/sections/EducationSection.astro';
|
||||||
import ProjectSection from '@components/sections/ProjectSection.astro';
|
import ProjectSection from '@components/sections/ProjectSection.astro';
|
||||||
import SkillsSlider from '@components/sections/SkillsSlider.astro';
|
import SkillsSliderSection from '@components/sections/SkillsSliderSection.astro';
|
||||||
|
|
||||||
import portraitImg from '@images/portrait.avif';
|
import portraitImg from '@images/portrait.avif';
|
||||||
|
|
||||||
@@ -44,10 +44,10 @@ const global = await directus.request(readSingleton('site_global'));
|
|||||||
|
|
||||||
<section class="mx-auto max-w-7xl px-4 py-10 sm:px-6 lg:px-8 lg:py-14">
|
<section class="mx-auto max-w-7xl px-4 py-10 sm:px-6 lg:px-8 lg:py-14">
|
||||||
<div class="flex flex-col gap-y-24 md:gap-y-32">
|
<div class="flex flex-col gap-y-24 md:gap-y-32">
|
||||||
<Experience className="smooth-reveal" />
|
<ExperienceSection className="smooth-reveal" />
|
||||||
<EducationSection className="smooth-reveal" />
|
<EducationSection className="smooth-reveal" />
|
||||||
<ProjectSection className="smooth-reveal" />
|
<ProjectSection className="smooth-reveal" />
|
||||||
<SkillsSlider className="smooth-reveal" />
|
<SkillsSliderSection className="smooth-reveal" />
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import { createHighlighter } from 'shiki';
|
|||||||
import { getDirectusImageURL } from '@lib/directusFunctions';
|
import { getDirectusImageURL } from '@lib/directusFunctions';
|
||||||
import BaseLayout from '@layouts/BaseLayout.astro';
|
import BaseLayout from '@layouts/BaseLayout.astro';
|
||||||
import Image from '@components/ui/images/Image.astro';
|
import Image from '@components/ui/images/Image.astro';
|
||||||
import SocialShare from '@components/buttons/SocialShare.astro';
|
import SocialShareButton from '@components/buttons/SocialShareButton.astro';
|
||||||
import { formatDateTime } from '@support/time';
|
import { formatDateTime } from '@support/time';
|
||||||
|
|
||||||
export async function getStaticPaths() {
|
export async function getStaticPaths() {
|
||||||
@@ -163,7 +163,7 @@ const content = marked.parse(post.content);
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
<SocialShare
|
<SocialShareButton
|
||||||
pageTitle={post.title}
|
pageTitle={post.title}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import BaseLayout from '@layouts/BaseLayout.astro';
|
|||||||
import HeroSection from '@components/sections/HeroSection.astro';
|
import HeroSection from '@components/sections/HeroSection.astro';
|
||||||
import FeaturesSection from '@components/sections/FeaturesSection.astro';
|
import FeaturesSection from '@components/sections/FeaturesSection.astro';
|
||||||
import WeatherSection from '@components/sections/WeatherSection.astro';
|
import WeatherSection from '@components/sections/WeatherSection.astro';
|
||||||
import LatestPosts from '@components/sections/LatestPosts.astro';
|
import LatestPostsSection from '@components/sections/LatestPostsSection.astro';
|
||||||
import GiteaSection from '@components/sections/GiteaSection.astro';
|
import GiteaSection from '@components/sections/GiteaSection.astro';
|
||||||
|
|
||||||
import homeImg from '@images/autumn_mountain.png';
|
import homeImg from '@images/autumn_mountain.png';
|
||||||
@@ -54,7 +54,7 @@ const weather = await directus.request(readSingleton('site_weather'));
|
|||||||
>
|
>
|
||||||
</WeatherSection>
|
</WeatherSection>
|
||||||
|
|
||||||
<LatestPosts />
|
<LatestPostsSection />
|
||||||
|
|
||||||
<GiteaSection
|
<GiteaSection
|
||||||
title="Follow me on Gitea"
|
title="Follow me on Gitea"
|
||||||
|
|||||||
@@ -31,6 +31,7 @@
|
|||||||
|
|
||||||
--color-header: light-dark(var(--color-neutral-800), var(--color-neutral-200));
|
--color-header: light-dark(var(--color-neutral-800), var(--color-neutral-200));
|
||||||
--color-primary: light-dark(var(--color-neutral-600), var(--color-neutral-200));
|
--color-primary: light-dark(var(--color-neutral-600), var(--color-neutral-200));
|
||||||
|
--color-primary-hover: light-dark(var(--color-neutral-800), var(--color-neutral-400));
|
||||||
--color-secondary: light-dark(var(--color-neutral-500), var(--color-neutral-400));
|
--color-secondary: light-dark(var(--color-neutral-500), var(--color-neutral-400));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user