feat: Add a recipe book

Created a recipe section in the blog from existing markdown content.
This commit is contained in:
Matthew Binning 2026-01-01 17:48:00 -08:00
parent bd24208274
commit bddfef86eb
6 changed files with 180 additions and 74 deletions

View file

@ -82,6 +82,44 @@
</article> </article>
</div> </div>
</section> </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> </main>
<!--#include virtual="/includes/footer.html" --> <!--#include virtual="/includes/footer.html" -->

45
common.nginx.nix Normal file
View file

@ -0,0 +1,45 @@
{ 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,6 +2,9 @@
set -e 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 information
usage() { 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 [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"
@ -27,15 +30,7 @@ case $ENV in
# Deploy website files via rsync # Deploy website files via rsync
printf "Deploying website files...\n" printf "Deploying website files...\n"
sudo rsync -av --delete \ sudo rsync -av --delete ${DEPLOY_FILES} ${STAGING_PATH}/
blog \
includes \
index.html \
blog.html \
resume.html \
style.css \
404.html \
${STAGING_PATH}/
# Set proper ownership # Set proper ownership
sudo chown -R nginx:nginx ${STAGING_PATH}/ sudo chown -R nginx:nginx ${STAGING_PATH}/
@ -50,8 +45,8 @@ case $ENV in
printf "Deploying to PRODUCTION environment...\n" printf "Deploying to PRODUCTION environment...\n"
# SSH details # SSH details
REMOTE_HOST="binning.net" REMOTE_HOST="crossbox"
REMOTE_USER="matthew.binning" REMOTE_USER="m3b"
REMOTE_PATH="/srv/www/binning.net" REMOTE_PATH="/srv/www/binning.net"
REMOTE_NIXOS="/etc/nixos/" REMOTE_NIXOS="/etc/nixos/"
@ -61,24 +56,21 @@ case $ENV in
fi fi
# Deploy website files via rsync over SSH # Deploy website files via rsync over SSH
# Using just the host from SSH config without user@ prefix
printf "Deploying website files...\n" printf "Deploying website files...\n"
rsync -avz --delete \ ssh ${REMOTE_HOST} "mkdir -p /tmp/${REMOTE_PATH}"
-e ssh \ rsync -avz --delete ${DEPLOY_FILES} ${REMOTE_HOST}:/tmp/${REMOTE_PATH}/
blog \ # Set proper permissions and move config on remote server
includes \ ssh ${REMOTE_HOST} "sudo mv /tmp/${REMOTE_PATH} ${REMOTE_PATH} && \
index.html \ sudo chown -R nginx:nginx ${REMOTE_PATH}/ && \
blog.html \ printf 'Content deployed.\n'"
resume.html \
style.css \
404.html \
${REMOTE_USER}@${REMOTE_HOST}:${REMOTE_PATH}/
# Deploy nginx configuration # Deploy nginx configuration
printf "Deploying nginx configuration...\n" printf "Deploying nginx configuration...\n"
scp prod.nginx.nix ${REMOTE_USER}@${REMOTE_HOST}:/tmp/nginx.nix scp prod.nginx.nix ${REMOTE_HOST}:/tmp/nginx.nix
# Set proper permissions and move config on remote server # Set proper permissions and move config on remote server
ssh ${REMOTE_USER}@${REMOTE_HOST} "sudo mv /tmp/nginx.nix ${REMOTE_NIXOS}nginx.nix && \ ssh ${REMOTE_HOST} "sudo mv /tmp/nginx.nix ${REMOTE_NIXOS}nginx.nix && \
sudo chown -R nginx:nginx ${REMOTE_PATH}/ && \ sudo chown -R nginx:nginx ${REMOTE_PATH}/ && \
printf 'Configuration deployed. Run sudo nixos-rebuild switch to activate.\n'" printf 'Configuration deployed. Run sudo nixos-rebuild switch to activate.\n'"

View file

@ -3,8 +3,8 @@
<a href="/" class="nav-brand">Home</a> <a href="/" class="nav-brand">Home</a>
<ul class="nav-menu"> <ul class="nav-menu">
<li><a href="/blog.html">Blog</a></li> <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> <li><a href="/resume.html">Resume</a></li>
<li><a href="https://forgejo.binning.net" target="_blank">Forgejo</a></li>
</ul> </ul>
</div> </div>
</nav> </nav>

120
style.css
View file

@ -6,19 +6,20 @@
} }
body { body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; font-family: 'Courier New', 'Courier', 'Monaco', 'Lucida Console', monospace;
line-height: 1.6; line-height: 1.7;
color: #333; color: #3e2723;
background-color: #f5f5f5; background-color: #f4ecd8;
} }
/* Navbar Styles */ /* Navbar Styles */
.navbar { .navbar {
background-color: #2c3e50; background-color: #5d4037;
box-shadow: 0 2px 4px rgba(0,0,0,0.1); box-shadow: 0 2px 4px rgba(62, 39, 35, 0.3);
position: sticky; position: sticky;
top: 0; top: 0;
z-index: 1000; z-index: 1000;
border-bottom: 2px solid #4e342e;
} }
.nav-container { .nav-container {
@ -31,14 +32,14 @@ body {
} }
.nav-brand { .nav-brand {
color: #fff; color: #f4ecd8;
text-decoration: none; text-decoration: none;
font-size: 1.5rem; font-size: 1.5rem;
font-weight: bold; font-weight: bold;
} }
.nav-brand:hover { .nav-brand:hover {
color: #3498db; color: #d7ccc8;
} }
.nav-menu { .nav-menu {
@ -48,14 +49,14 @@ body {
} }
.nav-menu a { .nav-menu a {
color: #fff; color: #f4ecd8;
text-decoration: none; text-decoration: none;
font-size: 1rem; font-size: 1rem;
transition: color 0.3s ease; transition: color 0.3s ease;
} }
.nav-menu a:hover { .nav-menu a:hover {
color: #3498db; color: #d7ccc8;
} }
/* Main Container */ /* Main Container */
@ -67,39 +68,41 @@ body {
/* Hero Section */ /* Hero Section */
.hero { .hero {
background-color: #fff; background-color: #efebe9;
padding: 3rem 2rem; padding: 3rem 2rem;
border-radius: 8px; border-radius: 4px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1); box-shadow: 0 2px 8px rgba(62, 39, 35, 0.2);
margin-bottom: 2rem; margin-bottom: 2rem;
text-align: center; text-align: center;
border: 1px solid #d7ccc8;
} }
.hero h1 { .hero h1 {
font-size: 2.5rem; font-size: 2.5rem;
color: #2c3e50; color: #4e342e;
margin-bottom: 1rem; margin-bottom: 1rem;
} }
.hero p { .hero p {
font-size: 1.2rem; font-size: 1.2rem;
color: #555; color: #6d4c41;
} }
/* Content Section */ /* Content Section */
.content-section { .content-section {
background-color: #fff; background-color: #efebe9;
padding: 2rem; padding: 2rem;
margin-bottom: 2rem; margin-bottom: 2rem;
border-radius: 8px; border-radius: 4px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1); box-shadow: 0 2px 8px rgba(62, 39, 35, 0.2);
border: 1px solid #d7ccc8;
} }
.content-section h2 { .content-section h2 {
color: #2c3e50; color: #4e342e;
font-size: 2rem; font-size: 2rem;
margin-bottom: 1.5rem; margin-bottom: 1.5rem;
border-bottom: 3px solid #3498db; border-bottom: 3px solid #8d6e63;
padding-bottom: 0.5rem; padding-bottom: 0.5rem;
} }
@ -107,20 +110,21 @@ body {
.genealogy-blurb p { .genealogy-blurb p {
margin-bottom: 1rem; margin-bottom: 1rem;
line-height: 1.8; line-height: 1.8;
color: #555; color: #5d4037;
} }
.contact-info strong { .contact-info strong {
color: #2c3e50; color: #4e342e;
} }
/* Footer */ /* Footer */
.footer { .footer {
background-color: #2c3e50; background-color: #5d4037;
color: #fff; color: #f4ecd8;
text-align: center; text-align: center;
padding: 2rem; padding: 2rem;
margin-top: 3rem; margin-top: 3rem;
border-top: 2px solid #4e342e;
} }
.footer p { .footer p {
@ -129,7 +133,7 @@ body {
/* Blog Styles */ /* Blog Styles */
.section-description { .section-description {
color: #666; color: #6d4c41;
font-style: italic; font-style: italic;
margin-bottom: 1.5rem; margin-bottom: 1.5rem;
} }
@ -142,55 +146,55 @@ body {
} }
.blog-card { .blog-card {
background-color: #f9f9f9; background-color: #faf8f3;
border: 1px solid #e0e0e0; border: 1px solid #d7ccc8;
border-radius: 8px; border-radius: 4px;
padding: 1.5rem; padding: 1.5rem;
transition: transform 0.2s ease, box-shadow 0.2s ease; transition: transform 0.2s ease, box-shadow 0.2s ease;
position: relative; position: relative;
} }
.blog-card:hover { .blog-card:hover {
transform: translateY(-4px); transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0,0,0,0.15); box-shadow: 0 4px 12px rgba(62, 39, 35, 0.25);
} }
.blog-card h3 { .blog-card h3 {
color: #2c3e50; color: #4e342e;
margin-bottom: 0.5rem; margin-bottom: 0.5rem;
font-size: 1.3rem; font-size: 1.3rem;
} }
.blog-card h3 a { .blog-card h3 a {
color: #2c3e50; color: #4e342e;
text-decoration: none; text-decoration: none;
transition: color 0.3s ease; transition: color 0.3s ease;
} }
.blog-card h3 a:hover { .blog-card h3 a:hover {
color: #3498db; color: #6d4c41;
} }
.blog-meta { .blog-meta {
color: #999; color: #8d6e63;
font-size: 0.9rem; font-size: 0.9rem;
margin-bottom: 0.75rem; margin-bottom: 0.75rem;
} }
.blog-card p { .blog-card p {
color: #555; color: #5d4037;
line-height: 1.6; line-height: 1.6;
} }
.blog-card.private { .blog-card.private {
border-color: #f39c12; border-color: #a1887f;
background-color: #fff9f0; background-color: #f5f5f0;
} }
.private-badge { .private-badge {
display: inline-block; display: inline-block;
background-color: #f39c12; background-color: #8d6e63;
color: #fff; color: #f4ecd8;
padding: 0.25rem 0.75rem; padding: 0.25rem 0.75rem;
border-radius: 4px; border-radius: 4px;
font-size: 0.85rem; font-size: 0.85rem;
@ -206,10 +210,10 @@ body {
} }
.skill-category h3 { .skill-category h3 {
color: #2c3e50; color: #4e342e;
margin-bottom: 1rem; margin-bottom: 1rem;
font-size: 1.2rem; font-size: 1.2rem;
border-bottom: 2px solid #3498db; border-bottom: 2px solid #8d6e63;
padding-bottom: 0.5rem; padding-bottom: 0.5rem;
} }
@ -220,7 +224,7 @@ body {
.skill-category li { .skill-category li {
padding: 0.5rem 0; padding: 0.5rem 0;
color: #555; color: #5d4037;
position: relative; position: relative;
padding-left: 1.5rem; padding-left: 1.5rem;
} }
@ -229,7 +233,7 @@ body {
content: "▸"; content: "▸";
position: absolute; position: absolute;
left: 0; left: 0;
color: #3498db; color: #8d6e63;
font-weight: bold; font-weight: bold;
} }
@ -242,13 +246,13 @@ body {
} }
.experience-item h3 { .experience-item h3 {
color: #2c3e50; color: #4e342e;
font-size: 1.4rem; font-size: 1.4rem;
margin-bottom: 0.5rem; margin-bottom: 0.5rem;
} }
.job-meta { .job-meta {
color: #999; color: #8d6e63;
font-size: 0.95rem; font-size: 0.95rem;
margin-bottom: 1rem; margin-bottom: 1rem;
font-style: italic; font-style: italic;
@ -260,16 +264,38 @@ body {
} }
.experience-item li { .experience-item li {
color: #555; color: #5d4037;
margin-bottom: 0.5rem; margin-bottom: 0.5rem;
line-height: 1.7; line-height: 1.7;
} }
.experience-item p { .experience-item p {
color: #555; color: #5d4037;
line-height: 1.7; 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 */ /* Responsive Design */
@media (max-width: 768px) { @media (max-width: 768px) {
.nav-container { .nav-container {

View file

@ -1,5 +1,6 @@
(A) Re-use code or code blocks where possible between staging.nginx.nix and prod.nginx.nix (A) Populate the blog sections dynamically with blog folders using some include or frame mechanism
(B) Re-use code or code blocks where possible between deploy.sh staging and deploy.sh prod (B) Add a sidebar to navigate across the Blog sections, subsections, and pages
(C) Integrate photos for recipes
DefCon article DefCon article
Oktoberfest article Oktoberfest article
health journey health journey
@ -18,3 +19,7 @@ x Possibly use some include mechanism in HTML to separate the navbar to its own
x Create a staging.nginx.nix based on prod.nginx.nix which sets up nginx as a local server. x Create a staging.nginx.nix based on prod.nginx.nix which sets up nginx as a local server.
x Update the deploy.sh script to deploy to either staging or prod over SSH to binning.net. x Update the deploy.sh script to deploy to either staging or prod over SSH to binning.net.
x Add user authentication to view private articles (either HTTP basic authentication or something else) x Add user authentication to view private articles (either HTTP basic authentication or something else)
x Re-use code or code blocks where possible between staging.nginx.nix and prod.nginx.nix
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