Merge pull request 'develop' (#2) from develop into master

Reviewed-on: matthew.binning/www#2
This commit is contained in:
Matthew Binning 2026-03-25 07:54:32 -07:00
commit 2db3ec3da4
14 changed files with 86 additions and 959 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}/
# Set proper ownership
sudo rsync -av --delete blog/ ${STAGING_PATH}/
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
# 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'"
printf "✓ Production deployment complete!\n\nSSH into %s and run: sudo nixos-rebuild switch\n" "${REMOTE_HOST}"
printf "✓ Production deployment complete!\n\nNginx configuration is managed by the nixos-config flake (hosts/crossbox/nginx.nix).\n"
;;
local)
printf "Deploying locally to production path...\n"
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;
}
}