diff --git a/public/images/Fred3.png b/public/images/Fred3.png new file mode 100644 index 0000000..f00df7f Binary files /dev/null and b/public/images/Fred3.png differ diff --git a/public/images/KnockedBack.png b/public/images/KnockedBack.png deleted file mode 100644 index c492be8..0000000 Binary files a/public/images/KnockedBack.png and /dev/null differ diff --git a/public/images/Stalinium.png b/public/images/Stalinium.png deleted file mode 100644 index ad3dcd2..0000000 Binary files a/public/images/Stalinium.png and /dev/null differ diff --git a/public/images/portfolio-project1.png b/public/images/portfolio-project1.png deleted file mode 100644 index f805b67..0000000 Binary files a/public/images/portfolio-project1.png and /dev/null differ diff --git a/public/images/portfolio-project2.png b/public/images/portfolio-project2.png deleted file mode 100644 index 0a59a1c..0000000 Binary files a/public/images/portfolio-project2.png and /dev/null differ diff --git a/public/images/portfolio-project3.png b/public/images/portfolio-project3.png deleted file mode 100644 index 50c8d18..0000000 Binary files a/public/images/portfolio-project3.png and /dev/null differ diff --git a/public/images/portfolio1.jpg b/public/images/portfolio1.jpg deleted file mode 100644 index fc94781..0000000 Binary files a/public/images/portfolio1.jpg and /dev/null differ diff --git a/public/images/projects/Ailments.png b/public/images/projects/Ailments.png new file mode 100644 index 0000000..126e710 Binary files /dev/null and b/public/images/projects/Ailments.png differ diff --git a/public/images/projects/Ascend.png b/public/images/projects/Ascend.png new file mode 100644 index 0000000..66fb5fb Binary files /dev/null and b/public/images/projects/Ascend.png differ diff --git a/public/images/projects/Autoclicker.png b/public/images/projects/Autoclicker.png new file mode 100644 index 0000000..4451368 Binary files /dev/null and b/public/images/projects/Autoclicker.png differ diff --git a/public/images/projects/BruteForce.png b/public/images/projects/BruteForce.png new file mode 100644 index 0000000..625cb43 Binary files /dev/null and b/public/images/projects/BruteForce.png differ diff --git a/public/images/projects/Discord_bot.png b/public/images/projects/Discord_bot.png new file mode 100644 index 0000000..86a3efc Binary files /dev/null and b/public/images/projects/Discord_bot.png differ diff --git a/public/images/projects/Gambling_API.png b/public/images/projects/Gambling_API.png new file mode 100644 index 0000000..2897147 Binary files /dev/null and b/public/images/projects/Gambling_API.png differ diff --git a/public/images/projects/IDPA26_WebShop.png b/public/images/projects/IDPA26_WebShop.png new file mode 100644 index 0000000..7340889 Binary files /dev/null and b/public/images/projects/IDPA26_WebShop.png differ diff --git a/public/images/projects/KnockedBack.png b/public/images/projects/KnockedBack.png new file mode 100644 index 0000000..7edb1b8 Binary files /dev/null and b/public/images/projects/KnockedBack.png differ diff --git a/public/images/projects/KrisHD_AI.png b/public/images/projects/KrisHD_AI.png new file mode 100644 index 0000000..bc9c578 Binary files /dev/null and b/public/images/projects/KrisHD_AI.png differ diff --git a/public/images/projects/MindMotion.png b/public/images/projects/MindMotion.png new file mode 100644 index 0000000..39eb5bb Binary files /dev/null and b/public/images/projects/MindMotion.png differ diff --git a/public/images/projects/Shoppy.png b/public/images/projects/Shoppy.png new file mode 100644 index 0000000..b886a09 Binary files /dev/null and b/public/images/projects/Shoppy.png differ diff --git a/public/images/projects/SkillEngine.png b/public/images/projects/SkillEngine.png new file mode 100644 index 0000000..79dc6f6 Binary files /dev/null and b/public/images/projects/SkillEngine.png differ diff --git a/public/images/projects/TARS.png b/public/images/projects/TARS.png new file mode 100644 index 0000000..e8520bf Binary files /dev/null and b/public/images/projects/TARS.png differ diff --git a/public/images/projects/WebsitePortfolio2.png b/public/images/projects/WebsitePortfolio2.png new file mode 100644 index 0000000..f705ffe Binary files /dev/null and b/public/images/projects/WebsitePortfolio2.png differ diff --git a/public/images/projects/linux_distro_roguelite_promotion.png b/public/images/projects/linux_distro_roguelite_promotion.png new file mode 100644 index 0000000..f80e902 Binary files /dev/null and b/public/images/projects/linux_distro_roguelite_promotion.png differ diff --git a/public/images/projects/linuxdistro-roguelite.png b/public/images/projects/linuxdistro-roguelite.png new file mode 100644 index 0000000..40bb811 Binary files /dev/null and b/public/images/projects/linuxdistro-roguelite.png differ diff --git a/src/app/about/page.js b/src/app/about/page.js index 9aff1c3..805d912 100644 --- a/src/app/about/page.js +++ b/src/app/about/page.js @@ -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 (<> -
-

About me

Always
learning.

-

I’m a developer driven by curiosity, thoughtful details, and the satisfying moment when a complex problem finally clicks.

-
+
+
+

About me

Always
learning.

+

I’m a developer driven by curiosity and that satisfying moment when a complex + problem finally clicks.

+
+
{timeline.map(item => (
{item.year}
-

{item.title}

{item.text}

{item.skills.map(skill => {skill})}
+
+

{item.title}

{item.text}

+
{item.skills.map(skill => {skill})}
+
))}
diff --git a/src/app/globals.css b/src/app/globals.css index 9e1f432..8b7b2f1 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -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; diff --git a/src/app/layout.js b/src/app/layout.js index 56dc345..7096a8d 100644 --- a/src/app/layout.js +++ b/src/app/layout.js @@ -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"}, }; diff --git a/src/app/page.js b/src/app/page.js index b130d22..7a31061 100644 --- a/src/app/page.js +++ b/src/app/page.js @@ -38,11 +38,11 @@ export default function Home() {

A little about me

Curious by default.

-

I like learning how things work, then making them work better. My projects move between polished interfaces, practical backend systems, and playful experiments.

+

I like learning how things work, then making them work better. My projects move between websites, games, and other experiments.

My journey
-
- A featured development project +
+ Frederik standing with outstretched arms beside the sea and rocky coastal cliffs
diff --git a/src/app/projects/ProjectGallery.js b/src/app/projects/ProjectGallery.js new file mode 100644 index 0000000..91d3186 --- /dev/null +++ b/src/app/projects/ProjectGallery.js @@ -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
+
+
+ {categories.map(item => )} +
+

{visibleProjects.length} projects

+
+
{visibleProjects.map(project => ( + +
+ {`${project.title} +
+

{project.title}

+

{project.description}

+
{project.stack.map(skill => {skill})}
+
View on GitHub{project.attribution && {project.attribution}}
+
+ ))}
+
; +} diff --git a/src/app/projects/page.js b/src/app/projects/page.js index d618b77..3b1c484 100644 --- a/src/app/projects/page.js +++ b/src/app/projects/page.js @@ -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 (<>
-

Selected work

Things I’ve
made.

-

A mix of useful tools, interactive websites, and game modifications—each built to explore a new idea.

+

Open-source work

Things I’ve
made.

+
+

From everyday tools to mods and games, a collection of {projects.length} projects to explore and build on.

+ Find me on GitHub +
-
{projects.map(project => ( - -
{`${project.title}
-

{project.title}

{project.description}

-
- ))}
+ ); } diff --git a/src/app/projects/projects.js b/src/app/projects/projects.js new file mode 100644 index 0000000..067fd21 --- /dev/null +++ b/src/app/projects/projects.js @@ -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: "", 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"], + }, +];