ferst-core 0.7.7 → 0.7.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/content/blocks.ts CHANGED
@@ -146,9 +146,19 @@ export const heroBlock = z.object({
146
146
  })
147
147
  .optional(),
148
148
  layout: z.enum(['centered', 'split', 'media']).default('centered'),
149
- /** How much of the screen a `media` hero takes: `standard` (a band), `tall`,
150
- * or `screen` — the full first screen, edge to edge (e.g. a video opener). */
151
- size: z.enum(['standard', 'tall', 'screen']).default('standard'),
149
+ /** How much of the screen a `media` hero takes: `compact` (a narrow band for a short,
150
+ * wide header strip, e.g. 1000 × 175), `standard` (a band), `tall`, or `screen` — the
151
+ * full first screen, edge to edge (e.g. a video opener). */
152
+ size: z.enum(['compact', 'standard', 'tall', 'screen']).default('standard'),
153
+ /** A `media` hero runs edge to edge across the screen, like a full-bleed banner
154
+ * (`screen` always does). */
155
+ fullBleed: z.boolean().default(false),
156
+ /** Which part of the background image shows when it's cropped: one or two positions,
157
+ * keywords or percentages (e.g. `center 30%`, `top`, `left 20%`). Default `center`. */
158
+ focus: z
159
+ .string()
160
+ .regex(/^(?:left|center|right|top|bottom|\d{1,3}%)(?: (?:left|center|right|top|bottom|\d{1,3}%))?$/, 'focus is one or two of left / center / right / top / bottom / a percentage, e.g. "center 30%"')
161
+ .optional(),
152
162
  /** A `media` hero's background drifts more slowly than the page as it scrolls (any
153
163
  * background: image, slides or video). Off on touch devices and under reduced motion. */
154
164
  parallax: z.boolean().default(false),
@@ -773,7 +783,30 @@ export type LeafBlock =
773
783
  | z.infer<typeof listBlock>;
774
784
 
775
785
  /** The full recursive block type: leaf primitives + layout containers. */
776
- export type Block = LeafBlock | RecipeBlock | SectionBlock | GridBlock | StackBlock;
786
+ /**
787
+ * Alignment overrides every block takes, so a page's look can follow a client's preference
788
+ * from the page data alone. Defaults are the sensible visual choice (a block follows its
789
+ * page / section; a recipe's section title is centred).
790
+ * - `align`: re-aligns the whole block — title, intro, text, and the icons, pills and
791
+ * buttons in its cards — overriding the page / section for this one block.
792
+ * - `headingAlign`: just a recipe's title + intro (e.g. a start-aligned title over centred
793
+ * cards). Recipes without a section title ignore it.
794
+ * `heading`, `banner` and `section` already had `align` with this meaning and keep it.
795
+ */
796
+ const alignOption = z.enum(['start', 'center', 'end']);
797
+ export interface BlockLayout {
798
+ align?: 'start' | 'center' | 'end';
799
+ headingAlign?: 'start' | 'center' | 'end';
800
+ }
801
+ function withLayout<T extends z.AnyZodObject>(block: T): z.AnyZodObject {
802
+ const shape = block.shape as Record<string, unknown>;
803
+ return block.extend({
804
+ ...('align' in shape ? {} : { align: alignOption.optional() }),
805
+ headingAlign: alignOption.optional(),
806
+ });
807
+ }
808
+
809
+ export type Block = (LeafBlock | RecipeBlock | SectionBlock | GridBlock | StackBlock) & BlockLayout;
777
810
  export type BlockType = Block['type'];
778
811
 
779
812
  export const sectionBlock = z.object({
@@ -806,54 +839,57 @@ export const stackBlock = z.object({
806
839
  * a member without a matching BlockRenderer registry entry is a TypeScript error
807
840
  * (the exhaustiveness guard against a silent-undefined render crash). Cast to
808
841
  * the recursive `Block` type because `z.lazy` fields can't be inferred. */
809
- export const blockSchema = z.discriminatedUnion('type', [
810
- headingBlock,
811
- proseBlock,
812
- imageBlock,
813
- buttonBlock,
814
- quoteBlock,
815
- dividerBlock,
816
- spacerBlock,
817
- badgeBlock,
818
- statBlock,
819
- listBlock,
820
- heroBlock,
821
- featureCardsBlock,
822
- ctaBlock,
823
- faqBlock,
824
- galleryBlock,
825
- testimonialBlock,
826
- contactFormBlock,
827
- formBlock,
828
- accordionBlock,
829
- tilesBlock,
830
- bannerBlock,
831
- statsBlock,
832
- stepsBlock,
833
- pricingBlock,
834
- logosBlock,
835
- tabsBlock,
836
- announcementBlock,
837
- bentoBlock,
838
- marqueeBlock,
839
- latestPostsBlock,
840
- mapEmbedBlock,
841
- tableBlock,
842
- noticeBlock,
843
- mediaCardsBlock,
844
- contactCardBlock,
845
- locationsBlock,
846
- videoBlock,
847
- documentsBlock,
848
- linkRowBlock,
849
- documentBlock,
850
- timetableBlock,
851
- contactsBlock,
852
- mediaTextBlock,
853
- sectionBlock,
854
- gridBlock,
855
- stackBlock,
856
- ]) as unknown as z.ZodType<Block>;
842
+ export const blockSchema = z.discriminatedUnion(
843
+ 'type',
844
+ [
845
+ headingBlock,
846
+ proseBlock,
847
+ imageBlock,
848
+ buttonBlock,
849
+ quoteBlock,
850
+ dividerBlock,
851
+ spacerBlock,
852
+ badgeBlock,
853
+ statBlock,
854
+ listBlock,
855
+ heroBlock,
856
+ featureCardsBlock,
857
+ ctaBlock,
858
+ faqBlock,
859
+ galleryBlock,
860
+ testimonialBlock,
861
+ contactFormBlock,
862
+ formBlock,
863
+ accordionBlock,
864
+ tilesBlock,
865
+ bannerBlock,
866
+ statsBlock,
867
+ stepsBlock,
868
+ pricingBlock,
869
+ logosBlock,
870
+ tabsBlock,
871
+ announcementBlock,
872
+ bentoBlock,
873
+ marqueeBlock,
874
+ latestPostsBlock,
875
+ mapEmbedBlock,
876
+ tableBlock,
877
+ noticeBlock,
878
+ mediaCardsBlock,
879
+ contactCardBlock,
880
+ locationsBlock,
881
+ videoBlock,
882
+ documentsBlock,
883
+ linkRowBlock,
884
+ documentBlock,
885
+ timetableBlock,
886
+ contactsBlock,
887
+ mediaTextBlock,
888
+ sectionBlock,
889
+ gridBlock,
890
+ stackBlock,
891
+ ].map(withLayout) as unknown as [z.AnyZodObject, z.AnyZodObject, ...z.AnyZodObject[]],
892
+ ) as unknown as z.ZodType<Block>;
857
893
 
858
894
  /** A page = metadata + an ordered list of blocks. Stored as JSON in the client
859
895
  * repo; the AI composes it, a human verifies. This is also the schema of the