portfolio overhaul

This commit is contained in:
SowinskiBraeden committed 2026-03-14 15:41:08 -07:00
1 parent 2b0b41f773
commit 7c26e115ee
20 files changed
+1870 -679

No files matched your search

+42
View File
@@ -0,0 +1,42 @@
# Portfolio content workflow
Projects and updates are now loaded from markdown files.
## Add a new project or update
1. Add a new `.md` file inside one of these folders:
- `public/content/projects/`
- `public/content/updates/`
2. Add front matter at the top of the file.
3. Run:
```bash
python3 tools/generate_content_index.py
```
That refreshes `public/content/index.json`, which is what the browser uses to build the projects and updates pages.
## Front matter example
```md
---
title: Example Entry
slug: example-entry
summary: Short card summary text.
label: Project
date: 2026-03-14
status: Active
featured: true
order: 99
github: https://github.com/example/repo
demo: https://example.com
tags: HTML, CSS, JavaScript
---
```
## Notes
- `demo` can be omitted.
- `featured` can be `true` or `false`.
- `order` controls the card order on the page.
- The markdown body below the front matter is rendered on `entry.html`.
+62 -97
View File
@@ -5,24 +5,23 @@
<title>Braeden Sowinski</title> <title>Braeden Sowinski</title>
<meta name='description' content="Braeden Sowinski's portfolio" /> <meta name='description' content="Braeden Sowinski's portfolio" />
<meta name='author' content="Braeden Sowinski" /> <meta name='author' content="Braeden Sowinski" />
<meta name='keywords' content="Braeden Sowinski, McDazzzled" /> <meta name='keywords' content="Braeden Sowinski, software engineer, systems programming, BCIT" />
<meta name='viewport' content='width=device-width, initial-scale=1.0'> <meta name='viewport' content='width=device-width, initial-scale=1.0'>
<!-- Load CSS -->
<link rel="stylesheet" href="./public/css/style.css"> <link rel="stylesheet" href="./public/css/style.css">
<link rel="stylesheet" href="./public/css/about.css"> <link rel="stylesheet" href="./public/css/about.css">
</head> </head>
<body class="js-stop-transition"> <body class="js-stop-transition">
<header class=header> <header class="header">
<nav class=nav> <nav class="nav">
<div class=logo> <div class="logo">
<a href="/" title="sowinski.dev (Alt + H)"> <a href="./" title="sowinski.dev (Alt + H)">
<img src="https://avatars.githubusercontent.com/u/48144618?v=4" alt=logo style="border-radius:50%;" <img src="https://avatars.githubusercontent.com/u/48144618?v=4" alt="logo" style="border-radius:50%;"
aria-label=logo height=32>sowinski.dev aria-label="logo" height="32">sowinski.dev
</a> </a>
<span class="logo-switches"> <span class="logo-switches">
<button id=theme-toggle accesskey=t title="(Alt + T)"> <button id="theme-toggle" accesskey="t" title="(Alt + T)">
<svg id="sun" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" <svg id="sun" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
stroke="currentcolor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> stroke="currentcolor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M21 12.79A9 9 0 1111.21 3 7 7 0 0021 12.79z" /> <path d="M21 12.79A9 9 0 1111.21 3 7 7 0 0021 12.79z" />
@@ -44,17 +43,17 @@
</div> </div>
<ul id="menu" class="nav__links"> <ul id="menu" class="nav__links">
<li id="home-link"> <li id="home-link">
<a href="/" title="Home"> <a href="./" title="Home">
<span class="active">Home</span> <span class="active">Home</span>
</a> </a>
</li> </li>
<li id="about-link"> <li id="about-link">
<a href="/about" title="about"> <a href="./about.html" title="About">
<span class="active">About</span> <span class="active">About</span>
</a> </a>
</li> </li>
<li id="projects-link"> <li id="projects-link">
<a href="/projects" title="projects"> <a href="./projects.html" title="Projects">
<span class="active">Projects</span> <span class="active">Projects</span>
</a> </a>
</li> </li>
@@ -62,39 +61,33 @@
<p class="menu cta">Menu</p> <p class="menu cta">Menu</p>
</nav> </nav>
<!-- Moble Nav Menu -->
<div id="mobile__menu" class="overlay"> <div id="mobile__menu" class="overlay">
<a class="close">&times;</a> <button type="button" class="close" aria-label="Close menu">&times;</button>
<div class="overlay__content"> <div class="overlay__content">
<a href="/" title="Home"> <a href="./" title="Home"><span class="active">Home</span></a>
<span class="active">Home</span> <a href="./about.html" title="About"><span class="active">About</span></a>
</a> <a href="./projects.html" title="Projects"><span class="active">Projects</span></a>
<a href="/about" title="about">
<span class="active">About</span>
</a>
<a href="/projects" title="projects">
<span class="active">Projects</span>
</a>
</div> </div>
</div> </div>
</header> </header>
<div class="container"> <main class="container page-shell">
<div class="left-content"> <div class="left-content">
<section id="profile"> <section id="profile">
<img src="https://avatars.githubusercontent.com/u/48144618" class="profile" alt="profile"> <img src="https://avatars.githubusercontent.com/u/48144618" class="profile" alt="Braeden Sowinski profile photo">
<div class="profile-sub"> <div class="profile-sub">
<h2 style="margin-top: 2px; margin-bottom: 2px;">Braeden Sowinski</h2> <h2 class="profile-name">Braeden Sowinski</h2>
<span class="des" style="font-size: 14pt; font-weight: 400;"> <p class="des profile-role">
Software Engineer & Systems Admin | CST Student @ BCIT Software Engineer &amp; Systems Admin | CST Student @ BCIT
Passion for Backend Systems & Data-Driven Development </p>
</span> <p class="des profile-focus">
Backend systems, APIs, data-driven software, and low-level problem solving
</p>
</div> </div>
</section> </section>
<section id="skills"> <section id="skills">
<h3>SKILLS</h3> <h3>Skills</h3>
<ul class="text skills-list">
<ul class="text">
<li>Java, C, Go</li> <li>Java, C, Go</li>
<li>JavaScript (Node.js)</li> <li>JavaScript (Node.js)</li>
<li>Python3+</li> <li>Python3+</li>
@@ -107,7 +100,7 @@
<li>Data-Driven Software</li> <li>Data-Driven Software</li>
<li>Oral & Written Communication</li> <li>Oral & Written Communication</li>
<li>Team Collaboration & Adaptability</li> <li>Team Collaboration & Adaptability</li>
<ul> </ul>
</section> </section>
</div> </div>
@@ -116,36 +109,47 @@
<p class="text"> <p class="text">
As a software engineer, I am passionate about developing back-end systems and data-driven applications. As a software engineer, I am passionate about developing back-end systems and data-driven applications.
Currently pursuing a Diploma in Computer Systems Technology at Currently pursuing a Diploma in Computer Systems Technology at
<abbr title="British Columbia Institute of Technology">BCIT</abbr>, I continuously expand my skills <abbr title="British Columbia Institute of Technology">BCIT</abbr>, I continue expanding my skills through
through hands-on projects and real-world applications, driven by curiosity and a love for problem-solving. hands-on projects, real-world applications, and a genuine love for problem-solving.
</p> </p>
<p class="text"> <p class="text">
Experience building back-end systems, APIs, and full-stack applications has strengthened my foundation Experience building back-end systems, APIs, and full-stack applications has strengthened my foundation in
in languages such as C, Go, Java, JavaScript, and Python, as well as technologies like Docker, MongoDB, languages such as C, Go, Java, JavaScript, and Python, as well as technologies like Docker, MongoDB, and SQL.
and SQL. Working on collaborative BCIT projects and open-source contributions has further developed my Working on collaborative BCIT projects and open-source software has also sharpened my communication,
skills in agile workflows, oral and written communication, and delivering solutions that solve real-world p adaptability, and ability to deliver practical solutions.
roblems, all while exploring new frameworks and methodologies </p>
<p class="text" style="margin-bottom: 0;">
Most recently, I was part of Team Cerberus Gate at the inaugural Red Team Hackathon, where we earned a
third-place finish after building a prototype focused on RF signal classification, modulation analysis, and
emitter geolocation.
</p> </p>
</section> </section>
<section class="honors" id="honors"> <section class="honors" id="honors">
<h3 class="title">Honors & Awards</h3> <h3 class="title">Highlights</h3>
<dl> <dl>
<dt class="text"><b>Red Team Hackathon — 3rd Place</b></dt>
<dd class="text">
Team Cerberus Gate earned a third-place finish at the inaugural Red Team Hackathon for building a working
prototype around signal classification and emitter tracking.
</dd>
<dt class="text"><b>Best Dressed Award</b></dt>
<dd class="text">
Team Cerberus Gate also received the Best Dressed Award during the same hackathon event.
</dd>
<dt class="text"><b>Capstone Project Recognition</b></dt> <dt class="text"><b>Capstone Project Recognition</b></dt>
<dd class="text"> <dd class="text">
Issued by Issued by <abbr title="Sullivan Heights Secondary School">SHSS</abbr> for work on schedule-generating
<abbr title="Sullivan Heights Secondary School">SHSS</abbr> software used to produce student schedules and master timetables.
— Award recognizing the hard work on my schedule-generating software, generating student schedules
and master timetables for a high school environment
</dd> </dd>
<dt class="text"><b>Master Digital Crafter</b></dt> <dt class="text"><b>Master Digital Crafter</b></dt>
<dd class="text"> <dd class="text">
Issued by Issued by <abbr title="Sullivan Heights Secondary School">SHSS</abbr> for excellence in Web Development 10
<abbr title="Sullivan Heights Secondary School">SHSS</abbr> and Software Engineering 11/12.
— Award issued by the former Head IT Admin/Teacher at Sullivan Heights Secondary for excelling in
Web Development 10, and Software Engineering 11/12
</dd> </dd>
</dl> </dl>
</section> </section>
@@ -153,73 +157,34 @@
<footer class="footer flex-container"> <footer class="footer flex-container">
<p>Get in touch with me!</p> <p>Get in touch with me!</p>
<div class="social-icons"> <div class="social-icons">
<a class="icon" href="https://www.linkedin.com/in/SowinskiBraeden" target=_blank rel="noopener noreferrer me" <a class="icon" href="https://www.linkedin.com/in/SowinskiBraeden" target="_blank" rel="noopener noreferrer me"
title=LinkedIn> title="LinkedIn">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-linkedin" <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-linkedin"
viewBox="0 0 16 16"> viewBox="0 0 16 16">
<path <path
d="M0 1.146C0 .513.526 0 1.175 0h13.65C15.474 0 16 .513 16 1.146v13.708c0 .633-.526 1.146-1.175 1.146H1.175C.526 16 0 15.487 0 14.854zm4.943 12.248V6.169H2.542v7.225zm-1.2-8.212c.837 0 1.358-.554 1.358-1.248-.015-.709-.52-1.248-1.342-1.248S2.4 3.226 2.4 3.934c0 .694.521 1.248 1.327 1.248zm4.908 8.212V9.359c0-.216.016-.432.08-.586.173-.431.568-.878 1.232-.878.869 0 1.216.662 1.216 1.634v3.865h2.401V9.25c0-2.22-1.184-3.252-2.764-3.252-1.274 0-1.845.7-2.165 1.193v.025h-.016l.016-.025V6.169h-2.4c.03.678 0 7.225 0 7.225z" /> d="M0 1.146C0 .513.526 0 1.175 0h13.65C15.474 0 16 .513 16 1.146v13.708c0 .633-.526 1.146-1.175 1.146H1.175C.526 16 0 15.487 0 14.854zm4.943 12.248V6.169H2.542v7.225zm-1.2-8.212c.837 0 1.358-.554 1.358-1.248-.015-.709-.52-1.248-1.342-1.248S2.4 3.226 2.4 3.934c0 .694.521 1.248 1.327 1.248zm4.908 8.212V9.359c0-.216.016-.432.08-.586.173-.431.568-.878 1.232-.878.869 0 1.216.662 1.216 1.634v3.865h2.401V9.25c0-2.22-1.184-3.252-2.764-3.252-1.274 0-1.845.7-2.165 1.193v.025h-.016l.016-.025V6.169h-2.4c.03.678 0 7.225 0 7.225z" />
</svg> </svg>
</a> </a>
<a class="icon" href=https://github.com/SowinskiBraeden target=_blank rel="noopener noreferrer me" title=Github> <a class="icon" href="https://github.com/SowinskiBraeden" target="_blank" rel="noopener noreferrer me" title="Github">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentcolor" stroke-width="2" <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentcolor" stroke-width="2"
stroke-linecap="round" stroke-linejoin="round"> stroke-linecap="round" stroke-linejoin="round">
<path <path
d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37.0 00-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44.0 0020 4.77 5.07 5.07.0 0019.91 1S18.73.65 16 2.48a13.38 13.38.0 00-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07.0 005 4.77 5.44 5.44.0 003.5 8.55c0 5.42 3.3 6.61 6.44 7A3.37 3.37.0 009 18.13V22" /> d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 00-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0020 4.77 5.07 5.07 0 0019.91 1S18.73.65 16 2.48a13.38 13.38 0 00-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 005 4.77 5.44 5.44 0 003.5 8.55c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 009 18.13V22" />
</svg> </svg>
</a> </a>
<a class="icon" href=https://twitter.com/braedensowinski target=_blank rel="noopener noreferrer me" title=Twitter> <a class="icon" href="mailto:braeden@sowinski.dev" target="_blank" rel="noopener noreferrer me" title="Email">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentcolor" stroke-width="2"
stroke-linecap="round" stroke-linejoin="round">
<path
d="M23 3a10.9 10.9.0 01-3.14 1.53 4.48 4.48.0 00-7.86 3v1A10.66 10.66.0 013 4s-4 9 5 13a11.64 11.64.0 01-7 2c9 5 20 0 20-11.5a4.5 4.5.0 00-.08-.83A7.72 7.72.0 0023 3z" />
</svg>
</a>
<a class="icon" href="mailto:braeden@sowinski.dev" target=_blank rel="noopener noreferrer me" title=Email>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 21" fill="none" stroke="currentcolor" stroke-width="2" <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 21" fill="none" stroke="currentcolor" stroke-width="2"
stroke-linecap="round" stroke-linejoin="round"> stroke-linecap="round" stroke-linejoin="round">
<path d="M4 4h16c1.1.0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1.0-2-.9-2-2V6c0-1.1.9-2 2-2z" /> <path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z" />
<polyline points="22,6 12,13 2,6" /> <polyline points="22,6 12,13 2,6" />
</svg> </svg>
</a> </a>
</div> </div>
</footer> </footer>
</div> </main>
<!-- Import Mobile.js for mobile nav menu -->
<script src="./public/scripts/mobile.js"></script> <script src="./public/scripts/mobile.js"></script>
<script src="./public/scripts/theme.js"></script>
<!-- Disable transitions on webpage load -->
<script>document.body.classList.remove('js-stop-transition')</script>
<!-- Change Theme Scripts -->
<script>
document.getElementById("theme-toggle").addEventListener("click", () => {
document.body.className.includes("light") ? (
document.body.classList.remove('light'),
localStorage.setItem("pref-theme",'dark')
) : (
document.body.classList.add('light'),
localStorage.setItem("pref-theme",'light')
),
"dark" == sessionStorage.getItem("theme") ? sessionStorage.setItem("theme", "light") : sessionStorage.setItem("theme", "dark");
});
</script>
<script>
// If storage is supported set theme to light by default
if (typeof(Storage) !== "undefined") {
if (sessionStorage.getItem("theme") == null || sessionStorage.getItem("theme") == undefined) sessionStorage.setItem("theme", "dark");
if (sessionStorage.getItem("theme") == 'light') {
document.body.className.includes("dark") ? (
document.body.classList.remove('dark'),
localStorage.setItem("pref-theme",'light')
) : (
document.body.classList.add('dark'),
localStorage.setItem("pref-theme",'dark')
);
}
}
</script>
</body> </body>
</html> </html>
+89
View File
@@ -0,0 +1,89 @@
<!DOCTYPE html>
<html lang='en'>
<head>
<title>Braeden Sowinski</title>
<meta name='description' content="Braeden Sowinski's portfolio" />
<meta name='author' content="Braeden Sowinski" />
<meta name='keywords' content="Braeden Sowinski, project entry, portfolio" />
<meta name='viewport' content='width=device-width, initial-scale=1.0'>
<link rel="stylesheet" href="./public/css/style.css">
<link rel="stylesheet" href="./public/css/projects.css">
</head>
<body class="js-stop-transition">
<header class="header">
<nav class="nav">
<div class="logo">
<a href="./" title="sowinski.dev (Alt + H)">
<img src="https://avatars.githubusercontent.com/u/48144618?v=4" alt="logo" style="border-radius:50%;"
aria-label="logo" height="32">sowinski.dev
</a>
<span class="logo-switches">
<button id="theme-toggle" accesskey="t" title="(Alt + T)">
<svg id="sun" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
stroke="currentcolor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M21 12.79A9 9 0 1111.21 3 7 7 0 0021 12.79z" />
</svg>
<svg id="moon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
stroke="currentcolor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="5" />
<line x1="12" y1="1" x2="12" y2="3" />
<line x1="12" y1="21" x2="12" y2="23" />
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64" />
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78" />
<line x1="1" y1="12" x2="3" y2="12" />
<line x1="21" y1="12" x2="23" y2="12" />
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36" />
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22" />
</svg>
</button>
</span>
</div>
<ul id="menu" class="nav__links">
<li id="home-link">
<a href="./" title="Home">
<span class="active">Home</span>
</a>
</li>
<li id="about-link">
<a href="./about.html" title="About">
<span class="active">About</span>
</a>
</li>
<li id="projects-link">
<a href="./projects.html" title="Projects">
<span class="active">Projects</span>
</a>
</li>
</ul>
<p class="menu cta">Menu</p>
</nav>
<div id="mobile__menu" class="overlay">
<button type="button" class="close" aria-label="Close menu">&times;</button>
<div class="overlay__content">
<a href="./" title="Home"><span class="active">Home</span></a>
<a href="./about.html" title="About"><span class="active">About</span></a>
<a href="./projects.html" title="Projects"><span class="active">Projects</span></a>
</div>
</div>
</header>
<main class="entry-page page-shell">
<div id="entry-shell" class="entry-shell">
<p class="loading-state">Loading entry...</p>
</div>
</main>
<script src="./public/scripts/mobile.js"></script>
<script src="./public/scripts/theme.js"></script>
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
<script src="./public/scripts/content.js"></script>
<script>
window.PORTFOLIO_CONTENT.initEntryPage();
</script>
</body>
</html>
+72 -96
View File
@@ -5,23 +5,22 @@
<title>Braeden Sowinski</title> <title>Braeden Sowinski</title>
<meta name='description' content="Braeden Sowinski's portfolio" /> <meta name='description' content="Braeden Sowinski's portfolio" />
<meta name='author' content="Braeden Sowinski" /> <meta name='author' content="Braeden Sowinski" />
<meta name='keywords' content="Braeden Sowinski, McDazzzled" /> <meta name='keywords' content="Braeden Sowinski, software engineer, systems programming, BCIT" />
<meta name='viewport' content='width=device-width, initial-scale=1.0'> <meta name='viewport' content='width=device-width, initial-scale=1.0'>
<!-- Load CSS -->
<link rel="stylesheet" href="./public/css/style.css"> <link rel="stylesheet" href="./public/css/style.css">
</head> </head>
<body class="js-stop-transition"> <body class="js-stop-transition">
<header class=header> <header class="header">
<nav class=nav> <nav class="nav">
<div class=logo> <div class="logo">
<a href="/" title="sowinski.dev"> <a href="./" title="sowinski.dev">
<img src="https://avatars.githubusercontent.com/u/48144618?v=4" alt=logo style="border-radius:50%;" <img src="https://avatars.githubusercontent.com/u/48144618?v=4" alt="logo" style="border-radius:50%;"
aria-label=logo height=32>sowinski.dev aria-label="logo" height="32">sowinski.dev
</a> </a>
<span class="logo-switches"> <span class="logo-switches">
<button id=theme-toggle accesskey=t title="(Alt + T)"> <button id="theme-toggle" accesskey="t" title="(Alt + T)">
<svg id="sun" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" <svg id="sun" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
stroke="currentcolor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> stroke="currentcolor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M21 12.79A9 9 0 1111.21 3 7 7 0 0021 12.79z" /> <path d="M21 12.79A9 9 0 1111.21 3 7 7 0 0021 12.79z" />
@@ -43,17 +42,17 @@
</div> </div>
<ul id="menu" class="nav__links"> <ul id="menu" class="nav__links">
<li id="home-link"> <li id="home-link">
<a href="/" title="Home"> <a href="./" title="Home">
<span class="active">Home</span> <span class="active">Home</span>
</a> </a>
</li> </li>
<li id="about-link"> <li id="about-link">
<a href="/about" title="about"> <a href="./about.html" title="About">
<span class="active">About</span> <span class="active">About</span>
</a> </a>
</li> </li>
<li id="projects-link"> <li id="projects-link">
<a href="/projects" title="projects"> <a href="./projects.html" title="Projects">
<span class="active">Projects</span> <span class="active">Projects</span>
</a> </a>
</li> </li>
@@ -61,99 +60,76 @@
<p class="menu cta">Menu</p> <p class="menu cta">Menu</p>
</nav> </nav>
<!-- Moble Nav Menu -->
<div id="mobile__menu" class="overlay"> <div id="mobile__menu" class="overlay">
<a class="close">&times;</a> <button type="button" class="close" aria-label="Close menu">&times;</button>
<div class="overlay__content"> <div class="overlay__content">
<a href="/" title="Home"><span class="active">Home</span></a> <a href="./" title="Home"><span class="active">Home</span></a>
<a href="/about" title="about"><span class="active">About</span></a> <a href="./about.html" title="About"><span class="active">About</span></a>
<a href="/projects" title="projects"><span class="active">Projects</span></a> <a href="./projects.html" title="Projects"><span class="active">Projects</span></a>
</div> </div>
</div> </div>
</header> </header>
<!-- Main Body --> <main class="main hero-main">
<div class="main"> <div class="hero-shell">
<img src="https://avatars.githubusercontent.com/u/48144618" class="profile" alt="profile"> <div class="hero-shell__shine"></div>
<div id="main-wrapper" class="svg-wrapper"> <img src="https://avatars.githubusercontent.com/u/48144618" class="profile" alt="Braeden Sowinski profile photo">
<svg height="60" width="320" xmlns="http://www.w3.org/2000/svg"> <div id="main-wrapper" class="svg-wrapper">
<rect x="0" y="0" class="shape" height="60" width="320" /> <svg height="60" width="320" xmlns="http://www.w3.org/2000/svg">
<h2 style="margin-top: 16pt;" class="name">Braeden Sowinski</h2> <rect x="0" y="0" class="shape" height="60" width="320" />
</svg> <h2 style="margin-top: 16pt;" class="name">Braeden Sowinski</h2>
</div>
<h2 style="margin-top: 2pt;" class="mobile-name">Braeden Sowinski</h2>
<span class="des">
Software Engineer & Systems Admin | CST Student @ BCIT
</span><br>
<span class="des">
Passion for Backend Systems & Data-Driven Development
</span>
<div class="social-icons">
<a class="icon" href="https://www.linkedin.com/in/SowinskiBraeden" target=_blank rel="noopener noreferrer me"
title=LinkedIn>
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-linkedin"
viewBox="0 0 16 16">
<path
d="M0 1.146C0 .513.526 0 1.175 0h13.65C15.474 0 16 .513 16 1.146v13.708c0 .633-.526 1.146-1.175 1.146H1.175C.526 16 0 15.487 0 14.854zm4.943 12.248V6.169H2.542v7.225zm-1.2-8.212c.837 0 1.358-.554 1.358-1.248-.015-.709-.52-1.248-1.342-1.248S2.4 3.226 2.4 3.934c0 .694.521 1.248 1.327 1.248zm4.908 8.212V9.359c0-.216.016-.432.08-.586.173-.431.568-.878 1.232-.878.869 0 1.216.662 1.216 1.634v3.865h2.401V9.25c0-2.22-1.184-3.252-2.764-3.252-1.274 0-1.845.7-2.165 1.193v.025h-.016l.016-.025V6.169h-2.4c.03.678 0 7.225 0 7.225z" />
</svg> </svg>
</a> </div>
<a class="icon" href=https://github.com/SowinskiBraeden target=_blank rel="noopener noreferrer me" title=Github> <h2 style="margin-top: 2pt;" class="mobile-name">Braeden Sowinski</h2>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentcolor" stroke-width="2"
stroke-linecap="round" stroke-linejoin="round"> <p class="des hero-description">
<path Software Engineer & Systems Admin | CST Student @ BCIT
d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37.0 00-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44.0 0020 4.77 5.07 5.07.0 0019.91 1S18.73.65 16 2.48a13.38 13.38.0 00-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07.0 005 4.77 5.44 5.44.0 003.5 8.55c0 5.42 3.3 6.61 6.44 7A3.37 3.37.0 009 18.13V22" /> </p>
</svg> <p class="des hero-description hero-description-secondary">
</a> Backend systems, APIs, data-driven software, and low-level problem solving
<a class="icon" href=https://twitter.com/braedensowinski target=_blank rel="noopener noreferrer me" title=Twitter> </p>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentcolor" stroke-width="2"
stroke-linecap="round" stroke-linejoin="round"> <div class="hero-badges">
<path <span class="badge">BCIT CST Diploma</span>
d="M23 3a10.9 10.9.0 01-3.14 1.53 4.48 4.48.0 00-7.86 3v1A10.66 10.66.0 013 4s-4 9 5 13a11.64 11.64.0 01-7 2c9 5 20 0 20-11.5a4.5 4.5.0 00-.08-.83A7.72 7.72.0 0023 3z" /> <span class="badge">Backend Systems</span>
</svg> <span class="badge">3rd Place · Red Team Hackathon</span>
</a> </div>
<a class="icon" href="mailto:braeden@sowinski.dev" target=_blank rel="noopener noreferrer me" title=Email>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 21" fill="none" stroke="currentcolor" stroke-width="2" <div class="hero-actions">
stroke-linecap="round" stroke-linejoin="round"> <a class="button button-primary" href="./projects.html">View Projects</a>
<path d="M4 4h16c1.1.0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1.0-2-.9-2-2V6c0-1.1.9-2 2-2z" /> <a class="button button-secondary" href="./about.html">About Me</a>
<polyline points="22,6 12,13 2,6" /> </div>
</svg>
</a> <div class="social-icons">
<a class="icon" href="https://www.linkedin.com/in/SowinskiBraeden" target="_blank" rel="noopener noreferrer me"
title="LinkedIn">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-linkedin"
viewBox="0 0 16 16">
<path
d="M0 1.146C0 .513.526 0 1.175 0h13.65C15.474 0 16 .513 16 1.146v13.708c0 .633-.526 1.146-1.175 1.146H1.175C.526 16 0 15.487 0 14.854zm4.943 12.248V6.169H2.542v7.225zm-1.2-8.212c.837 0 1.358-.554 1.358-1.248-.015-.709-.52-1.248-1.342-1.248S2.4 3.226 2.4 3.934c0 .694.521 1.248 1.327 1.248zm4.908 8.212V9.359c0-.216.016-.432.08-.586.173-.431.568-.878 1.232-.878.869 0 1.216.662 1.216 1.634v3.865h2.401V9.25c0-2.22-1.184-3.252-2.764-3.252-1.274 0-1.845.7-2.165 1.193v.025h-.016l.016-.025V6.169h-2.4c.03.678 0 7.225 0 7.225z" />
</svg>
</a>
<a class="icon" href="https://github.com/SowinskiBraeden" target="_blank" rel="noopener noreferrer me" title="Github">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentcolor" stroke-width="2"
stroke-linecap="round" stroke-linejoin="round">
<path
d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 00-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0020 4.77 5.07 5.07 0 0019.91 1S18.73.65 16 2.48a13.38 13.38 0 00-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 005 4.77 5.44 5.44 0 003.5 8.55c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 009 18.13V22" />
</svg>
</a>
<a class="icon" href="mailto:braeden@sowinski.dev" target="_blank" rel="noopener noreferrer me" title="Email">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 21" fill="none" stroke="currentcolor" stroke-width="2"
stroke-linecap="round" stroke-linejoin="round">
<path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z" />
<polyline points="22,6 12,13 2,6" />
</svg>
</a>
</div>
</div> </div>
</main>
<!-- Import Mobile.js for mobile nav menu --> <script src="./public/scripts/mobile.js"></script>
<script src="./public/scripts/mobile.js"></script> <script src="./public/scripts/theme.js"></script>
<script src="./public/scripts/hero-effects.js"></script>
<!-- Disable transitions on webpage load -->
<script>document.body.classList.remove('js-stop-transition')</script>
<!-- Change Theme Scripts -->
<script>
document.getElementById("theme-toggle").addEventListener("click", () => {
document.body.className.includes("light") ? (
document.body.classList.remove('light'),
localStorage.setItem("pref-theme",'dark')
) : (
document.body.classList.add('light'),
localStorage.setItem("pref-theme",'light')
),
"dark" == sessionStorage.getItem("theme") ? sessionStorage.setItem("theme", "light") : sessionStorage.setItem("theme", "dark");
});
</script>
<script>
// If storage is supported set theme to light by default
if (typeof(Storage) !== "undefined") {
if (sessionStorage.getItem("theme") == null || sessionStorage.getItem("theme") == undefined) sessionStorage.setItem("theme", "dark");
if (sessionStorage.getItem("theme") == 'light') {
document.body.className.includes("dark") ? (
document.body.classList.remove('dark'),
localStorage.setItem("pref-theme",'light')
) : (
document.body.classList.add('dark'),
localStorage.setItem("pref-theme",'dark')
);
}
}
</script>
</body> </body>
</html> </html>
+80 -209
View File
@@ -1,27 +1,27 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang='en'>
<head> <head>
<title>Braeden Sowinski</title> <title>Braeden Sowinski</title>
<meta name='description' content="Web portfolio of Braeden Sowinski (McDazzzled)" /> <meta name='description' content="Braeden Sowinski's portfolio" />
<meta name='author' content="Braeden Sowinski" /> <meta name='author' content="Braeden Sowinski" />
<meta name='keywords' content="Braeden Sowinski, McDazzzled" /> <meta name='keywords' content="Braeden Sowinski, projects, portfolio, software engineer" />
<meta name='viewport' content='width=device-width, initial-scale=1.0'> <meta name='viewport' content='width=device-width, initial-scale=1.0'>
<!-- Load CSS -->
<link rel="stylesheet" href="./public/css/style.css"> <link rel="stylesheet" href="./public/css/style.css">
<link rel="stylesheet" href="./public/css/projects.css"> <link rel="stylesheet" href="./public/css/projects.css">
</head> </head>
<body class="js-stop-transition"> <body class="js-stop-transition">
<header class=header> <header class="header">
<nav class=nav> <nav class="nav">
<div class=logo> <div class="logo">
<a href="/" title="sowinski.dev"> <a href="./" title="sowinski.dev (Alt + H)">
<img src="https://avatars.githubusercontent.com/u/48144618?v=4" alt=logo style="border-radius:50%;" <img src="https://avatars.githubusercontent.com/u/48144618?v=4" alt="logo" style="border-radius:50%;"
aria-label=logo height=32>sowinski.dev aria-label="logo" height="32">sowinski.dev
</a> </a>
<span class="logo-switches"> <span class="logo-switches">
<button id=theme-toggle accesskey=t title="(Alt + T)"> <button id="theme-toggle" accesskey="t" title="(Alt + T)">
<svg id="sun" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" <svg id="sun" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
stroke="currentcolor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> stroke="currentcolor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M21 12.79A9 9 0 1111.21 3 7 7 0 0021 12.79z" /> <path d="M21 12.79A9 9 0 1111.21 3 7 7 0 0021 12.79z" />
@@ -43,17 +43,17 @@
</div> </div>
<ul id="menu" class="nav__links"> <ul id="menu" class="nav__links">
<li id="home-link"> <li id="home-link">
<a href="/" title="Home"> <a href="./" title="Home">
<span class="active">Home</span> <span class="active">Home</span>
</a> </a>
</li> </li>
<li id="about-link"> <li id="about-link">
<a href="/about" title="about"> <a href="./about.html" title="About">
<span class="active">About</span> <span class="active">About</span>
</a> </a>
</li> </li>
<li id="projects-link"> <li id="projects-link">
<a href="/projects" title="projects"> <a href="./projects.html" title="Projects">
<span class="active">Projects</span> <span class="active">Projects</span>
</a> </a>
</li> </li>
@@ -61,215 +61,86 @@
<p class="menu cta">Menu</p> <p class="menu cta">Menu</p>
</nav> </nav>
<!-- Moble Nav Menu -->
<div id="mobile__menu" class="overlay"> <div id="mobile__menu" class="overlay">
<a class="close">&times;</a> <button type="button" class="close" aria-label="Close menu">&times;</button>
<div class="overlay__content"> <div class="overlay__content">
<a href="/" title="Home"> <a href="./" title="Home"><span class="active">Home</span></a>
<span class="active">Home</span> <a href="./about.html" title="About"><span class="active">About</span></a>
</a> <a href="./projects.html" title="Projects"><span class="active">Projects</span></a>
<a href="/about" title="about">
<span class="active">About</span>
</a>
<a href="/projects" title="projects">
<span class="active">Projects</span>
</a>
</div> </div>
</div> </div>
</header> </header>
<section class="card-body"> <main class="projects-page page-shell">
<div class="container"> <section class="projects-hero">
<div class="card"> <p class="eyebrow">Projects & Updates</p>
<div class="box"> <h1>Selected work, recent milestones, and technical writeups.</h1>
<div class="content"> <p class="projects-intro">
<div class="content-head"> This section now loads portfolio entries from markdown files so new projects, updates, and achievements can be
<h2>01</h2> added without rebuilding the page structure by hand.
<h3>Schedule Generator App</h3> </p>
</div> </section>
<div class="content-body">
<p> <section class="content-section">
A desktop application to interact with my schedule generator algorithm. <div class="section-heading">
The algorithm is written in Python and generates master and student timetables. <div>
I am proud of the algorithm I devised to solve this complex real-world problem. <p class="eyebrow">Portfolio</p>
</p> <h2>Selected Projects</h2>
<a href="https://github.com/SowinskiBraeden/ScheduleGeneratorApp" target="_blank">Github</a> </div>
</div> <a class="section-link" href="https://github.com/SowinskiBraeden" target="_blank" rel="noopener noreferrer">View Github</a>
</div> </div>
<div id="projects-grid" class="content-grid">
<p class="loading-state">Loading projects...</p>
</div>
</section>
<section class="content-section updates-section">
<div class="section-heading">
<div>
<p class="eyebrow">Recent</p>
<h2>Updates & Achievements</h2>
</div> </div>
</div> </div>
<div id="updates-grid" class="content-grid updates-grid">
<div class="card"> <p class="loading-state">Loading updates...</p>
<div class="box">
<div class="content">
<div class="content-head">
<h2>02</h2>
<h3>School Management API</h3>
</div>
<div class="content-body">
<p>
I have developed an API to manage an entire school, keeping track of students and staff
details. With the potential to be expanded upon more, I have yet to find the time to
further develop this API, but I used this project to deepen my understanding of the
programming language Go, API development, and clear documentation.
</p>
<a href="https://github.com/SowinskiBraeden/school-management-api" target="_blank">Github</a>
</div>
</div>
</div>
</div> </div>
</section>
<div class="card"> <footer class="footer flex-container site-footer">
<div class="box"> <p>Get in touch with me!</p>
<div class="content"> <div class="social-icons">
<div class="content-head"> <a class="icon" href="https://www.linkedin.com/in/SowinskiBraeden" target="_blank" rel="noopener noreferrer me"
<h2>03</h2> title="LinkedIn">
<h3>Lines Police CAD</h3> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-linkedin"
</div> viewBox="0 0 16 16">
<div class="content-body"> <path
<p> d="M0 1.146C0 .513.526 0 1.175 0h13.65C15.474 0 16 .513 16 1.146v13.708c0 .633-.526 1.146-1.175 1.146H1.175C.526 16 0 15.487 0 14.854zm4.943 12.248V6.169H2.542v7.225zm-1.2-8.212c.837 0 1.358-.554 1.358-1.248-.015-.709-.52-1.248-1.342-1.248S2.4 3.226 2.4 3.934c0 .694.521 1.248 1.327 1.248zm4.908 8.212V9.359c0-.216.016-.432.08-.586.173-.431.568-.878 1.232-.878.869 0 1.216.662 1.216 1.634v3.865h2.401V9.25c0-2.22-1.184-3.252-2.764-3.252-1.274 0-1.845.7-2.165 1.193v.025h-.016l.016-.025V6.169h-2.4c.03.678 0 7.225 0 7.225z" />
<strong style="color:#eb5234;">Lines Police CAD</strong> is a website service built with </svg>
Node.js utilizing Express.js and the MongoDB frameworks. With over half a million users, I </a>
have contributed new features, bug fixes as well as serving general tech support. <a class="icon" href="https://github.com/SowinskiBraeden" target="_blank" rel="noopener noreferrer me" title="Github">
</p> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentcolor" stroke-width="2"
<div id="links"> stroke-linecap="round" stroke-linejoin="round">
<a href="https://www.linespolice-cad.com/" target="_blank">View LPC</a> <path
<a href="https://github.com/LinesMerrill/police-cad" target="_blank">Github</a> d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 00-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0020 4.77 5.07 5.07 0 0019.91 1S18.73.65 16 2.48a13.38 13.38 0 00-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 005 4.77 5.44 5.44 0 003.5 8.55c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 009 18.13V22" />
</div> </svg>
</div> </a>
</div> <a class="icon" href="mailto:braeden@sowinski.dev" target="_blank" rel="noopener noreferrer me" title="Email">
</div> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 21" fill="none" stroke="currentcolor" stroke-width="2"
stroke-linecap="round" stroke-linejoin="round">
<path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z" />
<polyline points="22,6 12,13 2,6" />
</svg>
</a>
</div> </div>
</footer>
</main>
<div class="card">
<div class="box">
<div class="content">
<div class="content-head">
<h2>04</h2>
<h3>DayZ Reforger Bot</h3>
</div>
<div class="content-body">
<p>
Another Discord bot I have developed for a game, greatly displays my ability to work on
large scale projects, with modularity, and expandability in mind. This project focuses
on parsing data, storing data, and providing this data in an appealing way to users. With
many key features built on top of the parsed data from the game server.
</p>
<a href="https://github.com/SowinskiBraeden/dayz-reforger" target="_blank">Github</a>
</div>
</div>
</div>
</div>
<div class="card">
<div class="box">
<div class="content">
<div class="content-head">
<h2>05</h2>
<h3>XP-System</h3>
</div>
<div class="content-body">
<p>
An old programming project that introduced me into the valuable world of web applications.
The XP-System was a way for teachers to create assignments for students, and for students
to gain XP from completing assignments, unlocking levels to make the learning experience
more fun and interactive with their peers.
</p>
<a href="https://github.com/SowinskiBraeden/XP-System" target="_blank">Github</a>
</div>
</div>
</div>
</div>
<div class="card">
<div class="box">
<div class="content">
<div class="content-head">
<h2>06</h2>
<h3>Portfolio</h3>
</div>
<div class="content-body">
<p>
I am not a huge fan of UI design, but I am a fan of minimalistic design, and this portfolio
is just that. Simple, minimalistic, and sleek for the eye. Displaying my projects and sharing
about me.
</p>
<a href="https://github.com/SowinskiBraeden/SowinskiBraeden" target="_blank">Github</a>
</div>
</div>
</div>
</div>
<footer class="footer flex-container">
<p>Get in touch with me!</p>
<div class="social-icons">
<a class="icon" href="https://www.linkedin.com/in/SowinskiBraeden" target=_blank rel="noopener noreferrer me"
title=LinkedIn>
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-linkedin"
viewBox="0 0 16 16">
<path
d="M0 1.146C0 .513.526 0 1.175 0h13.65C15.474 0 16 .513 16 1.146v13.708c0 .633-.526 1.146-1.175 1.146H1.175C.526 16 0 15.487 0 14.854zm4.943 12.248V6.169H2.542v7.225zm-1.2-8.212c.837 0 1.358-.554 1.358-1.248-.015-.709-.52-1.248-1.342-1.248S2.4 3.226 2.4 3.934c0 .694.521 1.248 1.327 1.248zm4.908 8.212V9.359c0-.216.016-.432.08-.586.173-.431.568-.878 1.232-.878.869 0 1.216.662 1.216 1.634v3.865h2.401V9.25c0-2.22-1.184-3.252-2.764-3.252-1.274 0-1.845.7-2.165 1.193v.025h-.016l.016-.025V6.169h-2.4c.03.678 0 7.225 0 7.225z" />
</svg>
</a>
<a class="icon" href=https://github.com/SowinskiBraeden target=_blank rel="noopener noreferrer me" title=Github>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentcolor" stroke-width="2"
stroke-linecap="round" stroke-linejoin="round">
<path
d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37.0 00-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44.0 0020 4.77 5.07 5.07.0 0019.91 1S18.73.65 16 2.48a13.38 13.38.0 00-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07.0 005 4.77 5.44 5.44.0 003.5 8.55c0 5.42 3.3 6.61 6.44 7A3.37 3.37.0 009 18.13V22" />
</svg>
</a>
<a class="icon" href=https://twitter.com/braedensowinski target=_blank rel="noopener noreferrer me" title=Twitter>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentcolor" stroke-width="2"
stroke-linecap="round" stroke-linejoin="round">
<path
d="M23 3a10.9 10.9.0 01-3.14 1.53 4.48 4.48.0 00-7.86 3v1A10.66 10.66.0 013 4s-4 9 5 13a11.64 11.64.0 01-7 2c9 5 20 0 20-11.5a4.5 4.5.0 00-.08-.83A7.72 7.72.0 0023 3z" />
</svg>
</a>
<a class="icon" href="mailto:braeden@sowinski.dev" target=_blank rel="noopener noreferrer me" title=Email>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 21" fill="none" stroke="currentcolor" stroke-width="2"
stroke-linecap="round" stroke-linejoin="round">
<path d="M4 4h16c1.1.0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1.0-2-.9-2-2V6c0-1.1.9-2 2-2z" />
<polyline points="22,6 12,13 2,6" />
</svg>
</a>
</div>
</footer>
</div>
</section>
<!-- Import Mobile.js for mobile nav menu -->
<script src="./public/scripts/mobile.js"></script> <script src="./public/scripts/mobile.js"></script>
<script src="./public/scripts/theme.js"></script>
<!-- Disable transitions on webpage load --> <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
<script>document.body.classList.remove('js-stop-transition')</script> <script src="./public/scripts/content.js"></script>
<!-- Change Theme Scripts -->
<script> <script>
document.getElementById("theme-toggle").addEventListener("click", () => { window.PORTFOLIO_CONTENT.initListPage();
document.body.className.includes("light") ? (
document.body.classList.remove('light'),
localStorage.setItem("pref-theme",'dark')
) : (
document.body.classList.add('light'),
localStorage.setItem("pref-theme",'light')
),
"dark" == sessionStorage.getItem("theme") ? sessionStorage.setItem("theme", "light") : sessionStorage.setItem("theme", "dark");
});
</script>
<script>
// If storage is supported set theme to light by default
if (typeof(Storage) !== "undefined") {
if (sessionStorage.getItem("theme") == null || sessionStorage.getItem("theme") == undefined) sessionStorage.setItem("theme", "dark");
if (sessionStorage.getItem("theme") == 'light') {
document.body.className.includes("dark") ? (
document.body.classList.remove('dark'),
localStorage.setItem("pref-theme",'light')
) : (
document.body.classList.add('dark'),
localStorage.setItem("pref-theme",'dark')
);
}
}
</script> </script>
</body> </body>
+147
View File
@@ -0,0 +1,147 @@
{
"projects": [
{
"title": "Schedule Generator App",
"slug": "schedule-generator-app",
"type": "projects",
"summary": "A desktop application to interact with my schedule generator algorithm. The algorithm is written in Python and generates master and student timetables.",
"label": "Project",
"date": "2023-08-01",
"status": "Personal Project",
"github": "https://github.com/SowinskiBraeden/ScheduleGeneratorApp",
"demo": null,
"featured": true,
"order": 1,
"tags": [
"Python",
"Desktop App",
"Scheduling",
"Data Processing"
],
"path": "./public/content/projects/schedule-generator-app.md"
},
{
"title": "School Management API",
"slug": "school-management-api",
"type": "projects",
"summary": "I developed an API to manage an entire school, keeping track of students and staff details while expanding my understanding of Go, API development, and documentation.",
"label": "Project",
"date": "2023-09-01",
"status": "Personal Project",
"github": "https://github.com/SowinskiBraeden/school-management-api",
"demo": null,
"featured": true,
"order": 2,
"tags": [
"Go",
"API",
"Documentation",
"Education Tech"
],
"path": "./public/content/projects/school-management-api.md"
},
{
"title": "Lines Police CAD",
"slug": "lines-police-cad",
"type": "projects",
"summary": "A web service built with Node.js, Express.js, and MongoDB. I contributed features, bug fixes, and general technical support to a product serving a large user base.",
"label": "Project",
"date": "2022-06-01",
"status": "Collaboration",
"github": "https://github.com/LinesMerrill/police-cad",
"demo": "https://www.linespolice-cad.com/",
"featured": false,
"order": 3,
"tags": [
"Node.js",
"Express",
"MongoDB",
"Web App"
],
"path": "./public/content/projects/lines-police-cad.md"
},
{
"title": "DayZ Reforger Bot",
"slug": "dayz-reforger-bot",
"type": "projects",
"summary": "A large-scale Discord bot focused on parsing game server data, storing it, and presenting valuable information back to users through analytics-driven features.",
"label": "Project",
"date": "2024-11-08",
"status": "Active",
"github": "https://github.com/SowinskiBraeden/dayz-reforger",
"demo": null,
"featured": true,
"order": 4,
"tags": [
"Node.js",
"Discord.js",
"MongoDB",
"Analytics",
"Nitrado"
],
"path": "./public/content/projects/dayz-reforger-bot.md"
},
{
"title": "XP-System",
"slug": "xp-system",
"type": "projects",
"summary": "An early web application project where teachers could assign work and students could earn XP and levels, making the learning process more interactive.",
"label": "Project",
"date": "2021-05-01",
"status": "Early Project",
"github": "https://github.com/SowinskiBraeden/XP-System",
"demo": null,
"featured": false,
"order": 5,
"tags": [
"Web App",
"Education Tech",
"Gamification"
],
"path": "./public/content/projects/xp-system.md"
},
{
"title": "Portfolio",
"slug": "portfolio",
"type": "projects",
"summary": "A minimal personal portfolio focused on clarity, clean presentation, and an easy way to showcase projects, updates, and professional growth.",
"label": "Project",
"date": "2026-03-14",
"status": "Active",
"github": "https://github.com/SowinskiBraeden/SowinskiBraeden",
"demo": null,
"featured": false,
"order": 6,
"tags": [
"HTML",
"CSS",
"JavaScript",
"Portfolio"
],
"path": "./public/content/projects/portfolio.md"
}
],
"updates": [
{
"title": "3rd Place at the inaugural Red Team Hackathon",
"slug": "red-team-hackathon-2026",
"type": "updates",
"summary": "Team Cerberus Gate earned a third-place finish while building a prototype focused on RF signal classification, modulation analysis, and emitter geolocation.",
"label": "Update",
"date": "2026-03-12",
"status": "Achievement",
"github": null,
"demo": null,
"featured": true,
"order": 1,
"tags": [
"Hackathon",
"RF",
"Machine Learning",
"Geolocation",
"BCIT"
],
"path": "./public/content/updates/red-team-hackathon-2026.md"
}
]
}
@@ -0,0 +1,28 @@
---
title: DayZ Reforger Bot
slug: dayz-reforger-bot
summary: A large-scale Discord bot focused on parsing game server data, storing it, and presenting valuable information back to users through analytics-driven features.
label: Project
date: 2024-11-08
status: Active
featured: true
order: 4
github: https://github.com/SowinskiBraeden/dayz-reforger
tags: Node.js, Discord.js, MongoDB, Analytics, Nitrado
---
## Overview
This project highlights my interest in scalable backend systems, data parsing, and user-focused tooling.
It started as a smaller utility but expanded into a much larger system handling killfeeds, alarms, faction armbands, server data, and other game-related features.
## Focus Areas
- Parsing and structuring raw game server data
- Storing and querying useful analytics data
- Designing for modular growth and future expansion
## Notes
This is one of the projects that best reflects how I like to build software: practical, data-driven, and designed with extensibility in mind.
@@ -0,0 +1,29 @@
---
title: Lines Police CAD
slug: lines-police-cad
summary: A web service built with Node.js, Express.js, and MongoDB. I contributed features, bug fixes, and general technical support to a product serving a large user base.
label: Project
date: 2022-06-01
status: Collaboration
featured: false
order: 3
github: https://github.com/LinesMerrill/police-cad
demo: https://www.linespolice-cad.com/
tags: Node.js, Express, MongoDB, Web App
---
## Overview
Lines Police CAD is a web-based service built with Node.js and MongoDB.
I contributed new features, bug fixes, and general technical support while working within an already established product and codebase.
## Focus Areas
- Contributing effectively inside a collaborative project
- Working on production-facing features and fixes
- Supporting a platform used by a large community
## Notes
This project strengthened my ability to work inside an existing system rather than only building from scratch.
+26
View File
@@ -0,0 +1,26 @@
---
title: Portfolio
slug: portfolio
summary: A minimal personal portfolio focused on clarity, clean presentation, and an easy way to showcase projects, updates, and professional growth.
label: Project
date: 2026-03-14
status: Active
featured: false
order: 6
github: https://github.com/SowinskiBraeden/SowinskiBraeden
tags: HTML, CSS, JavaScript, Portfolio
---
## Overview
This portfolio is intentionally minimal.
The goal is to keep the presentation clean and professional while still giving enough room to show technical work, current interests, and recent accomplishments.
## What Changed
This version introduces a markdown-based content system so projects and updates can be added more cleanly over time.
## Notes
The structure stays simple, but the content model is much easier to maintain and extend.
@@ -0,0 +1,28 @@
---
title: Schedule Generator App
slug: schedule-generator-app
summary: A desktop application to interact with my schedule generator algorithm. The algorithm is written in Python and generates master and student timetables.
label: Project
date: 2023-08-01
status: Personal Project
featured: true
order: 1
github: https://github.com/SowinskiBraeden/ScheduleGeneratorApp
tags: Python, Desktop App, Scheduling, Data Processing
---
## Overview
This desktop application was built to make my schedule generation algorithm easier to use and easier to test in a real workflow.
The project takes a difficult real-world scheduling problem and turns it into a usable tool for producing both student schedules and master timetables.
## Focus Areas
- Desktop tooling around a custom scheduling algorithm
- Parsing and transforming structured school data
- Exporting useful outputs for administrators and students
## Notes
This project is one of the clearest examples of my interest in backend logic, data processing, and software that solves practical operational problems.
@@ -0,0 +1,28 @@
---
title: School Management API
slug: school-management-api
summary: I developed an API to manage an entire school, keeping track of students and staff details while expanding my understanding of Go, API development, and documentation.
label: Project
date: 2023-09-01
status: Personal Project
featured: true
order: 2
github: https://github.com/SowinskiBraeden/school-management-api
tags: Go, API, Documentation, Education Tech
---
## Overview
This API was designed around managing student and staff information in a school environment.
It gave me a strong opportunity to deepen my understanding of Go, REST-style API design, and clear technical documentation.
## Focus Areas
- Building maintainable backend services in Go
- Managing structured school data and future expansion paths
- Designing endpoints that are easy to reason about and document
## Notes
The broader vision around this work included course management, schedule generation, and other administration tools that could grow over time.
+24
View File
@@ -0,0 +1,24 @@
---
title: XP-System
slug: xp-system
summary: An early web application project where teachers could assign work and students could earn XP and levels, making the learning process more interactive.
label: Project
date: 2021-05-01
status: Early Project
featured: false
order: 5
github: https://github.com/SowinskiBraeden/XP-System
tags: Web App, Education Tech, Gamification
---
## Overview
XP-System was one of the projects that introduced me to the value of web applications and product thinking.
The core idea was to make school work feel more engaging by layering XP, levels, and a clearer sense of progress on top of classroom assignments.
## Focus Areas
- Building an interactive web application
- Thinking about user motivation and engagement
- Learning early lessons around application structure and workflows
@@ -0,0 +1,27 @@
---
title: 3rd Place at the inaugural Red Team Hackathon
slug: red-team-hackathon-2026
summary: Team Cerberus Gate earned a third-place finish while building a prototype focused on RF signal classification, modulation analysis, and emitter geolocation.
label: Update
date: 2026-03-12
status: Achievement
featured: true
order: 1
tags: Hackathon, RF, Machine Learning, Geolocation, BCIT
---
## Team Cerberus Gate
At the inaugural Red Team Hackathon, I competed with **Team Cerberus Gate** on the **Find My Force** challenge.
Our team built a prototype aimed at classifying RF signals, identifying modulation schemes, and estimating emitter locations. It was a fast-paced systems problem that combined software, signal understanding, and rapid iteration under pressure.
## Result
Our team finished **3rd place** and also received the **Best Dressed Award**.
## Why it mattered to me
This was the kind of challenge I enjoy most: ambiguous requirements, technical constraints, short timelines, and a working prototype that had to come together quickly.
It also gave me a chance to build alongside a strong team and work on a problem that blended software engineering, signal analysis, and practical decision-making.
+96 -80
View File
@@ -1,17 +1,21 @@
@import url('https://fonts.googleapis.com/css2?family=Bebas+Neue&family=Jersey+15&family=Sixtyfour+Convergence&family=Ubuntu:ital,wght@0,300;0,400;0,500;0,700;1,300;1,400;1,500;1,700&display=swap');
.container { .container {
display: grid; display: grid;
grid-template-columns: minmax(280px, 320px) repeat(3, 1fr);
grid-template-areas: grid-template-areas:
"pro about about about about about" "pro about about about"
"pro other other other other other" "pro other other other"
"footer footer footer footer footer footer"; "footer footer footer footer";
gap: 24pt; grid-template-rows: auto 1fr auto;
padding: 24pt; gap: 24px;
padding-top: 18px;
min-height: calc(100vh - 132px);
align-items: stretch;
} }
.left-content { .left-content {
grid-area: pro; grid-area: pro;
display: flex;
flex-direction: column;
} }
.about { .about {
@@ -20,103 +24,115 @@
.honors { .honors {
grid-area: other; grid-area: other;
display: flex;
flex-direction: column;
height: 100%;
} }
.footer { .footer {
grid-area: footer; grid-area: footer;
} }
.about {
display: flex;
flex-direction: column;
}
section { section {
transition: .8s; transition: 0.8s;
padding: 24pt; padding: 24px;
background-color: var(--menu-text-bg); background-color: var(--menu-text-bg);
border-radius: 12pt; border-radius: 18px;
border: 1px solid var(--border);
} }
section h3 { section h3 {
margin-top: -5pt; margin-top: 0;
} margin-bottom: 14px;
font-size: 1.2rem;
.social-icons {
margin-top: 6pt;
margin-right: 24pt;
} }
#profile { #profile {
background-image: url("../images/pic.png"); background-image: linear-gradient(rgba(0, 0, 0, 0.24), rgba(0, 0, 0, 0.24)), url("../images/pic.png");
background-size: cover;
background-position: center;
color: #d8d8d9; color: #d8d8d9;
} border-bottom-right-radius: 0;
border-bottom-left-radius: 0;
#profile img { margin-bottom: 0;
border: 6pt solid var(--primary);
transition: .8s;
}
.profile-sub {
background-color: rgba(0, 0, 0, .7);
padding: 12pt;
border-radius: 12pt;
}
dt {
margin-top: 12pt;
}
.text {
margin-right: 24pt;
font-family: "Ubuntu", serif;
font-weight: 400;
font-style: normal;
transition: .8s;
}
footer {
display: flex;
justify-content: space-between;
transition: .8s;
padding: 8pt;
background-color: var(--menu-text-bg);
border-radius: 12pt;
}
footer p {
margin-left: 12pt;
font-family: "Ubuntu", serif;
font-weight: 400;
font-style: normal;
margin-top: 20pt;
}
#profile {
border-bottom-right-radius: 0pt;
border-bottom-left-radius: 0pt;
margin-bottom: 0pt;
} }
#skills { #skills {
border-top-left-radius: 0pt; border-top-left-radius: 0;
border-top-right-radius: 0pt; border-top-right-radius: 0;
flex: 1;
} }
@media only screen and (max-width: 740pt) { #profile img {
border: 4px solid rgba(235, 82, 52, 0.18);
box-shadow: var(--shadow);
}
.profile-sub {
background-color: rgba(0, 0, 0, 0.62);
padding: 16px;
border-radius: 14px;
margin-top: 16px;
}
.skills-list {
padding-left: 18px;
}
.skills-list li {
margin-bottom: 8px;
}
.text {
font-weight: 400;
transition: 0.8s;
margin: 0 0 14px;
}
dt {
margin-top: 16px;
}
dd {
margin-left: 0;
color: var(--muted);
}
.honors dl,
.skills-list {
margin-bottom: 0;
}
@media only screen and (max-width: 920px) {
.container { .container {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: space-between; min-height: auto;
} }
#skills { #skills {
margin-bottom: 0pt; margin-bottom: 0;
}
.footer {
display: block;
text-align: center;
}
.footer p {
margin: 0pt;
margin-top: 12pt;
}
.social-icons {
margin: 0pt;
margin-top: 6pt;
} }
} }
.profile-name {
margin: 0 0 6px;
font-size: clamp(1.45rem, 2vw, 1.9rem);
line-height: 1.1;
}
.profile-role,
.profile-focus {
margin: 0;
}
.profile-focus {
margin-top: 6px;
color: rgba(255, 255, 255, 0.76);
}
+234 -120
View File
@@ -1,165 +1,279 @@
@import url('https://fonts.googleapis.com/css2?family=Bebas+Neue&family=Jersey+15&family=Sixtyfour+Convergence&display=swap'); .projects-page,
@import url('https://fonts.googleapis.com/css2?family=Bebas+Neue&family=Jersey+15&family=Sixtyfour+Convergence&family=Ubuntu:ital,wght@0,300;0,400;0,500;0,700;1,300;1,400;1,500;1,700&display=swap'); .entry-page {
padding-top: 18px;
.container {
margin-top: 2%;
display: flex;
flex-direction: row;
justify-content: space-around;
flex-wrap: wrap;
} }
.card { .projects-hero,
transition: 0.8s; .entry-shell,
width: 54vh; .content-section {
height: auto; background: var(--menu-text-bg);
margin: 30pt; border: 1px solid var(--border);
padding: 20pt; border-radius: 22px;
background-color: var(--menu-text-bg); padding: 28px;
border-radius: 24pt; box-shadow: var(--shadow);
display: flex;
} }
.box { .projects-hero {
transition: 0.8s; margin-bottom: 24px;
background-color: var(--theme);
border-radius: 12pt;
padding-top: 12pt;
padding-left: 12pt;
padding-right: 12pt;
} }
.content { .projects-hero h1,
transition: 0.8s; .entry-title {
margin-left: 18pt; font-size: clamp(2rem, 4vw, 3rem);
margin-right: 18pt; line-height: 1.1;
align-items: stretch; margin: 6px 0 14px;
} }
.content h2 { .projects-intro,
transition: 0.8s; .entry-summary {
font-size: 275%; margin: 0;
margin-top: 5pt; max-width: 760px;
font-family: "Sixtyfour Convergence", serif; color: var(--muted);
font-optical-sizing: auto;
font-weight: 400;
font-style: normal;
font-variation-settings:
"BLED" 0,
"SCAN" 0,
"XELA" 0,
"YELA" 0;
margin-bottom: 0pt;
text-shadow: 1pt 1pt 2pt red, 0 0 1em var(--accent), 0 0 0.2em var(--secondary);
min-width: 2.75cm;
} }
.content h3 { .eyebrow {
font-size: 150%;
font-family: "Jersey 15", serif;
font-weight: 400;
font-style: normal;
text-transform: uppercase; text-transform: uppercase;
margin-top: 20pt; letter-spacing: 0.14em;
margin-left: 0.75cm; font-size: 0.78rem;
color: var(--secondary);
margin: 0;
font-weight: 700;
} }
.content p { .content-section {
transition: 0.8s; margin-bottom: 24px;
font-size: 18px;
line-height: 1.6;
word-break: break-word;
background: var(--theme);
font-family: -apple-system, BlinkMacSystemFont, segoe ui, Roboto, Oxygen, Ubuntu, Cantarell, open sans, helvetica neue, sans-serif;
} }
.content a { .section-heading {
background-color: var(--secondary);
padding: 8pt;
border-radius: 8pt;
position: relative;
box-shadow: 1pt 1pt 2pt red, 0 0 1em var(--accent), 0 0 0.2em var(--secondary);
margin-bottom: 15pt;
}
.content-head {
display: flex; display: flex;
flex-direction: row; justify-content: space-between;
align-items: end;
gap: 12px;
margin-bottom: 18px;
} }
.content-body { .section-heading h2 {
margin: 6px 0 0;
font-size: 1.8rem;
}
.section-link,
.card-link,
.entry-back,
.inline-button {
display: inline-flex;
align-items: center;
justify-content: center;
border-radius: 12px;
border: 1px solid var(--border);
padding: 10px 14px;
font-weight: 600;
background: transparent;
}
.section-link:hover,
.card-link:hover,
.entry-back:hover,
.inline-button:hover {
border-color: rgba(235, 82, 52, 0.32);
}
.content-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 18px;
}
.content-card {
border-radius: 18px;
padding: 22px;
background: var(--panel);
border: 1px solid var(--border);
display: flex; display: flex;
flex-direction: column; flex-direction: column;
min-height: 100%;
}
.content-card.featured {
border-color: rgba(235, 82, 52, 0.34);
}
.card-meta,
.entry-meta,
.entry-links,
.card-links,
.tag-list,
.entry-tag-list {
display: flex;
flex-wrap: wrap;
gap: 10px;
}
.card-meta {
margin-bottom: 14px;
}
.card-kicker,
.card-date,
.entry-pill {
display: inline-flex;
align-items: center;
border-radius: 999px;
padding: 6px 12px;
font-size: 0.84rem;
font-weight: 600;
}
.card-kicker,
.entry-pill-primary {
background: rgba(235, 82, 52, 0.16);
color: var(--secondary);
}
.card-date,
.entry-pill-secondary {
background: rgba(216, 216, 217, 0.08);
color: var(--muted);
}
.content-card h3 {
margin: 0 0 10px;
font-size: 1.35rem;
line-height: 1.2;
}
.card-summary {
color: var(--muted);
margin: 0 0 18px;
flex: 1; flex: 1;
} }
.content-body p { .tag,
min-height: 5cm; .entry-tag {
display: inline-flex;
border-radius: 999px;
padding: 6px 10px;
font-size: 0.82rem;
background: rgba(255, 255, 255, 0.04);
border: 1px solid var(--border);
} }
.content-body a { .card-links,
margin-bottom: 15pt; .entry-links {
text-align: center; margin-top: 18px;
max-width: 100pt;
} }
.content-body #links { .card-link-primary,
display: flex; .inline-button-primary {
justify-content: space-around; background: var(--secondary);
color: #fff;
border-color: transparent;
} }
#links a { .card-link-primary:hover,
margin-bottom: 15pt; .inline-button-primary:hover {
text-align: center; color: #fff;
width: 100pt; filter: brightness(1.04);
} }
/* prevents it changing color */ .updates-grid .content-card {
.content-body a:hover { background: linear-gradient(180deg, rgba(235, 82, 52, 0.08), rgba(255, 255, 255, 0.02));
}
.site-footer {
margin-bottom: 24px;
}
.entry-shell {
padding: 24px 28px 30px;
}
.entry-header {
margin-bottom: 24px;
}
.entry-back {
margin-bottom: 20px;
}
.entry-meta {
margin-bottom: 18px;
}
.entry-links {
margin-top: 18px;
}
.entry-content {
margin-top: 28px;
padding-top: 24px;
border-top: 1px solid var(--border);
}
.entry-content h1,
.entry-content h2,
.entry-content h3,
.entry-content h4 {
margin-top: 28px;
line-height: 1.2;
}
.entry-content p,
.entry-content li,
.entry-content blockquote {
color: var(--primary); color: var(--primary);
} }
.footer { .entry-content ul,
display: flex; .entry-content ol {
justify-content: space-between; padding-left: 22px;
transition: .8s;
padding: 8pt;
background-color: var(--menu-text-bg);
border-radius: 12pt;
margin-bottom: 24pt;
width: 80vw;
} }
.footer p { .entry-content a {
font-family: "Ubuntu", serif; color: var(--secondary);
font-weight: 400;
font-style: normal;
margin-left: 12pt;
} }
.social-icons { .entry-content blockquote {
margin-top: 6pt; margin: 18px 0;
margin-right: 24pt; padding: 12px 16px;
border-left: 4px solid var(--secondary);
background: rgba(235, 82, 52, 0.08);
border-radius: 0 12px 12px 0;
} }
.content-body a:hover { .entry-content code {
background-color: #e4a295; background: rgba(0, 0, 0, 0.24);
padding: 2px 6px;
border-radius: 6px;
font-size: 0.92rem;
} }
@media only screen and (max-width: 544pt) { .entry-content pre {
.content-head h2 { padding: 16px;
margin-top: 24pt; overflow-x: auto;
background: rgba(0, 0, 0, 0.24);
border-radius: 16px;
}
.entry-content pre code {
background: transparent;
padding: 0;
}
.entry-empty {
text-align: center;
padding: 40px 18px;
}
@media only screen and (max-width: 740pt) {
.projects-hero,
.entry-shell,
.content-section {
padding: 22px;
} }
.footer {
display: block; .section-heading {
text-align: center; flex-direction: column;
} align-items: start;
.footer p {
margin: 0pt;
margin-top: 12pt;
}
.social-icons {
margin: 0pt;
margin-top: 6pt;
} }
} }
+525 -71
View File
@@ -1,15 +1,24 @@
@import url("https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;700;800&display=swap"); @import url("https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700;800&display=swap");
/* root is dark theme */
:root { :root {
--theme: #1d1e20; --theme: #1d1e20;
--primary: #d8d8d9; --primary: #d8d8d9;
--secondary: #eb5234; --secondary: #eb5234;
--accent: #ff005a; --accent: #ff005a;
--panel: rgba(216, 216, 217); --panel: rgba(216, 216, 217, 0.08);
--panel-text: #303030; --panel-text: #303030;
--menu-text-bg: #303030; --menu-text-bg: #303030;
--not-found: #030005; --not-found: #030005;
--border: rgba(216, 216, 217, 0.12);
--muted: rgba(216, 216, 217, 0.72);
--shadow: 0 16px 44px rgba(0, 0, 0, 0.18);
--hero-glow-x: 50%;
--hero-glow-y: 20%;
--hero-glow-alt-x: 50%;
--hero-glow-alt-y: 28%;
--hero-glow-rotate: 0deg;
--hero-glow-shift-x: 0px;
--hero-glow-shift-y: 0px;
} }
.light { .light {
@@ -17,10 +26,28 @@
--primary: rgba(0, 0, 0, 0.88); --primary: rgba(0, 0, 0, 0.88);
--secondary: #eb5234; --secondary: #eb5234;
--accent: #ff005a; --accent: #ff005a;
--panel: #303030; --panel: rgba(0, 0, 0, 0.06);
--panel-text: rgba(216, 216, 217); --panel-text: rgba(216, 216, 217);
--menu-text-bg: #9e9e9e; --menu-text-bg: #b7b7b7;
--not-found: #303030; --not-found: #303030;
--border: rgba(0, 0, 0, 0.08);
--muted: rgba(0, 0, 0, 0.68);
--shadow: 0 16px 40px rgba(0, 0, 0, 0.08);
--hero-glow-x: 50%;
--hero-glow-y: 20%;
--hero-glow-alt-x: 50%;
--hero-glow-alt-y: 28%;
--hero-glow-rotate: 0deg;
--hero-glow-shift-x: 0px;
--hero-glow-shift-y: 0px;
}
* {
box-sizing: border-box;
}
html {
scroll-behavior: smooth;
} }
body { body {
@@ -29,7 +56,40 @@ body {
line-height: 1.6; line-height: 1.6;
word-break: break-word; word-break: break-word;
background: var(--theme); background: var(--theme);
font-family: -apple-system, BlinkMacSystemFont, segoe ui, Roboto, Oxygen, Ubuntu, Cantarell, open sans, helvetica neue, sans-serif; font-family: "Poppins", -apple-system, BlinkMacSystemFont, segoe ui, Roboto, Oxygen, Ubuntu, Cantarell, open sans,
helvetica neue, sans-serif;
min-height: 100vh;
position: relative;
overflow-x: hidden;
}
body::before,
body::after {
content: "";
position: fixed;
inset: -20%;
pointer-events: none;
z-index: 0;
transition: transform 0.18s ease-out, opacity 0.25s ease-out;
}
body::before {
background:
radial-gradient(circle at var(--hero-glow-x) var(--hero-glow-y), rgba(235, 82, 52, 0.24), transparent 18%),
radial-gradient(circle at var(--hero-glow-alt-x) var(--hero-glow-alt-y), rgba(255, 0, 90, 0.14), transparent 20%);
filter: blur(48px);
opacity: 0.9;
transform: translate3d(var(--hero-glow-shift-x), var(--hero-glow-shift-y), 0) rotate(var(--hero-glow-rotate));
}
body::after {
background:
radial-gradient(circle at 20% 20%, rgba(235, 82, 52, 0.08), transparent 20%),
radial-gradient(circle at 80% 18%, rgba(255, 0, 90, 0.05), transparent 18%);
filter: blur(90px);
opacity: 0.55;
transform: translate3d(calc(var(--hero-glow-shift-x) * -0.35), calc(var(--hero-glow-shift-y) * -0.35), 0)
rotate(calc(var(--hero-glow-rotate) * -0.45));
} }
body.js-stop-transition * { body.js-stop-transition * {
@@ -42,17 +102,36 @@ a:hover {
a, a,
body, body,
h1 { h1,
h2,
h3,
h4,
p,
li,
dd,
dt {
color: var(--primary); color: var(--primary);
} }
header, header,
nav { nav {
display: block display: block;
}
header,
main,
.page-shell {
position: relative;
z-index: 1;
}
.header {
position: relative;
z-index: 1000;
} }
ul { ul {
padding: 0 padding: 0;
} }
ul li::before { ul li::before {
@@ -77,19 +156,16 @@ ul li#projects-link:hover::before {
a { a {
transition: 0.8s; transition: 0.8s;
text-decoration: none;
} }
a:hover { a:hover {
color: var(--secondary); color: var(--secondary);
} }
a {
text-decoration: none
}
body, body,
ul { ul {
margin: 0 margin: 0;
} }
.nav { .nav {
@@ -104,7 +180,7 @@ ul {
} }
.nav a { .nav a {
display: block display: block;
} }
.logo, .logo,
@@ -114,12 +190,16 @@ ul {
} }
.logo { .logo {
flex-wrap: inherit flex-wrap: inherit;
align-items: center;
gap: 4px;
} }
.logo a { .logo a {
font-size: 24px; font-size: 24px;
font-weight: 700 font-weight: 700;
display: inline-flex;
align-items: center;
} }
.logo a img { .logo a img {
@@ -128,17 +208,17 @@ ul {
pointer-events: none; pointer-events: none;
transform: translate(0, -10%); transform: translate(0, -10%);
border-radius: 6pt; border-radius: 6pt;
margin-inline-end: 8pt margin-inline-end: 8pt;
} }
#menu { #menu {
list-style: none; list-style: none;
word-break: keep-all; word-break: keep-all;
overflow-x: auto; overflow-x: auto;
white-space: nowrap white-space: nowrap;
} }
#menu li+li { #menu li + li {
margin-inline-start: 24pt; margin-inline-start: 24pt;
} }
@@ -150,18 +230,27 @@ ul {
font-weight: 500; font-weight: 500;
} }
.page-shell {
width: min(1120px, calc(100% - 32px));
margin: 0 auto;
padding-bottom: 32px;
}
.main { .main {
zoom: 110%;
text-align: center; text-align: center;
position: absolute; position: absolute;
top: 50%; top: 52%;
left: 50%; left: 50%;
transform: translate(-50%, -50%); transform: translate(-50%, -50%);
width: 50%; width: min(760px, calc(100% - 32px));
padding: 10pt; padding: 24px 12px 12px;
color: var(--primary); color: var(--primary);
} }
.hero-main {
position: absolute;
}
.svg-wrapper { .svg-wrapper {
margin: 0 auto; margin: 0 auto;
width: 320pt; width: 320pt;
@@ -194,23 +283,26 @@ ul {
transform: translate(0, -64pt); transform: translate(0, -64pt);
} }
.name:hover { .name:hover,
.des:hover,
.mobile-name:hover {
color: var(--secondary); color: var(--secondary);
cursor: default; cursor: default;
} }
.des { .des {
transition: 0.8s; transition: 0.8s;
font-size: larger; font-size: 1.06rem;
display: block;
} }
.des:hover { .hero-description {
color: var(--secondary); margin: 0;
cursor: default;
} }
.des:focus { .hero-description-secondary {
color: var(--secondary); margin-top: 6px;
color: var(--muted);
} }
.profile { .profile {
@@ -218,6 +310,70 @@ ul {
height: 190pt; height: 190pt;
border-radius: 50%; border-radius: 50%;
transition: 0.8s; transition: 0.8s;
object-fit: cover;
border: 4px solid rgba(235, 82, 52, 0.18);
box-shadow: var(--shadow);
}
.hero-main .profile,
.hero-main .svg-wrapper,
.hero-main .hero-description,
.hero-main .hero-badges,
.hero-main .hero-actions,
.hero-main .social-icons {
will-change: transform;
transition: transform 0.18s ease-out;
}
.hero-badges {
display: flex;
justify-content: center;
gap: 10px;
flex-wrap: wrap;
margin-top: 18px;
}
.badge {
border: 1px solid var(--border);
background: var(--panel);
color: var(--primary);
border-radius: 999px;
padding: 8px 14px;
font-size: 0.88rem;
}
.hero-actions {
display: flex;
gap: 12px;
justify-content: center;
flex-wrap: wrap;
margin-top: 20px;
}
.button {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 148px;
border-radius: 12px;
padding: 12px 18px;
font-weight: 600;
border: 1px solid transparent;
}
.button-primary {
background: var(--secondary);
color: #fff;
}
.button-primary:hover {
color: #fff;
filter: brightness(1.04);
}
.button-secondary {
background: transparent;
border-color: var(--border);
} }
.social-icons { .social-icons {
@@ -235,7 +391,6 @@ ul {
.social-icons a { .social-icons a {
color: var(--primary); color: var(--primary);
text-decoration: none;
} }
.icon { .icon {
@@ -247,7 +402,9 @@ ul {
} }
.logo-switches { .logo-switches {
flex-wrap: inherit flex-wrap: inherit;
display: inline-flex;
align-items: center;
} }
#theme-toggle svg { #theme-toggle svg {
@@ -255,12 +412,18 @@ ul {
} }
button#theme-toggle { button#theme-toggle {
background-color: var(--theme); background-color: transparent;
border: none; border: none;
color: var(--primary); color: var(--primary);
font-size: 26px; font-size: 26px;
margin: auto 4pt; margin: 0 0 0 4pt;
transition: 0.8s; transition: 0.8s;
display: inline-flex;
align-items: center;
justify-content: center;
line-height: 1;
padding: 0;
height: 32px;
} }
button#theme-toggle:hover { button#theme-toggle:hover {
@@ -277,7 +440,6 @@ body:not(.light) #sun {
display: none; display: none;
} }
/* Mobile Nav */
.cta { .cta {
cursor: pointer; cursor: pointer;
font-weight: 600; font-weight: 600;
@@ -293,50 +455,80 @@ body:not(.light) #sun {
} }
.overlay { .overlay {
height: 100%;
width: 0;
position: fixed; position: fixed;
z-index: 1; inset: 0;
left: 0; z-index: 2147483647;
top: 0; isolation: isolate;
background-color: #24252a; background: rgba(20, 20, 24, 0.86);
overflow-x: hidden; backdrop-filter: blur(14px) saturate(135%);
transition: all 0.5s ease 0s; -webkit-backdrop-filter: blur(14px) saturate(135%);
overflow: hidden;
opacity: 0;
visibility: hidden;
pointer-events: none;
transition: opacity 0.28s ease, visibility 0.28s ease;
} }
.overlay--active { .overlay--active {
width: 100%; opacity: 1;
visibility: visible;
pointer-events: auto;
} }
.overlay__content { .overlay__content {
position: relative;
z-index: 2;
display: flex; display: flex;
height: 100%; min-height: 100%;
width: 100%;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
opacity: 0;
transform: translateY(12px) scale(0.985);
transition: opacity 0.24s ease, transform 0.24s ease;
}
.overlay--active .overlay__content {
opacity: 1;
transform: translateY(0) scale(1);
} }
.overlay a { .overlay a {
color: white; color: white;
text-decoration: none; text-decoration: none;
padding: 15pt; padding: 15pt;
font-size: 36px; font-size: clamp(1.9rem, 5vw, 2.35rem);
line-height: 1.1;
display: block; display: block;
transition: all 0.3s ease 0s; text-align: center;
transition: color 0.24s ease, transform 0.24s ease;
}
.overlay__content a {
width: 100%;
max-width: 320px;
} }
.overlay a:hover, .overlay a:hover,
.overlay a:focus { .overlay a:focus {
color: var(--secondary); color: var(--secondary);
transform: translateY(-1px);
} }
.overlay .close { .overlay .close {
position: absolute; position: absolute;
z-index: 3;
top: 20pt; top: 20pt;
right: 45pt; right: 45pt;
font-size: 60px; font-size: 60px;
line-height: 1;
color: #edf0f1; color: #edf0f1;
cursor: pointer; cursor: pointer;
border: 0;
background: transparent;
padding: 0;
appearance: none;
} }
.mobile-name { .mobile-name {
@@ -346,40 +538,56 @@ body:not(.light) #sun {
font-weight: 900; font-weight: 900;
} }
.mobile-name:hover { body.menu-open {
color: var(--secondary); overflow: hidden;
cursor: default;
} }
@media screen and (max-width:768pt) { body.menu-open main,
.profile img { body.menu-open .page-shell,
transform: scale(.85); body.menu-open .container,
} body.menu-open .projects-page,
body.menu-open .entry-page,
body.menu-open .footer {
pointer-events: none;
}
body.menu-open .header,
body.menu-open .overlay,
body.menu-open .overlay * {
pointer-events: auto;
}
.footer {
display: flex;
justify-content: space-between;
align-items: center;
gap: 16px;
transition: 0.8s;
padding: 12pt 18pt;
background-color: var(--menu-text-bg);
border-radius: 18px;
}
.footer p {
margin: 0;
}
.loading-state {
color: var(--muted);
}
@media screen and (max-width: 768pt) {
.profile { .profile {
width: 128pt; width: 128pt;
height: auto; height: 128pt;
}
.first-entry {
min-height: 260pt;
}
.archive-month {
flex-direction: column;
}
.archive-year {
margin-top: 20pt;
} }
.footer { .footer {
padding: calc((60 - 24pt - 10pt)/2)24pt; padding: 16px;
} }
} }
@media only screen and (max-width: 740pt) { @media only screen and (max-width: 740pt) {
.nav__links li a, .nav__links li a,
.svg-wrapper#main-wrapper { .svg-wrapper#main-wrapper {
display: none; display: none;
@@ -393,10 +601,256 @@ body:not(.light) #sun {
.logo { .logo {
margin-top: 12pt; margin-top: 12pt;
} }
.logo-switches { .logo-switches {
margin-top: 4pt; margin-top: 4pt;
} }
.main { .main {
width: 100%; position: static;
transform: none;
width: min(760px, calc(100% - 24px));
margin: 44px auto 0;
padding-bottom: 40px;
}
.hero-main {
position: static;
}
.page-shell {
width: min(1120px, calc(100% - 20px));
}
.footer {
display: block;
text-align: center;
}
.social-icons {
margin-top: 6pt;
}
.overlay .close {
right: 20px;
top: 16px;
}
}
@media (prefers-reduced-motion: reduce) {
body::before,
body::after {
transition: none;
transform: none !important;
}
.hero-main .profile,
.hero-main .svg-wrapper,
.hero-main .hero-description,
.hero-main .hero-badges,
.hero-main .hero-actions,
.hero-main .social-icons {
transition: none;
transform: none !important;
}
}
/* Glassy premium hero refinements */
:root {
--hero-shell-spot-x: 50%;
--hero-shell-spot-y: 28%;
--hero-shell-tilt-x: 0deg;
--hero-shell-tilt-y: 0deg;
}
body {
background:
linear-gradient(180deg, rgba(255, 255, 255, 0.015), transparent 28%),
linear-gradient(140deg, rgba(235, 82, 52, 0.045), transparent 34%),
linear-gradient(220deg, rgba(255, 0, 90, 0.03), transparent 30%),
var(--theme);
}
body::before {
background:
radial-gradient(circle at var(--hero-glow-x) var(--hero-glow-y), rgba(255, 255, 255, 0.08), transparent 12%),
radial-gradient(circle at var(--hero-glow-x) var(--hero-glow-y), rgba(235, 82, 52, 0.3), transparent 24%),
radial-gradient(circle at var(--hero-glow-alt-x) var(--hero-glow-alt-y), rgba(255, 0, 90, 0.18), transparent 24%);
filter: blur(78px);
opacity: 1;
}
body::after {
background:
radial-gradient(circle at 18% 18%, rgba(255, 255, 255, 0.055), transparent 15%),
radial-gradient(circle at 82% 14%, rgba(235, 82, 52, 0.08), transparent 18%),
radial-gradient(circle at 70% 76%, rgba(255, 255, 255, 0.03), transparent 20%);
filter: blur(110px);
opacity: 0.82;
}
.logo {
align-items: center;
}
.logo-switches {
align-items: center;
min-height: 32px;
}
button#theme-toggle {
margin-top: 1px;
}
.hero-main {
width: min(820px, calc(100% - 32px));
}
.hero-shell {
position: relative;
overflow: hidden;
border-radius: 30px;
padding: 34px 28px 18px;
border: 1px solid rgba(255, 255, 255, 0.13);
background:
linear-gradient(180deg, rgba(255, 255, 255, 0.12), rgba(255, 255, 255, 0.02)),
linear-gradient(135deg, rgba(235, 82, 52, 0.09), transparent 42%),
linear-gradient(225deg, rgba(255, 0, 90, 0.05), transparent 34%),
rgba(20, 21, 24, 0.44);
box-shadow:
0 24px 80px rgba(0, 0, 0, 0.22),
inset 0 1px 0 rgba(255, 255, 255, 0.18),
inset 0 -1px 0 rgba(255, 255, 255, 0.04);
backdrop-filter: blur(22px) saturate(135%);
-webkit-backdrop-filter: blur(22px) saturate(135%);
isolation: isolate;
transform: perspective(1200px) rotateX(var(--hero-shell-tilt-x)) rotateY(var(--hero-shell-tilt-y));
transition: transform 0.18s ease-out, border-color 0.25s ease-out, box-shadow 0.25s ease-out;
}
body.light .hero-shell {
background:
linear-gradient(180deg, rgba(255, 255, 255, 0.72), rgba(255, 255, 255, 0.4)),
linear-gradient(135deg, rgba(235, 82, 52, 0.08), transparent 42%),
linear-gradient(225deg, rgba(255, 0, 90, 0.04), transparent 34%),
rgba(255, 255, 255, 0.42);
border-color: rgba(255, 255, 255, 0.55);
box-shadow:
0 24px 60px rgba(0, 0, 0, 0.08),
inset 0 1px 0 rgba(255, 255, 255, 0.75),
inset 0 -1px 0 rgba(255, 255, 255, 0.35);
}
.hero-shell::before,
.hero-shell::after,
.hero-shell__shine {
content: "";
position: absolute;
inset: 0;
pointer-events: none;
}
.hero-shell::before {
background: radial-gradient(circle at var(--hero-shell-spot-x) var(--hero-shell-spot-y), rgba(255, 255, 255, 0.14), transparent 26%);
opacity: 0.85;
z-index: 0;
}
.hero-shell::after {
inset: 1px;
border-radius: 29px;
border: 1px solid rgba(255, 255, 255, 0.07);
z-index: 1;
}
.hero-shell__shine {
background: linear-gradient(115deg, transparent 18%, rgba(255, 255, 255, 0.085) 42%, transparent 68%);
transform: translate3d(calc(var(--hero-glow-shift-x) * 0.18), calc(var(--hero-glow-shift-y) * 0.16), 0) rotate(0deg);
opacity: 0.9;
z-index: 0;
}
.hero-shell > * {
position: relative;
z-index: 2;
}
.profile {
border: 4px solid rgba(255, 255, 255, 0.14);
box-shadow:
0 16px 42px rgba(0, 0, 0, 0.22),
0 0 0 1px rgba(255, 255, 255, 0.05);
}
.shape {
stroke: rgba(235, 82, 52, 0.95);
filter: drop-shadow(0 6px 18px rgba(235, 82, 52, 0.18));
}
.hero-description-secondary {
max-width: 640px;
margin-left: auto;
margin-right: auto;
}
.badge {
background: rgba(255, 255, 255, 0.07);
border-color: rgba(255, 255, 255, 0.12);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.08);
}
body.light .badge {
background: rgba(255, 255, 255, 0.55);
border-color: rgba(255, 255, 255, 0.6);
}
.button {
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.08);
}
.button-primary {
background: linear-gradient(135deg, rgba(255, 108, 74, 0.96), rgba(235, 82, 52, 0.9));
box-shadow:
0 10px 24px rgba(235, 82, 52, 0.2),
inset 0 1px 0 rgba(255, 255, 255, 0.14);
}
.button-secondary {
background: rgba(255, 255, 255, 0.05);
border-color: rgba(255, 255, 255, 0.12);
}
body.light .button-secondary {
background: rgba(255, 255, 255, 0.55);
border-color: rgba(255, 255, 255, 0.65);
}
.social-icons {
padding-top: 16px;
}
@media only screen and (max-width: 740pt) {
.hero-main {
width: min(760px, calc(100% - 24px));
}
.hero-shell {
padding: 26px 18px 12px;
border-radius: 24px;
}
}
@media (prefers-reduced-motion: reduce) {
.hero-shell {
transform: none !important;
}
.hero-shell__shine {
transform: none !important;
} }
} }
+194
View File
@@ -0,0 +1,194 @@
(function() {
const CONTENT_INDEX_PATH = "./public/content/index.json";
const escapeHtml = (value) => String(value)
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/\"/g, "&quot;")
.replace(/'/g, "&#039;");
const formatDate = (value) => {
if (!value) {
return "";
}
const date = new Date(`${value}T00:00:00`);
if (Number.isNaN(date.getTime())) {
return value;
}
return new Intl.DateTimeFormat("en-CA", {
year: "numeric",
month: "short",
day: "numeric"
}).format(date);
};
const getIndex = async () => {
if (window.location.protocol === "file:") {
throw new Error("This content page needs a local web server. Run python3 -m http.server and open http://localhost:8000.");
}
const response = await fetch(CONTENT_INDEX_PATH);
if (!response.ok) {
throw new Error("Unable to load content index.");
}
return response.json();
};
const stripFrontMatter = (markdown) => {
if (!markdown.startsWith("---")) {
return markdown;
}
const parts = markdown.split("---");
if (parts.length < 3) {
return markdown;
}
return parts.slice(2).join("---").trim();
};
const renderTags = (tags, className) => {
if (!tags || !tags.length) {
return "";
}
return `<div class="${className}">${tags.map((tag) => `<span class="${className === "tag-list" ? "tag" : "entry-tag"}">${escapeHtml(tag)}</span>`).join("")}</div>`;
};
const renderLinks = (entry, className) => {
const links = [];
links.push(`<a class="${className} ${className}-primary" href="./entry.html?type=${encodeURIComponent(entry.type)}&slug=${encodeURIComponent(entry.slug)}">Read More</a>`);
if (entry.github) {
links.push(`<a class="${className}" href="${escapeHtml(entry.github)}" target="_blank" rel="noopener noreferrer">Github</a>`);
}
if (entry.demo) {
links.push(`<a class="${className}" href="${escapeHtml(entry.demo)}" target="_blank" rel="noopener noreferrer">Live</a>`);
}
return `<div class="${className.replace("card-link", "card-links")}">${links.join("")}</div>`;
};
const createCard = (entry) => {
const formattedDate = formatDate(entry.date);
const featuredClass = entry.featured ? " featured" : "";
return `
<article class="content-card${featuredClass}">
<div class="card-meta">
<span class="card-kicker">${escapeHtml(entry.label || entry.type)}</span>
${formattedDate ? `<span class="card-date">${escapeHtml(formattedDate)}</span>` : ""}
</div>
<h3>${escapeHtml(entry.title)}</h3>
<p class="card-summary">${escapeHtml(entry.summary || "")}</p>
${renderTags(entry.tags || [], "tag-list")}
${renderLinks(entry, "card-link")}
</article>
`;
};
const renderSection = (containerId, entries, emptyMessage) => {
const container = document.getElementById(containerId);
if (!container) {
return;
}
if (!entries.length) {
container.innerHTML = `<p class="loading-state">${escapeHtml(emptyMessage)}</p>`;
return;
}
container.innerHTML = entries.map(createCard).join("");
};
const initListPage = async () => {
try {
const index = await getIndex();
renderSection("projects-grid", index.projects || [], "No projects added yet.");
renderSection("updates-grid", index.updates || [], "No updates added yet.");
} catch (error) {
renderSection("projects-grid", [], error.message);
renderSection("updates-grid", [], error.message);
}
};
const initEntryPage = async () => {
const shell = document.getElementById("entry-shell");
if (!shell) {
return;
}
try {
const params = new URLSearchParams(window.location.search);
const type = params.get("type");
const slug = params.get("slug");
if (!type || !slug) {
shell.innerHTML = `
<div class="entry-empty">
<p class="eyebrow">Not found</p>
<h1 class="entry-title">This entry could not be loaded.</h1>
<a class="entry-back" href="./projects.html">Back to projects</a>
</div>
`;
return;
}
const index = await getIndex();
const entries = [...(index.projects || []), ...(index.updates || [])];
const entry = entries.find((item) => item.type === type && item.slug === slug);
if (!entry) {
throw new Error("Entry not found.");
}
const response = await fetch(entry.path);
if (!response.ok) {
throw new Error("Unable to load markdown entry.");
}
const rawMarkdown = await response.text();
const markdown = stripFrontMatter(rawMarkdown);
shell.innerHTML = `
<div class="entry-header">
<a class="entry-back" href="./projects.html">Back to projects</a>
<div class="entry-meta">
<span class="entry-pill entry-pill-primary">${escapeHtml(entry.label || entry.type)}</span>
${entry.date ? `<span class="entry-pill entry-pill-secondary">${escapeHtml(formatDate(entry.date))}</span>` : ""}
${entry.status ? `<span class="entry-pill entry-pill-secondary">${escapeHtml(entry.status)}</span>` : ""}
</div>
<h1 class="entry-title">${escapeHtml(entry.title)}</h1>
<p class="entry-summary">${escapeHtml(entry.summary || "")}</p>
${renderTags(entry.tags || [], "entry-tag-list")}
<div class="entry-links">
${entry.github ? `<a class="inline-button" href="${escapeHtml(entry.github)}" target="_blank" rel="noopener noreferrer">Github</a>` : ""}
${entry.demo ? `<a class="inline-button inline-button-primary" href="${escapeHtml(entry.demo)}" target="_blank" rel="noopener noreferrer">Live</a>` : ""}
</div>
</div>
<article class="entry-content">
${window.marked.parse(markdown)}
</article>
`;
} catch (error) {
shell.innerHTML = `
<div class="entry-empty">
<p class="eyebrow">Not found</p>
<h1 class="entry-title">${escapeHtml(error.message)}</h1>
<a class="entry-back" href="./projects.html">Back to projects</a>
</div>
`;
}
};
window.PORTFOLIO_CONTENT = {
initListPage,
initEntryPage
};
})();
+82
View File
@@ -0,0 +1,82 @@
(function() {
const hero = document.querySelector(".hero-main");
const heroShell = document.querySelector(".hero-main .hero-shell");
const profile = document.querySelector(".hero-main .profile");
const title = document.querySelector(".hero-main .svg-wrapper");
const descriptions = document.querySelectorAll(
".hero-main .hero-description, .hero-main .hero-badges, .hero-main .hero-actions, .hero-main .social-icons"
);
if (!hero || !heroShell || !profile || !title) {
return;
}
const prefersReducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
const coarsePointer = window.matchMedia("(pointer: coarse)").matches;
if (prefersReducedMotion || coarsePointer) {
return;
}
let targetX = 0;
let targetY = 0;
let currentX = 0;
let currentY = 0;
const clamp = (value, min, max) => Math.min(Math.max(value, min), max);
const setTransform = (element, x, y) => {
element.style.transform = `translate3d(${x}px, ${y}px, 0)`;
};
const animate = () => {
currentX += (targetX - currentX) * 0.065;
currentY += (targetY - currentY) * 0.065;
const bgX = 50 + currentX * 6.8;
const bgY = 18 + currentY * 5.6;
const bgXAlt = 50 - currentX * 5.2;
const bgYAlt = 30 - currentY * 4.6;
const glowRotate = currentX * 16 + currentY * 10;
const glowShiftX = currentX * 54;
const glowShiftY = currentY * 42;
document.body.style.setProperty("--hero-glow-x", `${bgX}%`);
document.body.style.setProperty("--hero-glow-y", `${bgY}%`);
document.body.style.setProperty("--hero-glow-alt-x", `${bgXAlt}%`);
document.body.style.setProperty("--hero-glow-alt-y", `${bgYAlt}%`);
document.body.style.setProperty("--hero-glow-rotate", `${glowRotate}deg`);
document.body.style.setProperty("--hero-glow-shift-x", `${glowShiftX}px`);
document.body.style.setProperty("--hero-glow-shift-y", `${glowShiftY}px`);
heroShell.style.setProperty("--hero-shell-spot-x", `${50 + currentX * 14}%`);
heroShell.style.setProperty("--hero-shell-spot-y", `${28 + currentY * 10}%`);
heroShell.style.setProperty("--hero-shell-tilt-x", `${currentY * -2.2}deg`);
heroShell.style.setProperty("--hero-shell-tilt-y", `${currentX * 2.6}deg`);
setTransform(profile, currentX * -4.5, currentY * -4.5);
setTransform(title, currentX * 2.2, currentY * 2.2);
descriptions.forEach((element, index) => {
const strength = 1.3 + index * 0.35;
setTransform(element, currentX * strength, currentY * strength);
});
requestAnimationFrame(animate);
};
window.addEventListener("mousemove", (event) => {
const x = clamp((event.clientX / window.innerWidth - 0.5) * 2, -1, 1);
const y = clamp((event.clientY / window.innerHeight - 0.5) * 2, -1, 1);
targetX = x;
targetY = y;
});
window.addEventListener("mouseleave", () => {
targetX = 0;
targetY = 0;
});
requestAnimationFrame(animate);
})();
+26 -6
View File
@@ -1,12 +1,32 @@
const doc = document; const doc = document;
const body = doc.body;
const menuOpen = doc.querySelector(".menu"); const menuOpen = doc.querySelector(".menu");
const menuClose = doc.querySelector(".close"); const menuClose = doc.querySelector(".close");
const overlay = doc.querySelector(".overlay"); const overlay = doc.querySelector(".overlay");
menuOpen.addEventListener("click", () => { if (menuOpen && menuClose && overlay) {
overlay.classList.add("overlay--active"); const openMenu = () => {
}); overlay.classList.add("overlay--active");
body.classList.add("menu-open");
};
menuClose.addEventListener("click", () => { const closeMenu = () => {
overlay.classList.remove("overlay--active"); overlay.classList.remove("overlay--active");
}); body.classList.remove("menu-open");
};
menuOpen.addEventListener("click", openMenu);
menuClose.addEventListener("click", closeMenu);
overlay.addEventListener("click", (event) => {
if (event.target === overlay) {
closeMenu();
}
});
window.addEventListener("keydown", (event) => {
if (event.key === "Escape") {
closeMenu();
}
});
}
+31
View File
@@ -0,0 +1,31 @@
(function() {
const body = document.body;
const themeToggle = document.getElementById("theme-toggle");
const applyStoredTheme = () => {
const storedTheme = localStorage.getItem("pref-theme") || sessionStorage.getItem("theme") || "dark";
if (storedTheme === "light") {
body.classList.add("light");
sessionStorage.setItem("theme", "light");
localStorage.setItem("pref-theme", "light");
return;
}
body.classList.remove("light");
sessionStorage.setItem("theme", "dark");
localStorage.setItem("pref-theme", "dark");
};
applyStoredTheme();
if (themeToggle) {
themeToggle.addEventListener("click", () => {
const isLight = body.classList.toggle("light");
const nextTheme = isLight ? "light" : "dark";
sessionStorage.setItem("theme", nextTheme);
localStorage.setItem("pref-theme", nextTheme);
});
}
body.classList.remove("js-stop-transition");
})();