From d30c4015a77489f217fd5379f1dbd262e05403dd Mon Sep 17 00:00:00 2001 From: Carsten Kragelund Date: Fri, 19 May 2023 14:04:12 +0200 Subject: [PATCH] Initial Website --- next.config.js | 6 +- package-lock.json | 62 +++ package.json | 3 + src/app/TODO.md | 6 + src/app/components/LinkBubble.tsx | 13 + src/app/components/ShowcaseCard.tsx | 18 + src/app/layout.tsx | 6 +- src/app/page.tsx | 638 +++++++++++++++++++++++----- tailwind.config.js | 74 +++- 9 files changed, 714 insertions(+), 112 deletions(-) create mode 100644 src/app/TODO.md create mode 100644 src/app/components/LinkBubble.tsx create mode 100644 src/app/components/ShowcaseCard.tsx diff --git a/next.config.js b/next.config.js index 767719f..a35bfad 100644 --- a/next.config.js +++ b/next.config.js @@ -1,4 +1,6 @@ /** @type {import('next').NextConfig} */ -const nextConfig = {} +const nextConfig = { + output: "export", +}; -module.exports = nextConfig +module.exports = nextConfig; diff --git a/package-lock.json b/package-lock.json index 851138a..1ff5861 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,17 +8,20 @@ "name": "portfolio", "version": "0.1.0", "dependencies": { + "@tanstack/react-query": "^4.29.5", "@types/node": "20.1.4", "@types/react": "18.2.6", "@types/react-dom": "18.2.4", "autoprefixer": "10.4.14", "eslint": "8.40.0", "eslint-config-next": "13.4.2", + "moment": "^2.29.4", "next": "13.4.2", "postcss": "8.4.23", "react": "18.2.0", "react-dom": "18.2.0", "tailwindcss": "3.3.2", + "tailwindcss-animate": "^1.0.5", "typescript": "5.0.4" } }, @@ -386,6 +389,41 @@ "tslib": "^2.4.0" } }, + "node_modules/@tanstack/query-core": { + "version": "4.29.5", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-4.29.5.tgz", + "integrity": "sha512-xXIiyQ/4r9KfaJ3k6kejqcaqFXXBTzN2aOJ5H1J6aTJE9hl/nbgAdfF6oiIu0CD5xowejJEJ6bBg8TO7BN4NuQ==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/react-query": { + "version": "4.29.5", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-4.29.5.tgz", + "integrity": "sha512-F87cibC3s3eG0Q90g2O+hqntpCrudKFnR8P24qkH9uccEhXErnJxBC/AAI4cJRV2bfMO8IeGZQYf3WyYgmSg0w==", + "dependencies": { + "@tanstack/query-core": "4.29.5", + "use-sync-external-store": "^1.2.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-native": "*" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + } + } + }, "node_modules/@types/json5": { "version": "0.0.29", "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", @@ -2771,6 +2809,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/moment": { + "version": "2.29.4", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", + "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==", + "engines": { + "node": "*" + } + }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -3960,6 +4006,14 @@ "node": ">=14.0.0" } }, + "node_modules/tailwindcss-animate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tailwindcss-animate/-/tailwindcss-animate-1.0.5.tgz", + "integrity": "sha512-UU3qrOJ4lFQABY+MVADmBm+0KW3xZyhMdRvejwtXqYOL7YjHYxmuREFAZdmVG5LPe5E9CAst846SLC4j5I3dcw==", + "peerDependencies": { + "tailwindcss": ">=3.0.0 || insiders" + } + }, "node_modules/tapable": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", @@ -4160,6 +4214,14 @@ "punycode": "^2.1.0" } }, + "node_modules/use-sync-external-store": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", + "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", diff --git a/package.json b/package.json index a194af6..a2a6cad 100644 --- a/package.json +++ b/package.json @@ -9,17 +9,20 @@ "lint": "next lint" }, "dependencies": { + "@tanstack/react-query": "^4.29.5", "@types/node": "20.1.4", "@types/react": "18.2.6", "@types/react-dom": "18.2.4", "autoprefixer": "10.4.14", "eslint": "8.40.0", "eslint-config-next": "13.4.2", + "moment": "^2.29.4", "next": "13.4.2", "postcss": "8.4.23", "react": "18.2.0", "react-dom": "18.2.0", "tailwindcss": "3.3.2", + "tailwindcss-animate": "^1.0.5", "typescript": "5.0.4" } } diff --git a/src/app/TODO.md b/src/app/TODO.md new file mode 100644 index 0000000..29f334c --- /dev/null +++ b/src/app/TODO.md @@ -0,0 +1,6 @@ +TODO: more equal ratio of content to card +TODO: apple tiles summary (figma community) +TODO: maybe bigger fonts +TODO: smaller gap +TODO: more opacity on glow, maybe just get rid of it +TODO: services in dot matrix (maybe color code for the legends) diff --git a/src/app/components/LinkBubble.tsx b/src/app/components/LinkBubble.tsx new file mode 100644 index 0000000..2f3b70f --- /dev/null +++ b/src/app/components/LinkBubble.tsx @@ -0,0 +1,13 @@ +import { HTMLAttributes } from "react"; + +export const LinkBubble: React.FC & { href: string }> = ({ + className, + children, + href +}) => { + return ( + + {children} + + ); +}; diff --git a/src/app/components/ShowcaseCard.tsx b/src/app/components/ShowcaseCard.tsx new file mode 100644 index 0000000..8a2e7a9 --- /dev/null +++ b/src/app/components/ShowcaseCard.tsx @@ -0,0 +1,18 @@ +import { HTMLAttributes, ReactNode } from "react"; + +export const ShowcaseCard: React.FC< + HTMLAttributes & { bg: string; alt: string } +> = ({ className, children, bg, alt }) => { + return ( +
+
+ {children} +
+ {alt} +
+ ); +}; diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 71b3fbf..a166ff3 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -15,7 +15,9 @@ export default function RootLayout({ }) { return ( - {children} + + {children} + - ) + ); } diff --git a/src/app/page.tsx b/src/app/page.tsx index 7e80296..0353424 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,113 +1,551 @@ -import Image from 'next/image' +import React, { HTMLAttributes } from "react"; +import { LinkBubble } from "./components/LinkBubble"; +import { ShowcaseCard } from "./components/ShowcaseCard"; -export default function Home() { - return ( -
-
-

- Get started by editing  - src/app/page.tsx -

- -
+type Rows = [1, 2, 3, 4, 5, 6, 7, 8, 9][number]; +type Columns = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13][number]; +type OpacitySteps = [ + 0, + 5, + 10, + 20, + 25, + 30, + 40, + 50, + 60, + 70, + 75, + 80, + 90, + 95, + 100 +][number]; +type OpacityClass = `opacity-${OpacitySteps}` | "none"; + +export type CardContent = React.FC< + { styles: string } & HTMLAttributes & T +>; +type Card = { + row: `row-start-${Rows} row-end-${Rows}`; + col: `col-start-${Columns} col-end-${Columns}`; + style?: string; + content: CardContent; +}; + +function SimpleSpan(text: string): CardContent { + return function simpleSpan({ className, styles }) { + return
{text}
; + }; +} -
- Next.js Logo +function Glow(child: JSX.Element, opacity?: OpacityClass): CardContent { + return function glow({ className, styles }) { + return ( +
+
+
{child}
+ ); + }; +} -
- -

- Docs{' '} - - -> - -

-

- Find in-depth information about Next.js features and API. -

+function infocard( + title: string, + binder: string, + subject: string, + subject_link: string +) { + return ( +
+ ); +} - -

- Learn{' '} - - -> - -

-

- Learn about Next.js in an interactive course with quizzes! -

-
+const aboutMe = ` +

Greetings! I'm Carsten. I'm a software engineer who loves diving deep into +whatever I end up hacking on. My language of choice is Rust, and I'm using Nix +to configure my system. My editors of choice are Emacs and CLion.

+

At Catppuccin I maintain the Emacs port.

+

When I'm not coding, you can probably find me listening +to music, running or reading a book, paticularly nordic noir.

+` + .replaceAll("\n", " ") + .trim(); - { + return ( +
+
+

+ Services on the Init System +

+

+ 🍵{" "} + + Gitea + {" "} + ❄ +

+

+ 🪄{" "} + + Hocus + {" "} + 🚧 +

+

+ ✂{" "} + + Snip.sh + {" "} + 🚧 +

+

+ 🍽{" "} + + Mealie + {" "} + 🚧 +

+

+ 📄{" "} + + Stirling + {" "} + 🚧 +

+

+ 👨‍🍳{" "} + + CyberChef + {" "} + ❄ +

+

+ 👾{" "} + + EmulatorJS + {" "} + 🚧 +

+

+ 🏠{" "} + + Homepage (This Page) + {" "} + ❄ +

+

+ 📜{" "} + + PSNR License + {" "} + 🚧 +

+

+ 📷{" "} + + Immich + {" "} + 🛑 +

+

+ 🎞{" "} + + Jellyfin + {" "} + 🛑 +

+

+ 🖼{" "} + + Imgproxy + {" "} + 🚧 +

+

+ 📦{" "} + + Minio + {" "} + 🛑 +

+
+
+

Glossary

+

❄: Managed by Nix

+

🐳: Managed with Docker

+

🚧: Under Construction

+

🛑: Private Use Only

+
+
+ ); + }, + }, + name: { + row: "row-start-1 row-end-2", + col: "col-start-10 col-end-13", + style: "text-3xl font-semibold flex items-center", + content: SimpleSpan("Carsten Kragelund (NyxKrage)"), + }, + commit: { + row: "row-start-2 row-end-5", + col: "col-start-10 col-end-13", + style: + "bg-300 animate-bg-scroll bg-gradient-to-tr from-sky-300 via-violet-300 to-pink-300 dark:bg-pink-700 flex flex-col justify-around", + content: Glow(Loading latest commit, "none"), + }, + links: { + row: "row-start-5 row-end-6", + col: "col-start-10 col-end-13", + style: "flex justify-between px-12 py-4", + content: ({ styles, className }) => ( + + ), + }, + showcase: { + row: "row-start-6 row-end-9", + col: "col-start-1 col-end-8", + content: ({ styles, className }) => { + return ( +
+ + + Catppuccin/Emacs - -

- Instantly deploy your Next.js site to a shareable URL with Vercel. -

-
+ + Soothing pastel colors for Emacs + +
+ + ⭐ 135 + + + + + Emacs-OS + +
+ + Emacs running as pid1 on modern linux + +
+ + ⭐ 35 + +
+ + + Sanctureplicum + +
+ + Dotfiles using nix with flakes + +
+ + ⭐ 3 + +
+ + + See more... + +
+ + More projects on{" "} + + my gitea instance + {" "} + &{" "} + + my github profile + + +
+
+
+ ); + }, + }, + about: { + row: "row-start-6 row-end-9", + col: "col-start-8 col-end-13", + style: "text-xl", + content: ({ styles, className }) => { + return ( +
+

About me

+
+
+ ); + }, + }, +}; + +import moment from "moment"; + +async function generateCommitCard() { + const data = await fetch("https://api.github.com/users/nyxkrage/events").then( + (res) => res.json() + ); + let commit = (data as any[]).find((v) => v.type === "PushEvent"); + let commit_repo = await fetch(commit.repo.url).then((res) => res.json()); + if (commit_repo.fork) { + commit.repo.name = commit_repo.parent.full_name; + } + const pr = (data as any[]).find( + (v) => v.type === "PullRequestEvent" && v.payload.action === "opened" + ); + return Glow( + <> +
+

Latest Commit

+ + + {commit.payload.commits[0].message} + {" "} + on{" "} + + {commit.repo.name} + +
+ + {moment(new Date(commit.created_at)).fromNow()} + +
+
+
+

Latest PR

+ + + {pr.payload.pull_request.title} + {" "} + on{" "} + + {pr.repo.name} + +
+ + {moment(new Date(pr.created_at)).fromNow()} + +
+ , + "none" + ); +} + +function cardToElement(card: Card, key?: string) { + let commonCardStyles = "rounded-4xl"; + let commonCardClass = "w-full h-full"; + return ( + + ); +} + +export default async function Home() { + if (cards.commit && !Array.isArray(cards.commit)) { + cards.commit.content = await generateCommitCard(); + } + + return ( +
+ {Object.entries(cards).flatMap(([k, v]) => { + if (Array.isArray(v)) { + return v.map((c, i) => { + return cardToElement(c, `${k}-${i}`); + }); + } else { + return cardToElement(v, k); + } + })}
- ) + ); } diff --git a/tailwind.config.js b/tailwind.config.js index d53b2ea..353b5eb 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -1,18 +1,76 @@ /** @type {import('tailwindcss').Config} */ module.exports = { content: [ - './src/pages/**/*.{js,ts,jsx,tsx,mdx}', - './src/components/**/*.{js,ts,jsx,tsx,mdx}', - './src/app/**/*.{js,ts,jsx,tsx,mdx}', + "./src/pages/**/*.{js,ts,jsx,tsx,mdx}", + "./src/components/**/*.{js,ts,jsx,tsx,mdx}", + "./src/app/**/*.{js,ts,jsx,tsx,mdx}", ], + darkMode: "class", theme: { extend: { + colors: { + "white-base": "#eff1f5", + "white-mantle": "#e6e9ef", + "white-crust": "#dce0e8", + "black-base": "#1e1e2e", + "black-mantle": "#181825", + "black-crust": "#11111b", + }, backgroundImage: { - 'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))', - 'gradient-conic': - 'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))', + "gradient-radial": "radial-gradient(var(--tw-gradient-stops))", + "gradient-conic": + "conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))", + }, + gridTemplateRows: { + 8: "repeat(8, minmax(0, 1fr))", + }, + gridRow: { + "span-7": "span 7 / span 7", + }, + gridRowStart: { + 7: "7", + 8: "8", + }, + gridRowEnd: { + 7: "7", + 8: "8", + 9: "9", + }, + gridColumn: { + "span-13": "span 13 / span 13", + }, + gridColumnEnd: { + 13: "13", + }, + borderRadius: { + "4xl": "2rem", + }, + backgroundSize: { + 300: "300%", + 400: "400%", + 500: "500%", + 600: "600%", + }, + animation: { + "bg-scroll": "bgscroll 10s linear infinite", + }, + keyframes: { + bgscroll: { + "0%, 100%": { + "background-position": "0 50%", + }, + "50%": { + "background-position": "100% 50%", + }, + }, + }, + aspectRatio: { + square: "auto 1 / 1", + }, + transitionProperty: { + location: "height, width, top, left, right, bottom", }, }, }, - plugins: [], -} + plugins: [require("tailwindcss-animate")], +};