Redesign: replace hugo-sustain with self-contained editorial theme

Drop the hugo-sustain submodule and build a custom theme forked from the
shrug.host design system, recolored to a potato identity (russet skin,
sprout-green leitmotif, purple-potato accent; potato-flesh light + soil
dark, follows prefers-color-scheme with a persisted toggle).

- Self-hosted Commit Mono + Source Serif 4 (no external requests)
- Token-based CSS: tokens / base / components / layouts
- Layouts: baseof, index (hero + latest posts + "now"), single, list,
  projects (card grid), partials for nav/footer/head + OG/Twitter/schema
- SEO scaffolding hugo-sustain lacked: OpenGraph, Person/BlogPosting
  schema, RSS, canonical
- Pipeline unchanged (Hugo 0.148.2, GitHub Pages); post URLs preserved

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-11 11:08:32 -04:00
co-authored by Claude Opus 4.8
parent 480c01a468
commit 698d70d9ef
26 changed files with 851 additions and 178 deletions
-3
View File
@@ -1,3 +0,0 @@
[submodule "themes/hugo-sustain"]
path = themes/hugo-sustain
url = https://git.shrug.pw/neil/hugo-sustain.git
+98
View File
@@ -0,0 +1,98 @@
/* ==========================================================================
thepotato.tech — Base: reset, elements, typography primitives
========================================================================== */
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0;}
html{scroll-behavior:smooth;-webkit-text-size-adjust:100%;}
body{
font-family:var(--font-serif);
font-size:var(--text-lg);
line-height:1.65;
color:var(--text);
background:var(--bg);
-webkit-font-smoothing:antialiased;
-moz-osx-font-smoothing:grayscale;
position:relative;
min-height:100vh;
display:flex;
flex-direction:column;
}
/* Film grain overlay */
body::before{
content:"";position:fixed;inset:0;z-index:9999;pointer-events:none;
background-image:var(--noise);mix-blend-mode:multiply;opacity:.6;
}
@media (prefers-color-scheme:dark){:root:not([data-theme="light"]) body::before{mix-blend-mode:screen;}}
:root[data-theme="dark"] body::before{mix-blend-mode:screen;}
.site-main{flex:1 0 auto;}
h1,h2,h3,h4,h5,h6{
font-family:var(--font-mono);
line-height:1.15;
font-weight:700;
color:var(--text);
letter-spacing:-.01em;
text-wrap:balance;
}
a{color:var(--link);text-decoration:none;transition:color var(--tr);}
a:hover{color:var(--link-hover);}
strong{font-weight:700;color:var(--text);}
em{font-style:italic;}
img{max-width:100%;height:auto;}
::selection{background:var(--sprout);color:#fff;}
:focus-visible{outline:2px solid var(--russet);outline-offset:3px;border-radius:3px;}
.sr-only{
position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;
clip:rect(0,0,0,0);white-space:nowrap;border:0;
}
/* Skip link */
.skip-link{
position:absolute;left:-999px;top:0;z-index:10000;
background:var(--russet);color:#fff;padding:var(--s3) var(--s5);
font-family:var(--font-mono);font-size:var(--text-sm);border-radius:0 0 var(--radius) 0;
}
.skip-link:focus{left:0;color:#fff;}
/* --- Layout primitives --- */
.container{max-width:var(--max);margin:0 auto;padding:0 var(--s6);}
.container--text{max-width:var(--max-text);}
.section{padding:var(--s20) 0;}
.section--surface{
background:var(--bg-surface);
border-top:1px solid var(--border);
border-bottom:1px solid var(--border);
}
/* --- Shared mono devices --- */
.eyebrow{
font-family:var(--font-mono);font-size:var(--text-sm);color:var(--sprout);
display:inline-flex;align-items:center;gap:var(--s2);letter-spacing:.02em;
}
.section-label{
font-family:var(--font-mono);font-size:var(--text-sm);color:var(--russet);
letter-spacing:.04em;display:block;margin-bottom:var(--s3);
}
/* The potato "eye" / sprout — leitmotif dot */
.dot{
display:inline-block;width:8px;height:8px;border-radius:50%;
background:var(--dot);flex:0 0 auto;
box-shadow:0 0 0 3px color-mix(in srgb,var(--sprout) 22%,transparent);
}
.dot--pulse{animation:pulse 2.4s ease-in-out infinite;}
@keyframes pulse{0%,100%{opacity:1;}50%{opacity:.4;}}
@media (prefers-reduced-motion:reduce){
.dot--pulse{animation:none;}
html{scroll-behavior:auto;}
}
+166
View File
@@ -0,0 +1,166 @@
/* ==========================================================================
thepotato.tech — Components: nav, buttons, chips, cards, post rows,
breadcrumb, prose, footer
========================================================================== */
/* --- Navbar --- */
.navbar{height:var(--nav-h);display:flex;align-items:center;border-bottom:1px solid var(--border);}
.navbar__inner{
max-width:var(--max);margin:0 auto;width:100%;padding:0 var(--s6);
display:flex;align-items:center;justify-content:space-between;gap:var(--s4);
}
.navbar__logo{
display:flex;align-items:center;gap:var(--s3);
font-family:var(--font-mono);font-weight:700;color:var(--text);font-size:var(--text-lg);
}
.navbar__logo .spud{font-size:1.15rem;line-height:1;}
.navbar__logo .shrug{color:var(--text-faint);font-weight:400;font-size:var(--text-sm);}
.navbar__logo:hover{color:var(--russet);}
.navbar__links{
display:flex;align-items:center;gap:var(--s6);list-style:none;
font-family:var(--font-mono);font-size:var(--text-sm);
}
.navbar__links a{color:var(--text-muted);}
.navbar__links a:hover,.navbar__links a[aria-current="page"]{color:var(--russet);}
.navbar__hamburger{display:none;background:none;border:0;color:var(--text);cursor:pointer;padding:var(--s2);}
.theme-toggle{
background:var(--bg-elevated);border:1px solid var(--border-strong);
color:var(--text);width:34px;height:34px;border-radius:999px;cursor:pointer;
display:inline-flex;align-items:center;justify-content:center;transition:border-color var(--tr);
}
.theme-toggle:hover{border-color:var(--russet);}
.theme-toggle .icon-moon{display:none;}
:root[data-theme="dark"] .theme-toggle .icon-sun{display:none;}
:root[data-theme="dark"] .theme-toggle .icon-moon{display:inline;}
@media (prefers-color-scheme:dark){
:root:not([data-theme="light"]) .theme-toggle .icon-sun{display:none;}
:root:not([data-theme="light"]) .theme-toggle .icon-moon{display:inline;}
}
/* --- Buttons --- */
.btn{
font-family:var(--font-mono);font-size:var(--text-sm);font-weight:700;
padding:.7rem 1.3rem;border-radius:999px;transition:all var(--tr);
border:1px solid transparent;display:inline-flex;align-items:center;gap:var(--s2);cursor:pointer;
}
.btn--primary{background:var(--russet);color:#fff;}
.btn--primary:hover{background:var(--russet-deep);color:#fff;transform:translateY(-1px);}
.btn--ghost{border-color:var(--border-strong);color:var(--text);}
.btn--ghost:hover{border-color:var(--russet);color:var(--russet);}
/* --- Chips --- */
.chip{
display:inline-block;font-family:var(--font-mono);font-size:var(--text-xs);
letter-spacing:.06em;text-transform:uppercase;padding:.15rem .5rem;border-radius:999px;
}
.chip--draft{color:var(--violet);border:1px solid color-mix(in srgb,var(--violet) 45%,transparent);}
.chip--tag{color:var(--text-muted);border:1px solid var(--border-strong);}
/* --- Section header --- */
.section-head{margin-bottom:var(--s10);}
.section-head h2{font-size:var(--text-3xl);}
.section-head p{color:var(--text-muted);max-width:var(--max-text);margin-top:var(--s3);}
/* --- Post list rows --- */
.posts{display:flex;flex-direction:column;}
.post-row{
display:grid;grid-template-columns:130px 1fr;gap:var(--s6);
padding:var(--s6) 0;border-top:1px solid var(--border);align-items:baseline;
}
.posts .post-row:last-child{border-bottom:1px solid var(--border);}
.post-row__meta{
font-family:var(--font-mono);font-size:var(--text-sm);color:var(--text-faint);
display:flex;flex-direction:column;gap:var(--s2);
}
.post-row__title{
font-family:var(--font-mono);font-size:var(--text-xl);font-weight:700;color:var(--text);
margin-bottom:var(--s2);transition:color var(--tr);text-wrap:balance;
}
.post-row:hover .post-row__title{color:var(--russet);}
.post-row__dek{color:var(--text-muted);font-size:var(--text-base);max-width:60ch;}
/* --- Cards (projects / now) --- */
.card-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));gap:var(--s6);}
.card{
border:1px solid var(--border);border-radius:var(--radius-lg);
padding:var(--s6);background:var(--bg-elevated);transition:border-color var(--tr),transform var(--tr);
}
.card:hover{border-color:var(--border-strong);transform:translateY(-2px);}
.card__title{font-size:var(--text-lg);margin-bottom:var(--s3);display:flex;align-items:center;gap:var(--s3);}
.card__title .icon{color:var(--sprout);width:1.1em;text-align:center;}
.card__body{color:var(--text-muted);font-size:var(--text-base);}
.card__body a{text-decoration:underline;text-underline-offset:2px;}
/* --- Breadcrumb --- */
.breadcrumb{
font-family:var(--font-mono);font-size:var(--text-sm);color:var(--text-faint);
margin-bottom:var(--s6);display:flex;gap:var(--s2);align-items:center;flex-wrap:wrap;
}
.breadcrumb a{color:var(--text-muted);}
.breadcrumb a:hover{color:var(--russet);}
/* --- Prose (post body) --- */
.prose{max-width:var(--max-text);margin:0 auto;}
.prose>*+*{margin-top:var(--s6);}
.prose p{color:var(--text);}
.prose h2{font-size:var(--text-2xl);margin-top:var(--s12);}
.prose h2::before{content:"// ";color:var(--russet);}
.prose h3{font-size:var(--text-xl);margin-top:var(--s10);}
.prose a{text-decoration:underline;text-decoration-color:color-mix(in srgb,var(--russet) 45%,transparent);text-underline-offset:3px;}
.prose a:hover{text-decoration-color:var(--russet);}
.prose ul,.prose ol{padding-left:1.4rem;}
.prose li{margin-bottom:var(--s2);}
.prose li::marker{color:var(--sprout);}
.prose blockquote{
border-left:3px solid var(--sprout);padding:var(--s2) 0 var(--s2) var(--s5);
color:var(--text-muted);font-style:italic;
}
.prose img{border-radius:var(--radius-lg);border:1px solid var(--border);margin:var(--s4) 0;}
.prose figure{margin:var(--s6) 0;}
.prose figcaption{font-family:var(--font-mono);font-size:var(--text-sm);color:var(--text-faint);margin-top:var(--s2);text-align:center;}
.prose code{
font-family:var(--font-mono);font-size:.9em;background:var(--bg-surface);
border:1px solid var(--border);border-radius:5px;padding:.1em .4em;
}
.prose pre{
font-family:var(--font-mono);font-size:var(--text-sm);background:var(--bg-soil);
color:var(--text-on-soil);padding:var(--s5);border-radius:var(--radius-lg);
overflow-x:auto;line-height:1.55;
}
.prose pre code{background:none;border:0;padding:0;color:inherit;font-size:inherit;}
.prose hr{border:0;border-top:1px solid var(--border);margin:var(--s10) 0;}
.prose table{width:100%;border-collapse:collapse;font-size:var(--text-base);}
.prose th,.prose td{border:1px solid var(--border);padding:var(--s2) var(--s3);text-align:left;}
.prose th{font-family:var(--font-mono);background:var(--bg-surface);}
/* --- Post footer --- */
.post-foot{
max-width:var(--max-text);margin:var(--s16) auto 0;padding-top:var(--s6);
border-top:1px solid var(--border);font-family:var(--font-mono);font-size:var(--text-sm);
color:var(--text-muted);display:flex;justify-content:space-between;gap:var(--s4);flex-wrap:wrap;
}
/* --- Footer --- */
.footer{border-top:1px solid var(--border);padding:var(--s12) 0;background:var(--bg-surface);}
.footer__inner{
max-width:var(--max);margin:0 auto;padding:0 var(--s6);
display:flex;justify-content:space-between;gap:var(--s6);flex-wrap:wrap;align-items:flex-start;
}
.footer__note{font-family:var(--font-mono);font-size:var(--text-sm);color:var(--text-faint);max-width:46ch;line-height:1.7;}
.footer__links{display:flex;gap:var(--s5);font-family:var(--font-mono);font-size:var(--text-sm);flex-wrap:wrap;}
.footer__links a{color:var(--text-muted);}
.footer__links a:hover{color:var(--russet);}
@media (max-width:720px){
.post-row{grid-template-columns:1fr;gap:var(--s2);}
.navbar__hamburger{display:inline-flex;}
.navbar__logo .shrug{display:none;}
.navbar__links{
position:absolute;top:var(--nav-h);left:0;right:0;flex-direction:column;align-items:flex-start;
gap:var(--s4);padding:var(--s5) var(--s6);background:var(--bg-elevated);
border-bottom:1px solid var(--border);box-shadow:var(--shadow-md);
display:none;z-index:150;
}
.navbar__links.is-open{display:flex;}
}
+51
View File
@@ -0,0 +1,51 @@
/* ==========================================================================
thepotato.tech — Layouts: hero, home sections, page hero, soil strip
========================================================================== */
/* --- Home hero --- */
.hero{padding:var(--s24) 0 var(--s16);}
.hero__eyebrow{margin-bottom:var(--s5);}
.hero__title{font-size:clamp(2.6rem,7vw,var(--text-6xl));line-height:1.05;margin-bottom:var(--s6);}
.hero .grad{
background:linear-gradient(102deg,var(--russet),var(--sprout-bright));
-webkit-background-clip:text;background-clip:text;color:transparent;
}
.hero__lede{font-size:var(--text-xl);max-width:var(--max-text);color:var(--text-muted);}
.hero__lede p{margin-bottom:var(--s4);}
.hero__lede strong{color:var(--text);}
.hero__lede a{text-decoration:underline;text-underline-offset:3px;text-decoration-color:color-mix(in srgb,var(--russet) 45%,transparent);}
.actions{display:flex;gap:var(--s4);flex-wrap:wrap;align-items:center;margin-top:var(--s8);}
.social-inline{
display:flex;gap:var(--s5);margin-top:var(--s10);
font-family:var(--font-mono);font-size:var(--text-sm);flex-wrap:wrap;
}
.social-inline a{color:var(--text-muted);display:inline-flex;gap:var(--s2);align-items:center;}
.social-inline a:hover{color:var(--sprout);}
/* --- "Now" / soil strip --- */
.soil{background:var(--bg-soil);color:var(--text-on-soil);}
.soil h1,.soil h2,.soil h3{color:var(--text-on-soil);}
.soil .section-label{color:var(--sprout-bright);}
.soil p{color:var(--text-on-soil-muted);}
.now-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(230px,1fr));gap:var(--s6);}
.now-card{border:1px solid rgba(255,255,255,.09);border-radius:var(--radius-lg);padding:var(--s6);background:rgba(255,255,255,.02);}
.now-card h3{font-size:var(--text-base);margin-bottom:var(--s2);display:flex;align-items:center;gap:var(--s2);}
.now-card p{font-size:var(--text-sm);}
.now-card .k{font-family:var(--font-mono);color:var(--sprout-bright);}
/* --- Page hero (single / list) --- */
.page-hero{padding:var(--s16) 0 var(--s10);border-bottom:1px solid var(--border);}
.page-hero__eyebrow{margin-bottom:var(--s5);}
.page-hero__title{font-size:clamp(2rem,5vw,var(--text-5xl));line-height:1.08;margin:var(--s4) 0 var(--s5);}
.page-hero__subtitle{font-size:var(--text-xl);color:var(--text-muted);max-width:var(--max-text);}
.page-hero__meta{
font-family:var(--font-mono);font-size:var(--text-sm);color:var(--text-muted);
display:flex;gap:var(--s4);flex-wrap:wrap;align-items:center;margin-top:var(--s4);
}
@media (max-width:640px){
.hero{padding:var(--s16) 0 var(--s12);}
.section{padding:var(--s16) 0;}
}
+122
View File
@@ -0,0 +1,122 @@
/* ==========================================================================
thepotato.tech — Design Tokens
Forked from the shrug.host editorial system, recolored to a potato palette:
russet skin, sprout-green, a rare purple-potato pop. Potato-flesh light,
soil dark. Light mode is the default; dark overrides via [data-theme="dark"]
and prefers-color-scheme.
========================================================================== */
/* --- Self-hosted fonts (no external requests) --- */
@font-face{
font-family:'Source Serif 4';
src:url('/fonts/SourceSerif4.woff2') format('woff2');
font-weight:200 900; font-style:normal; font-display:swap;
}
@font-face{
font-family:'Source Serif 4';
src:url('/fonts/SourceSerif4-Italic.woff2') format('woff2');
font-weight:200 900; font-style:italic; font-display:swap;
}
@font-face{
font-family:'Commit Mono';
src:url('/fonts/CommitMono-400-Regular.woff2') format('woff2');
font-weight:400; font-style:normal; font-display:swap;
}
@font-face{
font-family:'Commit Mono';
src:url('/fonts/CommitMono-400-Italic.woff2') format('woff2');
font-weight:400; font-style:italic; font-display:swap;
}
@font-face{
font-family:'Commit Mono';
src:url('/fonts/CommitMono-700-Regular.woff2') format('woff2');
font-weight:700; font-style:normal; font-display:swap;
}
@font-face{
font-family:'Commit Mono';
src:url('/fonts/CommitMono-700-Italic.woff2') format('woff2');
font-weight:700; font-style:italic; font-display:swap;
}
/* --- Light mode tokens (default) --- */
:root{
/* Potato palette */
--russet:#8a5622; /* potato skin — links, CTA */
--russet-deep:#6f4318;
--sprout:#697a2b; /* light-struck / sprouting green — leitmotif dot */
--sprout-bright:#7d9134;
--violet:#6a4a86; /* purple-potato — rare pop (draft chips) */
/* Backgrounds */
--bg:#efe7d6; /* potato-flesh paper */
--bg-surface:#e7dcc5;
--bg-elevated:#f6efe1;
--bg-soil:#241d12; /* dark inset sections */
/* Text */
--text:#241d12; /* soil */
--text-muted:#6d5f45;
--text-faint:#93856a;
--text-on-soil:#f2ead8;
--text-on-soil-muted:#c3b591;
/* Borders */
--border:#d8cbac;
--border-strong:#c4b492;
/* Semantic */
--link:var(--russet);
--link-hover:var(--russet-deep);
--dot:var(--sprout);
/* Shadows */
--shadow-sm:0 1px 2px rgba(60,42,16,.10);
--shadow-md:0 6px 20px rgba(60,42,16,.10),0 2px 6px rgba(60,42,16,.06);
/* Typography */
--font-mono:'Commit Mono','JetBrains Mono',ui-monospace,monospace;
--font-serif:'Source Serif 4',Georgia,ui-serif,serif;
--text-xs:.75rem; --text-sm:.875rem; --text-base:1rem; --text-lg:1.125rem;
--text-xl:1.25rem; --text-2xl:1.5rem; --text-3xl:1.875rem; --text-4xl:2.25rem;
--text-5xl:3rem; --text-6xl:3.9rem;
/* Spacing */
--s1:.25rem; --s2:.5rem; --s3:.75rem; --s4:1rem; --s5:1.25rem; --s6:1.5rem;
--s8:2rem; --s10:2.5rem; --s12:3rem; --s16:4rem; --s20:5rem; --s24:6rem;
/* Layout */
--max:1080px; --max-text:680px;
--radius:8px; --radius-lg:14px;
--nav-h:60px;
--tr:.18s ease;
/* Film-grain overlay */
--noise:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='200' height='200'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='200' height='200' filter='url(%23n)' opacity='0.05'/%3E%3C/svg%3E");
}
/* --- Dark mode: soil at night --- */
@media (prefers-color-scheme:dark){
:root:not([data-theme="light"]){
--russet:#c8823f; --russet-deep:#dd9a57;
--sprout:#9fb653; --sprout-bright:#b6cf63; --violet:#b192d6;
--bg:#17130d; --bg-surface:#201a11; --bg-elevated:#2a2216; --bg-soil:#0e0b07;
--text:#efe6d4; --text-muted:#b6a884; --text-faint:#8a7c60;
--text-on-soil:#efe6d4; --text-on-soil-muted:#b6a884;
--border:#33291b; --border-strong:#463a26;
--shadow-sm:0 1px 2px rgba(0,0,0,.4);
--shadow-md:0 6px 20px rgba(0,0,0,.5),0 2px 6px rgba(0,0,0,.3);
--noise:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='200' height='200'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='200' height='200' filter='url(%23n)' opacity='0.08'/%3E%3C/svg%3E");
}
}
:root[data-theme="dark"]{
--russet:#c8823f; --russet-deep:#dd9a57;
--sprout:#9fb653; --sprout-bright:#b6cf63; --violet:#b192d6;
--bg:#17130d; --bg-surface:#201a11; --bg-elevated:#2a2216; --bg-soil:#0e0b07;
--text:#efe6d4; --text-muted:#b6a884; --text-faint:#8a7c60;
--text-on-soil:#efe6d4; --text-on-soil-muted:#b6a884;
--border:#33291b; --border-strong:#463a26;
--shadow-sm:0 1px 2px rgba(0,0,0,.4);
--shadow-md:0 6px 20px rgba(0,0,0,.5),0 2px 6px rgba(0,0,0,.3);
--noise:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='200' height='200'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='200' height='200' filter='url(%23n)' opacity='0.08'/%3E%3C/svg%3E");
}
-130
View File
@@ -1,130 +0,0 @@
$primary-color: #115c9e;
$hover-color: #5E7191;
html, body {
border-top-color: $primary-color;
}
div.footer {
border-bottom-color: $primary-color;
}
.container a {
color: $primary-color;
}
.container a:hover {
color: $hover-color;
}
.panel-default h4 a, h5 a {
color: $primary-color;
}
figure.inline {
display: inline-flex;
justify-content: center;
align-items: center;
height: auto;
width: 90%;
}
figure.inline-40 {
display: inline-flex;
height: auto;
width: 40%;
}
figure.inline-60 {
display: inline-flex;
height: auto;
width: 60%;
}
figure.inline-80 {
display: inline-flex;
height: auto;
width: 80%;
}
figure.right {
float: right;
}
figure.left {
float: left;
}
figure > img {
display: inline-flex;
width: 90%;
}
h2, h3 {
clear: both;
}
h5 {
font-weight: bold
}
.clearfix {
overflow: auto;
}
.clearfix::after {
content: "";
clear: both;
display: table;
}
/* Projects page styling */
.project-card {
background: #fff;
border: 1px solid #e8e8e8;
border-radius: 8px;
padding: 1.5rem;
height: 100%;
transition: box-shadow 0.3s ease, transform 0.2s ease;
display: flex;
flex-direction: column;
}
.project-card:hover {
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
transform: translateY(-2px);
}
.project-header {
display: flex;
align-items: center;
margin-bottom: 1rem;
}
.project-icon {
font-size: 1.8rem;
color: $primary-color;
margin-right: 1rem;
min-width: 2rem;
text-align: center;
}
.project-title {
margin: 0;
flex: 1;
}
.project-title a {
color: $primary-color;
text-decoration: none;
font-weight: 600;
}
.project-title a:hover {
color: $hover-color;
text-decoration: none;
}
.project-description {
color: #666;
line-height: 1.6;
margin: 0;
flex: 1;
text-align: left;
}
+34 -22
View File
@@ -1,40 +1,52 @@
#baseURL = "https://drop1.neilhanlon.me:1313/"
baseURL = "/"
languageCode = "en-US"
title = "Neil Hanlon"
# Enable comments by entering your Disqus shortname
disqusShortname = ""
# Enable Google Analytics by entering your tracking code
# googleAnalytics = ""
theme = "hugo-sustain"
enableRobotsTXT = true
[permalinks]
post = "/:year/:month/:day/:slug"
[params]
avatar = "img/profile.png"
author = "Neil Hanlon"
description = "Neil Hanlon - Senior Network/DevOps/Infrastructure Engineer"
description = "Neil Hanlon Senior Network/DevOps/Infrastructure Engineer"
hero_eyebrow = "neil@potato:~$ whoami"
# Custom assets can be linked with their paths relative to static/
custom_css = []
custom_scss = ['sass/overrides.scss']
custom_js = []
# "What's running" cards on the homepage
[[params.now]]
title = "Consulting"
body = "Infrastructure for growing tech companies via <span class=\"k\">shrugpw.com</span> — compliance, migrations, networks done right."
[[params.now]]
title = "Rocky Linux"
body = "Still building and maintaining the infrastructure. Open source is the good part of this whole thing."
[[params.now]]
title = "The homelab"
body = "FreeIPA, FreeRADIUS, an SSID with a potato in the name, and far too many little agents writing to a database."
[params.social]
Github = "NeilHanlon"
Email = "neil@shrug.pw"
Bluesky = "@shrug.pw"
LinkedIn = "hanlonneil"
Signal = "hScowCEn-H1GKkCrare0m_xOEPqTWxt51pW9SI2bU5TlXrMW9QFD6qHBLqG1jDq9"
Keybase = "nhanlon"
#Stackoverflow = "username"
#Medium = "hanlon.neil"
Instagram = "noellathekitty"
Github = "NeilHanlon"
Email = "neil@shrug.pw"
Bluesky = "@shrug.pw"
LinkedIn = "hanlonneil"
Signal = "hScowCEn-H1GKkCrare0m_xOEPqTWxt51pW9SI2bU5TlXrMW9QFD6qHBLqG1jDq9"
Keybase = "nhanlon"
Instagram = "noellathekitty"
[params.volunteer]
Rocky = "https://rockylinux.org"
Rocky = "https://rockylinux.org"
[markup]
[markup.goldmark.renderer]
unsafe = true
[markup.highlight]
noClasses = false
guessSyntax = true
[outputs]
home = ["HTML", "RSS"]
[taxonomies]
tag = "tags"
category = "categories"
## Main Menu
[[menu.main]]
+17
View File
@@ -0,0 +1,17 @@
<!doctype html>
<html lang="{{ .Site.LanguageCode | default "en" }}">
<head>
{{- partial "head.html" . }}
</head>
<body class="{{ block "body-class" . }}{{ end }}">
<a href="#main-content" class="skip-link">Skip to content</a>
{{- partial "navbar.html" . }}
<main class="site-main" id="main-content">
{{- block "main" . }}{{- end }}
</main>
{{- partial "footer.html" . }}
</body>
</html>
+33
View File
@@ -0,0 +1,33 @@
{{ define "main" }}
<div class="page-hero">
<div class="container">
<p class="page-hero__eyebrow eyebrow">
<span class="dot dot--pulse" aria-hidden="true"></span>
{{ with .Params.eyebrow }}{{ . }}{{ else }}~/{{ .Section | default "posts" }}{{ end }}
</p>
<h1 class="page-hero__title">{{ .Title }}</h1>
{{- with .Description }}<p class="page-hero__subtitle">{{ . }}</p>{{ end }}
</div>
</div>
<div class="section">
<div class="container">
{{- with .Content }}<div class="prose" style="margin-bottom:var(--s10);">{{ . }}</div>{{ end }}
<div class="posts">
{{- range .Pages.ByDate.Reverse }}
<a href="{{ .RelPermalink }}" class="post-row">
<div class="post-row__meta">
<time datetime="{{ .Date.Format "2006-01-02" }}">{{ .Date.Format "Jan 2, 2006" }}</time>
{{- if .Draft }}<span class="chip chip--draft">draft</span>
{{- else }}{{ with .Params.tags }}<span class="chip chip--tag">{{ index . 0 }}</span>{{ end }}{{ end }}
</div>
<div>
<div class="post-row__title">{{ .Title }}</div>
<p class="post-row__dek">{{ .Description | default .Summary | truncate 180 }}</p>
</div>
</a>
{{- end }}
</div>
</div>
</div>
{{ end }}
+24 -21
View File
@@ -1,25 +1,28 @@
{{ define "main" }}
<!-- Header -->
{{ partial "header" . }}
<div class="page-hero">
<div class="container">
<section id="projects">
<h4 class="my-5">{{ .Site.Data.projects.name }}</h4>
<div class="row">
{{ range $el := .Site.Data.projects.source }}
<div class="col-md-6 mb-4">
<div class="project-card">
<div class="project-header">
<i class="{{ .icon }} project-icon"></i>
<h5 class="project-title">
<a href="{{ .url }}" target="_blank">{{ $el.name }}</a>
</h5>
</div>
<p class="project-description">{{ $el.description }}</p>
</div>
</div>
{{ end }}
</div>
</section>
<p class="page-hero__eyebrow eyebrow">
<span class="dot dot--pulse" aria-hidden="true"></span> ~/projects
</p>
<h1 class="page-hero__title">{{ .Title }}</h1>
<p class="page-hero__subtitle">Open source, governance, and infrastructure I help keep running.</p>
</div>
</div>
<div class="section">
<div class="container">
{{- with .Content }}<div class="prose" style="margin-bottom:var(--s10);">{{ . }}</div>{{ end }}
<div class="card-grid">
{{- range .Site.Data.projects.source }}
<div class="card">
<h3 class="card__title"><span class="dot" aria-hidden="true"></span> {{ .name }}</h3>
<div class="card__body">
<p>{{ .description }}</p>
{{- with .url }}<p style="margin-top:var(--s3);"><a href="{{ . }}" rel="noopener">{{ . | replaceRE "^https?://" "" | replaceRE "/$" "" }} →</a></p>{{ end }}
</div>
</div>
{{- end }}
</div>
</div>
</div>
{{ end }}
+43
View File
@@ -0,0 +1,43 @@
{{ define "main" }}
<article>
<div class="page-hero">
<div class="container">
<nav class="breadcrumb" aria-label="Breadcrumb">
<a href="/">Home</a>
<span aria-hidden="true"></span>
{{- if .CurrentSection.Title }}
<a href="{{ .CurrentSection.RelPermalink }}">{{ .CurrentSection.Title }}</a>
<span aria-hidden="true"></span>
{{- end }}
<span aria-current="page">{{ .Title }}</span>
</nav>
<p class="page-hero__eyebrow eyebrow">
<span class="dot dot--pulse" aria-hidden="true"></span>
{{ with .Params.eyebrow }}{{ . }}{{ else }}~/{{ .Section }}{{ end }}
</p>
<h1 class="page-hero__title">{{ .Title }}</h1>
{{- with .Description }}<p class="page-hero__subtitle">{{ . }}</p>{{ end }}
<div class="page-hero__meta">
<time datetime="{{ .Date.Format "2006-01-02" }}">{{ .Date.Format "January 2, 2006" }}</time>
<span aria-hidden="true">·</span>
<span>{{ .ReadingTime }} min read</span>
{{- if .Draft }}<span aria-hidden="true">·</span><span class="chip chip--draft">draft</span>{{ end }}
</div>
</div>
</div>
<div class="section">
<div class="container">
<div class="prose">
{{ .Content }}
</div>
<div class="post-foot">
<span>
{{- with .Params.tags }}Filed under {{ delimit . " · " }}{{ else }}{{ .Site.Params.author }}{{ end -}}
</span>
<a href="{{ .CurrentSection.RelPermalink }}">← All posts</a>
</div>
</div>
</div>
</article>
{{ end }}
+77
View File
@@ -0,0 +1,77 @@
{{ define "body-class" }}page-home{{ end }}
{{ define "main" }}
<header class="hero">
<div class="container">
<p class="hero__eyebrow eyebrow">
<span class="dot dot--pulse" aria-hidden="true"></span>
{{ .Site.Params.hero_eyebrow | default "neil@potato:~$ whoami" }}
</p>
<h1 class="hero__title">
{{ .Site.Params.author }} —<br>
infrastructure, networks, systems<br>
&amp; the occasional <span class="grad">potato</span>.
</h1>
<div class="hero__lede">
{{ .Content }}
</div>
<div class="actions">
<a class="btn btn--primary" href="/posts/">Read the blog →</a>
<a class="btn btn--ghost" href="/projects/">Projects</a>
<a class="btn btn--ghost" href="https://shrugpw.com" rel="noopener">Hire me →</a>
</div>
<div class="social-inline">
{{- with .Site.Params.social.github }}<a href="https://github.com/{{ . }}" rel="me noopener"><span class="dot" aria-hidden="true"></span> github/{{ . }}</a>{{ end }}
{{- with .Site.Params.social.bluesky }}<a href="https://bsky.app/profile/{{ replace . "@" "" }}" rel="me noopener">bsky/{{ . }}</a>{{ end }}
{{- with .Site.Params.social.instagram }}<a href="https://instagram.com/{{ . }}" rel="noopener">the cat's instagram</a>{{ end }}
{{- with .Site.Params.social.email }}<a href="mailto:{{ . }}">{{ . }}</a>{{ end }}
</div>
</div>
</header>
<section class="section section--surface" aria-labelledby="writing-heading">
<div class="container">
<div class="section-head">
<span class="section-label">// writing</span>
<h2 id="writing-heading">Latest posts</h2>
<p>Homelab, infrastructure, and the occasional life event that turns into a systems-design problem.</p>
</div>
<div class="posts">
{{- range first 8 (where .Site.RegularPages "Section" "posts").ByDate.Reverse }}
<a href="{{ .RelPermalink }}" class="post-row">
<div class="post-row__meta">
<time datetime="{{ .Date.Format "2006-01-02" }}">{{ .Date.Format "Jan 2, 2006" }}</time>
{{- if .Draft }}<span class="chip chip--draft">draft</span>
{{- else }}{{ with .Params.tags }}<span class="chip chip--tag">{{ index . 0 }}</span>{{ end }}{{ end }}
</div>
<div>
<div class="post-row__title">{{ .Title }}</div>
<p class="post-row__dek">{{ .Description | default .Summary | truncate 180 }}</p>
</div>
</a>
{{- end }}
</div>
</div>
</section>
{{- with .Site.Params.now }}
<section class="section soil" aria-labelledby="now-heading">
<div class="container">
<div class="section-head">
<span class="section-label">// now</span>
<h2 id="now-heading">What's running</h2>
</div>
<div class="now-grid">
{{- range . }}
<div class="now-card">
<h3><span class="dot" aria-hidden="true"></span> {{ .title }}</h3>
<p>{{ .body | safeHTML }}</p>
</div>
{{- end }}
</div>
</div>
</section>
{{- end }}
{{ end }}
+15
View File
@@ -0,0 +1,15 @@
<footer class="footer" aria-label="Site footer">
<div class="footer__inner">
<p class="footer__note">
Built with Hugo. No trackers, no popups, occasionally a typo left in on purpose.
<span aria-hidden="true">¯\_(ツ)_/¯</span><br>
&copy; {{ now.Year }} {{ .Site.Params.author }}.
</p>
<div class="footer__links">
{{- with .Site.Params.social.github }}<a href="https://github.com/{{ . }}" rel="me noopener">github</a>{{ end }}
{{- with .Site.Params.social.bluesky }}<a href="https://bsky.app/profile/{{ replace . "@" "" }}" rel="me noopener">bluesky</a>{{ end }}
{{- with .OutputFormats.Get "rss" }}<a href="{{ .RelPermalink }}">rss</a>{{ end }}
<a href="https://shrugpw.com" rel="noopener">consulting</a>
</div>
</div>
</footer>
+45
View File
@@ -0,0 +1,45 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="color-scheme" content="light dark">
<title>{{ if .IsHome }}{{ .Site.Title }} — {{ .Site.Params.description }}{{ else }}{{ .Title }} | {{ .Site.Title }}{{ end }}</title>
<meta name="description" content="{{ with .Description }}{{ . }}{{ else }}{{ .Site.Params.description }}{{ end }}">
<meta name="author" content="{{ .Site.Params.author }}">
{{- partial "opengraph.html" . }}
{{- partial "twitter_cards.html" . }}
{{- partial "schema.html" . }}
<link rel="canonical" href="{{ .Permalink }}">
<!-- Favicon -->
<link rel="icon" type="image/svg+xml" href="/favicon.svg">
{{- with .OutputFormats.Get "rss" }}
<link rel="alternate" type="application/rss+xml" title="{{ $.Site.Title }}" href="{{ .Permalink }}">
{{- end }}
<!-- Preload critical fonts -->
<link rel="preload" href="/fonts/CommitMono-700-Regular.woff2" as="font" type="font/woff2" crossorigin>
<link rel="preload" href="/fonts/SourceSerif4.woff2" as="font" type="font/woff2" crossorigin>
<!-- Styles -->
{{- $tokens := resources.Get "css/tokens.css" }}
{{- $base := resources.Get "css/base.css" }}
{{- $components := resources.Get "css/components.css" }}
{{- $layouts := resources.Get "css/layouts.css" }}
{{- $css := slice $tokens $base $components $layouts | resources.Concat "css/main.css" }}
{{- if hugo.IsProduction }}
{{- $css = $css | resources.Minify | resources.Fingerprint }}
<link rel="stylesheet" href="{{ $css.RelPermalink }}" integrity="{{ $css.Data.Integrity }}">
{{- else }}
<link rel="stylesheet" href="{{ $css.RelPermalink }}">
{{- end }}
<!-- Theme: read preference before render to avoid flash -->
<script>
(function(){
var t = localStorage.getItem('potato-theme');
if (t) { document.documentElement.setAttribute('data-theme', t); }
})();
</script>
+58
View File
@@ -0,0 +1,58 @@
<nav class="navbar" aria-label="Main navigation">
<div class="navbar__inner">
<a href="/" class="navbar__logo" aria-label="{{ .Site.Title }} home">
<span class="spud" aria-hidden="true">🥔</span>
<span>thepotato.tech</span>
<span class="shrug" aria-hidden="true">¯\_(ツ)_/¯</span>
</a>
<button class="navbar__hamburger" aria-label="Toggle navigation" aria-expanded="false" id="nav-hamburger">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" aria-hidden="true">
<line x1="2" y1="5" x2="18" y2="5"/><line x1="2" y1="10" x2="18" y2="10"/><line x1="2" y1="15" x2="18" y2="15"/>
</svg>
</button>
<ul class="navbar__links" id="nav-links" role="list">
{{- $cur := . }}
{{- range .Site.Menus.main }}
<li><a href="{{ .URL }}"{{ if $cur.IsMenuCurrent "main" . }} aria-current="page"{{ end }}>{{ .Name }}</a></li>
{{- end }}
<li>
<button class="theme-toggle" id="theme-toggle" aria-label="Toggle dark mode">
<svg class="icon-sun" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<circle cx="12" cy="12" r="5"/><line x1="12" y1="1" x2="12" y2="3"/><line x1="12" y1="21" x2="12" y2="23"/>
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/>
<line x1="1" y1="12" x2="3" y2="12"/><line x1="21" y1="12" x2="23" y2="12"/>
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/>
</svg>
<svg class="icon-moon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/>
</svg>
<span class="sr-only">Toggle theme</span>
</button>
</li>
</ul>
</div>
</nav>
<script>
(function(){
var btn = document.getElementById('theme-toggle');
if (btn) btn.addEventListener('click', function(){
var root = document.documentElement;
var cur = root.getAttribute('data-theme');
if (!cur) cur = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
var next = cur === 'dark' ? 'light' : 'dark';
root.setAttribute('data-theme', next);
localStorage.setItem('potato-theme', next);
});
var burger = document.getElementById('nav-hamburger');
var links = document.getElementById('nav-links');
if (burger && links) burger.addEventListener('click', function(){
var open = links.classList.toggle('is-open');
burger.setAttribute('aria-expanded', open ? 'true' : 'false');
});
})();
</script>
+16
View File
@@ -0,0 +1,16 @@
{{/* Open Graph meta tags */}}
{{- $title := cond .IsHome .Site.Title .Title -}}
{{- $desc := .Description | default .Site.Params.description -}}
{{- $type := cond .IsHome "website" "article" -}}
<meta property="og:url" content="{{ .Permalink }}">
<meta property="og:site_name" content="{{ .Site.Title }}">
<meta property="og:title" content="{{ $title }}">
<meta property="og:description" content="{{ $desc }}">
<meta property="og:locale" content="{{ .Site.Language.Lang }}">
<meta property="og:type" content="{{ $type }}">
{{- with .Params.image }}<meta property="og:image" content="{{ . | absURL }}">{{- end }}
{{- if not .IsHome }}
{{- with .Date }}<meta property="article:published_time" content="{{ .Format "2006-01-02T15:04:05Z07:00" }}">{{- end }}
{{- with .Lastmod }}<meta property="article:modified_time" content="{{ .Format "2006-01-02T15:04:05Z07:00" }}">{{- end }}
{{- range .Params.tags }}<meta property="article:tag" content="{{ . }}">{{- end }}
{{- end }}
+33
View File
@@ -0,0 +1,33 @@
{{/* Schema.org — Person on home, BlogPosting on articles */}}
{{- if .IsHome }}
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Person",
"name": "{{ .Site.Params.author }}",
"url": "{{ .Site.BaseURL }}",
"jobTitle": "Infrastructure, Network & Systems Engineer",
"description": "{{ .Site.Params.description }}",
"sameAs": [
{{- $links := slice -}}
{{- with .Site.Params.social.github }}{{ $links = $links | append (printf "https://github.com/%s" .) }}{{ end -}}
{{- with .Site.Params.social.bluesky }}{{ $links = $links | append (printf "https://bsky.app/profile/%s" (replace . "@" "")) }}{{ end -}}
{{- with .Site.Params.social.linkedin }}{{ $links = $links | append (printf "https://linkedin.com/in/%s" .) }}{{ end -}}
{{ delimit (apply $links "printf" "%q" ".") ", " }}
]
}
</script>
{{- else if .IsPage }}
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "BlogPosting",
"headline": {{ .Title | jsonify }},
"description": {{ (.Description | default .Summary) | jsonify }},
"url": "{{ .Permalink }}",
"datePublished": "{{ .Date.Format "2006-01-02T15:04:05Z07:00" }}",
"dateModified": "{{ .Lastmod.Format "2006-01-02T15:04:05Z07:00" }}",
"author": { "@type": "Person", "name": "{{ .Site.Params.author }}", "url": "{{ .Site.BaseURL }}" }
}
</script>
{{- end }}
+7
View File
@@ -0,0 +1,7 @@
{{/* Twitter/X card meta */}}
{{- $title := cond .IsHome .Site.Title .Title -}}
{{- $desc := .Description | default .Site.Params.description -}}
<meta name="twitter:card" content="{{ cond (isset .Params "image") "summary_large_image" "summary" }}">
<meta name="twitter:title" content="{{ $title }}">
<meta name="twitter:description" content="{{ $desc }}">
{{- with .Params.image }}<meta name="twitter:image" content="{{ . | absURL }}">{{- end }}
+11
View File
@@ -0,0 +1,11 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" width="64" height="64">
<rect width="64" height="64" rx="12" fill="#efe7d6"/>
<path d="M20 22c-5 4-8 12-4 22 3 7 12 11 22 8 10-3 16-13 12-24-3-8-11-12-19-11-4 .5-8 2-11 5z" fill="#8a5622"/>
<path d="M22 25c-3 3-5 9-2 16 2 5 8 8 15 6 7-2 11-9 8-16-2-6-8-9-14-8-3 .3-5 1-7 2z" fill="#a06a30" opacity="0.55"/>
<circle cx="26" cy="30" r="2.1" fill="#241d12" opacity="0.7"/>
<circle cx="38" cy="26" r="1.7" fill="#241d12" opacity="0.7"/>
<circle cx="42" cy="40" r="2" fill="#241d12" opacity="0.7"/>
<circle cx="30" cy="44" r="1.6" fill="#241d12" opacity="0.7"/>
<path d="M38 25c1-5 4-8 8-8" fill="none" stroke="#697a2b" stroke-width="3" stroke-linecap="round"/>
<circle cx="46" cy="16" r="3" fill="#7d9134"/>
</svg>

After

Width:  |  Height:  |  Size: 789 B

Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.