Files
porto/app/routes/home.tsx
T
bagas 9dd425b57f
Docker Build and Push / build-and-push (push) Successful in 5m38s
Mas rusdi pengen terkenal
2026-08-10 23:37:28 +07:00

713 lines
28 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useEffect, useState } from "react"
import { ArrowDown } from "lucide-react"
import type { Route } from "./+types/home"
import { homepageJsonLd, site } from "../seo"
export function meta({}: Route.MetaArgs) {
return [
{ title: site.title },
{
name: "description",
content: site.description,
},
{ tagName: "link", rel: "canonical", href: site.url },
{ name: "robots", content: "index, follow" },
{ property: "og:type", content: "website" },
{ property: "og:url", content: site.url },
{ property: "og:title", content: site.title },
{ property: "og:description", content: site.description },
{ property: "og:site_name", content: site.name },
{ name: "twitter:card", content: "summary_large_image" },
{ name: "twitter:title", content: site.title },
{ name: "twitter:description", content: site.description },
{ "script:ld+json": homepageJsonLd },
]
}
const navItems = [
{ label: "skills", id: "skills" },
{ label: "experience", id: "experience" },
{ label: "projects", id: "projects" },
{ label: "campus", id: "campus-experience" },
{ label: "education", id: "education" },
{ label: "contact", id: "contact" },
]
const skills = [
{
label: "Language",
items: ["Go", "TypeScript", "JavaScript", "Java", "Python"],
},
{
label: "Framework",
items: [
"Next.js",
"React",
"React Router",
"Spring Boot",
"Tailwind CSS",
"Quart",
"Scrapy",
],
},
{
label: "Database",
items: ["PostgreSQL", "MySQL", "Redis", "SQLite"],
},
{
label: "Tools",
items: ["Docker", "Git", "Gitea", "gRPC", "GORM", "Drizzle ORM"],
},
];
const projects = [
{
code: "PRJ-01",
name: "Tunnl Please",
description: "SSH tunneling platform.",
source: "https://git.fossy.my.id/bagas/tunnl_please",
live: "https://tunel.live/",
},
{
code: "PRJ-02",
name: "Filekeeper",
description: "Secure file-hosting platform.",
source: "https://git.fossy.my.id/bagas/filekeeper",
},
{
code: "PRJ-03",
name: "Cutit",
description: "URL shortener.",
source: "https://github.com/fossyy/cutit",
},
]
const education = [
{
dates: "2025 — Present",
title: "Bachelor of Information Systems",
school: "University of Indonesia",
},
{
dates: "2021 — 2024",
title: "Pengembangan Perangkat Lunak dan Gim — PPLG",
school: "SMKN 2 GUGUAK",
},
]
const campusExperience = [
{
number: "05.1",
dates: "March 2026 — Present",
type: "CAMPUS ORGANIZATION",
role: "Member of Web Development SIG",
organization: "RISTEK Fakultas Ilmu Komputer Universitas Indonesia",
description:
"Member of the Web Development Special Interest Group at RISTEK Fakultas Ilmu Komputer Universitas Indonesia.",
},
{
number: "05.2",
dates: "March 2026 — Present",
type: "EVENT COMMITTEE",
role: "DevOps Specialist",
organization: "Open House Fasilkom UI",
description:
"Responsible for the DevOps function within the Open House Fasilkom UI event committee.",
},
{
number: "05.3",
dates: "March 2026 — Present",
type: "EVENT COMMITTEE",
role: "Web Developer",
organization: "COMPFEST",
description:
"Contributes as a Web Developer within the COMPFEST event committee.",
},
]
const externalLinkProps = {
target: "_blank",
rel: "noopener noreferrer",
} as const
const terminalCommand = "whoami"
const fallbackIpAddress = "67.67.67.67"
const terminalOutput = [
"fullstack developer",
"information systems student",
"building efficient systems",
]
function Cursor({ color = "orange" }: { color?: "orange" | "cyan" }) {
return (
<span
aria-hidden="true"
className={`terminal-cursor ml-1 inline-block h-4 w-2 align-middle ${
color === "cyan" ? "bg-cyan-dark" : "bg-orange"
}`}
/>
)
}
function SectionHeading({ number, children }: { number: string; children: string }) {
return (
<div className="mb-12 flex items-baseline gap-4">
<span className="font-terminal text-xs text-orange-dark">{number}</span>
<h2 className="font-display text-4xl tracking-wide text-ink sm:text-5xl">
{children}
</h2>
</div>
)
}
function Tag({ children }: { children: string }) {
return (
<span className="border border-outline bg-panel px-2 py-1 font-terminal text-xs text-ink">
{children}
</span>
)
}
function HeroTerminal() {
const [ipAddress, setIpAddress] = useState(fallbackIpAddress)
const [typedSshCommand, setTypedSshCommand] = useState("")
const [isConnected, setIsConnected] = useState(false)
const [typedCommand, setTypedCommand] = useState("")
const [visibleOutput, setVisibleOutput] = useState(0)
useEffect(() => {
const controller = new AbortController()
let cancelled = false
const timers: Array<ReturnType<typeof setTimeout>> = []
const schedule = (callback: () => void, delay: number) => {
timers.push(setTimeout(callback, delay))
}
const requestTimeout = setTimeout(() => controller.abort(), 3000)
const runSequence = (visitorIp: string) => {
const sshCommand = `ssh portofolio@${visitorIp}`
if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
setTypedSshCommand(sshCommand)
setIsConnected(true)
setTypedCommand(terminalCommand)
setVisibleOutput(terminalOutput.length)
return
}
const initialDelay = 400
const sshTypingSpeed = 55
for (let index = 1; index <= sshCommand.length; index += 1) {
schedule(
() => setTypedSshCommand(sshCommand.slice(0, index)),
initialDelay + index * sshTypingSpeed,
)
}
const connectedAt = initialDelay + sshCommand.length * sshTypingSpeed + 450
schedule(() => setIsConnected(true), connectedAt)
const whoamiStart = connectedAt + 550
for (let index = 1; index <= terminalCommand.length; index += 1) {
schedule(
() => setTypedCommand(terminalCommand.slice(0, index)),
whoamiStart + index * 90,
)
}
const outputStart = whoamiStart + terminalCommand.length * 90 + 450
terminalOutput.forEach((_, index) => {
schedule(() => setVisibleOutput(index + 1), outputStart + index * 260)
})
}
const loadIpAndRun = async () => {
let visitorIp = fallbackIpAddress
try {
const response = await fetch("https://ipinfo.io/json", {
signal: controller.signal,
})
if (!response.ok) throw new Error("Unable to resolve visitor IP")
const result: unknown = await response.json()
if (
typeof result === "object" &&
result !== null &&
"ip" in result &&
typeof result.ip === "string" &&
result.ip.trim()
) {
visitorIp = result.ip.trim()
}
} catch {
visitorIp = fallbackIpAddress
} finally {
clearTimeout(requestTimeout)
}
if (!cancelled) {
setIpAddress(visitorIp)
runSequence(visitorIp)
}
}
void loadIpAndRun()
return () => {
cancelled = true
controller.abort()
clearTimeout(requestTimeout)
timers.forEach(clearTimeout)
}
}, [])
const sshCommand = `ssh portofolio@${ipAddress}`
const isTypingSsh = typedSshCommand.length < sshCommand.length
const isTyping = typedCommand.length < terminalCommand.length
const isComplete = visibleOutput === terminalOutput.length
return (
<div
aria-label={`Terminal commands: ssh portofolio at ${ipAddress}, then whoami. Output: ${terminalOutput.join(", ")}.`}
className="flex min-h-48 flex-col items-start justify-center bg-paper p-6 font-terminal text-xs text-cyan-dark"
>
<div aria-hidden="true">
<span className="text-muted">browser:~$</span>{" "}
{typedSshCommand}
{isTypingSsh && <Cursor color="cyan" />}
</div>
<div aria-hidden="true" className="min-h-[4.5rem]">
{isConnected && (
<>
<div>connected to portofolio@{ipAddress}</div>
<div>
<span className="text-muted">portofolio@portfolio:~$</span>{" "}
{typedCommand}
{isTyping && <Cursor color="cyan" />}
</div>
{terminalOutput.slice(0, visibleOutput).map((line) => (
<div key={line}>{line}</div>
))}
</>
)}
{isConnected && !isTyping && !isComplete && <Cursor color="cyan" />}
{isConnected && isComplete && (
<div>
<Cursor color="cyan" />
</div>
)}
</div>
</div>
)
}
export default function Home() {
return (
<div className="min-h-screen overflow-x-hidden bg-paper text-ink antialiased">
<div
aria-hidden="true"
className="scanlines pointer-events-none fixed inset-0 z-[60]"
/>
<header className="fixed inset-x-0 top-0 z-50 border-b border-outline bg-paper">
<div className="flex h-12 items-stretch">
<a
href="#home"
className="flex items-center border-r border-outline bg-panel px-4 font-terminal text-sm font-bold tracking-wider text-orange-dark focus-visible:outline-2 focus-visible:outline-offset-[-2px] focus-visible:outline-orange-dark"
>
BAGAS
</a>
<nav aria-label="Primary" className="hidden flex-1 items-stretch md:flex">
{navItems.map((item) => (
<a
key={item.id}
href={`#${item.id}`}
className="flex items-center border-r border-outline px-4 font-terminal text-sm font-bold tracking-wide text-muted transition-colors hover:bg-panel-high hover:text-ink focus-visible:outline-2 focus-visible:outline-offset-[-2px] focus-visible:outline-orange-dark"
>
{item.label}
</a>
))}
</nav>
<div className="ml-auto flex shrink-0 items-center px-4 font-terminal text-xs text-muted">
<span
aria-hidden="true"
className="mr-2 block h-2 w-2 border border-cyan-dark bg-cyan"
/>
available for collaboration
</div>
</div>
<nav
aria-label="Mobile primary"
className="flex h-10 items-stretch overflow-x-auto border-t border-outline md:hidden"
>
{navItems.map((item) => (
<a
key={item.id}
href={`#${item.id}`}
className="flex shrink-0 items-center border-r border-outline px-4 font-terminal text-xs font-bold tracking-wide text-muted transition-colors hover:bg-panel-high hover:text-ink focus-visible:outline-2 focus-visible:outline-offset-[-2px] focus-visible:outline-orange-dark"
>
{item.label}
</a>
))}
</nav>
</header>
<main
id="home"
className="relative z-20 mx-auto w-full max-w-[720px] px-5 pt-[88px] md:px-0 md:pt-24"
>
<section className="relative flex min-h-[870px] flex-col justify-center border-x border-outline">
<div
aria-hidden="true"
className="pointer-events-none absolute inset-0 -z-10 flex select-none items-center justify-center overflow-hidden opacity-30"
>
<span className="-ml-20 whitespace-nowrap font-display text-[120px] leading-[0.92] tracking-[-0.02em] text-panel-high md:text-[300px] md:tracking-[-0.04em]">
bagas
</span>
</div>
<h1 className="pt-12 text-center font-terminal text-xs text-muted">
developer / student / self-hoster
</h1>
<div className="flex justify-center border-b border-outline p-8 sm:p-12">
<div className="w-full max-w-lg border border-outline bg-panel">
<div className="flex items-center border-b border-outline bg-panel-high p-2">
<div aria-hidden="true" className="flex gap-1">
<span className="h-3 w-3 border border-outline" />
<span className="h-3 w-3 border border-outline" />
<span className="h-3 w-3 border border-outline" />
</div>
<span className="ml-2 font-terminal text-xs text-muted">terminal</span>
</div>
<HeroTerminal />
</div>
</div>
<div className="border-b border-outline p-6">
<div className="mx-auto max-w-2xl space-y-4 font-body text-lg leading-7">
<p>
Im a Fullstack Developer and Information Systems student at the University of Indonesia with experience in backend development, infrastructure, automation, and self-hosted systems. I enjoy building efficient, secure, and reliable solutions while continuously learning new technologies.
</p>
</div>
</div>
<div className="flex flex-col items-center justify-center gap-4 p-12 sm:flex-row">
<a
href="#projects"
className="border border-outline bg-panel p-2 font-terminal text-xs transition-colors hover:bg-panel-high focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-orange-dark"
>
<span className="mr-1 text-muted">$</span>view projects
</a>
<a
href="#contact"
className="border border-outline bg-panel p-2 font-terminal text-xs transition-colors hover:bg-panel-high focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-orange-dark"
>
<span className="mr-1 text-muted">$</span>get in touch
<Cursor />
</a>
</div>
<div className="flex items-center justify-center gap-2 border-t border-outline p-12 font-terminal text-xs text-muted">
<span>scroll to inspect</span>
<ArrowDown aria-hidden="true" size={16} strokeWidth={1.5} />
</div>
</section>
<section
id="skills"
className="relative scroll-mt-24 overflow-hidden border-x border-b border-outline px-5 py-24 md:scroll-mt-12 md:px-12"
>
<div
aria-hidden="true"
className="pointer-events-none absolute inset-0 -z-10 flex select-none items-center justify-center opacity-10"
>
<span className="whitespace-nowrap font-display text-[120px] leading-none text-ink md:text-[300px]">
stack
</span>
</div>
<SectionHeading number="01">technical skills</SectionHeading>
<div className="space-y-6 border-t border-outline pt-6">
{skills.map((group) => (
<div key={group.label}>
<h3 className="mb-2 font-terminal text-sm font-bold tracking-wide">
{group.label}
</h3>
<div className="flex flex-wrap gap-2">
{group.items.map((item) => (
<Tag key={item}>{item}</Tag>
))}
</div>
</div>
))}
</div>
</section>
<section
id="experience"
className="scroll-mt-24 border-x border-b border-outline px-5 py-24 md:scroll-mt-12 md:px-12"
>
<SectionHeading number="02">experience</SectionHeading>
<div className="border-t border-outline pt-6">
<article className="flex flex-col gap-4 md:flex-row">
<p className="w-48 shrink-0 font-terminal text-xs text-muted">
Oct 2024 Apr 2025
</p>
<div className="flex-grow">
<h3 className="font-body text-lg font-bold">Back-end Developer Intern</h3>
<p className="mb-2 font-terminal text-xs text-cyan-dark">
PT eBdesk Teknologi
</p>
<p className="mb-4 max-w-lg font-body text-[15px] leading-6 text-muted">
Developed a flexible web-crawling system capable of extracting data
from different website structures without requiring a completely new
crawler for every website.
</p>
<p className="mb-2 font-terminal text-xs font-bold">
Dynamic Web Crawling System
</p>
<div className="flex flex-wrap gap-2">
{[
"Go",
"Tailwind CSS",
"Templ",
"HTMX",
].map((item) => (
<Tag key={item}>{item}</Tag>
))}
</div>
</div>
</article>
</div>
</section>
<section
id="projects"
className="scroll-mt-24 border-x border-b border-outline px-5 py-24 md:scroll-mt-12 md:px-12"
>
<SectionHeading number="03">selected projects</SectionHeading>
<div className="divide-y divide-outline border-t border-outline pt-6">
{projects.map((project) => (
<article
key={project.code}
className="flex flex-col items-start justify-between gap-4 py-6 first:pt-0 md:flex-row"
>
<div>
<p className="mb-1 font-terminal text-xs text-muted">{project.code}</p>
<h3 className="font-body text-lg font-bold">{project.name}</h3>
<p className="mt-2 font-body text-[15px] leading-6 text-muted">
{project.description}
</p>
</div>
<div className="flex shrink-0 flex-col items-start gap-2 md:items-end">
<a
{...externalLinkProps}
href={project.source}
aria-label={`Inspect source for ${project.name}`}
className="font-terminal text-xs text-orange-dark hover:underline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-orange-dark"
>
[ inspect_source ]
</a>
{project.live && (
<a
{...externalLinkProps}
href={project.live}
aria-label={`View ${project.name} live`}
className="font-terminal text-xs text-cyan-dark hover:underline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-orange-dark"
>
[ view_live ]
</a>
)}
</div>
</article>
))}
</div>
</section>
<section
id="campus-experience"
className="relative scroll-mt-24 overflow-hidden border-x border-b border-outline px-5 py-24 md:scroll-mt-12 md:px-12"
>
<SectionHeading number="05">campus technical experience</SectionHeading>
<div className="border-t border-outline pt-6">
<p className="mb-12 max-w-2xl font-body text-[15px] leading-6">
Technical involvement in campus organizations and event committees,
focusing on web development and infrastructure.
</p>
<div className="divide-y divide-outline">
{campusExperience.map((item) => (
<article
key={item.number}
className="flex flex-col gap-4 py-6 first:pt-0 md:flex-row"
>
<div className="flex w-48 shrink-0 flex-col gap-1 font-terminal text-xs">
<span className="text-orange-dark">{item.number}</span>
<span className="text-muted">{item.dates}</span>
<span className="text-muted">{item.type}</span>
</div>
<div className="flex-grow">
<div className="mb-1 flex items-center">
<span aria-hidden="true" className="mr-2 h-2 w-2 bg-orange" />
<h3 className="font-body text-lg font-bold">{item.role}</h3>
</div>
<p className="mb-2 font-terminal text-xs text-cyan-dark">
{item.organization}
</p>
<p className="max-w-lg font-body text-[15px] leading-6 text-muted">
{item.description}
</p>
<p className="mt-2 font-terminal text-xs text-muted">
Depok, West Java, Indonesia
</p>
</div>
</article>
))}
</div>
</div>
</section>
<section
id="education"
className="scroll-mt-24 border-x border-b border-outline px-5 py-24 md:scroll-mt-12 md:px-12"
>
<SectionHeading number="06">education</SectionHeading>
<div className="space-y-12 border-t border-outline pt-6">
{education.map((item) => (
<article key={item.title} className="flex flex-col gap-4 md:flex-row">
<p className="w-48 shrink-0 font-terminal text-xs text-muted">{item.dates}</p>
<div>
<h3 className="font-body text-lg font-bold">{item.title}</h3>
<p className="font-terminal text-xs text-cyan-dark">{item.school}</p>
</div>
</article>
))}
</div>
</section>
<section
id="contact"
className="relative scroll-mt-24 overflow-hidden border-x border-outline px-5 py-24 md:scroll-mt-12 md:px-12"
>
<div
aria-hidden="true"
className="pointer-events-none absolute inset-0 -z-10 flex select-none items-center justify-center opacity-10"
>
<span className="whitespace-nowrap font-display text-[120px] leading-none text-ink md:text-[300px]">
contact
</span>
</div>
<SectionHeading number="07">contact</SectionHeading>
<div className="border-t border-outline pt-6">
<p className="mb-12 max-w-2xl font-body text-[15px] leading-6">
Im always interested in new opportunities, technical discussions, and
collaborations. Feel free to reach out.
</p>
<div className="grid grid-cols-1 gap-12 md:grid-cols-2">
<dl className="space-y-2 font-terminal text-xs">
<div className="flex items-start">
<dt className="mr-4 w-20 shrink-0 text-muted">email:</dt>
<dd>
<a
href="mailto:bagas@fossy.my.id"
className="text-orange-dark hover:underline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-orange-dark"
>
bagas@fossy.my.id
</a>
</dd>
</div>
<div className="flex items-start">
<dt className="mr-4 w-20 shrink-0 text-muted">phone:</dt>
<dd>
<a
href="tel:+6285355003282"
className="hover:text-orange-dark hover:underline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-orange-dark"
>
+62 853-5500-3282
</a>
</dd>
</div>
<div className="flex items-start">
<dt className="mr-4 w-20 shrink-0 text-muted">website:</dt>
<dd>
<a
{...externalLinkProps}
href="https://fossy.my.id/"
className="text-orange-dark hover:underline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-orange-dark"
>
fossy.my.id
</a>
</dd>
</div>
<div className="flex items-start">
<dt className="mr-4 w-20 shrink-0 text-muted">github:</dt>
<dd>
<a
{...externalLinkProps}
href="https://github.com/fossyy"
className="text-orange-dark hover:underline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-orange-dark"
>
github.com/fossyy
</a>
</dd>
</div>
<div className="flex items-start">
<dt className="mr-4 w-20 shrink-0 text-muted">linkedin:</dt>
<dd>
<a
{...externalLinkProps}
href="https://www.linkedin.com/in/bagas-aulia-rezki/"
className="text-orange-dark hover:underline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-orange-dark"
>
in/bagas-aulia-rezki
</a>
<Cursor />
</dd>
</div>
</dl>
<dl className="border border-outline bg-panel p-6 font-terminal text-xs text-muted">
<div className="mb-2">
<dt className="inline text-muted">status:</dt>{" "}
<dd className="inline">open to collaboration</dd>
</div>
<div className="mb-2">
<dt className="inline text-muted">location:</dt>{" "}
<dd className="inline">depok, indonesia</dd>
</div>
<div className="mb-2">
<dt className="inline text-muted">timezone:</dt>{" "}
<dd className="inline">utc+7</dd>
</div>
<div>
<dt className="inline text-muted">response:</dt>{" "}
<dd className="inline">email preferred</dd>
</div>
</dl>
</div>
</div>
</section>
</main>
<footer className="relative z-20 mx-auto flex w-full max-w-[720px] flex-col items-center justify-between gap-4 border-t border-outline bg-paper px-5 py-12 text-center md:flex-row md:px-6 md:text-left">
<div className="flex flex-col items-center md:items-start">
<span className="font-terminal text-sm font-bold tracking-wide text-orange-dark">
bagas<span aria-hidden="true" className="ml-1 inline-block h-4 w-2 bg-orange" />
</span>
<span className="mt-1 font-terminal text-xs text-muted">
Bagas Aulia Rezki | Fullstack Developer &amp; Information Systems Student
</span>
</div>
<nav aria-label="Footer" className="flex flex-wrap justify-center gap-4 font-terminal text-xs text-muted">
<a {...externalLinkProps} href="https://github.com/fossyy" className="hover:text-orange-dark focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-orange-dark">GitHub</a>
<a {...externalLinkProps} href="https://www.linkedin.com/in/bagas-aulia-rezki/" className="hover:text-orange-dark focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-orange-dark">LinkedIn</a>
<a {...externalLinkProps} href="https://fossy.my.id/" className="hover:text-orange-dark focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-orange-dark">Website</a>
<a href="#home" className="hover:text-orange-dark focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-orange-dark">Back to top</a>
</nav>
<span className="font-terminal text-xs text-muted">© Bagas Aulia Rezki</span>
</footer>
</div>
)
}