From 57b3a72fa285edf3568fdd313dd6f5f97475b7a6 Mon Sep 17 00:00:00 2001 From: Eugene Fox Date: Thu, 11 Dec 2025 05:51:44 +0000 Subject: [PATCH 1/3] feat!: site refresh + dependency update --- .devcontainer/devcontainer.json | 7 +- COPYING | 10 +- .../decorations/pawprint-trail-vertical.svg | 4 +- app/_assets/decorations/pawprint-trail.svg | 4 +- app/_assets/illustrations/skills.ts | 8 + .../illustrations/skills/admin-skills.svg | 2 +- .../skills/architecture-skills.svg | 2 +- .../illustrations/skills/databases-skills.svg | 2 +- .../illustrations/skills/design-skills.svg | 2 +- .../illustrations/skills/devops-skills.svg | 1661 +++++--- .../illustrations/skills/dotnet-skills.svg | 3 +- .../illustrations/skills/nodejs-skills.svg | 3 +- .../illustrations/skills/security-skills.svg | 343 ++ app/_components/AlertMessage.tsx | 34 +- app/_components/CookieBanner.tsx | 1 + app/_components/RevokeConsentButton.tsx | 1 + app/_components/Sidemenu.tsx | 3 +- app/_data/FrontSection.module.scss | 13 - app/_data/FrontSection.tsx | 9 +- app/_data/bio.tsx | 17 +- app/_data/experience.ts | 20 - app/_data/experience.tsx | 95 + app/_data/projects.ts | 109 +- app/_data/skills.ts | 60 - app/_data/skills.tsx | 105 + app/_data/socials.ts | 2 +- app/_page_sections/AboutSection.module.scss | 2 +- app/_page_sections/AboutSection.tsx | 6 +- app/_page_sections/ContactSection.tsx | 2 +- .../ExperienceSection.module.scss | 67 +- app/_page_sections/ExperienceSection.tsx | 17 +- .../ProjectsSection.module.scss | 47 +- app/_page_sections/ProjectsSection.tsx | 51 +- app/_page_sections/SkillsSection.module.scss | 118 +- app/_page_sections/SkillsSection.tsx | 97 +- app/_styles/globals.scss | 20 +- app/_utils/turnstile.ts | 4 +- app/attribution/page.tsx | 7 +- app/loading.tsx | 2 +- app/not-found.tsx | 2 +- app/page.tsx | 6 +- eslint.config.mjs | 69 +- next.config.mjs | 44 + package.json | 37 +- public/clarity.js | 9 +- public/sus.mp3 | Bin 0 -> 142106 bytes tsconfig.json | 5 +- yarn.lock | 3554 +++++++++++++---- 48 files changed, 4943 insertions(+), 1743 deletions(-) create mode 100644 app/_assets/illustrations/skills/security-skills.svg delete mode 100644 app/_data/experience.ts create mode 100644 app/_data/experience.tsx delete mode 100644 app/_data/skills.ts create mode 100644 app/_data/skills.tsx create mode 100644 public/sus.mp3 diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 0307a6b..c600ae2 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -6,14 +6,17 @@ "features": { "ghcr.io/devcontainers/features/docker-in-docker:2": { + "installDockerBuildx": true, "version": "latest", - "enableNonRootDocker": "true", - "moby": "false" + "dockerDashComposeVersion": "v2" }, "ghcr.io/devcontainers/features/node:1": { "version": "latest", "pnpmVersion": "none", "nvmVersion": "latest" + }, + "ghcr.io/devcontainers-extra/features/corepack:1": { + "version": "latest" } }, diff --git a/COPYING b/COPYING index 7c578b5..0bb13cb 100644 --- a/COPYING +++ b/COPYING @@ -3,18 +3,18 @@ Following files and directories are excempt from MIT license coverage and are subjects to general copyright law: - - /app/_assets - - /app/_data + - /app/_assets/ + - /app/_data/ - /app/apple-icon.png - /app/favicon.ico - /app/icon.svg - /app/opengraph-image.alt.txt - /app/opengraph-image.png -You must obtain written permission from the author to use any -copyrighted material. +You must obtain written a permission from the author to use any +copyrighted materials. -You may use copyrighted material without excplicit permission +You may use copyrighted materials without excplicit permission in following cases: - Educational purposes. diff --git a/app/_assets/decorations/pawprint-trail-vertical.svg b/app/_assets/decorations/pawprint-trail-vertical.svg index 77d5393..7e365b2 100644 --- a/app/_assets/decorations/pawprint-trail-vertical.svg +++ b/app/_assets/decorations/pawprint-trail-vertical.svg @@ -3,13 +3,13 @@ diff --git a/app/_assets/decorations/pawprint-trail.svg b/app/_assets/decorations/pawprint-trail.svg index 27efe37..78f4d9b 100644 --- a/app/_assets/decorations/pawprint-trail.svg +++ b/app/_assets/decorations/pawprint-trail.svg @@ -3,14 +3,14 @@ diff --git a/app/_assets/illustrations/skills.ts b/app/_assets/illustrations/skills.ts index 7150f3a..b30285c 100644 --- a/app/_assets/illustrations/skills.ts +++ b/app/_assets/illustrations/skills.ts @@ -6,6 +6,7 @@ import designSkills from "./skills/design-skills.svg"; import devopsSkills from "./skills/devops-skills.svg"; import dotnetSkills from "./skills/dotnet-skills.svg"; import nodejsSkills from "./skills/nodejs-skills.svg"; +import securitySkills from "./skills/security-skills.svg"; export const admin: ImageExport = { @@ -49,6 +50,12 @@ export const nodejs: ImageExport = alt: "Cartoon fox lifted by balloons representing TypeScript, JavaScript, and React, with a laptop below." }; +export const security: ImageExport = +{ + src: securitySkills, + alt: "" +}; + const skills = { nodejs, @@ -58,6 +65,7 @@ const skills = design, devops, admin, + security, }; export default skills; diff --git a/app/_assets/illustrations/skills/admin-skills.svg b/app/_assets/illustrations/skills/admin-skills.svg index 1f0f385..2b637f9 100644 --- a/app/_assets/illustrations/skills/admin-skills.svg +++ b/app/_assets/illustrations/skills/admin-skills.svg @@ -1,5 +1,5 @@ - +
- -
-

{ title }

-

-

-
- ); + return [alertText, title, message]; } catch { return null; } +} + +const AlertMessage: React.FC = async () => +{ + const result = await fetchAlert(); + + if (!result) + return null; + + const [alertText, title, message] = result; + + return ( +
+ +
+

{ title }

+

+

+
+ ); }; export default AlertMessage; diff --git a/app/_components/CookieBanner.tsx b/app/_components/CookieBanner.tsx index 7922024..c9f6bb8 100644 --- a/app/_components/CookieBanner.tsx +++ b/app/_components/CookieBanner.tsx @@ -15,6 +15,7 @@ const CookieBanner: React.FC = () => return; const choice = getCookieChoice(); + // eslint-disable-next-line react-hooks/set-state-in-effect setVisible(choice === "none"); // Since Clarity cookies expiration dates extend well beyond 60 days, diff --git a/app/_components/RevokeConsentButton.tsx b/app/_components/RevokeConsentButton.tsx index c3df29d..6b80a1f 100644 --- a/app/_components/RevokeConsentButton.tsx +++ b/app/_components/RevokeConsentButton.tsx @@ -10,6 +10,7 @@ const RevokeConsentButton: React.FC = () => useEffect(() => { + // eslint-disable-next-line react-hooks/set-state-in-effect setHasConsent(getCookieChoice() === "accepted"); }, []); diff --git a/app/_components/Sidemenu.tsx b/app/_components/Sidemenu.tsx index 9d9c5ec..e5e6a91 100644 --- a/app/_components/Sidemenu.tsx +++ b/app/_components/Sidemenu.tsx @@ -2,7 +2,8 @@ import links from "@/_data/links"; import socials from "@/_data/socials"; -import { Dismiss24Regular, Navigation24Regular } from "@fluentui/react-icons"; +import Dismiss24Regular from "@fluentui/svg-icons/icons/dismiss_24_regular.svg"; +import Navigation24Regular from "@fluentui/svg-icons/icons/navigation_24_regular.svg"; import React, { useCallback, useEffect, useRef, useState } from "react"; import Button, { ButtonProps } from "./Button"; import NavigationLinks from "./NavigationLinks"; diff --git a/app/_data/FrontSection.module.scss b/app/_data/FrontSection.module.scss index 1a54ada..19636dd 100644 --- a/app/_data/FrontSection.module.scss +++ b/app/_data/FrontSection.module.scss @@ -30,19 +30,6 @@ } } - .highlight - { - color: $colorNeutralForegroundInverted; - background-color: $colorNeutralBackgroundInverted; - padding: $spacingXXS $spacingNone; - - &::selection - { - color: $colorNeutralForegroundInverted; - background-color: $colorBrandForeground1; - } - } - .illustrations { justify-self: center; diff --git a/app/_data/FrontSection.tsx b/app/_data/FrontSection.tsx index 42c1ef5..7b23459 100644 --- a/app/_data/FrontSection.tsx +++ b/app/_data/FrontSection.tsx @@ -9,12 +9,11 @@ import Package from "@/../package.json"; const FrontSection: React.FC = () => (
-

Hello World!

-

{ Package.author.name } is here!

+

Hello there!

+

My name is { Package.author.name }

- I am a software engineer with extensive experience in
- .NET and React development
- and you are on my website! + I am a software engineer from Russia
with extensive experience + in .NET and React development

diff --git a/app/_data/bio.tsx b/app/_data/bio.tsx index 3494e78..049846c 100644 --- a/app/_data/bio.tsx +++ b/app/_data/bio.tsx @@ -1,13 +1,10 @@ -export const Bio: React.FC = () => ( - <> -

{ bioPremise }

-

My journey as a programmer started in 2018 from a silly free-time hobby. Since then, I have released a couple of personal projects, some of which have become quite popular.

-

Graduated from Bonch-Bruevich University of Telecommunications in 2023 where I got my Bachelor's degree in computer science. It was fun. Took part in a number of hackathons (usually first place for us) as well as science conferences (including those hosted by IEEE). In 2025 got Master's in Radiotechnology.

-

Also, before graduation I managed to work in several different companies in different IT fields (mostly software development, of course).

-

Out-of-box thinking, and constant self-improvement is my life strategy. New tool released? - Yes, please! GitHub is hosting another conference? - Sign me up! There is a new challenging task to complete? - Oh, boy, here we go again! So many things to learn, so little time to spare...

-

Overall, enthusiastic, fast learning and energetic person. Love coding and creating something new. Like to draw and compose music. Aviasim enthusiast. Proud member of the furry community.

- -); +export const Bio: React.FC = () => <> +

I am a software engineer who loves desiging and building complex services, creating nice intuitive applications, and solving engineering challenges.

+

I started programming in 2018 because I am not a good artist, and programming allows to bring my visions and ideas to life. My specialty is React and .NET in web development, but I always look out for other fields and tools as well.

+

Thanks to my quick adaptation and communication skills, I can either be a good team player, or handle tasks independently.

+

Graduated from Bonch-Bruevich University of Telecommunications. In 2023 received Bachelor's degree in Computer science, then in 2025 – Master's in Radiotechnology. Despite that, I began my career path much earlier and consider myself a self-taught developer.

+

In my free time I like to play some instruments, pilot a 737 in a flightsim, or just keep working, but on my own pet projects.

+; export const bioPremise: string = "My name is Eugene Fox. I am a professional software developer primarily focused on .NET and React projects."; diff --git a/app/_data/experience.ts b/app/_data/experience.ts deleted file mode 100644 index 433f1c0..0000000 --- a/app/_data/experience.ts +++ /dev/null @@ -1,20 +0,0 @@ -const experience: WorkplaceEntry[] = - [ - { title: "IT/VR tutor", year: "2020", place: "Quantorium", tech: "Unity, STEM" }, - { title: "System administrator", year: "2021", place: "Quantorium", tech: "M365, Intune, Azure" }, - { title: "Software Engineer", place: "[nordcloud]", tech: "ASP.NET, EF Core" }, - { title: "CTO", year: "2022", place: "FoxDev Studio", tech: "Unity, Xamarin, .NET, React, Azure" }, - { title: "Senior Software Engineer", year: "2023", place: "A-rial", tech: ".NET, React, DevOps" }, - { title: "Senior Software Architect", year: "2024", place: "A-rial", tech: ".NET, React, Embedded devices" }, - { title: "Here", place: "Your company" }, - ]; - -export default experience; - -export type WorkplaceEntry = - { - year?: string; - place?: string; - title: string; - tech?: string; - }; diff --git a/app/_data/experience.tsx b/app/_data/experience.tsx new file mode 100644 index 0000000..8d4ff8a --- /dev/null +++ b/app/_data/experience.tsx @@ -0,0 +1,95 @@ +import Button from "@/_components/Button"; +import { ReactElement } from "react"; + +const experience: WorkplaceEntry[] = + [ + { + place: "Nordcloud, Saint-Petersburg, RU", + title: "Software Engineer", + summary: "Working on MightyCall cloud call center product", + year: "2021", + description: <> +

Implementing new features and fixing bugs in large-scale distributed VoIP system with ASP.NET and Angular

+
    +
  • Completed 2 week onboarding in 3 days.
  • +
  • Found and fixed a critical issue in system's build process, improving performance by 40%
  • +
+ + }, + { + place: "Quantorium, Saint-Petersburg, RU", + title: "System administrator", + summary: "Administration of school's IT infrastructure", + year: "2021", + description:
    +
  • Integrated Microsoft Azure and M365 services which reduced overall workload of the staff by 30%.
  • +
  • Implemented Azure Intune services for management of 100+ school devices.
  • +
  • Integrated modern interactive solutions into education process during COVID-19 pandemic.
  • +
  • Implemented storage inventory system which helped to track 100% of school's inventory.
  • +
+ }, + { + place: "A-rial, Saint-Petersburg, RU", + title: "Software Engineer", + summary: "Legacy software support and DevOps", + year: "2023", + description: <> +

Supporting legacy WLAN controller system, as well as maintaining company's IT infrastructure

+

Stack: .NET, React, Golang, Vue, Mongo

+
    +
  • Built company's IT infrastructure from scratch (email, cloud, git, etc.)
  • +
  • Found and fixed several critical bugs in one of the projects which allowed the company to receive next round of investments.
  • +
  • Designed and implemented web interface for wireless routers using React
  • +
  • Lead critical QA field tests for WLAN controller product.
  • +
+ + }, + { + place: "A-rial, Saint-Petersburg, RU", + title: "Lead Software Engineer", + summary: "Working on software for RF Analyzer hardware", + year: "2024", + description: <> +

Creating, desiging and implementing RF analyzer software, as well as participating in hardware design.

+

Stack: ASP.NET (RESTFul API), React, Linux

+
    +
  • Implemented both frontend and backend as modular components with ASP.NET and React.
  • +
  • Wrote abstraction layers for managing Wi-Fi and SDR hardware in C#.
  • +
  • Set up a complete CI/CD pipeline with GitHub Actions.
  • +
+ + }, + { + place: "A-rial, Saint-Petersburg, RU", + title: "Lead System Architect", + summary: "Working on WLAN Controller system", + year: "2025", + description: <> +

Designing and implementing large-scale distributed WLAN controller system

+

Stack: ASP.NET (RESTFul API), MongoDB, Postges (EF Core), RabbitMQ (MassTransit), MQTT, Docker.

+
    +
  • Designed an architecture of a new event-driven microservice-based system to replace legacy monolith from scratch (HLD + LLD).
  • +
  • Wrote a comprehensive techref for each of 16 components.
  • +
  • Solo implemented the whole system from start to finish in just 3 months.
  • +
  • Designed and wrote an agent service with .NET NativeAOT to operate OpenWRT routers.
  • +
+ + }, + { + place: "Your company", + title: "Here", + summary: "Working on new and exciting projects", + description: + }, + ]; + +export default experience; + +export type WorkplaceEntry = + { + year?: string; + place?: string; + title: string; + summary?: string; + description?: ReactElement; + }; diff --git a/app/_data/projects.ts b/app/_data/projects.ts index d68ade9..31a90d5 100644 --- a/app/_data/projects.ts +++ b/app/_data/projects.ts @@ -9,7 +9,19 @@ import passwordGeneratorLight from "@/_assets/illustrations/projects/PasswordGen import simpleOtpImg from "@/_assets/illustrations/projects/SimpleOTP.svg"; import tabsAsideDark from "@/_assets/illustrations/projects/TabsAside/dark.webp"; import tabsAsideLight from "@/_assets/illustrations/projects/TabsAside/light.webp"; -import * as ic from "@fluentui/react-icons"; +import Beaker24Regular from "@fluentui/svg-icons/icons/beaker_24_regular.svg"; +import Branch24Regular from "@fluentui/svg-icons/icons/branch_24_regular.svg"; +import Code24Regular from "@fluentui/svg-icons/icons/code_24_regular.svg"; +import Color24Regular from "@fluentui/svg-icons/icons/color_24_regular.svg"; +import Database24Regular from "@fluentui/svg-icons/icons/database_24_regular.svg"; +import Desktop24Regular from "@fluentui/svg-icons/icons/desktop_24_regular.svg"; +import FlashFlow24Regular from "@fluentui/svg-icons/icons/flash_flow_24_regular.svg"; +import FlashSettings24Regular from "@fluentui/svg-icons/icons/flash_settings_24_regular.svg"; +import Globe24Regular from "@fluentui/svg-icons/icons/globe_24_regular.svg"; +import HeartPulse24Regular from "@fluentui/svg-icons/icons/heart_pulse_24_regular.svg"; +import Phone24Regular from "@fluentui/svg-icons/icons/phone_24_regular.svg"; +import PhoneDesktop24Regular from "@fluentui/svg-icons/icons/phone_desktop_24_regular.svg"; +import Server24Regular from "@fluentui/svg-icons/icons/server_24_regular.svg"; import { StaticImageData } from "next/image"; const projects: Project[] = @@ -20,20 +32,20 @@ const projects: Project[] = description: [ "During one of the classes at university I struggled to log into my account on a lab computer. I have long random passwords, so I had to type them manually from my phone which took about 5 minutes. I thought that there must be a better way to do this.", - "So, I came up with this idea where you can easily send your credentials to any computer by simply scanning a QR code with a password manager app.", + "So, I came up with this idea where you can easily send your credentials to any computer by simply scanning a QR code with a password manager app (this was before WebAuthn became widely adopted).", "In the end, I have created a big web service with mobile app and a customer portal, that could authenticate users on any website, and any device within a few seconds." ], image: ezlogImg, link: "https://github.com/xfox111/easylogon-web", stack: [ - { text: "C#/TypeScript", icon: ic.Code24Regular }, - { text: ".NET 6", icon: ic.Server24Regular }, - { text: "React/Vite", icon: ic.PhoneDesktop24Regular }, - { text: "Xamarin.Forms", icon: ic.Phone24Regular }, - { text: "SQL Server", icon: ic.Database24Regular }, - { text: "Azure DevOps", icon: ic.Branch24Regular }, - { text: "Azure Pipelines/GitHub Actions", icon: ic.FlashFlow24Regular } + { text: "C#/TypeScript", icon: Code24Regular }, + { text: ".NET 6", icon: Server24Regular }, + { text: "React/Vite", icon: PhoneDesktop24Regular }, + { text: "Xamarin.Forms", icon: Phone24Regular }, + { text: "SQL Server", icon: Database24Regular }, + { text: "Azure DevOps", icon: Branch24Regular }, + { text: "Azure Pipelines/GitHub Actions", icon: FlashFlow24Regular } ] }, { @@ -49,12 +61,12 @@ const projects: Project[] = link: "https://github.com/xfox111/TabsAsideExtension", stack: [ - { text: "React/WXT", icon: ic.Desktop24Regular }, - { text: "TypeScript", icon: ic.Code24Regular }, - { text: "Browser extension", icon: ic.FlashSettings24Regular }, - { text: "Fluent UI", icon: ic.Color24Regular }, - { text: "GitHub", icon: ic.Branch24Regular }, - { text: "GitHub Actions", icon: ic.FlashFlow24Regular }, + { text: "React/WXT", icon: Desktop24Regular }, + { text: "TypeScript", icon: Code24Regular }, + { text: "Browser extension", icon: FlashSettings24Regular }, + { text: "Fluent UI", icon: Color24Regular }, + { text: "GitHub", icon: Branch24Regular }, + { text: "GitHub Actions", icon: FlashFlow24Regular }, ] }, { @@ -69,10 +81,10 @@ const projects: Project[] = link: "https://github.com/xfox111/SimpleOTP", stack: [ - { text: ".NET/C#", icon: ic.Code24Regular }, - { text: "MSTest", icon: ic.Beaker24Regular }, - { text: "GitHub", icon: ic.Branch24Regular }, - { text: "GitHub Actions", icon: ic.FlashFlow24Regular }, + { text: ".NET/C#", icon: Code24Regular }, + { text: "MSTest", icon: Beaker24Regular }, + { text: "GitHub", icon: Branch24Regular }, + { text: "GitHub Actions", icon: FlashFlow24Regular }, ] }, { @@ -88,32 +100,35 @@ const projects: Project[] = link: "https://github.com/xfox111/PasswordGeneratorExtension", stack: [ - { text: "React/Vite", icon: ic.Desktop24Regular }, - { text: "TypeScript", icon: ic.Code24Regular }, - { text: "Browser extension", icon: ic.FlashSettings24Regular }, - { text: "Fluent UI", icon: ic.Color24Regular }, - { text: "GitHub", icon: ic.Branch24Regular }, - { text: "GitHub Actions", icon: ic.FlashFlow24Regular }, + { text: "React/Vite", icon: Desktop24Regular }, + { text: "TypeScript", icon: Code24Regular }, + { text: "Browser extension", icon: FlashSettings24Regular }, + { text: "Fluent UI", icon: Color24Regular }, + { text: "GitHub", icon: Branch24Regular }, + { text: "GitHub Actions", icon: FlashFlow24Regular }, ] }, { - title: "GUT.Schedule", + title: "GUT.Schedule / Bonch.Calendar", subtitle: "Mobile app that exports Bonch university schedule to e-calendar", description: [ - "[2019]", - "I created this app during my time in Bonch-Bruevich University of Telecommunications as a BS student.", - "It was designed to help students to manage their timetable in a more convenient and effective way." + "I created this app in 2019 during my time in Bonch-Bruevich University of Telecommunications as a BS student.", + "It was designed to help students to manage their timetable in a more convenient and effective way – by exporting their schedule to any calendar app on their Android phones.", + "In 2025 I made it into a web-service that can generate web calendars the students can subscribe to directly from their calendar apps, regardless of the platform they are using.", ], image: gutScheduleImg, - link: "https://github.com/xfox111/GUTSchedule", + link: "https://github.com/stars/XFox111/lists/bonch", stack: [ - { text: ".NET/C#", icon: ic.Code24Regular }, - { text: "Xamarin.Android", icon: ic.Phone24Regular }, - { text: "GitHub", icon: ic.Branch24Regular }, - { text: "NUnit 3", icon: ic.Beaker24Regular }, - { text: "Azure Pipelines", icon: ic.FlashFlow24Regular }, + { text: ".NET/C#", icon: Code24Regular }, + { text: "Xamarin.Android", icon: Phone24Regular }, + { text: "GitHub", icon: Branch24Regular }, + { text: "NUnit", icon: Beaker24Regular }, + { text: "Azure Pipelines", icon: FlashFlow24Regular }, + { text: "React/Vite", icon: Globe24Regular }, + { text: "Typescript", icon: Code24Regular }, + { text: "GitHub Actions", icon: FlashFlow24Regular }, ] }, { @@ -123,19 +138,19 @@ const projects: Project[] = [ "[2019]", "My first published app.", - "I like to watch videos while working on my projects, but at the time YouTube didn not have a proper picture-in-picture mode and overall had a lot of issues with the UX, so this was my way to fix this.", - "Unfortunately, Google doesn't like third-party YouTube clients." + "I like to watch videos while working on my projects, but at the time YouTube didn't have a proper picture-in-picture mode and overall had a lot of issues with the UX, so this was my way to fix it.", + "Unfortunately, Google doesn't like third-party YouTube clients, so soon after publishing, they revoked app's API access :(" ], image: foxTubeLight, imageDark: foxTubeDark, link: "https://www.youtube.com/watch?v=Mio9FbxmbhM", stack: [ - { text: ".NET/C#", icon: ic.Code24Regular }, - { text: "UWP", icon: ic.Desktop24Regular }, - { text: "Azure DevOps", icon: ic.Branch24Regular }, - { text: "AppCenter", icon: ic.HeartPulse24Regular }, - { text: "Azure Pipelines", icon: ic.FlashFlow24Regular }, + { text: ".NET/C#", icon: Code24Regular }, + { text: "UWP", icon: Desktop24Regular }, + { text: "Azure DevOps", icon: Branch24Regular }, + { text: "AppCenter", icon: HeartPulse24Regular }, + { text: "Azure Pipelines", icon: FlashFlow24Regular }, ] }, { @@ -152,10 +167,10 @@ const projects: Project[] = link: "https://github.com/xfox111/MotionDecoder", stack: [ - { text: ".NET/C#", icon: ic.Code24Regular }, - { text: "WinForms", icon: ic.Desktop24Regular }, - { text: "Accord.NET", icon: ic.FlashSettings24Regular }, - { text: "GitHub", icon: ic.Branch24Regular }, + { text: ".NET/C#", icon: Code24Regular }, + { text: "WinForms", icon: Desktop24Regular }, + { text: "Accord.NET", icon: FlashSettings24Regular }, + { text: "GitHub", icon: Branch24Regular }, ] } ]; @@ -175,6 +190,6 @@ export type Project = type TechStackItem = { - icon: ic.FluentIcon; + icon: React.FC; text: string; }; diff --git a/app/_data/skills.ts b/app/_data/skills.ts deleted file mode 100644 index 9cb75bb..0000000 --- a/app/_data/skills.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { ImageExport } from "@/_assets/assets"; -import imgs from "@/_assets/illustrations/skills"; -import * as ic from "@fluentui/react-icons"; - -const skills: Skill[] = - [ - { - title: "NodeJS", - description: "React, Vite, Next.js, SASS, TypeScript", - icon: ic.WindowDevToolsRegular, - image: imgs.nodejs - }, - { - title: ".NET", - description: "ASP.NET, Razor, WinUI/UWP, WPF, WinForms | Xamarin.Forms, MAUI", - icon: ic.PhoneDesktopRegular, - image: imgs.dotnet - }, - { - title: "Architecture & systems", - description: "Docker, Nginx, Linux | Modules, microservices", - icon: ic.DesktopFlowRegular, - image: imgs.architecture - }, - { - title: "Databases", - description: "Entity Framework, MongoDB", - icon: ic.DatabaseMultipleRegular, - image: imgs.databases - }, - { - title: "Design", - description: "Figma, Photoshop, Illustrator", - icon: ic.DesignIdeasRegular, - // Note, this picture has a special behavior in @/_page_sections/SkillsSection.tsx:24 - image: imgs.design - }, - { - title: "DevOps", - description: "GitHub, Azure DevOps, AppCenter, Atlassian", - icon: ic.FlashFlowRegular, - image: imgs.devops - }, - { - title: "Administration", - description: "Ansible, M365, Azure, InTune", - icon: ic.ConnectedRegular, - image: imgs.admin - } - ]; - -export default skills; - -export type Skill = - { - title: string; - description: string; - icon: ic.FluentIcon; - image: ImageExport; - }; diff --git a/app/_data/skills.tsx b/app/_data/skills.tsx new file mode 100644 index 0000000..1990600 --- /dev/null +++ b/app/_data/skills.tsx @@ -0,0 +1,105 @@ +import { ImageExport } from "@/_assets/assets"; +import imgs from "@/_assets/illustrations/skills"; +import WindowDevTools24Regular from "@fluentui/svg-icons/icons/window_dev_tools_24_regular.svg"; +import PhoneDesktop24Regular from "@fluentui/svg-icons/icons/phone_desktop_24_regular.svg"; +import DesktopFlow24Regular from "@fluentui/svg-icons/icons/desktop_flow_24_regular.svg"; +import DatabaseMultiple32Regular from "@fluentui/svg-icons/icons/database_multiple_32_regular.svg"; +import DesignIdeas24Regular from "@fluentui/svg-icons/icons/design_ideas_24_regular.svg"; +import FlashFlow24Regular from "@fluentui/svg-icons/icons/flash_flow_24_regular.svg"; +import Connected24Regular from "@fluentui/svg-icons/icons/connected_24_regular.svg"; +import ShieldKeyhole24Regular from "@fluentui/svg-icons/icons/shield_keyhole_24_regular.svg"; +import React from "react"; + +const skills: Skill[] = + [ + { + title: "NodeJS", + caption: "React, Vite, Next.js, SASS, TypeScript", + description: <> + I learned React while being on a 2 week quarantine back in 2020. Since then, it was my go-to framework for frontend in both commercial and personal projects. While .NET is still my main choice for backend, I occasionally use Next.js for smaller projects as well. + , + icon: WindowDevTools24Regular, + image: imgs.nodejs + }, + { + title: ".NET", + caption: "ASP.NET, WebAPI, Minimal API, MVC | MAUI, WinUI", + description: <> + My bread and butter. The one and only! I learned C# back in 2018 while trying to make a game in Unity. Since then, I have fallen in love with the language and the ecosystem. I have used .NET for everything: web, desktop, mobile, IoT... you name it. + , + icon: PhoneDesktop24Regular, + image: imgs.dotnet + }, + { + title: "System design", + caption: "Clean architecture, Microservices, Event-driven design", + description: <> + Working for small companies (especially abmitious ones) has its perks. One of them is that you get to do everything, including architecture design. Throughout my career, I have designed a couple of high-load distributed systems, as well as many smaller applications. + , + icon: DesktopFlow24Regular, + image: imgs.architecture + }, + { + title: "Databases & ORM", + caption: "SQL, Postgres, MongoDB, EF Core", + description: <> + If there is a SQL, then where is prequel?.. + , + icon: DatabaseMultiple32Regular, + image: imgs.databases + }, + { + title: "UI/UX Design", + caption: "Figma, Photoshop, Illustrator", + description: <> + Even though I am not a professional designer, I know a thing or two about good UX, since for the most of the projects, I was usually responsible for it as well. +

+ For UI though, I prefer sticking to existing design systems (like Fluent UI or Material). But of course, I can draw a couple of buttons if needed. + , + icon: DesignIdeas24Regular, + // Note, this picture has a special behavior in @/_page_sections/SkillsSection.tsx + image: imgs.design + }, + { + title: "DevOps & Tooling", + caption: "Git, Jira, CI/CD automation, Docker", + description: <> + Back when I was learning programming, whenever I started a new project, I imagined being at a big company with dozens of people working on the same codebase. So, I always tried to make it look like it: version control, documentation, CI/CD, kanban, sprints...

+ Who would've thought that this actually would come in handy! + , + icon: FlashFlow24Regular, + image: imgs.devops + }, + { + title: "Infrastructure & Administration", + caption: "Azure, AWS, Docker, K8S, Nginx, Linux, Ansible", + description: <> + Thanks to my past experience, I have a solid understanding of how to build and manage infrastructure. I have worked with both cloud and on-premises solutions, as well as different containerization and orchestration tools. + , + icon: Connected24Regular, + image: imgs.admin + }, + { + title: "Application security", + caption: "JWT, WebAuthn, OAuth2, TOTP", + description: <> + Throughout my work (as a system administrator and as an engineer), I had to learn a lot about best security practices and cybersecurity in general. +

+ So, I have experience implementing various authentication and authorization mechanisms (some of which I wrote from scratch by following specifications), as well as securing applications against common vulnerabilities. + , + icon: ShieldKeyhole24Regular, + // Note, this picture has a special behavior in @/_page_sections/SkillsSection.tsx + image: imgs.security + }, + ]; + +export default skills; + +export type Skill = + { + title: string; + caption: string; + icon: React.FC; + image: ImageExport; + description: React.ReactElement; + }; diff --git a/app/_data/socials.ts b/app/_data/socials.ts index 246d9b1..4599195 100644 --- a/app/_data/socials.ts +++ b/app/_data/socials.ts @@ -12,7 +12,7 @@ const socials: Socials = href: "https://www.linkedin.com/in/xfox/", username: "@xfox" }, - "BlueSky": + "Bluesky": { href: "https://bsky.app/profile/xfox111.net", username: "@xfox111.net", diff --git a/app/_page_sections/AboutSection.module.scss b/app/_page_sections/AboutSection.module.scss index 5d92eda..6e54903 100644 --- a/app/_page_sections/AboutSection.module.scss +++ b/app/_page_sections/AboutSection.module.scss @@ -8,7 +8,7 @@ @include centerTwo; - > div:first-child + .content { @include flex(column); gap: $spacingM; diff --git a/app/_page_sections/AboutSection.tsx b/app/_page_sections/AboutSection.tsx index edb66da..b3be419 100644 --- a/app/_page_sections/AboutSection.tsx +++ b/app/_page_sections/AboutSection.tsx @@ -6,13 +6,13 @@ import cls from "./AboutSection.module.scss"; const AboutSection: React.FC = () => (
-
+ { + +

About me

- - {
); diff --git a/app/_page_sections/ContactSection.tsx b/app/_page_sections/ContactSection.tsx index b7b5d56..76ac451 100644 --- a/app/_page_sections/ContactSection.tsx +++ b/app/_page_sections/ContactSection.tsx @@ -36,7 +36,7 @@ const ContactSection: React.FC = () => return (
-

Let's get in touch

+

Let's get in touch!

diff --git a/app/_page_sections/ExperienceSection.module.scss b/app/_page_sections/ExperienceSection.module.scss index 9ae077f..b5759ca 100644 --- a/app/_page_sections/ExperienceSection.module.scss +++ b/app/_page_sections/ExperienceSection.module.scss @@ -68,7 +68,7 @@ box-shadow: inset 0 0 0 16px $colorNeutralBackground1; } - .description + .info { p { @@ -113,9 +113,16 @@ .item { grid-template-columns: 64px auto 1fr; + grid-template-rows: auto auto; padding: $spacingXXXL $spacingNone; gap: $spacingM; align-items: center; + + .description + { + grid-row: 2/2; + grid-column: 3/4; + } } } @@ -126,7 +133,7 @@ .line { - bottom: 72px; + bottom: 132px; width: 100%; height: 8px; align-content: center; @@ -147,9 +154,10 @@ .item { - grid-template-rows: 128px auto 48px; + grid-template-rows: 128px auto 48px 48px; padding: $spacingNone $spacingM; row-gap: $spacingM; + cursor: pointer; .year { @@ -171,6 +179,23 @@ } .description + { + grid-row: 4/4; + + transition-property: font-size, line-height, opacity; + transition-duration: $durationNormal; + transition-timing-function: $curveEasyEaseMax; + + font-size: 0; + opacity: 0; + + p + { + margin-bottom: $spacingSNudge; + } + } + + .info { grid-row: 1/1; align-self: self-end; @@ -197,7 +222,7 @@ { // Item that is being hovered or focused - &:is(:hover, :focus-visible, :focus-within) + &:is(:focus-visible, :focus-within) { .year, @@ -207,16 +232,27 @@ opacity: 1; } - > i + .description { - box-shadow: inset 0 0 0 0 $colorNeutralBackground1; + @include body2($fontFamilyBaseAlt); + opacity: 1; } - .description > p + .info > p { @include subtitle2($fontFamilyBaseAlt); opacity: 1; } + + .year + { + font-size: $fontSizeHero800 !important; + } + } + + &:is(:hover, :focus-visible, :focus-within) > i + { + box-shadow: inset 0 0 0 0 $colorNeutralBackground1; } // Other not focused items @@ -228,8 +264,11 @@ { @include body1($fontFamilyBaseAlt); color: $colorNeutralForeground3; + } - @media screen and (max-width: 1200px) + @media screen and (max-width: 1200px) + { + .title { opacity: 0; font-size: 0; @@ -237,6 +276,18 @@ } } } + + @media screen and (max-width: 1400px) + { + &:has(:focus-visible, :focus-within) .item:not(:focus-visible, :focus-within) + { + .title + { + opacity: 0; + font-size: 0; + } + } + } } } } diff --git a/app/_page_sections/ExperienceSection.tsx b/app/_page_sections/ExperienceSection.tsx index f4beef3..29c2d04 100644 --- a/app/_page_sections/ExperienceSection.tsx +++ b/app/_page_sections/ExperienceSection.tsx @@ -22,16 +22,23 @@ const ExperienceSection: React.FC = () => (
-

{ i.year }

-
+ { i.description } +
+

0 && experience[index - 1].year === i.year ? { fontSize: 0 } : undefined }> + { i.year } +

+ +

{ i.place }

{ i.title }

-

{ i.tech ?? Contact me }

+

{ i.summary ?? Contact me }

) }
+ + {/*

Deserunt esse irure duis magna irure. Eiusmod voluptate amet et elit adipisicing ut. Nulla minim elit anim mollit nisi amet est et magna veniam. Qui deserunt eiusmod laboris ex. Ex aute duis duis incididunt quis adipisicing dolor sit aliqua consectetur eu fugiat. Fugiat ipsum dolor elit ad commodo aliquip anim anim nostrud. Lorem adipisicing ex quis veniam aute amet cupidatat reprehenderit do laborum minim laboris sunt.

*/}
); @@ -47,8 +54,8 @@ function getAriaLabel(item: WorkplaceEntry): string if (item.place) str.push(`at ${item.place}`); - if (item.tech) - return str.join(" ") + `. ${item.tech}`; + if (item.summary) + return str.join(" ") + `. ${item.summary}`; else return str.join(" "); diff --git a/app/_page_sections/ProjectsSection.module.scss b/app/_page_sections/ProjectsSection.module.scss index 9472b69..9fbc7b5 100644 --- a/app/_page_sections/ProjectsSection.module.scss +++ b/app/_page_sections/ProjectsSection.module.scss @@ -4,9 +4,16 @@ { @include centerTwo; - .listItem + .content { - background-position: right; + overflow-x: hidden; + gap: $spacingM; + @include flex(column); + + .listItem + { + background-position-x: right; + } } .descriptions @@ -16,12 +23,6 @@ overflow-x: visible; min-height: 760px; - @media screen and (max-width: 860px) - { - min-height: unset; - padding-top: calc(56px + $spacingXL); - } - img { width: 100%; @@ -35,6 +36,18 @@ @include slideIn; } + .mobileNav + { + margin-top: $spacingL; + display: none; + gap: $spacingMNudge; + + .next + { + flex-grow: 1; + } + } + .projectItem { @include flex(column); @@ -96,4 +109,22 @@ { align-self: flex-end; } + + @media screen and (max-width: 860px) + { + .descriptions + { + min-height: unset; + + .mobileNav + { + display: flex; + } + } + + .list + { + display: none; + } + } } diff --git a/app/_page_sections/ProjectsSection.tsx b/app/_page_sections/ProjectsSection.tsx index 8d53a7b..2439e02 100644 --- a/app/_page_sections/ProjectsSection.tsx +++ b/app/_page_sections/ProjectsSection.tsx @@ -5,7 +5,9 @@ import Button from "@/_components/Button"; import links from "@/_data/links"; import projects from "@/_data/projects"; import shared from "@/_styles/gallery.module.scss"; -import { ArrowRight24Regular } from "@fluentui/react-icons"; +import ArrowRight24Regular from "@fluentui/svg-icons/icons/arrow_right_24_regular.svg"; +import ChevronLeft24Regular from "@fluentui/svg-icons/icons/chevron_left_24_regular.svg"; +import ChevronRight24Regular from "@fluentui/svg-icons/icons/chevron_right_24_regular.svg"; import Image from "next/image"; import React, { useCallback, useState } from "react"; import { networkFor } from "react-social-icons"; @@ -23,23 +25,25 @@ const ProjectsSection: React.FC = () => return (
-
+

My pet projects

- { projects.map((project, index) => - - ) } +
+ { project.title } + { project.subtitle } +
+ + ) } +
+ } +
); diff --git a/app/_page_sections/SkillsSection.module.scss b/app/_page_sections/SkillsSection.module.scss index 359f78a..4d5f3f1 100644 --- a/app/_page_sections/SkillsSection.module.scss +++ b/app/_page_sections/SkillsSection.module.scss @@ -9,14 +9,48 @@ .illustrations { justify-self: center; - position: relative; + width: 100%; - img + .item { - width: 100%; - max-height: 600px; + position: relative; + @include flex(column); + gap: $spacingXXXL; @include slideIn; + + img + { + align-self: flex-end; + max-height: 400px; + width: auto; + max-width: 100%; + height: auto; + } + + h3 + { + @include subtitle1($fontFamilyBaseAlt); + display: none; + } + + p + { + @include subtitle1($fontFamilyBaseAlt); + } + } + + .mobileNav + { + margin-top: $spacingM; + display: none; + gap: $spacingMNudge; + + .next + { + flex-grow: 1; + background-position-x: right; + } } // [SPECIAL] @@ -25,28 +59,88 @@ position: absolute; cursor: pointer; - bottom: calc(50% - 20px + 13.5%); - left: 40%; - width: 20%; - height: 40px; + right: 214px; + top: 81px; + width: 60px; + height: 25px; - @media screen and (max-width: 1400px) + @media screen and (max-width: 573px) { - bottom: calc(50% - 20px + 6vw) + right: calc(50% - 30px); + top: calc(15vw - 7px); } } + + // [SPECIAL] + .sus + { + position: absolute; + cursor: pointer; + + left: 0; + top: 200px; + width: 90px; + height: 100px; + + @media screen and (max-width: 1472px) + { + top: calc(15vw - 20px); + width: calc(8vw - 24px); + height: calc(8vw - 17px); + + @media screen and (max-width: 860px) + { + top: calc(35vw - 65px); + width: 12vw; + height: 14vw; + } + } + + } } - .list + .content { + @include flex(column); + gap: $spacingM; + .cta { align-self: flex-end; } + } - @media screen and (max-width: 860px) + @media screen and (max-width: 860px) + { + .content { grid-row: 1; + + .list + { + display: none; + } + } + + .illustrations + { + .mobileNav + { + display: flex; + } + + .item + { + h3 + { + display: block; + } + + p + { + @include subtitle2($fontFamilyBaseAlt); + } + } } } } diff --git a/app/_page_sections/SkillsSection.tsx b/app/_page_sections/SkillsSection.tsx index 6fdbc41..283076e 100644 --- a/app/_page_sections/SkillsSection.tsx +++ b/app/_page_sections/SkillsSection.tsx @@ -1,13 +1,15 @@ "use client"; import Button from "@/_components/Button"; +import links from "@/_data/links"; import skills from "@/_data/skills"; import shared from "@/_styles/gallery.module.scss"; -import { ArrowDownload24Regular } from "@fluentui/react-icons"; +import ArrowDownload24Regular from "@fluentui/svg-icons/icons/arrow_download_24_regular.svg"; +import ChevronLeft24Regular from "@fluentui/svg-icons/icons/chevron_left_24_regular.svg"; +import ChevronRight24Regular from "@fluentui/svg-icons/icons/chevron_right_24_regular.svg"; import Image from "next/image"; import React, { useCallback, useId, useState } from "react"; import cls from "./SkillsSection.module.scss"; -import links from "@/_data/links"; const SkillsSection: React.FC = () => { @@ -24,39 +26,74 @@ const SkillsSection: React.FC = () =>
{ skills.map((i, index) => - +