e28230bcef
Deploy to Firebase Hosting / deploy (push) Failing after 2m4s
## Responsive sources Wire `ensureResponsiveImage` (multi-width × multi-format srcset) into the three remaining single-`<img>` components: - ImageBlock (body): widths 600 + 1200, fit follows transform params - ImageGalleryBlock (single-image branch): widths 600 + 1280, fit=contain - PostCard (post-list cards): widths 400 + 800, fit=cover, ar=3:2 `resolveContentImages` now sets `block.resolvedResponsive` alongside the legacy `resolvedImageSrc`, so every Svelte block component renders `<picture>` if the responsive shape is present and falls back to a plain `<img>` otherwise. Posts list pages (`/posts`, `/posts/page/N`, `/posts/tag/.../page/N`) call a new `resolvePostCardResponsive` helper instead of one-shot `ensureTransformedImage`. ## Drop AVIF from defaults Server-side AVIF encoding on the CMS is 5-10× slower than WebP. The bandwidth win is marginal for this site and the build time hit is severe (multi-minute hangs on each new variant). Default formats are now `["webp", "jpeg"]` for `ensureResponsiveImage`, in `Layout.astro`'s TopBanner hero, and in the post-card helper. Callers can still opt back into AVIF per-call. ## Module split for client-safe types `ResponsiveImage` is now defined in a tiny `rusty-image-types.ts` and duplicated in `rusty-image.ts` so Svelte components can `import type` the shape without dragging `rusty-image.ts` (and its `node:crypto`/`node:fs` imports) into the browser bundle. `resolvePostCardResponsive` lives in a new `server-image.ts` for the same reason — `blog-utils.ts` is imported by Svelte islands, so its server-only helpers move out. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
79 lines
2.8 KiB
Svelte
79 lines
2.8 KiB
Svelte
<script lang="ts">
|
|
import { getBlockLayoutClasses } from "../../lib/block-layout";
|
|
import type { ImageBlockData } from "../../lib/block-types";
|
|
|
|
let { block }: { block: ImageBlockData } = $props();
|
|
|
|
function getUrl(b: ImageBlockData): string | null {
|
|
if (typeof b.resolvedImageSrc === "string" && b.resolvedImageSrc) return b.resolvedImageSrc;
|
|
const img = b.img ?? b.image;
|
|
if (typeof img === "string" && img.startsWith("http")) return img;
|
|
if (img && typeof img === "object") {
|
|
const src = (img as { src?: string }).src ?? (img as { file?: { url?: string } }).file?.url;
|
|
if (typeof src === "string" && src) return src.startsWith("//") ? `https:${src}` : src;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
const layoutClasses = $derived(getBlockLayoutClasses(block.layout));
|
|
const imageSource = $derived(block.image ?? block.img);
|
|
const imageUrl = $derived(getUrl(block));
|
|
$effect(() => {
|
|
if (!imageUrl && block._slug) {
|
|
console.warn("[ImageBlock] Bild nicht verfügbar:", block._slug, {
|
|
resolvedImageSrc: block.resolvedImageSrc,
|
|
hasImage: !!block.image,
|
|
hasImg: !!block.img,
|
|
imageType: typeof block.image,
|
|
imgType: typeof block.img,
|
|
imgSrc: block.img && typeof block.img === "object" ? (block.img as { src?: string }).src : undefined,
|
|
imageSrc: block.image && typeof block.image === "object" ? (block.image as { src?: string }).src : undefined,
|
|
});
|
|
}
|
|
});
|
|
const title = $derived(
|
|
typeof imageSource === "object" && imageSource
|
|
? ("description" in imageSource && imageSource.description
|
|
? imageSource.description
|
|
: "title" in imageSource
|
|
? (imageSource as { title?: string }).title
|
|
: undefined)
|
|
: undefined,
|
|
);
|
|
</script>
|
|
|
|
<div class={layoutClasses} data-block-type="image" data-block-slug={block._slug}>
|
|
{#if imageUrl}
|
|
<figure class="max-w-[400px]">
|
|
{#if block.resolvedResponsive}
|
|
<picture>
|
|
{#each block.resolvedResponsive.sources as s (s.type)}
|
|
<source type={s.type} srcset={s.srcset} sizes="(max-width: 768px) 100vw, 400px" />
|
|
{/each}
|
|
<img
|
|
src={block.resolvedResponsive.fallback}
|
|
width={block.resolvedResponsive.width}
|
|
height={block.resolvedResponsive.height}
|
|
alt={block.caption ?? title ?? ""}
|
|
class="w-full rounded-sm"
|
|
loading="lazy"
|
|
decoding="async"
|
|
/>
|
|
</picture>
|
|
{:else}
|
|
<img
|
|
src={imageUrl}
|
|
alt={block.caption ?? title ?? ""}
|
|
class="w-full rounded-sm"
|
|
loading="lazy"
|
|
/>
|
|
{/if}
|
|
{#if block.caption}
|
|
<figcaption class="mt-1 text-sm text-zinc-600">{block.caption}</figcaption>
|
|
{/if}
|
|
</figure>
|
|
{:else}
|
|
<p class="text-sm text-zinc-500">Bild nicht verfügbar.</p>
|
|
{/if}
|
|
</div>
|