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

9
.editorconfig Normal file
View file

@ -0,0 +1,9 @@
root = true
[*]
end_of_line = lf
insert_final_newline = true
[*.{html,njk,js,scss,css}]
indent_style = tab
trim_trailing_whitespace = true

61
.eleventy.js Normal file
View file

@ -0,0 +1,61 @@
const eleventyNavigationPlugin = require("@11ty/eleventy-navigation");
const eleventySass = require("eleventy-sass");
const mdDefList = require("markdown-it-deflist");
module.exports = function (eleventyConfig) {
eleventyConfig.setBrowserSyncConfig({
files: "./_site/css/**/*.css",
});
eleventyConfig.addPassthroughCopy({
"node_modules/bootstrap-icons/bootstrap-icons.svg":
"images/bootstrap-icons.svg",
"node_modules/@fortawesome/fontawesome-free/sprites":
"images/fontawesome",
"src/js/site.js": "js/site.js"
});
eleventyConfig.addPlugin(eleventyNavigationPlugin);
eleventyConfig.addPlugin(eleventySass, {
sass: {
loadPaths: ["node_modules"],
},
});
eleventyConfig.amendLibrary("md", mdLib => mdLib.use(mdDefList));
eleventyConfig.addFilter("IsNotPage", (collection, url) =>
collection.filter(item => item.url != url));
eleventyConfig.addFilter("IsMainPageSection", (collection) => {
return collection.filter(item => !item.url || item.url.startsWith("/_sections"));
});
eleventyConfig.addFilter("IsNotMainPageSection", (collection) => {
return collection.filter(item => item.url != null && (item.data.tags == null || !item.data.tags.includes("MainPage")));
});
eleventyConfig.addFilter("orderBySectionOrder", (collection) =>
collection.sort((a, b) => a.data.sectionOrder - b.data.sectionOrder)
);
eleventyConfig.addFilter("filterDrafts", collection => {
if (process.env.BUILD_DRAFTS === true) {
console.log("Skipping filtering drafts");
return collection;
}
return collection.filter(item => {
console.log(item);
if (item.data.draft != null) {
return !item.data.draft;
}
return true;
});
});
eleventyConfig.addFilter("log", (value) => console.log(value));
console.log("BUILD_DRAFTS: " + process.env.BUILD_DRAFTS);
return {
dir: {
input: "src",
},
};
};

33
.gitignore vendored
View file

@ -1,4 +1,29 @@
static/lib/
static/info.php
.vscode/
public/
# Node.js files
node_modules/
# Built files
/_site/
# MacOS files
.DS_Store
.AppleDouble
.LSOverride
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk

3
.nova/Configuration.json Normal file
View file

@ -0,0 +1,3 @@
{
}

21
.nova/Tasks/Eleventy.json Normal file
View file

@ -0,0 +1,21 @@
{
"actions" : {
"build" : {
"enabled" : true,
"script" : "npm install\nnpm run build"
},
"clean" : {
"enabled" : true,
"script" : "rm -r _site\nnpm clean-install"
},
"run" : {
"enabled" : true,
"script" : "npm install\nnpm start"
}
},
"environment" : {
"BUILD_DRAFTS" : "false"
},
"openLogOnRun" : "fail",
"persistent" : true
}

View file

@ -1,5 +1,5 @@
# Personal Website
# Personal Site
The files for my personal site at [neilbrommer.com](https://neilbrommer.com).
The source for my personal website at [neilbrommer.com](https://neilbrommer.com).
Built using [Hugo](https://gohugo.io/), [Bootstrap 4](https://getbootstrap.com/) and [FontAwesome 5](https://fontawesome.com/).
Built using [11ty](https://www.11ty.dev/).

View file

@ -1,6 +0,0 @@
+++
title = "{{ replace .Name '-' ' ' | title }}"
date = {{ .Date }}
draft = true
+++

View file

@ -1,6 +0,0 @@
languageCode = "en-us"
title = "Neil Brommer"
baseURL = "https://neilbrommer.com/"
enableGitInfo = true
enableRobotsTXT = true
googleAnalytics = "UA-105178749-1"

View file

@ -1,11 +0,0 @@
+++
title = "Neil Brommer"
date = 2018-05-12T09:37:22-07:00
draft = false
[menu]
[menu.main]
title = "Home"
weight = -100
+++
Recent graduate from <a href="https://www.ewu.edu/" target="_blank">Eastern Washington University</a> with a Bachelor of Science in Computer Science

View file

@ -1,44 +0,0 @@
+++
title = "Contact"
date = 2018-05-12T11:58:21-07:00
draft = false
[menu]
[menu.main]
weight = -70
+++
<div id="successAlert" class="alert alert-success" role="alert" style="display: none;">Message sent successfully!</div>
<div id="errorAlert" class="alert alert-danger" role="alert" style="display: none"></div>
<form id="contactForm" class="jumbotron border rounded p-1 p-sm-4">
<div class="form-group">
<label for="nameField">Name
<span class="required">*</span>
</label>
<input id="nameField" type="text" required="required" class="form-control">
</div>
<div class="form-group">
<label for="emailField">Email Address
<span class="required">*</span>
</label>
<input id="emailField" type="email" required="required" class="form-control">
</div>
<div class="form-group">
<label for="subjectField">Subject</label>
<input id="subjectField" type="text" class="form-control">
</div>
<div class="form-group">
<label for="messageField">Message
<span class="required">*</span>
</label>
<textarea id="messageField" rows="5" required="required" class="form-control"></textarea>
</div>
<div class="form-group captcha">
<div class="g-recaptcha" data-sitekey="6LfFATcUAAAAAJ_YZ3qvYWqrtOUHmCItq-azzV3x"></div>
</div>
<button id="contactSubmit" type="submit" class="btn btn-primary">Submit</button>
</form>
<script src='https://www.google.com/recaptcha/api.js'></script>
<script src="/js/contact.js"></script>

View file

@ -1,11 +0,0 @@
+++
title = "Links"
date = 2018-05-12T14:28:21-07:00
draft = false
description = "A list of links (mostly to articles) that I find useful or interesting."
[menu]
[menu.main]
weight = -80
+++
{{< linkCardList >}}

View file

@ -1,91 +0,0 @@
+++
title = "Resume"
date = 2018-05-12T11:48:46-07:00
draft = false
description = """
References and more contact information available on request via the [contact page](/contact).
PDF version available [here](/NeilBrommer-WebResume.pdf)"""
[menu]
[menu.main]
weight = -90
+++
## Education
<p>
<div class="row">
<div class="col-sm-8">
<b>Eastern Washington University</b>
</div>
<div class="col-sm-4">
<b class="float-sm-right">Cheney, Washington</b>
</div>
</div>
<div class="row">
<div class="col-sm-8">
<i>Bachelor of Science in Computer Science, 3.52 GPA</i>
</div>
<div class="col-sm-4">
<i class="float-sm-right">2014 &ndash; 2018</i>
</div>
</div>
</p>
<p>
<div class="row">
<div class="col-sm-8">
<b>Spokane Community College</b>
</div>
<div class="col-sm-4">
<b class="float-sm-right">Spokane, Washington</b>
</div>
</div>
<div class="row">
<div class="col-sm-8">
<i>Associate of Applied Science in Network Design and Administration, 3.13 GPA</i>
</div>
<div class="col-sm-4">
<i class="float-sm-right">2011 &ndash; 2013</i>
</div>
</div>
Networking with Cisco switches and routers, with basic Windows and Linux administration
</p>
## Relevant Coursework and Skills
<ul class="row">
<li class="col-md-6">Object-Oriented Languages &ndash; Java, C#, C++</li>
<li class="col-md-6">Object-Oriented design patterns</li>
<li class="col-md-6">GUI Programming &ndash; JavaFX, WinForms, WPF</li>
<li class="col-md-6">Databases &ndash; SQL, Database Design</li>
<li class="col-md-6">Web Development &ndash; HTML, CSS, JavaScript, JQuery, PHP, REST APIs</li>
<li class="col-md-6">Operating Systems &ndash; C, Unix, threads and processes, memory management</li>
<li class="col-md-6">Software Engineering</li>
<li class="col-md-6">Version Control Systems &ndash; Git</li>
<li class="col-md-6">Network Programming &ndash; Network sockets, binary protocols</li>
<li class="col-md-6">Data Mining</li>
<li class="col-md-6">Linux Administration &ndash; Basic system maintenance, Email, DNS</li>
<li class="col-md-6">Windows Administration &ndash; MS Exchange, Active Directory</li>
</ul>
## Projects
See the [projects](/#projects) list
## Certifications
<p>
<div style="font-weight: bold;">CCNA</div>
<div class="row">
<div class="col-sm-8">
<i>Cisco Certified Network Administrator</i>
</div>
<div class="col-sm-4">
<i class="float-sm-right">September 2013 &ndash; 2016</i>
</div>
</div>
Ciscos second level of computer networking certifications. Focuses on designing and managing routed and switched networks.
</p>

View file

@ -1,54 +0,0 @@
[[link]]
title = "Ok→Cancel versus Cancel→Ok"
description = "A look at button ordering in GUIs from the Factorio blog"
URL = "https://www.factorio.com/blog/post/fff-246"
[[link]]
title = "Principles of Lighting and Rendering with John Carmack at QuakeCon 2013"
description = "John Carmack talking about various 3D rendering concepts."
URL = "https://www.youtube.com/watch?v=IyUgHPs86XM"
[[link]]
title = "Why the Flash Crash Really Matters"
description = "An article on the flash crash of 2010 and the <a href=\"https://en.wikipedia.org/wiki/High-frequency_trading\" target=\"_blank\">high-frequency trading</a> programs that caused it."
URL = "http://nautil.us/issue/23/dominoes/why-the-flash-crash-really-matters"
[[link]]
title = "Algorithms and Bias: Q. and A. With Cynthia Dwork"
description = "An interview with Cynthia Dwork of Microsoft Research on how algorithms show bias and where those biases come from."
URL = "https://www.nytimes.com/2015/08/11/upshot/algorithms-and-bias-q-and-a-with-cynthia-dwork.html?abt=0002&abg=1"
[[link]]
title = "Origin of Quake3s Fast InvSqrt()"
description = "An article on the inverse square root function in Quake 3 looking into how it works and its history."
URL = "https://www.beyond3d.com/content/articles/8/"
[[link]]
title = "Real Time Rendering: An Overview for Artists"
description = "A look at various aspects of real time 3D rendering, especially in video games."
URL = "https://jesshiderue4.wordpress.com/real-time-rendering-an-overview-for-artists/"
[[link]]
title = "Entering the Quantum Era—How Firefox got fast again and where its going to get faster"
description = "An article that looks at various architectural changes to Firefox in version 57 and changes planned for future versions."
URL = "https://hacks.mozilla.org/2017/11/entering-the-quantum-era-how-firefox-got-fast-again-and-where-its-going-to-get-faster/"
[[link]]
title = "The whole web at maximum FPS: How WebRender gets rid of jank "
description = " An article that gives a high level description how Firefoxs new rendering engine works."
URL = "https://hacks.mozilla.org/2017/10/the-whole-web-at-maximum-fps-how-webrender-gets-rid-of-jank/"
[[link]]
title = "Inside a super fast CSS engine: Quantum CSS (aka Stylo)"
description = "A high level overview of how Firefoxs new CSS engine Stylo works."
URL = "https://hacks.mozilla.org/2017/08/inside-a-super-fast-css-engine-quantum-css-aka-stylo/"
[[link]]
title = "The Judges Code"
description = "An article on Judge William Alsup, who was the judge for the first <a href=\"https://en.wikipedia.org/wiki/Oracle_America,_Inc._v._Google,_Inc.\" target=\"_blank\"> Oracle v. Google</a> case over whether an API can be copywritten, and is now the judge for the <a href=\"http://www.businessinsider.com/google-waymo-v-uber-case-explained-2017-5\" target=\"_blank\">Waymo (Google) v. Uber</a>."
URL = "https://www.theverge.com/2017/10/19/16503076/oracle-vs-google-judge-william-alsup-interview-waymo-uber"
[[link]]
title = "LinkedIn Dark Patterns"
description = "An in depth blog post detailing how LinkedIn used dark patterns to trick users."
URL = "https://schlosser.io/writing/linkedin-dark-patterns/"

View file

@ -1,78 +0,0 @@
[[project]]
title = "Growing Neighbors"
incomplete = false
description = "The website for the Growing Neighbors organization in Spokane, WA. I worked as part of a team building this site."
[[project.links]]
title = "Site"
external = true
URL = "https://growingneighbors.org/"
[[project]]
title = "Snippets"
incomplete = true
description = "A web based tool for managing reusable pieces of code and useful programming resources"
[[project.links]]
title = "Web Client"
external = true
URL = "https://snippets.neilbrommer.com/"
[[project.links]]
title = "Documentation"
external = true
URL = "https://snippets.neilbrommer.com/documentation.html"
[[project.links]]
title = "Source Code"
external = true
URL = "https://github.com/NeilBrommer/SnippetManager"
[[project]]
title = "Start"
incomplete = false
description = "A new tab page that displays lists of links using the browser's indexedDB to store all data"
[[project.links]]
title = "Site"
external = true
URL = "https://start.neilbrommer.com/"
[[project.links]]
title = "Source Code"
external = true
URL = "https://github.com/NeilBrommer/NewTabPage"
[[project]]
title = "Picture Viewer"
incomplete = false
description = "A Windows program for viewing image files written using C# and WPF"
[[project.links]]
title = "Source Code"
external = true
URL = "https://github.com/NeilBrommer/PictureViewer"
[[project]]
title = "Website"
incomplete = false
description = "The source code for this website"
[[project.links]]
title = "Source Code"
external = true
URL = "https://github.com/NeilBrommer/Personal-Site"
[[project]]
title = "Resume"
incomplete = false
description = "My [Resume](resume)"
[[project.links]]
title = "PDF"
external = true
URL = "/NeilBrommer-WebResume.pdf"
[[project.links]]
title = "Source Code"
external = true
URL = "https://github.com/NeilBrommer/Resume"
[[project]]
title = "Auto Dark"
incomplete = false
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."
[[project.links]]
title = "Source Code"
external = true
URL = "https://github.com/NeilBrommer/WindowsAutoDark"

View file

@ -1,2 +0,0 @@
github = "https://github.com/NeilBrommer"
linkedin = "https://www.linkedin.com/in/neilbrommer/"

View file

@ -1,22 +0,0 @@
{{ partial "htmlhead" . }}
</head>
<body>
{{ partial "navbar" .}}
<div class="container">
{{ if isset .Params "description" }}
<div class="jumbotron">
<h1 id="title" class="display-4 mb-4">{{ .Title }}</h1>
{{ .Params.description | markdownify }}
</div>
{{ else }}
<h1 class="display-4 mb-4">{{ .Title }}</h1>
{{ end }}
{{.Content}}
</div>
</body>
</html>

View file

@ -1,26 +0,0 @@
{{ partial "htmlhead" . }}
<link href="/css/index.css" rel="stylesheet">
</head>
<body>
{{ partial "navbar" . }}
<div id="header" class="text-center">
<div id="headerContent" class="container">
<h1 id="title" class="display-4 font-weight-normal">{{ .Title }}</h1>
<div class="lead"> {{ .Content }} </div>
</div>
</div>
<div class="container">
<h1 id="projects" class="mb-4">Projects</h1>
<div class="card-columns">
{{ range .Site.Data.projects.project }}
{{ partial "projectCard" . }}
{{ end }}
</div>
</div>
</body>
</html>

View file

@ -1,41 +0,0 @@
<!DOCTYPE HTML>
<html lang='{{ .Site.Language.Lang | default "en-us" }}'>
<head>
<title>{{ if eq (.Site.Title) (.Title) }}{{ .Site.Title }}{{ else }}{{ .Site.Title }} &middot; {{ .Title }}{{ end }}</title>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no" />
<meta name="author" content="Neil Brommer">
<link rel="icon" href="/favicon.ico">
<!-- theme colors -->
<!-- Chrome, Firefox OS and Opera -->
<meta name="theme-color" content="#3f51b5">
<!-- Windows Phone -->
<meta name="msapplication-navbutton-color" content="#3f51b5">
<!-- iOS -->
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<!-- Bootstrap -->
<link href="/lib/bootstrap/css/bootstrap.min.css" rel="stylesheet">
<!-- font awesome -->
<script defer src="/lib/fontawesome/svg-with-js/js/fa-brands.js"></script>
<script defer src="/lib/fontawesome/svg-with-js/js/fa-regular.js"></script>
<script defer src="/lib/fontawesome/svg-with-js/js/fa-solid.js"></script>
<script src="/lib/fontawesome/svg-with-js/js/fontawesome.js"></script>
<!-- Custom styles for this site -->
<link href="/css/main.css" rel="stylesheet">
<!-- Bootstrap core JavaScript -->
<script src="/lib/jquery/jquery-3.2.1.js"></script>
<script src="/lib/popper/popper.js"></script>
<script src="/lib/bootstrap/js/bootstrap.min.js"></script>
<!-- Custom JS -->
<script src="/js/main.js"></script>
{{ template "_internal/google_analytics_async.html" . }}

View file

@ -1,11 +0,0 @@
<div class="card">
<h4 class="card-header">{{ .title }}</h4>
<div class="card-body">
<p><a class="card-link" href="{{ .URL }}" target="_blank">Link</a></p>
{{ if isset . "description" }}
<p>{{ .description | markdownify }}</p>
{{ end }}
</div>
</div>

View file

@ -1,47 +0,0 @@
<nav id="topnav" class="navbar navbar-expand-md navbar-dark fixed-top bg-dark">
<div class="container">
<a class="navbar-brand" href="/">{{ .Site.Title }}</a>
<button class="navbar-toggler mr-0 ml-auto" type="button" data-toggle="collapse" data-target="#navbarCollapse" aria-controls="navbarCollapse"
aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarCollapse">
<ul class="navbar-nav mr-auto">
{{ $currentPage := . }}
{{ range .Site.Menus.main }}
<li class="nav-item {{if or ($currentPage.IsMenuCurrent "main" .) ($currentPage.HasMenuCurrent "main" .) }} active{{end}}">
<a class="nav-link" href="{{ .URL }}">{{.Title}}</a>
</li>
{{ end }}
</ul>
<ul id="social-menu" class="navbar-nav">
{{ with .Site.Data.social }}
{{ with .github }}
<li class="nav-item">
<a class="nav-link" href="{{ . }}" target="_blank" title="GitHub">
<span class="fab fa-github-alt"></span>
</a>
</li>
{{ end }}
{{ with .linkedin }}
<li class="nav-item">
<a class="nav-link" href="{{ . }}" target="_blank" title="LinkedIn">
<span class="fab fa-linkedin"></span>
</a>
</li>
{{ end }}
{{ end }}
<li class="nav-item ml-auto">
<button id="btnTheme" type="button" class="btn btn-dark" aria-pressed="false" autocomplete="off">
<span id="themeText" class="fas fa-moon"></span>
</button>
</li>
</ul>
</div>
</div>
</nav>

View file

@ -1,24 +0,0 @@
<div class="card">
<h4 class="card-header">{{ .title }}</h4>
<div class="card-body">
{{ if .incomplete }}
<p class="text-danger"><small>Incomplete</small></p>
{{ end }}
{{ if isset . "description" }}
<p>{{ .description | markdownify }}</p>
{{ end }}
{{ if isset . "links" }}
{{ range .links }}
<a class="card-link" href="{{ .URL }}"
{{ if and (isset . "external") (eq .external true) }}
target="_blank"
{{ end }}
>
{{ .title }}
</a>
{{ end }}
{{ end }}
</div>
</div>

View file

@ -1,9 +0,0 @@
{{ with $.Site.Data.links }}
<div class="card-columns">
{{ range .link }}
{{ partial "linkCard" . }}
{{ end }}
</div>
{{ end }}

4232
package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

25
package.json Normal file
View file

@ -0,0 +1,25 @@
{
"name": "websiterewrite",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"watch": "npx @11ty/eleventy --serve",
"start": "open http://localhost:8080 & npm run watch",
"build": "npx @11ty/eleventy"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@11ty/eleventy": "^2.0.0",
"@11ty/eleventy-navigation": "^0.3.5",
"eleventy-sass": "^2.2.3",
"markdown-it-deflist": "^2.1.0"
},
"dependencies": {
"@fortawesome/fontawesome-free": "^6.4.0",
"bootstrap-icons": "^1.10.5",
"normalize.css": "^8.0.1"
}
}

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)

Binary file not shown.

View file

@ -1,113 +0,0 @@
<?php
use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\Exception;
require 'PHPMailer/src/Exception.php';
require 'PHPMailer/src/PHPMailer.php';
require 'PHPMailer/src/SMTP.php';
include_once "info.php";
if ($_SERVER["REQUEST_METHOD"] == 'POST') {
if (isset($_POST["captcha"]) && !empty($_POST["captcha"])) {
$captcha = $_POST["captcha"];
$url = "https://www.google.com/recaptcha/api/siteverify";
$data = array(
"secret" => $recaptchaSecret,
"response" => $captcha,
"remoteip" => $_SERVER["REMOTE_ADDR"]
);
$options = array(
"http" => array(
"header" => "Content-Type: application/x-www-form-urlencoded\r\n",
"method" => "POST",
"content" => http_build_query($data)
)
);
$context = stream_context_create($options);
$result = file_get_contents($url, false, $context);
if ($result === false) {
http_response_code(500);
die("Error verifying reCAPTCHA");
}
$result = json_decode($result, true);
if ($result["success"] == false) {
http_response_code(400);
die("Could not verify reCAPTCHA");
}
if (!isset($_POST["name"])) {
http_response_code(400);
die("A name is required");
}
$name = trim($_POST["name"]);
if ($name == '') {
http_response_code(400);
die("The name cannot be empty");
} else if (strtolower($name) == 'anon' || strToLower($name) == 'anonymous') {
http_response_code(400);
die("Enter a real name");
}
if (!isset($_POST["email"])) {
http_response_code(400);
die("An email address is required");
}
$email = trim($_POST["email"]);
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
http_response_code(400);
die("Invalid email address");
}
$subject = "Message from contact form";
if (isset($_POST["subject"]) && trim($_POST["subject"]) != "") {
$subject = $subject . ": " . $_POST["subject"];
}
if (!isset($_POST["message"])) {
http_response_code(400);
die("A message is required");
}
$message = trim($_POST["message"]);
if ($message == '') {
http_response_code(400);
die("A message is required");
}
$message = $name . ' -- &lt;' . $email . '&gt;<hr>' . $message;
$mail = new PHPMailer(true); // true enables exceptions
try {
$mail->isSMTP();
//$mail->SMTPDebug = 3;
$mail->Host = $mailHost;
$mail->SMTPAuth = true;
$mail->Username = $mailUser;
$mail->Password = $mailPass;
$mail->SMTPSecure = 'ssl';
$mail->Port = $mailPort;
$mail->setFrom($mailFrom, 'Website Contact Form');
$mail->addAddress($mailDest);
$mail->addReplyTo($email);
$mail->isHTML(true);
$mail->Subject = $subject;
$mail->Body = $message;
$mail->Send();
} catch (Exception $e) {
http_response_code(500);
echo 'Message could not be sent.';
echo 'Mailer Error: ' . $mail->ErrorInfo;
}
} else { // no recaptcha
http_response_code(400);
echo "Bad reCAPTCHA";
}
} else {
http_response_code(404);
}
?>

View file

@ -1,26 +0,0 @@
body {
padding-top: 0px;
}
#header {
padding: 10vw 2rem;
margin-bottom: 2rem;
color: white;
text-shadow: 1px 1px 2px gray;
box-shadow: 0 1px 3px rgba(0,0,0,0.16), 0 1px 3px rgba(0,0,0,0.23);
background: linear-gradient(rgba(0, 0, 0, 0.1), rgba(0, 0, 0, 0.1)), url('/img/trianglify.svg');
}
#headerContent {
padding-top: 70px; /* account for nav bar */
}
#header a {
color: #a10022 !important;
}
#title {
margin-bottom: 2rem;
}

View file

@ -1,149 +0,0 @@
:root {
--main-color: #3f51b5; /* R:63, G:82, B:181 */
--main-dark-color: #002984; /* R:0, G:41, B:132 */
--main-light-color: #757de8; /* R:117, G:125, B:232 */
--accent-color: #FFC107; /* R:255, G:193, B:7 */
}
body {
padding-top: 70px;
padding-bottom: 40px;
cursor: default;
}
.navbar.fixed-top {
padding-top: 0;
padding-bottom: 0;
background-color: #3f51b5 !important;
box-shadow: 0 3px 6px rgba(0,0,0,0.16), 0 3px 6px rgba(0,0,0,0.23);
border-bottom: #002984 1px solid;
}
.nav-item {
padding-top: 0.5rem;
padding-bottom: 0.5rem;
}
@media screen and (min-width: 768px) {
.navbar-nav .nav-item.active {
padding-bottom: 0.25rem;
border-bottom: white 0.25rem solid;
}
#btnTheme {
margin-left: 0.5rem;
}
}
@media screen and (max-width: 767px) {
.navbar-nav .nav-item.active {
padding-left: 0.75rem;
border-left: white 0.25rem solid;
}
.navbar-nav .nav-item {
padding-left: 1rem;
}
#social-menu {
flex-direction: row;
}
#social-menu .nav-item:not(:first-child) {
padding-left: 2rem;
}
}
.navbar {
padding-left: 0;
}
.navbar .navbar-brand {
margin-top: 0.5rem;
margin-bottom: 0.5rem;
padding-left: 1rem;
}
.card, .jumbotron {
box-shadow: 0 .25rem .75rem rgba(0, 0, 0, .05);
}
h1:not(.card-header):not(#title), h2:not(.card-header), h3:not(.card-header), h4:not(.card-header), h5:not(.card-header), h6:not(.card-header) {
margin-top: 30px;
margin-bottom: 15px;
padding-bottom: 10px;
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
}
a:not(.nav-link):not(.navbar-brand) {
color: #3f51b5;
}
a:hover:not(.nav-link):not(.navbar-brand) {
color: #757de8;
}
li.col-md-6 {
padding-left: 0px;
}
.cover {
text-align: center;
}
.required {
color: red;
}
.invalid-captcha {
box-shadow: 0 0 1.5px 1px red;
}
.fa {
font-size: 0.85rem;
}
.full-height {
height: 100%;
}
/* Dark Mode */
.dark-mode {
color: white;
background-color: #343a40;
}
.dark-mode .jumbotron {
background-color: #2b3136;
}
.dark-mode a:not(.navbar-brand):not(.nav-link) {
color: #757de8 !important;
}
.dark-mode a:not(.navbar-brand):not(.nav-link):hover {
color: #3f51b5 !important;
}
.dark-mode .card {
background-color: #343a40;
border-color: rgba(255, 255, 255, 0.125);
}
.dark-mode .form-control {
background-color: rgb(81, 89, 107);
border-color: rgb(60, 66, 80);
}
.dark-mode .form-control:focus {
box-shadow: 0 0 0 .2rem rgba(255,255,255,.5);
}
/* for transitioning to/from dark mode */
.transition {
-webkit-transition: color 0.25s ease-in-out, background-color 0.25s ease-in-out, box-shadow 0.25s ease-in-out, border 0.25s ease-in-out;
-moz-transition: color 0.25s ease-in-out, background-color 0.25s ease-in-out, box-shadow 0.25s ease-in-out, border 0.25s ease-in-out;
-o-transition: color 0.25s ease-in-out, background-color 0.25s ease-in-out, box-shadow 0.25s ease-in-out, border 0.25s ease-in-out;
transition: color 0.25s ease-in-out, background-color 0.25s ease-in-out, box-shadow 0.25s ease-in-out, border 0.25s ease-in-out;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 98 KiB

View file

@ -1,65 +0,0 @@
$(document).ready(function() {
$("#contactForm").on("submit", sendForm);
});
function sendForm(e) {
e.preventDefault(); // prevent the page from refreshing
$("#contactSubmit").prop("disabled", true);
if ($("#successAlert")[0].style.display != 'none') {
$("#successAlert").slideUp(250);
}
if ($("#errorAlert")[0].style.display != 'none') {
$("#errorAlert").slideUp(250);
}
var captcha = grecaptcha.getResponse();
if (captcha.length == 0) {
$(".captcha").addClass("invalid-captcha");
} else {
$(".captcha").removeClass("invalid-captcha");
var name = $("#nameField").val();
var email = $("#emailField").val();
var subject = $("#subjectField").val();
var message = $("#messageField").val();
$.ajax({
url: "../contact.php",
type: "POST",
data: {
"name": name,
"email": email,
"subject": subject,
"message": message,
"captcha": captcha
},
success: messageSuccess,
error: messageError,
complete: doneSending
});
}
}
function messageSuccess(result) {
$("#successAlert").slideDown(500);
}
function messageError(result) {
var alert = $("#errorAlert");
alert.empty();
$(document.createTextNode("Error: " + result.responseText)).appendTo(alert);
alert.slideDown(500);
}
function doneSending() {
var html = $("html");
var top = html.scrollTop() + $("body").scrollTop() // Get position of the body
if(top != 0) {
$("html,body").animate({scrollTop:0}, '500');
}
$("#contactSubmit").prop("disabled", false);
}

View file

@ -1,67 +0,0 @@
var theme = window.localStorage.getItem("theme");
if (theme != null && theme == "true")
$("body").addClass("dark-mode");
$(document).ready(function () {
$("#btnTheme").click(function () {
if ($("#btnTheme").hasClass("btn-light")) {
transitionLight();
} else {
transitionDark();
}
});
var theme = window.localStorage.getItem("theme");
if (theme != null && theme == "true")
setDark();
});
function checkTheme() {
if (theme != null && theme == "true")
setDark();
}
function transitionDark() {
$(".card").addClass("transition");
$(".jumbotron").addClass("transition");
$(".form-control").addClass("transition");
$("a:not(.navbar-brand):not(.nav-link)").addClass("transition");
$("body").addClass("transition dark-mode");
setTimeout(endTransition, 250);
$("#btnTheme").removeClass("btn-dark").addClass("transition btn-light");
$("#themeText").replaceWith($("<span>").attr("id", "themeText").addClass("fas fa-sun"));
window.localStorage.setItem("theme", "true");
}
function transitionLight() {
$(".card").addClass("transition");
$(".jumbotron").addClass("transition");
$(".form-control").addClass("transition");
$("a:not(.navbar-brand):not(.nav-link)").addClass("transition");
$("body").addClass("transition").removeClass("dark-mode");
setTimeout(endTransition, 250);
$("#btnTheme").removeClass("btn-light").addClass("transition btn-dark");
$("#themeText").replaceWith($("<span>").attr("id", "themeText").addClass("fas fa-moon"));
window.localStorage.setItem("theme", "false");
}
function endTransition() {
$("body").removeClass("transition");
$(".card").removeClass("transition");
$(".jumbotron").removeClass("transition");
$("a:not(.navbar-brand):not(.nav-link)").removeClass("transition");
$(".form-control").removeClass("transition");
}
function setDark() {
$("body").addClass("dark-mode");
$("#btnTheme").removeClass("btn-dark").addClass("btn-light");
$("#themeText").replaceWith($("<span>").attr("id", "themeText").addClass("fas fa-sun"));
}