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

18
pnpm-lock.yaml

@ -14,15 +14,6 @@ importers:
date-fns:
specifier: ^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:
specifier: ^18.2.0
version: 18.2.0
@ -54,6 +45,15 @@ importers:
'@vitejs/plugin-react':
specifier: ^4.1.0
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:
specifier: ^5.12.0
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) {
if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) return;
switch (e.key) {
case "ArrowLeft":
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 { 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
const opaque = css`&& { opacity: 1 }`;
@ -11,6 +39,9 @@ const opaque = css`&& { opacity: 1 }`;
const halfVisible = css`&&& {opacity: 0.5}`;
// 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 tripleBlink = async (el: HTMLElement) => {
@ -30,12 +61,14 @@ const tripleBlink = async (el: HTMLElement) => {
el.classList.remove(halfVisible);
};
const Flicker: React.FC<{
children: React.ReactNode;
index: number;
description: string;
style?: React.CSSProperties;
}> = ({ children, index, description, style }) => {
export const Tooltip = forwardRef<
HTMLButtonElement,
{
children: React.ReactNode;
description: React.ReactNode;
style?: React.CSSProperties;
}
>(({ children, description, style }, ref) => {
return (
<span
style={style}
@ -43,45 +76,27 @@ const Flicker: React.FC<{
position: relative;
&&& button:focus ~ .tooltip,
&&& button:hover ~ .tooltip {
&&&:hover .tooltip {
opacity: 1;
pointer-events: all;
}
&&& button:focus,
&&& button:hover {
&&&:hover button {
opacity: 1;
}
`}>
<button
className={css`
border-bottom: 1px dashed var(--text-colour);
opacity: 0;
background-color: transparent;
border: none;
color: inherit;
position: relative;
font-size: 0.9rem;
font-size: inherit;
padding: 0;
`}
ref={async el => {
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);
}
}}>
ref={ref}>
{children}
</button>
<span
@ -90,22 +105,14 @@ const Flicker: React.FC<{
css`
/* tooltip */
position: absolute;
top: 150%;
top: 100%;
left: 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;
transition: opacity 0.2s;
user-select: none;
text-align: left;
pointer-events: none;
z-index: 999;
@media screen and (max-width: 65rem) {
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>
);
};
});
const FlickerList: React.FC<{
list: { text: string; description: string }[];
list: { text: string; description: React.ReactNode }[];
style?: React.CSSProperties;
}> = ({ list, style }) => {
className?: string;
}> = ({ list, style, className }) => {
return (
<ul
style={style}
className={css`
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
margin: 0;
padding: 0;
list-style: none;
&:has(> li button:focus) li button:not(:focus),
&:has(> li button:hover) li button:not(:hover) {
opacity: 0.5;
}
className={cx(
className,
css`
display: flex;
flex-wrap: wrap;
gap: 0.8rem;
margin: 0;
padding: 0;
list-style: none;
&: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 */
&&&& li:has(~ li button:focus) .tooltip,
/* any button that has a previous button focused, hide its tooltip */
&&&& li:has(button:focus) ~ li .tooltip {
opacity: 0;
}
`}>
/* any button that has a subsequent button focused, hide its tooltip */
&&&& li:has(~ li button:focus) .tooltip,
/* any button that has a previous button focused, hide its tooltip */
&&&& li:has(button:focus) ~ li .tooltip {
opacity: 0;
}
`,
)}>
{[
...intersperse(
list.map((item, index) => (
@ -156,9 +184,32 @@ const FlickerList: React.FC<{
className={css`
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}
</Flicker>
</Tooltip>
</li>
)),
index => <li key={index}>·</li>,

2
src/components/Menu.tsx

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

3
src/index.css

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

4
src/index.tsx

@ -6,7 +6,7 @@ import { normalise } from "./util";
const Home = lazy(() => import("./pages/main/Home"));
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 NotFound = lazy(() => import("./pages/main/404"));
@ -24,7 +24,7 @@ function App() {
if (normalised === "/") return <Home />;
if (normalised === "/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 === "/live") return <Live />;
// if (normalised === "/blog") return <BlogHome />;

177
src/pages/main/Home.tsx

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

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

Loading…
Cancel
Save