Skip to content

Commit a9cb3da

Browse files
ten9876claude
andcommitted
Blog cards: thumbnail beside the summary, and bust the CSS cache
The reported symptom — enormous images and arrows, no card framing — was the blog CSS not applying at all. styles.css is served with max-age=14400 and was linked without a version, so browsers held the pre-blog stylesheet against the new markup and fell back to intrinsic sizes. Link it as styles.css?v=3 on all three pages so the rules actually arrive. Rework the card as a horizontal media object: a 236px thumbnail on the left, summary on the right, in a single 900px column so lines stay a readable length. The thumbnail fills its column via absolute inset, so it tracks the card height the text sets. Below 940px it narrows to 190px; below 560px it stacks with the image on top. The post hero is capped at 340px so a wide screenshot no longer pushes the headline off-screen. Also make that failure mode survivable rather than catastrophic: inline icons and images now carry width/height, so missing CSS yields a plain page instead of full-bleed screenshots and 300px arrows. Image dimensions double as aspect-ratio hints, removing load-time layout shift. build.py now matches the stylesheet link by pattern — the exact-string replace would have silently skipped inlining and emitted an unstyled bundle the moment a version query appeared. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent eeae08e commit a9cb3da

5 files changed

Lines changed: 50 additions & 33 deletions

File tree

404.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<meta name="viewport" content="width=device-width, initial-scale=1" />
66
<title>Signal lost — AetherSDR</title>
77
<link rel="icon" type="image/png" href="/assets/img/logo-96.png" />
8-
<link rel="stylesheet" href="/styles.css" />
8+
<link rel="stylesheet" href="/styles.css?v=3" />
99
<style>
1010
.nf { min-height: 78vh; display: grid; place-items: center; text-align: center; }
1111
.nf-inner { max-width: 30rem; padding: 0 24px; }

blog.html

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828

2929
<link rel="icon" type="image/png" href="assets/img/logo-96.png" />
3030
<link rel="apple-touch-icon" href="assets/img/apple-touch-icon.png" />
31-
<link rel="stylesheet" href="styles.css" />
31+
<link rel="stylesheet" href="styles.css?v=3" />
3232

3333
<!-- Without JS there's nothing to route, so show every post stacked under
3434
the index rather than a grid of cards that go nowhere. -->
@@ -58,19 +58,19 @@
5858
<div class="nav-item">
5959
<button class="nav-dd-toggle" type="button" aria-haspopup="true">
6060
Support
61-
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M6 9l6 6 6-6"/></svg>
61+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M6 9l6 6 6-6"/></svg>
6262
</button>
6363
<div class="nav-dd" role="menu">
6464
<a role="menuitem" href="https://lu5dx.github.io/AetherSDRDocsEnglish/latest/" target="_blank" rel="noopener">
65-
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linejoin="round"><path d="M4 5a2 2 0 0 1 2-2h9l5 5v11a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2z"/><path d="M8 10h8M8 14h8"/></svg>
65+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linejoin="round"><path d="M4 5a2 2 0 0 1 2-2h9l5 5v11a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2z"/><path d="M8 10h8M8 14h8"/></svg>
6666
User Manual
6767
</a>
6868
<a role="menuitem" href="https://www.youtube.com/@AetherSDR" target="_blank" rel="noopener">
69-
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linejoin="round"><rect x="3" y="5" width="18" height="14" rx="2"/><path d="M10 9l5 3-5 3z"/></svg>
69+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linejoin="round"><rect x="3" y="5" width="18" height="14" rx="2"/><path d="M10 9l5 3-5 3z"/></svg>
7070
Video tutorials
7171
</a>
7272
<a role="menuitem" href="https://github.com/aethersdr/AetherSDR/issues/new/choose" target="_blank" rel="noopener">
73-
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="4"/><path d="M12 2v3M12 19v3M2 12h3M19 12h3M5 5l2 2M17 17l2 2M19 5l-2 2M7 17l-2 2"/></svg>
73+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="4"/><path d="M12 2v3M12 19v3M2 12h3M19 12h3M5 5l2 2M17 17l2 2M19 5l-2 2M7 17l-2 2"/></svg>
7474
File an issue on GitHub
7575
</a>
7676
</div>
@@ -102,7 +102,7 @@ <h2>The AetherSDR blog</h2>
102102

103103
<a class="blog-card" href="#gpu-multislice">
104104
<div class="blog-card-media">
105-
<img src="assets/img/pan-multislice.jpg" alt="Multiple AetherSDR panadapter slices rendering simultaneously on the 20m band" loading="lazy" />
105+
<img width="1400" height="1098" src="assets/img/pan-multislice.jpg" alt="Multiple AetherSDR panadapter slices rendering simultaneously on the 20m band" loading="lazy" />
106106
</div>
107107
<div class="blog-card-body">
108108
<span class="blog-meta">
@@ -116,14 +116,14 @@ <h3>Every slice, rendered at once</h3>
116116
removed that tradeoff — here's what changed in the render pipeline, and what
117117
a real multislice 20m capture looks like at 60 fps.</p>
118118
<span class="blog-more">Read post
119-
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14m0 0-5-5m5 5-5 5"/></svg>
119+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14m0 0-5-5m5 5-5 5"/></svg>
120120
</span>
121121
</div>
122122
</a>
123123

124124
<a class="blog-card" href="#signal-chain">
125125
<div class="blog-card-media">
126-
<img src="assets/img/aetherial-strip.jpg" alt="The Aetherial signal-chain strip in AetherSDR, showing the processing stages end to end" loading="lazy" />
126+
<img width="1400" height="926" src="assets/img/aetherial-strip.jpg" alt="The Aetherial signal-chain strip in AetherSDR, showing the processing stages end to end" loading="lazy" />
127127
</div>
128128
<div class="blog-card-body">
129129
<span class="blog-meta">
@@ -137,14 +137,14 @@ <h3>Inside the Aetherial signal chain</h3>
137137
what each stage does to the audio, why the order matters, and how to
138138
set it up for a crowded band without over-processing yourself into mush.</p>
139139
<span class="blog-more">Read post
140-
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14m0 0-5-5m5 5-5 5"/></svg>
140+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14m0 0-5-5m5 5-5 5"/></svg>
141141
</span>
142142
</div>
143143
</a>
144144

145145
<a class="blog-card" href="#three-platforms">
146146
<div class="blog-card-media">
147-
<img src="assets/img/hero-3dss.jpg" alt="AetherSDR's 3D stacked-trace panadapter over the 40m band with the full control surface" loading="lazy" />
147+
<img width="1640" height="830" src="assets/img/hero-3dss.jpg" alt="AetherSDR's 3D stacked-trace panadapter over the 40m band with the full control surface" loading="lazy" />
148148
</div>
149149
<div class="blog-card-body">
150150
<span class="blog-meta">
@@ -158,7 +158,7 @@ <h3>Native on three platforms, no compromises</h3>
158158
Qt6 and C++20 codebase. Here's how the build is structured, and where
159159
platform-specific code actually earns its keep.</p>
160160
<span class="blog-more">Read post
161-
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14m0 0-5-5m5 5-5 5"/></svg>
161+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14m0 0-5-5m5 5-5 5"/></svg>
162162
</span>
163163
</div>
164164
</a>
@@ -174,12 +174,12 @@ <h3>Native on three platforms, no compromises</h3>
174174
<article class="blog-post" data-post="gpu-multislice" tabindex="-1" aria-labelledby="h-gpu-multislice" hidden>
175175
<div class="wrap blog-post-wrap">
176176
<a class="blog-back" href="#" data-blog-back>
177-
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 12H5m0 0 5-5m-5 5 5 5"/></svg>
177+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 12H5m0 0 5-5m-5 5 5 5"/></svg>
178178
Back to blog
179179
</a>
180180

181181
<figure class="blog-hero">
182-
<img src="assets/img/pan-multislice.jpg" alt="Multiple AetherSDR panadapter slices rendering simultaneously on the 20m band" />
182+
<img width="1400" height="1098" src="assets/img/pan-multislice.jpg" alt="Multiple AetherSDR panadapter slices rendering simultaneously on the 20m band" />
183183
</figure>
184184

185185
<span class="blog-meta">
@@ -230,7 +230,7 @@ <h2>What it means at the operating position</h2>
230230
</div>
231231

232232
<a class="blog-back blog-back-end" href="#" data-blog-back>
233-
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 12H5m0 0 5-5m-5 5 5 5"/></svg>
233+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 12H5m0 0 5-5m-5 5 5 5"/></svg>
234234
Back to blog
235235
</a>
236236
</div>
@@ -239,12 +239,12 @@ <h2>What it means at the operating position</h2>
239239
<article class="blog-post" data-post="signal-chain" tabindex="-1" aria-labelledby="h-signal-chain" hidden>
240240
<div class="wrap blog-post-wrap">
241241
<a class="blog-back" href="#" data-blog-back>
242-
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 12H5m0 0 5-5m-5 5 5 5"/></svg>
242+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 12H5m0 0 5-5m-5 5 5 5"/></svg>
243243
Back to blog
244244
</a>
245245

246246
<figure class="blog-hero">
247-
<img src="assets/img/aetherial-strip.jpg" alt="The Aetherial signal-chain strip in AetherSDR, showing the processing stages end to end" />
247+
<img width="1400" height="926" src="assets/img/aetherial-strip.jpg" alt="The Aetherial signal-chain strip in AetherSDR, showing the processing stages end to end" />
248248
</figure>
249249

250250
<span class="blog-meta">
@@ -295,7 +295,7 @@ <h2>On transmit</h2>
295295
</div>
296296

297297
<a class="blog-back blog-back-end" href="#" data-blog-back>
298-
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 12H5m0 0 5-5m-5 5 5 5"/></svg>
298+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 12H5m0 0 5-5m-5 5 5 5"/></svg>
299299
Back to blog
300300
</a>
301301
</div>
@@ -304,12 +304,12 @@ <h2>On transmit</h2>
304304
<article class="blog-post" data-post="three-platforms" tabindex="-1" aria-labelledby="h-three-platforms" hidden>
305305
<div class="wrap blog-post-wrap">
306306
<a class="blog-back" href="#" data-blog-back>
307-
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 12H5m0 0 5-5m-5 5 5 5"/></svg>
307+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 12H5m0 0 5-5m-5 5 5 5"/></svg>
308308
Back to blog
309309
</a>
310310

311311
<figure class="blog-hero">
312-
<img src="assets/img/hero-3dss.jpg" alt="AetherSDR's 3D stacked-trace panadapter over the 40m band with the full control surface" />
312+
<img width="1640" height="830" src="assets/img/hero-3dss.jpg" alt="AetherSDR's 3D stacked-trace panadapter over the 40m band with the full control surface" />
313313
</figure>
314314

315315
<span class="blog-meta">
@@ -357,7 +357,7 @@ <h2>Get the build for your machine</h2>
357357
</div>
358358

359359
<a class="blog-back blog-back-end" href="#" data-blog-back>
360-
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 12H5m0 0 5-5m-5 5 5 5"/></svg>
360+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 12H5m0 0 5-5m-5 5 5 5"/></svg>
361361
Back to blog
362362
</a>
363363
</div>

index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929

3030
<link rel="icon" type="image/png" href="assets/img/logo-96.png" />
3131
<link rel="apple-touch-icon" href="assets/img/apple-touch-icon.png" />
32-
<link rel="stylesheet" href="styles.css" />
32+
<link rel="stylesheet" href="styles.css?v=3" />
3333
</head>
3434
<body>
3535

scripts/build.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,16 @@ def main():
2727
with open(os.path.join(ROOT, "styles.css"), encoding="utf-8") as f:
2828
css = f.read()
2929

30-
# Inline the stylesheet.
31-
html = html.replace(
32-
'<link rel="stylesheet" href="styles.css" />',
33-
f"<style>\n{css}\n</style>",
30+
# Inline the stylesheet. Matched by pattern rather than exact string so a
31+
# cache-busting query (styles.css?v=3) doesn't silently skip the inlining
32+
# and produce an unstyled bundle.
33+
html, n = re.subn(
34+
r'<link rel="stylesheet" href="styles\.css[^"]*"\s*/?>',
35+
lambda _m: f"<style>\n{css}\n</style>",
36+
html,
3437
)
38+
if n != 1:
39+
raise SystemExit(f"expected 1 stylesheet link to inline, found {n}")
3540

3641
# Inline JS as a real <script> before the asset pass — data-URI-ing a
3742
# script src would break execution in the single-file bundle.

styles.css

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -549,9 +549,13 @@ footer.foot { border-top: 1px solid var(--line); margin-top: 30px; padding: 46px
549549
.nav-links a.is-active { color: var(--cyan); }
550550

551551
.blog-index { padding-top: 76px; }
552-
.blog-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 22px; }
552+
/* One column of wide cards: thumbnail left, summary right. Capped well under
553+
the page width so summary lines stay a comfortable reading length. */
554+
.blog-grid { display: grid; gap: 18px; max-width: 900px; margin: 0 auto; }
553555
.blog-card {
554-
display: flex; flex-direction: column;
556+
display: grid;
557+
grid-template-columns: 236px 1fr;
558+
min-height: 176px;
555559
border: 1px solid var(--line);
556560
border-radius: var(--radius);
557561
background: var(--bg-1);
@@ -563,13 +567,16 @@ footer.foot { border-top: 1px solid var(--line); margin-top: 30px; padding: 46px
563567
transform: translateY(-3px);
564568
box-shadow: var(--shadow);
565569
}
566-
.blog-card-media { display: block; aspect-ratio: 16 / 9; background: var(--bg-2); overflow: hidden; }
570+
/* Absolute fill so the thumbnail matches the card's height — set by the text —
571+
without stretching, whatever the source image's aspect ratio is. */
572+
.blog-card-media { position: relative; background: var(--bg-2); overflow: hidden; }
567573
.blog-card-media img {
574+
position: absolute; inset: 0;
568575
width: 100%; height: 100%; object-fit: cover; display: block;
569576
transition: transform .35s ease;
570577
}
571578
.blog-card:hover .blog-card-media img { transform: scale(1.035); }
572-
.blog-card-body { display: block; padding: 20px 21px 22px; }
579+
.blog-card-body { display: block; padding: 20px 24px 22px; align-self: center; }
573580
.blog-card-body h3 { font-size: 18px; margin: 9px 0 8px; line-height: 1.3; }
574581
.blog-card-body p {
575582
margin: 0; color: var(--muted); font-size: 14px; line-height: 1.62;
@@ -610,7 +617,8 @@ footer.foot { border-top: 1px solid var(--line); margin-top: 30px; padding: 46px
610617
overflow: hidden;
611618
box-shadow: var(--shadow);
612619
}
613-
.blog-hero img { width: 100%; display: block; }
620+
/* Capped so a wide screenshot doesn't push the headline below the fold. */
621+
.blog-hero img { width: 100%; max-height: 340px; object-fit: cover; display: block; }
614622
.blog-post h1 {
615623
font-size: clamp(30px, 4.4vw, 44px);
616624
line-height: 1.14; letter-spacing: -0.02em;
@@ -644,7 +652,7 @@ footer.foot { border-top: 1px solid var(--line); margin-top: 30px; padding: 46px
644652

645653
/* ---------- Responsive ---------- */
646654
@media (max-width: 940px) {
647-
.blog-grid { grid-template-columns: repeat(2, 1fr); }
655+
.blog-card { grid-template-columns: 190px 1fr; min-height: 158px; }
648656
.blog-index { padding-top: 46px; }
649657
.blog-post { padding-top: 36px; }
650658
.feature-grid, .dl-grid { grid-template-columns: repeat(2, 1fr); }
@@ -661,7 +669,11 @@ footer.foot { border-top: 1px solid var(--line); margin-top: 30px; padding: 46px
661669
.nav-links > :not(.nav-blog) { display: none; }
662670
}
663671
@media (max-width: 560px) {
664-
.feature-grid, .dl-grid, .partners-grid, .blog-grid { grid-template-columns: 1fr; }
672+
.feature-grid, .dl-grid, .partners-grid { grid-template-columns: 1fr; }
673+
/* Too narrow to sit side by side — stack, with the image on top. */
674+
.blog-card { grid-template-columns: 1fr; min-height: 0; }
675+
.blog-card-media { height: 170px; }
676+
.blog-hero img { max-height: 220px; }
665677
.built-grid { grid-template-columns: 1fr 1fr; }
666678
.foot-grid { grid-template-columns: 1fr; }
667679
.hero { padding-top: 62px; }

0 commit comments

Comments
 (0)