feat: animations, responsive design
Animations - added opacity animation for experience page - added stagger animation for navbar items on mobile - added animation for landing page for years and language indications Pages - About Page mockup Redesign - Navbar to include icons on desktop
This commit is contained in:
37
.air.toml
37
.air.toml
@@ -1,37 +0,0 @@
|
||||
root = "."
|
||||
tmp_dir = "tmp"
|
||||
|
||||
[build]
|
||||
bin = "./tmp/main"
|
||||
cmd = "templ generate && go build -o ./tmp/main ."
|
||||
delay = 1000
|
||||
exclude_dir = ["assets", "tmp", "vendor"]
|
||||
exclude_file = []
|
||||
exclude_regex = [".*_templ.go"]
|
||||
exclude_unchanged = false
|
||||
follow_symlink = false
|
||||
full_bin = ""
|
||||
include_dir = []
|
||||
include_ext = ["go", "tpl", "tmpl", "templ", "html"]
|
||||
kill_delay = "0s"
|
||||
log = "build-errors.log"
|
||||
send_interrupt = false
|
||||
stop_on_error = true
|
||||
|
||||
[color]
|
||||
app = ""
|
||||
build = "yellow"
|
||||
main = "magenta"
|
||||
runner = "green"
|
||||
watcher = "cyan"
|
||||
|
||||
[log]
|
||||
time = false
|
||||
|
||||
[misc]
|
||||
clean_on_exit = false
|
||||
|
||||
[proxy]
|
||||
enabled = true
|
||||
proxy_port = 8383
|
||||
app_port = 8282
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -23,3 +23,6 @@ go.work.sum
|
||||
|
||||
# env file
|
||||
.env
|
||||
|
||||
# frontend
|
||||
ts/node_modules
|
||||
|
||||
@@ -4,12 +4,12 @@ WORKDIR /app
|
||||
COPY go.mod go.sum ./
|
||||
RUN go mod download
|
||||
COPY . /app
|
||||
RUN CGO_ENABLED=0 GOOS=linux go build -o /entrypoint
|
||||
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 /entrypoint /entrypoint
|
||||
COPY --from=build-stage /app/entrypoint /entrypoint
|
||||
COPY --from=build-stage /app/assets /assets
|
||||
EXPOSE 8080
|
||||
USER nonroot:nonroot
|
||||
|
||||
20
Makefile
20
Makefile
@@ -1,5 +1,17 @@
|
||||
generate:
|
||||
templ generate --watch
|
||||
# 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"
|
||||
|
||||
server:
|
||||
air
|
||||
# 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
|
||||
|
||||
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/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 |
@@ -1,40 +0,0 @@
|
||||
function calculateDuration(startDate, endDate) {
|
||||
const start = new Date(startDate);
|
||||
const end = endDate ? new Date(endDate) : new Date();
|
||||
|
||||
const years = end.getFullYear() - start.getFullYear();
|
||||
const months = end.getMonth() - start.getMonth();
|
||||
|
||||
let totalMonths = years * 12 + months;
|
||||
if (end.getDate() < start.getDate()) {
|
||||
totalMonths--;
|
||||
}
|
||||
|
||||
const finalYears = Math.floor(totalMonths / 12);
|
||||
const finalMonths = totalMonths % 12;
|
||||
|
||||
let duration = "";
|
||||
if (finalYears > 0) {
|
||||
duration += `${finalYears} year${finalYears > 1 ? "s" : ""}`;
|
||||
}
|
||||
if (finalMonths > 0) {
|
||||
if (duration) duration += " ";
|
||||
duration += `${finalMonths} month${finalMonths > 1 ? "s" : ""}`;
|
||||
}
|
||||
return duration;
|
||||
}
|
||||
|
||||
function formatDateRange(startDate, endDate) {
|
||||
const start = new Date(startDate);
|
||||
const end = endDate ? new Date(endDate) : new Date();
|
||||
|
||||
const formatDate = (date) => {
|
||||
return date.toLocaleDateString("en-US", {
|
||||
month: "short",
|
||||
year: "numeric",
|
||||
});
|
||||
};
|
||||
|
||||
const duration = calculateDuration(startDate, endDate);
|
||||
return `${formatDate(start)} - ${endDate ? formatDate(end) : "Present"} · ${duration}`;
|
||||
}
|
||||
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"}
|
||||
@@ -1,23 +0,0 @@
|
||||
const { animate, scroll } = Motion;
|
||||
|
||||
const yearsExperience = document.getElementById("years-experience");
|
||||
const programmingLangues = document.getElementById("programming-languages");
|
||||
|
||||
const startDate = new Date("2019-01-01");
|
||||
years = Math.floor((new Date() - startDate) / 1000 / 60 / 60 / 24 / 365);
|
||||
|
||||
animate(0, years, {
|
||||
duration: 2,
|
||||
ease: "circOut",
|
||||
onUpdate: (latest) => {
|
||||
yearsExperience.innerHTML = Math.round(latest);
|
||||
},
|
||||
});
|
||||
|
||||
animate(0, 4, {
|
||||
duration: 2,
|
||||
ease: "circOut",
|
||||
onUpdate: (latest) => {
|
||||
programmingLangues.innerHTML = Math.round(latest);
|
||||
},
|
||||
});
|
||||
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"}
|
||||
@@ -5,7 +5,7 @@
|
||||
"end": null,
|
||||
"stack": ["Go", "Wails", "React", "SQLite3", "TailwindCSS"],
|
||||
"description": "Invoie Management System that helps mechanics create invoices, manage customers, finances and more.",
|
||||
"thumbnail": "/static/images/invoice-manager-thumbnail.png",
|
||||
"thumbnail": "/assets/images/invoice-manager-thumbnail.png",
|
||||
"github_link": "",
|
||||
"status": "In Progress"
|
||||
},
|
||||
|
||||
@@ -2,11 +2,201 @@ package components
|
||||
|
||||
templ AboutPage() {
|
||||
@rootLayout() {
|
||||
<main class="w-full h-full flex flex-col gap-0 items-center justify-center">
|
||||
<section class="flex flex-col gap-6">
|
||||
<h1 class="text-3xl">About Me</h1>
|
||||
<p class="text-base">I am a software engineer with a passion for web development. I have experience with a variety of technologies including React, Node.js, and MongoDB. I am always looking to learn new things and improve my skills.</p>
|
||||
</section>
|
||||
<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>
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
1
components/about_page_templ.txt
Normal file
1
components/about_page_templ.txt
Normal file
File diff suppressed because one or more lines are too long
1
components/contact_page_templ.txt
Normal file
1
components/contact_page_templ.txt
Normal file
File diff suppressed because one or more lines are too long
@@ -1,46 +0,0 @@
|
||||
package components
|
||||
|
||||
import "github.com/moh682/portfolio/domain"
|
||||
import "slices"
|
||||
import "strings"
|
||||
|
||||
templ experienceCard(exp domain.Experience) {
|
||||
<section class="h-full w-full flex relative items-center">
|
||||
<div class="flex h-[600px] sm:px-12 lg:py-40 w-full items-center gap-12 justify-between">
|
||||
<!-- Left Side: Job Details -->
|
||||
<div class="p-6 w-1/2 flex flex-col gap-4 bg-slate-800 rounded-lg hover:bg-slate-800 transition-all animate-slide">
|
||||
<h3 class="text-2xl 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>
|
||||
<!-- Timeline Marker -->
|
||||
<div></div>
|
||||
<!-- Right Side: Company & Role Details -->
|
||||
<div class="flex flex-col w-1/2 gap-6 p-6 bg-slate-800 rounded-lg animate-slideIn">
|
||||
<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>
|
||||
</div>
|
||||
</section>
|
||||
}
|
||||
@@ -1,199 +0,0 @@
|
||||
// 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=\"h-full w-full flex relative items-center\"><div class=\"flex h-[600px] sm:px-12 lg:py-40 w-full items-center gap-12 justify-between\"><!-- Left Side: Job Details --><div class=\"p-6 w-1/2 flex flex-col gap-4 bg-slate-800 rounded-lg hover:bg-slate-800 transition-all animate-slide\"><h3 class=\"text-2xl 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/experience_card.templ`, Line: 12, Col: 65}
|
||||
}
|
||||
_, 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/experience_card.templ`, Line: 14, Col: 113}
|
||||
}
|
||||
_, 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/experience_card.templ`, Line: 15, Col: 48}
|
||||
}
|
||||
_, 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/experience_card.templ`, Line: 16, Col: 59}
|
||||
}
|
||||
_, 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/experience_card.templ`, Line: 16, Col: 82}
|
||||
}
|
||||
_, 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/experience_card.templ`, Line: 17, Col: 100}
|
||||
}
|
||||
_, 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><!-- Timeline Marker --><div></div><!-- Right Side: Company & Role Details --><div class=\"flex flex-col w-1/2 gap-6 p-6 bg-slate-800 rounded-lg animate-slideIn\"><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/experience_card.templ`, Line: 30, Col: 61}
|
||||
}
|
||||
_, 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/experience_card.templ`, Line: 33, Col: 20}
|
||||
}
|
||||
_, 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/experience_card.templ`, Line: 40, Col: 27}
|
||||
}
|
||||
_, 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></div></section>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
var _ = templruntime.GeneratedTemplate
|
||||
@@ -2,59 +2,61 @@
|
||||
package components
|
||||
|
||||
import "github.com/moh682/portfolio/domain"
|
||||
import "fmt"
|
||||
|
||||
import "slices"
|
||||
import "strings"
|
||||
|
||||
templ experienceCard(exp domain.Experience) {
|
||||
<section class="min-h-[var(--content-height)] w-full flex relative items-center">
|
||||
<!-- Left Side: Job Details -->
|
||||
<div class="p-6 flex flex-col gap-4 transition-all opacity-0">
|
||||
<h3 class="text-xl font-semibold text-white">{ exp.Job.Title }</h3>
|
||||
<div class="flex flex-col gap-2">
|
||||
<a href={ templ.SafeURL(exp.Company.Link) } class="text-emerald-400 font-medium text-lg ">{ exp.Company.Name }</a>
|
||||
<p class="text-slate-400 ">{ exp.Duration() }</p>
|
||||
<p class="text-slate-600 text-sm">{ exp.GetStartDate() } - { exp.GetEndDate() } </p>
|
||||
<a href={ templ.SafeURL(exp.Company.LocationLink) } class="text-emerald-500">{ exp.Company.City }</a>
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
for _, stack := range slices.Concat(exp.Job.FrontendStack, exp.Job.BackendStack, exp.Job.InfrastructureStack) {
|
||||
@techStack(stack)
|
||||
}
|
||||
</div>
|
||||
<div class="flex flex-col gap-2">
|
||||
<h4 class="text-xl font-medium">About { exp.Company.Name }</h4>
|
||||
for _, description := range strings.Split(exp.Company.Description, "\n") {
|
||||
<p class="text-slate-300">
|
||||
{ description }
|
||||
</p>
|
||||
}
|
||||
</div>
|
||||
<div class="flex flex-col gap-2">
|
||||
<h4 class="text-xl font-medium">Role & Responsibilities</h4>
|
||||
<div class="text-slate-300 whitespace-pre-wrap">
|
||||
{ exp.Job.Description }
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
}
|
||||
|
||||
templ ExperiencesPage(experiences []domain.Experience) {
|
||||
@rootLayout() {
|
||||
<main class="bg-gradient-to-b from-slate-900 w-full h-full to-slate-800 overflow-hidden">
|
||||
<div class="relative h-full flex w-full items-center justify-between" x-data="{active:$persist(0)}">
|
||||
<!-- Progress Indicator -->
|
||||
<div class="flex items-center px-2 top-0 h-screen z-10 ">
|
||||
<div class="flex flex-col z-100 gap-4 -translate-y-1/2">
|
||||
for i := range experiences {
|
||||
<div
|
||||
class="flex items-center justify-center pointer-events-auto py-2 px-2 hover:cursor-pointer"
|
||||
x-on:click={ fmt.Sprintf("active = %d", i) }
|
||||
>
|
||||
<div
|
||||
class={ "h-12 w-1 mb-4 transition-all duration-300 cursor-pointer hover:bg-emerald-400" }
|
||||
data-index={ templ.JSONString(i) }
|
||||
x-bind:class={ fmt.Sprintf("active == %d ? 'bg-emerald-500' : 'bg-slate-700'", i) }
|
||||
></div>
|
||||
</div>
|
||||
}
|
||||
<script defer src="assets/js/experiences.js"></script>
|
||||
<main class="bg-gradient-to-b from-slate-900 w-full to-slate-800 px-6 overflow-x-hidden">
|
||||
<div class="flex w-full items-center justify-between" x-data="{active:$persist(0)}">
|
||||
<div class="relative w-full flex items-center justify-center">
|
||||
<!-- Timeline -->
|
||||
<div id="timeline" class="fixed w-10 top-[var(--navbar-height)] left-2 h-[var(--content-height)] flex items-center justify-center">
|
||||
<div class="absolute bg-radial-[at_50%_25%] from-40% from-slate-700 h-full w-0.5 to-slate-800"></div>
|
||||
<div id="timeline-point" class={ "flex items-center flex-grow-0 justify-center w-10 h-10 rounded-full bg-emerald-900 border-4 border-slate-800 z-[80]" }>
|
||||
<div class="absolute w-2 h-2 z-[80] bg-emerald-400 rounded-full"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-full h-full flex items-center justify-center">
|
||||
<div class="w-full h-full items-center content-center relative xl:w-[1600px]">
|
||||
<div class="text-center py-8 animate-fade">
|
||||
<h2 class="text-3xl sm:text-4xl font-bold text-white mb-4">Professional Experience</h2>
|
||||
<p class="text-slate-400">Building and scaling software solutions that matter</p>
|
||||
</div>
|
||||
<div class="w-full h-full absolute flex items-center justify-center">
|
||||
<div class="h-full w-0.5 bg-slate-700 transform"></div>
|
||||
</div>
|
||||
<div class="h-full custom-scrollbar scroll-smooth">
|
||||
<div>
|
||||
for i, exp := range experiences {
|
||||
<div
|
||||
class={ "absolute w-full transition-all opacity-0 duration-700 ease-out translate-y-10" }
|
||||
data-experience-index={ templ.JSONString(i) }
|
||||
x-bind:class={ fmt.Sprintf("active == %d ? 'z-10 opacity-100 translate-y-0' : ' -z-10 translate-y-10'", i) }
|
||||
>
|
||||
<div class="h-full w-full">
|
||||
<div class={ fmt.Sprintf("relative flex w-full h-full items-center justify-center translate-y-[%dpx]", i*4*32) }>
|
||||
<div class="absolute animate-pulse w-16 h-16 rounded-full bg-slate-800/50 z-50"></div>
|
||||
<div class={ "flex items-center flex-grow-0 justify-center w-10 h-10 rounded-full bg-emerald-900 border-4 border-slate-800 z-[80]" }>
|
||||
<div class="absolute w-2 h-2 z-[80] bg-emerald-400 rounded-full"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@experienceCard(exp)
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div id="exps-container" class="ml-4 items-center content-center relative xl:w-[1600px]">
|
||||
for _, exp := range experiences {
|
||||
@experienceCard(exp)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -10,9 +10,11 @@ import "github.com/a-h/templ"
|
||||
import templruntime "github.com/a-h/templ/runtime"
|
||||
|
||||
import "github.com/moh682/portfolio/domain"
|
||||
import "fmt"
|
||||
|
||||
func ExperiencesPage(experiences []domain.Experience) templ.Component {
|
||||
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 {
|
||||
@@ -33,7 +35,191 @@ func ExperiencesPage(experiences []domain.Experience) templ.Component {
|
||||
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_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 {
|
||||
@@ -45,190 +231,45 @@ func ExperiencesPage(experiences []domain.Experience) templ.Component {
|
||||
}()
|
||||
}
|
||||
ctx = templ.InitializeContext(ctx)
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<main class=\"bg-gradient-to-b from-slate-900 w-full h-full to-slate-800 overflow-hidden\"><div class=\"relative h-full flex w-full items-center justify-between\" x-data=\"{active:$persist(0)}\"><!-- Progress Indicator --><div class=\"flex items-center px-2 top-0 h-screen z-10 \"><div class=\"flex flex-col z-100 gap-4 -translate-y-1/2\">")
|
||||
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
|
||||
}
|
||||
for i := range experiences {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<div class=\"flex items-center justify-center pointer-events-auto py-2 px-2 hover:cursor-pointer\" x-on:click=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var3 string
|
||||
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("active = %d", i))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/experiences_page.templ`, Line: 17, Col: 50}
|
||||
}
|
||||
_, 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, "\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var4 = []any{"h-12 w-1 mb-4 transition-all duration-300 cursor-pointer hover:bg-emerald-400"}
|
||||
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, 4, "<div 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/experiences_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, 5, "\" data-index=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var6 string
|
||||
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(templ.JSONString(i))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/experiences_page.templ`, Line: 21, Col: 41}
|
||||
}
|
||||
_, 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, "\" x-bind:class=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var7 string
|
||||
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("active == %d ? 'bg-emerald-500' : 'bg-slate-700'", i))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/experiences_page.templ`, Line: 22, Col: 90}
|
||||
}
|
||||
_, 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, "\"></div></div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "</div></div><div class=\"w-full h-full flex items-center justify-center\"><div class=\"w-full h-full items-center content-center relative xl:w-[1600px]\"><div class=\"text-center py-8 animate-fade\"><h2 class=\"text-3xl sm:text-4xl font-bold text-white mb-4\">Professional Experience</h2><p class=\"text-slate-400\">Building and scaling software solutions that matter</p></div><div class=\"w-full h-full absolute flex items-center justify-center\"><div class=\"h-full w-0.5 bg-slate-700 transform\"></div></div><div class=\"h-full custom-scrollbar scroll-smooth\"><div>")
|
||||
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
|
||||
}
|
||||
for i, exp := range experiences {
|
||||
var templ_7745c5c3_Var8 = []any{"absolute w-full transition-all opacity-0 duration-700 ease-out translate-y-10"}
|
||||
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var8...)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "<div class=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var9 string
|
||||
templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var8).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_Var9))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "\" data-experience-index=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var10 string
|
||||
templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(templ.JSONString(i))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/experiences_page.templ`, Line: 42, Col: 53}
|
||||
}
|
||||
_, 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, "\" x-bind:class=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var11 string
|
||||
templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("active == %d ? 'z-10 opacity-100 translate-y-0' : ' -z-10 translate-y-10'", i))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/experiences_page.templ`, Line: 43, Col: 116}
|
||||
}
|
||||
_, 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, 12, "\"><div class=\"h-full w-full\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var12 = []any{fmt.Sprintf("relative flex w-full h-full items-center justify-center translate-y-[%dpx]", i*4*32)}
|
||||
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var12...)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "<div class=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var13 string
|
||||
templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var12).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_Var13))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "\"><div class=\"absolute animate-pulse w-16 h-16 rounded-full bg-slate-800/50 z-50\"></div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var14 = []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_Var14...)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "<div class=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var15 string
|
||||
templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var14).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_Var15))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "\"><div class=\"absolute w-2 h-2 z-[80] bg-emerald-400 rounded-full\"></div></div></div></div>")
|
||||
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, 17, "</div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "</div></div></div></div></div></main>")
|
||||
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_Var2), templ_7745c5c3_Buffer)
|
||||
templ_7745c5c3_Err = rootLayout().Render(templ.WithChildren(ctx, templ_7745c5c3_Var14), templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
|
||||
19
components/experiences_page_templ.txt
Normal file
19
components/experiences_page_templ.txt
Normal file
@@ -0,0 +1,19 @@
|
||||
<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\">
|
||||
</h3><div class=\"flex flex-col gap-2\"><a href=\"
|
||||
\" class=\"text-emerald-400 font-medium text-lg \">
|
||||
</a><p class=\"text-slate-400 \">
|
||||
</p><p class=\"text-slate-600 text-sm\">
|
||||
-
|
||||
</p><a href=\"
|
||||
\" class=\"text-emerald-500\">
|
||||
</a></div><div class=\"flex flex-wrap gap-2\">
|
||||
</div><div class=\"flex flex-col gap-2\"><h4 class=\"text-xl font-medium\">About
|
||||
</h4>
|
||||
<p class=\"text-slate-300\">
|
||||
</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\">
|
||||
</div></div></div></section>
|
||||
<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=\"
|
||||
\"><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]\">
|
||||
</div></div></div></main>
|
||||
5
components/external_links_templ.txt
Normal file
5
components/external_links_templ.txt
Normal file
@@ -0,0 +1,5 @@
|
||||
<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=\"
|
||||
\" height=\"
|
||||
\" 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=\"
|
||||
\" height=\"
|
||||
\" 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>
|
||||
@@ -2,16 +2,16 @@ package components
|
||||
|
||||
templ LandingPage() {
|
||||
@rootLayout() {
|
||||
<script defer src="assets/js/landing-page-animations.js"></script>
|
||||
<main class="flex items-center justify-center h-full bg-gradient-to-b from-slate-900 to-slate-800">
|
||||
<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="container mx-auto px-4 sm:px-6 lg:px-8 py-16 mb-24 sm:py-24">
|
||||
<div class="max-w-7xl mx-auto">
|
||||
<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="space-y-8">
|
||||
<div class="space-y-4">
|
||||
<span class="px-4 py-2 bg-emerald-900/50 text-emerald-400 rounded-full text-sm font-medium">Software Developer</span>
|
||||
<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>
|
||||
@@ -46,7 +46,7 @@ templ LandingPage() {
|
||||
<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="/images/profile.jpg"
|
||||
src="/assets/images/portfolio-photo.webp"
|
||||
alt="Mohammad Hariri"
|
||||
class="w-full h-full object-cover rounded-xl"
|
||||
/>
|
||||
@@ -54,7 +54,7 @@ templ LandingPage() {
|
||||
</div>
|
||||
</div>
|
||||
<!-- Stats Section -->
|
||||
<div class="mt-20 grid grid-cols-2 sm:grid-cols-4 gap-8">
|
||||
<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>
|
||||
|
||||
@@ -41,7 +41,7 @@ func LandingPage() templ.Component {
|
||||
}()
|
||||
}
|
||||
ctx = templ.InitializeContext(ctx)
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<script defer src=\"static/js/landing-page-animations.js\"></script> <main class=\"flex items-center justify-center h-full bg-gradient-to-b from-slate-900 to-slate-800\"><!-- Hero Section --><div class=\"container mx-auto px-4 sm:px-6 lg:px-8 py-16 mb-24 sm:py-24\"><div class=\"max-w-7xl mx-auto\"><div class=\"grid grid-cols-1 lg:grid-cols-2 gap-12 items-center\"><!-- Left Content --><div class=\"space-y-8\"><div class=\"space-y-4\"><span class=\"px-4 py-2 bg-emerald-900/50 text-emerald-400 rounded-full text-sm font-medium\">Software Developer</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=\"/images/profile.jpg\" alt=\"Mohammad Hariri\" class=\"w-full h-full object-cover rounded-xl\"></div></div></div><!-- Stats Section --><div class=\"mt-20 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>")
|
||||
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
|
||||
}
|
||||
|
||||
1
components/landing_page_templ.txt
Normal file
1
components/landing_page_templ.txt
Normal file
@@ -0,0 +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>
|
||||
@@ -1,58 +1,23 @@
|
||||
package components
|
||||
|
||||
templ rootLayout() {
|
||||
<html class="w-full h-full overflow-hidden bg-slate-900 text-white">
|
||||
<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>
|
||||
<style>
|
||||
@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;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="flex flex-col w-full h-full">
|
||||
@navbar()
|
||||
<div class="h-full w-full">
|
||||
{ children... }
|
||||
</div>
|
||||
{ children... }
|
||||
</body>
|
||||
</html>
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ func rootLayout() templ.Component {
|
||||
templ_7745c5c3_Var1 = templ.NopComponent
|
||||
}
|
||||
ctx = templ.ClearChildren(ctx)
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<html class=\"w-full h-full overflow-hidden bg-slate-900 text-white\"><head><script src=\"https://unpkg.com/@tailwindcss/browser@4\"></script><script src=\"https://cdn.jsdelivr.net/npm/motion@latest/dist/motion.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><title>Mohammad's Portfolio</title><style>\n\t\t\t@keyframes fadeIn {\n\t\t\t\tfrom { opacity: 0; }\n\t\t\t\tto { opacity: 1; }\n\t\t\t}\n\n\t\t\t@keyframes slideIn {\n\t\t\t\tfrom { \n\t\t\t\t\topacity: 0;\n\t\t\t\t\ttransform: translateY(20px);\n\t\t\t\t}\n\t\t\t\tto {\n\t\t\t\t\topacity: 1;\n\t\t\t\t\ttransform: translateY(0);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t.animate-fade-in {\n\t\t\t\topacity: 0;\n\t\t\t\tanimation: fadeIn 1s ease-in forwards;\n\t\t\t}\n\n\t\t\t.animate-slide-in {\n\t\t\t\topacity: 0;\n\t\t\t\tanimation: slideIn 1s ease-out forwards;\n\t\t\t}\n\n\t\t\t.delay-300 {\n\t\t\t\tanimation-delay: 300ms;\n\t\t\t}\n\n\t\t\t.delay-600 {\n\t\t\t\tanimation-delay: 600ms;\n\t\t\t}\n\n\t\t\t.delay-900 {\n\t\t\t\tanimation-delay: 900ms;\n\t\t\t}\n\t\t</style></head><body class=\"flex flex-col w-full h-full\">")
|
||||
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
|
||||
}
|
||||
@@ -37,15 +37,11 @@ func rootLayout() templ.Component {
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<div class=\"h-full w-full\">")
|
||||
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, 3, "</div></body></html>")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "</body></html>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
|
||||
2
components/layout_templ.txt
Normal file
2
components/layout_templ.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
<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\">
|
||||
</body></html>
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
components/logo_templ.txt
Normal file
1
components/logo_templ.txt
Normal file
File diff suppressed because one or more lines are too long
@@ -1,28 +1,86 @@
|
||||
package components
|
||||
|
||||
templ navbar() {
|
||||
<nav class="bg-slate-900 z-50 w-full py-4 justify-between items-center h-20 flex gap-6 px-6">
|
||||
<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="/">
|
||||
@Logo(4 * 32)
|
||||
<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-6">
|
||||
<li><a href="/experiences">Experiences</a></li>
|
||||
<li><a href="/projects">Projects</a></li>
|
||||
<li><a href="/about">About</a></li>
|
||||
<li><a href="/contact">Contact</a></li>
|
||||
<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">
|
||||
<li class="md:hidden">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-align-justify"><path d="M3 12h18"></path><path d="M3 18h18"></path><path d="M3 6h18"></path></svg>
|
||||
</li>
|
||||
<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>
|
||||
}
|
||||
|
||||
@@ -29,15 +29,15 @@ func navbar() templ.Component {
|
||||
templ_7745c5c3_Var1 = templ.NopComponent
|
||||
}
|
||||
ctx = templ.ClearChildren(ctx)
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<nav class=\"bg-slate-900 z-50 w-full py-4 justify-between items-center h-20 flex gap-6 px-6\"><ul class=\"flex gap-4\"><li><a href=\"/\">")
|
||||
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(4*32).Render(ctx, templ_7745c5c3_Buffer)
|
||||
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-6\"><li><a href=\"/experiences\">Experiences</a></li><li><a href=\"/projects\">Projects</a></li><li><a href=\"/about\">About</a></li><li><a href=\"/contact\">Contact</a></li></div></div></ul><ul class=\"w-6\"><li class=\"md:hidden\"><svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"lucide lucide-align-justify\"><path d=\"M3 12h18\"></path><path d=\"M3 18h18\"></path><path d=\"M3 6h18\"></path></svg></li></ul></nav>")
|
||||
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
|
||||
}
|
||||
|
||||
2
components/navbar_templ.txt
Normal file
2
components/navbar_templ.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
<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\">
|
||||
</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>
|
||||
1
components/notfound_templ.txt
Normal file
1
components/notfound_templ.txt
Normal file
@@ -0,0 +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>
|
||||
17
components/projects_page_templ.txt
Normal file
17
components/projects_page_templ.txt
Normal file
@@ -0,0 +1,17 @@
|
||||
<span class=\"px-3 py-1 w-fit text-sm bg-slate-700 text-slate-300 rounded-full\">
|
||||
</span>
|
||||
<span class=\"
|
||||
\">
|
||||
</span>
|
||||
<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=\"
|
||||
\" alt=\"
|
||||
\" 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\">
|
||||
</h3>
|
||||
<p class=\"text-slate-400 mb-4 line-clamp-2\">
|
||||
</p><div class=\"flex flex-wrap gap-2\">
|
||||
</div></div>
|
||||
<a href=\"
|
||||
\" 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>
|
||||
<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\">
|
||||
</div></div></main>
|
||||
13
components/tech_stack_templ.txt
Normal file
13
components/tech_stack_templ.txt
Normal file
@@ -0,0 +1,13 @@
|
||||
<a href=\"
|
||||
\" 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\">
|
||||
<img src=\"
|
||||
\" alt=\"
|
||||
\" class=\"w-5 h-5 object-contain\">
|
||||
<span class=\"text-emerald-500 w-fit font-medium group-hover:text-emerald-400\">
|
||||
</span></a>
|
||||
<div class=\"flex items-center gap-2 px-3 py-2 rounded-md bg-slate-700\">
|
||||
<img src=\"
|
||||
\" alt=\"
|
||||
\" class=\"w-5 h-5 object-contain\">
|
||||
<span class=\"text-white w-fit font-medium\">
|
||||
</span></div>
|
||||
31
compose_production.yaml
Normal file
31
compose_production.yaml
Normal file
@@ -0,0 +1,31 @@
|
||||
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"
|
||||
- "com.centurylinklabs.watchtower.enable=true"
|
||||
image: portfolio:prod
|
||||
restart: always
|
||||
volumes:
|
||||
letsencrypt:
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
|
||||
-
|
||||
13
main.go
13
main.go
@@ -15,11 +15,7 @@ import (
|
||||
)
|
||||
|
||||
func templHandlerWithOptions(component templ.Component) http.Handler {
|
||||
t := templ.Handler(component, templ.WithStatus(200), templ.WithContentType("text/html"))
|
||||
|
||||
log.Println("templHandlerWithOptions: ", t.Status)
|
||||
|
||||
return t
|
||||
return templ.Handler(component, templ.WithStatus(200), templ.WithContentType("text/html"))
|
||||
}
|
||||
|
||||
func rootHandler(handler http.Handler) http.Handler {
|
||||
@@ -37,7 +33,7 @@ func SafeFileServer(root http.FileSystem) http.Handler {
|
||||
w.Header().Set("X-Frame-Options", "DENY")
|
||||
w.Header().Set("X-XSS-Protection", "1; mode=block")
|
||||
|
||||
log.Println("path", r.URL.Path)
|
||||
log.Println("assets handler", r.URL.Path)
|
||||
|
||||
// Only allow specific file extensions
|
||||
if allowed := func(path string) bool {
|
||||
@@ -45,6 +41,7 @@ func SafeFileServer(root http.FileSystem) http.Handler {
|
||||
".js": true,
|
||||
".css": true,
|
||||
".png": true,
|
||||
".webp": true,
|
||||
".jpg": true,
|
||||
".jpeg": true,
|
||||
".svg": true,
|
||||
@@ -75,7 +72,7 @@ func main() {
|
||||
server := http.NewServeMux()
|
||||
|
||||
// Serve static files with restrictions
|
||||
staticFS := http.Dir("static")
|
||||
staticFS := http.Dir("assets")
|
||||
server.Handle("GET /assets/", http.StripPrefix("/assets/", SafeFileServer(staticFS)))
|
||||
|
||||
server.Handle("GET /about", templHandlerWithOptions(components.AboutPage()))
|
||||
@@ -98,7 +95,7 @@ func main() {
|
||||
server.Handle("GET /{$}", templHandlerWithOptions(components.LandingPage()))
|
||||
|
||||
// 404 fallback page
|
||||
server.Handle("GET /", templHandlerWithOptions(components.NotFound()))
|
||||
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()))
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
package middlewares
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"github.com/a-h/templ"
|
||||
)
|
||||
|
||||
func NotFound(next http.Handler, component templ.Component) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
res := newStatusResponseWriter(w)
|
||||
log.Println("before NotFound: ", res.status)
|
||||
next.ServeHTTP(res, r)
|
||||
log.Println("after NotFound: ", res.status)
|
||||
if res.status == 0 {
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1
|
||||
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