initial page
This commit is contained in:
37
.air.toml
Normal file
37
.air.toml
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
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
|
||||||
36
.github/workflows/release.yml
vendored
Normal file
36
.github/workflows/release.yml
vendored
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
name: Release
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- 'v*'
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
packages: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
release:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
- uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version: 1.22
|
||||||
|
cache: true
|
||||||
|
- uses: ko-build/setup-ko@v0.7
|
||||||
|
- uses: sigstore/cosign-installer@v3.7.0
|
||||||
|
with:
|
||||||
|
cosign-release: v2.2.3
|
||||||
|
- uses: goreleaser/goreleaser-action@v5
|
||||||
|
with:
|
||||||
|
version: v1.24.0
|
||||||
|
args: release --clean
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: '${{ secrets.GITHUB_TOKEN }}'
|
||||||
|
COSIGN_PASSWORD: '${{ secrets.COSIGN_PASSWORD }}'
|
||||||
|
COSIGN_PRIVATE_KEY: '${{ secrets.COSIGN_PRIVATE_KEY }}'
|
||||||
|
COSIGN_PUBLIC_KEY: '${{ secrets.COSIGN_PUBLIC_KEY }}'
|
||||||
16
Dockerfile
Normal file
16
Dockerfile
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# Build.
|
||||||
|
FROM golang:1.24.0 AS build-stage
|
||||||
|
WORKDIR /app
|
||||||
|
COPY go.mod go.sum ./
|
||||||
|
RUN go mod download
|
||||||
|
COPY . /app
|
||||||
|
RUN CGO_ENABLED=0 GOOS=linux go build -o /entrypoint
|
||||||
|
|
||||||
|
# Deploy.
|
||||||
|
FROM gcr.io/distroless/static-debian11 AS release-stage
|
||||||
|
WORKDIR /
|
||||||
|
COPY --from=build-stage /entrypoint /entrypoint
|
||||||
|
COPY --from=build-stage /app/assets /assets
|
||||||
|
EXPOSE 8080
|
||||||
|
USER nonroot:nonroot
|
||||||
|
ENTRYPOINT ["/entrypoint"]
|
||||||
BIN
assets/.DS_Store
vendored
Normal file
BIN
assets/.DS_Store
vendored
Normal file
Binary file not shown.
BIN
assets/images/invoice-manager-thumbnail.png
Normal file
BIN
assets/images/invoice-manager-thumbnail.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 69 KiB |
40
assets/js/dateUtils.js
Normal file
40
assets/js/dateUtils.js
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
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}`;
|
||||||
|
}
|
||||||
23
assets/js/landing-page-animations.js
Normal file
23
assets/js/landing-page-animations.js
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
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);
|
||||||
|
},
|
||||||
|
});
|
||||||
126
assets/json/experiences.json
Normal file
126
assets/json/experiences.json
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"start_date": "2022-07-01T00:00:00.000Z",
|
||||||
|
"end_date": null,
|
||||||
|
"company": {
|
||||||
|
"name": "Awaze",
|
||||||
|
"logo": "",
|
||||||
|
"link": "https://www.awaze.com/",
|
||||||
|
"location_link": "https://www.google.com/maps/place//data=!4m2!3m1!1s0x465252660a2125a1:0x5a2e60c905a1fd5a?sa=X&ved=1t:8290&ictx=111",
|
||||||
|
"city": "Virum (Copenhagen)",
|
||||||
|
"description": "Awaze is the largest managed vacation rentals and holiday resorts business in Europe, which brings together some of the continent's most trusted travel brands, including cottages.com, Hoseasons and NOVASOL."
|
||||||
|
},
|
||||||
|
"Job": {
|
||||||
|
"title": "Software Engineer",
|
||||||
|
"description": "I am working as a Software Engineer in the Awaze's Digital team. I am responsible for developing and maintaining the company's websites and internal tools. I am also involved in the development of new features and improvements to the existing systems.",
|
||||||
|
"objectives": ["Objective 1", "Objective 2", "Objective 3"],
|
||||||
|
"frontend_stack": [
|
||||||
|
{ "name": "NextJS", "link": "https://nextjs.org/" },
|
||||||
|
{ "name": "React", "link": "https://reactjs.org/" },
|
||||||
|
{ "name": "Zustand", "link": "https://zustand-demo.pmnd.rs/" },
|
||||||
|
{ "name": "Bit", "link": "https://bit.dev/" },
|
||||||
|
{
|
||||||
|
"name": "ApolloClient",
|
||||||
|
"link": "https://www.apollographql.com/docs/react"
|
||||||
|
},
|
||||||
|
{ "name": "Storybook", "link": "https://storybook.js.org/" },
|
||||||
|
{
|
||||||
|
"name": "Styled-Components",
|
||||||
|
"link": "https://styled-components.com/"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"backend_stack": [
|
||||||
|
{ "name": "Nodejs", "link": "https://nodejs.org/en" },
|
||||||
|
{ "name": "GraphQL", "link": "https://graphql.org/" },
|
||||||
|
{ "name": "Apollo", "link": "https://www.apollographql.com/docs/" },
|
||||||
|
{ "name": "Lambda", "link": "https://aws.amazon.com/lambda/" },
|
||||||
|
{ "name": "Traveller (MSSQL)", "link": "" }
|
||||||
|
],
|
||||||
|
"infrastructure_stack": [
|
||||||
|
{ "name": "Serverless", "link": "https://www.serverless.com/" },
|
||||||
|
{ "name": "Cognito", "link": "https://aws.amazon.com/cognito/" },
|
||||||
|
{ "name": "AWS SES", "link": "" },
|
||||||
|
{ "name": "Github", "link": "https://github.com/" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"start_date": "2021-08-01T00:00:00.000Z",
|
||||||
|
"end_date": "2022-07-30T00:00:00.000Z",
|
||||||
|
"company": {
|
||||||
|
"name": "Kiapro",
|
||||||
|
"logo": "",
|
||||||
|
"link": "https://www.kiapro.dk/om-kiapro",
|
||||||
|
"location_link": "https://www.google.com/maps/place//data=!4m2!3m1!1s0x46525052d1d32a6d:0x47b68788842d92aa?sa=X&ved=1t:8290&ictx=111",
|
||||||
|
"city": "Herlev (Copenhagen)",
|
||||||
|
"description": "Kiapro is a Danish company that provides software solutions both for Fin-Tech and Healthcare sector. The company's main product is a platform that helps Fin-Tech management engage with employees through an interactive intranet.\nOther products include all-round applications for Chiropractors to follow patients through their transformation which is called BackTrace."
|
||||||
|
},
|
||||||
|
"Job": {
|
||||||
|
"title": "Full Stack Developer",
|
||||||
|
"description": "As a Full Stack Developer at Doctolib, I contributed to the development of the core booking platform and various healthcare professional tools, focusing on both performance optimization and feature development.",
|
||||||
|
"objectives": [
|
||||||
|
"Improved platform performance and scalability",
|
||||||
|
"Developed new features for healthcare professionals",
|
||||||
|
"Implemented real-time notification systems"
|
||||||
|
],
|
||||||
|
"frontend_stack": [
|
||||||
|
{ "name": "React", "link": "https://reactjs.org/" },
|
||||||
|
{ "name": "Redux", "link": "https://redux.js.org/" },
|
||||||
|
{ "name": "Capacitor", "link": "https://capacitorjs.com/" },
|
||||||
|
{ "name": "TypeScript", "link": "https://www.typescriptlang.org/" }
|
||||||
|
],
|
||||||
|
"backend_stack": [
|
||||||
|
{ "name": "PHP", "link": "https://www.ruby-lang.org/" },
|
||||||
|
{ "name": "PostgreSQL", "link": "https://www.postgresql.org/" },
|
||||||
|
{ "name": "Go", "link": "https://go.dev/" },
|
||||||
|
{ "name": "Fiber", "link": "https://gofiber.io/" },
|
||||||
|
{ "name": "Laravel", "link": "https://laravel.com/" }
|
||||||
|
],
|
||||||
|
"infrastructure_stack": [
|
||||||
|
{ "name": "Docker", "link": "https://www.docker.com/" },
|
||||||
|
{ "name": "Self-hosted VM", "link": "" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"start_date": "2019-10-01T00:00:00.000Z",
|
||||||
|
"end_date": "2021-07-31T00:00:00.000Z",
|
||||||
|
"company": {
|
||||||
|
"name": "Fellowmind",
|
||||||
|
"logo": "",
|
||||||
|
"link": "https://www.fellowmind.com/da-dk/",
|
||||||
|
"location_link": "https://maps.google.com/maps?hl=da&gl=se&um=1&ie=UTF-8&fb=1&sa=X&ftid=0x465253083f4d1201:0x8e4ee256108ec86a",
|
||||||
|
"city": "Copenhagen Area",
|
||||||
|
"description": "Fellowmind helps customers with accelerating their digital transformation by using Microsoft's cloud-solutions, progress to agile development, implement integrated platforms and help end-users to apply those techonologies. With other words, to allow people to enjoy working with technology, and to make the technology work for them."
|
||||||
|
},
|
||||||
|
"Job": {
|
||||||
|
"title": "Software Engineer",
|
||||||
|
"description": "I was apart of the team called IntraActive which was responsible for creating Interactive Components that can be added to their pre-built Microsoft pages. I Developed and maintained a customisable Phonebook that can be configured to any company theme.",
|
||||||
|
"objectives": [
|
||||||
|
"Developed new monitoring integrations",
|
||||||
|
"Optimized data processing pipelines",
|
||||||
|
"Enhanced visualization components"
|
||||||
|
],
|
||||||
|
"frontend_stack": [
|
||||||
|
{
|
||||||
|
"name": "Microsoft SPFX",
|
||||||
|
"link": "https://learn.microsoft.com/en-us/sharepoint/dev/spfx/sharepoint-framework-overview"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Microsoft Sharepoint",
|
||||||
|
"link": "https://learn.microsoft.com/en-us/sharepoint/"
|
||||||
|
},
|
||||||
|
{ "name": "TypeScript", "link": "https://www.typescriptlang.org/" },
|
||||||
|
{ "name": "React", "link": "https://reactjs.org/" },
|
||||||
|
{ "name": "Mobx", "link": "https://mobx.js.org/README.html" }
|
||||||
|
],
|
||||||
|
"backend_stack": [
|
||||||
|
{ "name": "C#", "link": "" },
|
||||||
|
{ "name": ".NET", "link": "" },
|
||||||
|
{ "name": "Azure Functions", "link": "" },
|
||||||
|
{ "name": "Cosmos", "link": "" }
|
||||||
|
],
|
||||||
|
"infrastructure_stack": [{ "name": "Microservices", "link": "" }]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
22
assets/json/projects.json
Normal file
22
assets/json/projects.json
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"name": "Invoice Management System",
|
||||||
|
"start": null,
|
||||||
|
"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",
|
||||||
|
"github_link": "",
|
||||||
|
"status": "In Progress"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Portfolio",
|
||||||
|
"start": null,
|
||||||
|
"end": null,
|
||||||
|
"stack": ["Go", "Templ", "Motion", "TailwindCSS"],
|
||||||
|
"description": "The website you are currently on. A portfolio website that showcases my projects, experiences and skills.",
|
||||||
|
"thumbnail": "",
|
||||||
|
"github_link": "",
|
||||||
|
"status": "Completed"
|
||||||
|
}
|
||||||
|
]
|
||||||
12
components/about_page.templ
Normal file
12
components/about_page.templ
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
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>
|
||||||
|
}
|
||||||
|
}
|
||||||
58
components/about_page_templ.go
Normal file
58
components/about_page_templ.go
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
// Code generated by templ - DO NOT EDIT.
|
||||||
|
|
||||||
|
// templ: version: v0.3.833
|
||||||
|
package components
|
||||||
|
|
||||||
|
//lint:file-ignore SA4006 This context is only used if a nested component is present.
|
||||||
|
|
||||||
|
import "github.com/a-h/templ"
|
||||||
|
import templruntime "github.com/a-h/templ/runtime"
|
||||||
|
|
||||||
|
func AboutPage() templ.Component {
|
||||||
|
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
||||||
|
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
||||||
|
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
|
||||||
|
return templ_7745c5c3_CtxErr
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
||||||
|
if !templ_7745c5c3_IsBuffer {
|
||||||
|
defer func() {
|
||||||
|
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||||
|
if templ_7745c5c3_Err == nil {
|
||||||
|
templ_7745c5c3_Err = templ_7745c5c3_BufErr
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
ctx = templ.InitializeContext(ctx)
|
||||||
|
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
|
||||||
|
if templ_7745c5c3_Var1 == nil {
|
||||||
|
templ_7745c5c3_Var1 = templ.NopComponent
|
||||||
|
}
|
||||||
|
ctx = templ.ClearChildren(ctx)
|
||||||
|
templ_7745c5c3_Var2 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
||||||
|
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
||||||
|
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
||||||
|
if !templ_7745c5c3_IsBuffer {
|
||||||
|
defer func() {
|
||||||
|
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||||
|
if templ_7745c5c3_Err == nil {
|
||||||
|
templ_7745c5c3_Err = templ_7745c5c3_BufErr
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
ctx = templ.InitializeContext(ctx)
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<main class=\"w-full h-full flex flex-col gap-0 items-center justify-center\"><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>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
templ_7745c5c3_Err = rootLayout().Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ = templruntime.GeneratedTemplate
|
||||||
109
components/contact_page.templ
Normal file
109
components/contact_page.templ
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
package components
|
||||||
|
|
||||||
|
templ ContactPage() {
|
||||||
|
@rootLayout() {
|
||||||
|
<main class="w-full min-h-full py-16 px-4 sm:px-6 lg:px-8">
|
||||||
|
<div class="max-w-3xl mx-auto">
|
||||||
|
<div class="text-center mb-12">
|
||||||
|
<h1 class="text-4xl font-bold text-slate-100 mb-4">Get in Touch</h1>
|
||||||
|
<p class="text-slate-400">Have a question or want to work together? Let's connect!</p>
|
||||||
|
</div>
|
||||||
|
<div class="bg-slate-800/50 rounded-2xl p-8 backdrop-blur-sm shadow-xl">
|
||||||
|
<form id="contactForm" class="space-y-6" onsubmit="handleSubmit(event)">
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
|
<div>
|
||||||
|
<label for="name" class="block text-sm font-medium text-slate-300 mb-2">Name</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="name"
|
||||||
|
name="name"
|
||||||
|
required
|
||||||
|
class="w-full px-4 py-3 bg-slate-700/50 border border-slate-600 rounded-lg focus:ring-2 focus:ring-emerald-500 focus:border-transparent text-slate-100 placeholder-slate-400 transition-all"
|
||||||
|
placeholder="Your name"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="email" class="block text-sm font-medium text-slate-300 mb-2">Email</label>
|
||||||
|
<input
|
||||||
|
type="email"
|
||||||
|
id="email"
|
||||||
|
name="email"
|
||||||
|
required
|
||||||
|
class="w-full px-4 py-3 bg-slate-700/50 border border-slate-600 rounded-lg focus:ring-2 focus:ring-emerald-500 focus:border-transparent text-slate-100 placeholder-slate-400 transition-all"
|
||||||
|
placeholder="your@email.com"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="subject" class="block text-sm font-medium text-slate-300 mb-2">Subject</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="subject"
|
||||||
|
name="subject"
|
||||||
|
required
|
||||||
|
class="w-full px-4 py-3 bg-slate-700/50 border border-slate-600 rounded-lg focus:ring-2 focus:ring-emerald-500 focus:border-transparent text-slate-100 placeholder-slate-400 transition-all"
|
||||||
|
placeholder="What's this about?"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="message" class="block text-sm font-medium text-slate-300 mb-2">Message</label>
|
||||||
|
<textarea
|
||||||
|
id="message"
|
||||||
|
name="message"
|
||||||
|
rows="6"
|
||||||
|
required
|
||||||
|
class="w-full px-4 py-3 bg-slate-700/50 border border-slate-600 rounded-lg focus:ring-2 focus:ring-emerald-500 focus:border-transparent text-slate-100 placeholder-slate-400 transition-all resize-none"
|
||||||
|
placeholder="Your message here..."
|
||||||
|
></textarea>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
class="inline-flex items-center px-6 py-3 border border-transparent rounded-lg text-base font-medium text-white bg-emerald-600 hover:bg-emerald-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-emerald-500 transition-all"
|
||||||
|
>
|
||||||
|
Send Message
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 ml-2" viewBox="0 0 20 20" fill="currentColor">
|
||||||
|
<path d="M10.894 2.553a1 1 0 00-1.788 0l-7 14a1 1 0 001.169 1.409l5-1.429A1 1 0 009 15.571V11a1 1 0 112 0v4.571a1 1 0 00.725.962l5 1.428a1 1 0 001.17-1.408l-7-14z"></path>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<div class="flex items-center space-x-4">
|
||||||
|
<a href="https://linkedin.com/in/yourprofile" target="_blank" rel="noopener noreferrer" class="text-slate-400 hover:text-slate-300 transition-colors">
|
||||||
|
<svg class="h-6 w-6" fill="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path d="M19 0h-14c-2.761 0-5 2.239-5 5v14c0 2.761 2.239 5 5 5h14c2.762 0 5-2.239 5-5v-14c0-2.761-2.238-5-5-5zm-11 19h-3v-11h3v11zm-1.5-12.268c-.966 0-1.75-.79-1.75-1.764s.784-1.764 1.75-1.764 1.75.79 1.75 1.764-.783 1.764-1.75 1.764zm13.5 12.268h-3v-5.604c0-3.368-4-3.113-4 0v5.604h-3v-11h3v1.765c1.396-2.586 7-2.777 7 2.476v6.759z"></path>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
<a href="https://github.com/yourusername" target="_blank" rel="noopener noreferrer" class="text-slate-400 hover:text-slate-300 transition-colors">
|
||||||
|
<svg class="h-6 w-6" fill="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"></path>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
<script>
|
||||||
|
function handleSubmit(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
const form = event.target;
|
||||||
|
const formData = new FormData(form);
|
||||||
|
|
||||||
|
// Construct mailto URL
|
||||||
|
const email = 'your@email.com'; // Replace with your email
|
||||||
|
const subject = formData.get('subject');
|
||||||
|
const name = formData.get('name');
|
||||||
|
const message = `Name: ${name}\nEmail: ${formData.get('email')}\n\nMessage:\n${formData.get('message')}`;
|
||||||
|
|
||||||
|
const mailtoUrl = `mailto:${email}?subject=${encodeURIComponent(subject)}&body=${encodeURIComponent(message)}`;
|
||||||
|
|
||||||
|
// Open email client
|
||||||
|
window.location.href = mailtoUrl;
|
||||||
|
|
||||||
|
// Optional: Reset form
|
||||||
|
form.reset();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
}
|
||||||
|
}
|
||||||
58
components/contact_page_templ.go
Normal file
58
components/contact_page_templ.go
Normal file
File diff suppressed because one or more lines are too long
46
components/experience_card.templ
Normal file
46
components/experience_card.templ
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
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>
|
||||||
|
}
|
||||||
199
components/experience_card_templ.go
Normal file
199
components/experience_card_templ.go
Normal file
@@ -0,0 +1,199 @@
|
|||||||
|
// 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
|
||||||
63
components/experiences_page.templ
Normal file
63
components/experiences_page.templ
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
|
||||||
|
package components
|
||||||
|
|
||||||
|
import "github.com/moh682/portfolio/domain"
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
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>
|
||||||
|
}
|
||||||
|
</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>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
}
|
||||||
|
}
|
||||||
239
components/experiences_page_templ.go
Normal file
239
components/experiences_page_templ.go
Normal file
@@ -0,0 +1,239 @@
|
|||||||
|
// 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 "fmt"
|
||||||
|
|
||||||
|
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_Var1 := templ.GetChildren(ctx)
|
||||||
|
if templ_7745c5c3_Var1 == nil {
|
||||||
|
templ_7745c5c3_Var1 = templ.NopComponent
|
||||||
|
}
|
||||||
|
ctx = templ.ClearChildren(ctx)
|
||||||
|
templ_7745c5c3_Var2 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
||||||
|
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
||||||
|
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
||||||
|
if !templ_7745c5c3_IsBuffer {
|
||||||
|
defer func() {
|
||||||
|
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||||
|
if templ_7745c5c3_Err == nil {
|
||||||
|
templ_7745c5c3_Err = templ_7745c5c3_BufErr
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
ctx = templ.InitializeContext(ctx)
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<main class=\"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\">")
|
||||||
|
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>")
|
||||||
|
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 = 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>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
templ_7745c5c3_Err = rootLayout().Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ = templruntime.GeneratedTemplate
|
||||||
14
components/external_links.templ
Normal file
14
components/external_links.templ
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
package components
|
||||||
|
|
||||||
|
var iconSize = "28"
|
||||||
|
|
||||||
|
templ externalLinks() {
|
||||||
|
<div class="fixed flex flex-col gap-6 right-5 bottom-0 p-5 w-5">
|
||||||
|
<a href="https://github.com/moh682" target="_blank" rel="noopener noreferrer">
|
||||||
|
<svg class="hover:cursor-pointer" xmlns="http://www.w3.org/2000/svg" width={ iconSize } height={ iconSize } viewBox="0 0 24 24" fill="transparent" stroke="var(--color-emerald-500)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-github"><path d="M15 22v-4a4.8 4.8 0 0 0-1-3.5c3 0 6-2 6-5.5.08-1.25-.27-2.48-1-3.5.28-1.15.28-2.35 0-3.5 0 0-1 0-3 1.5-2.64-.5-5.36-.5-8 0C6 2 5 2 5 2c-.3 1.15-.3 2.35 0 3.5A5.403 5.403 0 0 0 4 9c0 3.5 3 5.5 6 5.5-.39.49-.68 1.05-.85 1.65-.17.6-.22 1.23-.15 1.85v4"></path><path d="M9 18c-4.51 2-5-2-7-2"></path></svg>
|
||||||
|
</a>
|
||||||
|
<a href="https://www.linkedin.com/in/mohammad-hariri-337a0116b" target="_blank" rel="noopener noreferrer">
|
||||||
|
<svg class="hover:cursor-pointer" xmlns="http://www.w3.org/2000/svg" width={ iconSize } height={ iconSize } viewBox="0 0 {iconSize} {iconSize}" fill="none" stroke="var(--color-emerald-500)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-linkedin"><path d="M16 8a6 6 0 0 1 6 6v7h-4v-7a2 2 0 0 0-2-2 2 2 0 0 0-2 2v7h-4v-7a6 6 0 0 1 6-6z"></path><rect width="4" height="12" x="2" y="9"></rect><circle cx="4" cy="4" r="2"></circle></svg>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
94
components/external_links_templ.go
Normal file
94
components/external_links_templ.go
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
// Code generated by templ - DO NOT EDIT.
|
||||||
|
|
||||||
|
// templ: version: v0.3.833
|
||||||
|
package components
|
||||||
|
|
||||||
|
//lint:file-ignore SA4006 This context is only used if a nested component is present.
|
||||||
|
|
||||||
|
import "github.com/a-h/templ"
|
||||||
|
import templruntime "github.com/a-h/templ/runtime"
|
||||||
|
|
||||||
|
var iconSize = "28"
|
||||||
|
|
||||||
|
func externalLinks() templ.Component {
|
||||||
|
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
||||||
|
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
||||||
|
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
|
||||||
|
return templ_7745c5c3_CtxErr
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
||||||
|
if !templ_7745c5c3_IsBuffer {
|
||||||
|
defer func() {
|
||||||
|
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||||
|
if templ_7745c5c3_Err == nil {
|
||||||
|
templ_7745c5c3_Err = templ_7745c5c3_BufErr
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
ctx = templ.InitializeContext(ctx)
|
||||||
|
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
|
||||||
|
if templ_7745c5c3_Var1 == nil {
|
||||||
|
templ_7745c5c3_Var1 = templ.NopComponent
|
||||||
|
}
|
||||||
|
ctx = templ.ClearChildren(ctx)
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<div class=\"fixed flex flex-col gap-6 right-5 bottom-0 p-5 w-5\"><a href=\"https://github.com/moh682\" target=\"_blank\" rel=\"noopener noreferrer\"><svg class=\"hover:cursor-pointer\" xmlns=\"http://www.w3.org/2000/svg\" width=\"")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var2 string
|
||||||
|
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(iconSize)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/external_links.templ`, Line: 8, Col: 88}
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "\" height=\"")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var3 string
|
||||||
|
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(iconSize)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/external_links.templ`, Line: 8, Col: 108}
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "\" viewBox=\"0 0 24 24\" fill=\"transparent\" stroke=\"var(--color-emerald-500)\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"lucide lucide-github\"><path d=\"M15 22v-4a4.8 4.8 0 0 0-1-3.5c3 0 6-2 6-5.5.08-1.25-.27-2.48-1-3.5.28-1.15.28-2.35 0-3.5 0 0-1 0-3 1.5-2.64-.5-5.36-.5-8 0C6 2 5 2 5 2c-.3 1.15-.3 2.35 0 3.5A5.403 5.403 0 0 0 4 9c0 3.5 3 5.5 6 5.5-.39.49-.68 1.05-.85 1.65-.17.6-.22 1.23-.15 1.85v4\"></path><path d=\"M9 18c-4.51 2-5-2-7-2\"></path></svg></a> <a href=\"https://www.linkedin.com/in/mohammad-hariri-337a0116b\" target=\"_blank\" rel=\"noopener noreferrer\"><svg class=\"hover:cursor-pointer\" xmlns=\"http://www.w3.org/2000/svg\" width=\"")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var4 string
|
||||||
|
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(iconSize)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/external_links.templ`, Line: 11, Col: 88}
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "\" height=\"")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var5 string
|
||||||
|
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(iconSize)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/external_links.templ`, Line: 11, Col: 108}
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "\" viewBox=\"0 0 {iconSize} {iconSize}\" fill=\"none\" stroke=\"var(--color-emerald-500)\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"lucide lucide-linkedin\"><path d=\"M16 8a6 6 0 0 1 6 6v7h-4v-7a2 2 0 0 0-2-2 2 2 0 0 0-2 2v7h-4v-7a6 6 0 0 1 6-6z\"></path><rect width=\"4\" height=\"12\" x=\"2\" y=\"9\"></rect><circle cx=\"4\" cy=\"4\" r=\"2\"></circle></svg></a></div>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ = templruntime.GeneratedTemplate
|
||||||
71
components/landing_page.templ
Normal file
71
components/landing_page.templ
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
package components
|
||||||
|
|
||||||
|
templ LandingPage() {
|
||||||
|
@rootLayout() {
|
||||||
|
<script defer src="assets/js/landing-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>
|
||||||
|
}
|
||||||
|
}
|
||||||
58
components/landing_page_templ.go
Normal file
58
components/landing_page_templ.go
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
// Code generated by templ - DO NOT EDIT.
|
||||||
|
|
||||||
|
// templ: version: v0.3.833
|
||||||
|
package components
|
||||||
|
|
||||||
|
//lint:file-ignore SA4006 This context is only used if a nested component is present.
|
||||||
|
|
||||||
|
import "github.com/a-h/templ"
|
||||||
|
import templruntime "github.com/a-h/templ/runtime"
|
||||||
|
|
||||||
|
func LandingPage() templ.Component {
|
||||||
|
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
||||||
|
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
||||||
|
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
|
||||||
|
return templ_7745c5c3_CtxErr
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
||||||
|
if !templ_7745c5c3_IsBuffer {
|
||||||
|
defer func() {
|
||||||
|
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||||
|
if templ_7745c5c3_Err == nil {
|
||||||
|
templ_7745c5c3_Err = templ_7745c5c3_BufErr
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
ctx = templ.InitializeContext(ctx)
|
||||||
|
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
|
||||||
|
if templ_7745c5c3_Var1 == nil {
|
||||||
|
templ_7745c5c3_Var1 = templ.NopComponent
|
||||||
|
}
|
||||||
|
ctx = templ.ClearChildren(ctx)
|
||||||
|
templ_7745c5c3_Var2 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
||||||
|
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
||||||
|
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
||||||
|
if !templ_7745c5c3_IsBuffer {
|
||||||
|
defer func() {
|
||||||
|
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||||
|
if templ_7745c5c3_Err == nil {
|
||||||
|
templ_7745c5c3_Err = templ_7745c5c3_BufErr
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
ctx = templ.InitializeContext(ctx)
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<script defer src=\"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>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
templ_7745c5c3_Err = rootLayout().Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ = templruntime.GeneratedTemplate
|
||||||
58
components/layout.templ
Normal file
58
components/layout.templ
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
package components
|
||||||
|
|
||||||
|
templ rootLayout() {
|
||||||
|
<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>
|
||||||
|
@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>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
}
|
||||||
56
components/layout_templ.go
Normal file
56
components/layout_templ.go
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
// Code generated by templ - DO NOT EDIT.
|
||||||
|
|
||||||
|
// templ: version: v0.3.833
|
||||||
|
package components
|
||||||
|
|
||||||
|
//lint:file-ignore SA4006 This context is only used if a nested component is present.
|
||||||
|
|
||||||
|
import "github.com/a-h/templ"
|
||||||
|
import templruntime "github.com/a-h/templ/runtime"
|
||||||
|
|
||||||
|
func rootLayout() templ.Component {
|
||||||
|
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
||||||
|
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
||||||
|
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
|
||||||
|
return templ_7745c5c3_CtxErr
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
||||||
|
if !templ_7745c5c3_IsBuffer {
|
||||||
|
defer func() {
|
||||||
|
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||||
|
if templ_7745c5c3_Err == nil {
|
||||||
|
templ_7745c5c3_Err = templ_7745c5c3_BufErr
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
ctx = templ.InitializeContext(ctx)
|
||||||
|
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
|
||||||
|
if templ_7745c5c3_Var1 == nil {
|
||||||
|
templ_7745c5c3_Var1 = templ.NopComponent
|
||||||
|
}
|
||||||
|
ctx = templ.ClearChildren(ctx)
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<html class=\"w-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\">")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = navbar().Render(ctx, templ_7745c5c3_Buffer)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = 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>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ = templruntime.GeneratedTemplate
|
||||||
7
components/logo.templ
Normal file
7
components/logo.templ
Normal file
File diff suppressed because one or more lines are too long
55
components/logo_templ.go
Normal file
55
components/logo_templ.go
Normal file
File diff suppressed because one or more lines are too long
28
components/navbar.templ
Normal file
28
components/navbar.templ
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
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">
|
||||||
|
<ul class="flex gap-4">
|
||||||
|
<li>
|
||||||
|
<a href="/">
|
||||||
|
@Logo(4 * 32)
|
||||||
|
</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>
|
||||||
|
}
|
||||||
48
components/navbar_templ.go
Normal file
48
components/navbar_templ.go
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
// Code generated by templ - DO NOT EDIT.
|
||||||
|
|
||||||
|
// templ: version: v0.3.833
|
||||||
|
package components
|
||||||
|
|
||||||
|
//lint:file-ignore SA4006 This context is only used if a nested component is present.
|
||||||
|
|
||||||
|
import "github.com/a-h/templ"
|
||||||
|
import templruntime "github.com/a-h/templ/runtime"
|
||||||
|
|
||||||
|
func navbar() templ.Component {
|
||||||
|
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
||||||
|
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
||||||
|
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
|
||||||
|
return templ_7745c5c3_CtxErr
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
||||||
|
if !templ_7745c5c3_IsBuffer {
|
||||||
|
defer func() {
|
||||||
|
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||||
|
if templ_7745c5c3_Err == nil {
|
||||||
|
templ_7745c5c3_Err = templ_7745c5c3_BufErr
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
ctx = templ.InitializeContext(ctx)
|
||||||
|
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
|
||||||
|
if templ_7745c5c3_Var1 == nil {
|
||||||
|
templ_7745c5c3_Var1 = templ.NopComponent
|
||||||
|
}
|
||||||
|
ctx = templ.ClearChildren(ctx)
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<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=\"/\">")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = Logo(4*32).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>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ = templruntime.GeneratedTemplate
|
||||||
10
components/notfound.templ
Normal file
10
components/notfound.templ
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
package components
|
||||||
|
|
||||||
|
templ NotFound() {
|
||||||
|
@rootLayout() {
|
||||||
|
<main class="w-full h-full flex flex-col gap-0 items-center justify-center">
|
||||||
|
<h1 class="text-3xl">404</h1>
|
||||||
|
<p class="text-base">Page not found</p>
|
||||||
|
</main>
|
||||||
|
}
|
||||||
|
}
|
||||||
58
components/notfound_templ.go
Normal file
58
components/notfound_templ.go
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
// Code generated by templ - DO NOT EDIT.
|
||||||
|
|
||||||
|
// templ: version: v0.3.833
|
||||||
|
package components
|
||||||
|
|
||||||
|
//lint:file-ignore SA4006 This context is only used if a nested component is present.
|
||||||
|
|
||||||
|
import "github.com/a-h/templ"
|
||||||
|
import templruntime "github.com/a-h/templ/runtime"
|
||||||
|
|
||||||
|
func NotFound() templ.Component {
|
||||||
|
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
||||||
|
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
||||||
|
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
|
||||||
|
return templ_7745c5c3_CtxErr
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
||||||
|
if !templ_7745c5c3_IsBuffer {
|
||||||
|
defer func() {
|
||||||
|
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||||
|
if templ_7745c5c3_Err == nil {
|
||||||
|
templ_7745c5c3_Err = templ_7745c5c3_BufErr
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
ctx = templ.InitializeContext(ctx)
|
||||||
|
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
|
||||||
|
if templ_7745c5c3_Var1 == nil {
|
||||||
|
templ_7745c5c3_Var1 = templ.NopComponent
|
||||||
|
}
|
||||||
|
ctx = templ.ClearChildren(ctx)
|
||||||
|
templ_7745c5c3_Var2 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
||||||
|
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
||||||
|
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
||||||
|
if !templ_7745c5c3_IsBuffer {
|
||||||
|
defer func() {
|
||||||
|
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||||
|
if templ_7745c5c3_Err == nil {
|
||||||
|
templ_7745c5c3_Err = templ_7745c5c3_BufErr
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
ctx = templ.InitializeContext(ctx)
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<main class=\"w-full h-full flex flex-col gap-0 items-center justify-center\"><h1 class=\"text-3xl\">404</h1><p class=\"text-base\">Page not found</p></main>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
templ_7745c5c3_Err = rootLayout().Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ = templruntime.GeneratedTemplate
|
||||||
65
components/projects_page.templ
Normal file
65
components/projects_page.templ
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
package components
|
||||||
|
|
||||||
|
import "github.com/moh682/portfolio/domain"
|
||||||
|
|
||||||
|
templ tag(tag string) {
|
||||||
|
<span class="px-3 py-1 w-fit text-sm bg-slate-700 text-slate-300 rounded-full">{ tag }</span>
|
||||||
|
}
|
||||||
|
|
||||||
|
templ status(status string) {
|
||||||
|
<span class={ "px-3 py-1 w-fit text-sm bg-emerald-700 text-white rounded-full", templ.KV("bg-yellow-600/50", status =="In Progress") }>{ status }</span>
|
||||||
|
}
|
||||||
|
|
||||||
|
templ projectCard(project domain.Project) {
|
||||||
|
<div class="group bg-slate-800 rounded-lg overflow-hidden transition-all duration-300 hover:transform hover:scale-[1.02] hover:shadow-xl">
|
||||||
|
<div class="relative overflow-hidden aspect-video">
|
||||||
|
<img
|
||||||
|
src={ project.Thumbnail }
|
||||||
|
alt={ project.Name }
|
||||||
|
class="w-full h-full object-cover transition-transform duration-300 group-hover:scale-110"
|
||||||
|
/>
|
||||||
|
<div class="absolute inset-0 bg-gradient-to-t from-slate-900 to-transparent opacity-0 group-hover:opacity-60 transition-opacity duration-300"></div>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col gap-4 p-6">
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
|
<h3 class="text-xl font-bold text-slate-100">{ project.Name }</h3>
|
||||||
|
@status(project.Status)
|
||||||
|
<p class="text-slate-400 mb-4 line-clamp-2">{ project.Description }</p>
|
||||||
|
<div class="flex flex-wrap gap-2">
|
||||||
|
for _, t := range project.Stack {
|
||||||
|
@tag(t)
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
if project.GithubLink != "" {
|
||||||
|
<a
|
||||||
|
href={ templ.SafeURL(project.GithubLink) }
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
class="inline-flex items-center text-emerald-500 hover:text-emerald-400 transition-colors"
|
||||||
|
>
|
||||||
|
View Project
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 ml-1" viewBox="0 0 20 20" fill="currentColor">
|
||||||
|
<path fill-rule="evenodd" d="M10.293 3.293a1 1 0 011.414 0l6 6a1 1 0 010 1.414l-6 6a1 1 0 01-1.414-1.414L14.586 11H3a1 1 0 110-2h11.586l-4.293-4.293a1 1 0 010-1.414z" clip-rule="evenodd"></path>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
templ ProjectsPage(projects []domain.Project) {
|
||||||
|
@rootLayout() {
|
||||||
|
<main class="w-full min-h-full py-16 px-4 sm:px-6 lg:px-8">
|
||||||
|
<div class="max-w-7xl mx-auto">
|
||||||
|
<h1 class="text-4xl font-bold text-slate-100 mb-2">Projects</h1>
|
||||||
|
<p class="text-slate-400 mb-12">A collection of projects I've worked on</p>
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
|
||||||
|
for _, project := range projects {
|
||||||
|
@projectCard(project)
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
}
|
||||||
|
}
|
||||||
290
components/projects_page_templ.go
Normal file
290
components/projects_page_templ.go
Normal file
@@ -0,0 +1,290 @@
|
|||||||
|
// Code generated by templ - DO NOT EDIT.
|
||||||
|
|
||||||
|
// templ: version: v0.3.833
|
||||||
|
package components
|
||||||
|
|
||||||
|
//lint:file-ignore SA4006 This context is only used if a nested component is present.
|
||||||
|
|
||||||
|
import "github.com/a-h/templ"
|
||||||
|
import templruntime "github.com/a-h/templ/runtime"
|
||||||
|
|
||||||
|
import "github.com/moh682/portfolio/domain"
|
||||||
|
|
||||||
|
func tag(tag string) templ.Component {
|
||||||
|
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
||||||
|
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
||||||
|
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
|
||||||
|
return templ_7745c5c3_CtxErr
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
||||||
|
if !templ_7745c5c3_IsBuffer {
|
||||||
|
defer func() {
|
||||||
|
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||||
|
if templ_7745c5c3_Err == nil {
|
||||||
|
templ_7745c5c3_Err = templ_7745c5c3_BufErr
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
ctx = templ.InitializeContext(ctx)
|
||||||
|
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
|
||||||
|
if templ_7745c5c3_Var1 == nil {
|
||||||
|
templ_7745c5c3_Var1 = templ.NopComponent
|
||||||
|
}
|
||||||
|
ctx = templ.ClearChildren(ctx)
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<span class=\"px-3 py-1 w-fit text-sm bg-slate-700 text-slate-300 rounded-full\">")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var2 string
|
||||||
|
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(tag)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/projects_page.templ`, Line: 6, Col: 85}
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "</span>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func status(status string) templ.Component {
|
||||||
|
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
||||||
|
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
||||||
|
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
|
||||||
|
return templ_7745c5c3_CtxErr
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
||||||
|
if !templ_7745c5c3_IsBuffer {
|
||||||
|
defer func() {
|
||||||
|
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||||
|
if templ_7745c5c3_Err == nil {
|
||||||
|
templ_7745c5c3_Err = templ_7745c5c3_BufErr
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
ctx = templ.InitializeContext(ctx)
|
||||||
|
templ_7745c5c3_Var3 := templ.GetChildren(ctx)
|
||||||
|
if templ_7745c5c3_Var3 == nil {
|
||||||
|
templ_7745c5c3_Var3 = templ.NopComponent
|
||||||
|
}
|
||||||
|
ctx = templ.ClearChildren(ctx)
|
||||||
|
var templ_7745c5c3_Var4 = []any{"px-3 py-1 w-fit text-sm bg-emerald-700 text-white rounded-full", templ.KV("bg-yellow-600/50", status == "In Progress")}
|
||||||
|
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var4...)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "<span class=\"")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var5 string
|
||||||
|
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var4).String())
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/projects_page.templ`, Line: 1, Col: 0}
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "\">")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var6 string
|
||||||
|
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(status)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/projects_page.templ`, Line: 10, Col: 144}
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "</span>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func projectCard(project domain.Project) templ.Component {
|
||||||
|
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
||||||
|
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
||||||
|
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
|
||||||
|
return templ_7745c5c3_CtxErr
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
||||||
|
if !templ_7745c5c3_IsBuffer {
|
||||||
|
defer func() {
|
||||||
|
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||||
|
if templ_7745c5c3_Err == nil {
|
||||||
|
templ_7745c5c3_Err = templ_7745c5c3_BufErr
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
ctx = templ.InitializeContext(ctx)
|
||||||
|
templ_7745c5c3_Var7 := templ.GetChildren(ctx)
|
||||||
|
if templ_7745c5c3_Var7 == nil {
|
||||||
|
templ_7745c5c3_Var7 = templ.NopComponent
|
||||||
|
}
|
||||||
|
ctx = templ.ClearChildren(ctx)
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "<div class=\"group bg-slate-800 rounded-lg overflow-hidden transition-all duration-300 hover:transform hover:scale-[1.02] hover:shadow-xl\"><div class=\"relative overflow-hidden aspect-video\"><img src=\"")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var8 string
|
||||||
|
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(project.Thumbnail)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/projects_page.templ`, Line: 17, Col: 27}
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "\" alt=\"")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var9 string
|
||||||
|
templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(project.Name)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/projects_page.templ`, Line: 18, Col: 22}
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "\" class=\"w-full h-full object-cover transition-transform duration-300 group-hover:scale-110\"><div class=\"absolute inset-0 bg-gradient-to-t from-slate-900 to-transparent opacity-0 group-hover:opacity-60 transition-opacity duration-300\"></div></div><div class=\"flex flex-col gap-4 p-6\"><div class=\"flex flex-col gap-2\"><h3 class=\"text-xl font-bold text-slate-100\">")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var10 string
|
||||||
|
templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(project.Name)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/projects_page.templ`, Line: 25, Col: 63}
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "</h3>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = status(project.Status).Render(ctx, templ_7745c5c3_Buffer)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "<p class=\"text-slate-400 mb-4 line-clamp-2\">")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var11 string
|
||||||
|
templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(project.Description)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/projects_page.templ`, Line: 27, Col: 69}
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "</p><div class=\"flex flex-wrap gap-2\">")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
for _, t := range project.Stack {
|
||||||
|
templ_7745c5c3_Err = tag(t).Render(ctx, templ_7745c5c3_Buffer)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "</div></div>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
if project.GithubLink != "" {
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "<a href=\"")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var12 templ.SafeURL = templ.SafeURL(project.GithubLink)
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var12)))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"inline-flex items-center text-emerald-500 hover:text-emerald-400 transition-colors\">View Project <svg xmlns=\"http://www.w3.org/2000/svg\" class=\"h-4 w-4 ml-1\" viewBox=\"0 0 20 20\" fill=\"currentColor\"><path fill-rule=\"evenodd\" d=\"M10.293 3.293a1 1 0 011.414 0l6 6a1 1 0 010 1.414l-6 6a1 1 0 01-1.414-1.414L14.586 11H3a1 1 0 110-2h11.586l-4.293-4.293a1 1 0 010-1.414z\" clip-rule=\"evenodd\"></path></svg></a>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "</div></div>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func ProjectsPage(projects []domain.Project) templ.Component {
|
||||||
|
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
||||||
|
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
||||||
|
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
|
||||||
|
return templ_7745c5c3_CtxErr
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
||||||
|
if !templ_7745c5c3_IsBuffer {
|
||||||
|
defer func() {
|
||||||
|
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||||
|
if templ_7745c5c3_Err == nil {
|
||||||
|
templ_7745c5c3_Err = templ_7745c5c3_BufErr
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
ctx = templ.InitializeContext(ctx)
|
||||||
|
templ_7745c5c3_Var13 := templ.GetChildren(ctx)
|
||||||
|
if templ_7745c5c3_Var13 == nil {
|
||||||
|
templ_7745c5c3_Var13 = templ.NopComponent
|
||||||
|
}
|
||||||
|
ctx = templ.ClearChildren(ctx)
|
||||||
|
templ_7745c5c3_Var14 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
||||||
|
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
||||||
|
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
||||||
|
if !templ_7745c5c3_IsBuffer {
|
||||||
|
defer func() {
|
||||||
|
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||||
|
if templ_7745c5c3_Err == nil {
|
||||||
|
templ_7745c5c3_Err = templ_7745c5c3_BufErr
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
ctx = templ.InitializeContext(ctx)
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "<main class=\"w-full min-h-full py-16 px-4 sm:px-6 lg:px-8\"><div class=\"max-w-7xl mx-auto\"><h1 class=\"text-4xl font-bold text-slate-100 mb-2\">Projects</h1><p class=\"text-slate-400 mb-12\">A collection of projects I've worked on</p><div class=\"grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8\">")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
for _, project := range projects {
|
||||||
|
templ_7745c5c3_Err = projectCard(project).Render(ctx, templ_7745c5c3_Buffer)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "</div></div></main>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
templ_7745c5c3_Err = rootLayout().Render(templ.WithChildren(ctx, templ_7745c5c3_Var14), templ_7745c5c3_Buffer)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ = templruntime.GeneratedTemplate
|
||||||
24
components/tech_stack.templ
Normal file
24
components/tech_stack.templ
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
package components
|
||||||
|
|
||||||
|
import "github.com/moh682/portfolio/domain"
|
||||||
|
|
||||||
|
templ techStack(stack domain.Stack) {
|
||||||
|
if stack.Link != "" {
|
||||||
|
<a
|
||||||
|
href={ templ.SafeURL(stack.Link) }
|
||||||
|
class="group flex items-center gap-2 px-3 py-2 rounded-md bg-slate-700/30 hover:bg-slate-700/50 transition-colors duration-200"
|
||||||
|
>
|
||||||
|
if stack.Logo != "" {
|
||||||
|
<img src={ stack.Logo } alt={ stack.Name + " logo" } class="w-5 h-5 object-contain"/>
|
||||||
|
}
|
||||||
|
<span class="text-emerald-500 w-fit font-medium group-hover:text-emerald-400">{ stack.Name }</span>
|
||||||
|
</a>
|
||||||
|
} else {
|
||||||
|
<div class="flex items-center gap-2 px-3 py-2 rounded-md bg-slate-700">
|
||||||
|
if stack.Logo != "" {
|
||||||
|
<img src={ stack.Logo } alt={ stack.Name + " logo" } class="w-5 h-5 object-contain"/>
|
||||||
|
}
|
||||||
|
<span class="text-white w-fit font-medium">{ stack.Name }</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
156
components/tech_stack_templ.go
Normal file
156
components/tech_stack_templ.go
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
// Code generated by templ - DO NOT EDIT.
|
||||||
|
|
||||||
|
// templ: version: v0.3.833
|
||||||
|
package components
|
||||||
|
|
||||||
|
//lint:file-ignore SA4006 This context is only used if a nested component is present.
|
||||||
|
|
||||||
|
import "github.com/a-h/templ"
|
||||||
|
import templruntime "github.com/a-h/templ/runtime"
|
||||||
|
|
||||||
|
import "github.com/moh682/portfolio/domain"
|
||||||
|
|
||||||
|
func techStack(stack domain.Stack) templ.Component {
|
||||||
|
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
||||||
|
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
||||||
|
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
|
||||||
|
return templ_7745c5c3_CtxErr
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
||||||
|
if !templ_7745c5c3_IsBuffer {
|
||||||
|
defer func() {
|
||||||
|
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||||
|
if templ_7745c5c3_Err == nil {
|
||||||
|
templ_7745c5c3_Err = templ_7745c5c3_BufErr
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
ctx = templ.InitializeContext(ctx)
|
||||||
|
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
|
||||||
|
if templ_7745c5c3_Var1 == nil {
|
||||||
|
templ_7745c5c3_Var1 = templ.NopComponent
|
||||||
|
}
|
||||||
|
ctx = templ.ClearChildren(ctx)
|
||||||
|
if stack.Link != "" {
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<a href=\"")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var2 templ.SafeURL = templ.SafeURL(stack.Link)
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var2)))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "\" class=\"group flex items-center gap-2 px-3 py-2 rounded-md bg-slate-700/30 hover:bg-slate-700/50 transition-colors duration-200\">")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
if stack.Logo != "" {
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "<img src=\"")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var3 string
|
||||||
|
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(stack.Logo)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/tech_stack.templ`, Line: 12, Col: 25}
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "\" alt=\"")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var4 string
|
||||||
|
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(stack.Name + " logo")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/tech_stack.templ`, Line: 12, Col: 54}
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "\" class=\"w-5 h-5 object-contain\"> ")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "<span class=\"text-emerald-500 w-fit font-medium group-hover:text-emerald-400\">")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var5 string
|
||||||
|
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(stack.Name)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/tech_stack.templ`, Line: 14, Col: 93}
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "</span></a>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "<div class=\"flex items-center gap-2 px-3 py-2 rounded-md bg-slate-700\">")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
if stack.Logo != "" {
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "<img src=\"")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var6 string
|
||||||
|
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(stack.Logo)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/tech_stack.templ`, Line: 19, Col: 25}
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "\" alt=\"")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var7 string
|
||||||
|
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(stack.Name + " logo")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/tech_stack.templ`, Line: 19, Col: 54}
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "\" class=\"w-5 h-5 object-contain\"> ")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "<span class=\"text-white w-fit font-medium\">")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var8 string
|
||||||
|
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(stack.Name)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/tech_stack.templ`, Line: 21, Col: 58}
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "</span></div>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ = templruntime.GeneratedTemplate
|
||||||
2
deploy_script
Normal file
2
deploy_script
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
|
||||||
|
-
|
||||||
83
domain/experience.go
Normal file
83
domain/experience.go
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
package domain
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Company struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
City string `json:"city"`
|
||||||
|
Link string `json:"link"`
|
||||||
|
Logo string `json:"logo"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
LocationLink string `json:"location_link"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Stack struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Logo string `json:"logo"`
|
||||||
|
Link string `json:"link"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Job struct {
|
||||||
|
Title string `json:"title"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
Objectives []string `json:"objectives"`
|
||||||
|
FrontendStack []Stack `json:"frontend_stack"`
|
||||||
|
BackendStack []Stack `json:"backend_stack"`
|
||||||
|
InfrastructureStack []Stack `json:"infrastructure_stack"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Experience struct {
|
||||||
|
Company Company `json:"company"`
|
||||||
|
Job Job `json:"job"`
|
||||||
|
StartDate time.Time `json:"start_date"`
|
||||||
|
EndDate time.Time `json:"end_date"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e Experience) Duration() string {
|
||||||
|
end := e.EndDate
|
||||||
|
if e.EndDate.IsZero() {
|
||||||
|
end = time.Now()
|
||||||
|
}
|
||||||
|
|
||||||
|
duration := end.Sub(e.StartDate)
|
||||||
|
years := int(duration.Hours() / (24 * 365))
|
||||||
|
months := int(duration.Hours()/(24*30)) - years*12
|
||||||
|
|
||||||
|
label := ""
|
||||||
|
if years == 1 {
|
||||||
|
label += "1 year"
|
||||||
|
}
|
||||||
|
|
||||||
|
if years > 1 {
|
||||||
|
label += fmt.Sprintf("%d years", years)
|
||||||
|
}
|
||||||
|
|
||||||
|
if label != "" {
|
||||||
|
label += ", "
|
||||||
|
}
|
||||||
|
|
||||||
|
if months == 1 {
|
||||||
|
label += "1 month"
|
||||||
|
}
|
||||||
|
|
||||||
|
if months > 1 {
|
||||||
|
label += fmt.Sprintf("%d months", months)
|
||||||
|
}
|
||||||
|
|
||||||
|
return label
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e Experience) GetStartDate() string {
|
||||||
|
return e.StartDate.Format("Jan 2006")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e Experience) GetEndDate() string {
|
||||||
|
if e.EndDate.IsZero() {
|
||||||
|
return "Present"
|
||||||
|
}
|
||||||
|
|
||||||
|
return e.EndDate.Format("Jan 2006")
|
||||||
|
}
|
||||||
14
domain/project.go
Normal file
14
domain/project.go
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
package domain
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
type Project struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Start time.Time `json:"start"`
|
||||||
|
End time.Time `json:"end"`
|
||||||
|
Stack []string `json:"stack"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
Thumbnail string `json:"thumbnail"`
|
||||||
|
GithubLink string `json:"github_link"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
}
|
||||||
5
go.mod
Normal file
5
go.mod
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
module github.com/moh682/portfolio
|
||||||
|
|
||||||
|
go 1.24.0
|
||||||
|
|
||||||
|
require github.com/a-h/templ v0.3.833
|
||||||
4
go.sum
Normal file
4
go.sum
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
github.com/a-h/templ v0.3.833 h1:L/KOk/0VvVTBegtE0fp2RJQiBm7/52Zxv5fqlEHiQUU=
|
||||||
|
github.com/a-h/templ v0.3.833/go.mod h1:cAu4AiZhtJfBjMY0HASlyzvkrtjnHWPeEsyGK2YYmfk=
|
||||||
|
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
114
main.go
Normal file
114
main.go
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "embed"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/a-h/templ"
|
||||||
|
"github.com/moh682/portfolio/components"
|
||||||
|
"github.com/moh682/portfolio/domain"
|
||||||
|
"github.com/moh682/portfolio/middlewares"
|
||||||
|
)
|
||||||
|
|
||||||
|
func templHandlerWithOptions(component templ.Component) http.Handler {
|
||||||
|
t := templ.Handler(component, templ.WithStatus(200), templ.WithContentType("text/html"))
|
||||||
|
|
||||||
|
log.Println("templHandlerWithOptions: ", t.Status)
|
||||||
|
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
func rootHandler(handler http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
handler.ServeHTTP(w, r)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// SafeFileServer wraps http.FileServer to only serve specific file types from specific directories
|
||||||
|
func SafeFileServer(root http.FileSystem) http.Handler {
|
||||||
|
fileServer := http.FileServer(root)
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// Set security headers
|
||||||
|
w.Header().Set("X-Content-Type-Options", "nosniff")
|
||||||
|
w.Header().Set("X-Frame-Options", "DENY")
|
||||||
|
w.Header().Set("X-XSS-Protection", "1; mode=block")
|
||||||
|
|
||||||
|
log.Println("path", r.URL.Path)
|
||||||
|
|
||||||
|
// Only allow specific file extensions
|
||||||
|
if allowed := func(path string) bool {
|
||||||
|
allowedExts := map[string]bool{
|
||||||
|
".js": true,
|
||||||
|
".css": true,
|
||||||
|
".png": true,
|
||||||
|
".jpg": true,
|
||||||
|
".jpeg": true,
|
||||||
|
".svg": true,
|
||||||
|
".ico": true,
|
||||||
|
}
|
||||||
|
for ext := range allowedExts {
|
||||||
|
if strings.HasSuffix(path, ext) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}(r.URL.Path); !allowed {
|
||||||
|
http.Error(w, "Forbidden", http.StatusForbidden)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fileServer.ServeHTTP(w, r)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
//go:embed assets/json/experiences.json
|
||||||
|
var embeddedExperiences []byte
|
||||||
|
|
||||||
|
//go:embed assets/json/projects.json
|
||||||
|
var embeddedProjects []byte
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
server := http.NewServeMux()
|
||||||
|
|
||||||
|
// Serve static files with restrictions
|
||||||
|
staticFS := http.Dir("static")
|
||||||
|
server.Handle("GET /assets/", http.StripPrefix("/assets/", SafeFileServer(staticFS)))
|
||||||
|
|
||||||
|
server.Handle("GET /about", templHandlerWithOptions(components.AboutPage()))
|
||||||
|
|
||||||
|
var exps []domain.Experience
|
||||||
|
err := json.Unmarshal(embeddedExperiences, &exps)
|
||||||
|
if err != nil {
|
||||||
|
log.Panic(err)
|
||||||
|
}
|
||||||
|
server.Handle("GET /experiences", templHandlerWithOptions(components.ExperiencesPage(exps)))
|
||||||
|
|
||||||
|
var projects []domain.Project
|
||||||
|
err = json.Unmarshal(embeddedProjects, &projects)
|
||||||
|
if err != nil {
|
||||||
|
log.Panic(err)
|
||||||
|
}
|
||||||
|
server.Handle("GET /projects", templHandlerWithOptions(components.ProjectsPage(projects)))
|
||||||
|
|
||||||
|
server.Handle("GET /contact", templHandlerWithOptions(components.ContactPage()))
|
||||||
|
server.Handle("GET /{$}", templHandlerWithOptions(components.LandingPage()))
|
||||||
|
|
||||||
|
// 404 fallback page
|
||||||
|
server.Handle("GET /", templHandlerWithOptions(components.NotFound()))
|
||||||
|
|
||||||
|
// TODO: add Blogs to my portfolio website
|
||||||
|
// server.Handle("GET /blogs", templHandlerWithOptions(components.AboutPage()))
|
||||||
|
|
||||||
|
fmt.Println("Listening on :8080")
|
||||||
|
log.Panic(
|
||||||
|
http.ListenAndServe(":8080",
|
||||||
|
middlewares.Combine(
|
||||||
|
middlewares.Logger(server),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
11
middlewares/combine.go
Normal file
11
middlewares/combine.go
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
package middlewares
|
||||||
|
|
||||||
|
import "net/http"
|
||||||
|
|
||||||
|
func Combine(nexts ...http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
for _, next := range nexts {
|
||||||
|
next.ServeHTTP(w, r)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
33
middlewares/logger.go
Normal file
33
middlewares/logger.go
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
package middlewares
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type statusResponseWriter struct {
|
||||||
|
http.ResponseWriter
|
||||||
|
status int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *statusResponseWriter) WriteHeader(code int) {
|
||||||
|
w.status = code
|
||||||
|
if w.status == 0 {
|
||||||
|
w.ResponseWriter.WriteHeader(code)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Logger(next http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
rw := newStatusResponseWriter(w)
|
||||||
|
now := time.Now()
|
||||||
|
next.ServeHTTP(rw, r)
|
||||||
|
since := time.Since(now)
|
||||||
|
log.Println(r.Method, r.URL.Path, r.RemoteAddr, rw.status, since)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func newStatusResponseWriter(w http.ResponseWriter) *statusResponseWriter {
|
||||||
|
return &statusResponseWriter{ResponseWriter: w}
|
||||||
|
}
|
||||||
20
middlewares/notfound.go
Normal file
20
middlewares/notfound.go
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
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 {
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
22
tailwind.config.ts
Normal file
22
tailwind.config.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
export default {
|
||||||
|
content: ["./components/**/*.{js,ts,jsx,tsx,templ}"],
|
||||||
|
theme: {
|
||||||
|
extend: {
|
||||||
|
animation: {
|
||||||
|
fadeIn: "fadeIn 1s ease-in forwards",
|
||||||
|
slideIn: "slideIn 1s ease-out forwards",
|
||||||
|
},
|
||||||
|
keyframes: {
|
||||||
|
fadeIn: {
|
||||||
|
"0%": { opacity: "0" },
|
||||||
|
"100%": { opacity: "1" },
|
||||||
|
},
|
||||||
|
slideIn: {
|
||||||
|
"0%": { opacity: "0", transform: "translateY(20px)" },
|
||||||
|
"100%": { opacity: "1", transform: "translateY(0)" },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
plugins: [],
|
||||||
|
};
|
||||||
1
tmp/build-errors.log
Normal file
1
tmp/build-errors.log
Normal file
@@ -0,0 +1 @@
|
|||||||
|
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
|
||||||
Reference in New Issue
Block a user