Rewrite site with 11ty

This commit is contained in:
Neil Brommer 2023-07-06 16:24:56 -07:00
parent 815d89ad49
commit 3da1f74f98
56 changed files with 5242 additions and 911 deletions

1
src/_data/layout.js Normal file
View file

@ -0,0 +1 @@
module.exports = "../_includes/layouts/layout.njk";

46
src/_data/projects.json Normal file
View file

@ -0,0 +1,46 @@
[
{
"name": "Start",
"description": "A new tab page that displays lists of links using the browsers indexedDB to store all data",
"links": [
{
"title": "Site",
"url": "https://start.neilbrommer.com/"
},
{
"title": "Source Code",
"url": "https://github.com/NeilBrommer/NewTabPage"
}
]
},
{
"name": "Blazor Start",
"description": "A work in progress rewrite of the Start project using Blazor WebAssembly",
"links": [
{
"title": "Source Code",
"url": "https://github.com/NeilBrommer/BlazorStart"
}
]
},
{
"name": "Website",
"description": "The source code for this website",
"links": [
{
"title": "Source Code",
"url": "https://github.com/NeilBrommer/Personal-Site"
}
]
},
{
"name": "Auto Dark",
"description": "A small utility for setting/toggling the Windows 10 dark theme. Useful in combination with the Windows Task Scheduler to automatically change the theme.",
"links": [
{
"title": "Source Code",
"url": "https://github.com/NeilBrommer/WindowsAutoDark"
}
]
}
]

View file

@ -0,0 +1,17 @@
[
{
"name": "GitHub",
"icon": "github",
"url": "https://github.com/NeilBrommer"
},
{
"name": "CodePen",
"icon": "codepen",
"url": "https://codepen.io/NeilBrommer"
},
{
"name": "LinkedIn",
"icon": "linkedin",
"url": "https://www.linkedin.com/in/neilbrommer/"
}
]

View file

@ -0,0 +1,87 @@
<!doctype html>
<html lang="en-US">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="author" content="Neil Brommer">
<meta name="description" content="The personal website of Neil Brommer">
<title>
{% if (title is defined) and (page.url != "/") %}
{{ title }} - Neil Brommer
{% else %}
Neil Brommer
{% endif %}
</title>
<link rel="stylesheet" href="{{ '/css/site.css' | url }}">
</head>
<body>
<header>
<nav class="navbar" aria-label="Main Menu">
<ul class="siteNav">
{% for section in collections.MainPage | filterDrafts | eleventyNavigation %}
<li {% if section.url == page.url %}class="active"{% endif %}>
<a href="/#{{ section.title | slugify }}">
{% if section.icon is defined %}
<svg class="bi" fill="currentColor" role="img">
<use xlink:href="/images/fontawesome/solid.svg#{{ section.icon }}" />
</svg>
{% endif %}
{{ section.title }}
</a>
</li>
{% endfor %}
{% set standalonePages = collections.all | filterDrafts | IsNotMainPageSection | eleventyNavigation %}
{% if standalonePages | length %}
<li><hr /></li>
{% for standalonePage in standalonePages %}
<li {% if standalonePage.url == page.url %}class="active"{% endif %}>
<a href="{{ standalonePage.url }}">
{% if standalonePage.icon is defined %}
<svg class="bi" fill="currentColor" role="img">
<use xlink:href="/images/fontawesome/solid.svg#{{ standalonePage.icon }}" />
</svg>
{% endif %}
{{ standalonePage.title }}
</a>
</li>
{% endfor %}
{% endif %}
</ul>
<ul class="externalNav">
{% for externalLink in socialLinks %}
<li>
<a href="{{ externalLink.url }}" title="{{ externalLink.name }}">
<svg class="bi" fill="currentColor" role="img">
<use xlink:href="/images/fontawesome/brands.svg#{{ externalLink.icon }}" />
</svg>
</a>
</li>
{% endfor %}
</ul>
</nav>
</header>
<main>
<h1 id="{{ title | slugify }}">
{{ title }}
<button class="sidebar-toggle" onclick="setSidebar()">
<svg class="bi" fill="currentColor" role="img" aria-label="Toggle Main Menu">
<use xlink:href="/images/bootstrap-icons.svg#layout-sidebar-inset" />
</svg>
</button>
</h1>
{{ content | safe }}
</main>
<script type="text/javascript" src="/js/site.js"></script>
</body>
</html>

30
src/_sections/Colors.njk Normal file
View file

@ -0,0 +1,30 @@
---
title: Colors
eleventyNavigation:
key: Colors
icon: eye-dropper
order: 3
tags: [ "MainPage" ]
sectionOrder: 3
draft: true
---
<div class="color-blocks">
<div class="color-block" style="background-color: var(--primary-dark-color); color: white;">
Primary Dark
</div>
<div class="color-block" style="background-color: var(--primary-color); color: white;">
Primary
</div>
<div class="color-block" style="background-color: var(--primary-light-color); color: black;">
Primary Light
</div>
<div class="color-block" style="background-color: var(--complementary-color); color: black">
Complementary
</div>
</div>
<div>
<input type="checkbox" id="testCheckBox" name="testCheckBox" checked />
<label for="testCheckBox">This checkbox has the accent color</label>
</div>

11
src/_sections/Contact.md Normal file
View file

@ -0,0 +1,11 @@
---
title: Contact
eleventyNavigation:
key: Contact
icon: envelope
order: 2
tags: [ "MainPage" ]
sectionOrder: 2
---
Send an email to `contact @ this site`

View file

@ -0,0 +1,25 @@
---
title: Projects
eleventyNavigation:
key: Projects
icon: screwdriver-wrench
order: 1
tags: [ "MainPage" ]
sectionOrder: 1
---
<div class="row-lg-3">
{% for project in projects %}
<section class="col card">
<h3>{{ project.name }}</h3>
<p>{{ project.description }}</p>
<ul class="card-links">
{% for link in project.links %}
<li>
<a href="{{ link.url }}" target="_blank">{{ link.title }}</a>
</li>
{% endfor %}
</ul>
</section>
{% endfor %}
</div>

View file

@ -0,0 +1,3 @@
{
"permalink": false
}

7
src/_sections/main.md Normal file
View file

@ -0,0 +1,7 @@
---
title: Neil Brommer
sectionOrder: 0
tags: [ "MainPage" ]
---
Full-stack web developer at [Washington State University](https://wsu.edu)

View file

@ -0,0 +1,71 @@
@use '_variables';
*,
::before,
::after {
box-sizing: border-box;
}
html {
scroll-behavior: smooth;
@media (prefers-reduced-motion: reduce) {
scroll-behavior: auto;
}
}
body {
font-family: -apple-system, system-ui, BlinkMacSystemFont, "Segoe UI", Roboto,
"Helvetica Neue", sans-serif;
line-height: 1.5;
color: var(--text-color);
background-color: var(--background-color);
accent-color: var(--primary-color);
@media (min-width: #{variables.$sidebar-breakpoint}) {
display: flex;
gap: 2rem;
}
}
header {
height: 100dvh;
flex: 0 0 var(--sidebar-width);
}
main {
width: 100%;
max-width: 70em;
padding: 2rem;
margin-left: auto;
margin-right: auto;
}
a {
color: var(--primary-color);
text-decoration: underline;
text-decoration-thickness: 2px;
text-underline-offset: 0.25em;
text-decoration-color: transparent;
transition: text-decoration-color 200ms ease;
&:hover,
&:active {
text-decoration-color: var(--primary-color);
}
@media (prefers-contrast: more) {
color: var(--primary-color-dark);
text-decoration-color: var(--primary-color);
}
@media (prefers-reduced-motion: reduce) {
text-decoration-color: var(--primary-color);
}
}
hr {
border: none;
height: 1px;
background-color: var(--primary-border-color);
}

View file

@ -0,0 +1,5 @@
// this allows changing the size using font-size
.bi {
width: 1em;
height: 1em;
}

View file

@ -0,0 +1,46 @@
@use '_variables';
.card {
display: flex;
flex-direction: column;
padding: 1rem;
margin-bottom: 1rem;
border: solid 1px var(--primary-border-color);
border-radius: var(--main-border-radius);
h1, h2, h3, h4, h5, h6 {
margin-bottom: 0;
}
.card-links {
display: flex;
flex-direction: row;
flex-wrap: wrap;
gap: 0.5em;
margin: auto -1rem -1rem -1rem;
padding: 0.5rem;
border-top: solid 1px var(--primary-border-color);
list-style: none;
li a {
display: inline-block;
padding: 0.5em 1em;
text-decoration: none;
border-radius: var(--main-border-radius);
transition: background-color 200ms ease;
&:hover, &:focus {
background-color: var(--nav-hover-background);
@media (prefers-reduced-motion: reduce) {
background-color: transparent;
}
}
@media (prefers-contrast: more),
(prefers-reduced-motion: reduce) {
text-decoration: underline;
}
}
}
}

View file

@ -0,0 +1,19 @@
@use 'variables';
$code-background-color: darken(variables.$background-color, 5%);
$code-background-color-dark: lighten(variables.$background-color-dark, 5%);
:root {
--code-background: #{$code-background-color};
@media (prefers-color-scheme: dark) {
--code-background: #{$code-background-color-dark};
}
}
code {
background: var(--code-background);
padding: 0.125em 0.25em;
border: solid 1px var(--primary-border-color);
border-radius: calc(var(--main-border-radius) / 2);
}

View file

@ -0,0 +1,22 @@
@use '_variables';
.color-blocks {
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: space-between;
gap: 1rem;
margin-bottom: 1rem;
}
.color-block {
flex: 1 1 0;
height: 100px;
display: flex;
align-items: center;
justify-content: center;
white-space: nowrap;
border: solid 1px var(--primary-border-color);
border-radius: var(--main-border-radius);
padding: 0.5em;
}

View file

@ -0,0 +1,61 @@
@use '_variables';
:root {
--column-spacing: 1.5em;
}
.row {
display: flex;
flex-wrap: wrap;
gap: var(--column-spacing);
.col {
flex: 0 0 100%;
// Default to md
@media (min-width: #{variables.$size-md}) {
flex: 1;
}
}
}
@mixin row($name, $breakpoint) {
.row-#{$name} {
display: flex;
flex-wrap: wrap;
align-items: flex-start;
gap: var(--column-spacing);
.col {
flex: 0 0 100%;
@media (min-width: #{$breakpoint}) {
flex: 1;
}
}
}
@for $i from 1 through 12 {
.row-#{$name}-#{$i} {
display: flex;
flex-wrap: wrap;
gap: var(--column-spacing);
.col {
flex: 0 0 100%;
@media (min-width: #{$breakpoint}) {
// Even width filling the whole row accounting for gap
flex: 0 0 calc((100% / #{$i}) - (var(--column-spacing) / #{$i} * (#{$i} - 1)));
}
}
}
}
}
@include row("xs", #{variables.$size-xs});
@include row("sm", #{variables.$size-sm});
@include row("md", #{variables.$size-md});
@include row("lg", #{variables.$size-lg});
@include row("xl", #{variables.$size-xl});
@include row("2x", #{variables.$size-2x});

View file

@ -0,0 +1,8 @@
h1, h2, h3, h4, h5, h6 {
font-weight: 500;
margin-top: 0;
}
h1 {
font-size: 3em;
}

View file

@ -0,0 +1,18 @@
ul {
padding-inline-start: 1.5rem;
}
li {
margin: 0.25em 0;
}
dl {
dt {
font-weight: bold;
}
dd {
padding: 0.25em 0;
margin-inline-start: 1em;
}
}

View file

@ -0,0 +1,43 @@
@use 'variables';
h1 {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
font-size: 2rem;
}
.sidebar-toggle {
font-size: 2rem;
appearance: none;
cursor: pointer;
color: inherit;
background-color: var(--background-color);
border: solid 1px var(--primary-border-color);
border-radius: var(--main-border-radius);
display: flex;
align-items: center;
padding: 0.25em;
margin: 0.25em;
transition: background-color 200ms ease, color 200ms ease;
&:hover, &focus {
color: var(--primary-color);
background-color: var(--nav-hover-background);
}
&:active {
background-color: var(--primary-light-color);
}
@media (min-width: #{variables.$sidebar-breakpoint}) {
display: none;
}
}

View file

@ -0,0 +1,117 @@
@use 'sass:color';
@use '_variables';
header {
position: fixed;
top: 0;
left: calc((var(--sidebar-width) + 2rem) * -1);
height: calc(100dvh - 2rem); // Account for margin
margin: 1rem;
border: solid 1px var(--primary-border-color);
border-radius: var(--main-border-radius);
background-color: rgba(var(--background-color-components), 0.5);
backdrop-filter: blur(10px);
// Goes slightly past the end, then bounces back to the final position
transition: left 250ms cubic-bezier(.44,1.36,.74,.97);
@media (prefers-reduced-motion: reduce) {
transition: none;
}
@media (prefers-contrast: more) {
backdrop-filter: none;
background-color: var(--background-color);
}
&[aria-hidden="false"] {
left: 0;
}
@media (min-width: #{variables.$sidebar-breakpoint}) {
// Always show the sidebar on larger screens
position: sticky;
left: unset;
height: 100dvh;
margin: 0;
border: none;
background-color: unset;
backdrop-filter: none;
}
}
nav.navbar {
display: flex;
flex-direction: column;
justify-content: space-between;
width: var(--sidebar-width);
height: 100%;
padding: 1rem;
overflow: scroll;
ul {
display: flex;
list-style: none;
padding-left: 0;
li {
margin-bottom: 0;
}
a {
text-decoration: none;
}
}
ul.siteNav {
flex-direction: column;
justify-items: flex-start;
gap: 0.5em;
li {
a {
// Use flex to correct icon alignment
display: flex;
flex-direction: row;
align-items: center;
column-gap: 0.5em;
width: 100%;
padding: 0.75em;
color: var(--nav-link-color);
border-radius: var(--main-border-radius);
transition: background-color 200ms ease;
}
&.active a {
background-color: var(--nav-active-background);
}
&:not(.active) a:hover, &:not(.active) a:focus {
background-color: var(--nav-hover-background);
@media (prefers-reduced-motion: reduce) {
background-color: transparent;
}
}
}
}
ul.externalNav {
flex-direction: row;
flex-wrap: wrap;
justify-content: center;
gap: 0.5em;
li {
a {
display: inline-block;
padding: 0 0.75em;
color: var(--nav-link-color);
}
}
}
}

View file

@ -0,0 +1,95 @@
@use 'sass:color';
@function select-foreground($backgroundColor, $lightColor: white, $darkColor: black) {
@if (lightness($backgroundColor) > 60) {
@return $darkColor;
} @else {
@return $lightColor;
}
}
$primary-r: 77;
$primary-g: 94;
$primary-b: 193;
$primary-color: rgb($primary-r, $primary-g, $primary-b);
$primary-light-color: lighten($primary-color, 35%);
$primary-dark-color: darken($primary-color, 10%);
$complementary-color: color.complement($primary-color);
$text-color: #3b4351;
$text-color-dark: white;
$background-color-components: 255, 255, 255;
$background-color: white;
$background-color-dark-components: 33, 33, 36;
$background-color-dark: rgb(33, 33, 36);
$main-border-radius: 8px;
$nav-active-background: rgba($primary-color, 0.15);
$nav-active-background-dark: rgba($primary-color, 0.35);
$nav-background-hover-color: rgba($primary-light-color, 0.2);
$nav-background-hover-color-dark: rgba($primary-color, 0.2);
$nav-link-color: $text-color;
$nav-link-color-dark: $text-color-dark;
$primary-border-color: darken($background-color, 15%);
$primary-border-color-dark: lighten($background-color-dark, 15%);
$primary-border-color-contrast: darken($background-color, 50%);
$primary-border-color-dark-contrast: lighten($background-color-dark, 50%);
$size-xs: 480px;
$size-sm: 600px;
$size-md: 840px;
$size-lg: 960px;
$size-xl: 1280px;
$size-2x: 1440px;
$sidebar-width: 15rem;
$sidebar-breakpoint: $size-md;
:root {
--size-xs: 480px;
--size-sm: 600px;
--size-md: 840px;
--size-lg: 960px;
--size-xl: 1280px;
--size-2x: 1440px;
--sidebar-width: #{$sidebar-width};
--sidebar-breakpoint: #{$sidebar-breakpoint};
--main-border-radius: #{$main-border-radius};
--primary-color: #{$primary-color};
--primary-light-color: #{$primary-light-color};
--primary-dark-color: #{$primary-dark-color};
--complementary-color: #{$complementary-color};
--primary-border-color: #{$primary-border-color};
--text-color: #{$text-color};
--background-color: #{$background-color};
--background-color-components: #{$background-color-components};
--nav-link-color: #{$nav-link-color};
--nav-active-background: #{$nav-active-background};
--nav-hover-background: #{$nav-background-hover-color};
@media (prefers-color-scheme: dark) {
--text-color: #{$text-color-dark};
--background-color: #{$background-color-dark};
--background-color-components: #{$background-color-dark-components};
--primary-border-color: #{$primary-border-color-dark};
--nav-link-color: #{$nav-link-color-dark};
--nav-active-background: #{$nav-active-background-dark};
--nav-hover-background: #{$nav-background-hover-color-dark};
@media (prefers-contrast: more) {
--primary-border-color: #{$primary-border-color-dark-contrast};
}
}
@media (prefers-contrast: more) {
--primary-border-color: #{$primary-border-color-contrast};
}
}

13
src/css/site.scss Normal file
View file

@ -0,0 +1,13 @@
@use 'normalize.css/normalize';
@use 'Components/_variables';
@use 'Components/_base';
@use 'Components/_bootstrap-icons';
@use 'Components/_columns';
@use 'Components/_navbar';
@use 'Components/_headings';
@use 'Components/_card';
@use 'Components/_color-block';
@use 'Components/_mobile-header';
@use 'Components/_lists';
@use 'Components/_code';

18
src/index.njk Normal file
View file

@ -0,0 +1,18 @@
---
title: Neil Brommer
eleventyNavigation:
key: Neil Brommer
icon: house
order: 0
tags: [ "MainPage" ]
---
{% for section in collections.MainPage | filterDrafts | IsNotPage(page.url) | IsMainPageSection | orderBySectionOrder %}
{% if not loop.first %}
<h2 id="{{ section.data.title | slugify }}">{{ section.data.title }}</h2>
{% endif %}
<section>
{{ section.templateContent | safe }}
</section>
{% endfor %}

48
src/js/site.js Normal file
View file

@ -0,0 +1,48 @@
"use strict";
function dismissSidebarOnClick(event) {
if (event.target.closest("header") == null && event.target.closest(".sidebar-toggle") == null) {
event.stopPropagation();
setSidebar(false);
}
}
function setSidebar(isOpen) {
let header = document.querySelector("header");
// If isOpen isn't provided, then just toggle the sidebar
if (isOpen == null) {
let currentlyOpen = header.getAttribute("aria-hidden") == "false";
isOpen = !currentlyOpen;
}
header.setAttribute("aria-hidden", (!isOpen).toString());
if (isOpen) {
document.querySelector("body")
.addEventListener("click", dismissSidebarOnClick);
} else {
document.querySelector("body")
.removeEventListener("click", dismissSidebarOnClick);
}
}
// Make main page sections active on scroll
document.addEventListener("DOMContentLoaded", () => {
const observer = new IntersectionObserver(entries => {
entries.forEach(entry => {
const id = entry.target.previousElementSibling.id;
const menuListItem = document.querySelector(`nav li a[href="/#${id}"]`).parentElement;
if (entry.intersectionRatio > 0) {
menuListItem.classList.add("active");
} else {
menuListItem.classList.remove("active");
}
});
});
document.querySelectorAll("h1[id] + section, h2[id] + section, h3[id] + section, h4[id] + section, h5[id] + section, h6[id] + section")
.forEach(section => observer.observe(section));
});

48
src/resume.md Normal file
View file

@ -0,0 +1,48 @@
---
title: Resume
eleventyNavigation:
key: Resume
icon: file-lines
order: 2
---
## Work History
[Washington State University](https://wsu.edu) [Division of Student Affairs](https://studentaffairs.wsu.edu/)
: Application Developer, 2018 - present
: Created and maintained custom web applications either as standalone software or integrating with
third party backends
## Education
[Eastern Washington University](https://www.ewu.edu/)
: Bachelor of Science in Computer Science
: 2014 - 2018
[Spokane Community College](https://scc.spokane.edu/)
: Associate of Applied Science in Network Design and Administration
: 2011 - 2013
## Skills
* C# - .NET Framework and .NET 5+
* [ASP.NET and ASP.NET Core](https://dotnet.microsoft.com/en-us/apps/aspnet) - [WebForms](https://learn.microsoft.com/en-us/aspnet/web-forms/what-is-web-forms), [MVC](https://dotnet.microsoft.com/en-us/apps/aspnet/mvc), [Razor Pages](https://learn.microsoft.com/en-us/aspnet/core/razor-pages/?view=aspnetcore-7.0&tabs=visual-studio), and [WebAPI](https://learn.microsoft.com/en-us/aspnet/core/web-api/)
* Entity Framework and [Entity Framework Core](https://learn.microsoft.com/en-us/ef/core/)
* [Blazor WebAssembly](https://dotnet.microsoft.com/en-us/apps/aspnet/web-apps/blazor)
* WinForms and WPF
* Xamarin native for MacOS
* Version control
* Git
* [Team Foundation Server/Azure DevOps Server](https://azure.microsoft.com/en-us/products/devops/server)
* Web development
* HTML, CSS, JavaScript, [SASS](https://sass-lang.com/), [TypeScript](https://www.typescriptlang.org/)
* [React](https://react.dev/)
* Create and consume REST APIs with [OpenAPI](https://www.openapis.org/), [Swashbuckle](https://github.com/domaindrivendev/Swashbuckle.AspNetCore), and [NSwag](https://github.com/RicoSuter/NSwag)
* [WCF](https://learn.microsoft.com/en-us/dotnet/framework/wcf/whats-wcf)/SOAP
* Web server administration
* Linux (NGINX, Apache, Caddy) and Windows Server (IIS)
* Continuous Integration and Continuous Deployment via [Azure DevOps Server](https://azure.microsoft.com/en-us/products/devops/server)