each row picks its own main image, and frames it

The shape is one decision for the whole section and stays on the template — every
collab is a 21:9 band or none of them is. Which part of THIS photograph survives
that shape is a decision about this photograph, and there is one per row. A single
rectangle chosen against one lead and applied to all of them is how a portrait
ends up cropped through somebody's head, which is what the block-level crop did
and why it is gone.

So a row's settings now carry Main image: a picture and a cropping frame in the
proportions the page asked for. Empty means the first item of its carousel, which
is the ordinary state and is not stored — reorder the carousel and the lead
follows. Choosing one is the deliberate act of saying "not that one", and it may
be a picture the carousel does not contain at all.

`cover_shape` rides along with each row so this panel can draw the right frame
without knowing how a template is assembled. SHAPES moved out of PageBuilderPanel
because two screens offer them now, and two lists is two places to add the next
ratio.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-05 15:41:37 +01:00
co-authored by Claude Opus 5
parent c6cebc5000
commit 5968cb5d83
5 changed files with 105 additions and 19 deletions
+12
View File
@@ -32,6 +32,18 @@ export type SubjectRecord = {
description: string | null;
sort_order: number;
photo_count: number;
/** The row's own lead, or null meaning "the first item of its carousel". */
cover_url: string | null;
/** How that picture is framed, `x,y,w,h` in percentages. Null is centred. */
cover_crop: string | null;
/**
* The shape the template frames a lead into, or null for the picture's own.
*
* Read off the page, not the row — one decision for the whole section — and sent with the row so
* this panel can draw a cropping frame of the right proportions without knowing how a template is
* put together.
*/
cover_shape: number | null;
};
export type Collection = SubjectRecord & {