mirror of
https://github.com/liyunze-coding/RythonDev.git
synced 2026-09-22 07:44:26 +00:00
updated experience component
This commit is contained in:
parent
389b1388be
commit
1a4213879b
19 changed files with 545 additions and 376 deletions
31
bun.lock
31
bun.lock
|
|
@ -7,6 +7,7 @@
|
||||||
"@astrojs/mdx": "^4.3.0",
|
"@astrojs/mdx": "^4.3.0",
|
||||||
"@astrojs/react": "^4.3.0",
|
"@astrojs/react": "^4.3.0",
|
||||||
"@astrojs/svelte": "^7.1.0",
|
"@astrojs/svelte": "^7.1.0",
|
||||||
|
"@radix-ui/react-tabs": "^1.1.13",
|
||||||
"@tailwindcss/vite": "^4.1.8",
|
"@tailwindcss/vite": "^4.1.8",
|
||||||
"@types/react": "^19.1.9",
|
"@types/react": "^19.1.9",
|
||||||
"@types/react-dom": "^19.1.7",
|
"@types/react-dom": "^19.1.7",
|
||||||
|
|
@ -197,6 +198,36 @@
|
||||||
|
|
||||||
"@oslojs/encoding": ["@oslojs/encoding@1.1.0", "", {}, "sha512-70wQhgYmndg4GCPxPPxPGevRKqTIJ2Nh4OkiMWmDAVYsTQ+Ta7Sq+rPevXyXGdzr30/qZBnyOalCszoMxlyldQ=="],
|
"@oslojs/encoding": ["@oslojs/encoding@1.1.0", "", {}, "sha512-70wQhgYmndg4GCPxPPxPGevRKqTIJ2Nh4OkiMWmDAVYsTQ+Ta7Sq+rPevXyXGdzr30/qZBnyOalCszoMxlyldQ=="],
|
||||||
|
|
||||||
|
"@radix-ui/primitive": ["@radix-ui/primitive@1.1.3", "", {}, "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg=="],
|
||||||
|
|
||||||
|
"@radix-ui/react-collection": ["@radix-ui/react-collection@1.1.7", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw=="],
|
||||||
|
|
||||||
|
"@radix-ui/react-compose-refs": ["@radix-ui/react-compose-refs@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg=="],
|
||||||
|
|
||||||
|
"@radix-ui/react-context": ["@radix-ui/react-context@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA=="],
|
||||||
|
|
||||||
|
"@radix-ui/react-direction": ["@radix-ui/react-direction@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw=="],
|
||||||
|
|
||||||
|
"@radix-ui/react-id": ["@radix-ui/react-id@1.1.1", "", { "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg=="],
|
||||||
|
|
||||||
|
"@radix-ui/react-presence": ["@radix-ui/react-presence@1.1.5", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ=="],
|
||||||
|
|
||||||
|
"@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="],
|
||||||
|
|
||||||
|
"@radix-ui/react-roving-focus": ["@radix-ui/react-roving-focus@1.1.11", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA=="],
|
||||||
|
|
||||||
|
"@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="],
|
||||||
|
|
||||||
|
"@radix-ui/react-tabs": ["@radix-ui/react-tabs@1.1.13", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-roving-focus": "1.1.11", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-7xdcatg7/U+7+Udyoj2zodtI9H/IIopqo+YOIcZOq1nJwXWBZ9p8xiu5llXlekDbZkca79a/fozEYQXIA4sW6A=="],
|
||||||
|
|
||||||
|
"@radix-ui/react-use-callback-ref": ["@radix-ui/react-use-callback-ref@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg=="],
|
||||||
|
|
||||||
|
"@radix-ui/react-use-controllable-state": ["@radix-ui/react-use-controllable-state@1.2.2", "", { "dependencies": { "@radix-ui/react-use-effect-event": "0.0.2", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg=="],
|
||||||
|
|
||||||
|
"@radix-ui/react-use-effect-event": ["@radix-ui/react-use-effect-event@0.0.2", "", { "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA=="],
|
||||||
|
|
||||||
|
"@radix-ui/react-use-layout-effect": ["@radix-ui/react-use-layout-effect@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ=="],
|
||||||
|
|
||||||
"@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.27", "", {}, "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA=="],
|
"@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.27", "", {}, "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA=="],
|
||||||
|
|
||||||
"@rollup/pluginutils": ["@rollup/pluginutils@5.2.0", "", { "dependencies": { "@types/estree": "^1.0.0", "estree-walker": "^2.0.2", "picomatch": "^4.0.2" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-qWJ2ZTbmumwiLFomfzTyt5Kng4hwPi9rwCYN4SHb6eaRU1KNO4ccxINHr/VhH4GgPlt1XfSTLX2LBTme8ne4Zw=="],
|
"@rollup/pluginutils": ["@rollup/pluginutils@5.2.0", "", { "dependencies": { "@types/estree": "^1.0.0", "estree-walker": "^2.0.2", "picomatch": "^4.0.2" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-qWJ2ZTbmumwiLFomfzTyt5Kng4hwPi9rwCYN4SHb6eaRU1KNO4ccxINHr/VhH4GgPlt1XfSTLX2LBTme8ne4Zw=="],
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@
|
||||||
"@astrojs/mdx": "^4.3.0",
|
"@astrojs/mdx": "^4.3.0",
|
||||||
"@astrojs/react": "^4.3.0",
|
"@astrojs/react": "^4.3.0",
|
||||||
"@astrojs/svelte": "^7.1.0",
|
"@astrojs/svelte": "^7.1.0",
|
||||||
|
"@radix-ui/react-tabs": "^1.1.13",
|
||||||
"@tailwindcss/vite": "^4.1.8",
|
"@tailwindcss/vite": "^4.1.8",
|
||||||
"@types/react": "^19.1.9",
|
"@types/react": "^19.1.9",
|
||||||
"@types/react-dom": "^19.1.7",
|
"@types/react-dom": "^19.1.7",
|
||||||
|
|
|
||||||
|
|
@ -16,10 +16,9 @@ import Line from "../global/NetworkLine.astro";
|
||||||
</div>
|
</div>
|
||||||
<hr class="mb-3 w-full" />
|
<hr class="mb-3 w-full" />
|
||||||
<p class="mb-3">
|
<p class="mb-3">
|
||||||
Led implementation of frontend development, while also
|
Led development of Astro websites with Payload CMS
|
||||||
developing and intergating a CMS on the backend for
|
backend implementation, manage Github Actions CI/CD and
|
||||||
website development. Actively contributing to LLM
|
deployments.
|
||||||
product research and development.
|
|
||||||
</p>
|
</p>
|
||||||
<h3 class="mb-1 text-lg font-bold">Relevant skills:</h3>
|
<h3 class="mb-1 text-lg font-bold">Relevant skills:</h3>
|
||||||
<div
|
<div
|
||||||
|
|
@ -49,8 +48,10 @@ import Line from "../global/NetworkLine.astro";
|
||||||
</div>
|
</div>
|
||||||
<hr class="mb-3 w-full" />
|
<hr class="mb-3 w-full" />
|
||||||
<p class="mb-3">
|
<p class="mb-3">
|
||||||
Awarded a 50% Vice-Chancellor Scholarship to study
|
Awarded 50% Vice-Chancellor Scholarship to study
|
||||||
Bachelor's in Computer Science.
|
Bachelor's in Computer Science. Active committee member
|
||||||
|
in cybersecurity association and software engineering
|
||||||
|
club.
|
||||||
</p>
|
</p>
|
||||||
<h3 class="mb-1 text-lg font-bold">Relevant skills:</h3>
|
<h3 class="mb-1 text-lg font-bold">Relevant skills:</h3>
|
||||||
<div
|
<div
|
||||||
|
|
|
||||||
228
src/components/experience/ExperienceComponent.tsx
Normal file
228
src/components/experience/ExperienceComponent.tsx
Normal file
|
|
@ -0,0 +1,228 @@
|
||||||
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||||
|
|
||||||
|
export default function Experience() {
|
||||||
|
return (
|
||||||
|
<Tabs
|
||||||
|
defaultValue="experience"
|
||||||
|
className="w-[90%] md:w-[600px] lg:w-1/2"
|
||||||
|
>
|
||||||
|
<TabsList className="w-full">
|
||||||
|
<TabsTrigger value="experience">Experience</TabsTrigger>
|
||||||
|
<TabsTrigger value="education">Education</TabsTrigger>
|
||||||
|
<TabsTrigger value="extracurricular">
|
||||||
|
Extracurricular
|
||||||
|
</TabsTrigger>
|
||||||
|
</TabsList>
|
||||||
|
<TabsContent
|
||||||
|
value="experience"
|
||||||
|
className="bg-secondary rounded-md px-5 py-5"
|
||||||
|
>
|
||||||
|
{ExperienceTabContent()}
|
||||||
|
</TabsContent>
|
||||||
|
<TabsContent
|
||||||
|
value="education"
|
||||||
|
className="bg-secondary rounded-md px-5 py-5"
|
||||||
|
>
|
||||||
|
{EducationTabContent()}
|
||||||
|
</TabsContent>
|
||||||
|
<TabsContent
|
||||||
|
value="extracurricular"
|
||||||
|
className="bg-secondary rounded-md px-5 py-5"
|
||||||
|
>
|
||||||
|
{ExtracurricularTabContent()}
|
||||||
|
</TabsContent>
|
||||||
|
</Tabs>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ExperienceTabContent() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{Position({
|
||||||
|
title: "Lead Full Stack Developer",
|
||||||
|
institution: "Hyson Horizon",
|
||||||
|
dateRange: "Dec 2024 - Now",
|
||||||
|
country: "Malaysia",
|
||||||
|
summary:
|
||||||
|
"Led development of clients' Astro websites with Payload CMS backend implementation, managed Github Actions CI/CD and deployments.",
|
||||||
|
className: "pb-5",
|
||||||
|
})}
|
||||||
|
{Position({
|
||||||
|
title: "Frontend Web Developer Intern",
|
||||||
|
institution: "Studio20",
|
||||||
|
dateRange: "Jan 2024 - Apr 2024",
|
||||||
|
country: "Malaysia",
|
||||||
|
summary:
|
||||||
|
"Developed WordPress sites and NextJS apps for clients. Used lazy-loading to optimise page load speeds. Ensured CMS allowed easy yet flexible customisations for clients.",
|
||||||
|
})}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function EducationTabContent() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{Position({
|
||||||
|
title: "Bachelors of Computer Science",
|
||||||
|
dateRange: "June 2024 - Now",
|
||||||
|
country: "Australia",
|
||||||
|
summary: `Awarded 50% Vice-Chancellor Scholarship to study
|
||||||
|
Bachelor's in Computer Science, minoring in Full Stack Development. Active committee member in cybersecurity association and software engineering club.`,
|
||||||
|
className: "pb-5",
|
||||||
|
})}
|
||||||
|
{Position({
|
||||||
|
title: "Diploma in Computer Science",
|
||||||
|
dateRange: "Jan 2022 - Apr 2024",
|
||||||
|
country: "Malaysia",
|
||||||
|
summary: `Focused in Data Science, took on role as President of Computer Programming Club and Vice President of Diploma Student Union.`,
|
||||||
|
})}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ExtracurricularTabContent() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{Position({
|
||||||
|
title: "University Cybersecurity Association, Development Committee",
|
||||||
|
dateRange: "Nov 2025 - Now",
|
||||||
|
country: "Australia",
|
||||||
|
summary: `Leading frontend development club's revamped website using Astro framework with Svelte components.`,
|
||||||
|
className: "pb-5",
|
||||||
|
})}
|
||||||
|
{Position({
|
||||||
|
title: "University Software Engineering Club Committee",
|
||||||
|
dateRange: "Sep 2025 - Now",
|
||||||
|
country: "Australia",
|
||||||
|
summary: `Leading and contributing to various club projects:`,
|
||||||
|
className: "pb-5",
|
||||||
|
highlights: [
|
||||||
|
`Rust Discord Bot, includes club membership verification feature using Supabase.`,
|
||||||
|
`Python web scraping AI project, using BeautifulSoup4 and Gemini API.`,
|
||||||
|
],
|
||||||
|
})}
|
||||||
|
{Position({
|
||||||
|
title: "Malaysian Association, Marketing Officer",
|
||||||
|
dateRange: "Mar 2025 - Oct 2025",
|
||||||
|
country: "Australia",
|
||||||
|
summary: `Contributed to marketing strategy through video ideation, execution (including on-screen presence and filming), and video editing.`,
|
||||||
|
})}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ICONS AND SUBCOMPONENTS
|
||||||
|
|
||||||
|
function LocationIcon() {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
strokeWidth={1.5}
|
||||||
|
stroke="currentColor"
|
||||||
|
className="size-4"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
d="M15 10.5a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
d="M19.5 10.5c0 7.142-7.5 11.25-7.5 11.25S4.5 17.642 4.5 10.5a7.5 7.5 0 1 1 15 0Z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function CalendarIcon() {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
strokeWidth={1.5}
|
||||||
|
stroke="currentColor"
|
||||||
|
className="size-4"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
d="M6.75 3v2.25M17.25 3v2.25M3 18.75V7.5a2.25 2.25 0 0 1 2.25-2.25h13.5A2.25 2.25 0 0 1 21 7.5v11.25m-18 0A2.25 2.25 0 0 0 5.25 21h13.5A2.25 2.25 0 0 0 21 18.75m-18 0v-7.5A2.25 2.25 0 0 1 5.25 9h13.5A2.25 2.25 0 0 1 21 11.25v7.5m-9-6h.008v.008H12v-.008ZM12 15h.008v.008H12V15Zm0 2.25h.008v.008H12v-.008ZM9.75 15h.008v.008H9.75V15Zm0 2.25h.008v.008H9.75v-.008ZM7.5 15h.008v.008H7.5V15Zm0 2.25h.008v.008H7.5v-.008Zm6.75-4.5h.008v.008h-.008v-.008Zm0 2.25h.008v.008h-.008V15Zm0 2.25h.008v.008h-.008v-.008Zm2.25-4.5h.008v.008H16.5v-.008Zm0 2.25h.008v.008H16.5V15Z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function BriefCaseIcon() {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
strokeWidth={1.5}
|
||||||
|
stroke="currentColor"
|
||||||
|
className="size-4"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
d="M20.25 14.15v4.25c0 1.094-.787 2.036-1.872 2.18-2.087.277-4.216.42-6.378.42s-4.291-.143-6.378-.42c-1.085-.144-1.872-1.086-1.872-2.18v-4.25m16.5 0a2.18 2.18 0 0 0 .75-1.661V8.706c0-1.081-.768-2.015-1.837-2.175a48.114 48.114 0 0 0-3.413-.387m4.5 8.006c-.194.165-.42.295-.673.38A23.978 23.978 0 0 1 12 15.75c-2.648 0-5.195-.429-7.577-1.22a2.016 2.016 0 0 1-.673-.38m0 0A2.18 2.18 0 0 1 3 12.489V8.706c0-1.081.768-2.015 1.837-2.175a48.111 48.111 0 0 1 3.413-.387m7.5 0V5.25A2.25 2.25 0 0 0 13.5 3h-3a2.25 2.25 0 0 0-2.25 2.25v.894m7.5 0a48.667 48.667 0 0 0-7.5 0M12 12.75h.008v.008H12v-.008Z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function Position({
|
||||||
|
title,
|
||||||
|
institution,
|
||||||
|
dateRange,
|
||||||
|
country,
|
||||||
|
summary,
|
||||||
|
className = "",
|
||||||
|
highlights = [],
|
||||||
|
}: {
|
||||||
|
title: string;
|
||||||
|
institution?: string;
|
||||||
|
dateRange: string;
|
||||||
|
country: string;
|
||||||
|
summary: string;
|
||||||
|
className?: string;
|
||||||
|
highlights?: Array<string>;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<div className="position-el flex flex-row items-start gap-3">
|
||||||
|
<div className="flex flex-col items-center justify-start gap-2 self-stretch pt-2">
|
||||||
|
<div className="bg-accent h-2 w-2 rounded-full"></div>
|
||||||
|
<div className="w-[1px] flex-grow bg-gray-400"></div>
|
||||||
|
</div>
|
||||||
|
<div className={className}>
|
||||||
|
<h2 className="text-lg font-bold">{title}</h2>
|
||||||
|
<div className="mt-1 flex flex-col items-start justify-start gap-1">
|
||||||
|
<div className="text-muted-foreground flex flex-row items-center justify-start gap-1 text-xs">
|
||||||
|
{CalendarIcon()} {dateRange}
|
||||||
|
</div>
|
||||||
|
<div className="text-muted-foreground mb-2 flex w-full flex-row items-center justify-start gap-5 text-xs">
|
||||||
|
{institution && (
|
||||||
|
<div className="flex flex-row items-center justify-start gap-1">
|
||||||
|
{BriefCaseIcon()} {institution}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="flex flex-row items-center justify-start gap-1">
|
||||||
|
{LocationIcon()} {country}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p className="text-base">{summary}</p>
|
||||||
|
<ul className="list-inside list-disc">
|
||||||
|
{highlights.map((highlight) => (
|
||||||
|
<li>{highlight}</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -1,35 +0,0 @@
|
||||||
---
|
|
||||||
import Card from "../global/Card.astro";
|
|
||||||
import Line from "../global/NetworkLine.astro";
|
|
||||||
---
|
|
||||||
|
|
||||||
<div id="Experience" class="h-fit">
|
|
||||||
<div class="flex flex-col items-center">
|
|
||||||
<div class="w-[90%] md:w-[600px] lg:w-1/2">
|
|
||||||
<Line height="50px" />
|
|
||||||
<Card primaryColor="#FFAC21" className="view-fadeUp">
|
|
||||||
<div class="px-4 py-4 md:px-8 md:py-8">
|
|
||||||
<div class="mb-3 flex w-full flex-col justify-start gap-1">
|
|
||||||
<h2 class="text-xl font-bold">
|
|
||||||
Diploma in Computer Science student
|
|
||||||
</h2>
|
|
||||||
<h3 class="font-bold">
|
|
||||||
@ Methodist College Kuala Lumpur
|
|
||||||
</h3>
|
|
||||||
<h3>Jan 2022 - June 2024</h3>
|
|
||||||
</div>
|
|
||||||
<hr class="mb-3 w-full" />
|
|
||||||
<h3 class="mb-1 text-lg font-bold">Achievements</h3>
|
|
||||||
<ul class="list-inside list-disc">
|
|
||||||
<li>
|
|
||||||
4-time Dean's List Award recipient (>3.5 GPA)
|
|
||||||
</li>
|
|
||||||
<li>3.8 CGPA</li>
|
|
||||||
<li>President of Computer Programming Club</li>
|
|
||||||
<li>Vice President of Diploma Union</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</Card>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
@ -11,7 +11,6 @@ type Props = {
|
||||||
|
|
||||||
const {
|
const {
|
||||||
href,
|
href,
|
||||||
primaryColor,
|
|
||||||
shadowColor = "rgb(12,12,12)",
|
shadowColor = "rgb(12,12,12)",
|
||||||
bgColor = "var(--color-secondary)",
|
bgColor = "var(--color-secondary)",
|
||||||
scaleOnHover = false,
|
scaleOnHover = false,
|
||||||
|
|
|
||||||
|
|
@ -4,23 +4,23 @@ import { HoverNavigation } from "@/components/ui/NavigationBar";
|
||||||
const navLinks = [
|
const navLinks = [
|
||||||
{
|
{
|
||||||
label: "Home",
|
label: "Home",
|
||||||
link: "/",
|
link: "/#home",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Experience",
|
label: "Experience",
|
||||||
link: "/experience",
|
link: "/#experience",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Projects",
|
label: "Projects",
|
||||||
link: "/projects",
|
link: "/#projects",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Skills",
|
label: "Skills",
|
||||||
link: "/skills",
|
link: "/#skills",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Contact",
|
label: "Contact",
|
||||||
link: "/contact",
|
link: "/#contact",
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
---
|
---
|
||||||
|
|
|
||||||
|
|
@ -18,23 +18,7 @@ import TwitchPlays from "../images/projects/twitchplays.webp";
|
||||||
chess engine to determine the best moves. Then uses
|
chess engine to determine the best moves. Then uses
|
||||||
PyAutoGUI to play the moves on the browser.
|
PyAutoGUI to play the moves on the browser.
|
||||||
`}
|
`}
|
||||||
tags={[
|
tags={["Python", "OpenCV", "PyAutoGUI"]}
|
||||||
{
|
|
||||||
text: "Python",
|
|
||||||
textColor: "white",
|
|
||||||
bgColor: "var(--color-python)",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: "OpenCV",
|
|
||||||
textColor: "white",
|
|
||||||
bgColor: "var(--color-opencv)",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: "PyAutoGUI",
|
|
||||||
textColor: "white",
|
|
||||||
bgColor: "black",
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
client:visible
|
client:visible
|
||||||
/>
|
/>
|
||||||
<Project
|
<Project
|
||||||
|
|
@ -47,18 +31,7 @@ import TwitchPlays from "../images/projects/twitchplays.webp";
|
||||||
Chatters can enter messages such as "up up down down left
|
Chatters can enter messages such as "up up down down left
|
||||||
right B A" and the Python program will trigger the
|
right B A" and the Python program will trigger the
|
||||||
corresponding keypresses.`}
|
corresponding keypresses.`}
|
||||||
tags={[
|
tags={["win32API", "Python"]}
|
||||||
{
|
|
||||||
text: "win32API",
|
|
||||||
bgColor: "var(--color-win32api)",
|
|
||||||
textColor: "white",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: "Python",
|
|
||||||
bgColor: "var(--color-python)",
|
|
||||||
textColor: "white",
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,6 @@
|
||||||
---
|
---
|
||||||
import { Image } from "astro:assets";
|
import { Image } from "astro:assets";
|
||||||
|
|
||||||
interface Tag {
|
|
||||||
text: string;
|
|
||||||
textColor: string;
|
|
||||||
bgColor: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
position?: "left" | "right";
|
position?: "left" | "right";
|
||||||
image?: ImageMetadata;
|
image?: ImageMetadata;
|
||||||
|
|
@ -15,7 +9,7 @@ interface Props {
|
||||||
title: string;
|
title: string;
|
||||||
description: string;
|
description: string;
|
||||||
label?: string;
|
label?: string;
|
||||||
tags?: Tag[];
|
tags?: Array<string>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
|
@ -117,7 +111,7 @@ const leftProject = position == "left";
|
||||||
<div
|
<div
|
||||||
class="my-1 mr-1 w-fit rounded-full px-3 py-1 transition-colors duration-300 text-white bg-secondary"
|
class="my-1 mr-1 w-fit rounded-full px-3 py-1 transition-colors duration-300 text-white bg-secondary"
|
||||||
>
|
>
|
||||||
{tag.text}
|
{tag}
|
||||||
</div>
|
</div>
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,23 +19,7 @@ import Project from "./Project.astro";
|
||||||
title="Coworking Guide"
|
title="Coworking Guide"
|
||||||
label="Visit Site"
|
label="Visit Site"
|
||||||
description="A guide to coworking streams for streamers, covers wide range of topics from tips on how to get started with setting up stream to how to grow your audience!"
|
description="A guide to coworking streams for streamers, covers wide range of topics from tips on how to get started with setting up stream to how to grow your audience!"
|
||||||
tags={[
|
tags={["Astro", "Typescript", "Markdown"]}
|
||||||
{
|
|
||||||
text: "Astro",
|
|
||||||
bgColor: "white",
|
|
||||||
textColor: "black",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: "TypeScript",
|
|
||||||
bgColor: "#3178C6",
|
|
||||||
textColor: "white",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: "Markdown",
|
|
||||||
bgColor: "black",
|
|
||||||
textColor: "white",
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<VideoProject
|
<VideoProject
|
||||||
|
|
@ -48,23 +32,7 @@ import Project from "./Project.astro";
|
||||||
description={`
|
description={`
|
||||||
A script that plays AimLab, a game that helps improve your aim in FPS games. Uses OpenCV to detect the targets and win32API to move the mouse and click.
|
A script that plays AimLab, a game that helps improve your aim in FPS games. Uses OpenCV to detect the targets and win32API to move the mouse and click.
|
||||||
`}
|
`}
|
||||||
tags={[
|
tags={["Python", "OpenCV", "win32API"]}
|
||||||
{
|
|
||||||
text: "Python",
|
|
||||||
textColor: "white",
|
|
||||||
bgColor: "var(--color-python)",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: "OpenCV",
|
|
||||||
textColor: "white",
|
|
||||||
bgColor: "var(--color-opencv)",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: "win32API",
|
|
||||||
textColor: "white",
|
|
||||||
bgColor: "var(--color-win32api)",
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Project
|
<Project
|
||||||
|
|
@ -75,18 +43,7 @@ import Project from "./Project.astro";
|
||||||
label="Visit Site"
|
label="Visit Site"
|
||||||
title="We're not really strangers"
|
title="We're not really strangers"
|
||||||
description="This is a digital adaptation of the popular card game designed to foster meaningful connections through engaging questions and prompts. Made into a PWA for offline usage on mobile!"
|
description="This is a digital adaptation of the popular card game designed to foster meaningful connections through engaging questions and prompts. Made into a PWA for offline usage on mobile!"
|
||||||
tags={[
|
tags={["React", "Typescript"]}
|
||||||
{
|
|
||||||
text: "React",
|
|
||||||
bgColor: "var(--color-react)",
|
|
||||||
textColor: "black",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: "TypeScript",
|
|
||||||
bgColor: "var(--color-typescript)",
|
|
||||||
textColor: "white",
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Project
|
<Project
|
||||||
|
|
@ -99,23 +56,7 @@ import Project from "./Project.astro";
|
||||||
to add their own tasks to the widget for accountability!
|
to add their own tasks to the widget for accountability!
|
||||||
Easy to setup and the customization possibilities are
|
Easy to setup and the customization possibilities are
|
||||||
endless!`}
|
endless!`}
|
||||||
tags={[
|
tags={["HTML", "CSS", "Javascript"]}
|
||||||
{
|
|
||||||
text: "HTML",
|
|
||||||
bgColor: "var(--color-html)",
|
|
||||||
textColor: "white",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: "CSS",
|
|
||||||
bgColor: "var(--color-blue-600)",
|
|
||||||
textColor: "white",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: "Javascript",
|
|
||||||
bgColor: "var(--color-yellow-500)",
|
|
||||||
textColor: "black",
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
>
|
>
|
||||||
<TaskWidget
|
<TaskWidget
|
||||||
classList={`
|
classList={`
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,6 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onMount } from "svelte";
|
import { onMount } from "svelte";
|
||||||
|
|
||||||
interface Tag {
|
|
||||||
text: string;
|
|
||||||
textColor: string;
|
|
||||||
bgColor: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
position?: "left" | "right";
|
position?: "left" | "right";
|
||||||
image_src: string;
|
image_src: string;
|
||||||
|
|
@ -14,7 +8,7 @@
|
||||||
video_src: string;
|
video_src: string;
|
||||||
title: string;
|
title: string;
|
||||||
description: string;
|
description: string;
|
||||||
tags?: Tag[];
|
tags?: Array<string>;
|
||||||
}
|
}
|
||||||
|
|
||||||
let {
|
let {
|
||||||
|
|
@ -137,7 +131,7 @@
|
||||||
w-fit rounded-full px-3
|
w-fit rounded-full px-3
|
||||||
py-1 text-white transition-colors duration-300"
|
py-1 text-white transition-colors duration-300"
|
||||||
>
|
>
|
||||||
{tag.text}
|
{tag}
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,10 @@
|
||||||
---
|
---
|
||||||
import Card from "../global/Card.astro";
|
import Card from "../global/Card.astro";
|
||||||
|
|
||||||
interface Tag {
|
|
||||||
text: string;
|
|
||||||
bgColor: string;
|
|
||||||
textColor?: string;
|
|
||||||
hoverTextColor?: string;
|
|
||||||
className?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
title: string;
|
title: string;
|
||||||
tags?: Tag[];
|
tags?: Array<string>;
|
||||||
primaryColor: string;
|
primaryColor?: string;
|
||||||
className?: string;
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -35,7 +27,7 @@ const { title, tags, primaryColor, className = "" } = Astro.props;
|
||||||
<div
|
<div
|
||||||
class={`tag-${index} my-1 mr-1 w-fit cursor-default rounded-full bg-[#303030] px-3 py-1 text-white transition-colors duration-300`}
|
class={`tag-${index} my-1 mr-1 w-fit cursor-default rounded-full bg-[#303030] px-3 py-1 text-white transition-colors duration-300`}
|
||||||
>
|
>
|
||||||
{tag.text}
|
{tag}
|
||||||
</div>
|
</div>
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,51 +13,16 @@ import Skill from "./Skill.astro";
|
||||||
title="Frontend Development"
|
title="Frontend Development"
|
||||||
primaryColor="#19a7ce"
|
primaryColor="#19a7ce"
|
||||||
tags={[
|
tags={[
|
||||||
{
|
"HTML",
|
||||||
text: "HTML",
|
"CSS",
|
||||||
bgColor: "var(--color-html)",
|
"Javascript",
|
||||||
},
|
"Typescript",
|
||||||
{
|
"Svelte",
|
||||||
text: "CSS",
|
"React",
|
||||||
bgColor: "var(--color-blue-700)",
|
"Vue",
|
||||||
},
|
"WordPress",
|
||||||
{
|
"Astro",
|
||||||
text: "Javascript",
|
"TailwindCSS",
|
||||||
bgColor: "var(--color-yellow-400)",
|
|
||||||
textColor: "black",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: "Typescript",
|
|
||||||
bgColor: "var(--color-typescript)",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: "Svelte",
|
|
||||||
bgColor: "var(--color-svelte)",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: "React",
|
|
||||||
bgColor: "var(--color-react)",
|
|
||||||
textColor: "black",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: "Vue",
|
|
||||||
bgColor: "var(--color-green-500)",
|
|
||||||
textColor: "black",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: "WordPress",
|
|
||||||
bgColor: "var(--color-wordpress)",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: "Astro",
|
|
||||||
bgColor: "white",
|
|
||||||
textColor: "black",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: "TailwindCSS",
|
|
||||||
bgColor: "var(--color-tailwind)",
|
|
||||||
textColor: "black",
|
|
||||||
},
|
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
<Line height="50px" />
|
<Line height="50px" />
|
||||||
|
|
@ -66,38 +31,15 @@ import Skill from "./Skill.astro";
|
||||||
title="Backend Development"
|
title="Backend Development"
|
||||||
primaryColor="#474F7A"
|
primaryColor="#474F7A"
|
||||||
tags={[
|
tags={[
|
||||||
{
|
"Python",
|
||||||
text: "Python",
|
"Typescript",
|
||||||
bgColor: "var(--color-python)",
|
"C#",
|
||||||
},
|
"Kotlin",
|
||||||
{
|
"PHP",
|
||||||
text: "Typescript",
|
"PostgreSQL",
|
||||||
bgColor: "var(--color-typescript)",
|
"MySQL",
|
||||||
},
|
"MongoDB",
|
||||||
{
|
"Supabase",
|
||||||
text: "C#",
|
|
||||||
bgColor: "var(--color-csharp)",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: "Kotlin",
|
|
||||||
bgColor: "var(--color-kotlin)",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: "PHP",
|
|
||||||
bgColor: "var(--color-php)",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: "PostgreSQL",
|
|
||||||
bgColor: "var(--color-postgresql)",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: "MySQL",
|
|
||||||
bgColor: "var(--color-mysql)",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: "MongoDB",
|
|
||||||
bgColor: "var(--color-mongodb)",
|
|
||||||
},
|
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
<Line height="50px" />
|
<Line height="50px" />
|
||||||
|
|
@ -106,39 +48,14 @@ import Skill from "./Skill.astro";
|
||||||
title="Data Science"
|
title="Data Science"
|
||||||
primaryColor="#a1eebd"
|
primaryColor="#a1eebd"
|
||||||
tags={[
|
tags={[
|
||||||
{
|
"Python",
|
||||||
text: "Python",
|
"R",
|
||||||
bgColor: "var(--color-python)",
|
"OpenCV",
|
||||||
},
|
"SAS",
|
||||||
{
|
"MySQL",
|
||||||
text: "R",
|
"Pandas",
|
||||||
bgColor: "var(--color-cyan-500)",
|
"Selenium",
|
||||||
},
|
"BeautifulSoup4",
|
||||||
{
|
|
||||||
text: "OpenCV",
|
|
||||||
bgColor: "var(--color-opencv)",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: "SAS",
|
|
||||||
bgColor: "var(--color-blue-500)",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: "MySQL",
|
|
||||||
bgColor: "var(--color-mysql)",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: "Pandas",
|
|
||||||
bgColor: "var(--color-blue-800)",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: "Selenium",
|
|
||||||
bgColor: "var(--color-green-700)",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: "BeautifulSoup4",
|
|
||||||
bgColor: "white",
|
|
||||||
textColor: "black",
|
|
||||||
},
|
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -49,54 +49,9 @@ export const HoverNavigation = ({
|
||||||
className?: string;
|
className?: string;
|
||||||
}) => {
|
}) => {
|
||||||
const [hoveredIndex, setHoveredIndex] = useState<number | null>(null);
|
const [hoveredIndex, setHoveredIndex] = useState<number | null>(null);
|
||||||
const { scrollY } = useScroll();
|
|
||||||
const [navVisible, setNavVisible] = useState(true);
|
|
||||||
const [availableVisible, setAvailableVisible] = useState(false);
|
|
||||||
|
|
||||||
const [onContactPage, setOnContactPage] = useState(false);
|
|
||||||
const [hamburgerOpen, setHamburgerOpen] = useState(false);
|
const [hamburgerOpen, setHamburgerOpen] = useState(false);
|
||||||
const { width } = useWindowDimensions();
|
const { width } = useWindowDimensions();
|
||||||
|
|
||||||
// if on contact page, do not do width 0%
|
|
||||||
useEffect(() => {
|
|
||||||
setOnContactPage(window.location.href.includes("/contact"));
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
updateNavBarState(width);
|
|
||||||
}, [width]);
|
|
||||||
|
|
||||||
const updateNavBarState = (
|
|
||||||
width: number,
|
|
||||||
scrollPosition: number = 0,
|
|
||||||
scrollUp: boolean = false,
|
|
||||||
) => {
|
|
||||||
if (width && width < 1024) {
|
|
||||||
setNavVisible(false);
|
|
||||||
setAvailableVisible(true);
|
|
||||||
} else if (scrollPosition < 50) {
|
|
||||||
setNavVisible(true);
|
|
||||||
setAvailableVisible(false);
|
|
||||||
} else if (!onContactPage) {
|
|
||||||
setNavVisible(scrollUp);
|
|
||||||
setAvailableVisible(!scrollUp);
|
|
||||||
} else {
|
|
||||||
setNavVisible(true);
|
|
||||||
setAvailableVisible(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
useMotionValueEvent(scrollY, "change", (current) => {
|
|
||||||
// Check if current is not undefined and is a number
|
|
||||||
if (typeof current === "number") {
|
|
||||||
let direction = current! - scrollY.getPrevious()!;
|
|
||||||
let scrollUp = direction <= 0;
|
|
||||||
let scrollProgress = scrollY.get();
|
|
||||||
|
|
||||||
updateNavBarState(width, scrollProgress, scrollUp);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="bg-secondary/50 fixed top-5 left-1/2 z-30 box-border flex w-11/12 -translate-x-1/2 items-center justify-center overflow-auto rounded-[40px] border-2 border-solid border-gray-700 px-5 py-3 backdrop-blur-md sm:w-4/5 lg:w-2/3 xl:w-auto">
|
<div className="bg-secondary/50 fixed top-5 left-1/2 z-30 box-border flex w-11/12 -translate-x-1/2 items-center justify-center overflow-auto rounded-[40px] border-2 border-solid border-gray-700 px-5 py-3 backdrop-blur-md sm:w-4/5 lg:w-2/3 xl:w-auto">
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
|
|
@ -112,13 +67,7 @@ export const HoverNavigation = ({
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col items-center justify-center">
|
<div className="flex flex-col items-center justify-center">
|
||||||
<motion.div
|
<motion.div className="flex w-auto flex-row items-center justify-center pr-2 whitespace-nowrap opacity-100 lg:w-0 lg:opacity-0">
|
||||||
animate={{
|
|
||||||
width: availableVisible ? "auto" : "0px",
|
|
||||||
opacity: availableVisible ? "100%" : "0%",
|
|
||||||
}}
|
|
||||||
className="flex w-auto flex-row items-center justify-center pr-2 whitespace-nowrap opacity-100 lg:w-0 lg:opacity-0"
|
|
||||||
>
|
|
||||||
<a
|
<a
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
href="/contact"
|
href="/contact"
|
||||||
|
|
@ -138,10 +87,6 @@ export const HoverNavigation = ({
|
||||||
>
|
>
|
||||||
<motion.ul
|
<motion.ul
|
||||||
className="hidden w-0 flex-row items-center justify-center overflow-hidden lg:flex lg:w-auto"
|
className="hidden w-0 flex-row items-center justify-center overflow-hidden lg:flex lg:w-auto"
|
||||||
animate={{
|
|
||||||
width: navVisible ? "auto" : "0px",
|
|
||||||
opacity: navVisible ? "100%" : "0%",
|
|
||||||
}}
|
|
||||||
transition={{
|
transition={{
|
||||||
ease: "easeInOut",
|
ease: "easeInOut",
|
||||||
}}
|
}}
|
||||||
|
|
|
||||||
141
src/components/ui/moving-border.tsx
Normal file
141
src/components/ui/moving-border.tsx
Normal file
|
|
@ -0,0 +1,141 @@
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
|
import {
|
||||||
|
motion,
|
||||||
|
useAnimationFrame,
|
||||||
|
useMotionTemplate,
|
||||||
|
useMotionValue,
|
||||||
|
useTransform,
|
||||||
|
} from "motion/react";
|
||||||
|
import { useRef } from "react";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
|
export function Button({
|
||||||
|
borderRadius = "1.75rem",
|
||||||
|
children,
|
||||||
|
as: Component = "button",
|
||||||
|
containerClassName,
|
||||||
|
borderClassName,
|
||||||
|
duration,
|
||||||
|
className,
|
||||||
|
...otherProps
|
||||||
|
}: {
|
||||||
|
borderRadius?: string;
|
||||||
|
children: React.ReactNode;
|
||||||
|
as?: any;
|
||||||
|
containerClassName?: string;
|
||||||
|
borderClassName?: string;
|
||||||
|
duration?: number;
|
||||||
|
className?: string;
|
||||||
|
[key: string]: any;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<Component
|
||||||
|
className={cn(
|
||||||
|
"relative h-16 w-fit overflow-hidden bg-transparent p-[1px] text-xl",
|
||||||
|
containerClassName,
|
||||||
|
)}
|
||||||
|
style={{
|
||||||
|
borderRadius: borderRadius,
|
||||||
|
}}
|
||||||
|
{...otherProps}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="absolute inset-0"
|
||||||
|
style={{ borderRadius: `calc(${borderRadius} * 0.96)` }}
|
||||||
|
>
|
||||||
|
<MovingBorder duration={duration} rx="30%" ry="30%">
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"h-20 w-20 bg-[radial-gradient(#72ffff_40%,transparent_60%)] opacity-[0.8]",
|
||||||
|
borderClassName,
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</MovingBorder>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"relative flex h-full w-full items-center justify-center border border-slate-800 bg-slate-900/[0.8] text-sm text-white antialiased backdrop-blur-xl",
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
style={{
|
||||||
|
borderRadius: `calc(${borderRadius} * 0.96)`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
</Component>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const MovingBorder = ({
|
||||||
|
children,
|
||||||
|
duration = 3000,
|
||||||
|
rx,
|
||||||
|
ry,
|
||||||
|
...otherProps
|
||||||
|
}: {
|
||||||
|
children: React.ReactNode;
|
||||||
|
duration?: number;
|
||||||
|
rx?: string;
|
||||||
|
ry?: string;
|
||||||
|
[key: string]: any;
|
||||||
|
}) => {
|
||||||
|
// @ts-expect-error
|
||||||
|
const pathRef = useRef<any>();
|
||||||
|
const progress = useMotionValue<number>(0);
|
||||||
|
|
||||||
|
useAnimationFrame((time) => {
|
||||||
|
const length = pathRef.current?.getTotalLength();
|
||||||
|
if (length) {
|
||||||
|
const pxPerMillisecond = length / duration;
|
||||||
|
progress.set((time * pxPerMillisecond) % length);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const x = useTransform(
|
||||||
|
progress,
|
||||||
|
(val) => pathRef.current?.getPointAtLength(val).x,
|
||||||
|
);
|
||||||
|
const y = useTransform(
|
||||||
|
progress,
|
||||||
|
(val) => pathRef.current?.getPointAtLength(val).y,
|
||||||
|
);
|
||||||
|
|
||||||
|
const transform = useMotionTemplate`translateX(${x}px) translateY(${y}px) translateX(-50%) translateY(-50%)`;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
preserveAspectRatio="none"
|
||||||
|
className="absolute h-full w-full"
|
||||||
|
width="100%"
|
||||||
|
height="100%"
|
||||||
|
{...otherProps}
|
||||||
|
>
|
||||||
|
<rect
|
||||||
|
fill="none"
|
||||||
|
width="100%"
|
||||||
|
height="100%"
|
||||||
|
rx={rx}
|
||||||
|
ry={ry}
|
||||||
|
ref={pathRef}
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<motion.div
|
||||||
|
style={{
|
||||||
|
position: "absolute",
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
display: "inline-block",
|
||||||
|
transform,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</motion.div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
66
src/components/ui/tabs.tsx
Normal file
66
src/components/ui/tabs.tsx
Normal file
|
|
@ -0,0 +1,66 @@
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import * as React from "react";
|
||||||
|
import * as TabsPrimitive from "@radix-ui/react-tabs";
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
|
function Tabs({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof TabsPrimitive.Root>) {
|
||||||
|
return (
|
||||||
|
<TabsPrimitive.Root
|
||||||
|
data-slot="tabs"
|
||||||
|
className={cn("flex flex-col gap-2", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function TabsList({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof TabsPrimitive.List>) {
|
||||||
|
return (
|
||||||
|
<TabsPrimitive.List
|
||||||
|
data-slot="tabs-list"
|
||||||
|
className={cn(
|
||||||
|
"text-primary inline-flex h-9 w-fit items-center justify-center rounded-lg bg-[#101010] p-[3px]",
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function TabsTrigger({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof TabsPrimitive.Trigger>) {
|
||||||
|
return (
|
||||||
|
<TabsPrimitive.Trigger
|
||||||
|
data-slot="tabs-trigger"
|
||||||
|
className={cn(
|
||||||
|
"data-[state=active]:bg-secondary focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:outline-ring text-primary hover:bg-secondary/20 inline-flex h-[calc(100%-1px)] flex-1 cursor-pointer items-center justify-center gap-1.5 rounded-md border border-transparent px-2 py-5 text-base font-medium whitespace-nowrap transition-[color,box-shadow] duration-300 focus-visible:ring-[3px] focus-visible:outline-1 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:shadow-sm [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function TabsContent({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof TabsPrimitive.Content>) {
|
||||||
|
return (
|
||||||
|
<TabsPrimitive.Content
|
||||||
|
data-slot="tabs-content"
|
||||||
|
className={cn("flex-1 outline-none", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Tabs, TabsList, TabsTrigger, TabsContent };
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
---
|
|
||||||
import ExperienceComponent from "../components/experience/ExperienceComponent.astro";
|
|
||||||
import MoreExperience from "../components/experience/MoreExperience.astro";
|
|
||||||
import Footer from "../components/global/FooterComponent.astro";
|
|
||||||
import PageLayout from "@/layouts/PageLayout.astro";
|
|
||||||
---
|
|
||||||
|
|
||||||
<PageLayout
|
|
||||||
title="Experience"
|
|
||||||
description="Find out RythonDev's previous education"
|
|
||||||
thumbnail="/images/profile/contact_photo.webp"
|
|
||||||
>
|
|
||||||
<div id="Experience" class="pt-30">
|
|
||||||
<ExperienceComponent />
|
|
||||||
<MoreExperience />
|
|
||||||
</div>
|
|
||||||
<Footer />
|
|
||||||
</PageLayout>
|
|
||||||
|
|
@ -2,11 +2,12 @@
|
||||||
import NetworkLine from "../components/global/NetworkLine.astro";
|
import NetworkLine from "../components/global/NetworkLine.astro";
|
||||||
import Intro from "../components/home/Intro.astro";
|
import Intro from "../components/home/Intro.astro";
|
||||||
import Projects from "../components/projects/ProjectsComponent.astro";
|
import Projects from "../components/projects/ProjectsComponent.astro";
|
||||||
import Experience from "../components/experience/ExperienceComponent.astro";
|
import Experience from "../components/experience/ExperienceComponent.tsx";
|
||||||
import Skills from "../components/skills/SkillsComponent.astro";
|
import Skills from "../components/skills/SkillsComponent.astro";
|
||||||
import Contact from "../components/contact/ContactComponent.astro";
|
import Contact from "../components/contact/ContactComponent.astro";
|
||||||
import Footer from "../components/global/FooterComponent.astro";
|
import Footer from "../components/global/FooterComponent.astro";
|
||||||
import PageLayout from "@/layouts/PageLayout.astro";
|
import PageLayout from "@/layouts/PageLayout.astro";
|
||||||
|
import { Button } from "@/components/ui/moving-border";
|
||||||
---
|
---
|
||||||
|
|
||||||
<PageLayout
|
<PageLayout
|
||||||
|
|
@ -18,17 +19,31 @@ import PageLayout from "@/layouts/PageLayout.astro";
|
||||||
<Intro />
|
<Intro />
|
||||||
</div>
|
</div>
|
||||||
<NetworkLine height="70px" />
|
<NetworkLine height="70px" />
|
||||||
<div id="education">
|
<div
|
||||||
<div class="mx-auto mb-10 h-[1px] w-1/3 bg-[#404040]"></div>
|
id="experience"
|
||||||
<Experience />
|
class="flex scroll-mt-18 flex-col items-center justify-center"
|
||||||
|
>
|
||||||
|
<!-- horizontal line -->
|
||||||
|
<hr class="mx-auto mb-10 h-[1px] w-1/3 border-[#404040]" />
|
||||||
|
<Experience client:visible />
|
||||||
</div>
|
</div>
|
||||||
<div id="projects">
|
<div id="projects" class="scroll-mt-12">
|
||||||
<div class="pt-20">
|
<div class="flex flex-col items-center justify-center pt-20">
|
||||||
<h1 class="mb-10 text-center text-5xl">Projects</h1>
|
<h1 class="mb-10 text-center text-5xl">Projects</h1>
|
||||||
<Projects />
|
<Projects />
|
||||||
|
<Button
|
||||||
|
client:visible
|
||||||
|
borderRadius="1.75rem"
|
||||||
|
className="bg-secondary cursor-pointer border-slate-800 px-5 text-white transition-all duration-300 hover:brightness-125"
|
||||||
|
duration={2000}
|
||||||
|
as={"a"}
|
||||||
|
href="/projects"
|
||||||
|
>
|
||||||
|
VIEW ALL PROJECTS
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="skills">
|
<div id="skills" class="scroll-mt-16">
|
||||||
<Skills />
|
<Skills />
|
||||||
</div>
|
</div>
|
||||||
<div id="contact" class="pt-20">
|
<div id="contact" class="pt-20">
|
||||||
|
|
@ -36,6 +51,7 @@ import PageLayout from "@/layouts/PageLayout.astro";
|
||||||
</div>
|
</div>
|
||||||
<Footer />
|
<Footer />
|
||||||
</PageLayout>
|
</PageLayout>
|
||||||
|
|
||||||
<script is:inline>
|
<script is:inline>
|
||||||
location.search.includes("?uwu") &&
|
location.search.includes("?uwu") &&
|
||||||
document.documentElement.classList.add("uwu");
|
document.documentElement.classList.add("uwu");
|
||||||
|
|
|
||||||
|
|
@ -1,27 +1,10 @@
|
||||||
@import "tailwindcss";
|
@import "tailwindcss";
|
||||||
|
|
||||||
@theme {
|
@theme {
|
||||||
--color-html: #b11700;
|
|
||||||
--color-typescript: #3178c6;
|
|
||||||
--color-svelte: #b11700;
|
|
||||||
--color-react: #00d8ff;
|
|
||||||
--color-wordpress: #21759b;
|
|
||||||
--color-htmx: #3366cc;
|
|
||||||
--color-tailwind: #07b6d4;
|
|
||||||
--color-python: #3776ab;
|
|
||||||
--color-csharp: #672079;
|
|
||||||
--color-java: #ed8b00;
|
|
||||||
--color-kotlin: #7f52ff;
|
|
||||||
--color-php: #3d5694;
|
|
||||||
--color-go: #006080;
|
|
||||||
--color-rust: #b11700;
|
|
||||||
--color-postgresql: #4479a1;
|
|
||||||
--color-mysql: #1d6998;
|
|
||||||
--color-mongodb: #09934e;
|
|
||||||
--color-opencv: #10652f;
|
|
||||||
--color-win32api: #0078d4;
|
|
||||||
--color-primary: #fafafa;
|
--color-primary: #fafafa;
|
||||||
--color-secondary: #1d1c20;
|
--color-secondary: #1d1c20;
|
||||||
|
--color-accent: #72ffff;
|
||||||
|
--color-muted-foreground: #ccc;
|
||||||
|
|
||||||
--animate-fadeIn: fadeIn 0.5s ease-in-out;
|
--animate-fadeIn: fadeIn 0.5s ease-in-out;
|
||||||
--animate-fadeUp: fadeUp 0.5s ease-in-out;
|
--animate-fadeUp: fadeUp 0.5s ease-in-out;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue