mirror of
https://git.aramjonghu.nl/AramJonghu/aramjonghu-site.git
synced 2026-06-06 17:18:24 +02:00
prettier plus updates to the header entry. Stream gets iframe page instead of redirect
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"hash": "38d6ee96",
|
||||
"configHash": "4c0366d0",
|
||||
"lockfileHash": "e1de79ea",
|
||||
"browserHash": "9db598fc",
|
||||
"optimized": {},
|
||||
"chunks": {}
|
||||
"hash": "38d6ee96",
|
||||
"configHash": "4c0366d0",
|
||||
"lockfileHash": "e1de79ea",
|
||||
"browserHash": "9db598fc",
|
||||
"optimized": {},
|
||||
"chunks": {}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
{
|
||||
"type": "module"
|
||||
"type": "module"
|
||||
}
|
||||
|
||||
+20
-20
@@ -5,25 +5,25 @@ import reactRefresh from "eslint-plugin-react-refresh";
|
||||
import { defineConfig, globalIgnores } from "eslint/config";
|
||||
|
||||
export default defineConfig([
|
||||
globalIgnores(["dist"]),
|
||||
{
|
||||
files: ["**/*.{js,jsx}"],
|
||||
extends: [
|
||||
js.configs.recommended,
|
||||
reactHooks.configs.flat.recommended,
|
||||
reactRefresh.configs.vite,
|
||||
],
|
||||
languageOptions: {
|
||||
ecmaVersion: 2020,
|
||||
globals: globals.browser,
|
||||
parserOptions: {
|
||||
ecmaVersion: "latest",
|
||||
ecmaFeatures: { jsx: true },
|
||||
sourceType: "module",
|
||||
},
|
||||
globalIgnores(["dist"]),
|
||||
{
|
||||
files: ["**/*.{js,jsx}"],
|
||||
extends: [
|
||||
js.configs.recommended,
|
||||
reactHooks.configs.flat.recommended,
|
||||
reactRefresh.configs.vite,
|
||||
],
|
||||
languageOptions: {
|
||||
ecmaVersion: 2020,
|
||||
globals: globals.browser,
|
||||
parserOptions: {
|
||||
ecmaVersion: "latest",
|
||||
ecmaFeatures: { jsx: true },
|
||||
sourceType: "module",
|
||||
},
|
||||
},
|
||||
rules: {
|
||||
"no-unused-vars": ["error", { varsIgnorePattern: "^[A-Z_]" }],
|
||||
},
|
||||
},
|
||||
rules: {
|
||||
"no-unused-vars": ["error", { varsIgnorePattern: "^[A-Z_]" }],
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
+14
-14
@@ -1,16 +1,16 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/svg+xml"
|
||||
href="./src/assets/favicon/favicon.ico"
|
||||
/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>aramjonghu-site</title>
|
||||
</head>
|
||||
<body class="bg-ctp-base">
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.jsx"></script>
|
||||
</body>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/svg+xml"
|
||||
href="./src/assets/favicon/favicon.ico"
|
||||
/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>aramjonghu-site</title>
|
||||
</head>
|
||||
<body class="bg-ctp-base">
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.jsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+35
-35
@@ -1,37 +1,37 @@
|
||||
{
|
||||
"name": "aramjonghu-site",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"lint": "eslint .",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tailwindcss/vite": "^4.2.2",
|
||||
"react": "^19.2.4",
|
||||
"react-dom": "^19.2.4",
|
||||
"react-markdown": "^10.1.0",
|
||||
"rehype-raw": "^7.0.0",
|
||||
"rehype-sanitize": "^6.0.0",
|
||||
"tailwindcss": "^4.2.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.29.0",
|
||||
"@catppuccin/tailwindcss": "^1.0.0",
|
||||
"@eslint/js": "^9.39.4",
|
||||
"@rolldown/plugin-babel": "^0.2.2",
|
||||
"@types/react": "^19.2.14",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"@vitejs/plugin-react": "^6.0.1",
|
||||
"babel-plugin-react-compiler": "^1.0.0",
|
||||
"eslint": "^9.39.4",
|
||||
"eslint-plugin-react-hooks": "^7.0.1",
|
||||
"eslint-plugin-react-refresh": "^0.5.2",
|
||||
"globals": "^17.4.0",
|
||||
"prettier": "^3.8.3",
|
||||
"vite": "^8.0.4"
|
||||
}
|
||||
"name": "aramjonghu-site",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"lint": "eslint .",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tailwindcss/vite": "^4.2.2",
|
||||
"react": "^19.2.4",
|
||||
"react-dom": "^19.2.4",
|
||||
"react-markdown": "^10.1.0",
|
||||
"rehype-raw": "^7.0.0",
|
||||
"rehype-sanitize": "^6.0.0",
|
||||
"tailwindcss": "^4.2.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.29.0",
|
||||
"@catppuccin/tailwindcss": "^1.0.0",
|
||||
"@eslint/js": "^9.39.4",
|
||||
"@rolldown/plugin-babel": "^0.2.2",
|
||||
"@types/react": "^19.2.14",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"@vitejs/plugin-react": "^6.0.1",
|
||||
"babel-plugin-react-compiler": "^1.0.0",
|
||||
"eslint": "^9.39.4",
|
||||
"eslint-plugin-react-hooks": "^7.0.1",
|
||||
"eslint-plugin-react-refresh": "^0.5.2",
|
||||
"globals": "^17.4.0",
|
||||
"prettier": "^3.8.3",
|
||||
"vite": "^8.0.4"
|
||||
}
|
||||
}
|
||||
|
||||
+13
-3
@@ -2,13 +2,23 @@
|
||||
@import "@catppuccin/tailwindcss/macchiato.css";
|
||||
|
||||
.ui-btn {
|
||||
@apply text-ctp-text border border-ctp-text rounded px-3 py-1 cursor-pointer transition-colors;
|
||||
@apply text-ctp-text border border-ctp-text rounded px-3 py-1 cursor-pointer transition-colors;
|
||||
}
|
||||
|
||||
.ui-btn:hover {
|
||||
@apply bg-ctp-surface0 text-ctp-green border-ctp-green duration-300 ease-in-out;
|
||||
@apply bg-ctp-surface0 text-ctp-green border-ctp-green duration-300 ease-in-out;
|
||||
}
|
||||
|
||||
.ui-btn-active {
|
||||
@apply bg-ctp-surface0 text-ctp-green border-ctp-green;
|
||||
@apply bg-ctp-surface0 text-ctp-green border-ctp-green;
|
||||
}
|
||||
|
||||
.stream-frame {
|
||||
width: 96vw;
|
||||
margin: 0 auto;
|
||||
@apply aspect-video rounded-2xl overflow-hidden border border-ctp-lavender-800 bg-ctp-mantle shadow-lg;
|
||||
}
|
||||
|
||||
.stream-frame iframe {
|
||||
@apply w-full h-full border-0;
|
||||
}
|
||||
|
||||
@@ -2,13 +2,17 @@ import "./App.css";
|
||||
|
||||
import Layout from "./components/Layout";
|
||||
import Home from "./pages/Home";
|
||||
import Stream from "./pages/Stream";
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<Layout>
|
||||
<Home />
|
||||
</Layout>
|
||||
);
|
||||
const path = window.location.pathname.toLowerCase();
|
||||
|
||||
let content = <Home />;
|
||||
if (path === "/stream") {
|
||||
content = <Stream />;
|
||||
}
|
||||
|
||||
return <Layout>{content}</Layout>;
|
||||
}
|
||||
|
||||
export default App;
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
import forgejo from "../assets/images/forgejo.svg";
|
||||
|
||||
export default function Footer() {
|
||||
const currentYear = new Date().getFullYear();
|
||||
const currentYear = new Date().getFullYear();
|
||||
|
||||
const goToRepo = () => {
|
||||
window.location.href =
|
||||
"https://git.aramjonghu.nl/AramJonghu/aramjonghu-site";
|
||||
};
|
||||
const goToRepo = () => {
|
||||
window.location.href =
|
||||
"https://git.aramjonghu.nl/AramJonghu/aramjonghu-site";
|
||||
};
|
||||
|
||||
return (
|
||||
<footer className="bg-ctp-mantle flex items-center justify-between px-24 py-6 border-t border-ctp-lavender-800 mt-10">
|
||||
<p className="text-sm text-ctp-text">
|
||||
© {currentYear} AramJonghu. All rights reserved.
|
||||
</p>
|
||||
return (
|
||||
<footer className="bg-ctp-mantle flex items-center justify-between px-24 py-6 border-t border-ctp-lavender-800 mt-10">
|
||||
<p className="text-sm text-ctp-text">
|
||||
© {currentYear} AramJonghu. All rights reserved.
|
||||
</p>
|
||||
|
||||
<img
|
||||
src={forgejo}
|
||||
alt="Forgejo repository"
|
||||
onClick={goToRepo}
|
||||
className="w-6 h-6 cursor-pointer hover:opacity-80 transition-opacity"
|
||||
/>
|
||||
</footer>
|
||||
);
|
||||
<img
|
||||
src={forgejo}
|
||||
alt="Forgejo repository"
|
||||
onClick={goToRepo}
|
||||
className="w-6 h-6 cursor-pointer hover:opacity-80 transition-opacity"
|
||||
/>
|
||||
</footer>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,43 +1,47 @@
|
||||
import Aku from "../assets/images/aku.png";
|
||||
|
||||
export default function Header({ theme }) {
|
||||
const { theme: currentTheme, toggleTheme } = theme;
|
||||
const { theme: currentTheme, toggleTheme } = theme;
|
||||
|
||||
const navItems = [
|
||||
{ label: "Nextcloud", url: "https://aramjonghu.nl/nextcloud" },
|
||||
{ label: "Navidrome", url: "https://music.aramjonghu.nl" },
|
||||
{ label: "Forgejo Git", url: "https://git.aramjonghu.nl" },
|
||||
{ label: "Stream", url: "https://stream.aramjonghu.nl" },
|
||||
{ label: "SearXNG", url: "https://xng.aramjonghu.nl" },
|
||||
{ label: "Gitea zach-dev", url: "https://git.zach-dev.cc" },
|
||||
];
|
||||
const navItems = [
|
||||
{ label: "Nextcloud", url: "https://aramjonghu.nl/nextcloud" },
|
||||
{ label: "Navidrome", url: "https://music.aramjonghu.nl" },
|
||||
{ label: "Forgejo Git", url: "https://git.aramjonghu.nl" },
|
||||
{ label: "Stream", url: "/stream" },
|
||||
{ label: "SearXNG", url: "https://xng.aramjonghu.nl" },
|
||||
{ label: "Gitea zach-dev", url: "https://git.zach-dev.cc" },
|
||||
];
|
||||
|
||||
const goHome = () => {
|
||||
window.location.href = "/";
|
||||
};
|
||||
const goHome = () => {
|
||||
window.location.href = "/";
|
||||
};
|
||||
|
||||
return (
|
||||
<header className="bg-ctp-mantle flex justify-between items-center px-12 py-6 gap-6 border-b border-ctp-lavender-800">
|
||||
<img
|
||||
className="size-16 cursor-pointer"
|
||||
src={Aku}
|
||||
alt="Home"
|
||||
onClick={goHome}
|
||||
/>
|
||||
return (
|
||||
<header className="bg-ctp-mantle flex justify-between items-center px-12 py-6 gap-6 border-b border-ctp-lavender-800">
|
||||
<img
|
||||
className="size-16 cursor-pointer"
|
||||
src={Aku}
|
||||
alt="Home"
|
||||
onClick={goHome}
|
||||
/>
|
||||
|
||||
<nav className="flex flex-wrap gap-3 items-center">
|
||||
{navItems.map((item) => (
|
||||
<a key={item.label} href={item.url} className="ui-btn ui-btn:hover">
|
||||
{item.label}
|
||||
</a>
|
||||
))}
|
||||
</nav>
|
||||
<nav className="flex flex-wrap gap-3 items-center">
|
||||
{navItems.map((item) => (
|
||||
<a
|
||||
key={item.label}
|
||||
href={item.url}
|
||||
className="ui-btn ui-btn:hover"
|
||||
>
|
||||
{item.label}
|
||||
</a>
|
||||
))}
|
||||
</nav>
|
||||
|
||||
<div>
|
||||
<button className="ui-btn ui-btn:hover" onClick={toggleTheme}>
|
||||
{currentTheme === "macchiato" ? "🌙 Dark" : "☀️ Light"}
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
<div>
|
||||
<button className="ui-btn ui-btn:hover" onClick={toggleTheme}>
|
||||
{currentTheme === "macchiato" ? "🌙 Dark" : "☀️ Light"}
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,13 +3,13 @@ import Header from "./Header";
|
||||
import { useTheme } from "../hooks/useTheme";
|
||||
|
||||
export default function Layout({ children }) {
|
||||
const theme = useTheme();
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<div className="flex flex-col min-h-screen bg-ctp-base">
|
||||
<Header theme={theme} />
|
||||
<main className="flex-1">{children}</main>
|
||||
<Footer />
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div className="flex flex-col min-h-screen bg-ctp-base">
|
||||
<Header theme={theme} />
|
||||
<main className="flex-1">{children}</main>
|
||||
<Footer />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,217 +3,224 @@ import ReactMarkdown from "react-markdown";
|
||||
import rehypeRaw from "rehype-raw";
|
||||
|
||||
const toRawUrl = (repoUrl) => {
|
||||
if (!repoUrl) return repoUrl;
|
||||
if (!repoUrl) return repoUrl;
|
||||
|
||||
if (
|
||||
repoUrl.includes("github.com") &&
|
||||
!repoUrl.includes("raw.githubusercontent.com")
|
||||
) {
|
||||
return repoUrl
|
||||
.replace("github.com", "raw.githubusercontent.com")
|
||||
.replace("/blob/", "/");
|
||||
}
|
||||
if (
|
||||
repoUrl.includes("github.com") &&
|
||||
!repoUrl.includes("raw.githubusercontent.com")
|
||||
) {
|
||||
return repoUrl
|
||||
.replace("github.com", "raw.githubusercontent.com")
|
||||
.replace("/blob/", "/");
|
||||
}
|
||||
|
||||
if (repoUrl.includes("/src/branch/")) {
|
||||
return repoUrl.replace("/src/branch/", "/raw/branch/");
|
||||
}
|
||||
if (repoUrl.includes("/src/branch/")) {
|
||||
return repoUrl.replace("/src/branch/", "/raw/branch/");
|
||||
}
|
||||
|
||||
return repoUrl;
|
||||
return repoUrl;
|
||||
};
|
||||
|
||||
const getRepoRawBase = (repoUrl) => {
|
||||
const rawUrl = toRawUrl(repoUrl);
|
||||
if (!rawUrl) return null;
|
||||
const rawUrl = toRawUrl(repoUrl);
|
||||
if (!rawUrl) return null;
|
||||
|
||||
try {
|
||||
const url = new URL(rawUrl);
|
||||
const parts = url.pathname.split("/").filter(Boolean);
|
||||
try {
|
||||
const url = new URL(rawUrl);
|
||||
const parts = url.pathname.split("/").filter(Boolean);
|
||||
|
||||
if (url.hostname.includes("raw.githubusercontent.com")) {
|
||||
return `${url.origin}/${parts.slice(0, 3).join("/")}`;
|
||||
if (url.hostname.includes("raw.githubusercontent.com")) {
|
||||
return `${url.origin}/${parts.slice(0, 3).join("/")}`;
|
||||
}
|
||||
|
||||
const rawIndex = parts.indexOf("raw");
|
||||
if (rawIndex !== -1) {
|
||||
return `${url.origin}/${parts.slice(0, rawIndex + 2).join("/")}`;
|
||||
}
|
||||
|
||||
return null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
|
||||
const rawIndex = parts.indexOf("raw");
|
||||
if (rawIndex !== -1) {
|
||||
return `${url.origin}/${parts.slice(0, rawIndex + 2).join("/")}`;
|
||||
}
|
||||
|
||||
return null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const resolveImageSrc = (src, base) => {
|
||||
if (!src) return src;
|
||||
if (!src) return src;
|
||||
|
||||
if (src.startsWith("http")) return src;
|
||||
if (src.startsWith("http")) return src;
|
||||
|
||||
if (src.includes("/blob/")) {
|
||||
return src
|
||||
.replace("github.com", "raw.githubusercontent.com")
|
||||
.replace("/blob/", "/");
|
||||
}
|
||||
if (src.includes("/blob/")) {
|
||||
return src
|
||||
.replace("github.com", "raw.githubusercontent.com")
|
||||
.replace("/blob/", "/");
|
||||
}
|
||||
|
||||
if (!base) return src;
|
||||
if (!base) return src;
|
||||
|
||||
const clean = src.replace(/^.\//, "");
|
||||
const clean = src.replace(/^.\//, "");
|
||||
|
||||
if (src.startsWith("/")) {
|
||||
return base + src;
|
||||
}
|
||||
if (src.startsWith("/")) {
|
||||
return base + src;
|
||||
}
|
||||
|
||||
return `${base}/${clean}`;
|
||||
return `${base}/${clean}`;
|
||||
};
|
||||
|
||||
const fetchREADME = async (repoUrl) => {
|
||||
try {
|
||||
const rawUrl = toRawUrl(repoUrl);
|
||||
const response = await fetch(rawUrl);
|
||||
try {
|
||||
const rawUrl = toRawUrl(repoUrl);
|
||||
const response = await fetch(rawUrl);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error("Failed to fetch README");
|
||||
if (!response.ok) {
|
||||
throw new Error("Failed to fetch README");
|
||||
}
|
||||
|
||||
return await response.text();
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return "Error fetching README. Make sure the URL points to a raw markdown file.";
|
||||
}
|
||||
|
||||
return await response.text();
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return "Error fetching README. Make sure the URL points to a raw markdown file.";
|
||||
}
|
||||
};
|
||||
|
||||
export default function ProjectsReadme({ repoUrl }) {
|
||||
const [readmeContent, setReadmeContent] = useState("");
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [readmeContent, setReadmeContent] = useState("");
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
|
||||
const base = useMemo(() => getRepoRawBase(repoUrl), [repoUrl]);
|
||||
const base = useMemo(() => getRepoRawBase(repoUrl), [repoUrl]);
|
||||
|
||||
useEffect(() => {
|
||||
setIsLoading(true);
|
||||
useEffect(() => {
|
||||
setIsLoading(true);
|
||||
|
||||
fetchREADME(repoUrl).then((content) => {
|
||||
setReadmeContent(content);
|
||||
setIsLoading(false);
|
||||
});
|
||||
}, [repoUrl]);
|
||||
fetchREADME(repoUrl).then((content) => {
|
||||
setReadmeContent(content);
|
||||
setIsLoading(false);
|
||||
});
|
||||
}, [repoUrl]);
|
||||
|
||||
return (
|
||||
<div className="w-full max-w-4xl mx-auto p-4 md:p-8 bg-ctp-mantle rounded-xl shadow-sm border border-ctp-text">
|
||||
{isLoading ? (
|
||||
<div className="flex flex-col gap-3 animate-pulse w-full">
|
||||
<div className="h-4 bg-ctp-overlay0 rounded-full w-3/4"></div>
|
||||
<div className="h-4 bg-ctp-overlay0 rounded-full w-3/4"></div>
|
||||
<div className="h-4 bg-ctp-overlay0 rounded-full w-3/4"></div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-ctp-text leading-relaxed prose prose-base md:prose-lg dark:prose-invert max-w-none">
|
||||
<ReactMarkdown
|
||||
rehypePlugins={[rehypeRaw]}
|
||||
components={{
|
||||
code({ className, children, ...props }) {
|
||||
const isBlock =
|
||||
className ||
|
||||
(typeof children === "string" && children.includes("\n"));
|
||||
return (
|
||||
<div className="w-full max-w-4xl mx-auto p-4 md:p-8 bg-ctp-mantle rounded-xl shadow-sm border border-ctp-text">
|
||||
{isLoading ? (
|
||||
<div className="flex flex-col gap-3 animate-pulse w-full">
|
||||
<div className="h-4 bg-ctp-overlay0 rounded-full w-3/4"></div>
|
||||
<div className="h-4 bg-ctp-overlay0 rounded-full w-3/4"></div>
|
||||
<div className="h-4 bg-ctp-overlay0 rounded-full w-3/4"></div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-ctp-text leading-relaxed prose prose-base md:prose-lg dark:prose-invert max-w-none">
|
||||
<ReactMarkdown
|
||||
rehypePlugins={[rehypeRaw]}
|
||||
components={{
|
||||
code({ className, children, ...props }) {
|
||||
const isBlock =
|
||||
className ||
|
||||
(typeof children === "string" &&
|
||||
children.includes("\n"));
|
||||
|
||||
if (isBlock) {
|
||||
return (
|
||||
<code
|
||||
className={`${className || ""} text-sm block overflow-x-auto`}
|
||||
{...props}
|
||||
if (isBlock) {
|
||||
return (
|
||||
<code
|
||||
className={`${className || ""} text-sm block overflow-x-auto`}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</code>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<code
|
||||
className="bg-ctp-mantle px-1.5 py-0.5 rounded text-sm font-mono"
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</code>
|
||||
);
|
||||
},
|
||||
|
||||
pre({ children, ...props }) {
|
||||
return (
|
||||
<pre
|
||||
className="bg-ctp-mantle p-4 rounded-lg overflow-x-auto text-sm my-4"
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</pre>
|
||||
);
|
||||
},
|
||||
|
||||
h1: ({ children }) => (
|
||||
<h1 className="text-3xl font-bold text-ctp-mauve mt-8 mb-4 border-b pb-2 border-ctp-green">
|
||||
{children}
|
||||
</h1>
|
||||
),
|
||||
|
||||
h2: ({ children }) => (
|
||||
<h2 className="text-2xl font-bold text-ctp-lavender mt-8 mb-4 border-b pb-2 border-ctp-green">
|
||||
{children}
|
||||
</h2>
|
||||
),
|
||||
|
||||
h3: ({ children }) => (
|
||||
<h3 className="text-xl font-bold text-ctp-lavender mt-5 mb-3">
|
||||
{children}
|
||||
</h3>
|
||||
),
|
||||
|
||||
p: ({ children }) => (
|
||||
<p className="mb-4 text-ctp-text">{children}</p>
|
||||
),
|
||||
|
||||
ul: ({ children }) => (
|
||||
<ul className="list-disc pl-6 mb-4">
|
||||
{children}
|
||||
</ul>
|
||||
),
|
||||
|
||||
ol: ({ children }) => (
|
||||
<ol className="list-decimal pl-6 mb-4">
|
||||
{children}
|
||||
</ol>
|
||||
),
|
||||
|
||||
li: ({ children }) => (
|
||||
<li className="mb-1">{children}</li>
|
||||
),
|
||||
|
||||
a: ({ children, href }) => (
|
||||
<a
|
||||
href={href}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="hover:underline text-ctp-green"
|
||||
>
|
||||
{children}
|
||||
</a>
|
||||
),
|
||||
|
||||
blockquote: ({ children }) => (
|
||||
<blockquote className="border-l-4 border-ctp-text pl-4 py-1 mb-4 bg-ctp-mantle rounded-r-lg">
|
||||
{children}
|
||||
</blockquote>
|
||||
),
|
||||
|
||||
img: ({ src, alt }) => {
|
||||
const resolved = resolveImageSrc(src, base);
|
||||
|
||||
return (
|
||||
<img
|
||||
src={resolved}
|
||||
alt={alt}
|
||||
className="max-w-full h-auto rounded-lg my-4 shadow-sm"
|
||||
loading="lazy"
|
||||
/>
|
||||
);
|
||||
},
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</code>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<code
|
||||
className="bg-ctp-mantle px-1.5 py-0.5 rounded text-sm font-mono"
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</code>
|
||||
);
|
||||
},
|
||||
|
||||
pre({ children, ...props }) {
|
||||
return (
|
||||
<pre
|
||||
className="bg-ctp-mantle p-4 rounded-lg overflow-x-auto text-sm my-4"
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</pre>
|
||||
);
|
||||
},
|
||||
|
||||
h1: ({ children }) => (
|
||||
<h1 className="text-3xl font-bold text-ctp-mauve mt-8 mb-4 border-b pb-2 border-ctp-green">
|
||||
{children}
|
||||
</h1>
|
||||
),
|
||||
|
||||
h2: ({ children }) => (
|
||||
<h2 className="text-2xl font-bold text-ctp-lavender mt-8 mb-4 border-b pb-2 border-ctp-green">
|
||||
{children}
|
||||
</h2>
|
||||
),
|
||||
|
||||
h3: ({ children }) => (
|
||||
<h3 className="text-xl font-bold text-ctp-lavender mt-5 mb-3">
|
||||
{children}
|
||||
</h3>
|
||||
),
|
||||
|
||||
p: ({ children }) => (
|
||||
<p className="mb-4 text-ctp-text">{children}</p>
|
||||
),
|
||||
|
||||
ul: ({ children }) => (
|
||||
<ul className="list-disc pl-6 mb-4">{children}</ul>
|
||||
),
|
||||
|
||||
ol: ({ children }) => (
|
||||
<ol className="list-decimal pl-6 mb-4">{children}</ol>
|
||||
),
|
||||
|
||||
li: ({ children }) => <li className="mb-1">{children}</li>,
|
||||
|
||||
a: ({ children, href }) => (
|
||||
<a
|
||||
href={href}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="hover:underline text-ctp-green"
|
||||
>
|
||||
{children}
|
||||
</a>
|
||||
),
|
||||
|
||||
blockquote: ({ children }) => (
|
||||
<blockquote className="border-l-4 border-ctp-text pl-4 py-1 mb-4 bg-ctp-mantle rounded-r-lg">
|
||||
{children}
|
||||
</blockquote>
|
||||
),
|
||||
|
||||
img: ({ src, alt }) => {
|
||||
const resolved = resolveImageSrc(src, base);
|
||||
|
||||
return (
|
||||
<img
|
||||
src={resolved}
|
||||
alt={alt}
|
||||
className="max-w-full h-auto rounded-lg my-4 shadow-sm"
|
||||
loading="lazy"
|
||||
/>
|
||||
);
|
||||
},
|
||||
}}
|
||||
>
|
||||
{readmeContent}
|
||||
</ReactMarkdown>
|
||||
{readmeContent}
|
||||
</ReactMarkdown>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,15 +2,15 @@ import { useEffect, useState } from "react";
|
||||
import { getInitialTheme, applyTheme, toggleTheme } from "../utils/theme";
|
||||
|
||||
export function useTheme() {
|
||||
const [theme, setTheme] = useState(getInitialTheme);
|
||||
const [theme, setTheme] = useState(getInitialTheme);
|
||||
|
||||
useEffect(() => {
|
||||
applyTheme(theme);
|
||||
}, [theme]);
|
||||
useEffect(() => {
|
||||
applyTheme(theme);
|
||||
}, [theme]);
|
||||
|
||||
const handleToggle = () => {
|
||||
setTheme((prev) => toggleTheme(prev));
|
||||
};
|
||||
const handleToggle = () => {
|
||||
setTheme((prev) => toggleTheme(prev));
|
||||
};
|
||||
|
||||
return { theme, toggleTheme: handleToggle };
|
||||
return { theme, toggleTheme: handleToggle };
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import { createRoot } from "react-dom/client";
|
||||
import App from "./App.jsx";
|
||||
|
||||
createRoot(document.getElementById("root")).render(
|
||||
<StrictMode>
|
||||
<App />
|
||||
</StrictMode>,
|
||||
<StrictMode>
|
||||
<App />
|
||||
</StrictMode>,
|
||||
);
|
||||
|
||||
+111
-106
@@ -2,115 +2,120 @@ import { useState } from "react";
|
||||
import ProjectsReadme from "../components/ProjectsReadme";
|
||||
|
||||
export default function Home() {
|
||||
const projectList = [
|
||||
{
|
||||
id: "site",
|
||||
name: "aramjonghu-site",
|
||||
url: "https://git.aramjonghu.nl/AramJonghu/aramjonghu-site/raw/branch/master/README.md",
|
||||
},
|
||||
{
|
||||
id: "game",
|
||||
name: "Game (Gameron the Lost Level)",
|
||||
url: "https://git.aramjonghu.nl/AramJonghu/gameron-the-lost-level/src/branch/master/README.md",
|
||||
},
|
||||
{
|
||||
id: "nvim",
|
||||
name: "Nvim Config",
|
||||
url: "https://git.aramjonghu.nl/AramJonghu/nvim/src/branch/main/README.md",
|
||||
},
|
||||
];
|
||||
const projectList = [
|
||||
{
|
||||
id: "site",
|
||||
name: "aramjonghu-site",
|
||||
url: "https://git.aramjonghu.nl/AramJonghu/aramjonghu-site/raw/branch/master/README.md",
|
||||
},
|
||||
{
|
||||
id: "game",
|
||||
name: "Game (Gameron the Lost Level)",
|
||||
url: "https://git.aramjonghu.nl/AramJonghu/gameron-the-lost-level/src/branch/master/README.md",
|
||||
},
|
||||
{
|
||||
id: "nvim",
|
||||
name: "Nvim Config",
|
||||
url: "https://git.aramjonghu.nl/AramJonghu/nvim/src/branch/main/README.md",
|
||||
},
|
||||
];
|
||||
|
||||
const [projIdx, setProjIdx] = useState(0);
|
||||
const prevProject = () =>
|
||||
setProjIdx((i) => (i - 1 + projectList.length) % projectList.length);
|
||||
const nextProject = () => setProjIdx((i) => (i + 1) % projectList.length);
|
||||
const [projIdx, setProjIdx] = useState(0);
|
||||
const prevProject = () =>
|
||||
setProjIdx((i) => (i - 1 + projectList.length) % projectList.length);
|
||||
const nextProject = () => setProjIdx((i) => (i + 1) % projectList.length);
|
||||
|
||||
const [activeTab, setActiveTab] = useState("about");
|
||||
const [activeTab, setActiveTab] = useState("about");
|
||||
|
||||
const sections = [
|
||||
{
|
||||
id: "projects",
|
||||
title: "Projects",
|
||||
content: (
|
||||
<>
|
||||
<h1 className="text-center m-6 text-3xl font-bold text-ctp-mauve">
|
||||
Projects
|
||||
</h1>
|
||||
<div className="flex justify-center mb-6">
|
||||
<button
|
||||
onClick={prevProject}
|
||||
className="px-3 py-1 rounded ui-btn text-ctp-text"
|
||||
>
|
||||
◀︎ Prev
|
||||
</button>
|
||||
<span className="self-center mx-6 font-medium">
|
||||
{projectList[projIdx].name} ({projIdx + 1}/{projectList.length})
|
||||
</span>
|
||||
<button
|
||||
onClick={nextProject}
|
||||
className="px-3 py-1 rounded ui-btn text-ctp-text"
|
||||
>
|
||||
Next ▶︎
|
||||
</button>
|
||||
</div>
|
||||
<ProjectsReadme repoUrl={projectList[projIdx].url} />
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: "systems",
|
||||
title: "Systems",
|
||||
content: <p>Systems content goes here.</p>,
|
||||
},
|
||||
{
|
||||
id: "about",
|
||||
title: "About",
|
||||
content: <p>About content goes here.</p>,
|
||||
},
|
||||
];
|
||||
const sections = [
|
||||
{
|
||||
id: "projects",
|
||||
title: "Projects",
|
||||
content: (
|
||||
<>
|
||||
<h1 className="text-center m-6 text-3xl font-bold text-ctp-mauve">
|
||||
Projects
|
||||
</h1>
|
||||
<div className="flex justify-center mb-6">
|
||||
<button
|
||||
onClick={prevProject}
|
||||
className="px-3 py-1 rounded ui-btn text-ctp-text"
|
||||
>
|
||||
◀︎ Prev
|
||||
</button>
|
||||
<span className="self-center mx-6 font-medium">
|
||||
{projectList[projIdx].name} ({projIdx + 1}/
|
||||
{projectList.length})
|
||||
</span>
|
||||
<button
|
||||
onClick={nextProject}
|
||||
className="px-3 py-1 rounded ui-btn text-ctp-text"
|
||||
>
|
||||
Next ▶︎
|
||||
</button>
|
||||
</div>
|
||||
<ProjectsReadme repoUrl={projectList[projIdx].url} />
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: "systems",
|
||||
title: "Systems",
|
||||
content: <p>Systems content goes here.</p>,
|
||||
},
|
||||
{
|
||||
id: "about",
|
||||
title: "About",
|
||||
content: <p>About content goes here.</p>,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="text-ctp-text flex flex-col items-center justify-center px-6 py-6">
|
||||
<div className="w-full max-w-6xl text-center">
|
||||
<h1 className="text-4xl font-bold text-ctp-mauve">AramJonghu</h1>
|
||||
<p className="mt-4">
|
||||
Software engineer building systems, services, and experiments across a
|
||||
self-hosted environment.
|
||||
</p>
|
||||
<p className="mt-3">
|
||||
This site acts as a hub for projects, infrastructure, and personal
|
||||
tooling.
|
||||
</p>
|
||||
<div className="mt-10 border rounded p-4">
|
||||
<div className="flex flex-wrap justify-center gap-12 mb-6">
|
||||
{sections.map((section) => (
|
||||
<button
|
||||
key={section.id}
|
||||
onClick={() => setActiveTab(section.id)}
|
||||
className={`ui-btn ${
|
||||
activeTab === section.id ? "ui-btn-active" : ""
|
||||
}`}
|
||||
>
|
||||
{section.title}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<div className="mt-6 text-left relative min-h-20">
|
||||
{sections.map((section) => (
|
||||
<div
|
||||
key={section.id}
|
||||
className={`transition-all duration-300 ${
|
||||
activeTab === section.id
|
||||
? "opacity-100 translate-y-0"
|
||||
: "opacity-0 -translate-y-2 pointer-events-none absolute inset-0"
|
||||
}`}
|
||||
>
|
||||
{section.content}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
return (
|
||||
<div className="text-ctp-text flex flex-col items-center justify-center px-6 py-6">
|
||||
<div className="w-full max-w-6xl text-center">
|
||||
<h1 className="text-4xl font-bold text-ctp-mauve">
|
||||
AramJonghu
|
||||
</h1>
|
||||
<p className="mt-4">
|
||||
Software engineer building systems, services, and
|
||||
experiments across a self-hosted environment.
|
||||
</p>
|
||||
<p className="mt-3">
|
||||
This site acts as a hub for projects, infrastructure, and
|
||||
personal tooling.
|
||||
</p>
|
||||
<div className="mt-10 border rounded p-4">
|
||||
<div className="flex flex-wrap justify-center gap-12 mb-6">
|
||||
{sections.map((section) => (
|
||||
<button
|
||||
key={section.id}
|
||||
onClick={() => setActiveTab(section.id)}
|
||||
className={`ui-btn ${
|
||||
activeTab === section.id
|
||||
? "ui-btn-active"
|
||||
: ""
|
||||
}`}
|
||||
>
|
||||
{section.title}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<div className="mt-6 text-left relative min-h-20">
|
||||
{sections.map((section) => (
|
||||
<div
|
||||
key={section.id}
|
||||
className={`transition-all duration-300 ${
|
||||
activeTab === section.id
|
||||
? "opacity-100 translate-y-0"
|
||||
: "opacity-0 -translate-y-2 pointer-events-none absolute inset-0"
|
||||
}`}
|
||||
>
|
||||
{section.content}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
export default function Stream() {
|
||||
return (
|
||||
<div className="text-ctp-text flex flex-col min-h-screen px-4 py-6">
|
||||
<div className="w-full text-center flex flex-col flex-1">
|
||||
<h1 className="text-4xl font-bold text-ctp-mauve mb-6">
|
||||
Stream
|
||||
</h1>
|
||||
|
||||
<div className="stream-frame w-full mx-auto flex-1 max-h-[85vh]">
|
||||
<iframe
|
||||
src="https://stream.aramjonghu.nl"
|
||||
title="Stream"
|
||||
allow="autoplay; fullscreen; picture-in-picture"
|
||||
loading="lazy"
|
||||
className="w-full h-full border-0 rounded-lg"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
+10
-10
@@ -2,29 +2,29 @@ const LIGHT = "latte";
|
||||
const DARK = "macchiato";
|
||||
|
||||
export function getSystemTheme() {
|
||||
return window.matchMedia("(prefers-color-scheme: dark)").matches
|
||||
? DARK
|
||||
: LIGHT;
|
||||
return window.matchMedia("(prefers-color-scheme: dark)").matches
|
||||
? DARK
|
||||
: LIGHT;
|
||||
}
|
||||
|
||||
export function getStoredTheme() {
|
||||
return localStorage.getItem("theme");
|
||||
return localStorage.getItem("theme");
|
||||
}
|
||||
|
||||
export function getInitialTheme() {
|
||||
return getStoredTheme() || getSystemTheme();
|
||||
return getStoredTheme() || getSystemTheme();
|
||||
}
|
||||
|
||||
export function applyTheme(theme) {
|
||||
const root = document.documentElement;
|
||||
const root = document.documentElement;
|
||||
|
||||
root.classList.remove(LIGHT, DARK);
|
||||
root.classList.remove(LIGHT, DARK);
|
||||
|
||||
root.classList.add(theme);
|
||||
root.classList.add(theme);
|
||||
|
||||
localStorage.setItem("theme", theme);
|
||||
localStorage.setItem("theme", theme);
|
||||
}
|
||||
|
||||
export function toggleTheme(current) {
|
||||
return current === DARK ? LIGHT : DARK;
|
||||
return current === DARK ? LIGHT : DARK;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
export default {
|
||||
darkMode: "class",
|
||||
content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
|
||||
theme: {
|
||||
extend: {},
|
||||
},
|
||||
plugins: [],
|
||||
darkMode: "class",
|
||||
content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
|
||||
theme: {
|
||||
extend: {},
|
||||
},
|
||||
plugins: [],
|
||||
};
|
||||
|
||||
@@ -4,9 +4,9 @@ import babel from "@rolldown/plugin-babel";
|
||||
import tailwindcss from "@tailwindcss/vite";
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
tailwindcss(),
|
||||
react(),
|
||||
babel({ presets: [reactCompilerPreset()] }),
|
||||
],
|
||||
plugins: [
|
||||
tailwindcss(),
|
||||
react(),
|
||||
babel({ presets: [reactCompilerPreset()] }),
|
||||
],
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user