v1.7.0: readability & UX pass
This commit is contained in:
1 parent
5384ae0cd1
commit
e6c472ca7a
6 files changed
+104
-4
No files matched your search
@@ -16,7 +16,7 @@ A clean, minimal theme for [Bludit](https://www.bludit.com) inspired by the
|
|||||||
- **Keyboard shortcuts** — `/` (search), `g h` (go home), `t` (back to top), `Esc` (close overlay).
|
- **Keyboard shortcuts** — `/` (search), `g h` (go home), `t` (back to top), `Esc` (close overlay).
|
||||||
- **Randomized navigation** — the Navigation/Pages plugin list is shuffled on every load.
|
- **Randomized navigation** — the Navigation/Pages plugin list is shuffled on every load.
|
||||||
- **Reading time & dates** shown on the post list and single posts.
|
- **Reading time & dates** shown on the post list and single posts.
|
||||||
- **Post-list excerpts** — the page description renders as a 2-line excerpt; tags are capped at 5 with a `+N` badge.
|
- **Post-list excerpts & thumbnails** — the page description renders as a 2-line excerpt with the cover image as a right-side thumbnail; tags are capped at 5 with a `+N` badge.
|
||||||
- **Older / newer post navigation** — chronological links under every article, above the related posts.
|
- **Older / newer post navigation** — chronological links under every article, above the related posts.
|
||||||
- **Social icons** in the navbar and hero (SVG files in `img/`).
|
- **Social icons** in the navbar and hero (SVG files in `img/`).
|
||||||
- **Tags & categories** rendered as pill badges.
|
- **Tags & categories** rendered as pill badges.
|
||||||
@@ -29,6 +29,8 @@ A clean, minimal theme for [Bludit](https://www.bludit.com) inspired by the
|
|||||||
- **SEO** — Open Graph, Twitter Card, JSON-LD (WebSite, Article, BreadcrumbList, sameAs), canonical URL, theme-color meta, cover-image preload. Duplicate OG/Twitter/canonical tags emitted by Bludit plugins are stripped automatically.
|
- **SEO** — Open Graph, Twitter Card, JSON-LD (WebSite, Article, BreadcrumbList, sameAs), canonical URL, theme-color meta, cover-image preload. Duplicate OG/Twitter/canonical tags emitted by Bludit plugins are stripped automatically.
|
||||||
- **Accessibility** — skip link, ARIA labels, `:focus-visible`, heading anchor links, print stylesheet.
|
- **Accessibility** — skip link, ARIA labels, `:focus-visible`, heading anchor links, print stylesheet.
|
||||||
- **PWA** — web app manifest included.
|
- **PWA** — web app manifest included.
|
||||||
|
- **CLS-free images** — `width`/`height` are injected server-side into local-upload images (plus lazy-loading and async decode).
|
||||||
|
- **View transitions** — smooth cross-document fade on supporting browsers; respects `prefers-reduced-motion`.
|
||||||
- Fully responsive; no jQuery, no Bootstrap JS, no webfont requests.
|
- Fully responsive; no jQuery, no Bootstrap JS, no webfont requests.
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|||||||
@@ -244,6 +244,24 @@
|
|||||||
--social-icon-filter: brightness(0) invert(1);
|
--social-icon-filter: brightness(0) invert(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ========================================
|
||||||
|
VIEW TRANSITIONS (cross-document)
|
||||||
|
Smooth fade between page navigations on supporting browsers
|
||||||
|
(Chrome/Edge 126+); silently ignored elsewhere. Disabled for
|
||||||
|
users who prefer reduced motion.
|
||||||
|
======================================== */
|
||||||
|
@view-transition {
|
||||||
|
navigation: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
::view-transition-group(*),
|
||||||
|
::view-transition-old(*),
|
||||||
|
::view-transition-new(*) {
|
||||||
|
animation: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* ========================================
|
/* ========================================
|
||||||
BASE
|
BASE
|
||||||
======================================== */
|
======================================== */
|
||||||
@@ -896,6 +914,44 @@ img.nav-svg-icon {
|
|||||||
margin-top: var(--space-sm);
|
margin-top: var(--space-sm);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Cover thumbnail: text left, image right (Blowfish-style) */
|
||||||
|
.post-list-row {
|
||||||
|
display: flex;
|
||||||
|
gap: var(--space-md);
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-list-main {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
min-width: 0; /* allow long titles/URLs to wrap instead of pushing the thumb */
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-list-thumb {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-list-thumb img {
|
||||||
|
width: 168px;
|
||||||
|
height: 112px;
|
||||||
|
object-fit: cover;
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
border: 1px solid var(--border-subtle);
|
||||||
|
background: var(--bg-subtle);
|
||||||
|
transition: transform 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-list-thumb:hover img {
|
||||||
|
transform: scale(1.03);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 575.98px) {
|
||||||
|
.post-list-thumb img {
|
||||||
|
width: 96px;
|
||||||
|
height: 72px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* "+N" overflow badge — muted, links to the post (where all tags are shown) */
|
/* "+N" overflow badge — muted, links to the post (where all tags are shown) */
|
||||||
.taxonomy-badge-more {
|
.taxonomy-badge-more {
|
||||||
color: var(--text-muted);
|
color: var(--text-muted);
|
||||||
|
|||||||
+1
-1
@@ -6,5 +6,5 @@
|
|||||||
"releaseDate": "2026-07-10",
|
"releaseDate": "2026-07-10",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"compatible": "3.22.0",
|
"compatible": "3.22.0",
|
||||||
"notes": "A Bludit theme inspired by the Blowfish Hugo theme. Five-theme swatch picker (Light/Dark/Nord/Dracula/Catppuccin), sticky ToC sidebar on articles, image lightbox with zoom, image carousel (```carousel fence), tabbed code blocks (```tabs fence), smart sidebar per page type, and clean responsive typography. v1.3.0: SEO (Open Graph, Twitter Card, JSON-LD, theme-color, cover preload), per-theme syntax highlighting (self-hosted highlight.js), copy-to-clipboard on code blocks, numbered pagination, inline-SVG icons (dropped the Bootstrap Icons webfont), removed jQuery/Bootstrap JS, deferred cached JS bundle, reading progress bar, back-to-top, randomised sidebar selection, lazy-loaded images, accessibility (skip link, ARIA, :focus-visible) and a print stylesheet. v1.4.0: canonical URL, WebSite + BreadcrumbList + author sameAs JSON-LD, og:image:alt / twitter:site+creator, cover-image aspect-ratio (CLS), heading anchor links, prev/next article nav, related posts, share buttons (X/LinkedIn/copy-link), mailto social icon, PWA web app manifest, 'updated on' date, and outbound-link hardening (noopener noreferrer). v1.5.0: callout/admonition boxes (> [!NOTE]/[!TIP]/[!WARNING]/...), code-block header bar with language label + copy button, visual breadcrumbs, custom 404 page, image figure/figcaption from the markdown title, a mobile table-of-contents drawer, and third-party resource hints. v1.6.0: self-hosted Inter (no Google Fonts), client-side search overlay (\"/\" key + navbar button), keyboard shortcuts (/ , g h, t, Esc), a tabbed-code copy button, series navigation (series-* tag), and content typography (keycaps, task-list checkboxes, <mark>, external-link icon, footnote/definition-list styling). the navbar search icon only appears once the on-page search box scrolls out of view, and code blocks use a header bar (language + copy) with tighter line-height. v1.7.0: readability pass - underlined in-content links, justified paragraphs with auto-hyphenation (left-aligned on mobile), post-list excerpts from the page description with a 5-tag cap (+N badge), older/newer post navigation under articles, and de-duplication of Open Graph / Twitter Card / canonical tags emitted by plugins."
|
"notes": "A Bludit theme inspired by the Blowfish Hugo theme. Five-theme swatch picker (Light/Dark/Nord/Dracula/Catppuccin), sticky ToC sidebar on articles, image lightbox with zoom, image carousel (```carousel fence), tabbed code blocks (```tabs fence), smart sidebar per page type, and clean responsive typography. v1.3.0: SEO (Open Graph, Twitter Card, JSON-LD, theme-color, cover preload), per-theme syntax highlighting (self-hosted highlight.js), copy-to-clipboard on code blocks, numbered pagination, inline-SVG icons (dropped the Bootstrap Icons webfont), removed jQuery/Bootstrap JS, deferred cached JS bundle, reading progress bar, back-to-top, randomised sidebar selection, lazy-loaded images, accessibility (skip link, ARIA, :focus-visible) and a print stylesheet. v1.4.0: canonical URL, WebSite + BreadcrumbList + author sameAs JSON-LD, og:image:alt / twitter:site+creator, cover-image aspect-ratio (CLS), heading anchor links, prev/next article nav, related posts, share buttons (X/LinkedIn/copy-link), mailto social icon, PWA web app manifest, 'updated on' date, and outbound-link hardening (noopener noreferrer). v1.5.0: callout/admonition boxes (> [!NOTE]/[!TIP]/[!WARNING]/...), code-block header bar with language label + copy button, visual breadcrumbs, custom 404 page, image figure/figcaption from the markdown title, a mobile table-of-contents drawer, and third-party resource hints. v1.6.0: self-hosted Inter (no Google Fonts), client-side search overlay (\"/\" key + navbar button), keyboard shortcuts (/ , g h, t, Esc), a tabbed-code copy button, series navigation (series-* tag), and content typography (keycaps, task-list checkboxes, <mark>, external-link icon, footnote/definition-list styling). the navbar search icon only appears once the on-page search box scrolls out of view, and code blocks use a header bar (language + copy) with tighter line-height. v1.7.0: readability pass - underlined in-content links, justified paragraphs with auto-hyphenation (left-aligned on mobile), post-list excerpts from the page description with a 5-tag cap (+N badge), older/newer post navigation under articles, de-duplication of Open Graph / Twitter Card / canonical tags emitted by plugins, cover thumbnails on the post list, server-side width/height injection on content images (CLS), and cross-document view transitions."
|
||||||
}
|
}
|
||||||
+17
-1
@@ -52,11 +52,16 @@
|
|||||||
<!-- Post list (titles only) -->
|
<!-- Post list (titles only) -->
|
||||||
<div class="post-list">
|
<div class="post-list">
|
||||||
<?php foreach ($content as $page) : ?>
|
<?php foreach ($content as $page) : ?>
|
||||||
<article class="post-list-item">
|
<?php $bdCover = $page->coverImage(); ?>
|
||||||
|
<article class="post-list-item<?php echo $bdCover ? ' has-thumb' : ''; ?>">
|
||||||
|
|
||||||
<!-- Load Bludit Plugins: Page Begin -->
|
<!-- Load Bludit Plugins: Page Begin -->
|
||||||
<?php Theme::plugins('pageBegin'); ?>
|
<?php Theme::plugins('pageBegin'); ?>
|
||||||
|
|
||||||
|
<div class="post-list-row">
|
||||||
|
|
||||||
|
<div class="post-list-main">
|
||||||
|
|
||||||
<div class="post-list-head">
|
<div class="post-list-head">
|
||||||
<a href="<?php echo $page->permalink(); ?>">
|
<a href="<?php echo $page->permalink(); ?>">
|
||||||
<h2 class="post-list-title"><?php echo blowfish_text($page->title()); ?></h2>
|
<h2 class="post-list-title"><?php echo blowfish_text($page->title()); ?></h2>
|
||||||
@@ -101,6 +106,17 @@
|
|||||||
</div>
|
</div>
|
||||||
<?php endif ?>
|
<?php endif ?>
|
||||||
|
|
||||||
|
</div><!-- /.post-list-main -->
|
||||||
|
|
||||||
|
<!-- Cover thumbnail -->
|
||||||
|
<?php if ($bdCover) : ?>
|
||||||
|
<a class="post-list-thumb" href="<?php echo $page->permalink(); ?>" tabindex="-1" aria-hidden="true">
|
||||||
|
<img src="<?php echo htmlspecialchars($bdCover, ENT_QUOTES, 'UTF-8'); ?>" alt="" loading="lazy" decoding="async" />
|
||||||
|
</a>
|
||||||
|
<?php endif ?>
|
||||||
|
|
||||||
|
</div><!-- /.post-list-row -->
|
||||||
|
|
||||||
<!-- Load Bludit Plugins: Page End -->
|
<!-- Load Bludit Plugins: Page End -->
|
||||||
<?php Theme::plugins('pageEnd'); ?>
|
<?php Theme::plugins('pageEnd'); ?>
|
||||||
|
|
||||||
|
|||||||
@@ -55,6 +55,28 @@ if (!function_exists('blowfish_icon')) {
|
|||||||
return DOMAIN_THEME . $rel . ($v ? '?v=' . $v : '');
|
return DOMAIN_THEME . $rel . ($v ? '?v=' . $v : '');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Inject width/height attributes into local-upload <img> tags that lack
|
||||||
|
// them, so the browser reserves the box before the image loads (no CLS).
|
||||||
|
// Only touches images served from this site's uploads folder; remote
|
||||||
|
// images and tags that already declare a width are left alone.
|
||||||
|
function blowfish_img_dimensions($html) {
|
||||||
|
if (!defined('DOMAIN_UPLOADS') || !defined('PATH_UPLOADS')) { return $html; }
|
||||||
|
return preg_replace_callback('/<img\b[^>]*>/i', function ($m) {
|
||||||
|
$tag = $m[0];
|
||||||
|
if (preg_match('/\b(?:width|height)\s*=/i', $tag)) { return $tag; }
|
||||||
|
if (!preg_match('/\bsrc=["\']([^"\']+)["\']/i', $tag, $src)) { return $tag; }
|
||||||
|
$url = html_entity_decode($src[1], ENT_QUOTES, 'UTF-8');
|
||||||
|
if (strpos($url, DOMAIN_UPLOADS) !== 0) { return $tag; }
|
||||||
|
$rel = rawurldecode(substr($url, strlen(DOMAIN_UPLOADS)));
|
||||||
|
if ($rel === '' || strpos($rel, '..') !== false) { return $tag; }
|
||||||
|
$file = PATH_UPLOADS . $rel;
|
||||||
|
if (!is_file($file)) { return $tag; }
|
||||||
|
$size = @getimagesize($file);
|
||||||
|
if (!$size || empty($size[0]) || empty($size[1])) { return $tag; }
|
||||||
|
return preg_replace('/<img\b/i', '<img width="' . (int) $size[0] . '" height="' . (int) $size[1] . '"', $tag, 1);
|
||||||
|
}, $html);
|
||||||
|
}
|
||||||
|
|
||||||
// Map a theme key to its toggle icon name.
|
// Map a theme key to its toggle icon name.
|
||||||
function blowfish_theme_icon($theme) {
|
function blowfish_theme_icon($theme) {
|
||||||
$map = array(
|
$map = array(
|
||||||
|
|||||||
+5
-1
@@ -102,11 +102,15 @@
|
|||||||
// Lazy-load + async-decode in-content images that don't already
|
// Lazy-load + async-decode in-content images that don't already
|
||||||
// set a loading attribute. Operates on the rendered HTML, so
|
// set a loading attribute. Operates on the rendered HTML, so
|
||||||
// carousel/tabs fences (still raw text inside <pre>) are untouched.
|
// carousel/tabs fences (still raw text inside <pre>) are untouched.
|
||||||
echo preg_replace(
|
$bdContent = preg_replace(
|
||||||
'/<img(?![^>]*\bloading=)/i',
|
'/<img(?![^>]*\bloading=)/i',
|
||||||
'<img loading="lazy" decoding="async"',
|
'<img loading="lazy" decoding="async"',
|
||||||
$page->content()
|
$page->content()
|
||||||
);
|
);
|
||||||
|
// Add width/height to local-upload images so the layout doesn't
|
||||||
|
// shift as they load (CLS). CSS `img { height: auto }` keeps the
|
||||||
|
// aspect ratio responsive.
|
||||||
|
echo blowfish_img_dimensions($bdContent);
|
||||||
?>
|
?>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user