diff --git a/apps/about-me/src/components/index.tsx b/apps/about-me/src/components/index.tsx index 4416b76..c552df7 100644 --- a/apps/about-me/src/components/index.tsx +++ b/apps/about-me/src/components/index.tsx @@ -1,6 +1,6 @@ import { ReactNode } from "react" -export const Section: React.FC<{children?: ReactNode}> = ({children}) =>
{children}
+export const Section: React.FC<{children?: ReactNode}> = ({children}) =>
{children}
export const UL: React.FC<{ children?: ReactNode }> = ({ children }) => export const SectionHeader: React.FC<{ text?: string, children?: ReactNode }> = ({ text, children }) =>
diff --git a/apps/about-me/src/pages/_document.tsx b/apps/about-me/src/pages/_document.tsx deleted file mode 100644 index 60526a6..0000000 --- a/apps/about-me/src/pages/_document.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import { Head } from "next/document"; -import { Main } from "next/document"; -import { NextScript } from "next/document"; -import { Html } from "next/document"; -import React from "react"; - -const document = () => - - -
- - - - -export default document; diff --git a/apps/about-me/src/pages/blogs/[slug].tsx b/apps/about-me/src/pages/blogs/[slug].tsx new file mode 100644 index 0000000..e69de29 diff --git a/apps/about-me/src/pages/blogs/index.tsx b/apps/about-me/src/pages/blogs/index.tsx new file mode 100644 index 0000000..e69de29 diff --git a/apps/about-me/src/pages/index.tsx b/apps/about-me/src/pages/index.tsx index 87552a4..11e1527 100644 --- a/apps/about-me/src/pages/index.tsx +++ b/apps/about-me/src/pages/index.tsx @@ -1,13 +1,13 @@ import { type NextPage } from "next"; import Head from "next/head"; import Image from "next/image"; -import React from "react"; +import React, { useContext, useEffect, useState } from "react"; import { cva } from "class-variance-authority"; import type { VariantProps } from "class-variance-authority"; import Link from "next/link"; import { - FaGithub, - FaLinkedin + FaGithub, + FaLinkedin } from 'react-icons/fa'; import { ReactNode } from "react"; import { useReducerMonad } from "@utils/stdx" @@ -21,338 +21,400 @@ import assert from "assert"; import { Section, SectionHeader, UL } from "@components/index"; const circle = cva("absolute rounded-full border -z-10", { - variants: { - size: { - 0: "h-[200px] w-[200px]", - 1: "h-[350px] w-[350px]", - 2: "h-[500px] w-[500px]", - 3: "h-[650px] w-[650px]", - 4: "h-[800px] w-[800px]", + variants: { + size: { + 0: "h-[200px] w-[200px]", + 1: "h-[350px] w-[350px]", + 2: "h-[500px] w-[500px]", + 3: "h-[650px] w-[650px]", + 4: "h-[800px] w-[800px]", + }, + animate: { + none: "", + pulse: "animate-pulse", + ping: "animate-ping", + }, + theme: { + light: "", + dark: "", + }, + color: { + back: "", + fore: "", + } }, - animate: { - none: "", - pulse: "animate-pulse", - ping: "animate-ping", - }, - color: { - back: "border-pink-50", - fore: "border-amber-300", + compoundVariants: [ + { + theme: "light", + color: "back", + className: "border-pink-50" + }, + { + theme: "light", + color: "fore", + className: "border-amber-300" + }, + { + theme: "dark", + color: "back", + className: "border-violet-300" + }, + { + theme: "dark", + color: "fore", + className: "border-amber-400" + }, + ], + defaultVariants: { + size: 0, + animate: "none", + color: "back", + theme: "light" } - }, - defaultVariants: { - size: 0, - animate: "none", - color: "back" - } }); const Circle: React.FC> = - ({ size, animate, color }) =>
+ ({ size, animate, color }) =>
const Shapes = () =>
- - - - - + + + + +
const Article: React.FC<{ children?: ReactNode }> = ({ children }) =>
- {children} + className="relative w-screen flex flex-col justify-center items-center overflow-hidden text-center min-h-screen gap-2"> + {children}
const InpageSection: React.FC<{ href: string, children?: ReactNode }> = ({ - href, - children + href, + children }) => - {children} - + border border-amber-400/50 hover:text-gray-500 hover:border-amber-400 transition-colors + hover:animate-pulse"> + {children} + const Brief = () =>
- {/*Avatar*/} -
- -
-
- Hung's avatar -
+ {/*Avatar*/} +
+ +
+
+ Hung's avatar +
+
+
+

+ Hung Tran +

+

+ Software Engineer +

+
+ About + Experience + Blog + Projects
-
-

- Hung Tran -

-

- Software Engineer -

-
- About - Experience - Blog - Projects -
const images = [ - { - src: SFChinatown, - alt: "chinatown @ San Francisco" - }, - { - src: SFChurch, - alt: "church @ San Francisco" - }, - { - src: Lizzie, - alt: "My cat Lizzie", - }, - { - src: SantaMonicaHigh, - alt: "Santa Monica in high ground" - }, - { - src: SantaMonicaStairs, - alt: "Santa Monica taken near stairs" - } + { + src: SFChinatown, + alt: "chinatown @ San Francisco" + }, + { + src: SFChurch, + alt: "church @ San Francisco" + }, + { + src: Lizzie, + alt: "My cat Lizzie", + }, + { + src: SantaMonicaHigh, + alt: "Santa Monica in high ground" + }, + { + src: SantaMonicaStairs, + alt: "Santa Monica taken near stairs" + } ]; type Image = typeof images[0]; function useImageDisplay(images: Image[]) { - const [imageRepo, setImageRepo] = React.useState(images); - const init = images.length == 0 ? undefined : 0; - const [imgIndex, imgDispatch] = useReducerMonad((idx, action: { - type: "next" | "prev" - } | { - type: "set", - index: number - }) => { - if (images.length == 0) { return undefined; } - let retval = idx; - assert(retval != undefined); - switch (action.type) { - case "next": - retval += 1; - break; - case "prev": - retval -= 1; - break; - case "set": - return action.index; + const [imageRepo, setImageRepo] = React.useState(images); + const init = images.length == 0 ? undefined : 0; + const [imgIndex, imgDispatch] = useReducerMonad((idx, action: { + type: "next" | "prev" + } | { + type: "set", + index: number + }) => { + if (images.length == 0) { return undefined; } + let retval = idx; + assert(retval != undefined); + switch (action.type) { + case "next": + retval += 1; + break; + case "prev": + retval -= 1; + break; + case "set": + return action.index; + } + retval = (images.length + retval) % images.length; + return retval; + }, init); + const image = imgIndex !== undefined ? imageRepo[imgIndex] : undefined; + return { + next: () => imgDispatch({ type: "next" }), + prev: () => imgDispatch({ type: "prev" }), + setIdx: (idx: number) => imgDispatch({ type: "set", index: idx }), + imageRepo, + setImageRepo, + imgIndex, + image, } - retval = (images.length + retval) % images.length; - return retval; - }, init); - const image = imgIndex !== undefined ? imageRepo[imgIndex] : undefined; - return { - next: () => imgDispatch({ type: "next" }), - prev: () => imgDispatch({ type: "prev" }), - setIdx: (idx: number) => imgDispatch({ type: "set", index: idx }), - imageRepo, - setImageRepo, - imgIndex, - image, - } } type ImageDisplayHook = ReturnType; const imageButton = cva("w-8 h-8 rounded-full flex justify-center items-center transition-colors font-semibold", { - variants: { - theme: { - light: "bg-amber-200/30 group-hover:bg-amber-200/60 text-gray-700" + variants: { + theme: { + light: "bg-amber-200/30 group-hover:bg-amber-200/60 text-gray-700" + } + }, + defaultVariants: { + theme: "light" } - }, - defaultVariants: { - theme: "light" - } }); const imageIndex = cva("rounded-full hover:cursor-pointer transition-all", { - variants: { - theme: { - light: "", + variants: { + theme: { + light: "", + }, + active: { + isActive: "w-3 h-3", + isInactive: "w-2 h-2", + } }, - active: { - isActive: "w-3 h-3", - isInactive: "w-2 h-2", + compoundVariants: [ + { + theme: "light", + active: "isActive", + className: "bg-pink-200/70" + }, + { + theme: "light", + active: "isInactive", + className: "bg-pink-200/40" + } + ], + defaultVariants: { + theme: "light", } - }, - compoundVariants: [ - { - theme: "light", - active: "isActive", - className: "bg-pink-200/70" - }, - { - theme: "light", - active: "isInactive", - className: "bg-pink-200/40" - } - ], - defaultVariants: { - theme: "light", - } }) const ImageDisplay: React.FC<{ imageDisplayHook: ImageDisplayHook }> = ({ imageDisplayHook: { - next, - prev, - imageRepo, - // setImageRepo, - imgIndex, - image + next, + prev, + imageRepo, + // setImageRepo, + imgIndex, + image } }) =>
- - -
-
- {Array.from(Array(imageRepo.length).keys()).map(i =>
)} -
+ + +
+
+ {Array.from(Array(imageRepo.length).keys()).map(i =>
)} +
+
+ {image && {image.alt}}
- {image && {image.alt}} -
const MyLink: React.FC<{ href: string, children?: ReactNode }> = ({ href, children }) => - {children} + className="text-amber-400/70 visited:text-purple-400/70 hover:text-amber-400 visited:hover:text-purple-400" href={href}> + {children} const About = () => { - const imgDisplay = useImageDisplay(images); + const imgDisplay = useImageDisplay(images); - return
- About -
- -
-
- - Aspiring software engineer with intense drive and curiosity in software development. - -
-
- My computer infrastructure consists of: -
    -
  • 24 home CPU cores
  • -
  • 60 GB of RAM (4 GB SDDR3 and 56 GB DDR4)
  • -
  • 2.5 TB of SSD + HDD
  • -
  • A mix of NixOS, Ubuntu, and Windows
  • -
-
-
- - In free time, you would find me: - -
    -
  • Learning about new technology in a week
  • -
  • Performing microbenchmarks on competing implementations and technologies
  • -
  • Optimizing my workspace through dotfiles
  • -
  • Contributing to open-source projects
  • -
  • Watching edutainment videos and podcasts about software
  • {/*TODO: add link to edutainment note-taking platform here*/} -
  • Producing music
  • -
-
-
-
-
+ return
+ About +
+ +
+
+ + Aspiring software engineer with intense drive and curiosity in software development. + +
+
+ My computer infrastructure consists of: +
    +
  • 24 home CPU cores
  • +
  • 60 GB of RAM (4 GB SDDR3 and 56 GB DDR4)
  • +
  • 2.5 TB of SSD + HDD
  • +
  • A mix of NixOS, Ubuntu, and Windows
  • +
+
+
+ + In free time, you would find me: + +
    +
  • Learning about new technology in a week
  • +
  • Performing microbenchmarks on competing implementations and technologies
  • +
  • Optimizing my workspace through dotfiles
  • +
  • Contributing to open-source projects
  • +
  • Watching edutainment videos and podcasts about software
  • {/*TODO: add link to edutainment note-taking platform here*/} +
  • Producing music
  • +
+
+
+
+
} const Experience = () =>
- Experience + Experience
const Blog = () =>
- Blogs - Currently have a Zettelkasten, but nothing is published yet. + Blogs + Currently have a Zettelkasten, but nothing is published yet.
const Projects = () =>
- Projects + Projects
const NavbarItem: React.FC<{ - text: string; - link: string; - children: React.ReactNode + text: string; + link: string; + children: React.ReactNode }> = ({ text, link, children }) => - + const iconCva = cva("transition-colors", { - variants: { - theme: { - light: "group-hover:fill-gray-700 fill-gray-400" + variants: { + theme: { + light: "group-hover:fill-gray-700 fill-gray-400" + } + }, + defaultVariants: { + theme: "light" } - }, - defaultVariants: { - theme: "light" - } }); const Navbar = () =>
-
-
- - +
+
+ + +
+ + Hung Tran +
- - Hung Tran - -
+type Theme = "dark" | "light"; + +const ThemeContext = React.createContext<{ + theme: Theme, + systemTheme: Theme, + setThemePreference(theme: Theme): void, +} | undefined>(undefined); +const ThemeProvider: React.FC<{ children: React.ReactNode, prefer?: "light" | "dark" }> = ({ children, prefer }) => { + const pref = prefer ?? "light"; + const [systemTheme, setSystemTheme] = useState(pref); + const [themePref, setThemePref] = useState(undefined); + // add a listener to system preference on which theme + // TODO: is there another way that remove event listener faster? + useEffect(() => { + console.log("useEffect called"); + type EventType = { matches: boolean }; + const onThemeChange = (event: EventType) => { + const newColorScheme = event.matches ? "dark" : "light"; + setSystemTheme(newColorScheme); + }; + window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', onThemeChange); + return () => { + console.log("useEffect unmount"); + window.matchMedia('(prefers-color-scheme: dark)').removeEventListener("change", onThemeChange) + } + }, []); + return {children} +} +const useTheme = () => useContext(ThemeContext); const Home: NextPage = () => { - return ( -
{/*vimium users cry if snap-mandatory*/} - - Hung Tran - - - - -
- -
-
- -
-
- -
-
- -
-
- -
-
- ); + return ( + +
{/*vimium users cry if snap-mandatory*/} + + Hung Tran + + + + +
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+ ); }; export default Home;