changed website design

This commit is contained in:
2026-09-22 17:23:20 +02:00
parent 1b52d55f02
commit b0b9749079
13 changed files with 438 additions and 106 deletions
+1 -1
View File
@@ -10,7 +10,7 @@
<link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link <link
href="https://fonts.googleapis.com/css2?family=Instrument+Serif:ital@0;1&family=Space+Mono:ital,wght@0,400;0,700;1,400;1,700&display=swap" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=Space+Mono:ital,wght@0,400;0,700;1,400;1,700&display=swap"
rel="stylesheet"> rel="stylesheet">
</head> </head>
View File
+1 -3
View File
@@ -1,6 +1,4 @@
import { useState } from 'react' import { Routes, Route, Navigate } from "react-router-dom";
import { BrowserRouter as Router, Routes, Route, Navigate } from "react-router-dom";
import './App.css'
import Menu from './Menu' import Menu from './Menu'
import Homepage from './Homepage'; import Homepage from './Homepage';
import Bird_CNN from './Bird_CNN'; import Bird_CNN from './Bird_CNN';
+3 -10
View File
@@ -86,16 +86,9 @@ function Bird_CNN() {
<> <>
<div className='site-box'> <div className='site-box'>
<h1 className='site-headline'>Bird Species Expert</h1> <h1 className='site-headline'>Bird Species Expert</h1>
<div <div className={styles["content-box"]}>
className={styles["content-box"]}
style={{
display: "flex",
flexWrap: "wrap",
justifyContent: "center"
}}
>
<div className={`${styles["explanation-box"]} ${styles.left}`}> <div className={`${styles["explanation-box"]} ${styles.left}`}>
<h2 style={{ color: "rgb(47, 168, 208)" }}>Explanation</h2> <h2 className={styles["explanation-heading"]}>Explanation</h2>
<span> <span>
Select and upload an image to our bird classification model. Select and upload an image to our bird classification model.
@@ -218,7 +211,7 @@ function Bird_CNN() {
</div> </div>
<div className={`${styles["explanation-box"]} ${styles.right}`}> <div className={`${styles["explanation-box"]} ${styles.right}`}>
<h3>Model Architecture</h3> <h3 className={styles["explanation-heading"]}>Model Architecture</h3>
<div style={{ display: "inline" }}> <div style={{ display: "inline" }}>
<span> <span>
+32 -11
View File
@@ -1,3 +1,11 @@
.species-list {
padding-left: 24px;
margin: 12px 0 0 0;
display: flex;
flex-direction: column;
gap: 6px;
}
.species-item { .species-item {
list-style: none; list-style: none;
position: relative; position: relative;
@@ -26,19 +34,31 @@ a:hover {
border-color: #c59eff; border-color: #c59eff;
} }
.content-box {
width: 100%;
display: flex;
flex-wrap: wrap;
align-items: flex-start;
justify-content: center;
gap: 30px;
}
.explanation-box { .explanation-box {
flex: 1; flex: 1;
min-width: 300px; min-width: 280px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
flex-wrap: wrap; gap: 4px;
overflow: hidden; padding: 10px 0;
border-radius: 12px; }
margin: 50px 50px 50px 50px;
.explanation-heading {
margin-top: 0;
color: var(--surface);
} }
.image-box { .image-box {
width: 100%;
height: fit-content; height: fit-content;
margin-top: 40px; margin-top: 40px;
margin-bottom: 40px; margin-bottom: 40px;
@@ -58,7 +78,7 @@ a:hover {
user-select: none; user-select: none;
height: auto; height: auto;
max-height: 400px; max-height: 400px;
max-width: min(700px, 80vw); max-width: 100%;
} }
.image-box img:hover { .image-box img:hover {
@@ -124,12 +144,13 @@ a:hover {
.request-box { .request-box {
height: fit-content; height: fit-content;
width: min(700px, 80%); width: min(500px, 100%);
min-width: 0;
padding: 10px 0;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
flex: 0 0 auto; flex: 0 1 auto;
margin: 0px 50px 0px 50px;
} }
.request-box * { .request-box * {
@@ -148,9 +169,9 @@ a:hover {
} }
.response-block { .response-block {
width: 80%; width: 100%;
height: 100px; height: 100px;
margin: 40px 40px 100px 40px; margin: clamp(20px, 5vw, 40px) 0 clamp(20px, 6vw, 60px) 0;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
+44 -3
View File
@@ -1,8 +1,49 @@
import { Link } from 'react-router-dom';
import styles from './Homepage.module.css';
const projects = [
{
to: "/bird_cnn",
title: "Bird Species Expert",
description: "A convolutional neural network that classifies bird species from a photo, with a live confidence score.",
tag: "CNN · PyTorch",
},
{
to: "/object_detection",
title: "Face Detection",
description: "A YOLO object detection model running on a live webcam feed, streamed to the server over a websocket.",
tag: "YOLO · Realtime",
},
{
to: "/reviews",
title: "Reviews",
description: "Leave a short, anonymous review of this site or my portfolio.",
tag: "Feedback",
},
];
function Homepage() { function Homepage() {
return ( return (
<> <div className='site-box'>
<h1 style={{ margin: "120px 40px 40px 40px" }}>Work in Progress, ignore this page and visit other Pages</h1> <h1 className='site-headline'>Marvin Krausser</h1>
</>
<p className={styles.intro}>
I build and deploy small machine learning projects end to end &mdash;
from training the model to serving it behind a live API. Below are a
couple of interactive demos, running on real models.
</p>
<div className={styles.grid}>
{projects.map(({ to, title, description, tag }) => (
<Link key={to} to={to} className={`card ${styles.tile}`}>
<span className={styles.tag}>{tag}</span>
<h2 className={styles.tileTitle}>{title}</h2>
<p className={styles.tileText}>{description}</p>
<span className={styles.cta}>Try it &rarr;</span>
</Link>
))}
</div>
</div>
); );
} }
+53
View File
@@ -0,0 +1,53 @@
.intro {
max-width: 65ch;
font-size: 1.1rem;
margin: -10px 0 50px 0;
}
.grid {
width: 100%;
display: grid;
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
gap: 24px;
}
.tile {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 10px;
text-decoration: none;
transition: border-color 0.15s, transform 0.15s, background-color 0.15s;
}
.tile:hover {
border-color: var(--surface);
background: rgba(47, 168, 208, 0.08);
transform: translateY(-3px);
}
.tag {
font-family: "Space Mono", monospace;
font-size: 0.75rem;
letter-spacing: 0.03em;
color: var(--surface);
text-transform: uppercase;
}
.tileTitle {
margin: 0;
font-size: 1.7rem;
color: var(--text);
}
.tileText {
margin: 0;
color: var(--text-dim);
flex-grow: 1;
}
.cta {
margin-top: 8px;
font-weight: bold;
color: var(--surface);
}
+124 -15
View File
@@ -1,29 +1,138 @@
#navbar-main { #navbar-main {
width: 100%; width: 100%;
height: 80px; height: var(--nav-height);
display: flex;
gap: 20px;
justify-content: center;
align-items: center;
border-bottom: 1px solid rgb(47, 168, 208);
position: fixed; position: fixed;
top: 0; top: 0;
left: 0; left: 0;
z-index: 10; z-index: 100;
background: rgb(4, 0, 10); background: rgba(4, 0, 10, 0.75);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
border-bottom: 1px solid var(--surface-line);
}
#navbar-inner {
max-width: 1200px;
height: 100%;
margin: 0 auto;
padding: 0 clamp(16px, 4vw, 40px);
display: flex;
align-items: center;
justify-content: space-between;
gap: 20px;
}
.navbar-brand {
font-family: 'Inter', system-ui, sans-serif;
font-weight: 600;
letter-spacing: -0.01em;
font-size: 1.2rem;
text-decoration: none;
color: var(--text);
white-space: nowrap;
flex-shrink: 0;
}
.navbar-brand:hover {
color: var(--surface);
}
#navbar-links {
display: flex;
align-items: center;
gap: 8px;
} }
.navbar-item { .navbar-item {
text-decoration: none; text-decoration: none;
height: 80px; color: var(--text-dim);
width: 120px; padding: 8px 16px;
display: flex; border-radius: 20px;
align-items: center; font-size: 1rem;
justify-content: center;
font-size: 1.1rem;
font-weight: bold; font-weight: bold;
white-space: nowrap;
transition: background-color 0.15s, color 0.15s;
} }
.navbar-item:hover { .navbar-item:hover {
background-color: rgb(47, 168, 208); color: var(--text);
background-color: var(--surface-dim);
}
.navbar-item.active {
color: black;
background-color: var(--surface);
}
#navbar-toggle {
display: none;
flex-direction: column;
justify-content: center;
align-items: center;
gap: 5px;
width: 40px;
height: 40px;
padding: 0;
background: transparent;
border: none;
cursor: pointer;
flex-shrink: 0;
z-index: 101;
}
#navbar-toggle .bar {
width: 22px;
height: 2px;
background: var(--text);
border-radius: 2px;
transition: transform 0.2s ease, opacity 0.2s ease;
}
#navbar-toggle .bar-1.open {
transform: translateY(7px) rotate(45deg);
}
#navbar-toggle .bar-2.open {
opacity: 0;
}
#navbar-toggle .bar-3.open {
transform: translateY(-7px) rotate(-45deg);
}
@media (max-width: 720px) {
#navbar-toggle {
display: flex;
}
#navbar-links {
position: fixed;
top: var(--nav-height);
left: 0;
width: 100%;
flex-direction: column;
align-items: stretch;
gap: 0;
background: rgba(4, 0, 10, 0.97);
backdrop-filter: blur(10px);
border-bottom: 1px solid var(--surface-line);
max-height: 0;
overflow: hidden;
transition: max-height 0.25s ease;
}
#navbar-links.open {
max-height: 320px;
}
.navbar-item {
border-radius: 0;
padding: 16px 24px;
text-align: center;
}
.navbar-item.active {
background-color: var(--surface-dim);
color: var(--surface);
}
} }
+44 -7
View File
@@ -1,16 +1,53 @@
import { Link } from 'react-router-dom'; import { useState } from 'react';
import { NavLink } from 'react-router-dom';
import './Menu.css' import './Menu.css'
const links = [
{ to: "/", label: "Home" },
{ to: "/bird_cnn", label: "Birds" },
{ to: "/object_detection", label: "YOLO" },
{ to: "/reviews", label: "Reviews" },
];
function Menu() { function Menu() {
const [open, setOpen] = useState(false);
const linkClass = ({ isActive }) =>
`navbar-item${isActive ? " active" : ""}`;
return ( return (
<>
<nav id="navbar-main"> <nav id="navbar-main">
<Link className='navbar-item' to="/">Homepage</Link> <div id="navbar-inner">
<Link className='navbar-item' to="/bird_cnn">Birds</Link> <NavLink to="/" className="navbar-brand" onClick={() => setOpen(false)}>
<Link className='navbar-item' to="/object_detection">YOLO</Link> Marvin Krausser
<Link className='navbar-item' to="/reviews">Reviews</Link> </NavLink>
<button
id="navbar-toggle"
aria-label={open ? "Close menu" : "Open menu"}
aria-expanded={open}
onClick={() => setOpen(!open)}
>
<span className={open ? "bar bar-1 open" : "bar bar-1"} />
<span className={open ? "bar bar-2 open" : "bar bar-2"} />
<span className={open ? "bar bar-3 open" : "bar bar-3"} />
</button>
<div id="navbar-links" className={open ? "open" : ""}>
{links.map(({ to, label }) => (
<NavLink
key={to}
end={to === "/"}
className={linkClass}
to={to}
onClick={() => setOpen(false)}
>
{label}
</NavLink>
))}
</div>
</div>
</nav> </nav>
</>
); );
} }
+4 -4
View File
@@ -174,9 +174,9 @@ function Object_Detection() {
return ( return (
<div className='site-box'> <div className='site-box'>
<h1 className='site-headline'>Face Detection</h1> <h1 className='site-headline'>Face Detection</h1>
<div id={styles["text-container-introduction"]}> <div className={styles["text-container-introduction"]}>
<p> <p>
The model’s performance has been limited to 2 FPS due to low-end server hardware constraints. The model's performance has been limited to 2 FPS due to low-end server hardware constraints.
</p> </p>
<p> <p>
The webcam video is transmitted to a server for processing. The server does not store, train on, or use the data for any purpose other than face detection. The webcam video is transmitted to a server for processing. The server does not store, train on, or use the data for any purpose other than face detection.
@@ -196,13 +196,13 @@ function Object_Detection() {
{running ? "End Detection" : "Start Detection"} {running ? "End Detection" : "Start Detection"}
</label> </label>
</div> </div>
<div id={styles["video-container"]}> <div className={styles["video-container"]}>
<video autoPlay={true} ref={videoRef} style={{ width: "100%", height: "100%", position: "absolute", zIndex: "3" }}></video> <video autoPlay={true} ref={videoRef} style={{ width: "100%", height: "100%", position: "absolute", zIndex: "3" }}></video>
<canvas ref={canvasRefBBox} style={{ width: "100%", height: "100%", position: "absolute", zIndex: "4" }} /> <canvas ref={canvasRefBBox} style={{ width: "100%", height: "100%", position: "absolute", zIndex: "4" }} />
<canvas ref={canvasRefSending} style={{ width: "100%", height: "100%", display: "none" }} /> <canvas ref={canvasRefSending} style={{ width: "100%", height: "100%", display: "none" }} />
</div> </div>
<div id={styles["text-container-explanation"]}> <div className={styles["text-container-explanation"]}>
<p> <p>
This Project is an implementation of the YOLO (You Only Look Once) algorithm. It was implemented with Pytorch and trained on a Dataset from Roboflow. This Project is an implementation of the YOLO (You Only Look Once) algorithm. It was implemented with Pytorch and trained on a Dataset from Roboflow.
</p> </p>
+29 -25
View File
@@ -1,36 +1,40 @@
#video-container { .video-container {
margin: 0px; margin: clamp(20px, 5vh, 50px) 0;
width: min(70vh, 90vw); width: min(70vh, 100%);
height: min(70vh, 90vw); max-width: 600px;
aspect-ratio: 1 / 1;
background-color: black; background-color: black;
border-radius: var(--radius);
overflow: hidden;
align-self: center;
position: relative;
border: 1px solid var(--surface-line);
}
.button-div {
width: fit-content;
align-self: center;
margin: clamp(24px, 6vw, 50px) 0;
}
.text-container-explanation {
margin: clamp(30px, 6vw, 70px) 0 0 0;
width: 100%;
max-width: 800px;
align-self: center; align-self: center;
position: relative; position: relative;
} }
.button-div{ .text-container-introduction {
width: 200px; margin: clamp(20px, 5vw, 50px) 0 0 0;
align-self: center; width: 100%;
margin: 50px; max-width: 800px;
}
#text-container-explanation {
margin: 70px 0 0 0;
width: 80vw;
height: 300px;
align-self: center; align-self: center;
position: relative; position: relative;
} }
#text-container-introduction { .text-container-introduction p,
margin: 50px 0 20px 0; .text-container-explanation p {
width: 70vw; margin: 20px 0;
height: fit-content;
align-self: center;
position: relative;
left: -10vw;
}
#text-container-introduction p {
margin: 40px 0 40px 0;
font-size: 1.1rem; font-size: 1.1rem;
} }
+8 -8
View File
@@ -1,17 +1,15 @@
.review-description { .review-description {
margin: 20px 70px 70px 70px; margin: 0 0 clamp(30px, 6vw, 60px) 0;
font-size: 1.1rem; font-size: 1.1rem;
width: min(800px, 70vw); max-width: 65ch;
height: fit-content; height: fit-content;
} }
.input-box { .input-box {
display: grid; display: grid;
gap: 10px; gap: 30px 60px;
column-gap: 10vw; width: 100%;
width: 98vw; max-width: 800px;
max-width: calc(3 * 200px + 10vw * 2);
min-height: 100vh;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
grid-auto-rows: fit-content(300px); grid-auto-rows: fit-content(300px);
@@ -35,6 +33,8 @@
.input-component-box.text { .input-component-box.text {
grid-column: 1 / -1; grid-column: 1 / -1;
width: 100%;
justify-items: center;
} }
.description { .description {
@@ -167,7 +167,7 @@
.review-text { .review-text {
resize: none; resize: none;
overflow:auto; overflow:auto;
width: min(600px, 98vw); width: min(600px, 100%);
height: 400px; height: 400px;
border-radius: 8px; border-radius: 8px;
border: 2px solid var(--bg); border: 2px solid var(--bg);
+88 -12
View File
@@ -1,43 +1,98 @@
:root {
--surface: rgb(47, 168, 208);
--surface-dim: rgba(47, 168, 208, 0.15);
--surface-line: rgba(47, 168, 208, 0.35);
--bg: rgb(4, 0, 10);
--bg-elevated: rgb(13, 10, 22);
--text: rgb(255, 255, 255);
--text-dim: rgba(255, 255, 255, 0.68);
--accent-2: #c59eff;
--radius: 16px;
--nav-height: 70px;
--page-pad: clamp(20px, 6vw, 100px);
}
html { html {
font-family: system-ui, sans-serif; font-family: system-ui, -apple-system, sans-serif;
color: rgb(255, 255, 255); color: var(--text);
font-size: 1rem; font-size: 1rem;
line-height: 1.7; line-height: 1.7;
-webkit-text-size-adjust: 100%;
} }
body { body {
background: var(--bg); background: var(--bg);
background-image:
radial-gradient(ellipse 900px 500px at 15% -10%, var(--surface-dim), transparent 60%),
radial-gradient(ellipse 700px 500px at 100% 10%, rgba(197, 158, 255, 0.08), transparent 55%);
background-attachment: fixed;
margin: 0; margin: 0;
padding: 0; padding: 0;
--surface: rgb(47, 168, 208); min-height: 100vh;
--bg: rgb(4, 0, 10); overflow-x: hidden;
} }
button { button {
font-size: 1rem; font-size: 1rem;
font-family: inherit;
} }
a { a {
color: rgb(255, 255, 255); color: var(--text);
} }
h1, h1,
h2, h2,
h3, h3,
h4 { h4 {
font-family: 'Times New Roman', serif; font-family: 'Inter', system-ui, sans-serif;
font-weight: 700;
letter-spacing: -0.02em;
line-height: 1.15;
}
p {
color: var(--text-dim);
} }
* { * {
box-sizing: border-box; box-sizing: border-box;
} }
*:focus-visible {
outline: 2px solid var(--accent-2);
outline-offset: 3px;
}
::selection {
background: var(--surface);
color: black;
}
/* scrollbar theming */
::-webkit-scrollbar {
width: 10px;
height: 10px;
}
::-webkit-scrollbar-track {
background: transparent;
}
::-webkit-scrollbar-thumb {
background: rgba(255, 255, 255, 0.2);
border-radius: 10px;
}
::-webkit-scrollbar-thumb:hover {
background: var(--surface);
}
.site-headline { .site-headline {
margin: 50px min(100px, 10vw) 50px min(100px, 10vw); margin: 0 0 clamp(24px, 5vw, 50px) 0;
color: var(--surface); color: var(--surface);
font-weight: bold; font-weight: bold;
font-size: clamp(1rem, min(8vw, 8vh), 4rem); font-size: clamp(2.2rem, 8vw, 4rem);
position: relative; position: relative;
width: fit-content; width: fit-content;
} }
@@ -45,8 +100,8 @@ h4 {
.site-headline::before { .site-headline::before {
content: ""; content: "";
position: absolute; position: absolute;
bottom: 0; bottom: -6px;
right: 0; left: 0;
height: 4px; height: 4px;
width: 100%; width: 100%;
border-radius: 20px; border-radius: 20px;
@@ -54,11 +109,30 @@ h4 {
} }
.site-box { .site-box {
margin-top: 150px; margin-top: calc(var(--nav-height) + clamp(30px, 8vh, 80px));
margin-bottom: clamp(40px, 8vh, 100px);
width: 100%; width: 100%;
max-width: 1200px;
padding: 0 var(--page-pad);
height: fit-content; height: fit-content;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
margin-left: auto;
margin-right: auto;
}
.page-lead {
color: var(--text-dim);
font-size: 1.05rem;
max-width: 70ch;
margin: -10px 0 40px 0;
}
.card {
background: var(--bg-elevated);
border: 1px solid var(--surface-line);
border-radius: var(--radius);
padding: clamp(20px, 3vw, 32px);
} }
.custom-button { .custom-button {
@@ -75,6 +149,7 @@ h4 {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
transition: border-color 0.15s, color 0.15s, background-color 0.15s;
} }
.custom-button:hover { .custom-button:hover {
@@ -90,4 +165,5 @@ h4 {
.custom-button.inactive:hover { .custom-button.inactive:hover {
border-color: var(--surface); border-color: var(--surface);
color: black;
} }