chore: merge remote main history
This commit is contained in:
36
.github/workflows/release.yml
vendored
Normal file
36
.github/workflows/release.yml
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
name: Release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
packages: write
|
||||
|
||||
jobs:
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: 1.22
|
||||
cache: true
|
||||
- uses: ko-build/setup-ko@v0.7
|
||||
- uses: sigstore/cosign-installer@v3.7.0
|
||||
with:
|
||||
cosign-release: v2.2.3
|
||||
- uses: goreleaser/goreleaser-action@v5
|
||||
with:
|
||||
version: v1.24.0
|
||||
args: release --clean
|
||||
env:
|
||||
GITHUB_TOKEN: '${{ secrets.GITHUB_TOKEN }}'
|
||||
COSIGN_PASSWORD: '${{ secrets.COSIGN_PASSWORD }}'
|
||||
COSIGN_PRIVATE_KEY: '${{ secrets.COSIGN_PRIVATE_KEY }}'
|
||||
COSIGN_PUBLIC_KEY: '${{ secrets.COSIGN_PUBLIC_KEY }}'
|
||||
16
Dockerfile
Normal file
16
Dockerfile
Normal file
@@ -0,0 +1,16 @@
|
||||
# Build.
|
||||
FROM golang:1.24.0 AS build-stage
|
||||
WORKDIR /app
|
||||
COPY go.mod go.sum ./
|
||||
RUN go mod download
|
||||
COPY . /app
|
||||
RUN CGO_ENABLED=0 GOOS=linux go build -o ./entrypoint
|
||||
|
||||
# Deploy.
|
||||
FROM gcr.io/distroless/static-debian11 AS release-stage
|
||||
WORKDIR /
|
||||
COPY --from=build-stage /app/entrypoint /entrypoint
|
||||
COPY --from=build-stage /app/assets /assets
|
||||
EXPOSE 8080
|
||||
USER nonroot:nonroot
|
||||
ENTRYPOINT ["/entrypoint"]
|
||||
17
Makefile
Normal file
17
Makefile
Normal file
@@ -0,0 +1,17 @@
|
||||
# run templ generation in watch mode to detect all .templ files and
|
||||
# re-create _templ.txt files on change, then send reload event to browser.
|
||||
# Default url: http://localhost:7331
|
||||
live/templ:
|
||||
templ generate --notify-proxy --proxybind="localhost" --proxyport="8080"
|
||||
|
||||
# run air to detect any go file changes to re-build and re-run the server.
|
||||
live/server:
|
||||
templ generate --watch --proxy="http://localhost:8080" --cmd="go run ."
|
||||
|
||||
# run esbuild to generate the index.js bundle in watch mode.
|
||||
live/rollup:
|
||||
npm --prefix ts run build:watch
|
||||
|
||||
# start all 5 watch processes in parallel.
|
||||
live:
|
||||
make -j5 live/rollup live/server
|
||||
BIN
assets/.DS_Store
vendored
Normal file
BIN
assets/.DS_Store
vendored
Normal file
Binary file not shown.
65
assets/css/styles.css
Normal file
65
assets/css/styles.css
Normal file
@@ -0,0 +1,65 @@
|
||||
@layer base {
|
||||
:root {
|
||||
--navbar-height: 6rem;
|
||||
--content-height: calc(100vh - var(--navbar-height));
|
||||
}
|
||||
|
||||
main {
|
||||
margin-top: var(--navbar-height);
|
||||
height: 100%;
|
||||
padding-bottom: 6rem;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.navbar-drawer {
|
||||
height: var(--content-height);
|
||||
}
|
||||
|
||||
[x-cloak] {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes slideIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.animate-fade-in {
|
||||
opacity: 0;
|
||||
animation: fadeIn 1s ease-in forwards;
|
||||
}
|
||||
|
||||
.animate-slide-in {
|
||||
opacity: 0;
|
||||
animation: slideIn 1s ease-out forwards;
|
||||
}
|
||||
|
||||
.delay-300 {
|
||||
animation-delay: 300ms;
|
||||
}
|
||||
|
||||
.delay-600 {
|
||||
animation-delay: 600ms;
|
||||
}
|
||||
|
||||
.delay-900 {
|
||||
animation-delay: 900ms;
|
||||
}
|
||||
BIN
assets/images/invoice-manager-thumbnail.png
Normal file
BIN
assets/images/invoice-manager-thumbnail.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 69 KiB |
BIN
assets/images/portfolio-photo.webp
Normal file
BIN
assets/images/portfolio-photo.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 38 KiB |
BIN
assets/images/profile.webp
Normal file
BIN
assets/images/profile.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 78 KiB |
2
assets/js/experiences.js
Normal file
2
assets/js/experiences.js
Normal file
@@ -0,0 +1,2 @@
|
||||
!function(){"use strict";const{animate:i,inView:t}=Motion;t("section div",(t=>(i(t,{opacity:1},{duration:.7}),()=>i(t,{opacity:0},{duration:.7}))),{some:.7,initial:!1})}();
|
||||
//# sourceMappingURL=experiences.js.map
|
||||
1
assets/js/experiences.js.map
Normal file
1
assets/js/experiences.js.map
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"experiences.js","sources":["../../ts/src/experiences.ts"],"sourcesContent":["const { animate, inView } = Motion;\n\ninView(\n \"section div\",\n (element) => {\n animate(\n element,\n { opacity: 1 },\n {\n duration: 0.7,\n },\n );\n return () => animate(element, { opacity: 0 }, { duration: 0.7 });\n },\n { some: 0.7, initial: false },\n);\n"],"names":["animate","inView","Motion","element","opacity","duration","some","initial"],"mappings":"yBAAA,MAAMA,QAAEA,EAAOC,OAAEA,GAAWC,OAE5BD,EACE,eACCE,IACCH,EACEG,EACA,CAAEC,QAAS,GACX,CACEC,SAAU,KAGP,IAAML,EAAQG,EAAS,CAAEC,QAAS,GAAK,CAAEC,SAAU,OAE5D,CAAEC,KAAM,GAAKC,SAAS"}
|
||||
2
assets/js/landing.js
Normal file
2
assets/js/landing.js
Normal file
@@ -0,0 +1,2 @@
|
||||
!function(){"use strict";const{animate:e,stagger:n}=Motion,t=document.getElementById("years-experience");if(!t)throw new Error("years-experience Element not found");const o=document.getElementById("programming-languages");if(!o)throw new Error("languages Element not found");e(0,6,{duration:1.5,ease:"circOut",onUpdate:e=>{const n=Math.floor(e);t.innerHTML=String(n)}}),e(0,4,{duration:1.5,ease:"circOut",onUpdate:e=>{const n=Math.floor(e);o.innerHTML=String(n)}}),e("#years-experience",{count:5},{duration:1.5,delay:n(.1)}),e("#languages",{count:5},{duration:1.5,delay:n(.1)})}();
|
||||
//# sourceMappingURL=landing.js.map
|
||||
1
assets/js/landing.js.map
Normal file
1
assets/js/landing.js.map
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"landing.js","sources":["../../ts/src/landing.ts"],"sourcesContent":["const { animate, stagger } = Motion;\n\nconst experienceCountSelector = \"#years-experience\";\nconst languagesCountSelector = \"#languages\";\n\nconst yrsExpCount = document.getElementById(\"years-experience\");\nif (!yrsExpCount) {\n throw new Error(\"years-experience Element not found\");\n}\n\nconst langCount = document.getElementById(\"programming-languages\");\nif (!langCount) {\n throw new Error(\"languages Element not found\");\n}\n\nanimate(0, 6, {\n duration: 1.5,\n ease: \"circOut\",\n onUpdate: (latest) => {\n const yrs = Math.floor(latest);\n yrsExpCount.innerHTML = String(yrs);\n },\n});\n\nanimate(0, 4, {\n duration: 1.5,\n ease: \"circOut\",\n onUpdate: (latest) => {\n const languages = Math.floor(latest);\n langCount.innerHTML = String(languages);\n },\n});\n\nanimate(\n experienceCountSelector,\n { count: 5 },\n { duration: 1.5, delay: stagger(0.1) },\n);\nanimate(\n languagesCountSelector,\n { count: 5 },\n { duration: 1.5, delay: stagger(0.1) },\n);\n"],"names":["animate","stagger","Motion","yrsExpCount","document","getElementById","Error","langCount","duration","ease","onUpdate","latest","yrs","Math","floor","innerHTML","String","languages","count","delay"],"mappings":"yBAAA,MAAMA,QAAEA,EAAOC,QAAEA,GAAYC,OAKvBC,EAAcC,SAASC,eAAe,oBAC5C,IAAKF,EACH,MAAM,IAAIG,MAAM,sCAGlB,MAAMC,EAAYH,SAASC,eAAe,yBAC1C,IAAKE,EACH,MAAM,IAAID,MAAM,+BAGlBN,EAAQ,EAAG,EAAG,CACZQ,SAAU,IACVC,KAAM,UACNC,SAAWC,IACT,MAAMC,EAAMC,KAAKC,MAAMH,GACvBR,EAAYY,UAAYC,OAAOJ,EAAI,IAIvCZ,EAAQ,EAAG,EAAG,CACZQ,SAAU,IACVC,KAAM,UACNC,SAAWC,IACT,MAAMM,EAAYJ,KAAKC,MAAMH,GAC7BJ,EAAUQ,UAAYC,OAAOC,EAAU,IAI3CjB,EA/BgC,oBAiC9B,CAAEkB,MAAO,GACT,CAAEV,SAAU,IAAKW,MAAOlB,EAAQ,MAElCD,EAnC+B,aAqC7B,CAAEkB,MAAO,GACT,CAAEV,SAAU,IAAKW,MAAOlB,EAAQ"}
|
||||
2
assets/js/navigation.js
Normal file
2
assets/js/navigation.js
Normal file
@@ -0,0 +1,2 @@
|
||||
!function(){"use strict";const{animate:e,stagger:i}=Motion,t="#mobile-nav>li";document.addEventListener("alpine:init",(()=>{Alpine.data("navbar",(()=>({isOpen:!1,open(){this.isOpen=!0,e(t,{opacity:[0,1],x:[-50,0]},{delay:i(.1)})},toggle(){this.isOpen?this.close():this.open()},close(){setTimeout((()=>{this.isOpen=!1}),300),e(t,{opacity:[1,0],x:[0,50]},{delay:i(.1)})}})))}))}();
|
||||
//# sourceMappingURL=navigation.js.map
|
||||
1
assets/js/navigation.js.map
Normal file
1
assets/js/navigation.js.map
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"navigation.js","sources":["../../ts/src/navigation.ts"],"sourcesContent":["const { animate, stagger } = Motion;\n\nconst navItemSelector = \"#mobile-nav>li\";\n\ndocument.addEventListener(\"alpine:init\", () => {\n Alpine.data(\"navbar\", () => {\n return {\n isOpen: false,\n open() {\n this.isOpen = true;\n animate(\n navItemSelector,\n { opacity: [0, 1], x: [-50, 0] },\n { delay: stagger(0.1) },\n );\n },\n toggle() {\n this.isOpen ? this.close() : this.open();\n },\n close() {\n setTimeout(() => {\n this.isOpen = false;\n }, 300);\n animate(\n navItemSelector,\n { opacity: [1, 0], x: [0, 50] },\n { delay: stagger(0.1) },\n );\n },\n };\n });\n});\n"],"names":["animate","stagger","Motion","navItemSelector","document","addEventListener","Alpine","data","isOpen","open","this","opacity","x","delay","toggle","close","setTimeout"],"mappings":"yBAAA,MAAMA,QAAEA,EAAOC,QAAEA,GAAYC,OAEvBC,EAAkB,iBAExBC,SAASC,iBAAiB,eAAe,KACvCC,OAAOC,KAAK,UAAU,KACb,CACLC,QAAQ,EACR,IAAAC,GACEC,KAAKF,QAAS,EACdR,EACEG,EACA,CAAEQ,QAAS,CAAC,EAAG,GAAIC,EAAG,EAAC,GAAK,IAC5B,CAAEC,MAAOZ,EAAQ,KAEpB,EACD,MAAAa,GACEJ,KAAKF,OAASE,KAAKK,QAAUL,KAAKD,MACnC,EACD,KAAAM,GACEC,YAAW,KACTN,KAAKF,QAAS,CAAK,GAClB,KACHR,EACEG,EACA,CAAEQ,QAAS,CAAC,EAAG,GAAIC,EAAG,CAAC,EAAG,KAC1B,CAAEC,MAAOZ,EAAQ,KAEpB,KAEH"}
|
||||
126
assets/json/experiences.json
Normal file
126
assets/json/experiences.json
Normal file
@@ -0,0 +1,126 @@
|
||||
[
|
||||
{
|
||||
"start_date": "2022-07-01T00:00:00.000Z",
|
||||
"end_date": null,
|
||||
"company": {
|
||||
"name": "Awaze",
|
||||
"logo": "",
|
||||
"link": "https://www.awaze.com/",
|
||||
"location_link": "https://www.google.com/maps/place//data=!4m2!3m1!1s0x465252660a2125a1:0x5a2e60c905a1fd5a?sa=X&ved=1t:8290&ictx=111",
|
||||
"city": "Virum (Copenhagen)",
|
||||
"description": "Awaze is the largest managed vacation rentals and holiday resorts business in Europe, which brings together some of the continent's most trusted travel brands, including cottages.com, Hoseasons and NOVASOL."
|
||||
},
|
||||
"Job": {
|
||||
"title": "Software Engineer",
|
||||
"description": "I am working as a Software Engineer in the Awaze's Digital team. I am responsible for developing and maintaining the company's guest facing applications, that include Authentication, Booking Management and (FAQ) Help pages",
|
||||
"objectives": ["Objective 1", "Objective 2", "Objective 3"],
|
||||
"frontend_stack": [
|
||||
{ "name": "NextJS", "link": "https://nextjs.org/" },
|
||||
{ "name": "React", "link": "https://reactjs.org/" },
|
||||
{ "name": "Zustand", "link": "https://zustand-demo.pmnd.rs/" },
|
||||
{ "name": "Bit", "link": "https://bit.dev/" },
|
||||
{
|
||||
"name": "ApolloClient",
|
||||
"link": "https://www.apollographql.com/docs/react"
|
||||
},
|
||||
{ "name": "Storybook", "link": "https://storybook.js.org/" },
|
||||
{
|
||||
"name": "Styled-Components",
|
||||
"link": "https://styled-components.com/"
|
||||
}
|
||||
],
|
||||
"backend_stack": [
|
||||
{ "name": "Nodejs", "link": "https://nodejs.org/en" },
|
||||
{ "name": "GraphQL", "link": "https://graphql.org/" },
|
||||
{ "name": "Apollo", "link": "https://www.apollographql.com/docs/" },
|
||||
{ "name": "Lambda", "link": "https://aws.amazon.com/lambda/" },
|
||||
{ "name": "Traveller (MSSQL)", "link": "" }
|
||||
],
|
||||
"infrastructure_stack": [
|
||||
{ "name": "Serverless", "link": "https://www.serverless.com/" },
|
||||
{ "name": "Cognito", "link": "https://aws.amazon.com/cognito/" },
|
||||
{ "name": "AWS SES", "link": "" },
|
||||
{ "name": "Github", "link": "https://github.com/" }
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"start_date": "2021-08-01T00:00:00.000Z",
|
||||
"end_date": "2022-07-30T00:00:00.000Z",
|
||||
"company": {
|
||||
"name": "Kiapro",
|
||||
"logo": "",
|
||||
"link": "https://www.kiapro.dk/om-kiapro",
|
||||
"location_link": "https://www.google.com/maps/place//data=!4m2!3m1!1s0x46525052d1d32a6d:0x47b68788842d92aa?sa=X&ved=1t:8290&ictx=111",
|
||||
"city": "Herlev (Copenhagen)",
|
||||
"description": "Kiapro is a Danish company that provides software solutions both for Fin-Tech and Healthcare sector. The company's main product is a platform that helps Fin-Tech management engage with employees through an interactive intranet.\nOther products include all-round applications for Chiropractors to follow patients through their transformation which is called BackTrace."
|
||||
},
|
||||
"Job": {
|
||||
"title": "Full Stack Developer",
|
||||
"description": "As a Full Stack Developer at Doctolib, I contributed to the development of the core booking platform and various healthcare professional tools, focusing on both performance optimization and feature development.",
|
||||
"objectives": [
|
||||
"Improved platform performance and scalability",
|
||||
"Developed new features for healthcare professionals",
|
||||
"Implemented real-time notification systems"
|
||||
],
|
||||
"frontend_stack": [
|
||||
{ "name": "React", "link": "https://reactjs.org/" },
|
||||
{ "name": "Redux", "link": "https://redux.js.org/" },
|
||||
{ "name": "Capacitor", "link": "https://capacitorjs.com/" },
|
||||
{ "name": "TypeScript", "link": "https://www.typescriptlang.org/" }
|
||||
],
|
||||
"backend_stack": [
|
||||
{ "name": "PHP", "link": "https://www.ruby-lang.org/" },
|
||||
{ "name": "PostgreSQL", "link": "https://www.postgresql.org/" },
|
||||
{ "name": "Go", "link": "https://go.dev/" },
|
||||
{ "name": "Fiber", "link": "https://gofiber.io/" },
|
||||
{ "name": "Laravel", "link": "https://laravel.com/" }
|
||||
],
|
||||
"infrastructure_stack": [
|
||||
{ "name": "Docker", "link": "https://www.docker.com/" },
|
||||
{ "name": "Self-hosted VM", "link": "" }
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"start_date": "2019-10-01T00:00:00.000Z",
|
||||
"end_date": "2021-07-31T00:00:00.000Z",
|
||||
"company": {
|
||||
"name": "Fellowmind",
|
||||
"logo": "",
|
||||
"link": "https://www.fellowmind.com/da-dk/",
|
||||
"location_link": "https://maps.google.com/maps?hl=da&gl=se&um=1&ie=UTF-8&fb=1&sa=X&ftid=0x465253083f4d1201:0x8e4ee256108ec86a",
|
||||
"city": "Copenhagen Area",
|
||||
"description": "Fellowmind helps customers with accelerating their digital transformation by using Microsoft's cloud-solutions, progress to agile development, implement integrated platforms and help end-users to apply those techonologies. With other words, to allow people to enjoy working with technology, and to make the technology work for them."
|
||||
},
|
||||
"Job": {
|
||||
"title": "Software Engineer",
|
||||
"description": "I was apart of the team called IntraActive which was responsible for creating Interactive Components that can be added to their pre-built Microsoft pages. I Developed and maintained a customisable Phonebook that can be configured to any company theme.",
|
||||
"objectives": [
|
||||
"Developed new monitoring integrations",
|
||||
"Optimized data processing pipelines",
|
||||
"Enhanced visualization components"
|
||||
],
|
||||
"frontend_stack": [
|
||||
{
|
||||
"name": "Microsoft SPFX",
|
||||
"link": "https://learn.microsoft.com/en-us/sharepoint/dev/spfx/sharepoint-framework-overview"
|
||||
},
|
||||
{
|
||||
"name": "Microsoft Sharepoint",
|
||||
"link": "https://learn.microsoft.com/en-us/sharepoint/"
|
||||
},
|
||||
{ "name": "TypeScript", "link": "https://www.typescriptlang.org/" },
|
||||
{ "name": "React", "link": "https://reactjs.org/" },
|
||||
{ "name": "Mobx", "link": "https://mobx.js.org/README.html" }
|
||||
],
|
||||
"backend_stack": [
|
||||
{ "name": "C#", "link": "" },
|
||||
{ "name": ".NET", "link": "" },
|
||||
{ "name": "Azure Functions", "link": "" },
|
||||
{ "name": "Cosmos", "link": "" }
|
||||
],
|
||||
"infrastructure_stack": [{ "name": "Microservices", "link": "" }]
|
||||
}
|
||||
}
|
||||
]
|
||||
30
assets/json/projects.json
Normal file
30
assets/json/projects.json
Normal file
@@ -0,0 +1,30 @@
|
||||
[
|
||||
{
|
||||
"name": "Invoice Management System",
|
||||
"start": null,
|
||||
"end": null,
|
||||
"stack": [
|
||||
"Go",
|
||||
"Wails",
|
||||
"React",
|
||||
"SQLite3",
|
||||
"TailwindCSS",
|
||||
"MacOS",
|
||||
"Windows"
|
||||
],
|
||||
"description": "Invoie Management System that helps mechanics create invoices, manage customers, finances and more.",
|
||||
"thumbnail": "/assets/images/invoice-manager-thumbnail.png",
|
||||
"github_link": "",
|
||||
"status": "In Progress"
|
||||
},
|
||||
{
|
||||
"name": "Portfolio",
|
||||
"start": null,
|
||||
"end": null,
|
||||
"stack": ["Go", "Templ", "Motion", "TailwindCSS", "Typescript", "AlpineJS"],
|
||||
"description": "The website you are currently on. A portfolio website that showcases my projects, experiences and skills.",
|
||||
"thumbnail": "",
|
||||
"github_link": "",
|
||||
"status": "In Progress"
|
||||
}
|
||||
]
|
||||
202
components/about_page.templ
Normal file
202
components/about_page.templ
Normal file
@@ -0,0 +1,202 @@
|
||||
package components
|
||||
|
||||
templ AboutPage() {
|
||||
@rootLayout() {
|
||||
<main class="w-full min-h-full py-16 px-4 sm:px-6 lg:px-8">
|
||||
<div class="max-w-4xl mx-auto flex flex-col gap-16">
|
||||
<!-- Hero Section -->
|
||||
<section class="text-center flex flex-col items-center gap-6">
|
||||
<div class="relative inline-block">
|
||||
<img
|
||||
src="/assets/images/profile.jpg"
|
||||
alt="Mohammad Hariri"
|
||||
class="w-40 h-40 rounded-full object-cover border-4 border-emerald-500 shadow-xl"
|
||||
/>
|
||||
<div class="absolute -bottom-2 left-1/2 -translate-x-1/2 bg-emerald-500 text-white px-3 py-1 rounded-full text-sm font-medium whitespace-nowrap">
|
||||
Available for Work
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h1 class="text-4xl font-bold text-slate-100 mb-4">Your Name</h1>
|
||||
<p class="text-xl text-slate-300">Full Stack Developer & Problem Solver</p>
|
||||
</div>
|
||||
<p class="text-slate-400 max-w-2xl mx-auto">
|
||||
Passionate about crafting elegant solutions to complex problems. With expertise in modern web technologies,
|
||||
I bring ideas to life through clean code and intuitive user experiences.
|
||||
</p>
|
||||
</section>
|
||||
<!-- Journey Section -->
|
||||
<section class="bg-slate-800/50 rounded-2xl p-8 backdrop-blur-sm">
|
||||
<h2 class="text-2xl font-bold text-slate-100">My Journey</h2>
|
||||
<div class="flex flex-col gap-8 mt-6">
|
||||
<div class="flex gap-4">
|
||||
<div class="flex-shrink-0 w-1 bg-emerald-500 rounded relative">
|
||||
<div class="absolute w-3 h-3 bg-emerald-500 rounded-full -left-1 top-0"></div>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="text-lg font-semibold text-emerald-400">Where I Started</h3>
|
||||
<p class="text-slate-300 mt-2">
|
||||
My journey in software development began with a curiosity about how things work.
|
||||
From building simple websites to developing complex applications, every step has been a learning experience.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex gap-4">
|
||||
<div class="flex-shrink-0 w-1 bg-emerald-500 rounded relative">
|
||||
<div class="absolute w-3 h-3 bg-emerald-500 rounded-full -left-1 top-0"></div>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="text-lg font-semibold text-emerald-400">Where I Am</h3>
|
||||
<p class="text-slate-300 mt-2">
|
||||
Currently, I specialize in full-stack development, focusing on creating scalable and maintainable applications.
|
||||
I'm passionate about using technology to solve real-world problems.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex gap-4">
|
||||
<div class="flex-shrink-0 w-1 bg-emerald-500 rounded relative">
|
||||
<div class="absolute w-3 h-3 bg-emerald-500 rounded-full -left-1 top-0"></div>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="text-lg font-semibold text-emerald-400">Where I'm Going</h3>
|
||||
<p class="text-slate-300 mt-2">
|
||||
My goal is to continue growing as a developer while contributing to meaningful projects that make a difference.
|
||||
I'm always excited to learn new technologies and tackle new challenges.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<!-- Skills Section -->
|
||||
<section class="flex flex-col gap-6">
|
||||
<h2 class="text-2xl font-bold text-slate-100">Technical Expertise</h2>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
<div class="bg-slate-800/50 p-6 rounded-xl backdrop-blur-sm">
|
||||
<h3 class="text-lg font-semibold text-emerald-400">Frontend Development</h3>
|
||||
<ul class="flex flex-col gap-2 text-slate-300 mt-4">
|
||||
<li class="flex items-center">
|
||||
<svg class="w-5 h-5 text-emerald-500 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
|
||||
</svg>
|
||||
React & Next.js
|
||||
</li>
|
||||
<li class="flex items-center">
|
||||
<svg class="w-5 h-5 text-emerald-500 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
|
||||
</svg>
|
||||
TypeScript
|
||||
</li>
|
||||
<li class="flex items-center">
|
||||
<svg class="w-5 h-5 text-emerald-500 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
|
||||
</svg>
|
||||
Tailwind CSS
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="bg-slate-800/50 p-6 rounded-xl backdrop-blur-sm">
|
||||
<h3 class="text-lg font-semibold text-emerald-400">Backend Development</h3>
|
||||
<ul class="flex flex-col gap-2 text-slate-300 mt-4">
|
||||
<li class="flex items-center">
|
||||
<svg class="w-5 h-5 text-emerald-500 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
|
||||
</svg>
|
||||
Go & Fiber
|
||||
</li>
|
||||
<li class="flex items-center">
|
||||
<svg class="w-5 h-5 text-emerald-500 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
|
||||
</svg>
|
||||
Node.js
|
||||
</li>
|
||||
<li class="flex items-center">
|
||||
<svg class="w-5 h-5 text-emerald-500 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
|
||||
</svg>
|
||||
PostgreSQL
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="bg-slate-800/50 p-6 rounded-xl backdrop-blur-sm">
|
||||
<h3 class="text-lg font-semibold text-emerald-400">Tools & Practices</h3>
|
||||
<ul class="flex flex-col gap-2 text-slate-300 mt-4">
|
||||
<li class="flex items-center">
|
||||
<svg class="w-5 h-5 text-emerald-500 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
|
||||
</svg>
|
||||
Git & GitHub
|
||||
</li>
|
||||
<li class="flex items-center">
|
||||
<svg class="w-5 h-5 text-emerald-500 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
|
||||
</svg>
|
||||
Docker
|
||||
</li>
|
||||
<li class="flex items-center">
|
||||
<svg class="w-5 h-5 text-emerald-500 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
|
||||
</svg>
|
||||
CI/CD
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<!-- Personal Section -->
|
||||
<section class="bg-slate-800/50 rounded-2xl p-8 backdrop-blur-sm">
|
||||
<h2 class="text-2xl font-bold text-slate-100 mb-6">Beyond the Code</h2>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-8">
|
||||
<div>
|
||||
<h3 class="text-lg font-semibold text-emerald-400">When I'm Not Coding</h3>
|
||||
<p class="text-slate-300">
|
||||
Outside of development, I enjoy [Your Hobbies/Interests]. I believe in maintaining a healthy work-life balance
|
||||
and finding inspiration in various aspects of life.
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="text-lg font-semibold text-emerald-400">Let's Connect</h3>
|
||||
<div class="flex flex-col gap-4">
|
||||
<p class="text-slate-300">
|
||||
I'm always interested in hearing about new projects and opportunities.
|
||||
Feel free to reach out if you'd like to collaborate or just chat about technology.
|
||||
</p>
|
||||
<div class="flex gap-4">
|
||||
<a
|
||||
href="https://github.com/yourusername"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="text-slate-400 hover:text-emerald-500 transition-colors"
|
||||
>
|
||||
<svg class="h-6 w-6" fill="currentColor" viewBox="0 0 24 24">
|
||||
<path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"></path>
|
||||
</svg>
|
||||
</a>
|
||||
<a
|
||||
href="https://linkedin.com/in/yourprofile"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="text-slate-400 hover:text-emerald-500 transition-colors"
|
||||
>
|
||||
<svg class="h-6 w-6" fill="currentColor" viewBox="0 0 24 24">
|
||||
<path d="M19 0h-14c-2.761 0-5 2.239-5 5v14c0 2.761 2.239 5 5 5h14c2.762 0 5-2.239 5-5v-14c0-2.761-2.238-5-5-5zm-11 19h-3v-11h3v11zm-1.5-12.268c-.966 0-1.75-.79-1.75-1.764s.784-1.764 1.75-1.764 1.75.79 1.75 1.764-.783 1.764-1.75 1.764zm13.5 12.268h-3v-5.604c0-3.368-4-3.113-4 0v5.604h-3v-11h3v1.765c1.396-2.586 7-2.777 7 2.476v6.759z"></path>
|
||||
</svg>
|
||||
</a>
|
||||
<a
|
||||
href="https://twitter.com/yourhandle"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="text-slate-400 hover:text-emerald-500 transition-colors"
|
||||
>
|
||||
<svg class="h-6 w-6" fill="currentColor" viewBox="0 0 24 24">
|
||||
<path d="M23.953 4.57a10 10 0 01-2.825.775 4.958 4.958 0 002.163-2.723c-.951.555-2.005.959-3.127 1.184a4.92 4.92 0 00-8.384 4.482C7.69 8.095 4.067 6.13 1.64 3.162a4.822 4.822 0 00-.666 2.475c0 1.71.87 3.213 2.188 4.096a4.904 4.904 0 01-2.228-.616v.06a4.923 4.923 0 003.946 4.827 4.996 4.996 0 01-2.212.085 4.936 4.936 0 004.604 3.417 9.867 9.867 0 01-6.102 2.105c-.39 0-.779-.023-1.17-.067a13.995 13.995 0 007.557 2.209c9.053 0 13.998-7.496 13.998-13.985 0-.21 0-.42-.015-.63A9.935 9.935 0 0024 4.59z"></path>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</main>
|
||||
}
|
||||
}
|
||||
58
components/about_page_templ.go
Normal file
58
components/about_page_templ.go
Normal file
File diff suppressed because one or more lines are too long
109
components/contact_page.templ
Normal file
109
components/contact_page.templ
Normal file
@@ -0,0 +1,109 @@
|
||||
package components
|
||||
|
||||
templ ContactPage() {
|
||||
@rootLayout() {
|
||||
<main class="w-full min-h-full py-16 px-4 sm:px-6 lg:px-8">
|
||||
<div class="max-w-3xl mx-auto">
|
||||
<div class="text-center mb-12">
|
||||
<h1 class="text-4xl font-bold text-slate-100 mb-4">Get in Touch</h1>
|
||||
<p class="text-slate-400">Have a question or want to work together? Let's connect!</p>
|
||||
</div>
|
||||
<div class="bg-slate-800/50 rounded-2xl p-8 backdrop-blur-sm shadow-xl">
|
||||
<form id="contactForm" class="space-y-6" onsubmit="handleSubmit(event)">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div>
|
||||
<label for="name" class="block text-sm font-medium text-slate-300 mb-2">Name</label>
|
||||
<input
|
||||
type="text"
|
||||
id="name"
|
||||
name="name"
|
||||
required
|
||||
class="w-full px-4 py-3 bg-slate-700/50 border border-slate-600 rounded-lg focus:ring-2 focus:ring-emerald-500 focus:border-transparent text-slate-100 placeholder-slate-400 transition-all"
|
||||
placeholder="Your name"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label for="email" class="block text-sm font-medium text-slate-300 mb-2">Email</label>
|
||||
<input
|
||||
type="email"
|
||||
id="email"
|
||||
name="email"
|
||||
required
|
||||
class="w-full px-4 py-3 bg-slate-700/50 border border-slate-600 rounded-lg focus:ring-2 focus:ring-emerald-500 focus:border-transparent text-slate-100 placeholder-slate-400 transition-all"
|
||||
placeholder="your@email.com"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label for="subject" class="block text-sm font-medium text-slate-300 mb-2">Subject</label>
|
||||
<input
|
||||
type="text"
|
||||
id="subject"
|
||||
name="subject"
|
||||
required
|
||||
class="w-full px-4 py-3 bg-slate-700/50 border border-slate-600 rounded-lg focus:ring-2 focus:ring-emerald-500 focus:border-transparent text-slate-100 placeholder-slate-400 transition-all"
|
||||
placeholder="What's this about?"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label for="message" class="block text-sm font-medium text-slate-300 mb-2">Message</label>
|
||||
<textarea
|
||||
id="message"
|
||||
name="message"
|
||||
rows="6"
|
||||
required
|
||||
class="w-full px-4 py-3 bg-slate-700/50 border border-slate-600 rounded-lg focus:ring-2 focus:ring-emerald-500 focus:border-transparent text-slate-100 placeholder-slate-400 transition-all resize-none"
|
||||
placeholder="Your message here..."
|
||||
></textarea>
|
||||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<button
|
||||
type="submit"
|
||||
class="inline-flex items-center px-6 py-3 border border-transparent rounded-lg text-base font-medium text-white bg-emerald-600 hover:bg-emerald-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-emerald-500 transition-all"
|
||||
>
|
||||
Send Message
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 ml-2" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path d="M10.894 2.553a1 1 0 00-1.788 0l-7 14a1 1 0 001.169 1.409l5-1.429A1 1 0 009 15.571V11a1 1 0 112 0v4.571a1 1 0 00.725.962l5 1.428a1 1 0 001.17-1.408l-7-14z"></path>
|
||||
</svg>
|
||||
</button>
|
||||
<div class="flex items-center space-x-4">
|
||||
<a href="https://linkedin.com/in/yourprofile" target="_blank" rel="noopener noreferrer" class="text-slate-400 hover:text-slate-300 transition-colors">
|
||||
<svg class="h-6 w-6" fill="currentColor" viewBox="0 0 24 24">
|
||||
<path d="M19 0h-14c-2.761 0-5 2.239-5 5v14c0 2.761 2.239 5 5 5h14c2.762 0 5-2.239 5-5v-14c0-2.761-2.238-5-5-5zm-11 19h-3v-11h3v11zm-1.5-12.268c-.966 0-1.75-.79-1.75-1.764s.784-1.764 1.75-1.764 1.75.79 1.75 1.764-.783 1.764-1.75 1.764zm13.5 12.268h-3v-5.604c0-3.368-4-3.113-4 0v5.604h-3v-11h3v1.765c1.396-2.586 7-2.777 7 2.476v6.759z"></path>
|
||||
</svg>
|
||||
</a>
|
||||
<a href="https://github.com/yourusername" target="_blank" rel="noopener noreferrer" class="text-slate-400 hover:text-slate-300 transition-colors">
|
||||
<svg class="h-6 w-6" fill="currentColor" viewBox="0 0 24 24">
|
||||
<path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"></path>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
<script>
|
||||
function handleSubmit(event) {
|
||||
event.preventDefault();
|
||||
|
||||
const form = event.target;
|
||||
const formData = new FormData(form);
|
||||
|
||||
// Construct mailto URL
|
||||
const email = 'your@email.com'; // Replace with your email
|
||||
const subject = formData.get('subject');
|
||||
const name = formData.get('name');
|
||||
const message = `Name: ${name}\nEmail: ${formData.get('email')}\n\nMessage:\n${formData.get('message')}`;
|
||||
|
||||
const mailtoUrl = `mailto:${email}?subject=${encodeURIComponent(subject)}&body=${encodeURIComponent(message)}`;
|
||||
|
||||
// Open email client
|
||||
window.location.href = mailtoUrl;
|
||||
|
||||
// Optional: Reset form
|
||||
form.reset();
|
||||
}
|
||||
</script>
|
||||
}
|
||||
}
|
||||
58
components/contact_page_templ.go
Normal file
58
components/contact_page_templ.go
Normal file
File diff suppressed because one or more lines are too long
65
components/experiences_page.templ
Normal file
65
components/experiences_page.templ
Normal file
@@ -0,0 +1,65 @@
|
||||
|
||||
package components
|
||||
|
||||
import "github.com/moh682/portfolio/domain"
|
||||
|
||||
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() {
|
||||
<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 id="exps-container" class="ml-4 items-center content-center relative xl:w-[1600px]">
|
||||
for _, exp := range experiences {
|
||||
@experienceCard(exp)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
}
|
||||
}
|
||||
280
components/experiences_page_templ.go
Normal file
280
components/experiences_page_templ.go
Normal file
@@ -0,0 +1,280 @@
|
||||
// Code generated by templ - DO NOT EDIT.
|
||||
|
||||
// templ: version: v0.3.833
|
||||
|
||||
package components
|
||||
|
||||
//lint:file-ignore SA4006 This context is only used if a nested component is present.
|
||||
|
||||
import "github.com/a-h/templ"
|
||||
import templruntime "github.com/a-h/templ/runtime"
|
||||
|
||||
import "github.com/moh682/portfolio/domain"
|
||||
|
||||
import "slices"
|
||||
import "strings"
|
||||
|
||||
func experienceCard(exp domain.Experience) templ.Component {
|
||||
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
||||
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
||||
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
|
||||
return templ_7745c5c3_CtxErr
|
||||
}
|
||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
defer func() {
|
||||
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err == nil {
|
||||
templ_7745c5c3_Err = templ_7745c5c3_BufErr
|
||||
}
|
||||
}()
|
||||
}
|
||||
ctx = templ.InitializeContext(ctx)
|
||||
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
|
||||
if templ_7745c5c3_Var1 == nil {
|
||||
templ_7745c5c3_Var1 = templ.NopComponent
|
||||
}
|
||||
ctx = templ.ClearChildren(ctx)
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<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\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var2 string
|
||||
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(exp.Job.Title)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/experiences_page.templ`, Line: 13, Col: 63}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "</h3><div class=\"flex flex-col gap-2\"><a href=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var3 templ.SafeURL = templ.SafeURL(exp.Company.Link)
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var3)))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "\" class=\"text-emerald-400 font-medium text-lg \">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var4 string
|
||||
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(exp.Company.Name)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/experiences_page.templ`, Line: 15, Col: 112}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "</a><p class=\"text-slate-400 \">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var5 string
|
||||
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(exp.Duration())
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/experiences_page.templ`, Line: 16, Col: 47}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "</p><p class=\"text-slate-600 text-sm\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var6 string
|
||||
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(exp.GetStartDate())
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/experiences_page.templ`, Line: 17, Col: 58}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, " - ")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var7 string
|
||||
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(exp.GetEndDate())
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/experiences_page.templ`, Line: 17, Col: 81}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "</p><a href=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var8 templ.SafeURL = templ.SafeURL(exp.Company.LocationLink)
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var8)))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "\" class=\"text-emerald-500\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var9 string
|
||||
templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(exp.Company.City)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/experiences_page.templ`, Line: 18, Col: 99}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "</a></div><div class=\"flex flex-wrap gap-2\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
for _, stack := range slices.Concat(exp.Job.FrontendStack, exp.Job.BackendStack, exp.Job.InfrastructureStack) {
|
||||
templ_7745c5c3_Err = techStack(stack).Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "</div><div class=\"flex flex-col gap-2\"><h4 class=\"text-xl font-medium\">About ")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var10 string
|
||||
templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(exp.Company.Name)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/experiences_page.templ`, Line: 26, Col: 60}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "</h4>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
for _, description := range strings.Split(exp.Company.Description, "\n") {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "<p class=\"text-slate-300\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var11 string
|
||||
templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(description)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/experiences_page.templ`, Line: 29, Col: 19}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "</p>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "</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\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var12 string
|
||||
templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(exp.Job.Description)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/experiences_page.templ`, Line: 36, Col: 26}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "</div></div></div></section>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func ExperiencesPage(experiences []domain.Experience) templ.Component {
|
||||
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
||||
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
||||
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
|
||||
return templ_7745c5c3_CtxErr
|
||||
}
|
||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
defer func() {
|
||||
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err == nil {
|
||||
templ_7745c5c3_Err = templ_7745c5c3_BufErr
|
||||
}
|
||||
}()
|
||||
}
|
||||
ctx = templ.InitializeContext(ctx)
|
||||
templ_7745c5c3_Var13 := templ.GetChildren(ctx)
|
||||
if templ_7745c5c3_Var13 == nil {
|
||||
templ_7745c5c3_Var13 = templ.NopComponent
|
||||
}
|
||||
ctx = templ.ClearChildren(ctx)
|
||||
templ_7745c5c3_Var14 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
||||
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
defer func() {
|
||||
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err == nil {
|
||||
templ_7745c5c3_Err = templ_7745c5c3_BufErr
|
||||
}
|
||||
}()
|
||||
}
|
||||
ctx = templ.InitializeContext(ctx)
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "<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>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var15 = []any{"flex items-center flex-grow-0 justify-center w-10 h-10 rounded-full bg-emerald-900 border-4 border-slate-800 z-[80]"}
|
||||
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var15...)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "<div id=\"timeline-point\" class=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var16 string
|
||||
templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var15).String())
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/experiences_page.templ`, Line: 1, Col: 0}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "\"><div class=\"absolute w-2 h-2 z-[80] bg-emerald-400 rounded-full\"></div></div></div><div id=\"exps-container\" class=\"ml-4 items-center content-center relative xl:w-[1600px]\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
for _, exp := range experiences {
|
||||
templ_7745c5c3_Err = experienceCard(exp).Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "</div></div></div></main>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
templ_7745c5c3_Err = rootLayout().Render(templ.WithChildren(ctx, templ_7745c5c3_Var14), templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
var _ = templruntime.GeneratedTemplate
|
||||
14
components/external_links.templ
Normal file
14
components/external_links.templ
Normal file
@@ -0,0 +1,14 @@
|
||||
package components
|
||||
|
||||
var iconSize = "28"
|
||||
|
||||
templ externalLinks() {
|
||||
<div class="fixed flex flex-col gap-6 right-5 bottom-0 p-5 w-5">
|
||||
<a href="https://github.com/moh682" target="_blank" rel="noopener noreferrer">
|
||||
<svg class="hover:cursor-pointer" xmlns="http://www.w3.org/2000/svg" width={ iconSize } height={ iconSize } viewBox="0 0 24 24" fill="transparent" stroke="var(--color-emerald-500)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-github"><path d="M15 22v-4a4.8 4.8 0 0 0-1-3.5c3 0 6-2 6-5.5.08-1.25-.27-2.48-1-3.5.28-1.15.28-2.35 0-3.5 0 0-1 0-3 1.5-2.64-.5-5.36-.5-8 0C6 2 5 2 5 2c-.3 1.15-.3 2.35 0 3.5A5.403 5.403 0 0 0 4 9c0 3.5 3 5.5 6 5.5-.39.49-.68 1.05-.85 1.65-.17.6-.22 1.23-.15 1.85v4"></path><path d="M9 18c-4.51 2-5-2-7-2"></path></svg>
|
||||
</a>
|
||||
<a href="https://www.linkedin.com/in/mohammad-hariri-337a0116b" target="_blank" rel="noopener noreferrer">
|
||||
<svg class="hover:cursor-pointer" xmlns="http://www.w3.org/2000/svg" width={ iconSize } height={ iconSize } viewBox="0 0 {iconSize} {iconSize}" fill="none" stroke="var(--color-emerald-500)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-linkedin"><path d="M16 8a6 6 0 0 1 6 6v7h-4v-7a2 2 0 0 0-2-2 2 2 0 0 0-2 2v7h-4v-7a6 6 0 0 1 6-6z"></path><rect width="4" height="12" x="2" y="9"></rect><circle cx="4" cy="4" r="2"></circle></svg>
|
||||
</a>
|
||||
</div>
|
||||
}
|
||||
94
components/external_links_templ.go
Normal file
94
components/external_links_templ.go
Normal file
@@ -0,0 +1,94 @@
|
||||
// Code generated by templ - DO NOT EDIT.
|
||||
|
||||
// templ: version: v0.3.833
|
||||
package components
|
||||
|
||||
//lint:file-ignore SA4006 This context is only used if a nested component is present.
|
||||
|
||||
import "github.com/a-h/templ"
|
||||
import templruntime "github.com/a-h/templ/runtime"
|
||||
|
||||
var iconSize = "28"
|
||||
|
||||
func externalLinks() templ.Component {
|
||||
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
||||
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
||||
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
|
||||
return templ_7745c5c3_CtxErr
|
||||
}
|
||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
defer func() {
|
||||
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err == nil {
|
||||
templ_7745c5c3_Err = templ_7745c5c3_BufErr
|
||||
}
|
||||
}()
|
||||
}
|
||||
ctx = templ.InitializeContext(ctx)
|
||||
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
|
||||
if templ_7745c5c3_Var1 == nil {
|
||||
templ_7745c5c3_Var1 = templ.NopComponent
|
||||
}
|
||||
ctx = templ.ClearChildren(ctx)
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<div class=\"fixed flex flex-col gap-6 right-5 bottom-0 p-5 w-5\"><a href=\"https://github.com/moh682\" target=\"_blank\" rel=\"noopener noreferrer\"><svg class=\"hover:cursor-pointer\" xmlns=\"http://www.w3.org/2000/svg\" width=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var2 string
|
||||
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(iconSize)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/external_links.templ`, Line: 8, Col: 88}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "\" height=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var3 string
|
||||
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(iconSize)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/external_links.templ`, Line: 8, Col: 108}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "\" viewBox=\"0 0 24 24\" fill=\"transparent\" stroke=\"var(--color-emerald-500)\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"lucide lucide-github\"><path d=\"M15 22v-4a4.8 4.8 0 0 0-1-3.5c3 0 6-2 6-5.5.08-1.25-.27-2.48-1-3.5.28-1.15.28-2.35 0-3.5 0 0-1 0-3 1.5-2.64-.5-5.36-.5-8 0C6 2 5 2 5 2c-.3 1.15-.3 2.35 0 3.5A5.403 5.403 0 0 0 4 9c0 3.5 3 5.5 6 5.5-.39.49-.68 1.05-.85 1.65-.17.6-.22 1.23-.15 1.85v4\"></path><path d=\"M9 18c-4.51 2-5-2-7-2\"></path></svg></a> <a href=\"https://www.linkedin.com/in/mohammad-hariri-337a0116b\" target=\"_blank\" rel=\"noopener noreferrer\"><svg class=\"hover:cursor-pointer\" xmlns=\"http://www.w3.org/2000/svg\" width=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var4 string
|
||||
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(iconSize)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/external_links.templ`, Line: 11, Col: 88}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "\" height=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var5 string
|
||||
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(iconSize)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/external_links.templ`, Line: 11, Col: 108}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "\" viewBox=\"0 0 {iconSize} {iconSize}\" fill=\"none\" stroke=\"var(--color-emerald-500)\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"lucide lucide-linkedin\"><path d=\"M16 8a6 6 0 0 1 6 6v7h-4v-7a2 2 0 0 0-2-2 2 2 0 0 0-2 2v7h-4v-7a6 6 0 0 1 6-6z\"></path><rect width=\"4\" height=\"12\" x=\"2\" y=\"9\"></rect><circle cx=\"4\" cy=\"4\" r=\"2\"></circle></svg></a></div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
var _ = templruntime.GeneratedTemplate
|
||||
71
components/landing_page.templ
Normal file
71
components/landing_page.templ
Normal file
@@ -0,0 +1,71 @@
|
||||
package components
|
||||
|
||||
templ LandingPage() {
|
||||
@rootLayout() {
|
||||
<script defer src="assets/js/landing.js"></script>
|
||||
<main class="flex flex-col gap-8 bg-gradient-to-b from-slate-900 to-slate-800">
|
||||
<!-- Hero Section -->
|
||||
<div class="px-4 sm:px-6 lg:px-8 py-16 sm:py-24">
|
||||
<div class="flex flex-col gap-24 ">
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-12 items-center">
|
||||
<!-- Left Content -->
|
||||
<div class="flex flex-col gap-8">
|
||||
<div class="flex flex-col gap-2">
|
||||
<span class="px-4 w-fit py-2 bg-emerald-900/50 text-emerald-400 rounded-full text-sm font-medium">Software Engineer</span>
|
||||
<h1 class="text-4xl sm:text-5xl lg:text-6xl font-bold text-white leading-tight">
|
||||
Hey there! I'm Mohammad Hariri
|
||||
</h1>
|
||||
<p class="text-xl text-slate-400 leading-relaxed">
|
||||
I turn complex problems into elegant code solutions. Passionate about building robust applications that make a real difference in people's lives.
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex flex-col sm:flex-row gap-4">
|
||||
<a href="#contact" class="inline-flex items-center justify-center px-6 py-3 border border-transparent text-base font-medium rounded-lg text-white bg-emerald-600 hover:bg-emerald-700 transition duration-300 shadow-lg hover:shadow-xl">
|
||||
Let's get in touch
|
||||
</a>
|
||||
<a href="#portfolio" class="inline-flex items-center justify-center px-6 py-3 border-2 border-slate-700 text-base font-medium rounded-lg text-slate-300 hover:bg-slate-800 transition duration-300">
|
||||
View My Work
|
||||
</a>
|
||||
</div>
|
||||
<!-- Social Links -->
|
||||
<div class="flex gap-6 pt-4">
|
||||
<a href="https://github.com/moh682" class="text-slate-400 hover:text-emerald-400 transition-colors">
|
||||
<svg class="w-6 h-6" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
||||
<path d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z"></path>
|
||||
</svg>
|
||||
</a>
|
||||
<a href="https://www.linkedin.com/in/mohammad-hariri-337a0116b/" class="text-gray-600 hover:text-emerald-600 transition-colors">
|
||||
<svg class="w-6 h-6" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
||||
<path d="M19 0h-14c-2.761 0-5 2.239-5 5v14c0 2.761 2.239 5 5 5h14c2.762 0 5-2.239 5-5v-14c0-2.761-2.238-5-5-5zm-11 19h-3v-11h3v11zm-1.5-12.268c-.966 0-1.75-.79-1.75-1.764s.784-1.764 1.75-1.764 1.75.79 1.75 1.764-.783 1.764-1.75 1.764zm13.5 12.268h-3v-5.604c0-3.368-4-3.113-4 0v5.604h-3v-11h3v1.765c1.396-2.586 7-2.777 7 2.476v6.759z"></path>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Right Content - Hero Image -->
|
||||
<div class="relative">
|
||||
<div class="absolute -inset-4 bg-gradient-to-r from-emerald-500 to-teal-500 rounded-2xl opacity-30 blur-xl"></div>
|
||||
<div class="relative bg-slate-800 p-4 rounded-2xl shadow-xl">
|
||||
<img
|
||||
src="/assets/images/portfolio-photo.webp"
|
||||
alt="Mohammad Hariri"
|
||||
class="w-full h-full object-cover rounded-xl"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Stats Section -->
|
||||
<div class="grid grid-cols-2 sm:grid-cols-4 gap-8">
|
||||
<div class="text-center">
|
||||
<div id="years-experience" class="text-4xl font-bold text-emerald-400">0</div>
|
||||
<div class="text-slate-400 mt-2">Years Experience</div>
|
||||
</div>
|
||||
<div class="text-center">
|
||||
<div id="programming-languages" class="text-4xl font-bold text-emerald-600">0</div>
|
||||
<div class="text-gray-600 mt-2">Programming Languages</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
}
|
||||
}
|
||||
58
components/landing_page_templ.go
Normal file
58
components/landing_page_templ.go
Normal file
@@ -0,0 +1,58 @@
|
||||
// Code generated by templ - DO NOT EDIT.
|
||||
|
||||
// templ: version: v0.3.833
|
||||
package components
|
||||
|
||||
//lint:file-ignore SA4006 This context is only used if a nested component is present.
|
||||
|
||||
import "github.com/a-h/templ"
|
||||
import templruntime "github.com/a-h/templ/runtime"
|
||||
|
||||
func LandingPage() templ.Component {
|
||||
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
||||
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
||||
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
|
||||
return templ_7745c5c3_CtxErr
|
||||
}
|
||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
defer func() {
|
||||
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err == nil {
|
||||
templ_7745c5c3_Err = templ_7745c5c3_BufErr
|
||||
}
|
||||
}()
|
||||
}
|
||||
ctx = templ.InitializeContext(ctx)
|
||||
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
|
||||
if templ_7745c5c3_Var1 == nil {
|
||||
templ_7745c5c3_Var1 = templ.NopComponent
|
||||
}
|
||||
ctx = templ.ClearChildren(ctx)
|
||||
templ_7745c5c3_Var2 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
||||
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
defer func() {
|
||||
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err == nil {
|
||||
templ_7745c5c3_Err = templ_7745c5c3_BufErr
|
||||
}
|
||||
}()
|
||||
}
|
||||
ctx = templ.InitializeContext(ctx)
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<script defer src=\"assets/js/landing.js\"></script> <main class=\"flex flex-col gap-8 bg-gradient-to-b from-slate-900 to-slate-800\"><!-- Hero Section --><div class=\"px-4 sm:px-6 lg:px-8 py-16 sm:py-24\"><div class=\"flex flex-col gap-24 \"><div class=\"grid grid-cols-1 lg:grid-cols-2 gap-12 items-center\"><!-- Left Content --><div class=\"flex flex-col gap-8\"><div class=\"flex flex-col gap-2\"><span class=\"px-4 w-fit py-2 bg-emerald-900/50 text-emerald-400 rounded-full text-sm font-medium\">Software Engineer</span><h1 class=\"text-4xl sm:text-5xl lg:text-6xl font-bold text-white leading-tight\">Hey there! I'm Mohammad Hariri</h1><p class=\"text-xl text-slate-400 leading-relaxed\">I turn complex problems into elegant code solutions. Passionate about building robust applications that make a real difference in people's lives.</p></div><div class=\"flex flex-col sm:flex-row gap-4\"><a href=\"#contact\" class=\"inline-flex items-center justify-center px-6 py-3 border border-transparent text-base font-medium rounded-lg text-white bg-emerald-600 hover:bg-emerald-700 transition duration-300 shadow-lg hover:shadow-xl\">Let's get in touch</a> <a href=\"#portfolio\" class=\"inline-flex items-center justify-center px-6 py-3 border-2 border-slate-700 text-base font-medium rounded-lg text-slate-300 hover:bg-slate-800 transition duration-300\">View My Work</a></div><!-- Social Links --><div class=\"flex gap-6 pt-4\"><a href=\"https://github.com/moh682\" class=\"text-slate-400 hover:text-emerald-400 transition-colors\"><svg class=\"w-6 h-6\" fill=\"currentColor\" viewBox=\"0 0 24 24\" aria-hidden=\"true\"><path d=\"M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z\"></path></svg></a> <a href=\"https://www.linkedin.com/in/mohammad-hariri-337a0116b/\" class=\"text-gray-600 hover:text-emerald-600 transition-colors\"><svg class=\"w-6 h-6\" fill=\"currentColor\" viewBox=\"0 0 24 24\" aria-hidden=\"true\"><path d=\"M19 0h-14c-2.761 0-5 2.239-5 5v14c0 2.761 2.239 5 5 5h14c2.762 0 5-2.239 5-5v-14c0-2.761-2.238-5-5-5zm-11 19h-3v-11h3v11zm-1.5-12.268c-.966 0-1.75-.79-1.75-1.764s.784-1.764 1.75-1.764 1.75.79 1.75 1.764-.783 1.764-1.75 1.764zm13.5 12.268h-3v-5.604c0-3.368-4-3.113-4 0v5.604h-3v-11h3v1.765c1.396-2.586 7-2.777 7 2.476v6.759z\"></path></svg></a></div></div><!-- Right Content - Hero Image --><div class=\"relative\"><div class=\"absolute -inset-4 bg-gradient-to-r from-emerald-500 to-teal-500 rounded-2xl opacity-30 blur-xl\"></div><div class=\"relative bg-slate-800 p-4 rounded-2xl shadow-xl\"><img src=\"/assets/images/portfolio-photo.webp\" alt=\"Mohammad Hariri\" class=\"w-full h-full object-cover rounded-xl\"></div></div></div><!-- Stats Section --><div class=\"grid grid-cols-2 sm:grid-cols-4 gap-8\"><div class=\"text-center\"><div id=\"years-experience\" class=\"text-4xl font-bold text-emerald-400\">0</div><div class=\"text-slate-400 mt-2\">Years Experience</div></div><div class=\"text-center\"><div id=\"programming-languages\" class=\"text-4xl font-bold text-emerald-600\">0</div><div class=\"text-gray-600 mt-2\">Programming Languages</div></div></div></div></div></main>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
templ_7745c5c3_Err = rootLayout().Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
var _ = templruntime.GeneratedTemplate
|
||||
23
components/layout.templ
Normal file
23
components/layout.templ
Normal file
@@ -0,0 +1,23 @@
|
||||
package components
|
||||
|
||||
templ rootLayout() {
|
||||
<html class="w-screen h-screen overflow-x-hidden bg-slate-900 text-white">
|
||||
<head>
|
||||
<!-- tailwindcss -->
|
||||
<script src="https://unpkg.com/@tailwindcss/browser@4"></script>
|
||||
<link rel="stylesheet" href="assets/css/styles.css"/>
|
||||
<!-- Motion.js -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/motion@latest/dist/motion.js"></script>
|
||||
<!-- Alpine.js -->
|
||||
<script defer src="https://cdn.jsdelivr.net/npm/@alpinejs/anchor@3.x.x/dist/cdn.min.js"></script>
|
||||
<script defer src="https://cdn.jsdelivr.net/npm/@alpinejs/persist@3.x.x/dist/cdn.min.js"></script>
|
||||
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
<title>Mohammad's Portfolio</title>
|
||||
</head>
|
||||
<body class="flex flex-col w-full h-full">
|
||||
@navbar()
|
||||
{ children... }
|
||||
</body>
|
||||
</html>
|
||||
}
|
||||
52
components/layout_templ.go
Normal file
52
components/layout_templ.go
Normal file
@@ -0,0 +1,52 @@
|
||||
// Code generated by templ - DO NOT EDIT.
|
||||
|
||||
// templ: version: v0.3.833
|
||||
package components
|
||||
|
||||
//lint:file-ignore SA4006 This context is only used if a nested component is present.
|
||||
|
||||
import "github.com/a-h/templ"
|
||||
import templruntime "github.com/a-h/templ/runtime"
|
||||
|
||||
func rootLayout() templ.Component {
|
||||
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
||||
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
||||
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
|
||||
return templ_7745c5c3_CtxErr
|
||||
}
|
||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
defer func() {
|
||||
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err == nil {
|
||||
templ_7745c5c3_Err = templ_7745c5c3_BufErr
|
||||
}
|
||||
}()
|
||||
}
|
||||
ctx = templ.InitializeContext(ctx)
|
||||
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
|
||||
if templ_7745c5c3_Var1 == nil {
|
||||
templ_7745c5c3_Var1 = templ.NopComponent
|
||||
}
|
||||
ctx = templ.ClearChildren(ctx)
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<html class=\"w-screen h-screen overflow-x-hidden bg-slate-900 text-white\"><head><!-- tailwindcss --><script src=\"https://unpkg.com/@tailwindcss/browser@4\"></script><link rel=\"stylesheet\" href=\"assets/css/styles.css\"><!-- Motion.js --><script src=\"https://cdn.jsdelivr.net/npm/motion@latest/dist/motion.js\"></script><!-- Alpine.js --><script defer src=\"https://cdn.jsdelivr.net/npm/@alpinejs/anchor@3.x.x/dist/cdn.min.js\"></script><script defer src=\"https://cdn.jsdelivr.net/npm/@alpinejs/persist@3.x.x/dist/cdn.min.js\"></script><script defer src=\"https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js\"></script><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"><title>Mohammad's Portfolio</title></head><body class=\"flex flex-col w-full h-full\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = navbar().Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templ_7745c5c3_Var1.Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "</body></html>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
var _ = templruntime.GeneratedTemplate
|
||||
7
components/logo.templ
Normal file
7
components/logo.templ
Normal file
File diff suppressed because one or more lines are too long
40
components/logo_templ.go
Normal file
40
components/logo_templ.go
Normal file
File diff suppressed because one or more lines are too long
86
components/navbar.templ
Normal file
86
components/navbar.templ
Normal file
@@ -0,0 +1,86 @@
|
||||
package components
|
||||
|
||||
templ navbar() {
|
||||
<script src="/assets/js/navigation.js"></script>
|
||||
<nav x-data="navbar" class="fixed h-[var(--navbar-height)] backdrop-blur-md bg-slate-900/90 z-50 w-full py-4 justify-between items-center flex gap-6 px-6 border-b border-slate-800">
|
||||
<ul class="flex gap-4">
|
||||
<li>
|
||||
<a href="/" class="text-white hover:text-blue-400 transition-colors duration-300">
|
||||
@Logo()
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<ul class="flex gap-4">
|
||||
<div class="hidden md:block">
|
||||
<div class="flex gap-8" id="desktop-nav">
|
||||
<li class="relative group">
|
||||
<a href="/experiences" class="flex items-center gap-2 text-slate-300 group-hover:text-white transition-colors duration-300">
|
||||
<svg class="w-5 h-5" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M20.25 14.15v4.25c0 1.094-.787 2.036-1.872 2.18-2.087.277-4.216.42-6.378.42s-4.291-.143-6.378-.42c-1.085-.144-1.872-1.086-1.872-2.18v-4.25m16.5 0a2.18 2.18 0 00.75-1.661V8.706c0-1.081-.768-2.015-1.837-2.175a48.114 48.114 0 00-3.413-.387m4.5 8.006c-.194.165-.42.295-.673.38A23.978 23.978 0 0112 15.75c-2.648 0-5.195-.429-7.577-1.22a2.016 2.016 0 01-.673-.38m0 0A2.18 2.18 0 013 12.489V8.706c0-1.081.768-2.015 1.837-2.175a48.111 48.111 0 013.413-.387m7.5 0V5.25A2.25 2.25 0 0013.5 3h-3a2.25 2.25 0 00-2.25 2.25v.894m7.5 0a48.667 48.667 0 00-7.5 0M12 12.75h.008v.008H12v-.008z"></path>
|
||||
</svg>
|
||||
<span>Experiences</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="relative group">
|
||||
<a href="/projects" class="flex items-center gap-2 text-slate-300 group-hover:text-white transition-colors duration-300">
|
||||
<svg class="w-5 h-5" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M17.25 6.75L22.5 12l-5.25 5.25m-10.5 0L1.5 12l5.25-5.25m7.5-3l-4.5 16.5"></path>
|
||||
</svg>
|
||||
<span>Projects</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="relative group">
|
||||
<a href="/about" class="flex items-center gap-2 text-slate-300 group-hover:text-white transition-colors duration-300">
|
||||
<svg class="w-5 h-5" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M15.75 6a3.75 3.75 0 11-7.5 0 3.75 3.75 0 017.5 0zM4.501 20.118a7.5 7.5 0 0114.998 0A17.933 17.933 0 0112 21.75c-2.676 0-5.216-.584-7.499-1.632z"></path>
|
||||
</svg>
|
||||
<span>About</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="relative group">
|
||||
<a href="/contact" class="flex items-center gap-2 text-slate-300 group-hover:text-white transition-colors duration-300">
|
||||
<svg class="w-5 h-5" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M21.75 6.75v10.5a2.25 2.25 0 01-2.25 2.25h-15a2.25 2.25 0 01-2.25-2.25V6.75m19.5 0A2.25 2.25 0 0019.5 4.5h-15a2.25 2.25 0 00-2.25 2.25m19.5 0v.243a2.25 2.25 0 01-1.07 1.916l-7.5 4.615a2.25 2.25 0 01-2.36 0L3.32 8.91a2.25 2.25 0 01-1.07-1.916V6.75"></path>
|
||||
</svg>
|
||||
<span>Contact</span>
|
||||
</a>
|
||||
</li>
|
||||
</div>
|
||||
</div>
|
||||
</ul>
|
||||
<ul class="w-6">
|
||||
<button @click="toggle" class="md:hidden relative w-8 h-8 focus:outline-none">
|
||||
<div class="absolute w-6 transform transition-all duration-300" :class="{'rotate-45 translate-y-0': isOpen, 'translate-y-[-8px]': !isOpen}">
|
||||
<div class="h-[2px] w-full bg-white rounded-lg"></div>
|
||||
</div>
|
||||
<div class="absolute w-6 transform transition-all duration-300" :class="{'opacity-0': isOpen}">
|
||||
<div class="h-[2px] w-full bg-white rounded-lg"></div>
|
||||
</div>
|
||||
<div class="absolute w-6 transform transition-all duration-300" :class="{'-rotate-45 translate-y-0': isOpen, 'translate-y-[8px]': !isOpen}">
|
||||
<div class="h-[2px] w-full bg-white rounded-lg"></div>
|
||||
</div>
|
||||
</button>
|
||||
<div
|
||||
@click="close"
|
||||
x-show="isOpen"
|
||||
x-cloak
|
||||
x-transition:enter="transition ease-out duration-300"
|
||||
x-transition:enter-start="opacity-0"
|
||||
x-transition:enter-end="opacity-100"
|
||||
x-transition:leave="transition ease-in duration-300"
|
||||
x-transition:leave-start="opacity-100"
|
||||
x-transition:leave-end="opacity-0"
|
||||
class="navbar-drawer top-[var(--navbar-height)] fixed inset-0 z-50 bg-slate-900/98 backdrop-blur-sm pt-24"
|
||||
>
|
||||
<div class="flex items-center justify-center h-full w-full">
|
||||
<div id="mobile-nav" class="flex gap-12 flex-col items-center justify-center px-2">
|
||||
<li><a class="text-4xl font-light text-slate-300 hover:text-white transition-colors duration-300" href="/experiences">Experiences</a></li>
|
||||
<li><a class="text-4xl font-light text-slate-300 hover:text-white transition-colors duration-300" href="/projects">Projects</a></li>
|
||||
<li><a class="text-4xl font-light text-slate-300 hover:text-white transition-colors duration-300" href="/about">About</a></li>
|
||||
<li><a class="text-4xl font-light text-slate-300 hover:text-white transition-colors duration-300" href="/contact">Contact</a></li>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ul>
|
||||
</nav>
|
||||
}
|
||||
48
components/navbar_templ.go
Normal file
48
components/navbar_templ.go
Normal file
@@ -0,0 +1,48 @@
|
||||
// Code generated by templ - DO NOT EDIT.
|
||||
|
||||
// templ: version: v0.3.833
|
||||
package components
|
||||
|
||||
//lint:file-ignore SA4006 This context is only used if a nested component is present.
|
||||
|
||||
import "github.com/a-h/templ"
|
||||
import templruntime "github.com/a-h/templ/runtime"
|
||||
|
||||
func navbar() templ.Component {
|
||||
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
||||
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
||||
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
|
||||
return templ_7745c5c3_CtxErr
|
||||
}
|
||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
defer func() {
|
||||
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err == nil {
|
||||
templ_7745c5c3_Err = templ_7745c5c3_BufErr
|
||||
}
|
||||
}()
|
||||
}
|
||||
ctx = templ.InitializeContext(ctx)
|
||||
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
|
||||
if templ_7745c5c3_Var1 == nil {
|
||||
templ_7745c5c3_Var1 = templ.NopComponent
|
||||
}
|
||||
ctx = templ.ClearChildren(ctx)
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<script src=\"/assets/js/navigation.js\"></script><nav x-data=\"navbar\" class=\"fixed h-[var(--navbar-height)] backdrop-blur-md bg-slate-900/90 z-50 w-full py-4 justify-between items-center flex gap-6 px-6 border-b border-slate-800\"><ul class=\"flex gap-4\"><li><a href=\"/\" class=\"text-white hover:text-blue-400 transition-colors duration-300\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = Logo().Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "</a></li></ul><ul class=\"flex gap-4\"><div class=\"hidden md:block\"><div class=\"flex gap-8\" id=\"desktop-nav\"><li class=\"relative group\"><a href=\"/experiences\" class=\"flex items-center gap-2 text-slate-300 group-hover:text-white transition-colors duration-300\"><svg class=\"w-5 h-5\" xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M20.25 14.15v4.25c0 1.094-.787 2.036-1.872 2.18-2.087.277-4.216.42-6.378.42s-4.291-.143-6.378-.42c-1.085-.144-1.872-1.086-1.872-2.18v-4.25m16.5 0a2.18 2.18 0 00.75-1.661V8.706c0-1.081-.768-2.015-1.837-2.175a48.114 48.114 0 00-3.413-.387m4.5 8.006c-.194.165-.42.295-.673.38A23.978 23.978 0 0112 15.75c-2.648 0-5.195-.429-7.577-1.22a2.016 2.016 0 01-.673-.38m0 0A2.18 2.18 0 013 12.489V8.706c0-1.081.768-2.015 1.837-2.175a48.111 48.111 0 013.413-.387m7.5 0V5.25A2.25 2.25 0 0013.5 3h-3a2.25 2.25 0 00-2.25 2.25v.894m7.5 0a48.667 48.667 0 00-7.5 0M12 12.75h.008v.008H12v-.008z\"></path></svg> <span>Experiences</span></a></li><li class=\"relative group\"><a href=\"/projects\" class=\"flex items-center gap-2 text-slate-300 group-hover:text-white transition-colors duration-300\"><svg class=\"w-5 h-5\" xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M17.25 6.75L22.5 12l-5.25 5.25m-10.5 0L1.5 12l5.25-5.25m7.5-3l-4.5 16.5\"></path></svg> <span>Projects</span></a></li><li class=\"relative group\"><a href=\"/about\" class=\"flex items-center gap-2 text-slate-300 group-hover:text-white transition-colors duration-300\"><svg class=\"w-5 h-5\" xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.75 6a3.75 3.75 0 11-7.5 0 3.75 3.75 0 017.5 0zM4.501 20.118a7.5 7.5 0 0114.998 0A17.933 17.933 0 0112 21.75c-2.676 0-5.216-.584-7.499-1.632z\"></path></svg> <span>About</span></a></li><li class=\"relative group\"><a href=\"/contact\" class=\"flex items-center gap-2 text-slate-300 group-hover:text-white transition-colors duration-300\"><svg class=\"w-5 h-5\" xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M21.75 6.75v10.5a2.25 2.25 0 01-2.25 2.25h-15a2.25 2.25 0 01-2.25-2.25V6.75m19.5 0A2.25 2.25 0 0019.5 4.5h-15a2.25 2.25 0 00-2.25 2.25m19.5 0v.243a2.25 2.25 0 01-1.07 1.916l-7.5 4.615a2.25 2.25 0 01-2.36 0L3.32 8.91a2.25 2.25 0 01-1.07-1.916V6.75\"></path></svg> <span>Contact</span></a></li></div></div></ul><ul class=\"w-6\"><button @click=\"toggle\" class=\"md:hidden relative w-8 h-8 focus:outline-none\"><div class=\"absolute w-6 transform transition-all duration-300\" :class=\"{'rotate-45 translate-y-0': isOpen, 'translate-y-[-8px]': !isOpen}\"><div class=\"h-[2px] w-full bg-white rounded-lg\"></div></div><div class=\"absolute w-6 transform transition-all duration-300\" :class=\"{'opacity-0': isOpen}\"><div class=\"h-[2px] w-full bg-white rounded-lg\"></div></div><div class=\"absolute w-6 transform transition-all duration-300\" :class=\"{'-rotate-45 translate-y-0': isOpen, 'translate-y-[8px]': !isOpen}\"><div class=\"h-[2px] w-full bg-white rounded-lg\"></div></div></button><div @click=\"close\" x-show=\"isOpen\" x-cloak x-transition:enter=\"transition ease-out duration-300\" x-transition:enter-start=\"opacity-0\" x-transition:enter-end=\"opacity-100\" x-transition:leave=\"transition ease-in duration-300\" x-transition:leave-start=\"opacity-100\" x-transition:leave-end=\"opacity-0\" class=\"navbar-drawer top-[var(--navbar-height)] fixed inset-0 z-50 bg-slate-900/98 backdrop-blur-sm pt-24\"><div class=\"flex items-center justify-center h-full w-full\"><div id=\"mobile-nav\" class=\"flex gap-12 flex-col items-center justify-center px-2\"><li><a class=\"text-4xl font-light text-slate-300 hover:text-white transition-colors duration-300\" href=\"/experiences\">Experiences</a></li><li><a class=\"text-4xl font-light text-slate-300 hover:text-white transition-colors duration-300\" href=\"/projects\">Projects</a></li><li><a class=\"text-4xl font-light text-slate-300 hover:text-white transition-colors duration-300\" href=\"/about\">About</a></li><li><a class=\"text-4xl font-light text-slate-300 hover:text-white transition-colors duration-300\" href=\"/contact\">Contact</a></li></div></div></div></ul></nav>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
var _ = templruntime.GeneratedTemplate
|
||||
10
components/notfound.templ
Normal file
10
components/notfound.templ
Normal file
@@ -0,0 +1,10 @@
|
||||
package components
|
||||
|
||||
templ NotFound() {
|
||||
@rootLayout() {
|
||||
<main class="w-full h-full flex flex-col gap-0 items-center justify-center">
|
||||
<h1 class="text-3xl">404</h1>
|
||||
<p class="text-base">Page not found</p>
|
||||
</main>
|
||||
}
|
||||
}
|
||||
58
components/notfound_templ.go
Normal file
58
components/notfound_templ.go
Normal file
@@ -0,0 +1,58 @@
|
||||
// Code generated by templ - DO NOT EDIT.
|
||||
|
||||
// templ: version: v0.3.833
|
||||
package components
|
||||
|
||||
//lint:file-ignore SA4006 This context is only used if a nested component is present.
|
||||
|
||||
import "github.com/a-h/templ"
|
||||
import templruntime "github.com/a-h/templ/runtime"
|
||||
|
||||
func NotFound() templ.Component {
|
||||
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
||||
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
||||
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
|
||||
return templ_7745c5c3_CtxErr
|
||||
}
|
||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
defer func() {
|
||||
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err == nil {
|
||||
templ_7745c5c3_Err = templ_7745c5c3_BufErr
|
||||
}
|
||||
}()
|
||||
}
|
||||
ctx = templ.InitializeContext(ctx)
|
||||
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
|
||||
if templ_7745c5c3_Var1 == nil {
|
||||
templ_7745c5c3_Var1 = templ.NopComponent
|
||||
}
|
||||
ctx = templ.ClearChildren(ctx)
|
||||
templ_7745c5c3_Var2 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
||||
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
defer func() {
|
||||
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err == nil {
|
||||
templ_7745c5c3_Err = templ_7745c5c3_BufErr
|
||||
}
|
||||
}()
|
||||
}
|
||||
ctx = templ.InitializeContext(ctx)
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<main class=\"w-full h-full flex flex-col gap-0 items-center justify-center\"><h1 class=\"text-3xl\">404</h1><p class=\"text-base\">Page not found</p></main>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
templ_7745c5c3_Err = rootLayout().Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
var _ = templruntime.GeneratedTemplate
|
||||
65
components/projects_page.templ
Normal file
65
components/projects_page.templ
Normal file
@@ -0,0 +1,65 @@
|
||||
package components
|
||||
|
||||
import "github.com/moh682/portfolio/domain"
|
||||
|
||||
templ tag(tag string) {
|
||||
<span class="px-3 py-1 w-fit text-sm bg-slate-700 text-slate-300 rounded-full">{ tag }</span>
|
||||
}
|
||||
|
||||
templ status(status string) {
|
||||
<span class={ "px-3 py-1 w-fit text-sm bg-emerald-700 text-white rounded-full", templ.KV("bg-yellow-600/50", status =="In Progress") }>{ status }</span>
|
||||
}
|
||||
|
||||
templ projectCard(project domain.Project) {
|
||||
<div class="group bg-slate-800 rounded-lg overflow-hidden transition-all duration-300 hover:transform hover:scale-[1.02] hover:shadow-xl">
|
||||
<div class="relative overflow-hidden aspect-video">
|
||||
<img
|
||||
src={ project.Thumbnail }
|
||||
alt={ project.Name }
|
||||
class="w-full h-full object-cover transition-transform duration-300 group-hover:scale-110"
|
||||
/>
|
||||
<div class="absolute inset-0 bg-gradient-to-t from-slate-900 to-transparent opacity-0 group-hover:opacity-60 transition-opacity duration-300"></div>
|
||||
</div>
|
||||
<div class="flex flex-col gap-4 p-6">
|
||||
<div class="flex flex-col gap-2">
|
||||
<h3 class="text-xl font-bold text-slate-100">{ project.Name }</h3>
|
||||
@status(project.Status)
|
||||
<p class="text-slate-400 mb-4 line-clamp-2">{ project.Description }</p>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
for _, t := range project.Stack {
|
||||
@tag(t)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
if project.GithubLink != "" {
|
||||
<a
|
||||
href={ templ.SafeURL(project.GithubLink) }
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="inline-flex items-center text-emerald-500 hover:text-emerald-400 transition-colors"
|
||||
>
|
||||
View Project
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 ml-1" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path fill-rule="evenodd" d="M10.293 3.293a1 1 0 011.414 0l6 6a1 1 0 010 1.414l-6 6a1 1 0 01-1.414-1.414L14.586 11H3a1 1 0 110-2h11.586l-4.293-4.293a1 1 0 010-1.414z" clip-rule="evenodd"></path>
|
||||
</svg>
|
||||
</a>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
templ ProjectsPage(projects []domain.Project) {
|
||||
@rootLayout() {
|
||||
<main class="w-full min-h-full py-16 px-4 sm:px-6 lg:px-8">
|
||||
<div class="max-w-7xl mx-auto">
|
||||
<h1 class="text-4xl font-bold text-slate-100 mb-2">Projects</h1>
|
||||
<p class="text-slate-400 mb-12">A collection of projects I've worked on</p>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
|
||||
for _, project := range projects {
|
||||
@projectCard(project)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
}
|
||||
}
|
||||
290
components/projects_page_templ.go
Normal file
290
components/projects_page_templ.go
Normal file
@@ -0,0 +1,290 @@
|
||||
// Code generated by templ - DO NOT EDIT.
|
||||
|
||||
// templ: version: v0.3.833
|
||||
package components
|
||||
|
||||
//lint:file-ignore SA4006 This context is only used if a nested component is present.
|
||||
|
||||
import "github.com/a-h/templ"
|
||||
import templruntime "github.com/a-h/templ/runtime"
|
||||
|
||||
import "github.com/moh682/portfolio/domain"
|
||||
|
||||
func tag(tag string) templ.Component {
|
||||
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
||||
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
||||
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
|
||||
return templ_7745c5c3_CtxErr
|
||||
}
|
||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
defer func() {
|
||||
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err == nil {
|
||||
templ_7745c5c3_Err = templ_7745c5c3_BufErr
|
||||
}
|
||||
}()
|
||||
}
|
||||
ctx = templ.InitializeContext(ctx)
|
||||
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
|
||||
if templ_7745c5c3_Var1 == nil {
|
||||
templ_7745c5c3_Var1 = templ.NopComponent
|
||||
}
|
||||
ctx = templ.ClearChildren(ctx)
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<span class=\"px-3 py-1 w-fit text-sm bg-slate-700 text-slate-300 rounded-full\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var2 string
|
||||
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(tag)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/projects_page.templ`, Line: 6, Col: 85}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "</span>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func status(status string) templ.Component {
|
||||
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
||||
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
||||
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
|
||||
return templ_7745c5c3_CtxErr
|
||||
}
|
||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
defer func() {
|
||||
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err == nil {
|
||||
templ_7745c5c3_Err = templ_7745c5c3_BufErr
|
||||
}
|
||||
}()
|
||||
}
|
||||
ctx = templ.InitializeContext(ctx)
|
||||
templ_7745c5c3_Var3 := templ.GetChildren(ctx)
|
||||
if templ_7745c5c3_Var3 == nil {
|
||||
templ_7745c5c3_Var3 = templ.NopComponent
|
||||
}
|
||||
ctx = templ.ClearChildren(ctx)
|
||||
var templ_7745c5c3_Var4 = []any{"px-3 py-1 w-fit text-sm bg-emerald-700 text-white rounded-full", templ.KV("bg-yellow-600/50", status == "In Progress")}
|
||||
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var4...)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "<span class=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var5 string
|
||||
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var4).String())
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/projects_page.templ`, Line: 1, Col: 0}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var6 string
|
||||
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(status)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/projects_page.templ`, Line: 10, Col: 144}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "</span>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func projectCard(project domain.Project) templ.Component {
|
||||
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
||||
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
||||
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
|
||||
return templ_7745c5c3_CtxErr
|
||||
}
|
||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
defer func() {
|
||||
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err == nil {
|
||||
templ_7745c5c3_Err = templ_7745c5c3_BufErr
|
||||
}
|
||||
}()
|
||||
}
|
||||
ctx = templ.InitializeContext(ctx)
|
||||
templ_7745c5c3_Var7 := templ.GetChildren(ctx)
|
||||
if templ_7745c5c3_Var7 == nil {
|
||||
templ_7745c5c3_Var7 = templ.NopComponent
|
||||
}
|
||||
ctx = templ.ClearChildren(ctx)
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "<div class=\"group bg-slate-800 rounded-lg overflow-hidden transition-all duration-300 hover:transform hover:scale-[1.02] hover:shadow-xl\"><div class=\"relative overflow-hidden aspect-video\"><img src=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var8 string
|
||||
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(project.Thumbnail)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/projects_page.templ`, Line: 17, Col: 27}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "\" alt=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var9 string
|
||||
templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(project.Name)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/projects_page.templ`, Line: 18, Col: 22}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "\" class=\"w-full h-full object-cover transition-transform duration-300 group-hover:scale-110\"><div class=\"absolute inset-0 bg-gradient-to-t from-slate-900 to-transparent opacity-0 group-hover:opacity-60 transition-opacity duration-300\"></div></div><div class=\"flex flex-col gap-4 p-6\"><div class=\"flex flex-col gap-2\"><h3 class=\"text-xl font-bold text-slate-100\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var10 string
|
||||
templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(project.Name)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/projects_page.templ`, Line: 25, Col: 63}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "</h3>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = status(project.Status).Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "<p class=\"text-slate-400 mb-4 line-clamp-2\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var11 string
|
||||
templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(project.Description)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/projects_page.templ`, Line: 27, Col: 69}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "</p><div class=\"flex flex-wrap gap-2\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
for _, t := range project.Stack {
|
||||
templ_7745c5c3_Err = tag(t).Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "</div></div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if project.GithubLink != "" {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "<a href=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var12 templ.SafeURL = templ.SafeURL(project.GithubLink)
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var12)))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"inline-flex items-center text-emerald-500 hover:text-emerald-400 transition-colors\">View Project <svg xmlns=\"http://www.w3.org/2000/svg\" class=\"h-4 w-4 ml-1\" viewBox=\"0 0 20 20\" fill=\"currentColor\"><path fill-rule=\"evenodd\" d=\"M10.293 3.293a1 1 0 011.414 0l6 6a1 1 0 010 1.414l-6 6a1 1 0 01-1.414-1.414L14.586 11H3a1 1 0 110-2h11.586l-4.293-4.293a1 1 0 010-1.414z\" clip-rule=\"evenodd\"></path></svg></a>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "</div></div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func ProjectsPage(projects []domain.Project) templ.Component {
|
||||
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
||||
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
||||
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
|
||||
return templ_7745c5c3_CtxErr
|
||||
}
|
||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
defer func() {
|
||||
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err == nil {
|
||||
templ_7745c5c3_Err = templ_7745c5c3_BufErr
|
||||
}
|
||||
}()
|
||||
}
|
||||
ctx = templ.InitializeContext(ctx)
|
||||
templ_7745c5c3_Var13 := templ.GetChildren(ctx)
|
||||
if templ_7745c5c3_Var13 == nil {
|
||||
templ_7745c5c3_Var13 = templ.NopComponent
|
||||
}
|
||||
ctx = templ.ClearChildren(ctx)
|
||||
templ_7745c5c3_Var14 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
||||
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
defer func() {
|
||||
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err == nil {
|
||||
templ_7745c5c3_Err = templ_7745c5c3_BufErr
|
||||
}
|
||||
}()
|
||||
}
|
||||
ctx = templ.InitializeContext(ctx)
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "<main class=\"w-full min-h-full py-16 px-4 sm:px-6 lg:px-8\"><div class=\"max-w-7xl mx-auto\"><h1 class=\"text-4xl font-bold text-slate-100 mb-2\">Projects</h1><p class=\"text-slate-400 mb-12\">A collection of projects I've worked on</p><div class=\"grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
for _, project := range projects {
|
||||
templ_7745c5c3_Err = projectCard(project).Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "</div></div></main>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
templ_7745c5c3_Err = rootLayout().Render(templ.WithChildren(ctx, templ_7745c5c3_Var14), templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
var _ = templruntime.GeneratedTemplate
|
||||
24
components/tech_stack.templ
Normal file
24
components/tech_stack.templ
Normal file
@@ -0,0 +1,24 @@
|
||||
package components
|
||||
|
||||
import "github.com/moh682/portfolio/domain"
|
||||
|
||||
templ techStack(stack domain.Stack) {
|
||||
if stack.Link != "" {
|
||||
<a
|
||||
href={ templ.SafeURL(stack.Link) }
|
||||
class="group flex items-center gap-2 px-3 py-2 rounded-md bg-slate-700/30 hover:bg-slate-700/50 transition-colors duration-200"
|
||||
>
|
||||
if stack.Logo != "" {
|
||||
<img src={ stack.Logo } alt={ stack.Name + " logo" } class="w-5 h-5 object-contain"/>
|
||||
}
|
||||
<span class="text-emerald-500 w-fit font-medium group-hover:text-emerald-400">{ stack.Name }</span>
|
||||
</a>
|
||||
} else {
|
||||
<div class="flex items-center gap-2 px-3 py-2 rounded-md bg-slate-700">
|
||||
if stack.Logo != "" {
|
||||
<img src={ stack.Logo } alt={ stack.Name + " logo" } class="w-5 h-5 object-contain"/>
|
||||
}
|
||||
<span class="text-white w-fit font-medium">{ stack.Name }</span>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
156
components/tech_stack_templ.go
Normal file
156
components/tech_stack_templ.go
Normal file
@@ -0,0 +1,156 @@
|
||||
// Code generated by templ - DO NOT EDIT.
|
||||
|
||||
// templ: version: v0.3.833
|
||||
package components
|
||||
|
||||
//lint:file-ignore SA4006 This context is only used if a nested component is present.
|
||||
|
||||
import "github.com/a-h/templ"
|
||||
import templruntime "github.com/a-h/templ/runtime"
|
||||
|
||||
import "github.com/moh682/portfolio/domain"
|
||||
|
||||
func techStack(stack domain.Stack) templ.Component {
|
||||
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
||||
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
||||
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
|
||||
return templ_7745c5c3_CtxErr
|
||||
}
|
||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
defer func() {
|
||||
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err == nil {
|
||||
templ_7745c5c3_Err = templ_7745c5c3_BufErr
|
||||
}
|
||||
}()
|
||||
}
|
||||
ctx = templ.InitializeContext(ctx)
|
||||
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
|
||||
if templ_7745c5c3_Var1 == nil {
|
||||
templ_7745c5c3_Var1 = templ.NopComponent
|
||||
}
|
||||
ctx = templ.ClearChildren(ctx)
|
||||
if stack.Link != "" {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<a href=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var2 templ.SafeURL = templ.SafeURL(stack.Link)
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var2)))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "\" class=\"group flex items-center gap-2 px-3 py-2 rounded-md bg-slate-700/30 hover:bg-slate-700/50 transition-colors duration-200\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if stack.Logo != "" {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "<img src=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var3 string
|
||||
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(stack.Logo)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/tech_stack.templ`, Line: 12, Col: 25}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "\" alt=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var4 string
|
||||
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(stack.Name + " logo")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/tech_stack.templ`, Line: 12, Col: 54}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "\" class=\"w-5 h-5 object-contain\"> ")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "<span class=\"text-emerald-500 w-fit font-medium group-hover:text-emerald-400\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var5 string
|
||||
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(stack.Name)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/tech_stack.templ`, Line: 14, Col: 93}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "</span></a>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
} else {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "<div class=\"flex items-center gap-2 px-3 py-2 rounded-md bg-slate-700\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if stack.Logo != "" {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "<img src=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var6 string
|
||||
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(stack.Logo)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/tech_stack.templ`, Line: 19, Col: 25}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "\" alt=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var7 string
|
||||
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(stack.Name + " logo")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/tech_stack.templ`, Line: 19, Col: 54}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "\" class=\"w-5 h-5 object-contain\"> ")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "<span class=\"text-white w-fit font-medium\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var8 string
|
||||
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(stack.Name)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/tech_stack.templ`, Line: 21, Col: 58}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "</span></div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
var _ = templruntime.GeneratedTemplate
|
||||
34
compose_production.yaml
Normal file
34
compose_production.yaml
Normal file
@@ -0,0 +1,34 @@
|
||||
services:
|
||||
reverse-proxy:
|
||||
image: traefik:v3.1
|
||||
command:
|
||||
- "--providers.docker"
|
||||
- "--providers.docker.exposedbydefault=false"
|
||||
- "--entryPoints.websecure.address=:443"
|
||||
- "--certificatesresolvers.myresolver.acme.tlschallenge=true"
|
||||
- "--certificatesresolvers.myresolver.acme.email=screenchok@gmail.com"
|
||||
- "--certificatesresolvers.myresolver.acme.storage=/letsencrypt/acme.json"
|
||||
- "--entrypoints.web.address=:80"
|
||||
- "--entrypoints.web.http.redirections.entrypoint.to=websecure"
|
||||
- "--entrypoints.web.http.redirections.entrypoint.scheme=https"
|
||||
ports:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
volumes:
|
||||
- letsencrypt:/letsencrypt
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
portfolio:
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.guestbook.rule=Host(`moshariri.com`)"
|
||||
- "traefik.http.routers.guestbook.entrypoints=websecure"
|
||||
- "traefik.http.routers.guestbook.tls.certresolver=myresolver"
|
||||
- "traefik.http.middlewares.forward-auth-headers.headers.customrequestheaders.X-Forwarded-For=true"
|
||||
- "traefik.http.middlewares.forward-auth-headers.headers.customrequestheaders.X-Real-IP=true"
|
||||
- "traefik.http.routers.guestbook.middlewares=forward-auth-headers"
|
||||
- "com.centurylinklabs.watchtower.enable=true"
|
||||
image: portfolio:prod
|
||||
restart: always
|
||||
volumes:
|
||||
letsencrypt:
|
||||
|
||||
83
domain/experience.go
Normal file
83
domain/experience.go
Normal file
@@ -0,0 +1,83 @@
|
||||
package domain
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Company struct {
|
||||
Name string `json:"name"`
|
||||
City string `json:"city"`
|
||||
Link string `json:"link"`
|
||||
Logo string `json:"logo"`
|
||||
Description string `json:"description"`
|
||||
LocationLink string `json:"location_link"`
|
||||
}
|
||||
|
||||
type Stack struct {
|
||||
Name string `json:"name"`
|
||||
Logo string `json:"logo"`
|
||||
Link string `json:"link"`
|
||||
}
|
||||
|
||||
type Job struct {
|
||||
Title string `json:"title"`
|
||||
Description string `json:"description"`
|
||||
Objectives []string `json:"objectives"`
|
||||
FrontendStack []Stack `json:"frontend_stack"`
|
||||
BackendStack []Stack `json:"backend_stack"`
|
||||
InfrastructureStack []Stack `json:"infrastructure_stack"`
|
||||
}
|
||||
|
||||
type Experience struct {
|
||||
Company Company `json:"company"`
|
||||
Job Job `json:"job"`
|
||||
StartDate time.Time `json:"start_date"`
|
||||
EndDate time.Time `json:"end_date"`
|
||||
}
|
||||
|
||||
func (e Experience) Duration() string {
|
||||
end := e.EndDate
|
||||
if e.EndDate.IsZero() {
|
||||
end = time.Now()
|
||||
}
|
||||
|
||||
duration := end.Sub(e.StartDate)
|
||||
years := int(duration.Hours() / (24 * 365))
|
||||
months := int(duration.Hours()/(24*30)) - years*12
|
||||
|
||||
label := ""
|
||||
if years == 1 {
|
||||
label += "1 year"
|
||||
}
|
||||
|
||||
if years > 1 {
|
||||
label += fmt.Sprintf("%d years", years)
|
||||
}
|
||||
|
||||
if label != "" {
|
||||
label += ", "
|
||||
}
|
||||
|
||||
if months == 1 {
|
||||
label += "1 month"
|
||||
}
|
||||
|
||||
if months > 1 {
|
||||
label += fmt.Sprintf("%d months", months)
|
||||
}
|
||||
|
||||
return label
|
||||
}
|
||||
|
||||
func (e Experience) GetStartDate() string {
|
||||
return e.StartDate.Format("Jan 2006")
|
||||
}
|
||||
|
||||
func (e Experience) GetEndDate() string {
|
||||
if e.EndDate.IsZero() {
|
||||
return "Present"
|
||||
}
|
||||
|
||||
return e.EndDate.Format("Jan 2006")
|
||||
}
|
||||
14
domain/project.go
Normal file
14
domain/project.go
Normal file
@@ -0,0 +1,14 @@
|
||||
package domain
|
||||
|
||||
import "time"
|
||||
|
||||
type Project struct {
|
||||
Name string `json:"name"`
|
||||
Start time.Time `json:"start"`
|
||||
End time.Time `json:"end"`
|
||||
Stack []string `json:"stack"`
|
||||
Description string `json:"description"`
|
||||
Thumbnail string `json:"thumbnail"`
|
||||
GithubLink string `json:"github_link"`
|
||||
Status string `json:"status"`
|
||||
}
|
||||
5
go.mod
Normal file
5
go.mod
Normal file
@@ -0,0 +1,5 @@
|
||||
module github.com/moh682/portfolio
|
||||
|
||||
go 1.24.0
|
||||
|
||||
require github.com/a-h/templ v0.3.833
|
||||
4
go.sum
Normal file
4
go.sum
Normal file
@@ -0,0 +1,4 @@
|
||||
github.com/a-h/templ v0.3.833 h1:L/KOk/0VvVTBegtE0fp2RJQiBm7/52Zxv5fqlEHiQUU=
|
||||
github.com/a-h/templ v0.3.833/go.mod h1:cAu4AiZhtJfBjMY0HASlyzvkrtjnHWPeEsyGK2YYmfk=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
113
main.go
Normal file
113
main.go
Normal file
@@ -0,0 +1,113 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/a-h/templ"
|
||||
"github.com/moh682/portfolio/components"
|
||||
"github.com/moh682/portfolio/domain"
|
||||
"github.com/moh682/portfolio/middlewares"
|
||||
)
|
||||
|
||||
func templHandlerWithOptions(component templ.Component) http.Handler {
|
||||
return templ.Handler(component, templ.WithStatus(200), templ.WithContentType("text/html"))
|
||||
}
|
||||
|
||||
func rootHandler(handler http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
handler.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
// SafeFileServer wraps http.FileServer to only serve specific file types from specific directories
|
||||
func SafeFileServer(root http.FileSystem) http.Handler {
|
||||
fileServer := http.FileServer(root)
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// Set security headers
|
||||
w.Header().Set("X-Content-Type-Options", "nosniff")
|
||||
w.Header().Set("X-Frame-Options", "DENY")
|
||||
w.Header().Set("X-XSS-Protection", "1; mode=block")
|
||||
|
||||
log.Println("assets handler", r.URL.Path)
|
||||
log.Println(w.Header())
|
||||
|
||||
// Only allow specific file extensions
|
||||
if allowed := func(path string) bool {
|
||||
allowedExts := map[string]string{
|
||||
".js": "text/javascript;charset=utf-8",
|
||||
".css": "text/css;charset=utf-8",
|
||||
".png": "image/png",
|
||||
".webp": "image/webp",
|
||||
".jpg": "image/jpeg",
|
||||
".jpeg": "image/jpeg",
|
||||
".svg": "image/svg+xml",
|
||||
".ico": "image/x-icon",
|
||||
}
|
||||
for ext, contentType := range allowedExts {
|
||||
if strings.HasSuffix(path, ext) {
|
||||
w.Header().Set("Content-Type", contentType)
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}(r.URL.Path); !allowed {
|
||||
http.Error(w, "Forbidden", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
fileServer.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
//go:embed assets/json/experiences.json
|
||||
var embeddedExperiences []byte
|
||||
|
||||
//go:embed assets/json/projects.json
|
||||
var embeddedProjects []byte
|
||||
|
||||
func main() {
|
||||
server := http.NewServeMux()
|
||||
|
||||
// Serve static files with restrictions
|
||||
staticFS := http.Dir("assets")
|
||||
server.Handle("GET /assets/", http.StripPrefix("/assets/", SafeFileServer(staticFS)))
|
||||
|
||||
server.Handle("GET /about", templHandlerWithOptions(components.AboutPage()))
|
||||
|
||||
var exps []domain.Experience
|
||||
err := json.Unmarshal(embeddedExperiences, &exps)
|
||||
if err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
server.Handle("GET /experiences", templHandlerWithOptions(components.ExperiencesPage(exps)))
|
||||
|
||||
var projects []domain.Project
|
||||
err = json.Unmarshal(embeddedProjects, &projects)
|
||||
if err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
server.Handle("GET /projects", templHandlerWithOptions(components.ProjectsPage(projects)))
|
||||
|
||||
server.Handle("GET /contact", templHandlerWithOptions(components.ContactPage()))
|
||||
server.Handle("GET /{$}", templHandlerWithOptions(components.LandingPage()))
|
||||
|
||||
// 404 fallback page
|
||||
server.Handle("GET /", templ.Handler(components.NotFound(), templ.WithStatus(404), templ.WithContentType("text/html")))
|
||||
|
||||
// TODO: add Blogs to my portfolio website
|
||||
// server.Handle("GET /blogs", templHandlerWithOptions(components.AboutPage()))
|
||||
|
||||
fmt.Println("Listening on :8080")
|
||||
log.Panic(
|
||||
http.ListenAndServe(":8080",
|
||||
middlewares.Combine(
|
||||
middlewares.Logger(server),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
11
middlewares/combine.go
Normal file
11
middlewares/combine.go
Normal file
@@ -0,0 +1,11 @@
|
||||
package middlewares
|
||||
|
||||
import "net/http"
|
||||
|
||||
func Combine(nexts ...http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
for _, next := range nexts {
|
||||
next.ServeHTTP(w, r)
|
||||
}
|
||||
})
|
||||
}
|
||||
34
middlewares/logger.go
Normal file
34
middlewares/logger.go
Normal file
@@ -0,0 +1,34 @@
|
||||
package middlewares
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
type statusResponseWriter struct {
|
||||
http.ResponseWriter
|
||||
status int
|
||||
}
|
||||
|
||||
func (w *statusResponseWriter) WriteHeader(code int) {
|
||||
w.status = code
|
||||
if w.status == 0 {
|
||||
w.ResponseWriter.WriteHeader(code)
|
||||
}
|
||||
}
|
||||
|
||||
func Logger(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
ips := r.Header.Get("X-Forwarded-For")
|
||||
rw := newStatusResponseWriter(w)
|
||||
now := time.Now()
|
||||
next.ServeHTTP(rw, r)
|
||||
since := time.Since(now)
|
||||
log.Println(r.Method, ips, r.URL.Path, rw.status, since)
|
||||
})
|
||||
}
|
||||
|
||||
func newStatusResponseWriter(w http.ResponseWriter) *statusResponseWriter {
|
||||
return &statusResponseWriter{ResponseWriter: w}
|
||||
}
|
||||
22
tailwind.config.ts
Normal file
22
tailwind.config.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
export default {
|
||||
content: ["./components/**/*.{js,ts,jsx,tsx,templ}"],
|
||||
theme: {
|
||||
extend: {
|
||||
animation: {
|
||||
fadeIn: "fadeIn 1s ease-in forwards",
|
||||
slideIn: "slideIn 1s ease-out forwards",
|
||||
},
|
||||
keyframes: {
|
||||
fadeIn: {
|
||||
"0%": { opacity: "0" },
|
||||
"100%": { opacity: "1" },
|
||||
},
|
||||
slideIn: {
|
||||
"0%": { opacity: "0", transform: "translateY(20px)" },
|
||||
"100%": { opacity: "1", transform: "translateY(0)" },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [],
|
||||
};
|
||||
1521
ts/package-lock.json
generated
Normal file
1521
ts/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
25
ts/package.json
Normal file
25
ts/package.json
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"name": "ts",
|
||||
"version": "1.0.0",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"build": "rollup -c",
|
||||
"build:watch": "rollup -c -w",
|
||||
"dev": "vite"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"description": "",
|
||||
"devDependencies": {
|
||||
"@rollup/plugin-commonjs": "^28.0.2",
|
||||
"@rollup/plugin-node-resolve": "^16.0.0",
|
||||
"@rollup/plugin-terser": "^0.4.4",
|
||||
"@rollup/plugin-typescript": "^12.1.2",
|
||||
"@types/alpinejs": "^3.13.11",
|
||||
"vite": "^6.1.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"motion": "^12.4.7",
|
||||
"motion-dom": "^12.4.5"
|
||||
}
|
||||
}
|
||||
40
ts/rollup.config.mjs
Normal file
40
ts/rollup.config.mjs
Normal file
@@ -0,0 +1,40 @@
|
||||
import resolve from "@rollup/plugin-node-resolve";
|
||||
import commonjs from "@rollup/plugin-commonjs";
|
||||
import typescript from "@rollup/plugin-typescript";
|
||||
import terser from "@rollup/plugin-terser";
|
||||
|
||||
const parseConfig = (input) => ({
|
||||
input: `src/${input}`,
|
||||
output: {
|
||||
file: `../assets/js/${input.slice(0, input.length - 3)}.js`,
|
||||
format: "iife", // Changed from 'cjs' to 'iife' for browser use
|
||||
name: "navigation",
|
||||
extend: true,
|
||||
sourcemap: true,
|
||||
},
|
||||
plugins: [
|
||||
resolve({
|
||||
browser: true,
|
||||
preferBuiltins: false,
|
||||
}),
|
||||
commonjs({
|
||||
include: /node_modules/,
|
||||
transformMixedEsModules: true,
|
||||
}),
|
||||
typescript({
|
||||
include: ["./src/types.d.ts"],
|
||||
tsconfig: "./tsconfig.json",
|
||||
}),
|
||||
terser({
|
||||
format: {
|
||||
comments: false,
|
||||
},
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
export default [
|
||||
parseConfig("navigation.ts"),
|
||||
parseConfig("landing.ts"),
|
||||
parseConfig("experiences.ts"),
|
||||
];
|
||||
16
ts/src/experiences.ts
Normal file
16
ts/src/experiences.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
const { animate, inView } = Motion;
|
||||
|
||||
inView(
|
||||
"section div",
|
||||
(element) => {
|
||||
animate(
|
||||
element,
|
||||
{ opacity: 1 },
|
||||
{
|
||||
duration: 0.7,
|
||||
},
|
||||
);
|
||||
return () => animate(element, { opacity: 0 }, { duration: 0.7 });
|
||||
},
|
||||
{ some: 0.7, initial: false },
|
||||
);
|
||||
43
ts/src/landing.ts
Normal file
43
ts/src/landing.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
const { animate, stagger } = Motion;
|
||||
|
||||
const experienceCountSelector = "#years-experience";
|
||||
const languagesCountSelector = "#languages";
|
||||
|
||||
const yrsExpCount = document.getElementById("years-experience");
|
||||
if (!yrsExpCount) {
|
||||
throw new Error("years-experience Element not found");
|
||||
}
|
||||
|
||||
const langCount = document.getElementById("programming-languages");
|
||||
if (!langCount) {
|
||||
throw new Error("languages Element not found");
|
||||
}
|
||||
|
||||
animate(0, 6, {
|
||||
duration: 1.5,
|
||||
ease: "circOut",
|
||||
onUpdate: (latest) => {
|
||||
const yrs = Math.floor(latest);
|
||||
yrsExpCount.innerHTML = String(yrs);
|
||||
},
|
||||
});
|
||||
|
||||
animate(0, 4, {
|
||||
duration: 1.5,
|
||||
ease: "circOut",
|
||||
onUpdate: (latest) => {
|
||||
const languages = Math.floor(latest);
|
||||
langCount.innerHTML = String(languages);
|
||||
},
|
||||
});
|
||||
|
||||
animate(
|
||||
experienceCountSelector,
|
||||
{ count: 5 },
|
||||
{ duration: 1.5, delay: stagger(0.1) },
|
||||
);
|
||||
animate(
|
||||
languagesCountSelector,
|
||||
{ count: 5 },
|
||||
{ duration: 1.5, delay: stagger(0.1) },
|
||||
);
|
||||
32
ts/src/navigation.ts
Normal file
32
ts/src/navigation.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
const { animate, stagger } = Motion;
|
||||
|
||||
const navItemSelector = "#mobile-nav>li";
|
||||
|
||||
document.addEventListener("alpine:init", () => {
|
||||
Alpine.data("navbar", () => {
|
||||
return {
|
||||
isOpen: false,
|
||||
open() {
|
||||
this.isOpen = true;
|
||||
animate(
|
||||
navItemSelector,
|
||||
{ opacity: [0, 1], x: [-50, 0] },
|
||||
{ delay: stagger(0.1) },
|
||||
);
|
||||
},
|
||||
toggle() {
|
||||
this.isOpen ? this.close() : this.open();
|
||||
},
|
||||
close() {
|
||||
setTimeout(() => {
|
||||
this.isOpen = false;
|
||||
}, 300);
|
||||
animate(
|
||||
navItemSelector,
|
||||
{ opacity: [1, 0], x: [0, 50] },
|
||||
{ delay: stagger(0.1) },
|
||||
);
|
||||
},
|
||||
};
|
||||
});
|
||||
});
|
||||
10
ts/src/types.d.ts
vendored
Normal file
10
ts/src/types.d.ts
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
import type motionPkg from "motion";
|
||||
import type alpine from "alpinejs";
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
Motion: typeof motionPkg;
|
||||
}
|
||||
const Motion: typeof motionPkg;
|
||||
const Alpine: typeof alpine;
|
||||
}
|
||||
113
ts/tsconfig.json
Normal file
113
ts/tsconfig.json
Normal file
@@ -0,0 +1,113 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
/* Visit https://aka.ms/tsconfig to read more about this file */
|
||||
|
||||
/* Projects */
|
||||
// "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
|
||||
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
|
||||
// "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
|
||||
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
|
||||
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
|
||||
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
|
||||
|
||||
/* Language and Environment */
|
||||
"target": "es2016" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,
|
||||
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
|
||||
// "jsx": "preserve", /* Specify what JSX code is generated. */
|
||||
// "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */
|
||||
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
|
||||
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
|
||||
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
|
||||
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
|
||||
// "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
|
||||
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
|
||||
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
|
||||
"moduleDetection": "force" /* Control what method is used to detect module-format JS files. */,
|
||||
|
||||
/* Modules */
|
||||
"module": "esnext" /* Specify what module code is generated. */,
|
||||
// "rootDir": "./", /* Specify the root folder within your source files. */
|
||||
// "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */
|
||||
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
|
||||
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
|
||||
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
|
||||
// "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. *
|
||||
"types": [
|
||||
"./src/types.d.ts"
|
||||
] /* Specify type package names to be included without being referenced in a source file. */,
|
||||
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
||||
// "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
|
||||
// "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */
|
||||
// "rewriteRelativeImportExtensions": true, /* Rewrite '.ts', '.tsx', '.mts', and '.cts' file extensions in relative import paths to their JavaScript equivalent in output files. */
|
||||
// "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */
|
||||
// "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */
|
||||
// "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */
|
||||
// "noUncheckedSideEffectImports": true, /* Check side effect imports. */
|
||||
// "resolveJsonModule": true, /* Enable importing .json files. */
|
||||
// "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */
|
||||
// "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
|
||||
|
||||
/* JavaScript Support */
|
||||
// "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
|
||||
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
|
||||
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
|
||||
|
||||
/* Emit */
|
||||
// "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
|
||||
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
|
||||
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
|
||||
// "sourceMap": true, /* Create source map files for emitted JavaScript files. */
|
||||
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
|
||||
// "noEmit": true, /* Disable emitting files from a compilation. */
|
||||
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
|
||||
// "outDir": "./", /* Specify an output folder for all emitted files. */
|
||||
// "removeComments": true, /* Disable emitting comments. */
|
||||
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
|
||||
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
|
||||
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
|
||||
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
|
||||
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
|
||||
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
|
||||
// "newLine": "crlf", /* Set the newline character for emitting files. */
|
||||
// "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
|
||||
// "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
|
||||
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
|
||||
// "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
|
||||
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
|
||||
|
||||
/* Interop Constraints */
|
||||
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
|
||||
// "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */
|
||||
// "isolatedDeclarations": true, /* Require sufficient annotation on exports so other tools can trivially generate declaration files. */
|
||||
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
|
||||
"esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */,
|
||||
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
|
||||
"forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */,
|
||||
|
||||
/* Type Checking */
|
||||
"strict": true /* Enable all strict type-checking options. */,
|
||||
"noImplicitAny": false /* Enable error reporting for expressions and declarations with an implied 'any' type. */,
|
||||
// "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
|
||||
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
|
||||
// "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
|
||||
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
|
||||
// "strictBuiltinIteratorReturn": true, /* Built-in iterators are instantiated with a 'TReturn' type of 'undefined' instead of 'any'. */
|
||||
// "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
|
||||
// "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
|
||||
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
|
||||
// "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
|
||||
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
|
||||
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
|
||||
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
|
||||
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
|
||||
// "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
|
||||
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
|
||||
// "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
|
||||
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
|
||||
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
|
||||
|
||||
/* Completeness */
|
||||
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
|
||||
"skipLibCheck": true /* Skip type checking all .d.ts files. */
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user