Compare commits

...

6 Commits

  1. 33
      package.json
  2. 11671
      pnpm-lock.yaml
  3. 5
      src/blog.tsx
  4. 211
      src/components/Container.tsx
  5. 38
      src/components/Menu.tsx
  6. 19
      src/components/Timeline.tsx
  7. 5
      src/index.css
  8. 48
      src/index.tsx
  9. 32
      src/pages/blog/Home.tsx
  10. 6
      src/pages/blog/components/BlogContent.tsx
  11. 10
      src/pages/main/404.tsx
  12. 39
      src/pages/main/Contact.tsx
  13. 103
      src/pages/main/Exp.tsx
  14. 2
      src/pages/main/Home.tsx
  15. 2
      src/pages/main/Projects.tsx
  16. 27
      src/util/index.ts
  17. 9
      vite.config.ts

33
package.json

@ -15,28 +15,25 @@
]
},
"dependencies": {
"@emotion/css": "^11.9.0",
"classnames": "^2.3.1",
"date-fns": "^2.28.0",
"framer-motion": "^6.3.15",
"gm": "^1.23.1",
"@emotion/css": "^11.11.2",
"date-fns": "^2.30.0",
"framer-motion": "^10.16.4",
"gm": "^1.25.0",
"imagen": "github:MKRhere/imagen",
"marked": "^4.0.17",
"marked": "^9.0.3",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.3.0"
"wouter": "^2.11.0"
},
"devDependencies": {
"@svgr/rollup": "^6.2.1",
"@types/gm": "^1.18.12",
"@types/marked": "^4.0.3",
"@types/node": "^18.0.0",
"@types/react": "^18.0.14",
"@types/react-dom": "^18.0.5",
"@types/react-router-dom": "^5.3.3",
"@vitejs/plugin-react": "^1.3.2",
"react-scripts": "5.0.1",
"typescript": "^4.7.4",
"vite": "^2.9.12"
"@svgr/rollup": "^8.1.0",
"@types/gm": "^1.25.2",
"@types/marked": "^5.0.2",
"@types/node": "^20.8.0",
"@types/react": "^18.2.24",
"@types/react-dom": "^18.2.8",
"@vitejs/plugin-react": "^4.1.0",
"typescript": "^5.2.2",
"vite": "^4.4.9"
}
}

11671
pnpm-lock.yaml

File diff suppressed because it is too large

5
src/blog.tsx

@ -1,15 +1,12 @@
import React from "react";
import { createRoot } from "react-dom/client";
import { BrowserRouter as Router } from "react-router-dom";
import "./index.css";
import BlogHome from "./pages/blog/Home";
createRoot(document.getElementById("root")!).render(
<React.StrictMode>
<Router>
<BlogHome />
</Router>
<BlogHome />
</React.StrictMode>,
);

211
src/components/Container.tsx

@ -1,35 +1,30 @@
import React, { useState, useEffect, useRef, useLayoutEffect } from "react";
import { css, cx } from "@emotion/css";
import { useNavigate } from "react-router-dom";
import useLocation from "wouter/use-location";
import { ReactComponent as Logo } from "../assets/logo.svg";
import { ReactComponent as Right } from "../assets/arrow-right.svg";
import { getTimeout } from "../util";
import Menu from "./Menu";
import { get, getTimeout } from "../util";
import Menu, { MenuEntries } from "./Menu";
import useMediaQuery from "../util/useMediaQuery";
const [timer, clear] = getTimeout();
const Container: React.FC<{
children: (string | React.DetailedReactHTMLElement<any, HTMLElement> | React.ReactElement)[];
children: (
| string
| React.DetailedReactHTMLElement<any, HTMLElement>
| React.ReactElement
)[];
hideNav?: boolean;
arrowReversed?: boolean;
next?: string;
className?: string;
}> = ({
children: _children,
hideNav = false,
arrowReversed = false,
next,
className,
...props
}) => {
const navigate = useNavigate();
}> = ({ children: _children, hideNav = false, className, ...props }) => {
const [location, navigate] = useLocation();
const mobile = useMediaQuery("(max-width: 50rem)");
const logoContainer = useRef<HTMLButtonElement>(null);
const highlightCircle = useRef<HTMLSpanElement>(null);
const highlightCircle = useRef<HTMLButtonElement>(null);
const containerChild = useRef<HTMLDivElement>(null);
const nextBtn = useRef<HTMLButtonElement>(null);
@ -37,11 +32,20 @@ const Container: React.FC<{
const children = React.Children.map(
_children,
(child: string | React.DetailedReactHTMLElement<any, HTMLElement> | React.ReactElement) =>
(
child:
| string
| React.DetailedReactHTMLElement<any, HTMLElement>
| React.ReactElement,
) =>
!child || typeof child === "string"
? child
: React.cloneElement(child, {
style: { opacity: 0, transform: "translateY(3rem)", transition: "all 300ms" },
style: {
opacity: 0,
transform: "translateY(3rem)",
transition: "all 300ms",
},
}),
);
@ -52,14 +56,19 @@ const Container: React.FC<{
if (highlightCircle.current) {
highlightCircle.current.classList.add("highlight");
timer(
() => highlightCircle.current && highlightCircle.current.classList.remove("highlight"),
() =>
highlightCircle.current &&
highlightCircle.current.classList.remove("highlight"),
1500,
);
}
if (nextBtn.current) {
nextBtn.current.style.width = "4rem";
timer(() => nextBtn.current && (nextBtn.current.style.right = "10vw"), 300);
timer(
() => nextBtn.current && (nextBtn.current.style.right = "10vw"),
300,
);
}
if (containerChild.current) {
@ -86,7 +95,9 @@ const Container: React.FC<{
const handleResize = () => {
if (containerChild.current && logoContainer.current)
logoContainer.current.style.left = `${containerChild.current.getBoundingClientRect().x}px`;
logoContainer.current.style.left = `${
containerChild.current.getBoundingClientRect().x
}px`;
};
useEffect(() => {
@ -96,33 +107,92 @@ const Container: React.FC<{
return () => window.removeEventListener("resize", handleResize);
}, []);
// on first render
useLayoutEffect(handleResize, []);
type MouseKb = React.MouseEvent | React.KeyboardEvent | KeyboardEvent;
const handleNext: React.MouseEventHandler<HTMLButtonElement> = e => {
const animateArrow = (e: MouseKb) => {
if (containerChild.current) {
([...containerChild.current.children] as (HTMLElement | SVGElement)[]).forEach(child => {
(
[...containerChild.current.children] as (HTMLElement | SVGElement)[]
).forEach(child => {
child.style.marginBottom = "2rem";
child.style.opacity = "0";
});
}
document.body.style.maxHeight = "100vh";
document.body.style.overflow = "hidden";
e.currentTarget.style.width = "0";
try {
const target = e.currentTarget! as HTMLButtonElement;
target.style.width = "0";
} catch {}
};
const current = MenuEntries.findIndex(([, path]) => location === path);
const next = get.next(MenuEntries, current)[1];
const prev = get.prev(MenuEntries, current)[1];
const end = current === MenuEntries.length - 1;
const handlePrev = (e: MouseKb) => {
animateArrow(e);
timer(() => prev && navigate(prev), 300);
};
const handleNext = (e: MouseKb) => {
animateArrow(e);
timer(() => next && navigate(next), 300);
};
function kbnav(e: KeyboardEvent) {
switch (e.key) {
case "ArrowLeft":
return handlePrev(e);
case "ArrowRight":
return handleNext(e);
}
}
useEffect(() => {
window.addEventListener("keydown", kbnav);
// cleanup
return () => window.removeEventListener("keydown", kbnav);
}, []);
// on first render
useLayoutEffect(handleResize, []);
return (
<div
className={css`
background: var(--background-colour);
padding: 15rem calc(100vw / 8) 8rem calc(100vw / 8);
padding-block-start: 15rem;
padding-block-end: 8rem;
padding-inline: calc(100vw / 8);
overflow-x: hidden;
min-height: 100vh;
position: relative;
`}>
<div
className={cx(
"fog",
css`
position: fixed;
width: 100vw;
left: 0;
bottom: 0;
height: 10rem;
background: rgb(0, 0, 0);
background: linear-gradient(
180deg,
rgba(0, 0, 0, 0) 0%,
rgba(0, 0, 0, 1) 100%
);
z-index: 1000;
pointer-events: none;
`,
)}
/>
{!hideNav && (
<button
<span
ref={logoContainer}
className={css`
position: absolute;
@ -134,7 +204,7 @@ const Container: React.FC<{
`}
onMouseOver={() => !mobile && setShowMenu(true)}
onMouseOut={() => !mobile && setShowMenu(false)}>
<span
<button
ref={highlightCircle}
className={cx(
css`
@ -143,7 +213,9 @@ const Container: React.FC<{
height: 5rem;
width: 5rem;
border-radius: 100%;
box-shadow: 0px 0px 50px 0px rgba(100, 100, 100, 0.65);
border: 0;
background: none;
box-shadow: 0 0 1rem 0 rgba(100, 100, 100, 0.5);
cursor: pointer;
& > svg {
@ -152,11 +224,13 @@ const Container: React.FC<{
position: absolute;
inset: 0;
z-index: 1;
outline: 0;
}
&::before {
content: "";
position: absolute;
top: 0.5rem;
left: -0.1rem;
width: 5rem;
height: 5rem;
@ -174,11 +248,13 @@ const Container: React.FC<{
opacity: 1;
}
&:hover::before {
&:hover::before,
&:focus::before {
width: 5.2rem;
height: 5.2rem;
top: -0.05rem;
top: -0.1rem;
left: -0.1rem;
outline: none;
}
&.highlight::before {
@ -193,49 +269,48 @@ const Container: React.FC<{
viewBox="0 0 264 264"
onClick={() => (mobile ? setShowMenu(true) : navigate("/"))}
/>
</span>
</button>
<Menu show={showMenu} setShowMenu={setShowMenu} />
</button>
</span>
)}
{next && (
<button
onClick={handleNext}
ref={nextBtn}
<button
onClick={handleNext}
ref={nextBtn}
title={end ? "Back to start" : "Next page"}
className={css`
position: fixed;
right: 14vw;
bottom: 10vh;
z-index: 500;
background: none;
padding: 0;
font-weight: 500;
cursor: pointer;
letter-spacing: 0.2rem;
border: none;
overflow: hidden;
width: 0;
transition: all 300ms;
overflow: hidden;
${end ? "rotate: 180deg;" : ""}
&:hover * {
fill: var(--primary-colour);
}
`}>
<Right
className={css`
position: fixed;
right: 14vw;
bottom: 10vh;
z-index: 500;
background: none;
padding: 0;
font-weight: 500;
cursor: pointer;
letter-spacing: 0.2rem;
border: none;
overflow: hidden;
width: 0;
transition: all 300ms;
overflow: hidden;
${arrowReversed ? "rotate: 180deg;" : ""}
&:hover * {
fill: var(--primary-colour);
}
`}>
<Right
className={css`
height: "2rem";
width: "2rem";
`}
/>
</button>
)}
height: "2rem";
width: "2rem";
`}
/>
</button>
<div
className={cx(
css`
width: 100%;
max-width: 60rem;
max-width: 62rem;
min-height: 100%;
margin: auto;

38
src/components/Menu.tsx

@ -1,16 +1,18 @@
import React from "react";
import { Link } from "react-router-dom";
import { css, cx } from "@emotion/css";
import { motion } from "framer-motion";
import RevealChildren from "./RevealChildren";
import useMediaQuery from "../util/useMediaQuery";
import { useNav } from "../util";
const menu = [
{ name: "Home", link: "/" },
{ name: "Experience", link: "/experience" },
{ name: "Projects", link: "/projects" },
{ name: "Contact", link: "/contact" },
];
export const MENU = {
Home: "/",
Experience: "/experience",
Projects: "/projects",
Contact: "/contact",
} as const;
export const MenuEntries = Object.entries(MENU);
const desktopNav = css`
float: right;
@ -44,6 +46,11 @@ const menuList = css`
& > li {
margin-left: 1rem;
}
& :focus-within {
opacity: 1 !important;
outline: none;
}
`;
const mobileMenu = css`
@ -57,18 +64,19 @@ const mobileMenu = css`
}
`;
const Menu: React.FC<{ show?: boolean; setShowMenu: (show: boolean) => void }> = ({
show = false,
setShowMenu,
}) => {
const Menu: React.FC<{
show?: boolean;
setShowMenu: (show: boolean) => void;
}> = ({ show = false, setShowMenu }) => {
const navigate = useNav();
// use same query as elsewhere for consistency
const mobile = useMediaQuery("(max-width: 50rem)");
const notmobile = !mobile;
const menuItems = menu.map(item => (
<Link key={item.link} to={item.link}>
{item.name}
</Link>
const menuItems = Object.entries(MENU).map(([name, link]) => (
<a key={link} onClick={navigate(link)} href={link}>
{name}
</a>
));
return (

19
src/components/Timeline.tsx

@ -2,7 +2,12 @@ import React, { useMemo } from "react";
import { css, cx } from "@emotion/css";
import { format, isBefore, startOfDay } from "date-fns";
export type TimelineUnit = { title?: string; url?: string; img?: string; date: string };
export type TimelineUnit = {
title?: string;
url?: string;
img?: string;
date: string;
};
export type TimelineUnits = TimelineUnit[];
const unit = css`
@ -15,7 +20,7 @@ const unit = css`
font-size: inherit;
text-align: inherit;
font-weight: inherit;
max-width: 400px;
max-width: 20rem;
transition: 100ms all;
text-decoration: none;
position: relative;
@ -82,7 +87,11 @@ const tlcontainer = css`
bottom: 0;
left: 0;
background: rgb(0, 0, 0);
background: linear-gradient(0deg, rgba(0, 0, 0, 1) 0%, rgba(0, 0, 0, 0) 100%);
background: linear-gradient(
0deg,
rgba(0, 0, 0, 1) 0%,
rgba(0, 0, 0, 0) 100%
);
}
`;
@ -107,7 +116,9 @@ const Unit: React.FC<{ contents: TimelineUnit }> = ({ contents }) => {
`,
)}
onClick={() =>
contents.url ? window.open(contents.url, "_blank", `noreferrer, noopener`) : ""
contents.url
? window.open(contents.url, "_blank", `noreferrer, noopener`)
: ""
}>
{contents.title ? <h4>{contents.title}</h4> : ""}
<h3>{format(date, "h:mm a")}</h3>

5
src/index.css

@ -5,7 +5,7 @@
--card-tags-hover: rgb(25, 25, 25);
--primary-colour: rgb(255, 85, 85);
--text-colour: rgb(211, 207, 201);
--text-subdued: rgb(163, 163, 163);
--text-subdued: rgb(150, 150, 150);
font-weight: 500;
font-size: max(16px, 0.8vw);
}
@ -77,6 +77,9 @@ h4 {
a {
color: var(--text-colour);
text-decoration: none;
font-family: Inter;
font-weight: 800;
transition: all 200ms;
}
a:hover {

48
src/index.tsx

@ -3,7 +3,7 @@ import React from "react";
import { createRoot } from "react-dom/client";
import "./index.css";
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import useLocation from "wouter/use-location";
import Home from "./pages/main/Home";
import Exp from "./pages/main/Exp";
@ -13,22 +13,40 @@ import Live from "./pages/main/Live";
import NotFound from "./pages/main/404";
import BlogHome from "./pages/blog/Home";
import { BlogPost } from "./pages/blog/components/BlogContent";
import { normalise } from "./util";
function App() {
const [location, navigate] = useLocation();
const normalised = normalise(location);
if (location !== normalised) {
navigate(normalised, { replace: true });
return null;
}
switch (normalised) {
case "/":
return <Home />;
case "/experience":
return <Exp />;
case "/projects":
return <Projects />;
case "/contact":
return <Contact />;
case "/live":
return <Live />;
case "/blog":
// return <BlogHome />;
default:
// if (location.startsWith("/blog")) return <BlogPost />;
return <NotFound />;
}
}
createRoot(document.getElementById("root")!).render(
<React.StrictMode>
<Router>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/experience" element={<Exp />} />
<Route path="/projects" element={<Projects />} />
<Route path="/contact" element={<Contact />} />
<Route path="/live" element={<Live />} />
{/* <Route path="/blog" element={<BlogHome />} /> */}
{/* <Route path="/blog/*" element={<BlogHome />} /> */}
<Route path="/*" element={<NotFound />} />
</Routes>
</Router>
<App />
</React.StrictMode>,
);

32
src/pages/blog/Home.tsx

@ -1,12 +1,12 @@
import { css } from "@emotion/css";
import { css, cx } from "@emotion/css";
import React, { useEffect, useState } from "react";
import { Link, useLocation, useNavigate } from "react-router-dom";
import useLocation from "wouter/use-location";
import { Spacer } from "../../components/Spacer";
import { ArticleSubHeader } from "./components/ArticleSubHeader";
import { BlogPost } from "./components/BlogContent";
import { articles, getBlogPath } from "../../data";
import { ReactComponent as DrawClose } from "../../assets/arrow-thin.svg";
import classNames from "classnames";
import { useNav } from "../../util";
const Header: React.FC = () => {
return (
@ -58,10 +58,10 @@ const Header: React.FC = () => {
};
const BlogHome: React.FC = () => {
const location = useLocation();
const navigate = useNavigate();
const [location] = useLocation();
const navigate = useNav();
const isArticleOpen = Boolean(location.pathname.split("/blog")[1]);
const isArticleOpen = Boolean(location.split("/blog")[1]);
const [isAsideClosed, setAsideClosed] = useState(isArticleOpen);
useEffect(() => {
@ -73,7 +73,7 @@ const BlogHome: React.FC = () => {
if (!isArticleOpen) return;
const handler = (e: KeyboardEvent) =>
e.key === "Escape" && navigate("/blog");
e.key === "Escape" && navigate("/blog")(e);
document.addEventListener("keydown", handler);
return () => document.removeEventListener("keydown", handler);
@ -84,7 +84,7 @@ const BlogHome: React.FC = () => {
<div
data-home
key="blog-home"
className={classNames(
className={cx(
{ "article-open": isArticleOpen, "aside-closed": isAsideClosed },
css`
display: flex;
@ -132,7 +132,7 @@ const BlogHome: React.FC = () => {
)}>
<button
onClick={() => setAsideClosed(closed => !closed)}
className={classNames(
className={cx(
"draw-ctl",
css`
border: none;
@ -160,7 +160,7 @@ const BlogHome: React.FC = () => {
)}>
<DrawClose
style={{ width: "2.6rem", height: "1rem" }}
className={classNames(
className={cx(
css`
transform: rotate(180deg);
transition: transform 100ms;
@ -182,7 +182,7 @@ const BlogHome: React.FC = () => {
overflow-y: auto;
`}>
<div
className={classNames(
className={cx(
"blog-list",
css`
width: 100%;
@ -206,9 +206,9 @@ const BlogHome: React.FC = () => {
const path = getBlogPath(article);
return (
<Link
<span
key={path}
to={path}
onClick={() => navigate(path)}
className={css`
display: flex;
flex-direction: column;
@ -234,13 +234,13 @@ const BlogHome: React.FC = () => {
<ArticleSubHeader article={article} />
<Spacer />
<p>{snippet}</p>
</Link>
</span>
);
})}
</div>
</aside>
<article
className={classNames(
className={cx(
{ "article-open": isArticleOpen },
css`
height: 100%;
@ -259,7 +259,7 @@ const BlogHome: React.FC = () => {
)}>
<div
key="blog-content"
className={classNames(
className={cx(
css`
background: #111111;
position: absolute;

6
src/pages/blog/components/BlogContent.tsx

@ -1,5 +1,5 @@
import React, { useEffect, useState } from "react";
import { useLocation } from "react-router-dom";
import useLocation from "wouter/use-location";
import { marked } from "marked";
import { Article, blog, getBlogPath, nextAndPrev } from "../../../data";
import "../../../blog.css";
@ -113,12 +113,12 @@ const btn = css`
export const BlogPost: React.FC = () => {
const navigate = useNav();
const location = useLocation();
const [location] = useLocation();
const [content, setContent] = useState("");
const [error, setError] = useState("");
const [loading, setLoading] = useState(true);
const [year, slug] = location.pathname.split("/").slice(-2);
const [year, slug] = location.split("/").slice(-2);
const article = blog[year]?.[slug];
const [next, prev] = nextAndPrev(year, slug);

10
src/pages/main/404.tsx

@ -1,16 +1,18 @@
import React from "react";
import { Link } from "react-router-dom";
import Container from "../../components/Container";
import { useNav } from "../../util";
function Home() {
const navigate = useNav();
return (
<Container>
<h1>Nothing here</h1>
<p>
404. Back to{" "}
<b>
<Link to="/">MKRhere?</Link>
</b>
<a href="/" onClick={navigate("/")}>
MKRhere?
</a>
</p>
</Container>
);

39
src/pages/main/Contact.tsx

@ -8,13 +8,17 @@ const A = css`
`;
type Contact = {
[k: string]: { value: string; link?: string; replacer?: Record<string, string> };
[k: string]: {
value: string;
link?: string;
replacer?: Record<string, string>;
};
};
const CONTACT: Contact = {
Twitter: { value: "MKRhere", link: "https://twitter.com/MKRhere" },
GitHub: { value: "MKRhere", link: "https://github.com/MKRhere" },
Email: {
"Twitter/𝕏": { value: "MKRhere", link: "https://twitter.com/MKRhere" },
"GitHub": { value: "MKRhere", link: "https://github.com/MKRhere" },
"Email": {
value: "mυthυkυmαr@thεfεαthεrs.in",
link: "mailto:mυthυkυmαr@thεfεαthεrs.in",
replacer: {
@ -23,7 +27,7 @@ const CONTACT: Contact = {
α: "a",
},
},
Phone: {
"Phone": {
value: "+9Ι Γ8Δ5 Γ9 8Δ88",
link: "tel:+91Γ8Δ5Γ98Δ88",
replacer: {
@ -73,8 +77,6 @@ const Home: React.FC = () => {
return (
<Container
next="/"
arrowReversed={true}
className={css`
min-height: 50vh;
display: flex;
@ -85,13 +87,28 @@ const Home: React.FC = () => {
className={css`
margin-top: auto;
display: flex;
flex-shrink: 1;
gap: 1rem;
ul {
padding: 0;
margin-left: 1rem;
display: flex;
flex-direction: column;
gap: 0.5rem;
max-width: 50vw;
li {
list-style: none;
min-width: 5rem;
max-width: 100%;
}
li a {
display: block;
max-width: 100%;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
}
`}>
@ -112,7 +129,11 @@ const Home: React.FC = () => {
return (
<li key={key}>
{value.link ? (
<a className={A} href={value.link} target="_blank" rel="noreferrer">
<a
className={A}
href={value.link}
target="_blank"
rel="noreferrer">
{value.value}
</a>
) : (

103
src/pages/main/Exp.tsx

@ -4,19 +4,32 @@ import Container from "../../components/Container";
const exp = [
{
title: "The Feathers",
location: "Chennai (formerly Tirunelveli and Ooty)",
position: "Founder",
year: "2011-19",
},
{
title: "StudioFlicks",
location: "Remote (Coimbatore)",
position: "Co-founder & Creative Head",
year: "2013-15",
},
{
title: "Vinzas",
location: "Chennai",
position: "Architectural Intern",
year: "2014",
},
{
title: "BlueCube",
title: "Blue Cube",
location: "Chennai",
position: "Architectural Intern",
year: "2015",
},
{
title: "OutFocus Magazine",
location: "Ooty",
position: "Editor / developer",
year: "2014-17",
},
@ -40,14 +53,14 @@ const exp = [
},
{
title: "Hugo's Way",
location: "Remote",
location: "Remote (Dublin)",
position: "Full stack developer",
year: "2018-19",
},
{
title: "Navana Tech",
location: "Remote",
position: "Lead web & architect",
location: "Remote (Mumbai)",
position: "Lead webdev & architect",
year: "2021-22",
},
{
@ -56,7 +69,7 @@ const exp = [
position: "Chief Maker",
year: "2019-present",
},
];
].reverse();
const Circle: React.FC = () => (
<div>
@ -67,7 +80,9 @@ const Circle: React.FC = () => (
background: #333333;
left: -50vw;
position: absolute;
top: calc(-2rem + 0.25rem / 2 - 0.5px);
top: calc(2rem + 0.25rem / 2);
/* centre it to the circle */
transform: translateY(-50%);
z-index: 0;
`}></div>
<div
@ -77,7 +92,7 @@ const Circle: React.FC = () => (
background: #ffffff;
border-radius: 100%;
position: absolute;
top: -2rem;
top: 2rem;
left: 0;
z-index: 100;
`}></div>
@ -96,22 +111,36 @@ const ExpUnit: React.FC<Experience> = ({ title, location, position, year }) => {
<div
className={css`
position: relative;
display: flex;
flex-direction: column;
gap: 0.6rem;
& * {
line-height: 1em;
}
& h5 {
color: var(--text-subdued);
font-weight: 400;
font-size: 0.9rem;
}
`}>
<Circle />
<h4>{[title, location].filter(Boolean).join(", ")}</h4>
<span
className={css`
color: #bdbdbd;
`}>
{position}
</span>
{" . "}
<span
className={css`
font-weight: 300;
`}>
{year}
</span>
<h4>{title}</h4>
<div>
<span
className={css`
color: var(--text-colour);
`}>
{position}
</span>
{" . "}
<span
className={css`
font-weight: 300;
`}>
{year}
</span>
</div>
<h5>{location}</h5>
</div>
);
};
@ -130,21 +159,33 @@ const age = getAge("27 May 1995");
const Exp: React.FC = () => {
return (
<Container next="/projects">
<h2>Im a {age} year old developer from Chennai, India.</h2>
<p>Here are some places Ive worked at in reverse chronological order:</p>
<Container>
<h2>
Im a {age} year old developer from
<br />
Chennai, India.
</h2>
<p>
Here are some places Ive worked at{" "}
<span
className={css`
/* font-size: 0.8rem; */
color: var(--text-subdued);
`}>
(recent first)
</span>
:
</p>
<div
className={css`
display: flex;
flex-direction: row-reverse;
width: 100%;
flex-wrap: wrap-reverse;
display: grid;
grid-template-columns: repeat(auto-fit, 20rem);
gap: 1rem;
& > * {
flex-basis: 15rem;
flex-grow: 1;
margin-top: 4rem;
margin-right: 3%;
padding-top: 4rem;
}
`}>
{exp.map(unit => (

2
src/pages/main/Home.tsx

@ -4,7 +4,7 @@ import Dashed from "../../components/Dashed";
const Home: React.FC = () => {
return (
<Container next="/experience">
<Container>
<h1>MKRhere</h1>
<p>
Web home of <Dashed>designer</Dashed>, <Dashed>developer</Dashed>, and{" "}

2
src/pages/main/Projects.tsx

@ -146,7 +146,7 @@ const ProjectUnit: React.FC<Project> = ({
const Exp: React.FC = () => {
return (
<Container next="/contact">
<Container>
<h2>What else have I built?</h2>
<p>Some tools, libraries, and apps over time:</p>
<div

27
src/util/index.ts

@ -1,5 +1,5 @@
import React from "react";
import { useNavigate } from "react-router-dom";
import useLocation from "wouter/use-location";
export const getTimeout = () => {
const clearables = new Set<number>();
@ -21,9 +21,10 @@ export const ellipses = (text: string, length: number = 100) =>
text.length > length ? text.slice(0, length - 3) + "..." : text;
export const useNav = () => {
const navigate = useNavigate();
const [, navigate] = useLocation();
return (link: string) => (e: React.MouseEvent) => {
return (link: string) => (e: React.MouseEvent | KeyboardEvent) => {
e?.preventDefault();
if (e.ctrlKey) return window.open(link, "_blank", "noreferrer noopener");
navigate(link);
};
@ -34,3 +35,23 @@ export function rewriteExtn(filename: string, extn: string) {
split[split.length - 1] = extn;
return split.join(".");
}
export function normalise(path: string) {
return (
(path.startsWith("/") ? "/" : "") +
path.trim().split("/").filter(Boolean).join("/")
);
}
export function comparePaths(p1: string, p2: string) {
return normalise(p1) === normalise(p2);
}
export const get = {
next<X>(xs: X[], i: number) {
return xs.at((i + 1) % xs.length)!;
},
prev<X>(xs: X[], i: number) {
return xs.at((i - 1) % xs.length)!;
},
};

9
vite.config.ts

@ -5,8 +5,13 @@ import svgr from "@svgr/rollup";
// https://vitejs.dev/config/
export default defineConfig({
server: { port: 3000 },
plugins: [react(), Object.assign(svgr({ ref: true, svgo: false }), { enforce: "pre" } as const)],
server: { port: 10000 },
plugins: [
react(),
Object.assign(svgr({ ref: true, svgo: false }), {
enforce: "pre",
} as const),
],
build: {
rollupOptions: {
input: {

Loading…
Cancel
Save