Add shared site header across all pages

Every hand-authored HTML page now carries the same sticky top nav:
Home / Skills / Anti-Patterns / Tutorials / Gallery / GitHub. The three
future sections (skills, anti-patterns, tutorials) will land behind
these links in subsequent commits.

- Add <header class="site-header"> with a <!-- site-header v1 --> marker
  to index, cheatsheet, gallery, privacy
- Each page links public/css/sub-pages.css so it picks up the shared
  header styling (sticky, backdrop blur, accent-underlined current item)
- Gallery: drop the inline .site-header/.header-brand/.header-nav
  definitions that predate the shared component (class names conflicted)
- Cheatsheet: restructure the page title into .cheatsheet-page-header
  so it doesn't collide with the new <header class="site-header">; move
  the page container max-width/padding off <body> onto .cheatsheet-page-header,
  main, and footer so the shared sticky header spans the full viewport
- Privacy: full styling refresh using sub-pages.css tokens + a small
  inline block for the page body typography
- Add a validateSiteHeader step to scripts/build.js that fails the build
  if any of the four pages drops the marker
- Add /privacy route to server/index.js so extensionless URLs work

All four built pages pass the marker check; dev server serves all four
with 200 and the shared header.
This commit is contained in:
Paul Bakaus
2026-04-08 08:09:41 -07:00
parent 54b687d16f
commit c598a63498
6 changed files with 142 additions and 66 deletions
+37 -25
View File
@@ -17,7 +17,8 @@
<link rel="icon" type="image/svg+xml" href="./favicon.svg">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Instrument+Sans:wght@400;500;600&family=Space+Grotesk:wght@400;500;600&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Cormorant+Garamond:ital,wght@0,400;0,600;1,400&family=Instrument+Sans:wght@400;500;600&family=Space+Grotesk:wght@400;500;600&display=swap" rel="stylesheet">
<link rel="stylesheet" href="./css/sub-pages.css">
<style>
:root {
--font-body: 'Instrument Sans', system-ui, sans-serif;
@@ -38,19 +39,25 @@
}
}
* { box-sizing: border-box; margin: 0; }
body {
font-family: var(--font-body);
background: var(--color-paper);
color: var(--color-ink);
line-height: 1.5;
padding: 2rem;
max-width: 900px;
margin: 0 auto;
}
header {
.cheatsheet-page-header,
main,
footer {
max-width: 900px;
margin-left: auto;
margin-right: auto;
padding-left: 2rem;
padding-right: 2rem;
}
.cheatsheet-page-header {
margin-top: 2rem;
margin-bottom: 2rem;
padding-bottom: 1.5rem;
border-bottom: 1px solid var(--color-mist);
@@ -67,18 +74,6 @@
font-size: 0.875rem;
}
.back-link {
display: inline-block;
margin-top: 0.75rem;
font-size: 0.8125rem;
color: var(--color-accent);
text-decoration: none;
}
.back-link:hover {
text-decoration: underline;
}
.category {
margin-bottom: 2rem;
}
@@ -152,8 +147,11 @@
}
@media print {
body { padding: 1rem; max-width: none; }
.back-link { display: none; }
body { padding: 1rem; }
.site-header { display: none; }
.cheatsheet-page-header,
main,
footer { max-width: none; padding-left: 0; padding-right: 0; }
footer { display: none; }
.command { page-break-inside: avoid; }
}
@@ -166,12 +164,26 @@
}
</style>
</head>
<body>
<header>
<body class="sub-page">
<!-- site-header v1 -->
<header class="site-header" data-site-header>
<a href="/" class="site-header-brand" aria-label="Impeccable home">
<span class="site-header-brand-name">Impeccable</span>
</a>
<nav class="site-header-nav" aria-label="Primary">
<a href="/" data-nav="home">Home</a>
<a href="/skills" data-nav="skills">Skills</a>
<a href="/anti-patterns" data-nav="anti-patterns">Anti-Patterns</a>
<a href="/tutorials" data-nav="tutorials">Tutorials</a>
<a href="/gallery" data-nav="gallery">Gallery</a>
<a href="https://github.com/pbakaus/impeccable" target="_blank" rel="noopener" data-nav="github">GitHub</a>
</nav>
</header>
<div class="cheatsheet-page-header">
<h1>Impeccable Commands</h1>
<p class="subtitle">Quick reference for all 21 design commands</p>
<a href="/" class="back-link">&larr; Back to impeccable.style</a>
</header>
</div>
<main id="commands-container">
<p style="color: var(--color-ash);">Loading commands...</p>
+16 -31
View File
@@ -16,6 +16,7 @@
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Cormorant+Garamond:ital,wght@0,400;0,600;1,400;1,600&family=Instrument+Sans:wght@400;500;600&family=Space+Grotesk:wght@400;500;600&display=swap" rel="stylesheet">
<link rel="stylesheet" href="./css/sub-pages.css">
<style>
:root {
--font-display: 'Cormorant Garamond', Georgia, serif;
@@ -50,28 +51,8 @@
-webkit-font-smoothing: antialiased;
}
a { color: var(--color-accent); text-decoration: underline; text-underline-offset: 3px; }
a:hover { color: var(--color-accent-hover); }
/* Header */
.site-header {
padding: var(--spacing-sm) var(--spacing-lg);
border-bottom: 1px solid var(--color-mist);
display: flex;
align-items: center;
justify-content: space-between;
}
.site-header a { text-decoration: none; }
.header-brand {
font-family: var(--font-display);
font-size: 1.25rem;
font-weight: 600;
font-style: italic;
color: var(--color-ink);
}
.header-nav { display: flex; gap: var(--spacing-md); font-size: 0.875rem; }
.header-nav a { color: var(--color-ash); }
.header-nav a:hover { color: var(--color-ink); }
.gallery-content a { color: var(--color-accent); text-decoration: underline; text-underline-offset: 3px; }
.gallery-content a:hover { color: var(--color-accent-hover); }
/* Hero */
.hero {
@@ -201,19 +182,23 @@
grid-template-columns: 1fr;
padding: 0 var(--spacing-sm) var(--spacing-lg);
}
.site-header { padding: var(--spacing-sm); }
.header-nav { gap: var(--spacing-sm); }
}
</style>
</head>
<body>
<body class="sub-page gallery-content">
<header class="site-header">
<a href="/" class="header-brand">Impeccable</a>
<nav class="header-nav">
<a href="/#antidote">Anti-Patterns</a>
<a href="/cheatsheet">Cheatsheet</a>
<a href="https://github.com/pbakaus/impeccable">GitHub</a>
<!-- site-header v1 -->
<header class="site-header" data-site-header>
<a href="/" class="site-header-brand" aria-label="Impeccable home">
<span class="site-header-brand-name">Impeccable</span>
</a>
<nav class="site-header-nav" aria-label="Primary">
<a href="/" data-nav="home">Home</a>
<a href="/skills" data-nav="skills">Skills</a>
<a href="/anti-patterns" data-nav="anti-patterns">Anti-Patterns</a>
<a href="/tutorials" data-nav="tutorials">Tutorials</a>
<a href="/gallery" data-nav="gallery" aria-current="page">Gallery</a>
<a href="https://github.com/pbakaus/impeccable" target="_blank" rel="noopener" data-nav="github">GitHub</a>
</nav>
</header>
+17 -1
View File
@@ -38,7 +38,8 @@
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Cormorant+Garamond:ital,wght@0,300;0,400;0,600;0,700;1,300;1,400&family=Instrument+Sans:wght@400;500;600;700&family=Inter:wght@400;500;600&family=Space+Grotesk:wght@400;500;600&display=swap" rel="stylesheet">
<link rel="stylesheet" href="./css/main.css">
<link rel="stylesheet" href="./css/sub-pages.css">
</head>
<body>
<!-- Skip to main content link for keyboard users -->
@@ -47,6 +48,21 @@
<!-- Grain Overlay -->
<div class="grain-overlay" aria-hidden="true"></div>
<!-- site-header v1 -->
<header class="site-header" data-site-header>
<a href="/" class="site-header-brand" aria-label="Impeccable home">
<span class="site-header-brand-name">Impeccable</span>
</a>
<nav class="site-header-nav" aria-label="Primary">
<a href="/" data-nav="home" aria-current="page">Home</a>
<a href="/skills" data-nav="skills">Skills</a>
<a href="/anti-patterns" data-nav="anti-patterns">Anti-Patterns</a>
<a href="/tutorials" data-nav="tutorials">Tutorials</a>
<a href="/gallery" data-nav="gallery">Gallery</a>
<a href="https://github.com/pbakaus/impeccable" target="_blank" rel="noopener" data-nav="github">GitHub</a>
</nav>
</header>
<!-- Sticky Section Nav -->
<nav class="section-nav" id="section-nav" aria-label="Page sections">
<a href="#foundation" class="section-nav-item" data-section="foundation"><span class="section-nav-num">01</span><span class="section-nav-label">Foundation</span></a>
+31 -8
View File
@@ -6,17 +6,39 @@
<title>Privacy Policy - Impeccable</title>
<meta name="robots" content="noindex">
<link rel="icon" type="image/svg+xml" href="./favicon.svg">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Cormorant+Garamond:ital,wght@0,400;0,600&family=Instrument+Sans:wght@400;500;600&family=Space+Grotesk:wght@400;500;600&display=swap" rel="stylesheet">
<link rel="stylesheet" href="./css/sub-pages.css">
<style>
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; max-width: 680px; margin: 0 auto; padding: 2rem 1.5rem; line-height: 1.6; color: #1a1a1a; }
h1 { font-size: 1.8rem; margin-bottom: 0.5rem; }
h2 { font-size: 1.2rem; margin-top: 2rem; }
.updated { color: #666; font-size: 0.9rem; margin-bottom: 2rem; }
a { color: #1a1a1a; }
.back { display: inline-block; margin-bottom: 2rem; font-size: 0.9rem; }
.privacy-content { max-width: 680px; margin: 0 auto; padding: 2rem 1.5rem 4rem; }
.privacy-content h1 { font-family: var(--font-display); font-size: 2.25rem; margin-bottom: 0.5rem; letter-spacing: -0.01em; }
.privacy-content h2 { font-size: 1.125rem; margin-top: 2rem; font-weight: 600; }
.privacy-content p, .privacy-content ul { color: var(--color-charcoal); margin-top: 0.5rem; }
.privacy-content ul { padding-left: 1.25rem; }
.privacy-content li { margin-top: 0.25rem; }
.privacy-content a { color: var(--color-accent); text-decoration: underline; text-underline-offset: 3px; }
.privacy-content a:hover { color: var(--color-accent-hover); }
.privacy-content .updated { color: var(--color-ash); font-size: 0.875rem; margin-bottom: 2rem; font-style: italic; }
</style>
</head>
<body>
<a href="/" class="back">&larr; Back to impeccable.style</a>
<body class="sub-page">
<!-- site-header v1 -->
<header class="site-header" data-site-header>
<a href="/" class="site-header-brand" aria-label="Impeccable home">
<span class="site-header-brand-name">Impeccable</span>
</a>
<nav class="site-header-nav" aria-label="Primary">
<a href="/" data-nav="home">Home</a>
<a href="/skills" data-nav="skills">Skills</a>
<a href="/anti-patterns" data-nav="anti-patterns">Anti-Patterns</a>
<a href="/tutorials" data-nav="tutorials">Tutorials</a>
<a href="/gallery" data-nav="gallery">Gallery</a>
<a href="https://github.com/pbakaus/impeccable" target="_blank" rel="noopener" data-nav="github">GitHub</a>
</nav>
</header>
<main class="privacy-content">
<h1>Privacy Policy</h1>
<p class="updated">Last updated: April 6, 2026</p>
@@ -48,5 +70,6 @@
<h2>Contact</h2>
<p>Questions about this policy? Open an issue on <a href="https://github.com/pbakaus/impeccable">GitHub</a> or reach out to <a href="https://x.com/pbakaus">@pbakaus</a>.</p>
</main>
</body>
</html>
+39 -1
View File
@@ -158,6 +158,40 @@ function validateAntipatternRules(rootDir) {
return errors;
}
/**
* Validate that every hand-authored HTML page carries the shared site header.
* The partial is stamped with `<!-- site-header v1 -->` so drift is loud.
*
* Returns the number of validation errors. Build fails if > 0.
*/
function validateSiteHeader(rootDir) {
const pages = [
'public/index.html',
'public/cheatsheet.html',
'public/gallery.html',
'public/privacy.html',
];
const marker = '<!-- site-header v1 -->';
let errors = 0;
for (const rel of pages) {
const full = path.join(rootDir, rel);
if (!fs.existsSync(full)) {
console.error(`${rel} is missing`);
errors++;
continue;
}
const src = fs.readFileSync(full, 'utf-8');
if (!src.includes(marker)) {
console.error(`${rel} is missing the shared site header marker '${marker}'`);
errors++;
}
}
if (errors === 0) {
console.log(`✓ Validated site header on ${pages.length} hand-authored pages`);
}
return errors;
}
/**
* Copy directory recursively
*/
@@ -478,7 +512,11 @@ async function build() {
// Cross-validate engine rules against impeccable SKILL.md DON'Ts
const validationErrors = validateAntipatternRules(ROOT_DIR);
if (countErrors > 0 || validationErrors > 0) {
// Verify every hand-authored HTML page carries the shared site header
const headerErrors = validateSiteHeader(ROOT_DIR);
if (countErrors > 0 || validationErrors > 0 || headerErrors > 0) {
process.exit(1);
}
+2
View File
@@ -2,6 +2,7 @@ import { serve, file } from "bun";
import homepage from "../public/index.html";
import cheatsheet from "../public/cheatsheet.html";
import gallery from "../public/gallery.html";
import privacy from "../public/privacy.html";
import {
getSkills,
getCommands,
@@ -18,6 +19,7 @@ const server = serve({
"/": homepage,
"/cheatsheet": cheatsheet,
"/gallery": gallery,
"/privacy": privacy,
// Static assets - all public subdirectories
"/assets/*": async (req) => {