Compare commits

...

6 Commits

  1. 6
      package.json
  2. 18
      pnpm-lock.yaml
  3. BIN
      public/assets/emoji/2x.png
  4. BIN
      public/assets/emoji/nerd.png
  5. BIN
      public/assets/emoji/technologist.png
  6. BIN
      public/assets/serenity/U+1F1EE_U+1F1F3.png
  7. BIN
      public/assets/serenity/U+1F389.png
  8. 2
      src/components/Container.tsx
  9. 37
      src/components/Emoji.tsx
  10. 185
      src/components/FlickerList.tsx
  11. 2
      src/components/Menu.tsx
  12. 3
      src/index.css
  13. 4
      src/index.tsx
  14. 177
      src/pages/main/Home.tsx
  15. 0
      src/pages/main/Work.tsx

6
package.json

@ -17,9 +17,6 @@
"dependencies": { "dependencies": {
"@emotion/css": "^11.11.2", "@emotion/css": "^11.11.2",
"date-fns": "^2.30.0", "date-fns": "^2.30.0",
"gm": "^1.25.0",
"imagen": "github:MKRhere/imagen",
"marked": "^9.1.2",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"wouter": "^2.12.1" "wouter": "^2.12.1"
@ -32,6 +29,9 @@
"@types/react": "^18.2.30", "@types/react": "^18.2.30",
"@types/react-dom": "^18.2.14", "@types/react-dom": "^18.2.14",
"@vitejs/plugin-react": "^4.1.0", "@vitejs/plugin-react": "^4.1.0",
"gm": "^1.25.0",
"imagen": "github:MKRhere/imagen",
"marked": "^9.1.2",
"rollup-plugin-visualizer": "^5.12.0", "rollup-plugin-visualizer": "^5.12.0",
"typescript": "^5.2.2", "typescript": "^5.2.2",
"vite": "^4.5.0" "vite": "^4.5.0"

18
pnpm-lock.yaml

@ -14,15 +14,6 @@ importers:
date-fns: date-fns:
specifier: ^2.30.0 specifier: ^2.30.0
version: 2.30.0 version: 2.30.0
gm:
specifier: ^1.25.0
version: 1.25.0
imagen:
specifier: github:MKRhere/imagen
version: https://codeload.github.com/MKRhere/imagen/tar.gz/c9988aeddaf8e03a1b30293842b8a6e3e2065e62
marked:
specifier: ^9.1.2
version: 9.1.2
react: react:
specifier: ^18.2.0 specifier: ^18.2.0
version: 18.2.0 version: 18.2.0
@ -54,6 +45,15 @@ importers:
'@vitejs/plugin-react': '@vitejs/plugin-react':
specifier: ^4.1.0 specifier: ^4.1.0
version: 4.1.0(vite@4.5.0(@types/node@20.8.7)) version: 4.1.0(vite@4.5.0(@types/node@20.8.7))
gm:
specifier: ^1.25.0
version: 1.25.0
imagen:
specifier: github:MKRhere/imagen
version: https://codeload.github.com/MKRhere/imagen/tar.gz/c9988aeddaf8e03a1b30293842b8a6e3e2065e62
marked:
specifier: ^9.1.2
version: 9.1.2
rollup-plugin-visualizer: rollup-plugin-visualizer:
specifier: ^5.12.0 specifier: ^5.12.0
version: 5.12.0(rollup@3.29.4) version: 5.12.0(rollup@3.29.4)

BIN
public/assets/emoji/2x.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 211 B

BIN
public/assets/emoji/nerd.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

BIN
public/assets/emoji/technologist.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
public/assets/serenity/U+1F1EE_U+1F1F3.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 B

BIN
public/assets/serenity/U+1F389.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 141 B

2
src/components/Container.tsx

@ -140,6 +140,8 @@ const Container: React.FC<{
}; };
function kbnav(e: KeyboardEvent) { function kbnav(e: KeyboardEvent) {
if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) return;
switch (e.key) { switch (e.key) {
case "ArrowLeft": case "ArrowLeft":
return handlePrev(e); return handlePrev(e);

37
src/components/Emoji.tsx

@ -0,0 +1,37 @@
import React from "react";
import { css, cx } from "@emotion/css";
const EmojiMap = {
"2x": "emoji/2x",
"nerd": "emoji/nerd",
"technologist": "emoji/technologist",
"wink": "emoji/wink",
"ind": "serenity/U+1F1EE_U+1F1F3",
"tada": "serenity/U+1F389",
};
export const Emoji = ({
emoji,
baseline,
}: {
emoji: keyof typeof EmojiMap;
baseline?: boolean;
}) => (
<img
className={cx(
"emoji",
css`
image-rendering: pixelated;
vertical-align: middle;
height: 1.2em;
&.baseline {
vertical-align: baseline;
}
`,
{ baseline },
)}
src={`/assets/${EmojiMap[emoji]}.png`}
/>
);

185
src/components/FlickerList.tsx

@ -1,8 +1,36 @@
import React from "react"; import React, { forwardRef } from "react";
import { css, cx } from "@emotion/css"; import { css, cx } from "@emotion/css";
import { intersperse, sleep } from "../util"; import { intersperse, sleep } from "../util";
// && is used to increase specificity to override global styles // && is a hack to increase specificity, NEVER try to understand this
// if you need to increase specificity just add more &&
// no, actually don't do this, this is a bad practice
// but I'm doing it here because YOLO
// see if I care, I'm a bad person
// psst
// hey
// if you're reading this and you think this is a good idea
// you're a bad person too
// don't do this
// this is bad
//
// are you still reading this?
// why are you still reading this?
// this is a bad idea
// stop reading this
// go do something else
// like, anything else
// literally anything else
// why are you still reading this
// stop
//
// Wait, can you fix this?
// Please?
// I'm sorry
// Please send help
// Send PRs
//
// I hope these comments are removed by the minifier
// prettier-ignore // prettier-ignore
const opaque = css`&& { opacity: 1 }`; const opaque = css`&& { opacity: 1 }`;
@ -11,6 +39,9 @@ const opaque = css`&& { opacity: 1 }`;
const halfVisible = css`&&& {opacity: 0.5}`; const halfVisible = css`&&& {opacity: 0.5}`;
// prettier-ignore // prettier-ignore
const invisible = css`& { opacity: 0 }`;
// prettier-ignore
const opacities = [0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45].map(each => css`&&& { opacity: ${0.5 + each} }`); const opacities = [0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45].map(each => css`&&& { opacity: ${0.5 + each} }`);
const tripleBlink = async (el: HTMLElement) => { const tripleBlink = async (el: HTMLElement) => {
@ -30,12 +61,14 @@ const tripleBlink = async (el: HTMLElement) => {
el.classList.remove(halfVisible); el.classList.remove(halfVisible);
}; };
const Flicker: React.FC<{ export const Tooltip = forwardRef<
children: React.ReactNode; HTMLButtonElement,
index: number; {
description: string; children: React.ReactNode;
style?: React.CSSProperties; description: React.ReactNode;
}> = ({ children, index, description, style }) => { style?: React.CSSProperties;
}
>(({ children, description, style }, ref) => {
return ( return (
<span <span
style={style} style={style}
@ -43,45 +76,27 @@ const Flicker: React.FC<{
position: relative; position: relative;
&&& button:focus ~ .tooltip, &&& button:focus ~ .tooltip,
&&& button:hover ~ .tooltip { &&&:hover .tooltip {
opacity: 1; opacity: 1;
pointer-events: all;
} }
&&& button:focus, &&& button:focus,
&&& button:hover { &&&:hover button {
opacity: 1; opacity: 1;
} }
`}> `}>
<button <button
className={css` className={css`
border-bottom: 1px dashed var(--text-colour); border-bottom: 1px dashed var(--text-colour);
opacity: 0;
background-color: transparent; background-color: transparent;
border: none; border: none;
color: inherit; color: inherit;
position: relative; position: relative;
font-size: 0.9rem; font-size: inherit;
padding: 0;
`} `}
ref={async el => { ref={ref}>
if (!el) return;
await sleep(500);
await sleep(300 * index);
el.classList.add(opaque);
await sleep(1000 + Math.random() * 1000);
tripleBlink(el);
while (true) {
await sleep(5000 + Math.random() * 10000);
const chosen =
opacities[Math.floor(Math.random() * opacities.length)];
el.classList.add(chosen);
await sleep(2000);
el.classList.remove(chosen);
tripleBlink(el);
}
}}>
{children} {children}
</button> </button>
<span <span
@ -90,22 +105,14 @@ const Flicker: React.FC<{
css` css`
/* tooltip */ /* tooltip */
position: absolute; position: absolute;
top: 150%; top: 100%;
left: 50%; left: 50%;
transform: translateX(-50%); transform: translateX(-50%);
background: var(--card-tags);
color: var(--text-colour);
border-radius: 0.5rem;
padding: 0.5rem 0.8rem;
font-size: 0.8rem;
min-width: 20rem;
width: fit-content;
max-width: 80vw;
opacity: 0; opacity: 0;
transition: opacity 0.2s; transition: opacity 0.2s;
user-select: none; user-select: none;
text-align: left;
pointer-events: none; pointer-events: none;
z-index: 999;
@media screen and (max-width: 65rem) { @media screen and (max-width: 65rem) {
position: fixed; position: fixed;
@ -115,39 +122,60 @@ const Flicker: React.FC<{
} }
`, `,
)}> )}>
{description} <span
className={css`
margin: 0.5rem;
display: block;
min-width: 20rem;
width: fit-content;
max-width: 80vw;
background: var(--card-tags);
color: var(--text-colour);
border-radius: 0.5rem;
padding: 0.5rem 0.8rem;
font-size: 0.8rem;
text-align: left;
line-height: 1.4;
`}>
{description}
</span>
</span> </span>
</span> </span>
); );
}; });
const FlickerList: React.FC<{ const FlickerList: React.FC<{
list: { text: string; description: string }[]; list: { text: string; description: React.ReactNode }[];
style?: React.CSSProperties; style?: React.CSSProperties;
}> = ({ list, style }) => { className?: string;
}> = ({ list, style, className }) => {
return ( return (
<ul <ul
style={style} style={style}
className={css` className={cx(
display: flex; className,
flex-wrap: wrap; css`
gap: 0.5rem; display: flex;
margin: 0; flex-wrap: wrap;
padding: 0; gap: 0.8rem;
list-style: none; margin: 0;
padding: 0;
&:has(> li button:focus) li button:not(:focus), list-style: none;
&:has(> li button:hover) li button:not(:hover) {
opacity: 0.5; &:has(:focus) li button:not(:focus),
} &:has(:hover) li button:not(:hover) {
opacity: 0.5;
}
/* any button that has a subsequent button focused, hide its tooltip */ /* any button that has a subsequent button focused, hide its tooltip */
&&&& li:has(~ li button:focus) .tooltip, &&&& li:has(~ li button:focus) .tooltip,
/* any button that has a previous button focused, hide its tooltip */ /* any button that has a previous button focused, hide its tooltip */
&&&& li:has(button:focus) ~ li .tooltip { &&&& li:has(button:focus) ~ li .tooltip {
opacity: 0; opacity: 0;
} }
`}> `,
)}>
{[ {[
...intersperse( ...intersperse(
list.map((item, index) => ( list.map((item, index) => (
@ -156,9 +184,32 @@ const FlickerList: React.FC<{
className={css` className={css`
display: inline-block; display: inline-block;
`}> `}>
<Flicker index={index} description={item.description}> <Tooltip
description={item.description}
ref={async el => {
if (!el) return;
el.classList.add(invisible);
await sleep(500);
await sleep(300 * index);
el.classList.add(opaque);
await sleep(1000 + Math.random() * 1000);
tripleBlink(el);
while (true) {
await sleep(5000 + Math.random() * 10000);
const chosen =
opacities[Math.floor(Math.random() * opacities.length)];
el.classList.add(chosen);
await sleep(2000);
el.classList.remove(chosen);
tripleBlink(el);
}
}}>
{item.text} {item.text}
</Flicker> </Tooltip>
</li> </li>
)), )),
index => <li key={index}>·</li>, index => <li key={index}>·</li>,

2
src/components/Menu.tsx

@ -7,7 +7,7 @@ import { useNav } from "../util";
export const MENU = { export const MENU = {
Home: "/", Home: "/",
Experience: "/experience", Experience: "/experience",
Projects: "/projects", Work: "/work",
Contact: "/contact", Contact: "/contact",
} as const; } as const;

3
src/index.css

@ -83,7 +83,8 @@ h4 {
font-size: min(5vw, 1.2rem); font-size: min(5vw, 1.2rem);
} }
a { a,
b {
color: var(--text-colour); color: var(--text-colour);
text-decoration: none; text-decoration: none;
font-weight: 800; font-weight: 800;

4
src/index.tsx

@ -6,7 +6,7 @@ import { normalise } from "./util";
const Home = lazy(() => import("./pages/main/Home")); const Home = lazy(() => import("./pages/main/Home"));
const Exp = lazy(() => import("./pages/main/Exp")); const Exp = lazy(() => import("./pages/main/Exp"));
const Projects = lazy(() => import("./pages/main/Projects")); const Work = lazy(() => import("./pages/main/Work"));
const Contact = lazy(() => import("./pages/main/Contact")); const Contact = lazy(() => import("./pages/main/Contact"));
const NotFound = lazy(() => import("./pages/main/404")); const NotFound = lazy(() => import("./pages/main/404"));
@ -24,7 +24,7 @@ function App() {
if (normalised === "/") return <Home />; if (normalised === "/") return <Home />;
if (normalised === "/experience") return <Exp />; if (normalised === "/experience") return <Exp />;
if (normalised.startsWith("/experience/")) return <Exp />; if (normalised.startsWith("/experience/")) return <Exp />;
if (normalised === "/projects") return <Projects />; if (normalised === "/work") return <Work />;
if (normalised === "/contact") return <Contact />; if (normalised === "/contact") return <Contact />;
// if (normalised === "/live") return <Live />; // if (normalised === "/live") return <Live />;
// if (normalised === "/blog") return <BlogHome />; // if (normalised === "/blog") return <BlogHome />;

177
src/pages/main/Home.tsx

@ -1,16 +1,10 @@
import React from "react"; import React from "react";
import Container from "../../components/Container"; import Container from "../../components/Container";
import FlickerList from "../../components/FlickerList"; import FlickerList, { Tooltip } from "../../components/FlickerList";
import { ReactComponent as Arrow } from "../../assets/arrow-thin.svg"; import { ReactComponent as Arrow } from "../../assets/arrow-thin.svg";
import { css, cx } from "@emotion/css"; import { css } from "@emotion/css";
import { setupCursorTracking } from "../../util"; import { setupCursorTracking } from "../../util";
import { Emoji } from "../../components/Emoji";
const section = css`
display: flex;
flex-direction: column;
gap: 1rem;
justify-content: center;
`;
const Home: React.FC = () => { const Home: React.FC = () => {
return ( return (
@ -18,32 +12,50 @@ const Home: React.FC = () => {
className={css` className={css`
--distance: 2rem; --distance: 2rem;
`}> `}>
<section <section>
style={{ <h1
// fiddle style={{
marginLeft: "-0.3rem", // fiddle
}}> marginLeft: "-0.31rem",
<h1>MKRhere</h1> }}>
MKRhere
</h1>
<FlickerList <FlickerList
style={{ style={{
// fiddle // fiddle
marginTop: "calc(-1.7rem - 2px + var(--distance))", marginTop: "calc(-1.7rem - 2px + var(--distance))",
marginLeft: "-0.1rem",
fontSize: "0.9rem",
}} }}
list={[ list={[
{ {
text: "Designer", text: "Designer",
description: description: (
"Graphic design is my passion 🤓 I have plenty of experience with Figma and Adobe Suite tools (especially Photoshop and InDesign)", <>
Graphic design is my passion <Emoji emoji="nerd" /> I have
plenty of experience with Figma and Adobe Suite tools
(especially Photoshop and InDesign)
</>
),
}, },
{ {
text: "Developer", text: "Developer",
description: description: (
"🧑🏻‍💻 I started developing websites in 2015, and in 2017 I joined The Devs Network, catapulting my growth as a full-time developer", <>
<Emoji emoji="technologist" /> I started developing websites
in 2015, and in 2017 I joined The Devs Network, catapulting my
growth as a full-time developer
</>
),
}, },
{ {
text: "Architect", text: "Architect",
description: description: (
"I have a formal degree in architecture! I'm an architect in both construction and software 😉", <>
I have a formal degree in architecture! I'm an architect in
both construction and software <Emoji emoji="2x" />
</>
),
}, },
]} ]}
/> />
@ -51,74 +63,95 @@ const Home: React.FC = () => {
<main <main
className={css` className={css`
/* fiddle */ /* fiddle */
margin-top: calc(-2.2rem + var(--distance)); margin-top: calc(-2.4rem + var(--distance));
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
gap: var(--distance); gap: var(--distance);
& img {
image-rendering: pixelated;
vertical-align: middle;
&.emoji {
margin-left: 0.4em;
}
}
`}> `}>
<img <img
src="/assets/mkr-in-pixels.png" src="/assets/mkr-in-pixels.png"
alt="MKR in pixels" alt="MKR in pixels"
style={{ style={{
imageRendering: "pixelated",
height: "8rem", height: "8rem",
aspectRatio: "1", aspectRatio: "1",
}} }}
/> />
<article <article
style={{ className={css`
// fiddle /* fiddle */
marginTop: "-0.4rem", margin-top: -0.4rem;
}}> display: flex;
<section flex-direction: column;
className={cx( `}>
section, <p>
css` Welcome to the web home of{" "}
gap: 0.2rem; <Tooltip
`, description={
)}> <>
<p> For legal reasons, my name is <i>Anu Rahul Nandhan</i>, but I
Welcome to the web home of <b>Anu Rahul Nandhan.</b> generally go by my{" "}
</p> <a
<p> href="https://en.wiktionary.org/wiki/nom_de_guerre"
I'm also commonly known as <b>Muthu Kumar</b>. target="_blank"
</p> rel="noreferrer">
</section> nom-de-guerre
<section className={section}> </a>{" "}
<p>I'm looking for work! 🎉</p> Muthu Kumar.
<button </>
className={css` }>
background: var(--card-tags); <b>Muthu Kumar</b>.
border: 0; </Tooltip>
border-radius: 0.5rem; </p>
width: fit-content; <p>
color: var(--text-colour); I'm from Chennai, South India
cursor: pointer; <Emoji emoji="ind" />
font-size: 1rem; </p>
<p>
I'm looking for work!
<Emoji emoji="tada" baseline />
</p>
<button
className={css`
background: var(--card-tags);
border: 0;
border-radius: 0.5rem;
width: fit-content;
color: var(--text-colour);
cursor: pointer;
font-size: 1rem;
margin-top: 0.4rem;
position: relative; position: relative;
z-index: 0; z-index: 0;
& a { & a {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 1rem; gap: 1rem;
padding: 1rem 2rem; padding: 1rem 2rem;
} }
& a:hover { & a:hover {
color: inherit; color: inherit;
} }
`} `}
ref={setupCursorTracking}> ref={setupCursorTracking}>
<div className="dynamic-gradient" /> <div className="dynamic-gradient" />
<a href="https://mkr.pw/resume" target="_blank"> <a href="https://mkr.pw/resume" target="_blank">
Download Resume Download Resume
<Arrow /> <Arrow />
</a> </a>
</button> </button>
</section>
</article> </article>
</main> </main>
</Container> </Container>

0
src/pages/main/Projects.tsx → src/pages/main/Work.tsx

Loading…
Cancel
Save