66 rule("stable-card-regions", "Reserve consistent media, body, and action regions so content length does not make peers look accidental."),
67 rule("aligned-card-actions", "Align equivalent card actions to the same block edge when their semantic role is shared."),
68 rule("subtle-hover", "Use hover/focus for subtle non-structural feedback; do not reflow peer cards."),
69 );
70 recipe.push(
71 "Mark repeated cards data-rp-item, their media data-rp-media, body data-rp-body, and equivalent CTA data-rp-action.",
72 "Use data-rp-motion=micro for hover/focus treatment and data-rp-motion=state for click/activation geometry.",
73 );
74 if (heightEvidence.sourceEqualHeight) {
75 rules.push(rule("equal-repeated-item-height", `Keep all peer cards at one block size. Source spread is ${heightEvidence.spreadRatio}; active state changes inline size, not height.`));
87 rules.push(rule("local-horizontal-overflow", "Keep intentional overflow inside the rail viewport; the document itself must not overflow horizontally."));
88 }
89 }
90
91 if (scene?.implementation?.primitive === "scroll-scene") {
92 rules.push(
93 rule("stable-scroll-composition", "Keep media and copy in the extracted scroll relationship; polish spacing without converting the scene to an unrelated static layout."),
94 rule("stable-tab-anchor", "Keep tab controls anchored while panel content changes; transition opacity/transform rather than causing abrupt horizontal movement."),
95 );
96 recipe.push("Use the scroll-scene markers and progress variables first; normalize text measure, control spacing, and panel transitions inside that composition.");
101 rules.unshift(rule("preserve-blueprint-composition", `Keep the blueprint's \`${blueprint.composition}\` composition, ordered background layers, and declared region relationships.`));
102 evidence.layoutBlueprint = {
103 composition: blueprint.composition,
104 backgroundKind: blueprint.background.kind,
105 relationships: blueprint.relationships,
106 confidence: blueprint.evidence.confidence,
107 };
108 }
109
110 if (section.kind === "hero") {
111 rules.push(rule("hero-identity-lock", "Preserve the hero media, headline hierarchy, copy anchor, and primary viewport footprint."));
112 }
113 if (section.capabilities?.hasCta) {
114 rules.push(rule("clear-action-affordance", "Give equivalent actions consistent size, focus treatment, and pointer affordance without inventing a new CTA hierarchy."));
178 reducedMotion: "Remove non-essential interpolation while preserving immediate final state and focus visibility.",
179 };
180}
181
182function globalRules() {
183 return [
184 rule("identity-before-polish", "Do not apply a best practice that changes an identity lock."),
185 rule("content-reflow", "Essential text reflows without clipping at narrow widths, zoom, or text scaling."),
186 rule("focus-equivalence", "Every pointer interaction has an operable keyboard path and visible focus treatment."),
187 rule("intentional-overflow", "Only bounded carousels/rails may overflow horizontally; the page may not."),
188 rule("interruptible-motion", "Rapid activation may interrupt animation, but final semantic state and content must remain correct."),
189 rule("reduced-motion", "Respect prefers-reduced-motion without removing access to content or state."),
190 ];
191}
192
193function baseSectionRules(section) {
194 const rules = [
195 rule("preserve-archetype", `Keep this ${section.variant || section.kind || "section"} recognizable as the same source layout archetype.`),
196 rule("consistent-spacing", "Use a coherent local spacing rhythm and align equivalent edges."),
197 rule("responsive-without-clipping", "Reflow content at narrow widths without clipping, accidental overlap, or page-level horizontal overflow."),
198 ];
199 if (section.capabilities?.hasMedia) rules.push(rule("stable-media-region", "Give source media a deliberate bounded region and preserve its crop/fill intent."));