aboutsummaryrefslogtreecommitdiff
path: root/src/pages
diff options
context:
space:
mode:
Diffstat (limited to 'src/pages')
-rw-r--r--src/pages/404Page.tsx21
-rw-r--r--src/pages/Gear.tsx43
-rw-r--r--src/pages/Home.tsx31
-rw-r--r--src/pages/Homelab.tsx52
-rw-r--r--src/pages/Printing.tsx70
-rw-r--r--src/pages/Projects.tsx24
6 files changed, 241 insertions, 0 deletions
diff --git a/src/pages/404Page.tsx b/src/pages/404Page.tsx
new file mode 100644
index 0000000..3eabe6b
--- /dev/null
+++ b/src/pages/404Page.tsx
@@ -0,0 +1,21 @@
+import { useNavigate } from "react-router-dom";
+import notFoundImg from "../assets/404.png";
+import { useEffect } from "react";
+
+export default function NotFoundPage() {
+ const navigate = useNavigate();
+
+ useEffect(() => {
+ const timer = setTimeout(() => {
+ navigate('/', { replace: true });
+ }, 4000);
+
+ return () => clearTimeout(timer);
+ }, [navigate]);
+ return(
+ <>
+ <h1>404 - Not found</h1>
+ <img src={notFoundImg} className="rounded-lg"/>
+ </>
+ );
+} \ No newline at end of file
diff --git a/src/pages/Gear.tsx b/src/pages/Gear.tsx
new file mode 100644
index 0000000..dbbcdfb
--- /dev/null
+++ b/src/pages/Gear.tsx
@@ -0,0 +1,43 @@
+import CardLink from '../components/CardLink';
+
+import {
+ dailyDrivers,
+ desktopParts,
+ nasParts,
+ type Part,
+} from '../data/gear';
+
+function PartsGroup({ title, parts }: { title?: string; parts: Part[] }) {
+ return (
+ <>
+ {title && <h2 className="text-2xl font-semibold my-8">{title}</h2>}
+ <ul className="space-y-2">
+ {parts.map((p) => (
+ <li key={p.name}>
+ <CardLink
+ title={p.name}
+ body={p.description}
+ href={p.url}
+ imgSrc={p.image}
+ />
+ </li>
+ ))}
+ </ul>
+ </>
+ );
+}
+
+export default function GearPage() {
+ return (
+ <>
+ <title>߸ gear</title>
+ <h1>Gear</h1>
+
+ <PartsGroup parts={dailyDrivers} />
+
+ <PartsGroup title="Desktop" parts={desktopParts} />
+
+ <PartsGroup title="NAS" parts={nasParts} />
+ </>
+ );
+} \ No newline at end of file
diff --git a/src/pages/Home.tsx b/src/pages/Home.tsx
new file mode 100644
index 0000000..912b0c5
--- /dev/null
+++ b/src/pages/Home.tsx
@@ -0,0 +1,31 @@
+import ImageGalleryGrid from "../components/ImageGalleryGrid";
+
+import dsImg from "../assets/3ds.jpeg";
+import esp32Img from "../assets/esp32.jpeg";
+import riceImg from "../assets/rice.jpg";
+import setupImg from "../assets/setup.jpg";
+
+export default function HomePage() {
+ return (
+ <>
+ <title>߸ hi</title>
+ <h1>Hi, <span className="text-blue-500 dark:text-purple-500">I'm Leander.</span></h1>
+
+ <p className="leading-relaxed mb-6">
+ I have a passion for hardware and software, studying computer science. Currently building own 3d printing projects and learning nix.
+ </p>
+
+ <ImageGalleryGrid images={[{ src: dsImg, alt: "Nintendo 3DS", id: 1}, { src: esp32Img, alt: "ESP 32", id: 2}, { src: riceImg, alt: "Linux rice", id: 3}, { src: setupImg, alt: "Setup", id: 4}]} />
+
+ <p className="mb-4">A few things I'm interrested in:</p>
+
+ <ul className="list-disc pl-6 space-y-1">
+ <li>Digital minimalism</li>
+ <li>*nix systems</li>
+ <li>3D printing</li>
+ <li>Homelab / self-hosting</li>
+ <li>Seinen manga</li>
+ </ul>
+ </>
+ );
+} \ No newline at end of file
diff --git a/src/pages/Homelab.tsx b/src/pages/Homelab.tsx
new file mode 100644
index 0000000..580babc
--- /dev/null
+++ b/src/pages/Homelab.tsx
@@ -0,0 +1,52 @@
+import nasImg from '../assets/nas.png';
+import piImg from '../assets/pi.png';
+import LinkWithIcon from '../components/LinkWithIcon';
+
+const nasServices = [
+ "Jellyfin: Media library",
+ "Kavita: Ebooks and manga",
+ "AdGuard Home: Ad blocking",
+ "Nginx: Reverse proxy",
+ "Ollama: Enough for small LLM testing",
+];
+
+export default function HomelabPage() {
+ return (
+ <>
+ <title>߸ homelab</title>
+ <h1>Home lab</h1>
+
+ <section className="mb-12">
+ <h2>NAS</h2>
+ <img src={nasImg} alt="NAS" className="mx-auto mb-4 w-64 rounded-lg shadow" />
+ <p className="mb-4 leading-relaxed">
+ My custom-built NAS running <strong>Unraid</strong> hosts the following services. See <LinkWithIcon href='/gear'>gear</LinkWithIcon> for specs.
+ </p>
+ <ul className="list-disc pl-6 space-y-1">
+ {nasServices.map((svc) => (
+ <li key={svc}>{svc}</li>
+ ))}
+ </ul>
+ <p className="mb-4 leading-relaxed">
+ For remote access, I use a VPN to connect to the machine. I also back up my MacBook using Time Machine, and for my desktop and VPS I use rsync.
+ </p>
+ </section>
+
+ <section className="mb-12">
+ <h2>Raspberry Pi</h2>
+ <img src={piImg} alt="Raspberry Pi 5" className="mx-auto mb-4 w-64 rounded-lg shadow" />
+ <p className="mb-4 leading-relaxed">
+ Raspberry Pi 5 (8GB) running Homebridge to integrate non-HomeKit devices. It also serves as a precision NTP server using a <LinkWithIcon href='https://store.uputronics.com/products/raspberry-pi-gps-rtc-expansion-board' target='_blank'>Uputronics GPS module</LinkWithIcon>.
+ </p>
+ </section>
+
+ {/* VPS */}
+ <section>
+ <h2>VPS</h2>
+ <p className="mb-4 leading-relaxed">
+ Cheap Ionos VPS for services exposed to the internet. Mainly using it for hosting this website and Nextcloud.
+ </p>
+ </section>
+ </>
+ );
+} \ No newline at end of file
diff --git a/src/pages/Printing.tsx b/src/pages/Printing.tsx
new file mode 100644
index 0000000..61dbe71
--- /dev/null
+++ b/src/pages/Printing.tsx
@@ -0,0 +1,70 @@
+import a1Img from "../assets/a1.png";
+import CodeSnippet from "../components/CodeSnippet";
+import LinkWithIcon from "../components/LinkWithIcon";
+
+export default function PrintingPage() {
+ const roboArmCode = `#include <Bluepad32.h>
+#include <ESP32Servo.h>
+
+#define DEADZONE 30
+#define BASE_PIN 15
+#define SHOULDER_PIN 2
+#define ELBOW_PIN 4
+#define WRIST_PIN 16
+#define HAND_PIN 17
+
+Servo base, shoulder, elbow, wrist, hand;
+ControllerPtr pad;
+
+void onConnectedGamepad(ControllerPtr ctl) {
+ Serial.printf("Gamepad #%d verbunden\n", ctl->index());
+ pad = ctl;
+}
+
+void onDisconnectedGamepad(ControllerPtr ctl) {
+ Serial.printf("Gamepad getrennt\n");
+}
+
+int16_t mapAxis(int16_t v) {
+ if (abs(v) < DEADZONE) v = 0;
+ return map(v, -512, 512, 0, 180);
+}
+void setup() {
+ BP32.setup(&onConnectedGamepad, &onDisconnectedGamepad);
+ BP32.enableNewBluetoothConnections(true);
+
+ base.attach(BASE_PIN);
+ shoulder.attach(SHOULDER_PIN);
+ elbow.attach(ELBOW_PIN);
+ wrist.attach(WRIST_PIN);
+ hand.attach(HAND_PIN);
+}
+
+void loop() {
+ BP32.update();
+ if (pad && pad->isConnected()) {
+ base.write(mapAxis(pad->axisX()));
+ shoulder.write(mapAxis(-pad->axisY()));
+ elbow.write(mapAxis(-pad->axisRY()));
+ hand.write(mapAxis(pad->throttle() - pad->brake()));
+
+ if (pad->l1()) wrist.write(0);
+ else if (pad->r1()) wrist.write(180);
+ else wrist.write(90);
+ }
+ delay(15);
+}`;
+ return (
+ <>
+ <title>߸ 3d printing</title>
+ <h1>3D Printing</h1>
+ <img src={a1Img} alt="Bambu Lab A1" className="mx-auto mb-4 w-64 rounded-lg shadow"/>
+ <h2>Projects</h2>
+ <h3>Robot Arm</h3>
+ <p><LinkWithIcon href="https://makerworld.com/en/models/528885-robotic-arm#profileId-445995" target="_blank">3D Model</LinkWithIcon> changed the model to work with my servo motors.</p>
+ <CodeSnippet code={roboArmCode} initialLines={5} />
+ <h3>Diy Drone</h3>
+ <p>WIP</p>
+ </>
+ );
+} \ No newline at end of file
diff --git a/src/pages/Projects.tsx b/src/pages/Projects.tsx
new file mode 100644
index 0000000..d49a3d2
--- /dev/null
+++ b/src/pages/Projects.tsx
@@ -0,0 +1,24 @@
+import CardLink from '../components/CardLink';
+import { projects, type Project } from '../data/projects';
+
+export default function ProjectsPage() {
+ return (
+ <>
+ <title>߸ projects</title>
+ <h1>Projects</h1>
+
+ <ul className="space-y-2">
+ {projects.map((p: Project) => (
+ <li key={p.name}>
+ <CardLink
+ title={p.name}
+ body={p.description}
+ href={p.url}
+ imgSrc={p.image}
+ />
+ </li>
+ ))}
+ </ul>
+ </>
+ );
+} \ No newline at end of file