feat: animations, responsive design

Animations
- added opacity animation for experience page
- added stagger animation for navbar items on mobile
- added animation for landing page for years and language indications

Pages
- About Page mockup

Redesign
- Navbar to include icons on desktop
This commit is contained in:
2025-02-21 00:07:25 +01:00
parent 32fea815f2
commit 5070f591d3
56 changed files with 2552 additions and 703 deletions

View File

@@ -2,59 +2,61 @@
package components
import "github.com/moh682/portfolio/domain"
import "fmt"
import "slices"
import "strings"
templ experienceCard(exp domain.Experience) {
<section class="min-h-[var(--content-height)] w-full flex relative items-center">
<!-- Left Side: Job Details -->
<div class="p-6 flex flex-col gap-4 transition-all opacity-0">
<h3 class="text-xl font-semibold text-white">{ exp.Job.Title }</h3>
<div class="flex flex-col gap-2">
<a href={ templ.SafeURL(exp.Company.Link) } class="text-emerald-400 font-medium text-lg ">{ exp.Company.Name }</a>
<p class="text-slate-400 ">{ exp.Duration() }</p>
<p class="text-slate-600 text-sm">{ exp.GetStartDate() } - { exp.GetEndDate() } </p>
<a href={ templ.SafeURL(exp.Company.LocationLink) } class="text-emerald-500">{ exp.Company.City }</a>
</div>
<div class="flex flex-wrap gap-2">
for _, stack := range slices.Concat(exp.Job.FrontendStack, exp.Job.BackendStack, exp.Job.InfrastructureStack) {
@techStack(stack)
}
</div>
<div class="flex flex-col gap-2">
<h4 class="text-xl font-medium">About { exp.Company.Name }</h4>
for _, description := range strings.Split(exp.Company.Description, "\n") {
<p class="text-slate-300">
{ description }
</p>
}
</div>
<div class="flex flex-col gap-2">
<h4 class="text-xl font-medium">Role & Responsibilities</h4>
<div class="text-slate-300 whitespace-pre-wrap">
{ exp.Job.Description }
</div>
</div>
</div>
</section>
}
templ ExperiencesPage(experiences []domain.Experience) {
@rootLayout() {
<main class="bg-gradient-to-b from-slate-900 w-full h-full to-slate-800 overflow-hidden">
<div class="relative h-full flex w-full items-center justify-between" x-data="{active:$persist(0)}">
<!-- Progress Indicator -->
<div class="flex items-center px-2 top-0 h-screen z-10 ">
<div class="flex flex-col z-100 gap-4 -translate-y-1/2">
for i := range experiences {
<div
class="flex items-center justify-center pointer-events-auto py-2 px-2 hover:cursor-pointer"
x-on:click={ fmt.Sprintf("active = %d", i) }
>
<div
class={ "h-12 w-1 mb-4 transition-all duration-300 cursor-pointer hover:bg-emerald-400" }
data-index={ templ.JSONString(i) }
x-bind:class={ fmt.Sprintf("active == %d ? 'bg-emerald-500' : 'bg-slate-700'", i) }
></div>
</div>
}
<script defer src="assets/js/experiences.js"></script>
<main class="bg-gradient-to-b from-slate-900 w-full to-slate-800 px-6 overflow-x-hidden">
<div class="flex w-full items-center justify-between" x-data="{active:$persist(0)}">
<div class="relative w-full flex items-center justify-center">
<!-- Timeline -->
<div id="timeline" class="fixed w-10 top-[var(--navbar-height)] left-2 h-[var(--content-height)] flex items-center justify-center">
<div class="absolute bg-radial-[at_50%_25%] from-40% from-slate-700 h-full w-0.5 to-slate-800"></div>
<div id="timeline-point" class={ "flex items-center flex-grow-0 justify-center w-10 h-10 rounded-full bg-emerald-900 border-4 border-slate-800 z-[80]" }>
<div class="absolute w-2 h-2 z-[80] bg-emerald-400 rounded-full"></div>
</div>
</div>
</div>
<div class="w-full h-full flex items-center justify-center">
<div class="w-full h-full items-center content-center relative xl:w-[1600px]">
<div class="text-center py-8 animate-fade">
<h2 class="text-3xl sm:text-4xl font-bold text-white mb-4">Professional Experience</h2>
<p class="text-slate-400">Building and scaling software solutions that matter</p>
</div>
<div class="w-full h-full absolute flex items-center justify-center">
<div class="h-full w-0.5 bg-slate-700 transform"></div>
</div>
<div class="h-full custom-scrollbar scroll-smooth">
<div>
for i, exp := range experiences {
<div
class={ "absolute w-full transition-all opacity-0 duration-700 ease-out translate-y-10" }
data-experience-index={ templ.JSONString(i) }
x-bind:class={ fmt.Sprintf("active == %d ? 'z-10 opacity-100 translate-y-0' : ' -z-10 translate-y-10'", i) }
>
<div class="h-full w-full">
<div class={ fmt.Sprintf("relative flex w-full h-full items-center justify-center translate-y-[%dpx]", i*4*32) }>
<div class="absolute animate-pulse w-16 h-16 rounded-full bg-slate-800/50 z-50"></div>
<div class={ "flex items-center flex-grow-0 justify-center w-10 h-10 rounded-full bg-emerald-900 border-4 border-slate-800 z-[80]" }>
<div class="absolute w-2 h-2 z-[80] bg-emerald-400 rounded-full"></div>
</div>
</div>
</div>
@experienceCard(exp)
</div>
}
</div>
</div>
<div id="exps-container" class="ml-4 items-center content-center relative xl:w-[1600px]">
for _, exp := range experiences {
@experienceCard(exp)
}
</div>
</div>
</div>