Real bugs: - loadArticle()/loadAuthorView() had no stale-render guard (unlike loadArticleList()/loadSearch()) -- a slower, superseded request could overwrite a newer one's render. Both now capture and check a generation snapshot. - #post/... links interpolated article.hash (raw, unencoded "author/slug") directly, but router.js unconditionally decodeURIComponent()s each segment when parsing -- would throw (silently swallowed) for any author/slug containing a literal "%" or other reserved character. Links now go through a new postHref() helper that encodes each segment individually. - A startup failure replaced the entire document.body, which would wipe out a host site's own page content when cmjs is embedded alongside it. Now scoped to cmjs's own mount points when at least one exists. - hydrateArticle() had no in-flight de-duplication -- two concurrent callers for the same not-yet-hydrated stub (e.g. a page's hydration racing the next page's prefetch) could each fire a duplicate fetch. Now tracked via a WeakMap keyed by the stub. - Leftover "expedition blog"/"expedition year" wording missed by the earlier astronomy-club genericization pass (that pass's grep didn't cover the English word "expedition"). Robustness gaps: - cache.js no longer permanently disables the whole IndexedDB cache after a single failed transaction -- only openDb() itself failing does that now. - Listing mtimes are normalized to minute precision at the one point they enter the system, so cache comparisons stay consistent even if a server flips between JSON and HTML-fallback autoindex. - The root author-listing fetch failing outright is now treated like "every author's listing failed" (a new rootFailed crawl stat) instead of surfacing a generic, less-specific error. - router.js now checks both required mount points exist right after startup and fails with a clear, catchable message instead of a bare TypeError the first time showListView()/showArticleView() runs. - The article view's "back" link now returns to whatever list/search/ filter page the user actually came from (tracked via lastListHash) instead of always resetting to page 1 of everything. - filterLabel() now checks filter.type === 'category' explicitly instead of falling through to it as the default case. - Author-name filtering is now case-sensitive, matching how author paths/profile lookups are looked up everywhere else. - Documented (in code comments and a new README note) that article/bio Markdown renders via marked.js with no HTML-sanitization pass -- only trusted authors should be adding content. Minor: - Extracted applyArticleMetaFields() to share the date/reading-time/ category-badge logic between renderCard() and loadArticle() instead of duplicating it. - Clarified resolveMediaUrl()'s WordPress-migration special case as legacy compatibility, not a general-purpose feature. - frontmatter.js no longer requires a trailing newline after the closing "---" -- a file that's nothing but frontmatter now parses instead of falling through to "no frontmatter at all". Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AXSVPS3UmmASiiprPfcnUd
76 lines
2.2 KiB
CSS
76 lines
2.2 KiB
CSS
/* CSS custom properties — single source of truth for colours, radius, etc.
|
|
Override these in a downstream stylesheet to theme the whole app. */
|
|
:root {
|
|
--color-bg: #f7f7f7;
|
|
--color-surface: #fff;
|
|
--color-border: #e0e0e0;
|
|
--color-text: #222;
|
|
--color-text-soft: #555;
|
|
--color-text-muted: #999;
|
|
--color-accent: #0057b8;
|
|
--color-tag-bg: #f0f0f0;
|
|
--color-tag-border: #ddd;
|
|
--color-code-bg: #f4f4f4;
|
|
--color-error: #c0392b;
|
|
--radius-sm: 3px;
|
|
--radius: 6px;
|
|
--shadow-card: 0 2px 8px rgba(0, 0, 0, 0.08);
|
|
}
|
|
|
|
@media (prefers-color-scheme: dark) {
|
|
:root {
|
|
--color-bg: #1a1a1a;
|
|
--color-surface: #252525;
|
|
--color-border: #3a3a3a;
|
|
--color-text: #e0e0e0;
|
|
--color-text-soft: #aaa;
|
|
--color-text-muted: #666;
|
|
--color-accent: #4d9cf0;
|
|
--color-tag-bg: #2d2d2d;
|
|
--color-tag-border: #3a3a3a;
|
|
--color-code-bg: #2a2a2a;
|
|
--color-error: #e74c3c;
|
|
--shadow-card: 0 2px 8px rgba(0, 0, 0, 0.35);
|
|
}
|
|
}
|
|
|
|
/* Box-sizing reset and base typography */
|
|
*, *::before, *::after {
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
body {
|
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
|
|
"Helvetica Neue", Arial, sans-serif;
|
|
font-size: 1rem;
|
|
line-height: 1.65;
|
|
color: var(--color-text);
|
|
background: var(--color-bg);
|
|
margin: 0;
|
|
padding: 0;
|
|
}
|
|
|
|
/* The demo blog follows its own restrained night-sky palette,
|
|
independently of the visitor's operating-system theme. */
|
|
body.blog-page {
|
|
--color-bg: #050506;
|
|
--color-surface: #101114;
|
|
--color-border: #292b31;
|
|
--color-text: #f4f1ed;
|
|
--color-text-soft: #c6c4c2;
|
|
--color-text-muted: #92959d;
|
|
--color-accent: #e25555;
|
|
--color-tag-bg: #191b21;
|
|
--color-tag-border: #363941;
|
|
--color-code-bg: #131519;
|
|
--color-error: #ff7777;
|
|
--shadow-card: 0 12px 35px rgba(0, 0, 0, 0.28);
|
|
}
|
|
|
|
/* Global keyboard-navigation focus indicator */
|
|
:focus-visible {
|
|
outline: 2px solid var(--color-accent);
|
|
outline-offset: 2px;
|
|
border-radius: 2px;
|
|
}
|