-
-
-
-
-
+ {/*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 &&
}
-
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 = () =>
-
+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;