update website

This commit is contained in:
2026-09-19 13:53:27 +02:00
parent d673f3f0e5
commit 102e419b1d
30 changed files with 344 additions and 31 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 221 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 214 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 278 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

+46 -10
View File
@@ -1,27 +1,63 @@
import Link from "next/link";
import Image from "next/image";
export const metadata = {title: "About"};
const timeline = [
{year: "2018", title: "The first spark", text: "I discovered programming through Scratch, turning small ideas into simple, playable games.", skills: ["Scratch", "Game logic"]},
{year: "2022", title: "Into the web", text: "I began building websites and learned the foundations of HTML, CSS, and accessible interface design.", skills: ["HTML", "CSS", "JavaScript"]},
{year: "2022", title: "From screen to hardware", text: "A Raspberry Pi opened the door to Python scripting, Linux, and the fun of connecting software with real hardware.", skills: ["Python", "Linux", "Raspberry Pi"]},
{year: "2024", title: "Full stack & beyond", text: "I expanded into databases, backend systems, Java, and larger projects—while continuing to sharpen my eye for frontend craft.", skills: ["SQL", "Python", "Java", "JavaScript"]},
{
year: "2018",
title: "The first spark",
text: "I discovered programming through Scratch, turning small ideas into simple, playable games.",
skills: ["Scratch", "Game logic"]
},
{
year: "2022",
title: "Web & hardware",
text: "I started building websites and exploring Python and Linux with a Raspberry Pi.",
skills: ["HTML & CSS", "JavaScript", "Python", "Linux"]
},
{
year: "2025", title: "Useful tools & web apps",
text: "From Autoclicker and this portfolio to AI chat and Discord bots, I began building tools with interfaces, accounts, and backend systems.",
skills: ["Python", "Next.js", "Express", "SQLite"],
},
{
year: "2026", title: "From web to mobile",
text: "The IDPA Webshop connected a React storefront to a REST API. Shoppy took that work to mobile with barcode scanning and personal shopping recommendations.",
skills: ["React", "REST APIs", "React Native", "Expo"],
},
{
year: "2026", title: "Games & gameplay systems",
text: "I expanded my Minecraft work with Ascend and companion mods, while building Linux Distro Roguelite, a Unity card game inspired by Linux and programming.",
skills: ["Java", "Forge", "Unity", "C#"],
},
];
export default function About() {
return (<>
<header className="page-head"><div className="shell page-head-row">
<div><p className="eyebrow">About me</p><h1 className="page-title">Always<br/>learning.</h1></div>
<p className="page-intro">I’m a developer driven by curiosity, thoughtful details, and the satisfying moment when a complex problem finally clicks.</p>
</div></header>
<header className="page-head">
<div className="shell page-head-row">
<div><p className="eyebrow">About me</p><h1 className="page-title">Always<br/>learning.</h1></div>
<p className="page-intro">I’m a developer driven by curiosity and that satisfying moment when a complex
problem finally clicks.</p>
</div>
</header>
<div className="shell about-grid">
<aside className="about-sticky">
<div className="image-frame about-portrait portrait-frame">
<Image src="/images/Fred3.png" alt="Frederik outdoors in a striped shirt" fill
sizes="(max-width: 800px) 100vw, 40vw"/>
</div>
<p className="eyebrow">My path so far</p><h2>From playful experiments to complete products.</h2>
<p>Every project has added a new tool, a sharper instinct, or a better question to ask.</p>
<p>Every project has added a new tool or a better idea (occasionally both).</p>
<Link href="/projects" className="button">View projects</Link>
</aside>
<div className="timeline">{timeline.map(item => (
<article className="timeline-item" key={`${item.year}-${item.title}`}>
<div className="timeline-year">{item.year}</div>
<div><h3>{item.title}</h3><p>{item.text}</p><div className="skills">{item.skills.map(skill => <span className="skill" key={skill}>{skill}</span>)}</div></div>
<div>
<h3>{item.title}</h3><p>{item.text}</p>
<div className="skills">{item.skills.map(skill => <span className="skill"
key={skill}>{skill}</span>)}</div>
</div>
</article>
))}</div>
</div>
+147
View File
@@ -291,6 +291,11 @@ button {
margin: 26px 0 34px;
}
.featured h2 {
font-size: clamp(3rem, 5vw, 4.5rem);
line-height: 1;
}
.image-frame {
position: relative;
overflow: hidden;
@@ -319,6 +324,20 @@ button {
filter: saturate(1);
}
.landscape-frame {
aspect-ratio: 16/9;
}
.landscape-frame img {
object-fit: contain;
object-position: center;
filter: none;
}
.landscape-frame:hover img {
transform: none;
}
.page-head {
padding: 110px 0 80px;
border-bottom: 1px solid var(--line);
@@ -436,6 +455,20 @@ button {
transition: transform .45s ease;
}
.github-project-image {
aspect-ratio: 2/1;
background: white;
}
.github-project-image img {
display: block;
object-fit: contain;
}
.project-card:hover .github-project-image img {
transform: none;
}
.project-card:hover .project-image img {
transform: scale(1.035);
}
@@ -470,6 +503,120 @@ button {
line-height: 1.6;
}
.portrait-frame {
background: radial-gradient(ellipse at 50% 100%, #e6b797, var(--paper-2) 75%);
}
.portrait-frame img {
object-fit: contain;
object-position: center bottom;
padding: 24px 24px 0;
filter: none;
}
.about-portrait {
aspect-ratio: 4/3;
margin-bottom: 32px;
}
.text-link {
display: inline-block;
border-bottom: 1px solid var(--accent);
padding-bottom: 4px;
color: var(--ink);
font-size: .9rem;
font-weight: 600;
}
.project-toolbar {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
align-items: center;
gap: 20px;
padding-top: 40px;
}
.project-filters {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.project-filters button {
min-height: 44px;
padding: 10px 16px;
border: 1px solid var(--line);
background: transparent;
color: var(--ink);
cursor: pointer;
font-size: .8rem;
}
.project-filters button:hover { border-color: var(--ink); }
.project-filters button[aria-pressed="true"] {
background: var(--ink);
color: var(--paper);
}
.project-count, .project-source {
color: var(--muted);
font: 500 .73rem var(--font-geist-mono), monospace;
}
.project-collection .projects-grid { padding-top: 36px; }
.project-card { min-width: 0; }
.project-cover {
display: flex;
flex-direction: column;
justify-content: space-between;
aspect-ratio: 16/9;
padding: clamp(20px, 3vw, 36px);
overflow: hidden;
border: 1px solid var(--line);
background-color: #e4e8dc;
background-image: linear-gradient(var(--line) 1px, transparent 1px), linear-gradient(90deg, var(--line) 1px, transparent 1px);
background-size: 40px 40px;
}
.project-cover-web { background-color: #ead9c7; }
.project-cover-game { background-color: #dce3ec; }
.project-cover-tool { background-color: #e5dfe9; }
.project-cover-label {
font: 600 .65rem var(--font-geist-mono), monospace;
text-transform: uppercase;
letter-spacing: .1em;
}
.project-cover-mark {
align-self: center;
font: 600 clamp(2rem, 4.5vw, 4rem)/1.2 var(--font-geist-mono), monospace;
letter-spacing: -.07em;
white-space: nowrap;
transition: transform .25s ease;
}
.project-cover-tagline { font-size: .8rem; line-height: 1.4; }
.project-card:hover .project-cover-mark { transform: translateY(-4px); }
.project-source {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
gap: 12px;
margin-top: 22px;
padding-top: 16px;
border-top: 1px solid var(--line);
}
.project-card:focus-visible, .project-filters button:focus-visible, .text-link:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 6px;
}
.contact-layout {
display: grid;
grid-template-columns: 1.1fr .9fr;
+2 -2
View File
@@ -7,8 +7,8 @@ const geistSans = Geist({variable: "--font-geist-sans", subsets: ["latin"]});
const geistMono = Geist_Mono({variable: "--font-geist-mono", subsets: ["latin"]});
export const metadata = {
title: {default: "Fretux — Developer & digital maker", template: "%s — Fretux"},
description: "Portfolio of Frederik Spirgi, a developer building thoughtful digital experiences.",
title: {default: "Fretux | Developer & digital maker", template: "%s | Fretux"},
description: "Portfolio of Frederik Spirgi, software developer",
icons: {icon: "/favicon.ico"},
};
+3 -3
View File
@@ -38,11 +38,11 @@ export default function Home() {
<div>
<p className="eyebrow">A little about me</p>
<h2>Curious by default.</h2>
<p>I like learning how things work, then making them work better. My projects move between polished interfaces, practical backend systems, and playful experiments.</p>
<p>I like learning how things work, then making them work better. My projects move between websites, games, and other experiments.</p>
<Link className="button light" href="/about">My journey</Link>
</div>
<div className="image-frame">
<Image src="/images/portfolio1.jpg" alt="A featured development project" fill sizes="(max-width: 800px) 100vw, 60vw"/>
<div className="image-frame landscape-frame">
<Image src="/images/portfolio2.jpg" alt="Frederik standing with outstretched arms beside the sea and rocky coastal cliffs" fill sizes="(max-width: 800px) calc(100vw - 30px), (max-width: 1228px) 60vw, 710px"/>
</div>
</div>
</section>
+36
View File
@@ -0,0 +1,36 @@
"use client";
import {useState} from "react";
import Image from "next/image";
import {projects} from "./projects";
const categories = ["All", "Web & apps", "Minecraft", "Games", "Tools & bots"];
export default function ProjectGallery() {
const [category, setCategory] = useState("All");
const visibleProjects = projects.filter(project => category === "All" || project.category === category);
return <section className="shell project-collection" aria-label="Project collection">
<div className="project-toolbar">
<div className="project-filters" role="group" aria-label="Filter projects by category">
{categories.map(item => <button type="button" key={item} aria-pressed={category === item}
onClick={() => setCategory(item)}>{item}</button>)}
</div>
<p className="project-count" role="status">{visibleProjects.length} projects</p>
</div>
<div className="projects-grid">{visibleProjects.map(project => (
<a className="project-card" href={`https://github.com/Fretuks/${project.repo}`}
target="_blank" rel="noopener noreferrer" key={project.repo}>
<div className="project-image github-project-image">
<Image src={`/images/projects/${project.repo}.png`}
alt={`${project.title} GitHub repository preview`}
width={1200} height={600} sizes="(max-width: 800px) 100vw, 50vw"/>
</div>
<div className="project-meta"><h2>{project.title}</h2><span aria-hidden="true">↗</span></div>
<p>{project.description}</p>
<div className="skills">{project.stack.map(skill => <span className="skill" key={skill}>{skill}</span>)}</div>
<div className="project-source"><span>View on GitHub</span>{project.attribution && <span>{project.attribution}</span>}</div>
</a>
))}</div>
</section>;
}
+8 -16
View File
@@ -1,23 +1,15 @@
import Image from "next/image";
import ProjectGallery from "./ProjectGallery";
import {projects} from "./projects";
export const metadata = {title: "Projects"};
const projects = [
{title: "Red Lake Stats", description: "An interactive JavaScript and Python tool for calculating damage statistics in the Roblox game The Red Lake.", image: "/images/portfolio-project1.png", link: "https://fretux.ch/trlstats"},
{title: "SBB History", description: "An interactive journey through the creation of Switzerland’s railway company, featuring an explorable map.", image: "/images/portfolio-project2.png", link: "https://sbb-history.fretux.ch"},
{title: "Fretux.ch", description: "My first portfolio—gamified to make exploring personal work feel more playful and memorable.", image: "/images/portfolio-project3.png", link: "https://fretux.ch"},
{title: "KnockedBack", description: "A Minecraft mod that reimagines what happens when a player dies.", image: "/images/KnockedBack.png", link: "https://modrinth.com/mod/knockedback"},
{title: "Stalinium", description: "A collaborative Minecraft mod adding the fictional Stalinium ore and a new layer of progression.", image: "/images/Stalinium.png", link: "https://modrinth.com/mod/stalinium-mod"},
];
export default function ProjectsPage() {
return (<>
<header className="page-head"><div className="shell page-head-row">
<div><p className="eyebrow">Selected work</p><h1 className="page-title">Things I’ve<br/>made.</h1></div>
<p className="page-intro">A mix of useful tools, interactive websites, and game modifications—each built to explore a new idea.</p>
<div><p className="eyebrow">Open-source work</p><h1 className="page-title">Things I’ve<br/>made.</h1></div>
<div className="page-intro">
<p>From everyday tools to mods and games, a collection of {projects.length} projects to explore and build on.</p>
<a className="text-link" href="https://github.com/Fretuks" target="_blank" rel="noopener noreferrer">Find me on GitHub <span aria-hidden="true">↗</span></a>
</div>
</div></header>
<div className="shell projects-grid">{projects.map(project => (
<a className="project-card" href={project.link} target="_blank" rel="noopener noreferrer" key={project.title}>
<div className="project-image"><Image src={project.image} alt={`${project.title} project preview`} width={800} height={550}/></div>
<div className="project-meta"><h2>{project.title}</h2><span aria-hidden="true">↗</span></div><p>{project.description}</p>
</a>
))}</div>
<ProjectGallery/>
</>);
}
+102
View File
@@ -0,0 +1,102 @@
// Reviewed against public GitHub repositories on 2026-09-18.
// Include implemented apps, tools, libraries, games, and substantial creative work.
// Exclude Fretuks (profile), M324im_Code (course exercises), and the-ims-stack
// (starter page). Developed forks are explicitly credited below.
export const projects = [
{
repo: "linuxdistro-roguelite", title: "Linux Distro Roguelite", category: "Games",
mark: "~/run", tagline: "Choose your distro. Build your deck.",
description: "A Unity deck-building roguelite with Linux distributions, programming-language cards, turn-based combat, and persistent progression.",
stack: ["Unity", "C#"],
},
{
repo: "KrisHD_AI", title: "Elsewhere", category: "Web & apps",
mark: "hello_", tagline: "A space for every conversation.",
description: "An AI chat workspace with streaming conversations, custom personas, roleplay sessions, and a shared persona market. Built with Express and SQLite.",
stack: ["JavaScript", "Express", "SQLite"],
},
{
repo: "Shoppy", title: "Shoppy", category: "Web & apps",
mark: "scan/+", tagline: "A little more clarity in every shop.",
description: "A mobile shopping companion that scans barcodes and evaluates products against personal preferences. Includes shopping lists, allergy settings, and cached product data for offline use.",
stack: ["React Native", "Expo", "Open Food Facts"],
},
{
repo: "Ascend", title: "Ascend", category: "Minecraft",
mark: "lvl ↑", tagline: "Make every level your own.",
description: "A Minecraft RPG progression mod inspired by Deepwoken, with experience, ten upgradeable attributes, knowledge, and shrines for reshaping your build.",
stack: ["Java", "Forge"],
},
{
repo: "IDPA26_WebShop", title: "IDPA Webshop", category: "Web & apps",
mark: "cart/+", tagline: "From the catalogue to checkout.",
description: "A full-stack school project with product browsing, accounts, carts, orders, and an administration interface, backed by a REST API.",
stack: ["React", "Express", "SQL"],
},
{
repo: "linux_distro_roguelite_promotion", title: "Roguelite Launch Platform", category: "Web & apps",
mark: "join_", tagline: "A community before the first run.",
description: "A companion website and Quarkus backend for game pre-registration, platform preferences, email confirmation, community milestones, and rewards.",
stack: ["Java", "Quarkus", "PostgreSQL"],
},
{
repo: "SkillEngine", title: "Skill Engine", category: "Minecraft",
mark: "[⋅]-[⋅]", tagline: "The foundation for a new skill tree.",
description: "A reusable Forge framework for JSON-defined skill trees, with node requirements, skill points, tooltips, and server synchronization for other mods to build on.",
stack: ["Java", "Forge", "JSON"],
},
{
repo: "MindMotion", title: "Mind & Motion", category: "Minecraft",
mark: "mind/", tagline: "Keep your sanity. Find your tempo.",
description: "A configurable Minecraft mod introducing sanity, insanity, and combat tempo. Environmental effects and a defensive vent ability add new decisions to exploration and combat.",
stack: ["Java", "Forge"],
},
{
repo: "KnockedBack", title: "KnockedBack", category: "Minecraft",
mark: "1 HP", tagline: "A second chance before the end.",
description: "Replaces most lethal damage with a temporary knocked-out state, giving players a chance to recover, carry teammates to safety, or finish a fight.",
stack: ["Java", "Forge"],
},
{
repo: "Ailments", title: "Ascend: Ailments", category: "Minecraft",
mark: "+effect", tagline: "Give combat a lasting effect.",
description: "A Forge library of eight combat effects, including bleed, fear, and soul rot, with a public API, configurable interactions, and optional Ascend integration.",
stack: ["Java", "Forge"],
},
{
repo: "Gambling_API", title: "Virtual-Chip Poker", category: "Web & apps",
mark: "♠ / ♣", tagline: "One table. A real-time connection.",
description: "A multiplayer poker app with virtual chips, lobbies, live WebSocket play, and persistent accounts. Includes transaction recovery and an HTTP API.",
stack: ["Express", "WebSockets", "SQLite"], attribution: "Fork-based project",
},
{
repo: "TARS", title: "T.A.R.S.", category: "Tools & bots",
mark: "/tars", tagline: "A helping hand for the server.",
description: "A Python Discord bot combining moderation, reminders, server diagnostics, community commands, and a boost-points reward shop.",
stack: ["Python", "Discord"],
},
{
repo: "Discord_bot", title: "Discord Utility Bot", category: "Tools & bots",
mark: "/help", tagline: "Small commands. Everyday utility.",
description: "A Discord.js bot with moderation, virtual currency, quizzes, timestamps, weather, and Wikipedia lookups, plus per-server command permissions.",
stack: ["JavaScript", "Discord.js", "MongoDB"], attribution: "Fork-based project",
},
{
repo: "Autoclicker", title: "Autoclicker", category: "Tools & bots",
mark: "click()", tagline: "Put repetitive clicks on repeat.",
description: "A Python desktop utility with a Tkinter interface and configurable keyboard shortcuts to start and stop automated clicking.",
stack: ["Python", "Tkinter", "pynput"],
},
{
repo: "BruteForce", title: "BruteForce Lab", category: "Tools & bots",
mark: "auth/?", tagline: "Explore how a login holds up.",
description: "An educational local test application for exploring password attacks and defenses, pairing an Express login backend with controlled attack simulations.",
stack: ["JavaScript", "Express", "Security education"],
},
{
repo: "WebsitePortfolio2", title: "Fretux Portfolio", category: "Web & apps",
mark: "<fred/>", tagline: "Code with character.",
description: "The portfolio you are exploring: a responsive Next.js site bringing together my projects, background, and experiments in digital design.",
stack: ["Next.js", "React", "CSS"],
},
];