feat: moved big css block out of pages into external css file

This commit is contained in:
2026-03-01 07:43:46 +01:00
parent 4c21b624f2
commit 24ca2d3317
8 changed files with 201 additions and 184 deletions

View File

@@ -159,6 +159,11 @@
{ {
"from": "src/main/engine/templates", "from": "src/main/engine/templates",
"to": "templates" "to": "templates"
},
{
"from": "src/main/engine/assets",
"to": "assets",
"filter": ["*.css"]
} }
], ],
"protocols": [ "protocols": [

View File

@@ -11,6 +11,26 @@ import { CALENDAR_RUNTIME_JS } from './assets/calendarRuntime';
import { TAG_CLOUD_RUNTIME_JS } from './assets/tagCloudRuntime'; import { TAG_CLOUD_RUNTIME_JS } from './assets/tagCloudRuntime';
import { resolveRenderLanguageFromProjectPreferences, translateRender } from '../shared/i18n'; import { resolveRenderLanguageFromProjectPreferences, translateRender } from '../shared/i18n';
function readLocalAsset(filename: string): string {
const candidates = [
path.join(__dirname, 'assets', filename),
path.join(process.cwd(), 'dist', 'main', 'engine', 'assets', filename),
path.join(process.cwd(), 'src', 'main', 'engine', 'assets', filename),
];
if (typeof process.resourcesPath === 'string' && process.resourcesPath.length > 0) {
candidates.unshift(path.join(process.resourcesPath, 'assets', filename));
}
for (const candidate of candidates) {
if (fs.existsSync(candidate)) {
return fs.readFileSync(candidate, 'utf-8');
}
}
throw new Error(`Local asset not found: ${filename}`);
}
export interface PythonMacroScript { export interface PythonMacroScript {
id: string; id: string;
slug: string; slug: string;
@@ -271,6 +291,10 @@ export const PREVIEW_ASSETS: Record<string, PreviewAssetDefinition> = {
contentType: 'application/javascript; charset=utf-8', contentType: 'application/javascript; charset=utf-8',
sourceText: CALENDAR_RUNTIME_JS, sourceText: CALENDAR_RUNTIME_JS,
}, },
'bds.css': {
contentType: 'text/css; charset=utf-8',
sourceText: readLocalAsset('bds.css'),
},
}; };
export const PREVIEW_IMAGE_ASSETS = { export const PREVIEW_IMAGE_ASSETS = {

View File

@@ -0,0 +1,145 @@
:root { color-scheme: light dark; }
@media only screen and (prefers-color-scheme: dark) {
:root:not([data-theme]) { --pico-background-color: #13171f; }
}
[data-theme='dark'] { --pico-background-color: #13171f; }
body { max-width: 960px; margin: 0 auto; padding: 2rem 1rem 4rem; background: var(--pico-background-color, var(--background-color)); color: var(--pico-color, var(--color)); }
main { display: grid; gap: 1rem; }
.blog-menu { position: relative; display: flex; align-items: baseline; justify-content: space-between; gap: .75rem; border-top: 1px solid var(--pico-muted-border-color, var(--muted-border-color)); border-bottom: 1px solid var(--pico-muted-border-color, var(--muted-border-color)); padding: .4rem 0; margin: -.15rem 0 .2rem; }
.blog-menu > .blog-menu-list { width: 100%; }
.blog-menu-list { list-style: none; display: flex; flex-wrap: wrap; align-items: baseline; gap: .25rem .75rem; margin: 0; padding: 0; }
.blog-menu-item { position: relative; }
.blog-menu-link { display: inline-flex; align-items: center; color: var(--pico-muted-color, var(--muted-color)); text-decoration: none; font-size: .94rem; line-height: 1.4; padding: .2rem .1rem; }
.blog-menu-item-with-children > .blog-menu-link::after { content: '▾'; font-size: .7em; margin-left: .38rem; opacity: .72; }
.blog-menu-link:hover,
.blog-menu-link:focus-visible { color: var(--pico-color, var(--color)); text-decoration: underline; }
.blog-menu-submenu { position: absolute; top: calc(100% + .12rem); left: 0; min-width: 12rem; display: none; border: 1px solid var(--pico-muted-border-color, var(--muted-border-color)); background: var(--pico-card-background-color, var(--card-background-color)); padding: .3rem 0; z-index: 10; }
.blog-menu-submenu .blog-menu-list { flex-direction: column; flex-wrap: nowrap; gap: 0; margin: 0; }
.blog-menu-submenu .blog-menu-item { display: block; padding: 0; margin: 0; }
.blog-menu-submenu .blog-menu-link { display: block; padding: .22rem .75rem; font-size: .88rem; line-height: 1.3; }
.blog-menu-submenu .blog-menu-item a.blog-menu-link { margin: 0; }
.blog-menu-item-with-children:hover > .blog-menu-submenu,
.blog-menu-item-with-children:focus-within > .blog-menu-submenu { display: block; }
.blog-menu-calendar { position: relative; display: inline-flex; align-items: baseline; justify-content: center; margin-left: auto; align-self: baseline; flex-shrink: 0; }
.blog-menu-calendar-button { display: inline-flex; align-items: center; justify-content: center; width: auto; height: auto; margin: 0; padding: .2rem .1rem; border: 0; background: transparent; color: var(--pico-muted-color, var(--muted-color)); border-radius: 0; cursor: pointer; font: inherit; font-size: .94rem; line-height: 1.4; appearance: none; -webkit-appearance: none; vertical-align: baseline; }
.blog-menu-calendar-button svg { display: block; width: .9rem; height: .9rem; fill: none; stroke: currentColor; transform: translateY(2px); }
.blog-menu-calendar-button:hover,
.blog-menu-calendar-button:focus-visible { color: var(--pico-color, var(--color)); }
.blog-calendar-panel { position: absolute; top: calc(100% + .15rem); right: 0; width: min(17.5rem, 92vw); border: 1px solid var(--pico-muted-border-color, var(--muted-border-color)); background: var(--pico-card-background-color, var(--card-background-color)); padding: .32rem; z-index: 30; }
.blog-calendar-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: .1rem; }
.blog-calendar-header strong { font-size: .9rem; line-height: 1.2; }
.blog-calendar-close { border: 1px solid var(--pico-muted-border-color, var(--muted-border-color)); background: transparent; color: var(--pico-muted-color, var(--muted-color)); width: 1.35rem; height: 1.35rem; border-radius: .2rem; padding: 0; cursor: pointer; line-height: 1; }
.blog-calendar-close:hover,
.blog-calendar-close:focus-visible { color: var(--pico-color, var(--color)); border-color: var(--pico-color, var(--color)); }
.blog-calendar-content { display: grid; gap: .08rem; }
.blog-calendar-status { margin: .1rem 0 0; color: var(--pico-muted-color, var(--muted-color)); font-size: .74rem; }
[data-blog-calendar-root] { font-size: .86rem; }
[data-blog-calendar-root] [data-vc=header] { margin-bottom: .08rem; }
[data-blog-calendar-root] [data-vc=month],
[data-blog-calendar-root] [data-vc=year] { padding: .08rem .18rem; font-size: .9rem; line-height: 1.15; }
[data-blog-calendar-root] [data-vc=months],
[data-blog-calendar-root] [data-vc=years] { row-gap: .32rem; }
[data-blog-calendar-root] [data-vc=years] { grid-template-columns: repeat(4, minmax(0, 1fr)); }
[data-blog-calendar-root] [data-vc-months-month],
[data-blog-calendar-root] [data-vc-years-year] { height: 1.72rem; }
[data-blog-calendar-root] [data-vc-months-month],
[data-blog-calendar-root] [data-vc-years-year] { word-break: normal; white-space: nowrap; }
[data-blog-calendar-root] [data-vc-years-year] { min-width: 2.5rem; font-size: .7rem; line-height: 1; }
[data-blog-calendar-root] [data-vc-week=days] { margin-bottom: .08rem; }
[data-blog-calendar-root] [data-vc-week-day] { font-size: .68rem; line-height: .9rem; min-width: 1.45rem; }
[data-blog-calendar-root] [data-vc-date] { padding-top: 0; padding-bottom: 0; }
[data-blog-calendar-root] [data-vc-date-btn] { min-height: 1.45rem; min-width: 1.45rem; font-size: .68rem; line-height: .9rem; }
[data-blog-calendar-has-posts='true'] [data-vc-date-btn] {
border-color: hsl(var(--blog-calendar-heat-hue, 210) 85% 42% / .95);
background-color: hsl(var(--blog-calendar-heat-hue, 210) 88% 52% / var(--blog-calendar-heat-alpha, 0));
}
[data-blog-calendar-root] [data-vc-months-month][data-blog-calendar-has-posts='true'],
[data-blog-calendar-root] [data-vc-years-year][data-blog-calendar-has-posts='true'] {
background-color: hsl(var(--blog-calendar-heat-hue, 210) 88% 52% / var(--blog-calendar-heat-alpha, 0));
border-color: hsl(var(--blog-calendar-heat-hue, 210) 85% 42% / .95);
}
.post { border: 1px solid var(--pico-muted-border-color, var(--muted-border-color)); padding: 1rem; background: var(--pico-card-background-color, var(--card-background-color)); min-width: 0; }
.post pre { position: relative; overflow-x: auto; max-width: 100%; border: 1px solid var(--pico-muted-border-color, var(--muted-border-color)); border-radius: .3rem; margin: .9rem 0; padding: .85rem .9rem; background: var(--pico-code-background-color, rgba(33, 38, 45, .82)); box-sizing: border-box; }
.post pre code { display: block; font-size: .88rem; line-height: 1.5; white-space: pre; }
.code-copy-button {
position: absolute;
top: .4rem;
right: .4rem;
border: 1px solid var(--pico-muted-border-color, var(--muted-border-color));
background: var(--pico-card-background-color, var(--card-background-color));
color: var(--pico-muted-color, var(--muted-color));
border-radius: .25rem;
width: 1.8rem;
height: 1.8rem;
display: inline-flex;
align-items: center;
justify-content: center;
padding: 0;
cursor: pointer;
opacity: .88;
}
.code-copy-button:hover,
.code-copy-button:focus-visible { opacity: 1; color: var(--pico-color, var(--color)); }
.code-copy-icon { font-size: .95rem; line-height: 1; }
.code-copy-success .code-copy-button { color: var(--pico-ins-color, rgb(53, 117, 56)); border-color: var(--pico-ins-color, rgb(53, 117, 56)); }
.code-copy-failed .code-copy-button { color: var(--pico-del-color, rgb(183, 72, 72)); border-color: var(--pico-del-color, rgb(183, 72, 72)); }
.post iframe { width: 100%; min-height: 20rem; }
.macro-youtube, .macro-vimeo { margin-bottom: 1rem; }
.macro-gallery, .macro-photo-archive, .macro-tag-cloud { border: 1px dashed var(--pico-muted-border-color, var(--muted-border-color)); padding: .75rem; margin: 1rem 0; }
.gallery-container { display: grid; gap: .5rem; }
.macro-gallery.gallery-cols-1 .gallery-container { grid-template-columns: 1fr; }
.macro-gallery.gallery-cols-2 .gallery-container { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.macro-gallery.gallery-cols-3 .gallery-container { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.macro-gallery.gallery-cols-4 .gallery-container { grid-template-columns: repeat(4, minmax(0, 1fr)); }
.macro-gallery.gallery-cols-5 .gallery-container { grid-template-columns: repeat(5, minmax(0, 1fr)); }
.macro-gallery.gallery-cols-6 .gallery-container { grid-template-columns: repeat(6, minmax(0, 1fr)); }
.gallery-item, .photo-archive-item { display: block; overflow: hidden; border-radius: .25rem; }
.gallery-item img, .photo-archive-item img { display: block; width: 100%; height: auto; aspect-ratio: 1 / 1; object-fit: cover; }
.lb-nav a, .lb-nav a:hover, .lb-nav a:focus-visible { border: 0; box-shadow: none; outline: none; text-decoration: none; }
.gallery-caption { margin-top: .5rem; text-align: center; color: var(--pico-muted-color, var(--muted-color)); font-size: .92rem; }
.gallery-empty, .photo-archive-empty { color: var(--pico-muted-color, var(--muted-color)); font-style: italic; }
.photo-archive-container { display: grid; gap: 1rem; }
.photo-archive-month { display: grid; grid-template-columns: 3.25rem 1fr; gap: .75rem; align-items: start; }
.photo-archive-month-label { display: flex; justify-content: center; align-items: center; }
.photo-archive-month-label span { writing-mode: vertical-rl; transform: rotate(180deg); letter-spacing: .08em; text-transform: uppercase; color: var(--pico-muted-color, var(--muted-color)); }
.photo-archive-gallery { display: grid; gap: .5rem; grid-template-columns: repeat(4, minmax(0, 1fr)); }
.photo-archive-single-month .photo-archive-gallery { grid-template-columns: repeat(5, minmax(0, 1fr)); }
.macro-tag-cloud { min-height: 14rem; }
.tag-cloud-canvas { display: block; width: 100%; height: auto; min-height: 12rem; }
.tag-cloud-empty { color: var(--pico-muted-color, var(--muted-color)); font-style: italic; }
.archive-day-group { display: grid; grid-template-columns: 5.25rem 1fr; gap: 1.25rem; align-items: stretch; }
.archive-day-marker { display: flex; justify-content: center; align-items: center; color: var(--pico-muted-color, var(--muted-color)); }
.archive-day-marker span { writing-mode: vertical-rl; transform: rotate(180deg); letter-spacing: .16em; font-size: 1.05rem; font-weight: 600; text-transform: uppercase; }
.archive-day-posts { display: grid; gap: 1rem; }
.archive-day-separator { position: relative; height: 2px; width: 100%; color: var(--pico-color, var(--color)); border-top: 1px solid currentColor; opacity: .18; margin: .45rem 0 .65rem; }
.archive-day-separator::before { content: ''; position: absolute; inset: 0; background: linear-gradient(to right, transparent 0%, transparent 18%, currentColor 58%, transparent 92%, transparent 100%); opacity: .85; }
.single-post { margin: 0; padding: 0; background: transparent; border: 0; box-shadow: none; }
.single-post-taxonomy { display: flex; flex-wrap: wrap; gap: .4rem .45rem; margin: -.1rem 0 .2rem; }
.single-post-taxonomy-bubble {
--bubble-accent: var(--pico-ins-color, rgb(53, 117, 56));
--bubble-bg: var(--bubble-accent);
display: inline-flex;
align-items: center;
border: 1px solid var(--bubble-accent);
border-radius: 999px;
padding: .1rem .5rem;
font-size: .74rem;
line-height: 1.35;
color: #000;
background: var(--bubble-bg, var(--bubble-accent));
text-decoration: none;
}
.single-post-taxonomy-bubble:hover,
.single-post-taxonomy-bubble:focus-visible { text-decoration: underline; }
.single-post-taxonomy-bubble-category { --bubble-accent: var(--pico-ins-color, rgb(53, 117, 56)); --bubble-bg: var(--pico-ins-color, rgb(53, 117, 56)); }
.single-post-taxonomy-bubble-tag { --bubble-accent: var(--pico-del-color, rgb(183, 72, 72)); --bubble-bg: var(--pico-del-color, rgb(183, 72, 72)); }
.single-post-backlinks { display: flex; flex-wrap: wrap; gap: .4rem .45rem; align-items: center; margin-top: 1.5rem; }
.single-post-backlinks-label { font-size: .74rem; line-height: 1.35; color: var(--pico-muted-color, var(--muted-color)); margin-right: .15rem; }
.single-post-backlink-bubble { --bubble-accent: var(--pico-primary, rgb(16, 107, 193)); --bubble-bg: var(--pico-primary, rgb(16, 107, 193)); color: var(--pico-primary-inverse, #fff); }
.preview-pagination { display: flex; justify-content: space-between; align-items: center; gap: .75rem; margin-top: .25rem; }
.preview-pagination-link { color: var(--pico-muted-color, var(--muted-color)); text-decoration: none; font-size: .92rem; opacity: .72; transition: opacity .15s ease-in-out; }
.preview-pagination-link:hover,
.preview-pagination-link:focus-visible { opacity: 1; text-decoration: underline; }
.preview-pagination .spacer { flex: 1; }
.not-found { display: grid; place-items: center; min-height: 48vh; }
.not-found article { max-width: 32rem; text-align: center; }

View File

@@ -7,9 +7,9 @@
<link rel="stylesheet" href="/assets/lightbox.min.css" /> <link rel="stylesheet" href="/assets/lightbox.min.css" />
<link rel="stylesheet" href="/assets/highlight.min.css" /> <link rel="stylesheet" href="/assets/highlight.min.css" />
<link rel="stylesheet" href="/assets/vanilla-calendar.min.css" /> <link rel="stylesheet" href="/assets/vanilla-calendar.min.css" />
<link rel="stylesheet" href="/assets/bds.css" />
<link rel="alternate" type="application/rss+xml" title="RSS" href="/rss.xml" /> <link rel="alternate" type="application/rss+xml" title="RSS" href="/rss.xml" />
<link rel="alternate" type="application/atom+xml" title="Atom" href="/atom.xml" /> <link rel="alternate" type="application/atom+xml" title="Atom" href="/atom.xml" />
{% render 'partials/styles' %}
<script defer src="/assets/highlight.min.js"></script> <script defer src="/assets/highlight.min.js"></script>
<script defer src="/assets/code-enhancements.js"></script> <script defer src="/assets/code-enhancements.js"></script>
<script defer src="/assets/d3.layout.cloud.js"></script> <script defer src="/assets/d3.layout.cloud.js"></script>

View File

@@ -1,147 +0,0 @@
<style>
:root { color-scheme: light dark; }
@media only screen and (prefers-color-scheme: dark) {
:root:not([data-theme]) { --pico-background-color: #13171f; }
}
[data-theme='dark'] { --pico-background-color: #13171f; }
body { max-width: 960px; margin: 0 auto; padding: 2rem 1rem 4rem; background: var(--pico-background-color, var(--background-color)); color: var(--pico-color, var(--color)); }
main { display: grid; gap: 1rem; }
.blog-menu { position: relative; display: flex; align-items: baseline; justify-content: space-between; gap: .75rem; border-top: 1px solid var(--pico-muted-border-color, var(--muted-border-color)); border-bottom: 1px solid var(--pico-muted-border-color, var(--muted-border-color)); padding: .4rem 0; margin: -.15rem 0 .2rem; }
.blog-menu > .blog-menu-list { width: 100%; }
.blog-menu-list { list-style: none; display: flex; flex-wrap: wrap; align-items: baseline; gap: .25rem .75rem; margin: 0; padding: 0; }
.blog-menu-item { position: relative; }
.blog-menu-link { display: inline-flex; align-items: center; color: var(--pico-muted-color, var(--muted-color)); text-decoration: none; font-size: .94rem; line-height: 1.4; padding: .2rem .1rem; }
.blog-menu-item-with-children > .blog-menu-link::after { content: '▾'; font-size: .7em; margin-left: .38rem; opacity: .72; }
.blog-menu-link:hover,
.blog-menu-link:focus-visible { color: var(--pico-color, var(--color)); text-decoration: underline; }
.blog-menu-submenu { position: absolute; top: calc(100% + .12rem); left: 0; min-width: 12rem; display: none; border: 1px solid var(--pico-muted-border-color, var(--muted-border-color)); background: var(--pico-card-background-color, var(--card-background-color)); padding: .3rem 0; z-index: 10; }
.blog-menu-submenu .blog-menu-list { flex-direction: column; flex-wrap: nowrap; gap: 0; margin: 0; }
.blog-menu-submenu .blog-menu-item { display: block; padding: 0; margin: 0; }
.blog-menu-submenu .blog-menu-link { display: block; padding: .22rem .75rem; font-size: .88rem; line-height: 1.3; }
.blog-menu-submenu .blog-menu-item a.blog-menu-link { margin: 0; }
.blog-menu-item-with-children:hover > .blog-menu-submenu,
.blog-menu-item-with-children:focus-within > .blog-menu-submenu { display: block; }
.blog-menu-calendar { position: relative; display: inline-flex; align-items: baseline; justify-content: center; margin-left: auto; align-self: baseline; flex-shrink: 0; }
.blog-menu-calendar-button { display: inline-flex; align-items: center; justify-content: center; width: auto; height: auto; margin: 0; padding: .2rem .1rem; border: 0; background: transparent; color: var(--pico-muted-color, var(--muted-color)); border-radius: 0; cursor: pointer; font: inherit; font-size: .94rem; line-height: 1.4; appearance: none; -webkit-appearance: none; vertical-align: baseline; }
.blog-menu-calendar-button svg { display: block; width: .9rem; height: .9rem; fill: none; stroke: currentColor; transform: translateY(2px); }
.blog-menu-calendar-button:hover,
.blog-menu-calendar-button:focus-visible { color: var(--pico-color, var(--color)); }
.blog-calendar-panel { position: absolute; top: calc(100% + .15rem); right: 0; width: min(17.5rem, 92vw); border: 1px solid var(--pico-muted-border-color, var(--muted-border-color)); background: var(--pico-card-background-color, var(--card-background-color)); padding: .32rem; z-index: 30; }
.blog-calendar-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: .1rem; }
.blog-calendar-header strong { font-size: .9rem; line-height: 1.2; }
.blog-calendar-close { border: 1px solid var(--pico-muted-border-color, var(--muted-border-color)); background: transparent; color: var(--pico-muted-color, var(--muted-color)); width: 1.35rem; height: 1.35rem; border-radius: .2rem; padding: 0; cursor: pointer; line-height: 1; }
.blog-calendar-close:hover,
.blog-calendar-close:focus-visible { color: var(--pico-color, var(--color)); border-color: var(--pico-color, var(--color)); }
.blog-calendar-content { display: grid; gap: .08rem; }
.blog-calendar-status { margin: .1rem 0 0; color: var(--pico-muted-color, var(--muted-color)); font-size: .74rem; }
[data-blog-calendar-root] { font-size: .86rem; }
[data-blog-calendar-root] [data-vc=header] { margin-bottom: .08rem; }
[data-blog-calendar-root] [data-vc=month],
[data-blog-calendar-root] [data-vc=year] { padding: .08rem .18rem; font-size: .9rem; line-height: 1.15; }
[data-blog-calendar-root] [data-vc=months],
[data-blog-calendar-root] [data-vc=years] { row-gap: .32rem; }
[data-blog-calendar-root] [data-vc=years] { grid-template-columns: repeat(4, minmax(0, 1fr)); }
[data-blog-calendar-root] [data-vc-months-month],
[data-blog-calendar-root] [data-vc-years-year] { height: 1.72rem; }
[data-blog-calendar-root] [data-vc-months-month],
[data-blog-calendar-root] [data-vc-years-year] { word-break: normal; white-space: nowrap; }
[data-blog-calendar-root] [data-vc-years-year] { min-width: 2.5rem; font-size: .7rem; line-height: 1; }
[data-blog-calendar-root] [data-vc-week=days] { margin-bottom: .08rem; }
[data-blog-calendar-root] [data-vc-week-day] { font-size: .68rem; line-height: .9rem; min-width: 1.45rem; }
[data-blog-calendar-root] [data-vc-date] { padding-top: 0; padding-bottom: 0; }
[data-blog-calendar-root] [data-vc-date-btn] { min-height: 1.45rem; min-width: 1.45rem; font-size: .68rem; line-height: .9rem; }
[data-blog-calendar-has-posts='true'] [data-vc-date-btn] {
border-color: hsl(var(--blog-calendar-heat-hue, 210) 85% 42% / .95);
background-color: hsl(var(--blog-calendar-heat-hue, 210) 88% 52% / var(--blog-calendar-heat-alpha, 0));
}
[data-blog-calendar-root] [data-vc-months-month][data-blog-calendar-has-posts='true'],
[data-blog-calendar-root] [data-vc-years-year][data-blog-calendar-has-posts='true'] {
background-color: hsl(var(--blog-calendar-heat-hue, 210) 88% 52% / var(--blog-calendar-heat-alpha, 0));
border-color: hsl(var(--blog-calendar-heat-hue, 210) 85% 42% / .95);
}
.post { border: 1px solid var(--pico-muted-border-color, var(--muted-border-color)); padding: 1rem; background: var(--pico-card-background-color, var(--card-background-color)); min-width: 0; }
.post pre { position: relative; overflow-x: auto; max-width: 100%; border: 1px solid var(--pico-muted-border-color, var(--muted-border-color)); border-radius: .3rem; margin: .9rem 0; padding: .85rem .9rem; background: var(--pico-code-background-color, rgba(33, 38, 45, .82)); box-sizing: border-box; }
.post pre code { display: block; font-size: .88rem; line-height: 1.5; white-space: pre; }
.code-copy-button {
position: absolute;
top: .4rem;
right: .4rem;
border: 1px solid var(--pico-muted-border-color, var(--muted-border-color));
background: var(--pico-card-background-color, var(--card-background-color));
color: var(--pico-muted-color, var(--muted-color));
border-radius: .25rem;
width: 1.8rem;
height: 1.8rem;
display: inline-flex;
align-items: center;
justify-content: center;
padding: 0;
cursor: pointer;
opacity: .88;
}
.code-copy-button:hover,
.code-copy-button:focus-visible { opacity: 1; color: var(--pico-color, var(--color)); }
.code-copy-icon { font-size: .95rem; line-height: 1; }
.code-copy-success .code-copy-button { color: var(--pico-ins-color, rgb(53, 117, 56)); border-color: var(--pico-ins-color, rgb(53, 117, 56)); }
.code-copy-failed .code-copy-button { color: var(--pico-del-color, rgb(183, 72, 72)); border-color: var(--pico-del-color, rgb(183, 72, 72)); }
.post iframe { width: 100%; min-height: 20rem; }
.macro-youtube, .macro-vimeo { margin-bottom: 1rem; }
.macro-gallery, .macro-photo-archive, .macro-tag-cloud { border: 1px dashed var(--pico-muted-border-color, var(--muted-border-color)); padding: .75rem; margin: 1rem 0; }
.gallery-container { display: grid; gap: .5rem; }
.macro-gallery.gallery-cols-1 .gallery-container { grid-template-columns: 1fr; }
.macro-gallery.gallery-cols-2 .gallery-container { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.macro-gallery.gallery-cols-3 .gallery-container { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.macro-gallery.gallery-cols-4 .gallery-container { grid-template-columns: repeat(4, minmax(0, 1fr)); }
.macro-gallery.gallery-cols-5 .gallery-container { grid-template-columns: repeat(5, minmax(0, 1fr)); }
.macro-gallery.gallery-cols-6 .gallery-container { grid-template-columns: repeat(6, minmax(0, 1fr)); }
.gallery-item, .photo-archive-item { display: block; overflow: hidden; border-radius: .25rem; }
.gallery-item img, .photo-archive-item img { display: block; width: 100%; height: auto; aspect-ratio: 1 / 1; object-fit: cover; }
.lb-nav a, .lb-nav a:hover, .lb-nav a:focus-visible { border: 0; box-shadow: none; outline: none; text-decoration: none; }
.gallery-caption { margin-top: .5rem; text-align: center; color: var(--pico-muted-color, var(--muted-color)); font-size: .92rem; }
.gallery-empty, .photo-archive-empty { color: var(--pico-muted-color, var(--muted-color)); font-style: italic; }
.photo-archive-container { display: grid; gap: 1rem; }
.photo-archive-month { display: grid; grid-template-columns: 3.25rem 1fr; gap: .75rem; align-items: start; }
.photo-archive-month-label { display: flex; justify-content: center; align-items: center; }
.photo-archive-month-label span { writing-mode: vertical-rl; transform: rotate(180deg); letter-spacing: .08em; text-transform: uppercase; color: var(--pico-muted-color, var(--muted-color)); }
.photo-archive-gallery { display: grid; gap: .5rem; grid-template-columns: repeat(4, minmax(0, 1fr)); }
.photo-archive-single-month .photo-archive-gallery { grid-template-columns: repeat(5, minmax(0, 1fr)); }
.macro-tag-cloud { min-height: 14rem; }
.tag-cloud-canvas { display: block; width: 100%; height: auto; min-height: 12rem; }
.tag-cloud-empty { color: var(--pico-muted-color, var(--muted-color)); font-style: italic; }
.archive-day-group { display: grid; grid-template-columns: 5.25rem 1fr; gap: 1.25rem; align-items: stretch; }
.archive-day-marker { display: flex; justify-content: center; align-items: center; color: var(--pico-muted-color, var(--muted-color)); }
.archive-day-marker span { writing-mode: vertical-rl; transform: rotate(180deg); letter-spacing: .16em; font-size: 1.05rem; font-weight: 600; text-transform: uppercase; }
.archive-day-posts { display: grid; gap: 1rem; }
.archive-day-separator { position: relative; height: 2px; width: 100%; color: var(--pico-color, var(--color)); border-top: 1px solid currentColor; opacity: .18; margin: .45rem 0 .65rem; }
.archive-day-separator::before { content: ''; position: absolute; inset: 0; background: linear-gradient(to right, transparent 0%, transparent 18%, currentColor 58%, transparent 92%, transparent 100%); opacity: .85; }
.single-post { margin: 0; padding: 0; background: transparent; border: 0; box-shadow: none; }
.single-post-taxonomy { display: flex; flex-wrap: wrap; gap: .4rem .45rem; margin: -.1rem 0 .2rem; }
.single-post-taxonomy-bubble {
--bubble-accent: var(--pico-ins-color, rgb(53, 117, 56));
--bubble-bg: var(--bubble-accent);
display: inline-flex;
align-items: center;
border: 1px solid var(--bubble-accent);
border-radius: 999px;
padding: .1rem .5rem;
font-size: .74rem;
line-height: 1.35;
color: #000;
background: var(--bubble-bg, var(--bubble-accent));
text-decoration: none;
}
.single-post-taxonomy-bubble:hover,
.single-post-taxonomy-bubble:focus-visible { text-decoration: underline; }
.single-post-taxonomy-bubble-category { --bubble-accent: var(--pico-ins-color, rgb(53, 117, 56)); --bubble-bg: var(--pico-ins-color, rgb(53, 117, 56)); }
.single-post-taxonomy-bubble-tag { --bubble-accent: var(--pico-del-color, rgb(183, 72, 72)); --bubble-bg: var(--pico-del-color, rgb(183, 72, 72)); }
.single-post-backlinks { display: flex; flex-wrap: wrap; gap: .4rem .45rem; align-items: center; margin-top: 1.5rem; }
.single-post-backlinks-label { font-size: .74rem; line-height: 1.35; color: var(--pico-muted-color, var(--muted-color)); margin-right: .15rem; }
.single-post-backlink-bubble { --bubble-accent: var(--pico-primary, rgb(16, 107, 193)); --bubble-bg: var(--pico-primary, rgb(16, 107, 193)); color: var(--pico-primary-inverse, #fff); }
.preview-pagination { display: flex; justify-content: space-between; align-items: center; gap: .75rem; margin-top: .25rem; }
.preview-pagination-link { color: var(--pico-muted-color, var(--muted-color)); text-decoration: none; font-size: .92rem; opacity: .72; transition: opacity .15s ease-in-out; }
.preview-pagination-link:hover,
.preview-pagination-link:focus-visible { opacity: 1; text-decoration: underline; }
.preview-pagination .spacer { flex: 1; }
.not-found { display: grid; place-items: center; min-height: 48vh; }
.not-found article { max-width: 32rem; text-align: center; }
</style>

View File

@@ -462,7 +462,7 @@ describe('BlogGenerationEngine', () => {
expect(html).toContain('data-template="post-list"'); expect(html).toContain('data-template="post-list"');
expect(html).toContain('/assets/pico.min.css'); expect(html).toContain('/assets/pico.min.css');
expect(html).toContain('/assets/lightbox.min.css'); expect(html).toContain('/assets/lightbox.min.css');
expect(html).toContain('.lb-nav a, .lb-nav a:hover, .lb-nav a:focus-visible { border: 0; box-shadow: none; outline: none; text-decoration: none; }'); expect(html).toContain('/assets/bds.css');
expect(html).toContain('/assets/tag-cloud.js'); expect(html).toContain('/assets/tag-cloud.js');
expect(html).toContain('rel="alternate" type="application/rss+xml"'); expect(html).toContain('rel="alternate" type="application/rss+xml"');
expect(html).toContain('href="/rss.xml"'); expect(html).toContain('href="/rss.xml"');
@@ -484,8 +484,7 @@ describe('BlogGenerationEngine', () => {
const indexPath = path.join(tempDir, 'html', 'index.html'); const indexPath = path.join(tempDir, 'html', 'index.html');
const html = await readFile(indexPath, 'utf-8'); const html = await readFile(indexPath, 'utf-8');
expect(html).toContain('href="/assets/pico.green.min.css"'); expect(html).toContain('href="/assets/pico.green.min.css"');
expect(html).toContain('@media only screen and (prefers-color-scheme: dark)'); expect(html).toContain('/assets/bds.css');
expect(html).toContain('--pico-background-color: #13171f;');
}); });
it('generates single post pages at /{year}/{month}/{day}/{slug}/index.html', async () => { it('generates single post pages at /{year}/{month}/{day}/{slug}/index.html', async () => {
@@ -533,14 +532,7 @@ describe('BlogGenerationEngine', () => {
expect(html).toContain('href="/category/article/"'); expect(html).toContain('href="/category/article/"');
expect(html).toContain('href="/tag/css-only/"'); expect(html).toContain('href="/tag/css-only/"');
expect(html).toContain('style="--bubble-accent: #22aa88;"'); expect(html).toContain('style="--bubble-accent: #22aa88;"');
expect(html).toContain('background: var(--bubble-bg, var(--bubble-accent));'); expect(html).toContain('/assets/bds.css');
expect(html).toContain('color: #000;');
expect(html).toContain('.single-post-taxonomy-bubble-category {');
expect(html).toContain('--bubble-accent: var(--pico-ins-color');
expect(html).toContain('--bubble-bg: var(--pico-ins-color');
expect(html).toContain('.single-post-taxonomy-bubble-tag {');
expect(html).toContain('--bubble-accent: var(--pico-del-color');
expect(html).toContain('--bubble-bg: var(--pico-del-color');
const categoryIndex = html.indexOf('single-post-taxonomy-bubble-category'); const categoryIndex = html.indexOf('single-post-taxonomy-bubble-category');
const tagIndex = html.indexOf('single-post-taxonomy-bubble-tag'); const tagIndex = html.indexOf('single-post-taxonomy-bubble-tag');

View File

@@ -181,6 +181,6 @@ describe('GenerationRouteRendererFactory', () => {
expect(html).toContain('class="macro-youtube"'); expect(html).toContain('class="macro-youtube"');
expect(html).toContain('youtube.com/embed/dQw4w9WgXcQ?rel=0'); expect(html).toContain('youtube.com/embed/dQw4w9WgXcQ?rel=0');
expect(html).toContain('.macro-youtube, .macro-vimeo { margin-bottom: 1rem; }'); expect(html).toContain('/assets/bds.css');
}); });
}); });

View File

@@ -318,6 +318,7 @@ describe('PreviewServer', () => {
expect(rootHtml).toContain('href="/assets/lightbox.min.css"'); expect(rootHtml).toContain('href="/assets/lightbox.min.css"');
expect(rootHtml).toContain('href="/assets/highlight.min.css"'); expect(rootHtml).toContain('href="/assets/highlight.min.css"');
expect(rootHtml).toContain('href="/assets/vanilla-calendar.min.css"'); expect(rootHtml).toContain('href="/assets/vanilla-calendar.min.css"');
expect(rootHtml).toContain('href="/assets/bds.css"');
expect(rootHtml).toContain('src="/assets/lightbox.min.js"'); expect(rootHtml).toContain('src="/assets/lightbox.min.js"');
expect(rootHtml).toContain('src="/assets/highlight.min.js"'); expect(rootHtml).toContain('src="/assets/highlight.min.js"');
expect(rootHtml).toContain('src="/assets/code-enhancements.js"'); expect(rootHtml).toContain('src="/assets/code-enhancements.js"');
@@ -360,6 +361,18 @@ describe('PreviewServer', () => {
expect(tagCloudJsResponse.status).toBe(200); expect(tagCloudJsResponse.status).toBe(200);
expect(tagCloudJsResponse.headers.get('content-type')).toContain('application/javascript'); expect(tagCloudJsResponse.headers.get('content-type')).toContain('application/javascript');
const bdsCssResponse = await fetch(`${server.getBaseUrl()}/assets/bds.css`);
expect(bdsCssResponse.status).toBe(200);
expect(bdsCssResponse.headers.get('content-type')).toContain('text/css');
const bdsCss = await bdsCssResponse.text();
expect(bdsCss).toContain('.blog-menu');
expect(bdsCss).toContain('.post {');
expect(bdsCss).toContain('.single-post-taxonomy-bubble');
expect(bdsCss).toContain('.archive-day-separator');
expect(bdsCss).toContain('.not-found');
expect(bdsCss).toContain('--pico-background-color: #13171f;');
expect(bdsCss).toContain('.lb-nav a, .lb-nav a:hover, .lb-nav a:focus-visible');
const lightboxPrevImageResponse = await fetch(`${server.getBaseUrl()}/images/prev.png`); const lightboxPrevImageResponse = await fetch(`${server.getBaseUrl()}/images/prev.png`);
expect(lightboxPrevImageResponse.status).toBe(200); expect(lightboxPrevImageResponse.status).toBe(200);
expect(lightboxPrevImageResponse.headers.get('content-type')).toContain('image/png'); expect(lightboxPrevImageResponse.headers.get('content-type')).toContain('image/png');
@@ -663,7 +676,7 @@ describe('PreviewServer', () => {
expect(html).toContain('<html lang="en" data-theme="dark">'); expect(html).toContain('<html lang="en" data-theme="dark">');
expect(html).toContain('href="/assets/pico.green.min.css"'); expect(html).toContain('href="/assets/pico.green.min.css"');
expect(html).toContain('--pico-background-color: #13171f;'); expect(html).toContain('/assets/bds.css');
}); });
it('limits list routes to 50 posts', async () => { it('limits list routes to 50 posts', async () => {
@@ -757,12 +770,7 @@ describe('PreviewServer', () => {
const separatorCount = (html.match(/class="archive-day-separator"/g) || []).length; const separatorCount = (html.match(/class="archive-day-separator"/g) || []).length;
expect(separatorCount).toBe(1); expect(separatorCount).toBe(1);
expect(html).toContain('.archive-day-separator { position: relative; height: 2px;'); expect(html).toContain('/assets/bds.css');
expect(html).toContain('color: var(--pico-color, var(--color));');
expect(html).toContain('border-top: 1px solid currentColor;');
expect(html).toContain('opacity: .18;');
expect(html).toContain('.archive-day-separator::before');
expect(html).toContain('linear-gradient(to right, transparent 0%, transparent 18%, currentColor 58%, transparent 92%, transparent 100%)');
}); });
it('supports day-and-slug post route', async () => { it('supports day-and-slug post route', async () => {
@@ -784,7 +792,7 @@ describe('PreviewServer', () => {
const html = await response.text(); const html = await response.text();
expect(html).toContain('Single Post'); expect(html).toContain('Single Post');
expect(html).toContain('data-template="single-post"'); expect(html).toContain('data-template="single-post"');
expect(html).toContain('.single-post { margin: 0; padding: 0; background: transparent; border: 0; box-shadow: none; }'); expect(html).toContain('/assets/bds.css');
}); });
it('resets lightbox nav anchor hover and focus styles to avoid frame artifacts over images', async () => { it('resets lightbox nav anchor hover and focus styles to avoid frame artifacts over images', async () => {
@@ -808,7 +816,7 @@ describe('PreviewServer', () => {
await server.start(0); await server.start(0);
const html = await (await fetch(`${server.getBaseUrl()}/2025/2/14/lightbox-style-post/`)).text(); const html = await (await fetch(`${server.getBaseUrl()}/2025/2/14/lightbox-style-post/`)).text();
expect(html).toContain('.lb-nav a, .lb-nav a:hover, .lb-nav a:focus-visible { border: 0; box-shadow: none; outline: none; text-decoration: none; }'); expect(html).toContain('/assets/bds.css');
}); });
it('keeps code blocks constrained to post column width with horizontal overflow inside the block', async () => { it('keeps code blocks constrained to post column width with horizontal overflow inside the block', async () => {
@@ -824,9 +832,7 @@ describe('PreviewServer', () => {
await server.start(0); await server.start(0);
const html = await (await fetch(`${server.getBaseUrl()}/`)).text(); const html = await (await fetch(`${server.getBaseUrl()}/`)).text();
expect(html).toContain('.post { border: 1px solid var(--pico-muted-border-color, var(--muted-border-color)); padding: 1rem; background: var(--pico-card-background-color, var(--card-background-color)); min-width: 0; }'); expect(html).toContain('/assets/bds.css');
expect(html).toContain('.post pre { position: relative; overflow-x: auto; max-width: 100%;');
expect(html).toContain('.post pre code { display: block; font-size: .88rem; line-height: 1.5; white-space: pre; }');
}); });
it('renders single post title as h1', async () => { it('renders single post title as h1', async () => {
@@ -903,14 +909,7 @@ describe('PreviewServer', () => {
expect(html).toContain('href="/category/article/"'); expect(html).toContain('href="/category/article/"');
expect(html).toContain('href="/tag/css-only/"'); expect(html).toContain('href="/tag/css-only/"');
expect(html).toContain('style="--bubble-accent: #22aa88;"'); expect(html).toContain('style="--bubble-accent: #22aa88;"');
expect(html).toContain('background: var(--bubble-bg, var(--bubble-accent));'); expect(html).toContain('/assets/bds.css');
expect(html).toContain('color: #000;');
expect(html).toContain('.single-post-taxonomy-bubble-category {');
expect(html).toContain('--bubble-accent: var(--pico-ins-color');
expect(html).toContain('--bubble-bg: var(--pico-ins-color');
expect(html).toContain('.single-post-taxonomy-bubble-tag {');
expect(html).toContain('--bubble-accent: var(--pico-del-color');
expect(html).toContain('--bubble-bg: var(--pico-del-color');
const categoryIndex = html.indexOf('single-post-taxonomy-bubble-category'); const categoryIndex = html.indexOf('single-post-taxonomy-bubble-category');
const tagIndex = html.indexOf('single-post-taxonomy-bubble-tag'); const tagIndex = html.indexOf('single-post-taxonomy-bubble-tag');
@@ -993,9 +992,8 @@ describe('PreviewServer', () => {
expect(html).toContain('href="/2025/03/10/source-post"'); expect(html).toContain('href="/2025/03/10/source-post"');
expect(html).toContain('href="/2025/03/12/a-very-long-slug-post-that-exceeds-thirty-characters"'); expect(html).toContain('href="/2025/03/12/a-very-long-slug-post-that-exceeds-thirty-characters"');
// Backlinks use pico accent color // Backlinks use pico accent color (in external bds.css)
expect(html).toContain('.single-post-backlink-bubble'); expect(html).toContain('/assets/bds.css');
expect(html).toContain('--pico-primary');
// Backlinks section is after the article // Backlinks section is after the article
const articleEndIndex = html.indexOf('</article>'); const articleEndIndex = html.indexOf('</article>');
@@ -1836,7 +1834,7 @@ describe('PreviewServer', () => {
expect(html).toContain('class="macro-youtube"'); expect(html).toContain('class="macro-youtube"');
expect(html).toContain('youtube.com/embed/dQw4w9WgXcQ?rel=0'); expect(html).toContain('youtube.com/embed/dQw4w9WgXcQ?rel=0');
expect(html).toContain('.macro-youtube, .macro-vimeo { margin-bottom: 1rem; }'); expect(html).toContain('/assets/bds.css');
}); });
it('resolves gallery linked images via post-media links even when media.linkedPostIds is empty', async () => { it('resolves gallery linked images via post-media links even when media.linkedPostIds is empty', async () => {