From dec4d22d46c8e5a94b14359e40404ccc62a746cb Mon Sep 17 00:00:00 2001 From: Kevin Date: Thu, 3 Sep 2026 10:41:48 -0400 Subject: [PATCH] Add SloanOps return banner to demo sites --- scripts/verify-build.mjs | 5 ++ src/components/DemoBanner.astro | 103 ++++++++++++++++++++++++++++++++ src/layouts/BaseLayout.astro | 2 + 3 files changed, 110 insertions(+) create mode 100644 src/components/DemoBanner.astro diff --git a/scripts/verify-build.mjs b/scripts/verify-build.mjs index df5ce6a..08ba960 100644 --- a/scripts/verify-build.mjs +++ b/scripts/verify-build.mjs @@ -12,6 +12,7 @@ const pages = await Promise.all(required.filter((file) => file.endsWith('.html') for (const { file, html } of pages) { if (html.includes('{{') || html.includes('{%')) throw new Error(`${file} contains an unresolved template placeholder.`); if (!/[^<]+<\/title>/.test(html)) throw new Error(`${file} is missing a title.`); + if (!html.includes('data-demo-banner') || !html.includes('href="https://sloanops.com/"')) throw new Error(`${file} is missing the SloanOps demo banner.`); for (const match of html.matchAll(/(?:src|href)="(\/images\/[^"?#]+)"/g)) await access(resolve(root, `.${match[1]}`)); } @@ -27,6 +28,10 @@ if (key === 'www') { if (key === 'blog') { const publishedPosts = ['first-walk-after-work', 'gentler-grooming-day', 'overnight-care-checklist', 'weekend-care-prep']; await Promise.all(publishedPosts.map((slug) => access(resolve(root, `posts/${slug}/index.html`)))); + for (const slug of publishedPosts) { + const html = await readFile(resolve(root, `posts/${slug}/index.html`), 'utf8'); + if (!html.includes('data-demo-banner') || !html.includes('href="https://sloanops.com/"')) throw new Error(`Blog post is missing the SloanOps demo banner: ${slug}.`); + } if (home.includes('Draft only example for editorial review')) throw new Error('Draft blog post appeared in the journal.'); try { await access(resolve(root, 'posts/draft-only-example/index.html')); diff --git a/src/components/DemoBanner.astro b/src/components/DemoBanner.astro new file mode 100644 index 0000000..7bd17ff --- /dev/null +++ b/src/components/DemoBanner.astro @@ -0,0 +1,103 @@ +<aside class="demo-banner" aria-label="Demo website notice" data-demo-banner> + <div class="demo-banner__inner"> + <p> + <strong>Demo website</strong> + <span>This is a sample site created by SloanOps.</span> + </p> + <a href="https://sloanops.com/"> + Return to SloanOps <span aria-hidden="true">↗</span> + </a> + </div> +</aside> + +<style> + .demo-banner { + background: #102a3a; + color: #f7fbfc; + font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + position: relative; + z-index: 50; + } + + .demo-banner__inner { + align-items: center; + display: flex; + gap: 1rem; + justify-content: space-between; + margin: auto; + min-height: 3.5rem; + padding: .4rem 0; + width: min(75rem, calc(100% - 2rem)); + } + + p { + align-items: center; + display: flex; + flex-wrap: wrap; + font-size: .88rem; + gap: .55rem; + line-height: 1.35; + margin: 0; + } + + strong { + background: rgba(255, 255, 255, .13); + border: 1px solid rgba(255, 255, 255, .24); + border-radius: 999px; + font-size: .68rem; + letter-spacing: .1em; + padding: .25rem .5rem; + text-transform: uppercase; + white-space: nowrap; + } + + .demo-banner a { + align-items: center; + background: #fff; + border: 1px solid #fff; + border-radius: 999px; + color: #102a3a; + display: inline-flex; + flex: none; + font-size: .82rem; + font-weight: 800; + gap: .4rem; + justify-content: center; + min-height: 2.75rem; + padding: .55rem .85rem; + text-decoration: none; + transition: background-color .2s ease, color .2s ease, transform .2s ease; + white-space: nowrap; + } + + .demo-banner a:hover, + .demo-banner a:focus-visible { + background: #d9f3f0; + color: #0b615b; + transform: translateY(-.08rem); + } + + @media (max-width: 36rem) { + .demo-banner__inner { + align-items: stretch; + flex-direction: column; + gap: .55rem; + padding: .7rem 0 .75rem; + } + + .demo-banner a { + align-self: flex-start; + } + } + + @media (prefers-reduced-motion: reduce) { + .demo-banner a { + transition: none; + } + + .demo-banner a:hover, + .demo-banner a:focus-visible { + transform: none; + } + } +</style> diff --git a/src/layouts/BaseLayout.astro b/src/layouts/BaseLayout.astro index ed484c5..b7bb797 100644 --- a/src/layouts/BaseLayout.astro +++ b/src/layouts/BaseLayout.astro @@ -1,5 +1,6 @@ --- import '../styles/global.css'; +import DemoBanner from '../components/DemoBanner.astro'; import Header from '../components/Header.astro'; import Footer from '../components/Footer.astro'; import { deployment, isBlog, isProduction, site } from '../lib/site-data.js'; @@ -32,6 +33,7 @@ const structuredData = isBlog ? { '@context': 'https://schema.org', '@type': 'We <script type="application/ld+json" set:html={JSON.stringify(structuredData)} /> </head> <body style={`--accent:${site.theme.accent};--ink:${site.theme.ink};--surface:${site.theme.surface}`}> + <DemoBanner /> <Header /> <main><slot /></main> <Footer />