diff --git a/src/components/blog/BlogImageSVG.astro b/src/components/blog/BlogImageSVG.astro index 58a981d7b..a6d2c46a2 100644 --- a/src/components/blog/BlogImageSVG.astro +++ b/src/components/blog/BlogImageSVG.astro @@ -8,10 +8,58 @@ interface Props { const { slug, title } = Astro.props; const svgContent = svgs[`/src/assets/blog/${slug}.svg`] ?? ''; + +/** + * Where the gradient's two stops sit, in the rect's own coordinates. + * + * The artwork is scaled inside its frame, so the frame shows the middle slice + * of the SVG and never its top or bottom edge. Stops at 0 and 1 would put both + * ends of the gradient in the part that is cropped away, and the visible slice + * would show neither the light colour nor the dark one. So they sit on the + * edges of the visible slice instead, and `spreadMethod` (pad, the default) + * fills the cropped remainder with the end colours. + * + * Both numbers stay inside 0..1. Moving the gradient VECTOR out of range + * instead — y1 below zero — is valid SVG and renders correctly in Chromium, + * but not on iOS. + * + * COVER_SCALE has to match the `scale()` in the stylesheet below. + */ +const COVER_SCALE = 1.35; +const visible = 1 / COVER_SCALE; +const stopTop = +((1 - visible) / 2).toFixed(4); +const stopFoot = +(1 - stopTop).toFixed(4); --- {svgContent && ( -
+ <> + {/* + The gradient the cover's `.bg` rect is filled with. It has to be a real + SVG paint server rather than a CSS gradient, because `fill` on an SVG + shape takes `url(#id)` and nothing else. + + One def per cover, keyed by slug: SVG ids are document-global, and a page + can carry more than one cover — the blog index shows several — so a shared + id would paint them all from whichever parsed first. The id reaches the + stylesheet through --cover-bg on the host, since a scoped rule cannot + interpolate the slug itself. + */} + + + > )}