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