Merge pull request 'develop' (#2) from develop into master
All checks were successful
Build and Deploy / deploy (push) Successful in 8s

Reviewed-on: matthew.binning/www#2
This commit is contained in:
matthew.binning 2026-03-25 07:54:32 -07:00
commit 0ea4c5179c
15 changed files with 111 additions and 967 deletions

View file

@ -0,0 +1,22 @@
name: Build and Deploy
on:
push:
branches:
- master
- develop
schedule:
- cron: '0 2 * * *'
jobs:
deploy:
runs-on: self-hosted
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Build
run: nix shell nixpkgs#mdbook --command ./deploy.sh build
- name: Deploy
run: nix shell nixpkgs#rsync --command ./deploy.sh local

5
.gitignore vendored
View file

@ -1 +1,4 @@
blog/*
blog/
src
content/*
example/*

View file

@ -1,27 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>404 - Page Not Found</title>
<link rel="stylesheet" href="/style.css">
</head>
<body>
<!--#include virtual="/includes/navbar.html" -->
<main class="container">
<section class="hero">
<h1>404 - Page Not Found</h1>
<p>The page you're looking for doesn't exist.</p>
</section>
<section class="content-section">
<h2>What happened?</h2>
<p>The page you requested could not be found. It may have been moved, deleted, or never existed.</p>
<p><a href="/" style="color: #3498db; text-decoration: none; font-weight: bold;">← Go back to homepage</a></p>
</section>
</main>
<!--#include virtual="/includes/footer.html" -->
</body>
</html>

127
blog.html
View file

@ -1,127 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Blog - WWW</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<!--#include virtual="/includes/navbar.html" -->
<main class="container">
<section class="hero">
<h1>Blog</h1>
<p>Stories, adventures, and reflections from life's journey</p>
</section>
<!-- The Copper Chronicle Section -->
<section class="content-section">
<h2>The Copper Chronicle</h2>
<p class="section-description">Adventures and travels with Copper, exploring the beautiful outdoors.</p>
<div class="blog-grid">
<article class="blog-card">
<h3><a href="/blog/copper-chronicle/big-sur.html">Big Sur Adventure</a></h3>
<p class="blog-meta">April 2024</p>
<p>Exploring the stunning coastline and trails of Big Sur with Copper.</p>
</article>
<article class="blog-card">
<h3><a href="/blog/copper-chronicle/dove-hunting.html">Dove Hunting</a></h3>
<p class="blog-meta">September 2024</p>
<p>A day in the field with friends, shotguns, and good company.</p>
</article>
<article class="blog-card">
<h3><a href="/blog/copper-chronicle/shasta-trip.html">Shasta & Dunsmuir</a></h3>
<p class="blog-meta">September 2024</p>
<p>Mountain adventures in Northern California.</p>
</article>
</div>
</section>
<!-- Life & Reflections Section -->
<section class="content-section">
<h2>Life & Reflections</h2>
<p class="section-description">Personal thoughts and journeys on health, faith, and life.</p>
<div class="blog-grid">
<article class="blog-card private">
<h3><a href="/blog/health-journey.html">Health Journey</a></h3>
<p class="blog-meta">Private Article</p>
<p>Reflections on physical and mental wellness.</p>
<span class="private-badge">🔒 Private</span>
</article>
<article class="blog-card private">
<h3><a href="/blog/faith-journey.html">Faith Journey</a></h3>
<p class="blog-meta">Private Article</p>
<p>Personal spiritual reflections and growth.</p>
<span class="private-badge">🔒 Private</span>
</article>
</div>
</section>
<!-- Events & Experiences Section -->
<section class="content-section">
<h2>Events & Experiences</h2>
<p class="section-description">Special moments and memorable experiences.</p>
<div class="blog-grid">
<article class="blog-card private">
<h3><a href="/blog/defcon.html">DefCon</a></h3>
<p class="blog-meta">Private Article</p>
<p>Notes and reflections from DefCon.</p>
<span class="private-badge">🔒 Private</span>
</article>
<article class="blog-card private">
<h3><a href="/blog/oktoberfest.html">Oktoberfest</a></h3>
<p class="blog-meta">Private Article</p>
<p>Celebrating traditions and community.</p>
<span class="private-badge">🔒 Private</span>
</article>
<article class="blog-card">
<h3><a href="/blog/independence-day.html">Independence Day</a></h3>
<p class="blog-meta">July 2024</p>
<p>A celebration of freedom and fireworks.</p>
</article>
</div>
</section>
<!-- Recipes Section -->
<section class="content-section">
<h2>Recipes</h2>
<p class="section-description">Family recipes and culinary experiments.</p>
<div class="blog-grid">
<article class="blog-card">
<h3><a href="/blog/public/recipe-book/st-patricks-day.html">St. Patrick's Day</a></h3>
<p class="blog-meta">Holiday</p>
<p>Traditional Irish recipes - corned beef, lamb stew, soda bread, and more.</p>
</article>
<article class="blog-card">
<h3><a href="/blog/public/recipe-book/christmas.html">Christmas</a></h3>
<p class="blog-meta">Holiday</p>
<p>Holiday favorites including eggnog, gingerbread fruitcake, and meatloaf.</p>
</article>
<article class="blog-card">
<h3><a href="/blog/public/recipe-book/earth-day.html">Earth Day</a></h3>
<p class="blog-meta">Special Event</p>
<p>Lamb stir fry and berry compote from April 2024.</p>
</article>
<article class="blog-card">
<h3><a href="/blog/public/recipe-book/camping.html">Camping & Backpacking</a></h3>
<p class="blog-meta">Outdoor</p>
<p>Trail-ready recipes and energy bars for adventures.</p>
</article>
<article class="blog-card">
<h3><a href="/blog/public/recipe-book/misc.html">Miscellaneous</a></h3>
<p class="blog-meta">Various</p>
<p>Drinks, snacks, sides, and external recipe links.</p>
</article>
<article class="blog-card">
<h3><a href="/blog/public/recipe-book/">Full Recipe Book</a></h3>
<p class="blog-meta">Index</p>
<p>Browse all recipe collections in one place.</p>
</article>
</div>
</section>
</main>
<!--#include virtual="/includes/footer.html" -->
</body>
</html>

19
book.toml Normal file
View file

@ -0,0 +1,19 @@
[book]
title = "The Bin"
authors = ["Matthew Binning"]
language = "en"
[build]
build-dir = "blog"
[output.html]
default-theme = "rust"
smart-punctuation = true
git-repository-url = "https://forgejo.binning.net/matthew.binning/www"
edit-url-template = "https://forgejo.binning.net/matthew.binning/www/src/branch/main/{path}"
no-section-label = true
fold.enable = true
fold.level = 0
[output.html.search]
enable = true

View file

@ -1,45 +0,0 @@
{ config, pkgs, lib, ... }:
{
# Common configuration shared between staging and prod
# This file contains nginx settings that are identical across environments
# Import this in both staging.nginx.nix and prod.nginx.nix
services.nginx = {
# Recommended settings
recommendedGzipSettings = true;
recommendedOptimisation = true;
recommendedProxySettings = true;
};
# Common location configurations that can be reused
# These are defined as library functions
lib.nginxLocations = {
# Standard root location with SSI support
rootLocation = {
index = "index.html";
tryFiles = "$uri $uri/ =404";
extraConfig = ''
# Enable Server Side Includes for navbar/footer includes
ssi on;
'';
};
# Private blog articles location with HTTP basic authentication
privateLocation = {
extraConfig = ''
auth_basic "Private Articles";
auth_basic_user_file /srv/nginx/.htpasswd;
# Enable Server Side Includes
ssi on;
'';
};
# Common extraConfig for custom 404
custom404 = ''
error_page 404 /404.html;
'';
};
}

View file

@ -2,83 +2,75 @@
set -e
# Common files to deploy (shared between staging and prod)
DEPLOY_FILES="blog includes index.html blog.html resume.html style.css 404.html"
# Usage information
usage() {
printf "Usage: %s [staging|prod]\n\n staging - Deploy to local staging environment (/srv/www/stage.binning.net)\n prod - Deploy to production server via SSH (www.binning.net)\n\nExample:\n %s staging\n %s prod\n" "$0" "$0" "$0"
printf "Usage: %s [build|staging|prod|local]\n\n build - Build the blog with mdbook\n staging - Deploy to local staging environment (/srv/www/stage.binning.net)\n prod - Deploy to production server via SSH (www.binning.net)\n local - Deploy directly to /srv/www/binning.net (used by Forgejo CI runner)\n\nExample:\n %s build\n %s staging\n %s prod\n %s local\n" "$0" "$0" "$0" "$0" "$0"
exit 1
}
# Check if argument provided
if [ $# -eq 0 ]; then
printf "Error: No environment specified\n"
printf "Error: No command specified\n"
usage
fi
ENV=$1
CMD=$1
case $CMD in
build)
printf "Building blog with mdbook...\n"
[ -s src ] || ln -s /var/lib/www src
mdbook build
printf "✓ Build complete!\n"
;;
case $ENV in
staging)
printf "Deploying to STAGING environment...\n"
STAGING_PATH="/srv/www/stage.binning.net"
# Create staging directory if it doesn't exist
sudo mkdir -p ${STAGING_PATH}
# Deploy website files via rsync
printf "Deploying website files...\n"
sudo rsync -av --delete ${DEPLOY_FILES} ${STAGING_PATH}/
sudo rsync -av --delete blog/ ${STAGING_PATH}/
# Set proper ownership
sudo chown -R nginx:nginx ${STAGING_PATH}/
# Copy nginx config
sudo cp -t /etc/nixos/ staging.nginx.nix
printf "✓ Staging deployment complete!\n Files deployed to: %s\n Nginx config: /etc/nixos/staging.nginx.nix\n\nTo activate, update your NixOS configuration to import staging.nginx.nix\nand run: sudo nixos-rebuild switch\n" "${STAGING_PATH}"
printf "✓ Staging deployment complete!\n Files deployed to: %s\n\nTo activate nginx, import staging.nginx.nix into your local NixOS config\nand run: sudo nixos-rebuild switch\n" "${STAGING_PATH}"
;;
prod)
printf "Deploying to PRODUCTION environment...\n"
# SSH details
REMOTE_HOST="crossbox"
REMOTE_USER="m3b"
REMOTE_USER="brimlock"
REMOTE_PATH="/srv/www/binning.net"
REMOTE_NIXOS="/etc/nixos/"
# Check if SSH key is set up
if ! ssh -o BatchMode=yes -o ConnectTimeout=5 ${REMOTE_USER}@${REMOTE_HOST} exit 2>/dev/null; then
printf "Warning: SSH connection test failed. Ensure SSH keys are configured.\nYou may be prompted for a password.\n"
fi
# Deploy website files via rsync over SSH
# Using just the host from SSH config without user@ prefix
printf "Deploying website files...\n"
ssh ${REMOTE_HOST} "mkdir -p /tmp/${REMOTE_PATH}"
rsync -avz --delete ${DEPLOY_FILES} ${REMOTE_HOST}:/tmp/${REMOTE_PATH}/
# Set proper permissions and move config on remote server
ssh ${REMOTE_HOST} "sudo mv /tmp/${REMOTE_PATH} ${REMOTE_PATH} && \
ssh ${REMOTE_USER}@${REMOTE_HOST} "mkdir -p /tmp/blog-deploy"
rsync -avz --delete blog/ ${REMOTE_USER}@${REMOTE_HOST}:/tmp/blog-deploy/
ssh ${REMOTE_USER}@${REMOTE_HOST} "sudo rsync -avz --delete /tmp/blog-deploy/ ${REMOTE_PATH}/ && \
sudo chown -R nginx:nginx ${REMOTE_PATH}/ && \
printf 'Content deployed.\n'"
# Deploy nginx configuration
printf "Deploying nginx configuration...\n"
scp prod.nginx.nix ${REMOTE_HOST}:/tmp/nginx.nix
printf "✓ Production deployment complete!\n\nNginx configuration is managed by the nixos-config flake (hosts/crossbox/nginx.nix).\n"
;;
# Set proper permissions and move config on remote server
ssh ${REMOTE_HOST} "sudo mv /tmp/nginx.nix ${REMOTE_NIXOS}nginx.nix && \
sudo chown -R nginx:nginx ${REMOTE_PATH}/ && \
printf 'Configuration deployed. Run sudo nixos-rebuild switch to activate.\n'"
local)
printf "Deploying locally to production path...\n"
printf "✓ Production deployment complete!\n\nSSH into %s and run: sudo nixos-rebuild switch\n" "${REMOTE_HOST}"
LOCAL_PATH="/srv/www/binning.net"
printf "Deploying website files...\n"
rsync -av --delete blog/ ${LOCAL_PATH}/
printf "✓ Local deployment complete!\n Files deployed to: %s\n" "${LOCAL_PATH}"
;;
*)
printf "Error: Invalid environment '%s'\n" "$ENV"
printf "Error: Invalid command '%s'\n" "$CMD"
usage
;;
esac

View file

@ -1,3 +0,0 @@
<footer class="footer">
<p>&copy; 2025 WWW - Built with simple HTML and CSS</p>
</footer>

View file

@ -1,10 +0,0 @@
<nav class="navbar">
<div class="nav-container">
<a href="/" class="nav-brand">Home</a>
<ul class="nav-menu">
<li><a href="/blog.html">Blog</a></li>
<li><a href="https://forgejo.binning.net" target="_blank">Git</a></li>
<li><a href="/resume.html">Resume</a></li>
</ul>
</div>
</nav>

View file

@ -1,37 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WWW - Personal Website</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<!--#include virtual="/includes/navbar.html" -->
<main class="container">
<section class="hero">
<h1>Welcome</h1>
<p>This is my personal website and blog.</p>
</section>
<section class="content-section">
<h2>About & Contact</h2>
<div class="contact-info">
<p><strong>Email:</strong> example@example.com</p>
<p>Feel free to reach out via email for any inquiries.</p>
</div>
</section>
<section class="content-section">
<h2>Genealogy</h2>
<div class="genealogy-blurb">
<p>I am interested in family history and genealogy. This website serves as a personal space to share stories, chronicles, and musings about life's journey, adventures, and family heritage.</p>
<p>The Copper Chronicle contains stories and memories from various trips and experiences. More to come as this site develops.</p>
</div>
</section>
</main>
<!--#include virtual="/includes/footer.html" -->
</body>
</html>

View file

@ -1,151 +0,0 @@
{ config, pkgs, lib, ... }:
let
# Read multiple API keys from the secrets file at build time
# Note: This embeds the secrets in the Nix store, which is a trade-off
# Alternative: Keep secrets file and read via njs module or external auth service
secretsFile = "/srv/nginx/secrets";
# Read API keys from file (one key per line, will be evaluated at build time)
# If the file doesn't exist yet, this will fail - create it first
apiKeysRaw = builtins.readFile secretsFile;
apiKeys = lib.filter (k: k != "") (lib.splitString "\n" apiKeysRaw);
# Generate map entries for each key
mapEntries = lib.concatMapStringsSep "\n "
(key: ''"Bearer ${key}" "authorized";'')
apiKeys;
in
{
services.nginx = {
enable = true;
# Recommended settings
recommendedGzipSettings = true;
recommendedOptimisation = true;
recommendedProxySettings = true;
recommendedTlsSettings = true;
# Increase bucket size for long Bearer tokens
mapHashBucketSize = 128;
# Map directive to check Authorization header against multiple keys
appendHttpConfig = ''
# Check if the Authorization header matches any expected value
map $http_authorization $auth_status {
default "unauthorized";
"" "no_auth";
${mapEntries}
}
'';
# Virtual hosts configuration
virtualHosts = {
# Main website - Static HTML/CSS
"www.binning.net" = {
enableACME = true;
forceSSL = true;
root = "/srv/www/binning.net";
locations."/" = {
index = "index.html";
tryFiles = "$uri $uri/ =404";
extraConfig = ''
# Enable Server Side Includes for navbar/footer includes
ssi on;
'';
};
# Private blog articles with HTTP basic authentication
locations."/blog/private/" = {
extraConfig = ''
auth_basic "Private Articles";
auth_basic_user_file /srv/nginx/.htpasswd;
# Enable Server Side Includes
ssi on;
'';
};
# Optional: Custom 404 page
extraConfig = ''
error_page 404 /404.html;
'';
};
# Ollama with Bearer token authentication
"ollama.binning.net" = {
enableACME = true;
forceSSL = true;
locations."/" = {
extraConfig = ''
# Check auth status
if ($auth_status = "no_auth") {
return 401 "Unauthorized: Bearer token required\n";
}
if ($auth_status = "unauthorized") {
return 403 "Forbidden: Invalid API key\n";
}
# Proxy to Ollama (only if authorized)
proxy_pass http://localhost:11434;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# Timeouts for long-running requests
proxy_read_timeout 300s;
proxy_connect_timeout 300s;
proxy_send_timeout 300s;
# Allow large request bodies
client_max_body_size 100M;
# Logging
access_log /var/log/nginx/ollama_access.log;
error_log /var/log/nginx/ollama_error.log;
'';
};
};
# Forgejo
"forgejo.binning.net" = {
enableACME = true;
forceSSL = true;
locations."/" = {
proxyPass = "http://127.0.0.1:3000";
# No extraConfig needed - recommendedProxySettings handles headers
};
};
# Radicale
"radicale.binning.net" = {
enableACME = true;
forceSSL = true;
locations."/" = {
proxyPass = "http://127.0.0.1:5232";
# recommendedProxySettings handles most headers
extraConfig = ''
proxy_set_header X-Script-Name "";
'';
};
};
};
};
# Firewall
networking.firewall.allowedTCPPorts = [ 80 443 ];
# ACME/Let's Encrypt
security.acme = {
acceptTerms = true;
defaults.email = "hvanb@pm.me";
};
}

View file

@ -1,122 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Resume - WWW</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<!--#include virtual="/includes/navbar.html" -->
<main class="container">
<section class="hero">
<h1>Resume</h1>
<p>Professional Experience & Skills</p>
</section>
<section class="content-section">
<h2>Summary</h2>
<p>Experienced software engineer with expertise in systems engineering, cloud infrastructure, and full-stack development. Passionate about building reliable, scalable solutions using modern technologies.</p>
</section>
<section class="content-section">
<h2>Technical Skills</h2>
<div class="skills-grid">
<div class="skill-category">
<h3>Languages</h3>
<ul>
<li>Rust</li>
<li>Python</li>
<li>Go</li>
<li>JavaScript/TypeScript</li>
<li>HTML/CSS</li>
</ul>
</div>
<div class="skill-category">
<h3>Systems & Infrastructure</h3>
<ul>
<li>Linux/NixOS</li>
<li>Docker</li>
<li>Nginx</li>
<li>Git</li>
<li>CI/CD</li>
</ul>
</div>
<div class="skill-category">
<h3>Frameworks & Tools</h3>
<ul>
<li>Rocket (Rust)</li>
<li>React</li>
<li>Node.js</li>
<li>PostgreSQL</li>
<li>RESTful APIs</li>
</ul>
</div>
</div>
</section>
<section class="content-section">
<h2>Professional Experience</h2>
<div class="experience-item">
<h3>Software Engineer</h3>
<p class="job-meta">Example Company • 2020 - Present</p>
<ul>
<li>Designed and implemented scalable backend services using Rust and Python</li>
<li>Managed infrastructure using NixOS and containerization technologies</li>
<li>Built and maintained CI/CD pipelines for automated testing and deployment</li>
<li>Collaborated with cross-functional teams to deliver high-quality software solutions</li>
</ul>
</div>
<div class="experience-item">
<h3>Systems Administrator</h3>
<p class="job-meta">Previous Company • 2018 - 2020</p>
<ul>
<li>Maintained Linux-based server infrastructure and monitoring systems</li>
<li>Automated routine tasks using shell scripting and Python</li>
<li>Implemented security best practices and access control policies</li>
<li>Provided technical support and documentation for development teams</li>
</ul>
</div>
</section>
<section class="content-section">
<h2>Education</h2>
<div class="experience-item">
<h3>Bachelor of Science in Computer Science</h3>
<p class="job-meta">University Name • 2014 - 2018</p>
<p>Focus on software engineering, algorithms, and systems programming.</p>
</div>
</section>
<section class="content-section">
<h2>Projects</h2>
<div class="experience-item">
<h3>Self-Hosted Infrastructure</h3>
<p>Designed and deployed a complete self-hosted solution including:</p>
<ul>
<li>Git hosting with Forgejo</li>
<li>Web server with Nginx and SSL/TLS</li>
<li>Calendar and contacts sync with Radicale</li>
<li>Declarative configuration management with NixOS</li>
</ul>
</div>
<div class="experience-item">
<h3>Personal Website & Blog</h3>
<p>Built a simple, reliable website using primitive technologies (HTML/CSS) focused on performance and maintainability.</p>
</div>
</section>
<section class="content-section">
<h2>Contact</h2>
<p><strong>Email:</strong> example@example.com</p>
<p><strong>GitHub:</strong> <a href="https://forgejo.binning.net" target="_blank">forgejo.binning.net</a></p>
</section>
</main>
<!--#include virtual="/includes/footer.html" -->
</body>
</html>

View file

@ -1,56 +0,0 @@
{ config, pkgs, lib, ... }:
{
services.nginx = {
enable = true;
# Recommended settings
recommendedGzipSettings = true;
recommendedOptimisation = true;
recommendedProxySettings = true;
# Virtual hosts configuration for local staging
virtualHosts = {
# Main website - Static HTML/CSS
# Access via http://localhost or http://localhost:80
"localhost" = {
# No SSL for local development
listen = [
{ addr = "127.0.0.1"; port = 80; }
{ addr = "0.0.0.0"; port = 80; }
];
root = "/srv/www/stage.binning.net";
locations."/" = {
index = "index.html";
tryFiles = "$uri $uri/ =404";
extraConfig = ''
# Enable Server Side Includes for navbar/footer includes
ssi on;
'';
};
# Private blog articles with HTTP basic authentication
locations."/blog/private/" = {
extraConfig = ''
auth_basic "Private Articles";
auth_basic_user_file /srv/nginx/.htpasswd;
# Enable Server Side Includes
ssi on;
'';
};
# Custom 404 page
extraConfig = ''
error_page 404 /404.html;
'';
};
};
};
# Firewall - allow local HTTP access
networking.firewall.allowedTCPPorts = [ 80 ];
}

331
style.css
View file

@ -1,331 +0,0 @@
/* CSS Reset and Base Styles */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Courier New', 'Courier', 'Monaco', 'Lucida Console', monospace;
line-height: 1.7;
color: #3e2723;
background-color: #f4ecd8;
}
/* Navbar Styles */
.navbar {
background-color: #5d4037;
box-shadow: 0 2px 4px rgba(62, 39, 35, 0.3);
position: sticky;
top: 0;
z-index: 1000;
border-bottom: 2px solid #4e342e;
}
.nav-container {
max-width: 1200px;
margin: 0 auto;
padding: 1rem 2rem;
display: flex;
justify-content: space-between;
align-items: center;
}
.nav-brand {
color: #f4ecd8;
text-decoration: none;
font-size: 1.5rem;
font-weight: bold;
}
.nav-brand:hover {
color: #d7ccc8;
}
.nav-menu {
list-style: none;
display: flex;
gap: 2rem;
}
.nav-menu a {
color: #f4ecd8;
text-decoration: none;
font-size: 1rem;
transition: color 0.3s ease;
}
.nav-menu a:hover {
color: #d7ccc8;
}
/* Main Container */
.container {
max-width: 1200px;
margin: 0 auto;
padding: 2rem;
}
/* Hero Section */
.hero {
background-color: #efebe9;
padding: 3rem 2rem;
border-radius: 4px;
box-shadow: 0 2px 8px rgba(62, 39, 35, 0.2);
margin-bottom: 2rem;
text-align: center;
border: 1px solid #d7ccc8;
}
.hero h1 {
font-size: 2.5rem;
color: #4e342e;
margin-bottom: 1rem;
}
.hero p {
font-size: 1.2rem;
color: #6d4c41;
}
/* Content Section */
.content-section {
background-color: #efebe9;
padding: 2rem;
margin-bottom: 2rem;
border-radius: 4px;
box-shadow: 0 2px 8px rgba(62, 39, 35, 0.2);
border: 1px solid #d7ccc8;
}
.content-section h2 {
color: #4e342e;
font-size: 2rem;
margin-bottom: 1.5rem;
border-bottom: 3px solid #8d6e63;
padding-bottom: 0.5rem;
}
.contact-info p,
.genealogy-blurb p {
margin-bottom: 1rem;
line-height: 1.8;
color: #5d4037;
}
.contact-info strong {
color: #4e342e;
}
/* Footer */
.footer {
background-color: #5d4037;
color: #f4ecd8;
text-align: center;
padding: 2rem;
margin-top: 3rem;
border-top: 2px solid #4e342e;
}
.footer p {
font-size: 0.9rem;
}
/* Blog Styles */
.section-description {
color: #6d4c41;
font-style: italic;
margin-bottom: 1.5rem;
}
.blog-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 1.5rem;
margin-top: 1.5rem;
}
.blog-card {
background-color: #faf8f3;
border: 1px solid #d7ccc8;
border-radius: 4px;
padding: 1.5rem;
transition: transform 0.2s ease, box-shadow 0.2s ease;
position: relative;
}
.blog-card:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(62, 39, 35, 0.25);
}
.blog-card h3 {
color: #4e342e;
margin-bottom: 0.5rem;
font-size: 1.3rem;
}
.blog-card h3 a {
color: #4e342e;
text-decoration: none;
transition: color 0.3s ease;
}
.blog-card h3 a:hover {
color: #6d4c41;
}
.blog-meta {
color: #8d6e63;
font-size: 0.9rem;
margin-bottom: 0.75rem;
}
.blog-card p {
color: #5d4037;
line-height: 1.6;
}
.blog-card.private {
border-color: #a1887f;
background-color: #f5f5f0;
}
.private-badge {
display: inline-block;
background-color: #8d6e63;
color: #f4ecd8;
padding: 0.25rem 0.75rem;
border-radius: 4px;
font-size: 0.85rem;
margin-top: 0.5rem;
}
/* Resume Styles */
.skills-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 2rem;
margin-top: 1.5rem;
}
.skill-category h3 {
color: #4e342e;
margin-bottom: 1rem;
font-size: 1.2rem;
border-bottom: 2px solid #8d6e63;
padding-bottom: 0.5rem;
}
.skill-category ul {
list-style: none;
padding-left: 0;
}
.skill-category li {
padding: 0.5rem 0;
color: #5d4037;
position: relative;
padding-left: 1.5rem;
}
.skill-category li:before {
content: "▸";
position: absolute;
left: 0;
color: #8d6e63;
font-weight: bold;
}
.experience-item {
margin-bottom: 2rem;
}
.experience-item:last-child {
margin-bottom: 0;
}
.experience-item h3 {
color: #4e342e;
font-size: 1.4rem;
margin-bottom: 0.5rem;
}
.job-meta {
color: #8d6e63;
font-size: 0.95rem;
margin-bottom: 1rem;
font-style: italic;
}
.experience-item ul {
margin-top: 1rem;
padding-left: 1.5rem;
}
.experience-item li {
color: #5d4037;
margin-bottom: 0.5rem;
line-height: 1.7;
}
.experience-item p {
color: #5d4037;
line-height: 1.7;
}
/* Recipe Page Styles */
.recipe-card {
background-color: #faf8f3;
border: 1px solid #d7ccc8;
border-radius: 4px;
padding: 1.5rem;
margin-bottom: 1.5rem;
}
.recipe-card h3 {
color: #4e342e;
border-bottom: 2px solid #8d6e63;
padding-bottom: 0.5rem;
margin-bottom: 1rem;
}
.recipe-year {
color: #8d6e63;
font-style: italic;
font-size: 0.9rem;
}
/* Responsive Design */
@media (max-width: 768px) {
.nav-container {
flex-direction: column;
gap: 1rem;
}
.nav-menu {
flex-direction: column;
gap: 1rem;
text-align: center;
}
.container {
padding: 1rem;
}
.hero h1 {
font-size: 2rem;
}
.content-section h2 {
font-size: 1.5rem;
}
.blog-grid {
grid-template-columns: 1fr;
}
.skills-grid {
grid-template-columns: 1fr;
}
}

View file

@ -1,11 +1,14 @@
(A) Populate the blog sections dynamically with blog folders using some include or frame mechanism
(B) Add a sidebar to navigate across the Blog sections, subsections, and pages
(C) Integrate photos for recipes
DefCon article
Oktoberfest article
health journey
faith journey
Add images and captions to those headings.
Personalize the index page.
Personalize the introduction page of the blog.
Determine which articles should be incorporated into the website
Integrate photos for recipes
Build out the Copper Chronicle
Fill out the DefCon article
Fill out the Oktoberfest article with old physical notes in big binder
x Add a build step to fetch/symlink "content" to this repo before mdbook build.
x Add a folder to hold the main html files and deploy it properly.
x Create a simple webpage with HTML and CSS
x Serve it from nginx
x Add a navbar at the top with the index page as link in the top-left.
@ -23,3 +26,17 @@ x Re-use code or code blocks where possible between staging.nginx.nix and prod.n
x Re-use code or code blocks where possible between deploy.sh staging and deploy.sh prod
x Re-theme the blog with sepia tone and monospace fonts/typefaces.
x Create a recipe section in the blog based on the markdown content in blog/public/recipes
x Populate the blog sections dynamically with blog folders using some include or frame mechanism
x Add a sidebar to navigate across the Blog sections, subsections, and pages
x Move copper chronicle to the private section of the blog.
x Add a lock symbol to the mdbook sidebar to indicate private/restricted sections.
x Create a "minimal" content set to test building and deployment without the entire blog/content.
x Import Abby's Binning Family Menu @Cline
x Fix anchors (relative links) showing in mdbook's sidebar.
x Create a minimal "Menu" (food menu) page on the main website, which has a selection of line items from the recipe book. @Cline
x 2026-03-22 Add a folder to hold the nginx/nix files and deploy it properly.
x 2026-03-22 Add a Forgejo pipeline to supersede deploy.sh.
x 2026-03-22 Remove the one line descriptions from the menu.
x 2026-03-22 Move the resume to the private section and add the lock symbol.
x 2026-03-23 Reduce the CSS where possible. Match my website's theme to align with mdbook's theme.
x Set up nightly builds