@@ -1,7 +1,10 @@.PHONY: run push.PHONY: run push buildrun: uv run flask --app app run --host 0.0.0.0 --port 8000 --debug npx nodemon --watch webpack.config.js --watch package.json --watch yarn.lock --ext js,json --exec "npx webpack --mode development --watch" & uv run flask --app app run --host 0.0.0.0 --port 8000 --debugbuild: npx webpack --mode productionpush: git remote | xargs -I R git push R master
@@ -127,6 +127,7 @@ def load_posts(): publish_date = meta.get("publish_date", post_date) post = { "filename": filename, "title": meta.get("title", ""), "slug": meta.get("slug", filename[:-3]), "date": post_date,
@@ -288,6 +289,21 @@ def blog_post_pdf(slug): )@app.route("/blog/<slug>/md/")def blog_post_md(slug): post = POSTS_BY_SLUG.get(slug) if not post or post["publish_date"] > date.today().isoformat(): abort(404) filepath = os.path.join(CONTENT_DIR, "posts", post["filename"]) with open(filepath) as f: content = f.read() return Response( content, mimetype="text/markdown", headers={"Content-Disposition": f'filename="{post["slug"]}.md"'}, )@app.route("/blog/tag/<tag>/")def blog_tag(tag): posts = get_published_posts()
@@ -4,6 +4,7 @@ "build": "webpack --config webpack.config.js --mode production" }, "dependencies": { "@fontsource/monaspace-argon": "^5.2.5", "@popperjs/core": "^2.11.5", "bootstrap": "^5.1.3", "codemirror": "^5.65.5"
@@ -13,6 +14,7 @@ "css-minimizer-webpack-plugin": "^4.0.0", "file-loader": "^6.2.0", "mini-css-extract-plugin": "^2.6.0", "nodemon": "^3.1.14", "sass": "^1.52.3", "sass-loader": "^13.0.0", "terser-webpack-plugin": "^5.3.3",
modified
static_src/index.js
@@ -1,3 +1,6 @@// fontsimport "@fontsource/monaspace-argon";// scriptsimport "./scripts/bootstrap.js";import "./scripts/dark.js";
modified
static_src/scripts/dark.js
@@ -1,54 +1 @@/** * dark.js * * Detects the systems current preference for dark or light mode but allows for * overriding the system preference. */const main = document.querySelector("main");const getSystemPreference = () => { if (window.matchMedia("(prefers-color-scheme: dark)").matches) { return "dark"; } else { return "light"; }};const setIcon = (preference) => { const icons = document.querySelectorAll(".prefers-color-scheme-icon"); icons.forEach((icon) => { icon.classList.add("d-none"); }); icons.forEach((icon) => { if (icon.classList.contains(preference)) { icon.classList.remove("d-none"); } });};const setPreference = (preference) => { setIcon(preference); if (preference === "system") { preference = getSystemPreference(); } if (preference === "dark") { main.classList.add("dark"); } else { main.classList.remove("dark"); }};const select = document.querySelector("#prefers-color-scheme");select.addEventListener("change", () => { localStorage.setItem("darkMode", select.value); setPreference(select.value);});const storedPreference = localStorage.getItem("darkMode");if (storedPreference) { select.value = storedPreference; setPreference(storedPreference);} else { setPreference("system");}// Dark mode is now the permanent theme — no toggle needed.
modified
static_src/scripts/search.js
@@ -3,77 +3,106 @@ * * Provides live search for any "id_search" field using the URL * /search/live/?q=<query> * * Arrow keys navigate results, Enter follows the selected link. */document.addEventListener("DOMContentLoaded", function () { const searchEl = document.getElementById("id_search"); if (searchEl) { searchEl.addEventListener("keyup", function (e) { const query = searchEl.value; if (query.length > 0) { const url = "/search/live/?q=" + query; fetch(url) .then((response) => response.json()) .then((data) => { // the results contain 'title', 'description', 'url' // add a new el to the parent of searchEl for the results // clear the previous el if it exists let resultsEl = searchEl.parentElement.querySelector("#id_search_results"); if (resultsEl) { searchEl.parentElement.removeChild(resultsEl); } resultsEl = document.createElement("div"); resultsEl.id = "id_search_results"; searchEl.parentElement.appendChild(resultsEl); // add a new ul to the resultsEl const resultsDiv = document.createElement("ul"); resultsDiv.classList.add("list-group", "rounded-bottom", "position-absolute", "mt-2"); resultsDiv.style.zIndex = "1100"; // above everything else bootstrap resultsDiv.style.width = searchEl.offsetWidth + "px"; resultsEl.appendChild(resultsDiv); // add a new li for each result data.forEach((result) => { const a = document.createElement("a"); a.classList.add("list-group-item", "list-group-item-action"); // strong the part of the title that matches the query const title = result.title.replace( new RegExp(query, "gi"), "<strong>" + query + "</strong>" ); const description = result.description.replace( new RegExp(query, "gi"), "<strong class='fw-bolder'>" + query + "</strong>" ); // add the title and the description, the search_descrption // should be in a span and muted a.innerHTML = "<span class='fw-bold'>" + title + "</span>" + "<br><span class='text-muted'>" + description + "</span>"; a.href = result.url; resultsDiv.appendChild(a); }); }); } }); // if the user clears the search field, remove the results searchEl.addEventListener("keyup", function (e) { if (searchEl.value.length === 0) { const resultsEl = searchEl.parentElement.querySelector("#id_search_results"); if (resultsEl) { searchEl.parentElement.removeChild(resultsEl); } } }); // if we are not focusing inside the search field, remove the results searchEl.addEventListener("blur", function (e) { // unless we are clicking on the results const searchResults = searchEl.parentElement.querySelector("#id_search_results"); if (searchResults) { if (!searchResults.contains(e.relatedTarget)) { const resultsEl = searchEl.parentElement.querySelector("#id_search_results"); if (resultsEl) { searchEl.parentElement.removeChild(resultsEl); } } } }); } if (!searchEl) return; let activeIndex = -1; const getItems = () => { const container = searchEl.parentElement.querySelector("#id_search_results ul"); return container ? Array.from(container.querySelectorAll("a")) : []; }; const setActive = (index) => { const items = getItems(); items.forEach((item) => item.classList.remove("active")); activeIndex = index; if (index >= 0 && index < items.length) { items[index].classList.add("active"); items[index].scrollIntoView({ block: "nearest" }); } }; const clearResults = () => { const resultsEl = searchEl.parentElement.querySelector("#id_search_results"); if (resultsEl) { searchEl.parentElement.removeChild(resultsEl); } activeIndex = -1; }; searchEl.addEventListener("keydown", function (e) { const items = getItems(); if (!items.length) return; if (e.key === "ArrowDown") { e.preventDefault(); setActive(activeIndex < items.length - 1 ? activeIndex + 1 : 0); } else if (e.key === "ArrowUp") { e.preventDefault(); setActive(activeIndex > 0 ? activeIndex - 1 : items.length - 1); } else if (e.key === "Enter" && activeIndex >= 0 && activeIndex < items.length) { e.preventDefault(); window.location.href = items[activeIndex].href; } }); searchEl.addEventListener("keyup", function (e) { // Ignore navigation keys if (["ArrowDown", "ArrowUp", "Enter"].includes(e.key)) return; const query = searchEl.value; if (query.length === 0) { clearResults(); return; } const url = "/search/live/?q=" + encodeURIComponent(query); fetch(url) .then((response) => response.json()) .then((data) => { clearResults(); if (!data.length) return; const resultsEl = document.createElement("div"); resultsEl.id = "id_search_results"; searchEl.parentElement.appendChild(resultsEl); const resultsDiv = document.createElement("ul"); resultsDiv.classList.add("list-group", "rounded-bottom", "position-absolute", "mt-2"); resultsDiv.style.zIndex = "1100"; resultsDiv.style.width = searchEl.offsetWidth + "px"; resultsDiv.style.backgroundColor = "#13120e"; resultsDiv.style.border = "1px solid rgba(107,158,120,0.15)"; resultsEl.appendChild(resultsDiv); data.forEach((result) => { const a = document.createElement("a"); a.classList.add("list-group-item", "list-group-item-action"); const title = result.title.replace( new RegExp(query, "gi"), "<strong>" + query + "</strong>" ); const description = result.description.replace( new RegExp(query, "gi"), "<strong class='fw-bolder'>" + query + "</strong>" ); a.innerHTML = "<span class='fw-bold'>" + title + "</span>" + "<br><span class='text-muted'>" + description + "</span>"; a.href = result.url; resultsDiv.appendChild(a); }); }); }); searchEl.addEventListener("blur", function (e) { const searchResults = searchEl.parentElement.querySelector("#id_search_results"); if (searchResults && !searchResults.contains(e.relatedTarget)) { clearResults(); } });});
modified
static_src/styles/base.scss
@@ -2,57 +2,142 @@ body { min-height: 100vh; display: flex; flex-direction: column; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; letter-spacing: 0.01em;}// ── Logo ──.logo { content: ""; display: block; background-image: linear-gradient( to right, rgb(14, 63, 244) 0px, rgb(132, 43, 255) 100% 135deg, rgb(14, 63, 180) 0%, rgb(107, 158, 120) 100% ); width: 36px; height: 36px; margin-top: auto; margin-bottom: auto; transition: transform 0.2s ease-in-out; border-radius: 3px; transition: transform 0.25s ease, box-shadow 0.25s ease; &:hover { transform: rotate(15deg) scale(1.1); transform: rotate(12deg) scale(1.08); box-shadow: 0 0 14px rgba(107, 158, 120, 0.3); }}// ── Navbar ──.navbar { border-bottom: 1px solid rgba(107, 158, 120, 0.06); .nav-link { font-size: 0.82rem; font-weight: 500; letter-spacing: 0.04em; padding-left: 1rem !important; padding-right: 1rem !important; text-transform: lowercase; position: relative; &::after { content: ''; position: absolute; bottom: 0; left: 1rem; right: 1rem; height: 1px; background: #6b9e78; box-shadow: 0 0 6px rgba(107, 158, 120, 0.3); transform: scaleX(0); transition: transform 250ms ease; } &:hover::after, &.active::after { transform: scaleX(1); } }}// ── Breadcrumb bar ──.breadcrumb-bar { background: rgba(107, 158, 120, 0.02); border-bottom: 1px solid rgba(107, 158, 120, 0.04);}// ── Footer ──footer { padding-top: 6rem; padding-top: 4rem; position: relative; .links { padding-top: 6rem; padding-top: 4rem; padding-bottom: 2rem; position: relative; z-index: 20; } .h5 { font-size: 0.72rem; letter-spacing: 0.1em; text-transform: uppercase; color: rgba(201, 168, 76, 0.55); font-weight: 600; } p { color: rgba(221, 215, 205, 0.5); font-size: 0.88rem; line-height: 1.75; a { color: rgba(221, 215, 205, 0.75); text-decoration: underline; text-underline-offset: 2px; transition: color 200ms ease; &:hover { color: #ddd7cd; } } }}// ── Footer bar (sosumi) ──.footer-bar { background: #000; border-top: 1px solid rgba(255, 255, 255, 0.05); background: #070605; border-top: 1px solid rgba(107, 158, 120, 0.05); padding: 1rem 0; color: rgba(255, 255, 255, 0.35); font-size: 0.85rem; letter-spacing: 0.02em; color: rgba(221, 215, 205, 0.3); font-size: 0.78rem; letter-spacing: 0.03em; position: relative; z-index: 20; small { color: rgba(255, 255, 255, 0.35); color: rgba(221, 215, 205, 0.3); } &-link { color: rgba(255, 255, 255, 0.35); transition: color 0.2s ease-in-out; color: rgba(221, 215, 205, 0.3); transition: color 200ms ease; &:hover { color: #fff; color: #ddd7cd; } }}// ── Footer animated plane ──.footer-plane { position: relative; width: 100%;
@@ -68,8 +153,8 @@ footer { z-index: 11; background: radial-gradient( ellipse at 50% 50%, rgba(10, 14, 15, 0) 0, rgb(0, 0, 0) 80% rgba(9, 8, 6, 0) 0, rgb(9, 8, 6) 80% ); }
@@ -89,13 +174,166 @@ footer { height: 200%; background-image: linear-gradient( 90deg, hsla(0, 0%, 50%, 0.5) 1px, rgba(107, 158, 120, 0.18) 1px, transparent 0 ), linear-gradient(180deg, hsla(0, 0%, 50%, 0.5) 1px, transparent 0); linear-gradient(180deg, rgba(107, 158, 120, 0.18) 1px, transparent 0); background-size: 40px 30px; background-repeat: repeat; transform-origin: 100% 0 0; animation: footer-plane-keyframes 17s linear infinite; }}// ── Section labels — terminal-prompt style with amber ──.section-label { font-size: 0.72rem; font-weight: 600; letter-spacing: 0.1em; text-transform: uppercase; color: #c9a84c; &::before { content: '> '; opacity: 0.5; }}// ── Card hover effects ──.card { transition: border-color 250ms ease, box-shadow 250ms ease; &:hover { border-color: rgba(107, 158, 120, 0.18); box-shadow: 0 0 20px rgba(107, 158, 120, 0.04); }}// ── Search form — terminal style ──.search-form { .search-icon { position: absolute; left: 0.85rem; top: 50%; transform: translateY(-50%); color: #665f56; pointer-events: none; z-index: 2; } .search-input { padding-left: 2.5rem; font-size: 0.85rem; letter-spacing: 0.02em; border: 1px solid rgba(107, 158, 120, 0.15); background: rgba(19, 18, 14, 0.6); color: #ddd7cd; transition: border-color 200ms ease, box-shadow 200ms ease; &::placeholder { color: #665f56; font-style: normal; } &:focus { border-color: rgba(107, 158, 120, 0.4); box-shadow: 0 0 0 2px rgba(107, 158, 120, 0.1); background: #13120e; } }}// ── Live search results ──#id_search_results .list-group-item { color: #ddd7cd; border-color: rgba(107, 158, 120, 0.08); .fw-bold { color: #ddd7cd; } &.active { background: rgba(107, 158, 120, 0.12); border-color: rgba(107, 158, 120, 0.2); color: #ddd7cd; } &:hover { background: rgba(107, 158, 120, 0.08); }}// ── Blog index filter pills ──.filter-label { font-size: 0.68rem; font-weight: 600; letter-spacing: 0.1em; text-transform: uppercase; color: #c9a84c; opacity: 0.7;}.filter-divider { width: 1px; height: 1.2rem; background: rgba(221, 215, 205, 0.1); margin: 0 0.25rem;}.btn-filter { background: rgba(221, 215, 205, 0.04); color: #a09890; border: 1px solid rgba(221, 215, 205, 0.08); font-size: 0.75rem; line-height: 1; padding: 0.4rem 0.75rem; display: inline-flex; align-items: center; transition: all 200ms ease; &:hover { background: rgba(107, 158, 120, 0.1); color: #ddd7cd; border-color: rgba(107, 158, 120, 0.2); }}.btn-filter-active { background: rgba(107, 158, 120, 0.15); color: #7db88c; border: 1px solid rgba(107, 158, 120, 0.3); font-size: 0.75rem; line-height: 1; padding: 0.4rem 0.75rem; display: inline-flex; align-items: center;}.btn-filter-clear { background: rgba(196, 112, 85, 0.1); color: #c47055; border: 1px solid rgba(196, 112, 85, 0.2); font-size: 0.75rem; line-height: 1; padding: 0.4rem 0.75rem; display: inline-flex; align-items: center; transition: all 200ms ease; &:hover { background: rgba(196, 112, 85, 0.2); color: #d88a70; border-color: rgba(196, 112, 85, 0.35); }}// ── Vertical rule override ──.vr { background-color: rgba(107, 158, 120, 0.12);}
modified
static_src/styles/blocks.scss
@@ -6,11 +6,46 @@.block-rich-text { max-width: 600px; margin: 2rem auto; font-size: 1.2em; font-size: 1.05em; line-height: 1.9; color: #c4bdb2; a { color: #7db88c; text-decoration: underline; text-underline-offset: 3px; text-decoration-color: rgba(125, 184, 140, 0.3); transition: text-decoration-color 200ms ease; &:hover { text-decoration-color: #7db88c; } } blockquote { border-left: 4px solid #dee2e6; padding-left: 1rem; border-left: 2px solid rgba(201, 168, 76, 0.3); padding-left: 1.25rem; color: #a09890; } h1, h2, h3, h4, h5, h6 { color: #ddd7cd; } strong { color: #ddd7cd; } code { background: rgba(107, 158, 120, 0.1); color: #8dc49c; padding: 0.15em 0.4em; border-radius: 0.2rem; font-size: 0.9em; } hr { border-color: rgba(107, 158, 120, 0.08); }}
@@ -26,6 +61,7 @@ height: auto; max-height: 75vh; max-width: 100%; border-radius: 0.25rem;}.block-embed {
modified
static_src/styles/bootstrap.scss
@@ -1,46 +1,132 @@@import "~bootstrap/scss/bootstrap";.bg-blue-700 { background: $blue-700 !important;}// Dark theme variable overrides — set before Bootstrap import// Typography — monospace-first for the TUI/hacker aesthetic$font-family-sans-serif: 'Monaspace Argon', ui-monospace, 'Cascadia Code', Consolas, 'SF Mono', Menlo, monospace;$font-family-monospace: 'Monaspace Argon', ui-monospace, 'Cascadia Code', Consolas, 'SF Mono', Menlo, monospace;$headings-font-family: null;$headings-font-weight: 700;$font-size-base: 0.92rem;$line-height-base: 1.7;$headings-line-height: 1.3;// Core palette — warm earth darks$white: #ede8e0;$gray-100: #ddd7cd;$gray-200: #c4bdb2;$gray-300: #a09890;$gray-400: #847c72;$gray-500: #665f56;$gray-600: #4a443c;$gray-700: #332e28;$gray-800: #211e1a;$gray-900: #161310;$black: #0b0a08;$body-bg: #0e0d0a;$body-color: #ddd7cd;// Accent — muted forest green + warm amber$green: #6b9e78;$green-bright: #7db88c;$green-dim: rgba(107, 158, 120, 0.12);$green-border: rgba(107, 158, 120, 0.2);$amber: #c9a84c;$amber-dim: rgba(201, 168, 76, 0.12);// Theme colors$primary: $green;$secondary: #4a443c;$success: #6b9e78;$info: #7eaab8;$warning: $amber;$danger: #c47055;$light: #211e1a;$dark: #0e0d0a;// Links$link-color: $green-bright;$link-hover-color: #95cca2;$link-decoration: none;$link-hover-decoration: underline;// Borders$border-color: rgba(221, 215, 205, 0.07);$border-radius: 0.25rem;$border-radius-sm: 0.2rem;$border-radius-lg: 0.375rem;// Cards$card-bg: #13120e;$card-border-color: rgba(107, 158, 120, 0.08);$card-color: #ddd7cd;$card-cap-bg: rgba(221, 215, 205, 0.03);// Inputs$input-bg: #13120e;$input-color: #ddd7cd;$input-border-color: rgba(107, 158, 120, 0.18);$input-focus-bg: #18160f;$input-focus-color: #ddd7cd;$input-focus-border-color: $green;$input-focus-box-shadow: 0 0 0 2px rgba(107, 158, 120, 0.15);$input-placeholder-color: #665f56;// Navbar$navbar-dark-color: rgba(221, 215, 205, 0.6);$navbar-dark-hover-color: #ddd7cd;$navbar-dark-active-color: #ddd7cd;// List groups$list-group-bg: #13120e;$list-group-border-color: rgba(107, 158, 120, 0.08);$list-group-hover-bg: #1a1812;$list-group-action-color: #a09890;$list-group-action-hover-color: #ddd7cd;$list-group-action-active-bg: #211e1a;$list-group-action-active-color: #ddd7cd;$list-group-active-bg: $green-dim;$list-group-active-border-color: rgba(107, 158, 120, 0.25);$list-group-active-color: $green-bright;// Breadcrumbs$breadcrumb-active-color: #ddd7cd;$breadcrumb-divider-color: #665f56;// Dropdowns$dropdown-bg: #13120e;$dropdown-border-color: rgba(107, 158, 120, 0.1);$dropdown-color: #ddd7cd;$dropdown-link-color: #a09890;$dropdown-link-hover-color: #ddd7cd;$dropdown-link-hover-bg: #1a1812;// Close button$btn-close-color: #ddd7cd;.bg-blue-800 { background: $blue-800 !important;}.bg-black { background: $black !important;}@import "~bootstrap/scss/bootstrap";.bg-gray-925 { background: shade-color($gray-900, 30%) !important;}.bg-yellow-100 { background: $yellow-100 !important;}// ── Custom utilities ──.border-yellow-500 { border-color: $yellow-500 !important;.bg-surface { background: #13120e !important;}.text-orange-300 { color: $orange-300 !important;.bg-deep { background: #090806 !important;}.text-gray-300 { color: $gray-300 !important;.text-muted { color: #9e968a !important;}.link-footer { color: $gray-500; color: $gray-400; text-decoration: none; transition: color 100ms ease-in-out; transition: color 200ms ease; &:hover { color: $white; text-decoration: underline; }}
@@ -50,22 +136,78 @@ white-space: nowrap; .breadcrumb-item { font-size: 0.9em; font-size: 0.82em; white-space: nowrap; display: inline-block; a { color: $gray-500; color: $gray-400; text-decoration: none; transition: color .15s ease-in-out; transition: color 150ms ease; &:hover { color: $gray-200; color: $gray-100; } } &.active { color: $white; color: $gray-200; } }}// Tag pill styling — forest green.btn-tag { background: $green-dim; color: $green-bright; border: 1px solid $green-border; font-size: 0.75rem; font-weight: 500; letter-spacing: 0.03em; line-height: 1; padding: 0.35rem 0.65rem; display: inline-flex; align-items: center; transition: all 200ms ease; &:hover { background: rgba(107, 158, 120, 0.2); color: #95cca2; border-color: rgba(107, 158, 120, 0.35); box-shadow: 0 0 8px rgba(107, 158, 120, 0.12); }}// Alert overrides for dark.alert-info { background: rgba(126, 170, 184, 0.08); border-color: rgba(126, 170, 184, 0.2); color: #9ec5d2;}.alert-warning { background: $amber-dim; border-color: rgba(201, 168, 76, 0.2); color: #ddc06a;}// List group header — amber accent for "old knowledge" feel.list-group-header { background: $amber-dim !important; color: $amber !important; font-weight: 600; font-size: 0.72rem; letter-spacing: 0.08em; text-transform: uppercase;}.list-group-footer { background: rgba(221, 215, 205, 0.02) !important; color: $gray-400 !important; transition: all 200ms ease; &:hover { background: rgba(221, 215, 205, 0.04) !important; color: $gray-200 !important; }}
modified
static_src/styles/code.scss
@@ -3,6 +3,7 @@ height: auto; max-width: 800px; border-radius: 0.25rem; border: 1px solid rgba(107, 158, 120, 0.08);}.CodeMirror-line {
@@ -14,9 +15,9 @@}.CodeMirror-linenumber.CodeMirror-gutter-elt { color: #8d8d8d; color: #4a443c;}.cm-s-material-darker .cm-comment { color: #8d8d8d; color: #665f56;}
modified
static_src/styles/dark.scss
@@ -1,42 +1,2 @@#prefers-color-scheme { width: 150px; background-color: #171a1d; border-color: #6b6b6b; color: white; padding-left: 40px;}.prefers-color-scheme-icon { position: absolute; color: white; margin: 8px;}main.dark { background: #e7e7e7; filter: invert(1); img { filter: invert(1); } .reverse-invert { filter: invert(1); } .card { background: none; } .list-group { filter: invert(1); } .text-muted { color: #3f3f3f !important; } .block-code { filter: invert(1); }}// Dark mode is now the permanent theme — no toggle needed.// All dark styling is handled via Bootstrap SCSS variable overrides in bootstrap.scss.
modified
static_src/styles/home.scss
@@ -0,0 +1 @@// Home page styles
modified
templates/404.html
@@ -12,8 +12,8 @@<div class="container"> <div class="row"> <div class="col text-center py-5"> <h1 class="display-1">404</h1> <p>That means the page you are looking for doesn't exist.</p> <h1 class="display-1 fw-bold">404</h1> <p class="text-muted">That means the page you are looking for doesn't exist.</p> </div> </div></div>
modified
templates/base.html
@@ -18,14 +18,14 @@<body> {% block nav %} <nav class="navbar navbar-dark navbar-expand-md bg-blue-800 d-print-none"> <nav class="navbar navbar-dark navbar-expand-md bg-deep d-print-none"> <div class="container"> <a href="/" class="navbar-brand logo shadow" aria-label="Isaac Bythewood"></a> <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation"> <button class="navbar-toggler border-0" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation"> <span class="navbar-toggler-icon"></span> </button> <div class="collapse navbar-collapse" id="navbarSupportedContent"> <ul class="navbar-nav"> <ul class="navbar-nav ms-3"> {% for nav_item in nav_items %} <li class="nav-item"> <a class="nav-link {% if active_tag and active_tag.slug == nav_item.slug %}active{% endif %}" href="{{ nav_item.url }}">
@@ -34,38 +34,16 @@ </li> {% endfor %} </ul> <div class="ms-0 ms-md-auto"> <svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" fill="currentColor" class="prefers-color-scheme-icon system" viewBox="0 0 16 16"> <path d="M13.5 3a.5.5 0 0 1 .5.5V11H2V3.5a.5.5 0 0 1 .5-.5h11zm-11-1A1.5 1.5 0 0 0 1 3.5V12h14V3.5A1.5 1.5 0 0 0 13.5 2h-11zM0 12.5h16a1.5 1.5 0 0 1-1.5 1.5h-13A1.5 1.5 0 0 1 0 12.5z"/> </svg> <svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" fill="currentColor" class="prefers-color-scheme-icon light d-none" viewBox="0 0 16 16"> <path d="M8 11a3 3 0 1 1 0-6 3 3 0 0 1 0 6zm0 1a4 4 0 1 0 0-8 4 4 0 0 0 0 8zM8 0a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2A.5.5 0 0 1 8 0zm0 13a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2A.5.5 0 0 1 8 13zm8-5a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1 0-1h2a.5.5 0 0 1 .5.5zM3 8a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1 0-1h2A.5.5 0 0 1 3 8zm10.657-5.657a.5.5 0 0 1 0 .707l-1.414 1.415a.5.5 0 1 1-.707-.708l1.414-1.414a.5.5 0 0 1 .707 0zm-9.193 9.193a.5.5 0 0 1 0 .707L3.05 13.657a.5.5 0 0 1-.707-.707l1.414-1.414a.5.5 0 0 1 .707 0zm9.193 2.121a.5.5 0 0 1-.707 0l-1.414-1.414a.5.5 0 0 1 .707-.707l1.414 1.414a.5.5 0 0 1 0 .707zM4.464 4.465a.5.5 0 0 1-.707 0L2.343 3.05a.5.5 0 1 1 .707-.707l1.414 1.414a.5.5 0 0 1 0 .708z"/> </svg> <svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" fill="currentColor" class="prefers-color-scheme-icon dark d-none" viewBox="0 0 16 16"> <path d="M6 .278a.768.768 0 0 1 .08.858 7.208 7.208 0 0 0-.878 3.46c0 4.021 3.278 7.277 7.318 7.277.527 0 1.04-.055 1.533-.16a.787.787 0 0 1 .81.316.733.733 0 0 1-.031.893A8.349 8.349 0 0 1 8.344 16C3.734 16 0 12.286 0 7.71 0 4.266 2.114 1.312 5.124.06A.752.752 0 0 1 6 .278zM4.858 1.311A7.269 7.269 0 0 0 1.025 7.71c0 4.02 3.279 7.276 7.319 7.276a7.316 7.316 0 0 0 5.205-2.162c-.337.042-.68.063-1.029.063-4.61 0-8.343-3.714-8.343-8.29 0-1.167.242-2.278.681-3.286z"/> </svg> <select class="form-select" id="prefers-color-scheme" aria-label="Select color scheme"> <option value="system" selected> System </option> <option value="light"> Light </option> <option value="dark"> Dark </option> </select> </div> </div> </div> </nav> {% endblock %} {% block breadcrumb_wrapper %} <div class="bg-blue-700 py-2 overflow-auto d-print-none"> <div class="breadcrumb-bar py-2 overflow-auto d-print-none"> <div class="container"> {% block breadcrumbs %} <nav style="--bs-breadcrumb-divider: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8'%3E%3Cpath d='M2.5 0L1 1.5 3.5 4 1 6.5 2.5 8l4-4-4-4z' fill='%236c757d'/%3E%3C/svg%3E");" aria-label="breadcrumb"> <nav style="--bs-breadcrumb-divider: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8'%3E%3Cpath d='M2.5 0L1 1.5 3.5 4 1 6.5 2.5 8l4-4-4-4z' fill='%235a5a72'/%3E%3C/svg%3E");" aria-label="breadcrumb"> <ol class="breadcrumb mb-0"> {% for crumb in breadcrumbs %} <li class="breadcrumb-item"><a href="{{ crumb.url }}">{{ crumb.title }}</a></li>
@@ -85,12 +63,12 @@ </main> {% block footer %} <footer class="bg-black text-light d-print-none"> <footer class="bg-deep text-light d-print-none"> <div class="container"> <div class="row links"> <div class="col-12 col-lg-6 mb-4 mb-lg-0"> <div class="h5 font-monospace mb-3">Blog</div> <p>A blog by <a href="https://isaacbythewood.com/" class="text-light">Isaac Bythewood</a>, <p>A blog by <a href="https://isaacbythewood.com/">Isaac Bythewood</a>, a Senior Solutions Architect at Craftmaster Furniture located in Elkin, NC. Mostly webdev musings and the occasional deep dive into infrastructure, security, and tooling. No warranty, technical
@@ -100,19 +78,21 @@ <div class="col-6 col-lg-2 offset-lg-2"> <div class="h5 font-monospace mb-3">Projects</div> <ul class="list-unstyled"> <li class="mb-2"><a href="https://github.com/overshard/taproot" class="link-footer">Taproot</a></li> <li class="mb-2"><a href="https://github.com/overshard/darkfurrow.com" class="link-footer">Dark Furrow</a></li> <li class="mb-2"><a href="https://github.com/overshard/timelite" class="link-footer">Timelite</a></li> <li class="mb-2"><a href="https://github.com/overshard/status" class="link-footer">Status</a></li> <li class="mb-2"><a href="https://github.com/overshard/analytics" class="link-footer">Analytics</a></li> <li class="mb-2"><a href="https://github.com/overshard/taproot" class="link-footer" target="_blank">Taproot</a></li> <li class="mb-2"><a href="https://github.com/overshard/darkfurrow.com" class="link-footer" target="_blank">Dark Furrow</a></li> <li class="mb-2"><a href="https://github.com/overshard/timelite" class="link-footer" target="_blank">Timelite</a></li> <li class="mb-2"><a href="https://github.com/overshard/status" class="link-footer" target="_blank">Status</a></li> <li class="mb-2"><a href="https://github.com/overshard/analytics" class="link-footer" target="_blank">Analytics</a></li> </ul> </div> <div class="col-6 col-lg-2"> <div class="h5 font-monospace mb-3">Links</div> <ul class="list-unstyled"> <li class="mb-2"><a href="https://isaacbythewood.com/" class="link-footer">Portfolio</a></li> <li class="mb-2"><a href="https://analytics.bythewood.me/properties/0d379e18-9ea7-4228-a8bf-82369c25ab84/" class="link-footer">Blog Analytics</a></li> <li class="mb-2"><a href="https://status.bythewood.me/properties/dbc133c9-ef2a-40a9-a3f0-a26c64bede0a/" class="link-footer">Blog Status</a></li> <li class="mb-2"><a href="https://isaacbythewood.com/" class="link-footer" target="_blank">Portfolio</a></li> <li class="mb-2"><a href="https://github.com/overshard" class="link-footer" target="_blank">GitHub</a></li> <li class="mb-2"><a href="https://www.linkedin.com/in/ibythewood/" class="link-footer" target="_blank">LinkedIn</a></li> <li class="mb-2"><a href="https://analytics.bythewood.me/properties/0d379e18-9ea7-4228-a8bf-82369c25ab84/" class="link-footer" target="_blank">Blog Analytics</a></li> <li class="mb-2"><a href="https://status.bythewood.me/properties/dbc133c9-ef2a-40a9-a3f0-a26c64bede0a/" class="link-footer" target="_blank">Blog Status</a></li> </ul> </div> </div>
modified
templates/blog_index.html
@@ -26,10 +26,8 @@ Currently filtered by year {{ active_year }}.{% block extra_breadcrumbs %}{% if active_tag %}<li class="breadcrumb-item"><a href="{{ url_for('blog_index') }}">Blog</a></li><li class="breadcrumb-item active">{{ active_tag.name|title }}</li>{% elif active_year %}<li class="breadcrumb-item"><a href="{{ url_for('blog_index') }}">Blog</a></li><li class="breadcrumb-item active">{{ active_year }}</li>{% else %}<li class="breadcrumb-item active">{{ page.title }}</li>
@@ -43,9 +41,9 @@ Currently filtered by year {{ active_year }}. <div class="col-sm-6"> <h1 class="mb-3 fw-bolder"> {% if active_tag %} <div class="text-muted fs-5">{{ active_tag.name|title }}</div> <div class="section-label fs-6 mb-1">{{ active_tag.name|title }}</div> {% elif active_year %} <div class="text-muted fs-5">{{ active_year }}</div> <div class="section-label fs-6 mb-1">{{ active_year }}</div> {% endif %} {{ page.title }} </h1>
@@ -56,6 +54,28 @@ Currently filtered by year {{ active_year }}. </div></div><div class="container mt-3"> <div class="d-flex flex-wrap align-items-center gap-2"> <span class="filter-label">tags</span> {% for tag in tags %} <a href="{{ url_for('blog_tag', tag=tag.slug) }}" class="btn btn-sm rounded-pill {% if active_tag and active_tag.slug == tag.slug %}btn-filter-active{% else %}btn-filter{% endif %}"> {{ tag.name|title }} </a> {% endfor %} <span class="filter-divider"></span> <span class="filter-label">year</span> {% for year in years %} <a href="{{ url_for('blog_year', year=year) }}" class="btn btn-sm rounded-pill {% if active_year == year %}btn-filter-active{% else %}btn-filter{% endif %}"> {{ year }} </a> {% endfor %} {% if active_tag or active_year %} <span class="filter-divider"></span> <a href="{{ url_for('blog_index') }}" class="btn btn-sm rounded-pill btn-filter-clear">clear</a> {% endif %} </div></div>{% set blog_post = blog_posts[0] if blog_posts else None %}{% if blog_post %}{% include "includes/blog_post_latest.html" %}
@@ -63,53 +83,33 @@ Currently filtered by year {{ active_year }}.<div class="container mt-5"> <div class="row"> <div class="col-8"> <div class="row"> {% for blog_post in blog_posts %} {% if not loop.first %} <div class="col-sm-12 col-md-6 mb-5"> {% include "includes/blog_post_card.html" %} </div> {% endif %} {% endfor %} </div> {% if extra_posts %} <div class="row mb-3"> <div class="col"> <div class="alert alert-info d-flex align-items-center row g-1 shadow-sm"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" class="bi bi-info-circle-fill col-2 col-md-1" viewBox="0 0 16 16"> <path d="M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16zm.93-9.412-1 4.705c-.07.34.029.533.304.533.194 0 .487-.07.686-.246l-.088.416c-.287.346-.92.598-1.465.598-.703 0-1.002-.422-.808-1.319l.738-3.468c.064-.293.006-.399-.287-.47l-.451-.081.082-.381 2.29-.287zM8 5.5a1 1 0 1 1 0-2 1 1 0 0 1 0 2z"/> </svg> <div class="col-10 col-md-11">I don't have any more posts with this filter at the moment but here are some other posts you might like!</div> </div> </div> </div> <div class="row"> {% for blog_post in extra_posts %} <div class="col-12 col-md-6 mb-5"> {% include "includes/blog_post_card.html" %} </div> {% endfor %} </div> {% endif %} {% for blog_post in blog_posts %} {% if not loop.first %} <div class="col-sm-12 col-md-6 col-lg-4 mb-5"> {% include "includes/blog_post_card.html" %} </div> <div class="col-4"> <div class="list-group"> <div class="list-group-item bg-secondary text-white fw-bold">Tags</div> {% for tag in tags %} <a href="{{ url_for('blog_tag', tag=tag.slug) }}" class="list-group-item {% if active_tag and active_tag.slug == tag.slug %}active{% endif %}">{{ tag.name|title }}</a> {% endfor %} <a href="{{ url_for('blog_index') }}" class="list-group-item bg-light">View all</a> </div> <div class="list-group mt-3"> <div class="list-group-item bg-secondary text-white fw-bold">Years</div> {% for year in years %} <a href="{{ url_for('blog_year', year=year) }}" class="list-group-item {% if active_year == year %}active{% endif %}">{{ year }}</a> {% endfor %} <a href="{{ url_for('blog_index') }}" class="list-group-item bg-light">View all</a> {% endif %} {% endfor %} </div> {% if extra_posts %} <div class="row mb-3"> <div class="col"> <div class="alert alert-info d-flex align-items-center row g-1"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" class="bi bi-info-circle-fill col-2 col-md-1" viewBox="0 0 16 16"> <path d="M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16zm.93-9.412-1 4.705c-.07.34.029.533.304.533.194 0 .487-.07.686-.246l-.088.416c-.287.346-.92.598-1.465.598-.703 0-1.002-.422-.808-1.319l.738-3.468c.064-.293.006-.399-.287-.47l-.451-.081.082-.381 2.29-.287zM8 5.5a1 1 0 1 1 0-2 1 1 0 0 1 0 2z"/> </svg> <div class="col-10 col-md-11">I don't have any more posts with this filter at the moment but here are some other posts you might like!</div> </div> </div> </div> <div class="row"> {% for blog_post in extra_posts %} <div class="col-12 col-md-6 col-lg-4 mb-5"> {% include "includes/blog_post_card.html" %} </div> {% endfor %} </div> {% endif %}</div>{% endblock %}
modified
templates/blog_post.html
@@ -13,12 +13,12 @@ <div class="d-flex justify-content-between align-items-center"> <div class="mb-2"> {% for tag in post.tags %} <a href="{{ url_for('blog_tag', tag=tag) }}" class="btn btn-secondary btn-sm rounded-pill py-0"> <a href="{{ url_for('blog_tag', tag=tag) }}" class="btn btn-tag btn-sm rounded-pill"> {{ tag|title }} </a> {% endfor %} </div> <div class="mb-2 d-flex align-items-center"> <div class="mb-2 d-flex align-items-center text-muted"> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-stopwatch me-2" viewBox="0 0 16 16"> <path d="M8.5 5.6a.5.5 0 1 0-1 0v2.9h-3a.5.5 0 0 0 0 1H8a.5.5 0 0 0 .5-.5V5.6z"/> <path d="M6.5 1A.5.5 0 0 1 7 .5h2a.5.5 0 0 1 0 1v.57c1.36.196 2.594.78 3.584 1.64a.715.715 0 0 1 .012-.013l.354-.354-.354-.353a.5.5 0 0 1 .707-.708l1.414 1.415a.5.5 0 1 1-.707.707l-.353-.354-.354.354a.512.512 0 0 1-.013.012A7 7 0 1 1 7 2.071V1.5a.5.5 0 0 1-.5-.5zM8 3a6 6 0 1 0 .001 12A6 6 0 0 0 8 3z"/>
@@ -27,10 +27,15 @@ </div> </div> <h1 class="fw-bolder">{{ post.title }}</h1> <p class="h5 text-muted">{{ post.description }}</p> <p class="h5 text-muted" style="font-weight: 400;">{{ post.description }}</p> </div> <div class="d-none d-md-block col-md-4 col-lg-6 text-end"> <a href="{{ url_for('blog_post_pdf', slug=post.slug) }}" target="_blank" class="btn btn-link btn-sm d-print-none" aria-label="Download PDF"> <div class="d-none d-md-block col-md-4 col-lg-6 text-end d-print-none"> <a href="{{ url_for('blog_post_md', slug=post.slug) }}" target="_blank" class="btn btn-link btn-sm" aria-label="Download Markdown"> <svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" fill="currentColor" class="bi bi-filetype-md" viewBox="0 0 16 16"> <path fill-rule="evenodd" d="M14 4.5V14a2 2 0 0 1-2 2h-1v-1h1a1 1 0 0 0 1-1V4.5h-2A1.5 1.5 0 0 1 9.5 3V1H4a1 1 0 0 0-1 1v9H2V2a2 2 0 0 1 2-2h5.5zM.706 13.189v2.66H0V11.85h.806l1.14 2.596h.026l1.14-2.596h.8v3.999h-.716v-2.66h-.038l-.946 2.159h-.516l-.952-2.16H.706Zm3.919 2.66V11.85h1.459c.406 0 .741.078 1.005.234.264.157.46.382.589.68.13.297.196.655.196 1.075 0 .422-.066.784-.196 1.084a1.45 1.45 0 0 1-.595.689c-.264.158-.597.236-.999.236H4.625Zm.791-3.354v2.707h.563c.186 0 .347-.028.483-.082a.8.8 0 0 0 .334-.252c.088-.114.153-.254.196-.422a2.3 2.3 0 0 0 .068-.592c0-.3-.04-.552-.118-.753a.89.89 0 0 0-.354-.454c-.159-.102-.361-.152-.61-.152h-.562Z"/> </svg> </a> <a href="{{ url_for('blog_post_pdf', slug=post.slug) }}" target="_blank" class="btn btn-link btn-sm" aria-label="Download PDF"> <svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" fill="currentColor" class="bi bi-filetype-pdf" viewBox="0 0 16 16"> <path fill-rule="evenodd" d="M14 4.5V14a2 2 0 0 1-2 2h-1v-1h1a1 1 0 0 0 1-1V4.5h-2A1.5 1.5 0 0 1 9.5 3V1H4a1 1 0 0 0-1 1v9H2V2a2 2 0 0 1 2-2h5.5L14 4.5ZM1.6 11.85H0v3.999h.791v-1.342h.803c.287 0 .531-.057.732-.173.203-.117.358-.275.463-.474a1.42 1.42 0 0 0 .161-.677c0-.25-.053-.476-.158-.677a1.176 1.176 0 0 0-.46-.477c-.2-.12-.443-.179-.732-.179Zm.545 1.333a.795.795 0 0 1-.085.38.574.574 0 0 1-.238.241.794.794 0 0 1-.375.082H.788V12.48h.66c.218 0 .389.06.512.181.123.122.185.296.185.522Zm1.217-1.333v3.999h1.46c.401 0 .734-.08.998-.237a1.45 1.45 0 0 0 .595-.689c.13-.3.196-.662.196-1.084 0-.42-.065-.778-.196-1.075a1.426 1.426 0 0 0-.589-.68c-.264-.156-.599-.234-1.005-.234H3.362Zm.791.645h.563c.248 0 .45.05.609.152a.89.89 0 0 1 .354.454c.079.201.118.452.118.753a2.3 2.3 0 0 1-.068.592 1.14 1.14 0 0 1-.196.422.8.8 0 0 1-.334.252 1.298 1.298 0 0 1-.483.082h-.563v-2.707Zm3.743 1.763v1.591h-.79V11.85h2.548v.653H7.896v1.117h1.606v.638H7.896Z"/> </svg>
@@ -50,8 +55,8 @@{% endif %}<div class="container"> <div class="border-bottom border-3 py-3"> <div class="text-muted small mb-2">Author</div> <div class="py-3" style="border-bottom: 1px solid rgba(255,255,255,0.06);"> <div class="section-label mb-2">Author</div> <div class="d-flex justify-content-between"> <div class="d-flex align-items-center"> <img class="rounded-circle me-3"
@@ -74,7 +79,7 @@<div class="container mt-5"> <article> {{ post.body_html|safe }} <hr class="mt-5" style="height: 3px; max-width: 600px; margin-left: auto; margin-right: auto;"> <hr class="mt-5" style="height: 2px; max-width: 600px; margin-left: auto; margin-right: auto; border-color: rgba(255,255,255,0.06);"> </article></div>
@@ -82,10 +87,10 @@<div class="container mt-5 d-print-none"> <div class="row"> <div class="col"> <div class="text-muted"> <div class="section-label mb-1"> Related posts </div> <p>Some posts in similar tags to this one.</p> <p class="text-muted">Some posts in similar tags to this one.</p> </div> </div> <div class="row d-flex flex-nowrap overflow-auto w-100">
modified
templates/home.html
@@ -27,10 +27,10 @@<div class="container mt-5"> <div class="row"> <div class="col"> <div class="text-muted"> <div class="section-label mb-1"> Random posts </div> <p>If you don't know what you're looking for check out some of my older posts.</p> <p class="text-muted">If you don't know what you're looking for check out some of my older posts.</p> </div> </div> <div class="row d-flex flex-nowrap overflow-auto w-100">
modified
templates/includes/blog_post_card.html
@@ -1,27 +1,27 @@<div class="card rounded-0 border-0 border-bottom h-100"> <div class="mb-3"><div class="card rounded border-0 h-100 bg-surface"> <div class="mb-3 pt-3 px-3"> {% for tag in blog_post.tags %} <a href="{{ url_for('blog_tag', tag=tag) }}" class="btn btn-light btn-sm rounded-pill fw-bold py-0"> <a href="{{ url_for('blog_tag', tag=tag) }}" class="btn btn-tag btn-sm rounded-pill"> {{ tag|title }} </a> {% endfor %} </div> {% if blog_post.cover_image %} <a href="{{ url_for('blog_post', slug=blog_post.slug) }}"> <img src="{{ url_for('content_images', filename=blog_post.cover_image) }}" class="card-img-top rounded img-fluid" alt="{{ blog_post.title }}"> <img src="{{ url_for('content_images', filename=blog_post.cover_image) }}" class="card-img-top rounded img-fluid px-3" alt="{{ blog_post.title }}"> </a> {% endif %} <div class="card-body d-flex flex-column"> <a href="{{ url_for('blog_post', slug=blog_post.slug) }}" class="text-decoration-none text-dark"> <div class="h5 card-title">{{ blog_post.title }}</div> <a href="{{ url_for('blog_post', slug=blog_post.slug) }}" class="text-decoration-none" style="color: #f0f0f5;"> <div class="h5 card-title fw-bold">{{ blog_post.title }}</div> </a> <div class="card-text text-muted flex-grow-1">{{ blog_post.description }}</div> <div class="card-text flex-grow-1" style="color: #a09890;">{{ blog_post.description }}</div> <div class="row g-0 mt-3"> <div class="col-12 col-xl-8 d-flex align-items-center"> <img class="rounded-circle me-2" src="{{ url_for('content_images', filename='avatar.webp') }}" alt="Isaac Bythewood" width="25" height="25"> <strong class="me-2">Isaac Bythewood</strong> <strong class="me-2" style="font-size: 0.9em;">Isaac Bythewood</strong> </div> <div class="col-12 col-xl-4 d-flex justify-content-start justify-content-xl-end text-muted"> <div class="col-12 col-xl-4 d-flex justify-content-start justify-content-xl-end text-muted" style="font-size: 0.85em;"> {{ blog_post.date }} </div> </div>
modified
templates/includes/blog_post_latest.html
@@ -1,22 +1,22 @@<div class="container mt-5"> <div class="row"> <div class="col"> <div class="text-muted">Latest post</div> <div class="section-label mb-2">Latest post</div> </div> </div> <div class="row"> <div class="col-12 col-md-6 col-lg-5 pb-5 border-bottom"> <a href="{{ url_for('blog_post', slug=blog_post.slug) }}" class="text-decoration-none text-dark"> <div class="col-12 col-md-6 col-lg-5 pb-5" style="border-bottom: 1px solid rgba(255,255,255,0.06);"> <a href="{{ url_for('blog_post', slug=blog_post.slug) }}" class="text-decoration-none" style="color: #f0f0f5;"> <h2 class="fw-bolder">{{ blog_post.title }}</h2> </a> <div class="mb-3"> {% for tag in blog_post.tags %} <a href="{{ url_for('blog_tag', tag=tag) }}" class="btn btn-light btn-sm rounded-pill fw-bold py-0"> <a href="{{ url_for('blog_tag', tag=tag) }}" class="btn btn-tag btn-sm rounded-pill"> {{ tag|title }} </a> {% endfor %} </div> <p class="h5 text-muted mb-3">{{ blog_post.description }}</p> <p class="h5 text-muted mb-3" style="font-weight: 400;">{{ blog_post.description }}</p> <div class="d-flex align-items-center"> <img class="rounded-circle me-3" src="{{ url_for('content_images', filename='avatar.webp') }}" alt="Isaac Bythewood" width="35" height="35"> <strong class="me-2">
@@ -28,7 +28,7 @@ </div> </div> {% if blog_post.cover_image %} <div class="col-12 col-md-6 col-lg-7 pb-5 border-bottom"> <div class="col-12 col-md-6 col-lg-7 pb-5" style="border-bottom: 1px solid rgba(255,255,255,0.06);"> <a href="{{ url_for('blog_post', slug=blog_post.slug) }}"> <img src="{{ url_for('content_images', filename=blog_post.cover_image) }}"
modified
templates/includes/search_form.html
@@ -1,11 +1,6 @@<form method="GET" action="/search/" class="position-relative"> <div class="form-floating"> <input type="text" class="form-control bg-light" id="id_search" name="q" value="{{ q }}" autofocus> <label for="id_search">Search posts</label> </div> <button class="btn btn-success position-absolute top-0 end-0 py-3 px-4 rounded-0 rounded-end border-transparent" type="submit" aria-label="Search"> <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" class="bi bi-search" viewBox="0 0 16 16"> <path d="M11.742 10.344a6.5 6.5 0 1 0-1.397 1.398h-.001c.03.04.062.078.098.115l3.85 3.85a1 1 0 0 0 1.415-1.414l-3.85-3.85a1.007 1.007 0 0 0-.115-.1zM12 6.5a5.5 5.5 0 1 1-11 0 5.5 5.5 0 0 1 11 0z"/> </svg> </button><form method="GET" action="/search/" class="position-relative search-form"> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="search-icon" viewBox="0 0 16 16"> <path d="M11.742 10.344a6.5 6.5 0 1 0-1.397 1.398h-.001c.03.04.062.078.098.115l3.85 3.85a1 1 0 0 0 1.415-1.414l-3.85-3.85a1.007 1.007 0 0 0-.115-.1zM12 6.5a5.5 5.5 0 1 1-11 0 5.5 5.5 0 0 1 11 0z"/> </svg> <input type="text" class="form-control search-input" id="id_search" name="q" value="{{ q }}" placeholder="search posts..." autofocus></form>
modified
templates/search.html
@@ -31,7 +31,7 @@ {% endfor %} {% elif q %} <div class="col"> <div class="alert alert-warning d-flex align-items-center row g-1 shadow-sm"> <div class="alert alert-warning d-flex align-items-center row g-1"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" class="bi bi-exclamation-circle-fill col-2 col-md-1" viewBox="0 0 16 16"> <path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0zM8 4a.905.905 0 0 0-.9.995l.35 3.507a.552.552 0 0 0 1.1 0l.35-3.507A.905.905 0 0 0 8 4zm.002 6a1 1 0 1 0 0 2 1 1 0 0 0 0-2z"/> </svg>
@@ -40,7 +40,7 @@ </div> {% else %} <div class="col"> <div class="alert alert-info d-flex align-items-center row g-1 shadow-sm"> <div class="alert alert-info d-flex align-items-center row g-1"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" class="bi bi-info-circle-fill col-2 col-md-1" viewBox="0 0 16 16"> <path d="M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16zm.93-9.412-1 4.705c-.07.34.029.533.304.533.194 0 .487-.07.686-.246l-.088.416c-.287.346-.92.598-1.465.598-.703 0-1.002-.422-.808-1.319l.738-3.468c.064-.293.006-.399-.287-.47l-.451-.081.082-.381 2.29-.287zM8 5.5a1 1 0 1 1 0-2 1 1 0 0 1 0 2z"/> </svg>
@@ -53,10 +53,10 @@ <div class="container mt-5 d-print-none"> <div class="row"> <div class="col"> <div class="text-muted"> <div class="section-label mb-1"> Random Posts </div> <p>If you don't know what to search for check out some of these posts.</p> <p class="text-muted">If you don't know what to search for check out some of these posts.</p> </div> </div> <div class="row">
modified
webpack.config.js
@@ -35,6 +35,13 @@ module.exports = { test: /\.scss$/, use: [MiniCssExtractPlugin.loader, "css-loader", "sass-loader"], }, { test: /\.(woff|woff2|eot|ttf|otf)$/, type: "asset/resource", generator: { filename: "fonts/[name][ext]", }, }, { test: /\.(png|jpg|gif|svg|webp)$/, use: [
@@ -7,6 +7,11 @@ resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz#1d572bfbbe14b7704e0ba0f39b74815b84870d70" integrity sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw=="@fontsource/monaspace-argon@^5.2.5": version "5.2.5" resolved "https://registry.yarnpkg.com/@fontsource/monaspace-argon/-/monaspace-argon-5.2.5.tgz#bdfb941d971501a6b8cff68168682dffdf626dbf" integrity sha512-EJ+jq1Smm3BB+8RK/gwB1uzjrSKdycZkKm8OZGCYvqJiTChKcGe7b2lmj0PmQbb/+lAEdCBIAcFLlPaWhtRANA=="@jridgewell/gen-mapping@^0.3.0": version "0.3.2" resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz#c1aedc61e853f2bb9f5dfe6d4442d3b565b253b9"
@@ -298,6 +303,11 @@ anymatch@~3.1.2: normalize-path "^3.0.0" picomatch "^2.0.4"balanced-match@^4.0.2: version "4.0.4" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-4.0.4.tgz#bfb10662feed8196a2c62e7c68e17720c274179a" integrity sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==big.js@^5.2.2: version "5.2.2" resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328"
@@ -318,6 +328,13 @@ bootstrap@^5.1.3: resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-5.2.0.tgz#838727fb60f1630db370fe57c63cbcf2962bb3d3" integrity sha512-qlnS9GL6YZE6Wnef46GxGv1UpGGzAwO0aPL1yOjzDIJpeApeMvqV24iL+pjr2kU4dduoBA9fINKWKgMToobx9A==brace-expansion@^5.0.5: version "5.0.5" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-5.0.5.tgz#dcc3a37116b79f3e1b46db994ced5d570e930fdb" integrity sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ== dependencies: balanced-match "^4.0.2"braces@~3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107"
@@ -370,6 +387,21 @@ caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001370: optionalDependencies: fsevents "~2.3.2"chokidar@^3.5.2: version "3.6.0" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b" integrity sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw== dependencies: anymatch "~3.1.2" braces "~3.0.2" glob-parent "~5.1.2" is-binary-path "~2.1.0" is-glob "~4.0.1" normalize-path "~3.0.0" readdirp "~3.6.0" optionalDependencies: fsevents "~2.3.2"chrome-trace-event@^1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz#1015eced4741e15d06664a957dbbf50d041e26ac"
@@ -534,6 +566,13 @@ csso@^4.2.0: dependencies: css-tree "^1.1.2"debug@^4: version "4.4.3" resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.3.tgz#c6ae432d9bd9662582fce08709b038c58e9e3d6a" integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA== dependencies: ms "^2.1.3"dom-serializer@^1.0.1: version "1.4.1" resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-1.4.1.tgz#de5d41b1aea290215dc45a6dae8adcf1d32e2d30"
@@ -697,6 +736,11 @@ graceful-fs@^4.1.2, graceful-fs@^4.2.4, graceful-fs@^4.2.9: resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.10.tgz#147d3a006da4ca3ce14728c7aefc287c367d7a6c" integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==has-flag@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==has-flag@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b"
@@ -714,6 +758,11 @@ icss-utils@^5.0.0, icss-utils@^5.1.0: resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-5.1.0.tgz#c6be6858abd013d768e98366ae47e25d5887b1ae" integrity sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==ignore-by-default@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/ignore-by-default/-/ignore-by-default-1.0.1.tgz#48ca6d72f6c6a3af00a9ad4ae6876be3889e2b09" integrity sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==immutable@^4.0.0: version "4.1.0" resolved "https://registry.yarnpkg.com/immutable/-/immutable-4.1.0.tgz#f795787f0db780183307b9eb2091fcac1f6fafef"
@@ -891,6 +940,18 @@ mini-css-extract-plugin@^2.6.0: dependencies: schema-utils "^4.0.0"minimatch@^10.2.1: version "10.2.5" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-10.2.5.tgz#bd48687a0be38ed2961399105600f832095861d1" integrity sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg== dependencies: brace-expansion "^5.0.5"ms@^2.1.3: version "2.1.3" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==nanoid@^3.3.4: version "3.3.4" resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.4.tgz#730b67e3cd09e2deacf03c027c81c9d9dbc5e8ab"
@@ -906,6 +967,22 @@ node-releases@^2.0.6: resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.6.tgz#8a7088c63a55e493845683ebf3c828d8c51c5503" integrity sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==nodemon@^3.1.14: version "3.1.14" resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-3.1.14.tgz#8487ca379c515301d221ec007f27f24ecafa2b51" integrity sha512-jakjZi93UtB3jHMWsXL68FXSAosbLfY0In5gtKq3niLSkrWznrVBzXFNOEMJUfc9+Ke7SHWoAZsiMkNP3vq6Jw== dependencies: chokidar "^3.5.2" debug "^4" ignore-by-default "^1.0.1" minimatch "^10.2.1" pstree.remy "^1.1.8" semver "^7.5.3" simple-update-notifier "^2.0.0" supports-color "^5.5.0" touch "^3.1.0" undefsafe "^2.0.5"normalize-path@^3.0.0, normalize-path@~3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65"
@@ -1221,6 +1298,11 @@ postcss@^8.4.13, postcss@^8.4.7: picocolors "^1.0.0" source-map-js "^1.0.2"pstree.remy@^1.1.8: version "1.1.8" resolved "https://registry.yarnpkg.com/pstree.remy/-/pstree.remy-1.1.8.tgz#c242224f4a67c21f686839bbdb4ac282b8373d3a" integrity sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==punycode@^2.1.0: version "2.1.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
@@ -1321,6 +1403,11 @@ semver@^7.3.5: dependencies: lru-cache "^6.0.0"semver@^7.5.3: version "7.7.4" resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.4.tgz#28464e36060e991fa7a11d0279d2d3f3b57a7e8a" integrity sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==serialize-javascript@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.0.tgz#efae5d88f45d7924141da8b5c3a7a7e663fefeb8"
@@ -1347,6 +1434,13 @@ shebang-regex@^3.0.0: resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==simple-update-notifier@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz#d70b92bdab7d6d90dfd73931195a30b6e3d7cebb" integrity sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w== dependencies: semver "^7.5.3""source-map-js@>=0.6.2 <2.0.0", source-map-js@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c"
@@ -1378,6 +1472,13 @@ stylehacks@^5.1.0: browserslist "^4.16.6" postcss-selector-parser "^6.0.4"supports-color@^5.5.0: version "5.5.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== dependencies: has-flag "^3.0.0"supports-color@^8.0.0: version "8.1.1" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c"
@@ -1436,6 +1537,16 @@ to-regex-range@^5.0.1: dependencies: is-number "^7.0.0"touch@^3.1.0: version "3.1.1" resolved "https://registry.yarnpkg.com/touch/-/touch-3.1.1.tgz#097a23d7b161476435e5c1344a95c0f75b4a5694" integrity sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==undefsafe@^2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/undefsafe/-/undefsafe-2.0.5.tgz#38733b9327bdcd226db889fb723a6efd162e6e2c" integrity sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==update-browserslist-db@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.5.tgz#be06a5eedd62f107b7c19eb5bcefb194411abf38"