@aglyn/plugins-ai 1.0.0-beta.233 → 1.0.0-beta.234

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.
Files changed (104) hide show
  1. package/package.json +17 -17
  2. package/src/lib/components/ai-job-links.d.ts +6 -0
  3. package/src/lib/components/ai-job-links.js +9 -0
  4. package/src/lib/components/ai-job-links.js.map +1 -1
  5. package/src/lib/components/ai-site-build-page.component.js +41 -3
  6. package/src/lib/components/ai-site-build-page.component.js.map +1 -1
  7. package/src/lib/components/ai-site-start-card.component.js +91 -8
  8. package/src/lib/components/ai-site-start-card.component.js.map +1 -1
  9. package/src/lib/jobs/ai-job-admission.d.ts +2 -1
  10. package/src/lib/jobs/ai-job-admission.js.map +1 -1
  11. package/src/lib/jobs/ai-job-email-step.d.ts +39 -0
  12. package/src/lib/jobs/ai-job-email-step.js +116 -5
  13. package/src/lib/jobs/ai-job-email-step.js.map +1 -1
  14. package/src/lib/jobs/ai-job-layout-language.d.ts +3 -0
  15. package/src/lib/jobs/ai-job-layout-language.js +3 -1
  16. package/src/lib/jobs/ai-job-layout-language.js.map +1 -1
  17. package/src/lib/jobs/ai-job-layout-site-pages.d.ts +2 -0
  18. package/src/lib/jobs/ai-job-layout-site-pages.js +23 -11
  19. package/src/lib/jobs/ai-job-layout-site-pages.js.map +1 -1
  20. package/src/lib/jobs/ai-job-layout-step.js +3 -2
  21. package/src/lib/jobs/ai-job-layout-step.js.map +1 -1
  22. package/src/lib/jobs/ai-job-page-language.d.ts +16 -0
  23. package/src/lib/jobs/ai-job-page-language.js +35 -6
  24. package/src/lib/jobs/ai-job-page-language.js.map +1 -1
  25. package/src/lib/jobs/ai-job-page-step.d.ts +3 -0
  26. package/src/lib/jobs/ai-job-page-step.js +18 -4
  27. package/src/lib/jobs/ai-job-page-step.js.map +1 -1
  28. package/src/lib/jobs/ai-job-plan-step.d.ts +34 -0
  29. package/src/lib/jobs/ai-job-plan-step.js +106 -14
  30. package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
  31. package/src/lib/jobs/ai-job-site-content.d.ts +6 -6
  32. package/src/lib/jobs/ai-job-site-content.js +6 -12
  33. package/src/lib/jobs/ai-job-site-content.js.map +1 -1
  34. package/src/lib/jobs/ai-job-site-step.d.ts +11 -0
  35. package/src/lib/jobs/ai-job-site-step.js +96 -44
  36. package/src/lib/jobs/ai-job-site-step.js.map +1 -1
  37. package/src/lib/jobs/ai-jobs.d.ts +8 -4
  38. package/src/lib/jobs/ai-jobs.js +19 -2
  39. package/src/lib/jobs/ai-jobs.js.map +1 -1
  40. package/src/lib/jobs/ai-layout-stock-photos.d.ts +98 -0
  41. package/src/lib/jobs/ai-layout-stock-photos.js +248 -0
  42. package/src/lib/jobs/ai-layout-stock-photos.js.map +1 -0
  43. package/src/lib/jobs/ai-site-publish.d.ts +11 -0
  44. package/src/lib/jobs/ai-site-publish.js +31 -1
  45. package/src/lib/jobs/ai-site-publish.js.map +1 -1
  46. package/src/lib/jobs/fixtures/ai-juniper-portfolio-page-recording.d.ts +151 -0
  47. package/src/lib/jobs/fixtures/ai-juniper-portfolio-page-recording.js +183 -0
  48. package/src/lib/jobs/fixtures/ai-juniper-portfolio-page-recording.js.map +1 -0
  49. package/src/lib/jobs/fixtures/ai-slow-roads-site-plan-recording.d.ts +27 -0
  50. package/src/lib/jobs/fixtures/ai-slow-roads-site-plan-recording.js +192 -0
  51. package/src/lib/jobs/fixtures/ai-slow-roads-site-plan-recording.js.map +1 -0
  52. package/src/lib/jobs/fixtures/ai-yoga-site-plan-recording.d.ts +27 -0
  53. package/src/lib/jobs/fixtures/ai-yoga-site-plan-recording.js +222 -0
  54. package/src/lib/jobs/fixtures/ai-yoga-site-plan-recording.js.map +1 -0
  55. package/src/lib/layout-language/ai-layout-compiler.d.ts +29 -0
  56. package/src/lib/layout-language/ai-layout-compiler.js +698 -12
  57. package/src/lib/layout-language/ai-layout-compiler.js.map +1 -1
  58. package/src/lib/layout-language/ai-layout-design.d.ts +107 -0
  59. package/src/lib/layout-language/ai-layout-design.js +197 -0
  60. package/src/lib/layout-language/ai-layout-design.js.map +1 -0
  61. package/src/lib/layout-language/ai-layout-frame.d.ts +6 -0
  62. package/src/lib/layout-language/ai-layout-frame.js +22 -6
  63. package/src/lib/layout-language/ai-layout-frame.js.map +1 -1
  64. package/src/lib/layout-language/ai-layout-language.d.ts +2 -0
  65. package/src/lib/layout-language/ai-layout-language.js +15 -1
  66. package/src/lib/layout-language/ai-layout-language.js.map +1 -1
  67. package/src/lib/layout-language/ai-layout-links.d.ts +11 -0
  68. package/src/lib/layout-language/ai-layout-links.js +4 -0
  69. package/src/lib/layout-language/ai-layout-links.js.map +1 -1
  70. package/src/lib/layout-language/ai-layout-pictures.d.ts +11 -5
  71. package/src/lib/layout-language/ai-layout-pictures.js +80 -17
  72. package/src/lib/layout-language/ai-layout-pictures.js.map +1 -1
  73. package/src/lib/model/ai-job-notice.d.ts +9 -1
  74. package/src/lib/model/ai-job-notice.js +23 -0
  75. package/src/lib/model/ai-job-notice.js.map +1 -1
  76. package/src/lib/model/ai-plan-capabilities.d.ts +7 -0
  77. package/src/lib/model/ai-plan-capabilities.js.map +1 -1
  78. package/src/lib/model/ai-site-build-progress.d.ts +12 -2
  79. package/src/lib/model/ai-site-build-progress.js +78 -4
  80. package/src/lib/model/ai-site-build-progress.js.map +1 -1
  81. package/src/lib/model/ai-site-job.d.ts +209 -22
  82. package/src/lib/model/ai-site-job.js +247 -20
  83. package/src/lib/model/ai-site-job.js.map +1 -1
  84. package/src/lib/model/ai-site-kinds.js +6 -1
  85. package/src/lib/model/ai-site-kinds.js.map +1 -1
  86. package/src/lib/model/ai-site-look.d.ts +21 -0
  87. package/src/lib/model/ai-site-look.js +63 -14
  88. package/src/lib/model/ai-site-look.js.map +1 -1
  89. package/src/lib/runtime/ai-doctrine-validators.d.ts +32 -1
  90. package/src/lib/runtime/ai-doctrine-validators.js +227 -4
  91. package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
  92. package/src/lib/runtime/ai-doctrine.js +5 -3
  93. package/src/lib/runtime/ai-doctrine.js.map +1 -1
  94. package/src/lib/runtime/ai-palette.generated.d.ts +2 -0
  95. package/src/lib/runtime/ai-palette.generated.js +18 -2
  96. package/src/lib/runtime/ai-palette.generated.js.map +1 -1
  97. package/src/lib/server/ai-jobs-resume.js.map +1 -1
  98. package/src/lib/server/ai-jobs-route.js +15 -3
  99. package/src/lib/server/ai-jobs-route.js.map +1 -1
  100. package/src/lib/server/assist-edit.js +5 -2
  101. package/src/lib/server/assist-edit.js.map +1 -1
  102. package/src/lib/usage/free-ai-credits-left.d.ts +54 -0
  103. package/src/lib/usage/free-ai-credits-left.js +62 -0
  104. package/src/lib/usage/free-ai-credits-left.js.map +1 -0
@@ -0,0 +1,107 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ /**
18
+ * THE DESIGNER LAYER of the layout compiler (AGL-3660): how a page the model
19
+ * described in the layout language is ARRANGED, decided by code from the
20
+ * site's kind and its job's seed — never asked of the model, so a recorded
21
+ * answer compiles into the new design with no new call.
22
+ *
23
+ * Why. A page compiled one way for every site read like a SaaS landing page:
24
+ * a centered text hero, three outlined cards, a numbered row, a solid form
25
+ * band. The designer templates people compare a site to (Squarespace's Loam,
26
+ * Clove, Auburn, Rivoli, Matsuya; Wix's portfolio and restaurant sets) are
27
+ * built from a handful of moves instead — a full-bleed photo hero with the
28
+ * type over it, image and text side by side, work shown as pictures in an
29
+ * uneven grid, writing as a list under thin rules, a closing band over a
30
+ * photo — and they pick those moves for the KIND of site.
31
+ *
32
+ * So a site's design is a set of VARIANTS — the hero's arrangement, how its
33
+ * groups are drawn, whether its text sections take a picture beside them,
34
+ * how its closing section reads — each chosen among the ones that suit the
35
+ * kind, by the seed. The choices are the site's: every page of one site is
36
+ * drawn with the same hero and the same group style, so the site reads as
37
+ * one design, and two sites of one kind come out differently.
38
+ *
39
+ * This is NOT a template library. A variant is a way of drawing whatever the
40
+ * model wrote; the sections, their order, their bands, their words and how
41
+ * many items each shows are still the model's, page by page.
42
+ *
43
+ * Pure: no tree, no theme, no network.
44
+ */
45
+ /** What the compiler is told about the site a page belongs to. */
46
+ export interface AiLayoutDesign {
47
+ /** The site's kind (`AI_SITE_KINDS`); an unknown one draws as a business site. */
48
+ kind: string;
49
+ /** The job's design seed: the same for every page of one site. */
50
+ seed: number;
51
+ /** Whether this page is the site's home, which opens tallest. */
52
+ home: boolean;
53
+ }
54
+ /**
55
+ * How the first section of a page is drawn:
56
+ * - `cover`: a full-bleed photo with the words over it on a scrim;
57
+ * - `split`: the words beside a tall photo, inside the page's measure;
58
+ * - `editorial`: display type across the page, the lede and the buttons in
59
+ * a row under it, and a wide photo below.
60
+ */
61
+ export type AiLayoutHeroVariant = 'cover' | 'split' | 'editorial';
62
+ /**
63
+ * How a group of cards is drawn where no component draws it:
64
+ * - `cards`: the theme's cards (the compiler's first design);
65
+ * - `ruled`: open columns, each under a thin rule, no card around it;
66
+ * - `pictures`: a picture over each item's title and caption, in an uneven
67
+ * grid (work, products);
68
+ * - `articles`: a picture over each title and summary, in even columns (writing);
69
+ * - `menu`: two columns of lines under rules (dishes, prices left out).
70
+ */
71
+ export type AiLayoutGroupVariant = 'cards' | 'ruled' | 'pictures' | 'articles' | 'menu';
72
+ /** How a numbered group is drawn: a row of numbers, or a ruled list beside its heading. */
73
+ export type AiLayoutStepsVariant = 'numbers' | 'timeline';
74
+ /** The site-level choices a page is drawn with. */
75
+ export interface AiLayoutDesignChoices {
76
+ hero: AiLayoutHeroVariant;
77
+ /** The group style of a section that names no subject the kind draws its own way. */
78
+ group: 'cards' | 'ruled';
79
+ steps: AiLayoutStepsVariant;
80
+ /** Whether a section of words takes a photo beside it. */
81
+ features: boolean;
82
+ /** Whether the photo of the first such section sits on the left. */
83
+ featureLeft: boolean;
84
+ /** Whether a closing section of words is drawn over a photo. */
85
+ coverClose: boolean;
86
+ /** Whether a section's heading sits beside its items rather than over them. */
87
+ splitHeads: boolean;
88
+ /** How work is laid out where the kind shows it as pictures. */
89
+ pictures: 'mosaic' | 'even';
90
+ }
91
+ type Family = 'gallery' | 'editorial' | 'hospitality' | 'calm' | 'retail' | 'standard';
92
+ /** The family a kind's pages are drawn in. */
93
+ export declare function aiLayoutFamily(kind: string): Family;
94
+ /**
95
+ * A site's choices, the same for every page of it. The first option of each
96
+ * list is the kind's best, so it is favored; the seed still turns it, so two
97
+ * sites of one kind are drawn apart.
98
+ */
99
+ export declare function aiLayoutDesignChoices(design: AiLayoutDesign): AiLayoutDesignChoices;
100
+ /**
101
+ * How a section's group of cards is drawn, from what the section is about:
102
+ * work in a gallery kind and products in a store are pictures, writing in a
103
+ * blog is articles, dishes in a restaurant a menu; anything else takes the
104
+ * site's own group style.
105
+ */
106
+ export declare function aiLayoutGroupVariant(design: AiLayoutDesign, choices: AiLayoutDesignChoices, words: string): AiLayoutGroupVariant;
107
+ export {};
@@ -0,0 +1,197 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */ /**
17
+ * THE DESIGNER LAYER of the layout compiler (AGL-3660): how a page the model
18
+ * described in the layout language is ARRANGED, decided by code from the
19
+ * site's kind and its job's seed — never asked of the model, so a recorded
20
+ * answer compiles into the new design with no new call.
21
+ *
22
+ * Why. A page compiled one way for every site read like a SaaS landing page:
23
+ * a centered text hero, three outlined cards, a numbered row, a solid form
24
+ * band. The designer templates people compare a site to (Squarespace's Loam,
25
+ * Clove, Auburn, Rivoli, Matsuya; Wix's portfolio and restaurant sets) are
26
+ * built from a handful of moves instead — a full-bleed photo hero with the
27
+ * type over it, image and text side by side, work shown as pictures in an
28
+ * uneven grid, writing as a list under thin rules, a closing band over a
29
+ * photo — and they pick those moves for the KIND of site.
30
+ *
31
+ * So a site's design is a set of VARIANTS — the hero's arrangement, how its
32
+ * groups are drawn, whether its text sections take a picture beside them,
33
+ * how its closing section reads — each chosen among the ones that suit the
34
+ * kind, by the seed. The choices are the site's: every page of one site is
35
+ * drawn with the same hero and the same group style, so the site reads as
36
+ * one design, and two sites of one kind come out differently.
37
+ *
38
+ * This is NOT a template library. A variant is a way of drawing whatever the
39
+ * model wrote; the sections, their order, their bands, their words and how
40
+ * many items each shows are still the model's, page by page.
41
+ *
42
+ * Pure: no tree, no theme, no network.
43
+ */ /** What the compiler is told about the site a page belongs to. */ const FAMILIES = {
44
+ portfolio: 'gallery',
45
+ photography: 'gallery',
46
+ studio: 'gallery',
47
+ blog: 'editorial',
48
+ restaurant: 'hospitality',
49
+ events: 'hospitality',
50
+ yoga: 'calm',
51
+ wellness: 'calm',
52
+ beauty: 'calm',
53
+ store: 'retail'
54
+ };
55
+ /** The heroes each kind opens with, best suited first. */ const HEROES = {
56
+ photography: [
57
+ 'cover',
58
+ 'editorial'
59
+ ],
60
+ portfolio: [
61
+ 'editorial',
62
+ 'split'
63
+ ],
64
+ studio: [
65
+ 'editorial',
66
+ 'cover'
67
+ ],
68
+ blog: [
69
+ 'editorial',
70
+ 'cover',
71
+ 'split'
72
+ ],
73
+ restaurant: [
74
+ 'cover',
75
+ 'split'
76
+ ],
77
+ events: [
78
+ 'cover',
79
+ 'split'
80
+ ],
81
+ fitness: [
82
+ 'cover',
83
+ 'split'
84
+ ],
85
+ music: [
86
+ 'cover',
87
+ 'editorial'
88
+ ],
89
+ nonprofit: [
90
+ 'cover',
91
+ 'split'
92
+ ],
93
+ realestate: [
94
+ 'cover',
95
+ 'split'
96
+ ],
97
+ trades: [
98
+ 'cover',
99
+ 'split'
100
+ ],
101
+ yoga: [
102
+ 'split',
103
+ 'cover'
104
+ ],
105
+ wellness: [
106
+ 'split',
107
+ 'cover'
108
+ ],
109
+ beauty: [
110
+ 'split',
111
+ 'cover',
112
+ 'editorial'
113
+ ],
114
+ store: [
115
+ 'split',
116
+ 'editorial'
117
+ ]
118
+ };
119
+ const DEFAULT_HEROES = [
120
+ 'split',
121
+ 'cover',
122
+ 'editorial'
123
+ ];
124
+ /** A small deterministic generator over a seed (mulberry32). */ function random(seed) {
125
+ let state = seed >>> 0;
126
+ return ()=>{
127
+ state = state + 0x6d2b79f5 >>> 0;
128
+ let t = state;
129
+ t = Math.imul(t ^ t >>> 15, t | 1);
130
+ t ^= t + Math.imul(t ^ t >>> 7, t | 61);
131
+ return ((t ^ t >>> 14) >>> 0) / 4294967296;
132
+ };
133
+ }
134
+ const pick = (next, list)=>list[Math.floor(next() * list.length) % list.length];
135
+ /** The family a kind's pages are drawn in. */ export function aiLayoutFamily(kind) {
136
+ var _FAMILIES_kind;
137
+ return (_FAMILIES_kind = FAMILIES[kind]) != null ? _FAMILIES_kind : 'standard';
138
+ }
139
+ /**
140
+ * A site's choices, the same for every page of it. The first option of each
141
+ * list is the kind's best, so it is favored; the seed still turns it, so two
142
+ * sites of one kind are drawn apart.
143
+ */ export function aiLayoutDesignChoices(design) {
144
+ var _HEROES_design_kind;
145
+ const next = random(design.seed ^ 0x2f6b9d1);
146
+ const family = aiLayoutFamily(design.kind);
147
+ const heroes = (_HEROES_design_kind = HEROES[design.kind]) != null ? _HEROES_design_kind : DEFAULT_HEROES;
148
+ const hero = next() < 0.55 ? heroes[0] : pick(next, heroes);
149
+ return {
150
+ hero,
151
+ group: family === 'calm' ? pick(next, [
152
+ 'cards',
153
+ 'cards',
154
+ 'ruled'
155
+ ]) : pick(next, [
156
+ 'ruled',
157
+ 'cards',
158
+ 'ruled'
159
+ ]),
160
+ steps: pick(next, [
161
+ 'timeline',
162
+ 'numbers',
163
+ 'timeline'
164
+ ]),
165
+ features: true,
166
+ featureLeft: next() < 0.5,
167
+ coverClose: heroes.includes('cover') && next() < 0.7,
168
+ splitHeads: family === 'editorial' || family === 'standard' ? next() < 0.6 : next() < 0.35,
169
+ pictures: design.kind === 'photography' ? pick(next, [
170
+ 'even',
171
+ 'mosaic'
172
+ ]) : pick(next, [
173
+ 'mosaic',
174
+ 'mosaic',
175
+ 'even'
176
+ ])
177
+ };
178
+ }
179
+ const WORK = /\b(work|works|project|projects|portfolio|gallery|galleries|case|cases|studies|selected|commission|commissions|illustration|illustrations|photo|photos|series|book|books|editorial|brand|collection|collections|wedding|weddings|portrait|portraits|session|sessions|shoot|shoots)\b/i;
180
+ const WRITING = /\b(article|articles|post|posts|writing|writings|latest|featured|journey|journeys|recipe|recipes|story|stories|essay|essays|guide|guides|issue|issues|episode|episodes)\b/i;
181
+ const DISHES = /\b(menu|menus|dish|dishes|breakfast|brunch|lunch|dinner|pastry|pastries|coffee|drink|drinks|cocktail|cocktails|wine|wines|highlight|highlights|favorite|favorites|favourites|special|specials|plate|plates|bake|bakes|bread|cake|cakes)\b/i;
182
+ const PRODUCTS = /\b(bestseller|bestsellers|best sellers|collection|collections|product|products|shop|range|candle|candles|gift|gifts|set|sets|new arrivals|arrivals)\b/i;
183
+ /**
184
+ * How a section's group of cards is drawn, from what the section is about:
185
+ * work in a gallery kind and products in a store are pictures, writing in a
186
+ * blog is articles, dishes in a restaurant a menu; anything else takes the
187
+ * site's own group style.
188
+ */ export function aiLayoutGroupVariant(design, choices, words) {
189
+ const family = aiLayoutFamily(design.kind);
190
+ if (family === 'gallery' && (WORK.test(words) || design.kind === 'photography')) return 'pictures';
191
+ if (family === 'editorial' && WRITING.test(words)) return 'articles';
192
+ if (family === 'hospitality' && DISHES.test(words)) return 'menu';
193
+ if (family === 'retail' && PRODUCTS.test(words)) return 'pictures';
194
+ return choices.group;
195
+ }
196
+
197
+ //# sourceMappingURL=ai-layout-design.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/layout-language/ai-layout-design.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * THE DESIGNER LAYER of the layout compiler (AGL-3660): how a page the model\n * described in the layout language is ARRANGED, decided by code from the\n * site's kind and its job's seed — never asked of the model, so a recorded\n * answer compiles into the new design with no new call.\n *\n * Why. A page compiled one way for every site read like a SaaS landing page:\n * a centered text hero, three outlined cards, a numbered row, a solid form\n * band. The designer templates people compare a site to (Squarespace's Loam,\n * Clove, Auburn, Rivoli, Matsuya; Wix's portfolio and restaurant sets) are\n * built from a handful of moves instead — a full-bleed photo hero with the\n * type over it, image and text side by side, work shown as pictures in an\n * uneven grid, writing as a list under thin rules, a closing band over a\n * photo — and they pick those moves for the KIND of site.\n *\n * So a site's design is a set of VARIANTS — the hero's arrangement, how its\n * groups are drawn, whether its text sections take a picture beside them,\n * how its closing section reads — each chosen among the ones that suit the\n * kind, by the seed. The choices are the site's: every page of one site is\n * drawn with the same hero and the same group style, so the site reads as\n * one design, and two sites of one kind come out differently.\n *\n * This is NOT a template library. A variant is a way of drawing whatever the\n * model wrote; the sections, their order, their bands, their words and how\n * many items each shows are still the model's, page by page.\n *\n * Pure: no tree, no theme, no network.\n */\n\n/** What the compiler is told about the site a page belongs to. */\nexport interface AiLayoutDesign {\n /** The site's kind (`AI_SITE_KINDS`); an unknown one draws as a business site. */\n kind: string\n /** The job's design seed: the same for every page of one site. */\n seed: number\n /** Whether this page is the site's home, which opens tallest. */\n home: boolean\n}\n\n/**\n * How the first section of a page is drawn:\n * - `cover`: a full-bleed photo with the words over it on a scrim;\n * - `split`: the words beside a tall photo, inside the page's measure;\n * - `editorial`: display type across the page, the lede and the buttons in\n * a row under it, and a wide photo below.\n */\nexport type AiLayoutHeroVariant = 'cover' | 'split' | 'editorial'\n\n/**\n * How a group of cards is drawn where no component draws it:\n * - `cards`: the theme's cards (the compiler's first design);\n * - `ruled`: open columns, each under a thin rule, no card around it;\n * - `pictures`: a picture over each item's title and caption, in an uneven\n * grid (work, products);\n * - `articles`: a picture over each title and summary, in even columns (writing);\n * - `menu`: two columns of lines under rules (dishes, prices left out).\n */\nexport type AiLayoutGroupVariant = 'cards' | 'ruled' | 'pictures' | 'articles' | 'menu'\n\n/** How a numbered group is drawn: a row of numbers, or a ruled list beside its heading. */\nexport type AiLayoutStepsVariant = 'numbers' | 'timeline'\n\n/** The site-level choices a page is drawn with. */\nexport interface AiLayoutDesignChoices {\n hero: AiLayoutHeroVariant\n /** The group style of a section that names no subject the kind draws its own way. */\n group: 'cards' | 'ruled'\n steps: AiLayoutStepsVariant\n /** Whether a section of words takes a photo beside it. */\n features: boolean\n /** Whether the photo of the first such section sits on the left. */\n featureLeft: boolean\n /** Whether a closing section of words is drawn over a photo. */\n coverClose: boolean\n /** Whether a section's heading sits beside its items rather than over them. */\n splitHeads: boolean\n /** How work is laid out where the kind shows it as pictures. */\n pictures: 'mosaic' | 'even'\n}\n\ntype Family = 'gallery' | 'editorial' | 'hospitality' | 'calm' | 'retail' | 'standard'\n\nconst FAMILIES: Readonly<Record<string, Family>> = {\n portfolio: 'gallery',\n photography: 'gallery',\n studio: 'gallery',\n blog: 'editorial',\n restaurant: 'hospitality',\n events: 'hospitality',\n yoga: 'calm',\n wellness: 'calm',\n beauty: 'calm',\n store: 'retail',\n}\n\n/** The heroes each kind opens with, best suited first. */\nconst HEROES: Readonly<Record<string, readonly AiLayoutHeroVariant[]>> = {\n photography: ['cover', 'editorial'],\n portfolio: ['editorial', 'split'],\n studio: ['editorial', 'cover'],\n blog: ['editorial', 'cover', 'split'],\n restaurant: ['cover', 'split'],\n events: ['cover', 'split'],\n fitness: ['cover', 'split'],\n music: ['cover', 'editorial'],\n nonprofit: ['cover', 'split'],\n realestate: ['cover', 'split'],\n trades: ['cover', 'split'],\n yoga: ['split', 'cover'],\n wellness: ['split', 'cover'],\n beauty: ['split', 'cover', 'editorial'],\n store: ['split', 'editorial'],\n}\nconst DEFAULT_HEROES: readonly AiLayoutHeroVariant[] = ['split', 'cover', 'editorial']\n\n/** A small deterministic generator over a seed (mulberry32). */\nfunction random(seed: number): () => number {\n let state = seed >>> 0\n return () => {\n state = (state + 0x6d2b79f5) >>> 0\n let t = state\n t = Math.imul(t ^ (t >>> 15), t | 1)\n t ^= t + Math.imul(t ^ (t >>> 7), t | 61)\n return ((t ^ (t >>> 14)) >>> 0) / 4294967296\n }\n}\n\nconst pick = <T>(next: () => number, list: readonly T[]): T => list[Math.floor(next() * list.length) % list.length] as T\n\n/** The family a kind's pages are drawn in. */\nexport function aiLayoutFamily(kind: string): Family {\n return FAMILIES[kind] ?? 'standard'\n}\n\n/**\n * A site's choices, the same for every page of it. The first option of each\n * list is the kind's best, so it is favored; the seed still turns it, so two\n * sites of one kind are drawn apart.\n */\nexport function aiLayoutDesignChoices(design: AiLayoutDesign): AiLayoutDesignChoices {\n const next = random(design.seed ^ 0x2f6b9d1)\n const family = aiLayoutFamily(design.kind)\n const heroes = HEROES[design.kind] ?? DEFAULT_HEROES\n const hero = next() < 0.55 ? (heroes[0] as AiLayoutHeroVariant) : pick(next, heroes)\n return {\n hero,\n group: family === 'calm' ? pick(next, ['cards', 'cards', 'ruled'] as const) : pick(next, ['ruled', 'cards', 'ruled'] as const),\n steps: pick(next, ['timeline', 'numbers', 'timeline'] as const),\n features: true,\n featureLeft: next() < 0.5,\n coverClose: heroes.includes('cover') && next() < 0.7,\n splitHeads: family === 'editorial' || family === 'standard' ? next() < 0.6 : next() < 0.35,\n pictures: design.kind === 'photography' ? pick(next, ['even', 'mosaic'] as const) : pick(next, ['mosaic', 'mosaic', 'even'] as const),\n }\n}\n\nconst WORK = /\\b(work|works|project|projects|portfolio|gallery|galleries|case|cases|studies|selected|commission|commissions|illustration|illustrations|photo|photos|series|book|books|editorial|brand|collection|collections|wedding|weddings|portrait|portraits|session|sessions|shoot|shoots)\\b/i\nconst WRITING = /\\b(article|articles|post|posts|writing|writings|latest|featured|journey|journeys|recipe|recipes|story|stories|essay|essays|guide|guides|issue|issues|episode|episodes)\\b/i\nconst DISHES = /\\b(menu|menus|dish|dishes|breakfast|brunch|lunch|dinner|pastry|pastries|coffee|drink|drinks|cocktail|cocktails|wine|wines|highlight|highlights|favorite|favorites|favourites|special|specials|plate|plates|bake|bakes|bread|cake|cakes)\\b/i\nconst PRODUCTS = /\\b(bestseller|bestsellers|best sellers|collection|collections|product|products|shop|range|candle|candles|gift|gifts|set|sets|new arrivals|arrivals)\\b/i\n\n/**\n * How a section's group of cards is drawn, from what the section is about:\n * work in a gallery kind and products in a store are pictures, writing in a\n * blog is articles, dishes in a restaurant a menu; anything else takes the\n * site's own group style.\n */\nexport function aiLayoutGroupVariant(\n design: AiLayoutDesign,\n choices: AiLayoutDesignChoices,\n words: string,\n): AiLayoutGroupVariant {\n const family = aiLayoutFamily(design.kind)\n if (family === 'gallery' && (WORK.test(words) || design.kind === 'photography')) return 'pictures'\n if (family === 'editorial' && WRITING.test(words)) return 'articles'\n if (family === 'hospitality' && DISHES.test(words)) return 'menu'\n if (family === 'retail' && PRODUCTS.test(words)) return 'pictures'\n return choices.group\n}\n"],"names":["FAMILIES","portfolio","photography","studio","blog","restaurant","events","yoga","wellness","beauty","store","HEROES","fitness","music","nonprofit","realestate","trades","DEFAULT_HEROES","random","seed","state","t","Math","imul","pick","next","list","floor","length","aiLayoutFamily","kind","aiLayoutDesignChoices","design","family","heroes","hero","group","steps","features","featureLeft","coverClose","includes","splitHeads","pictures","WORK","WRITING","DISHES","PRODUCTS","aiLayoutGroupVariant","choices","words","test"],"mappings":"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"}
@@ -38,6 +38,12 @@ export interface AiLayoutFramePlan {
38
38
  homeId: string | null;
39
39
  /** The pages the navigation links, in order. */
40
40
  navPages: readonly AiLayoutPage[];
41
+ /**
42
+ * Whether the site's pages close on a dark photo band (AGL-3660,
43
+ * `AiLayoutDesignChoices.coverClose`): a brand or dark footer under it
44
+ * would read as a second loud band, so the footer takes the quiet one.
45
+ */
46
+ closesDark?: boolean;
41
47
  }
42
48
  export interface AiLayoutCompiledFrame {
43
49
  tree: AiLayoutRawTree;
@@ -111,7 +111,12 @@ import { AiLayoutTreeBuilder } from "./ai-layout-tree.js";
111
111
  },
112
112
  formSection: null,
113
113
  pageIcon: AI_ICON_LIBRARY.sparkle,
114
- formsPlaced: new Set()
114
+ formsPlaced: new Set(),
115
+ // A frame is drawn the platform's one way on every site.
116
+ design: null,
117
+ pictures: 0,
118
+ features: 0,
119
+ splitAt: -2
115
120
  };
116
121
  const name = aiLayoutFitText(plan.siteName, 'heading') || 'Home';
117
122
  const header = (_frame_header = frame.header) != null ? _frame_header : {
@@ -121,12 +126,16 @@ import { AiLayoutTreeBuilder } from "./ai-layout-tree.js";
121
126
  const look = headerLook(band);
122
127
  const scope = frameScope(page, 'header', band === 'brand' || band === 'dark' ? 'brand' : 'plain', false);
123
128
  const navLink = (entry, role, interactions)=>{
124
- const id = tree.add('muiScreenLink', {
125
- children: aiLayoutFitText(entry.label, 'label') || entry.label.slice(0, 28),
126
- screenId: entry.id,
129
+ const id = tree.add('muiScreenLink', _extends({
130
+ children: aiLayoutFitText(entry.label, 'label') || entry.label.slice(0, 28)
131
+ }, entry.href ? {
132
+ href: entry.href
133
+ } : {
134
+ screenId: entry.id
135
+ }, {
127
136
  renderAs: 'link',
128
137
  color: 'inherit'
129
- }, {
138
+ }), {
130
139
  textDecoration: 'none'
131
140
  }, null, role);
132
141
  if (interactions) tree.nodes[id].interactions = interactions;
@@ -265,6 +274,8 @@ import { AiLayoutTreeBuilder } from "./ai-layout-tree.js";
265
274
  ], 'toolbar');
266
275
  const bar = tree.add('muiAppBar', _extends({}, look.props, {
267
276
  position: 'sticky',
277
+ // Over a page that opens with a photo cover, the bar sits on the photo (AGL-3660).
278
+ overHero: true,
268
279
  component: 'header',
269
280
  ariaLabel: 'Site header'
270
281
  }), look.sx, [
@@ -320,7 +331,12 @@ import { AiLayoutTreeBuilder } from "./ai-layout-tree.js";
320
331
  */ function compileFooter(page, footer, plan, name) {
321
332
  var _ref, _ref1;
322
333
  const tree = page.tree;
323
- const band = (_ref = footer == null ? void 0 : footer.band) != null ? _ref : 'soft';
334
+ const asked = (_ref = footer == null ? void 0 : footer.band) != null ? _ref : 'soft';
335
+ const band = plan.closesDark && (asked === 'brand' || asked === 'dark') ? 'soft' : asked;
336
+ if (band !== asked) page.settled.push({
337
+ at: 'footer',
338
+ what: `a ${asked} footer under a dark closing band drawn soft`
339
+ });
324
340
  const scope = frameScope(page, 'footer', band, (footer == null ? void 0 : footer.align) === 'center');
325
341
  // The footer's page links are the site's pages, every one, as the header's
326
342
  // are (AGL-3660): a list the model wrote of page links is drawn with them
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/layout-language/ai-layout-frame.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { AI_ICON_LIBRARY } from '../runtime/ai-icon-library'\nimport {\n AI_LAYOUT_MAX_IMAGES,\n bandSx,\n compileFlow,\n compileRow,\n destinationProps,\n type PageScope,\n type SectionScope,\n} from './ai-layout-compiler'\nimport { aiLayoutFitText } from './ai-layout-copy'\nimport {\n AI_LAYOUT_MAX_ITEMS,\n type AiLayoutBand,\n type AiLayoutBlock,\n type AiLayoutSection,\n type AiLayoutSettlement,\n} from './ai-layout-language'\nimport {\n aiLayoutResolveLink,\n type AiLayoutPage,\n type AiLayoutTargets,\n} from './ai-layout-links'\nimport { AiLayoutTreeBuilder, type AiLayoutRawTree } from './ai-layout-tree'\n\n/**\n * A site's frame — the header and the footer every page renders between —\n * compiled from the layout language (AGL-3660), the same language a page is\n * designed in.\n *\n * The design is the model's: the header's band and alignment and its one\n * call to action, and the footer's columns and every word in them. What a\n * frame owes the site is the platform's, written here, so it is never wrong\n * and never invented: the site's name as the brand, linking home; its pages\n * as the navigation, a row on a wide screen and the platform's Drawer behind\n * its Menu Button on a phone; the Layout Slot, the page's one main landmark,\n * held tall enough that the footer sits at the bottom of a short page; and\n * the site's pages and name in the footer, where the design gave neither.\n */\n\nexport interface AiLayoutFramePlan {\n /** The site's name, which the header and the footer carry as its brand. */\n siteName: string\n /** The site's home page, which the brand links; none where the site has none yet. */\n homeId: string | null\n /** The pages the navigation links, in order. */\n navPages: readonly AiLayoutPage[]\n}\n\nexport interface AiLayoutCompiledFrame {\n tree: AiLayoutRawTree\n settled: AiLayoutSettlement[]\n}\n\n/** The header's colors by band, as palette roles. */\nfunction headerLook(band: AiLayoutBand): {\n props: Record<string, unknown>\n sx: Record<string, unknown>\n} {\n if (band === 'brand')\n return { props: { color: 'primary' }, sx: { boxShadow: 'none' } }\n if (band === 'dark') {\n return {\n props: { color: 'primary' },\n sx: {\n boxShadow: 'none',\n bgcolor: 'primary.dark',\n color: 'primary.contrastText',\n },\n }\n }\n // A rule, a shadow or nothing under a plain header is the site theme's\n // MuiAppBar style (AGL-3660); the band's color stays here.\n return {\n props: { color: 'inherit' },\n sx: { bgcolor: 'background.paper' },\n }\n}\n\n/** A line that reads as a copyright notice: \"© 2026 …\", \"Copyright …\", \"All rights reserved.\" */\nexport function aiLayoutIsCopyright(text: unknown): boolean {\n return typeof text === 'string' && /^\\s*(?:©|\\(c\\)|copyright\\b)|all rights reserved/i.test(text)\n}\n\n/** What a footer leaves out: a picture slot, and the page sections a footer is no place for. */\nconst FOOTER_LEAVES_OUT: ReadonlySet<AiLayoutBlock['kind']> = new Set([\n 'image',\n 'faq',\n 'quotes',\n 'cards',\n 'steps',\n 'stats',\n 'form',\n 'component',\n])\n\n/** A frame scope: no headings of its own (a layout carries no outline), no pictures. */\nfunction frameScope(\n page: PageScope,\n at: string,\n band: AiLayoutBand,\n centered: boolean,\n): SectionScope {\n return {\n page,\n index: at === 'header' ? 0 : 1,\n at,\n band,\n centered,\n level: 2,\n headed: true,\n itemsLeft: AI_LAYOUT_MAX_ITEMS,\n frame: true,\n link: { sections: [], from: -1, formSection: null },\n }\n}\n\n/** Compiles a site's header and footer, designed in the layout language, into its layout. */\nexport function aiCompileLayoutFrame(\n frame: { header: AiLayoutSection | null; footer: AiLayoutSection | null },\n plan: AiLayoutFramePlan,\n targets: AiLayoutTargets,\n): AiLayoutCompiledFrame {\n const tree = new AiLayoutTreeBuilder('lf')\n const page: PageScope = {\n tree,\n plan: { title: plan.siteName, sections: [] },\n targets: { ...targets, pageId: null },\n // A footer is drawn compact: its lines are lines, never blocks rule 1 would ask to share.\n options: { reusableComponents: true },\n settled: [],\n scrollTo: {},\n // A frame's lines are its own, never a page section's items.\n itemIds: [],\n // A frame shows no picture slot: a header and a footer are the same on every page.\n images: AI_LAYOUT_MAX_IMAGES,\n strong: { brand: 0, dark: 0 },\n formSection: null,\n pageIcon: AI_ICON_LIBRARY.sparkle,\n formsPlaced: new Set(),\n }\n const name = aiLayoutFitText(plan.siteName, 'heading') || 'Home'\n const header = frame.header ?? { blocks: [] }\n const band = header.band ?? 'plain'\n const look = headerLook(band)\n const scope = frameScope(\n page,\n 'header',\n band === 'brand' || band === 'dark' ? 'brand' : 'plain',\n false,\n )\n const navLink = (\n entry: AiLayoutPage,\n role: string,\n interactions?: unknown[],\n ) => {\n const id = tree.add(\n 'muiScreenLink',\n {\n children:\n aiLayoutFitText(entry.label, 'label') || entry.label.slice(0, 28),\n screenId: entry.id,\n renderAs: 'link',\n color: 'inherit',\n },\n { textDecoration: 'none' },\n null,\n role,\n )\n if (interactions) tree.nodes[id].interactions = interactions\n return id\n }\n const pages = plan.navPages.filter((entry) => entry.label.trim())\n const centeredNav = header.align === 'center' && pages.length > 0\n // The brand takes the room the navigation does not, and gives way before\n // it does: a long name ends in an ellipsis rather than pushing into the links.\n const brandSx = {\n typography: 'h6',\n fontWeight: 800,\n textDecoration: 'none',\n whiteSpace: 'nowrap',\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n minWidth: 0,\n ...(centeredNav ? {} : { flexGrow: 1 }),\n }\n const brand = plan.homeId\n ? tree.add(\n 'muiScreenLink',\n {\n children: name.slice(0, 40),\n screenId: plan.homeId,\n renderAs: 'link',\n color: 'inherit',\n },\n brandSx,\n null,\n 'brand',\n )\n : tree.add(\n 'muiTypography',\n { children: name, variant: 'h6', component: 'p' },\n brandSx,\n null,\n 'brand',\n )\n const nav = pages.length\n ? tree.add(\n 'muiStack',\n {\n component: 'nav',\n ariaLabel: 'Main',\n direction: 'row',\n spacing: '3',\n alignItems: 'center',\n },\n { display: { xs: 'none', md: 'flex' } },\n pages.map((entry) => navLink(entry, 'navLink')),\n 'nav',\n )\n : null\n // The design's call to action: its first button, where it goes somewhere.\n const ctaBlock = header.blocks.find((block) => block.kind === 'button')\n for (const block of header.blocks) {\n if (block !== ctaBlock)\n page.settled.push({\n at: 'header',\n what: `a ${block.kind} in the header; left out`,\n })\n }\n const ctaLabel = ctaBlock ? aiLayoutFitText(ctaBlock.text, 'label') : ''\n const ctaTo =\n ctaBlock && ctaLabel\n ? aiLayoutResolveLink(ctaBlock.to, ctaLabel, scope.link, page.targets)\n : null\n if (ctaBlock && !ctaTo)\n page.settled.push({\n at: 'header',\n what: `the button \"${ctaLabel}\" has nowhere to go; left out`,\n })\n const ctaProps = ctaTo\n ? {\n children: ctaLabel,\n variant: 'contained',\n color: scope.band === 'brand' ? 'secondary' : 'primary',\n ...destinationProps(ctaTo),\n }\n : null\n // On the smallest phones the call to action moves into the menu.\n const cta = ctaProps\n ? tree.add(\n 'muiButton',\n ctaProps,\n {\n display: { xs: 'none', sm: 'inline-flex' },\n whiteSpace: 'nowrap',\n flexShrink: 0,\n },\n null,\n 'cta',\n )\n : null\n // A phone's menu: the platform's Drawer, opened by its Menu Button; a link\n // followed from it closes it.\n const close = [AI_LAYOUT_CLOSE_DRAWER]\n const drawer = pages.length\n ? tree.add(\n 'muiDrawer',\n { anchor: 'right' },\n null,\n [\n tree.add(\n 'muiStack',\n { component: 'nav', ariaLabel: 'Menu' },\n {\n display: 'flex',\n flexDirection: 'column',\n gap: 2,\n p: 3,\n typography: 'h6',\n },\n [\n ...pages.map((entry) => navLink(entry, 'menuLink', close)),\n ctaProps\n ? tree.add(\n 'muiButton',\n { ...ctaProps, fullWidth: true },\n { mt: 2 },\n null,\n 'menuCta',\n )\n : null,\n ],\n 'menu',\n ),\n ],\n 'drawer',\n )\n : null\n const toggle = drawer\n ? tree.add(\n 'muiDrawerToggle',\n { ariaLabel: 'Open the menu' },\n { display: { xs: 'inline-flex', md: 'none' }, flexShrink: 0 },\n null,\n 'menuButton',\n )\n : null\n const middle =\n nav && centeredNav\n ? tree.add(\n 'muiBox',\n null,\n { flexGrow: 1, display: 'flex', justifyContent: 'center' },\n [nav],\n 'navRoom',\n )\n : nav\n // The row lives in a Container inside the Toolbar Content, which may only sit in an App Bar.\n const row = tree.add(\n 'muiContainer',\n { maxWidth: 'lg' },\n { display: 'flex', alignItems: 'center', columnGap: 3 },\n [brand, middle, cta, toggle, drawer],\n 'headerRow',\n )\n const toolbar = tree.add(\n 'muiToolbar',\n { disableGutters: true },\n { minHeight: { xs: 64, md: 76 } },\n [row],\n 'toolbar',\n )\n const bar = tree.add(\n 'muiAppBar',\n {\n ...look.props,\n position: 'sticky',\n component: 'header',\n ariaLabel: 'Site header',\n },\n look.sx,\n [toolbar],\n 'header',\n )\n // The page grows into the room the window leaves, so a short page keeps\n // its footer at the bottom.\n const slot = tree.add(\n 'layoutSlot',\n { component: 'main' },\n { flexGrow: 1 },\n null,\n 'slot',\n )\n const footer = compileFooter(page, frame.footer, plan, name)\n const column = tree.add(\n 'muiStack',\n null,\n { minHeight: '100vh' },\n [bar, slot, footer],\n 'frame',\n )\n tree.prune(column, new Set([slot]))\n const rootId = tree.add('div', null, null, [column], 'layout')\n return { tree: { rootId, nodes: tree.nodes }, settled: page.settled }\n}\n\n/** A link followed from the phone menu closes it. */\nconst AI_LAYOUT_CLOSE_DRAWER = {\n id: 'ai-close-menu',\n name: 'Close the menu',\n enabled: true,\n trigger: { event: 'elementClick', everyTime: true },\n steps: [{ type: 'closeDrawer' }],\n}\n\n/**\n * The footer the design describes, with what every footer owes a visitor\n * written by code: the site's pages, where the design links none of them,\n * and the site's name under a rule.\n */\nfunction compileFooter(\n page: PageScope,\n footer: AiLayoutSection | null,\n plan: AiLayoutFramePlan,\n name: string,\n): string {\n const tree = page.tree\n const band = footer?.band ?? 'soft'\n const scope = frameScope(page, 'footer', band, footer?.align === 'center')\n // The footer's page links are the site's pages, every one, as the header's\n // are (AGL-3660): a list the model wrote of page links is drawn with them\n // in its place, and a footer with none gets one. The model places the list;\n // it never chooses which pages it holds.\n const navItems = plan.navPages.map((entry) => ({\n title: entry.label,\n text: '',\n to: `page:${entry.id}`,\n }))\n let linksPages = false\n const blocks = (footer?.blocks ?? []).flatMap((given): AiLayoutBlock[] => {\n // The bottom bar carries the frame's one copyright line; a line of the\n // answer's own that reads as one would print it twice.\n if (aiLayoutIsCopyright(given.text)) {\n page.settled.push({ at: 'footer', what: 'a copyright line; the footer writes its own' })\n return []\n }\n const notice = (item: { title: string; text: string }) => aiLayoutIsCopyright(item.title) || aiLayoutIsCopyright(item.text)\n const block = given.items?.some(notice) ? { ...given, items: given.items.filter((item) => !notice(item)) } : given\n if (block !== given) page.settled.push({ at: 'footer', what: 'a copyright line in a list; the footer writes its own' })\n if (FOOTER_LEAVES_OUT.has(block.kind)) {\n page.settled.push({\n at: 'footer',\n what: `a ${block.kind} in the footer; left out`,\n })\n return []\n }\n const pageLinks =\n block.kind === 'list' &&\n (block.items ?? []).some((item) => /^page:/.test(item.to ?? ''))\n if (!pageLinks) return [block]\n if (linksPages || !navItems.length) {\n page.settled.push({ at: 'footer', what: 'a second list of page links; left out' })\n return []\n }\n linksPages = true\n return [{ ...block, items: navItems }]\n })\n const cols = footer?.cols && footer.cols.length >= 2 ? footer.cols : null\n const columns: AiLayoutBlock[][] = cols ? cols.map(() => []) : [[]]\n for (const block of blocks)\n columns[\n cols && block.col !== undefined ? Math.min(block.col, cols.length - 1) : 0\n ].push(block)\n const weights = cols ? [...cols] : [2]\n if (!linksPages && plan.navPages.length) {\n columns.push([\n {\n kind: 'list',\n items: plan.navPages.map((entry) => ({\n title: entry.label,\n text: '',\n to: `page:${entry.id}`,\n })),\n },\n ])\n weights.push(1)\n }\n const filled = columns\n .map((column, position) => ({ column, weight: weights[position] ?? 1 }))\n .filter((entry) => entry.column.length)\n const top =\n filled.length > 1\n ? compileRow(scope, filled)\n : filled.length === 1\n ? compileFlow(scope, filled[0].column, 'full')\n : null\n const copyright = tree.add(\n 'muiTypography',\n { children: `© ${name}`, variant: 'body2', component: 'p' },\n scope.band === 'brand' ? null : { color: 'text.secondary' },\n null,\n 'copyright',\n )\n const bottom = tree.add(\n 'muiBox',\n null,\n { pt: 3, borderTop: 1, borderColor: 'divider' },\n [copyright],\n 'footerBottom',\n )\n const stack = tree.add(\n 'muiStack',\n { spacing: '6' },\n null,\n [top, bottom],\n 'footerStack',\n )\n const container = tree.add(\n 'muiContainer',\n { maxWidth: 'lg' },\n { py: { xs: 6, md: 8 } },\n [stack],\n 'footerInner',\n )\n return tree.add(\n 'section',\n {\n element: 'footer',\n ariaLabel: 'Site footer',\n ...(scope.band === 'dark' ? { colorScheme: 'dark' } : {}),\n },\n scope.band === 'soft'\n ? { bgcolor: 'background.paper', borderTop: 1, borderColor: 'divider' }\n : bandSx(scope.band),\n [container],\n 'footer',\n )\n}\n"],"names":["AI_ICON_LIBRARY","AI_LAYOUT_MAX_IMAGES","bandSx","compileFlow","compileRow","destinationProps","aiLayoutFitText","AI_LAYOUT_MAX_ITEMS","aiLayoutResolveLink","AiLayoutTreeBuilder","headerLook","band","props","color","sx","boxShadow","bgcolor","aiLayoutIsCopyright","text","test","FOOTER_LEAVES_OUT","Set","frameScope","page","at","centered","index","level","headed","itemsLeft","frame","link","sections","from","formSection","aiCompileLayoutFrame","plan","targets","header","tree","title","siteName","pageId","options","reusableComponents","settled","scrollTo","itemIds","images","strong","brand","dark","pageIcon","sparkle","formsPlaced","name","blocks","look","scope","navLink","entry","role","interactions","id","add","children","label","slice","screenId","renderAs","textDecoration","nodes","pages","navPages","filter","trim","centeredNav","align","length","brandSx","typography","fontWeight","whiteSpace","overflow","textOverflow","minWidth","flexGrow","homeId","variant","component","nav","ariaLabel","direction","spacing","alignItems","display","xs","md","map","ctaBlock","find","block","kind","push","what","ctaLabel","ctaTo","to","ctaProps","cta","sm","flexShrink","close","AI_LAYOUT_CLOSE_DRAWER","drawer","anchor","flexDirection","gap","p","fullWidth","mt","toggle","middle","justifyContent","row","maxWidth","columnGap","toolbar","disableGutters","minHeight","bar","position","slot","footer","compileFooter","column","prune","rootId","enabled","trigger","event","everyTime","steps","type","navItems","linksPages","flatMap","given","notice","item","items","some","has","pageLinks","cols","columns","col","undefined","Math","min","weights","filled","weight","top","copyright","bottom","pt","borderTop","borderColor","stack","container","py","element","colorScheme"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/layout-language/ai-layout-frame.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { AI_ICON_LIBRARY } from '../runtime/ai-icon-library'\nimport {\n AI_LAYOUT_MAX_IMAGES,\n bandSx,\n compileFlow,\n compileRow,\n destinationProps,\n type PageScope,\n type SectionScope,\n} from './ai-layout-compiler'\nimport { aiLayoutFitText } from './ai-layout-copy'\nimport {\n AI_LAYOUT_MAX_ITEMS,\n type AiLayoutBand,\n type AiLayoutBlock,\n type AiLayoutSection,\n type AiLayoutSettlement,\n} from './ai-layout-language'\nimport {\n aiLayoutResolveLink,\n type AiLayoutPage,\n type AiLayoutTargets,\n} from './ai-layout-links'\nimport { AiLayoutTreeBuilder, type AiLayoutRawTree } from './ai-layout-tree'\n\n/**\n * A site's frame — the header and the footer every page renders between —\n * compiled from the layout language (AGL-3660), the same language a page is\n * designed in.\n *\n * The design is the model's: the header's band and alignment and its one\n * call to action, and the footer's columns and every word in them. What a\n * frame owes the site is the platform's, written here, so it is never wrong\n * and never invented: the site's name as the brand, linking home; its pages\n * as the navigation, a row on a wide screen and the platform's Drawer behind\n * its Menu Button on a phone; the Layout Slot, the page's one main landmark,\n * held tall enough that the footer sits at the bottom of a short page; and\n * the site's pages and name in the footer, where the design gave neither.\n */\n\nexport interface AiLayoutFramePlan {\n /** The site's name, which the header and the footer carry as its brand. */\n siteName: string\n /** The site's home page, which the brand links; none where the site has none yet. */\n homeId: string | null\n /** The pages the navigation links, in order. */\n navPages: readonly AiLayoutPage[]\n /**\n * Whether the site's pages close on a dark photo band (AGL-3660,\n * `AiLayoutDesignChoices.coverClose`): a brand or dark footer under it\n * would read as a second loud band, so the footer takes the quiet one.\n */\n closesDark?: boolean\n}\n\nexport interface AiLayoutCompiledFrame {\n tree: AiLayoutRawTree\n settled: AiLayoutSettlement[]\n}\n\n/** The header's colors by band, as palette roles. */\nfunction headerLook(band: AiLayoutBand): {\n props: Record<string, unknown>\n sx: Record<string, unknown>\n} {\n if (band === 'brand')\n return { props: { color: 'primary' }, sx: { boxShadow: 'none' } }\n if (band === 'dark') {\n return {\n props: { color: 'primary' },\n sx: {\n boxShadow: 'none',\n bgcolor: 'primary.dark',\n color: 'primary.contrastText',\n },\n }\n }\n // A rule, a shadow or nothing under a plain header is the site theme's\n // MuiAppBar style (AGL-3660); the band's color stays here.\n return {\n props: { color: 'inherit' },\n sx: { bgcolor: 'background.paper' },\n }\n}\n\n/** A line that reads as a copyright notice: \"© 2026 …\", \"Copyright …\", \"All rights reserved.\" */\nexport function aiLayoutIsCopyright(text: unknown): boolean {\n return typeof text === 'string' && /^\\s*(?:©|\\(c\\)|copyright\\b)|all rights reserved/i.test(text)\n}\n\n/** What a footer leaves out: a picture slot, and the page sections a footer is no place for. */\nconst FOOTER_LEAVES_OUT: ReadonlySet<AiLayoutBlock['kind']> = new Set([\n 'image',\n 'faq',\n 'quotes',\n 'cards',\n 'steps',\n 'stats',\n 'form',\n 'component',\n])\n\n/** A frame scope: no headings of its own (a layout carries no outline), no pictures. */\nfunction frameScope(\n page: PageScope,\n at: string,\n band: AiLayoutBand,\n centered: boolean,\n): SectionScope {\n return {\n page,\n index: at === 'header' ? 0 : 1,\n at,\n band,\n centered,\n level: 2,\n headed: true,\n itemsLeft: AI_LAYOUT_MAX_ITEMS,\n frame: true,\n link: { sections: [], from: -1, formSection: null },\n }\n}\n\n/** Compiles a site's header and footer, designed in the layout language, into its layout. */\nexport function aiCompileLayoutFrame(\n frame: { header: AiLayoutSection | null; footer: AiLayoutSection | null },\n plan: AiLayoutFramePlan,\n targets: AiLayoutTargets,\n): AiLayoutCompiledFrame {\n const tree = new AiLayoutTreeBuilder('lf')\n const page: PageScope = {\n tree,\n plan: { title: plan.siteName, sections: [] },\n targets: { ...targets, pageId: null },\n // A footer is drawn compact: its lines are lines, never blocks rule 1 would ask to share.\n options: { reusableComponents: true },\n settled: [],\n scrollTo: {},\n // A frame's lines are its own, never a page section's items.\n itemIds: [],\n // A frame shows no picture slot: a header and a footer are the same on every page.\n images: AI_LAYOUT_MAX_IMAGES,\n strong: { brand: 0, dark: 0 },\n formSection: null,\n pageIcon: AI_ICON_LIBRARY.sparkle,\n formsPlaced: new Set(),\n // A frame is drawn the platform's one way on every site.\n design: null,\n pictures: 0,\n features: 0,\n splitAt: -2,\n }\n const name = aiLayoutFitText(plan.siteName, 'heading') || 'Home'\n const header = frame.header ?? { blocks: [] }\n const band = header.band ?? 'plain'\n const look = headerLook(band)\n const scope = frameScope(\n page,\n 'header',\n band === 'brand' || band === 'dark' ? 'brand' : 'plain',\n false,\n )\n const navLink = (\n entry: AiLayoutPage,\n role: string,\n interactions?: unknown[],\n ) => {\n const id = tree.add(\n 'muiScreenLink',\n {\n children:\n aiLayoutFitText(entry.label, 'label') || entry.label.slice(0, 28),\n // The blog is a path, not a page (AGL-3660).\n ...(entry.href ? { href: entry.href } : { screenId: entry.id }),\n renderAs: 'link',\n color: 'inherit',\n },\n { textDecoration: 'none' },\n null,\n role,\n )\n if (interactions) tree.nodes[id].interactions = interactions\n return id\n }\n const pages = plan.navPages.filter((entry) => entry.label.trim())\n const centeredNav = header.align === 'center' && pages.length > 0\n // The brand takes the room the navigation does not, and gives way before\n // it does: a long name ends in an ellipsis rather than pushing into the links.\n const brandSx = {\n typography: 'h6',\n fontWeight: 800,\n textDecoration: 'none',\n whiteSpace: 'nowrap',\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n minWidth: 0,\n ...(centeredNav ? {} : { flexGrow: 1 }),\n }\n const brand = plan.homeId\n ? tree.add(\n 'muiScreenLink',\n {\n children: name.slice(0, 40),\n screenId: plan.homeId,\n renderAs: 'link',\n color: 'inherit',\n },\n brandSx,\n null,\n 'brand',\n )\n : tree.add(\n 'muiTypography',\n { children: name, variant: 'h6', component: 'p' },\n brandSx,\n null,\n 'brand',\n )\n const nav = pages.length\n ? tree.add(\n 'muiStack',\n {\n component: 'nav',\n ariaLabel: 'Main',\n direction: 'row',\n spacing: '3',\n alignItems: 'center',\n },\n { display: { xs: 'none', md: 'flex' } },\n pages.map((entry) => navLink(entry, 'navLink')),\n 'nav',\n )\n : null\n // The design's call to action: its first button, where it goes somewhere.\n const ctaBlock = header.blocks.find((block) => block.kind === 'button')\n for (const block of header.blocks) {\n if (block !== ctaBlock)\n page.settled.push({\n at: 'header',\n what: `a ${block.kind} in the header; left out`,\n })\n }\n const ctaLabel = ctaBlock ? aiLayoutFitText(ctaBlock.text, 'label') : ''\n const ctaTo =\n ctaBlock && ctaLabel\n ? aiLayoutResolveLink(ctaBlock.to, ctaLabel, scope.link, page.targets)\n : null\n if (ctaBlock && !ctaTo)\n page.settled.push({\n at: 'header',\n what: `the button \"${ctaLabel}\" has nowhere to go; left out`,\n })\n const ctaProps = ctaTo\n ? {\n children: ctaLabel,\n variant: 'contained',\n color: scope.band === 'brand' ? 'secondary' : 'primary',\n ...destinationProps(ctaTo),\n }\n : null\n // On the smallest phones the call to action moves into the menu.\n const cta = ctaProps\n ? tree.add(\n 'muiButton',\n ctaProps,\n {\n display: { xs: 'none', sm: 'inline-flex' },\n whiteSpace: 'nowrap',\n flexShrink: 0,\n },\n null,\n 'cta',\n )\n : null\n // A phone's menu: the platform's Drawer, opened by its Menu Button; a link\n // followed from it closes it.\n const close = [AI_LAYOUT_CLOSE_DRAWER]\n const drawer = pages.length\n ? tree.add(\n 'muiDrawer',\n { anchor: 'right' },\n null,\n [\n tree.add(\n 'muiStack',\n { component: 'nav', ariaLabel: 'Menu' },\n {\n display: 'flex',\n flexDirection: 'column',\n gap: 2,\n p: 3,\n typography: 'h6',\n },\n [\n ...pages.map((entry) => navLink(entry, 'menuLink', close)),\n ctaProps\n ? tree.add(\n 'muiButton',\n { ...ctaProps, fullWidth: true },\n { mt: 2 },\n null,\n 'menuCta',\n )\n : null,\n ],\n 'menu',\n ),\n ],\n 'drawer',\n )\n : null\n const toggle = drawer\n ? tree.add(\n 'muiDrawerToggle',\n { ariaLabel: 'Open the menu' },\n { display: { xs: 'inline-flex', md: 'none' }, flexShrink: 0 },\n null,\n 'menuButton',\n )\n : null\n const middle =\n nav && centeredNav\n ? tree.add(\n 'muiBox',\n null,\n { flexGrow: 1, display: 'flex', justifyContent: 'center' },\n [nav],\n 'navRoom',\n )\n : nav\n // The row lives in a Container inside the Toolbar Content, which may only sit in an App Bar.\n const row = tree.add(\n 'muiContainer',\n { maxWidth: 'lg' },\n { display: 'flex', alignItems: 'center', columnGap: 3 },\n [brand, middle, cta, toggle, drawer],\n 'headerRow',\n )\n const toolbar = tree.add(\n 'muiToolbar',\n { disableGutters: true },\n { minHeight: { xs: 64, md: 76 } },\n [row],\n 'toolbar',\n )\n const bar = tree.add(\n 'muiAppBar',\n {\n ...look.props,\n position: 'sticky',\n // Over a page that opens with a photo cover, the bar sits on the photo (AGL-3660).\n overHero: true,\n component: 'header',\n ariaLabel: 'Site header',\n },\n look.sx,\n [toolbar],\n 'header',\n )\n // The page grows into the room the window leaves, so a short page keeps\n // its footer at the bottom.\n const slot = tree.add(\n 'layoutSlot',\n { component: 'main' },\n { flexGrow: 1 },\n null,\n 'slot',\n )\n const footer = compileFooter(page, frame.footer, plan, name)\n const column = tree.add(\n 'muiStack',\n null,\n { minHeight: '100vh' },\n [bar, slot, footer],\n 'frame',\n )\n tree.prune(column, new Set([slot]))\n const rootId = tree.add('div', null, null, [column], 'layout')\n return { tree: { rootId, nodes: tree.nodes }, settled: page.settled }\n}\n\n/** A link followed from the phone menu closes it. */\nconst AI_LAYOUT_CLOSE_DRAWER = {\n id: 'ai-close-menu',\n name: 'Close the menu',\n enabled: true,\n trigger: { event: 'elementClick', everyTime: true },\n steps: [{ type: 'closeDrawer' }],\n}\n\n/**\n * The footer the design describes, with what every footer owes a visitor\n * written by code: the site's pages, where the design links none of them,\n * and the site's name under a rule.\n */\nfunction compileFooter(\n page: PageScope,\n footer: AiLayoutSection | null,\n plan: AiLayoutFramePlan,\n name: string,\n): string {\n const tree = page.tree\n const asked = footer?.band ?? 'soft'\n const band = plan.closesDark && (asked === 'brand' || asked === 'dark') ? 'soft' : asked\n if (band !== asked) page.settled.push({ at: 'footer', what: `a ${asked} footer under a dark closing band drawn soft` })\n const scope = frameScope(page, 'footer', band, footer?.align === 'center')\n // The footer's page links are the site's pages, every one, as the header's\n // are (AGL-3660): a list the model wrote of page links is drawn with them\n // in its place, and a footer with none gets one. The model places the list;\n // it never chooses which pages it holds.\n const navItems = plan.navPages.map((entry) => ({\n title: entry.label,\n text: '',\n to: `page:${entry.id}`,\n }))\n let linksPages = false\n const blocks = (footer?.blocks ?? []).flatMap((given): AiLayoutBlock[] => {\n // The bottom bar carries the frame's one copyright line; a line of the\n // answer's own that reads as one would print it twice.\n if (aiLayoutIsCopyright(given.text)) {\n page.settled.push({ at: 'footer', what: 'a copyright line; the footer writes its own' })\n return []\n }\n const notice = (item: { title: string; text: string }) => aiLayoutIsCopyright(item.title) || aiLayoutIsCopyright(item.text)\n const block = given.items?.some(notice) ? { ...given, items: given.items.filter((item) => !notice(item)) } : given\n if (block !== given) page.settled.push({ at: 'footer', what: 'a copyright line in a list; the footer writes its own' })\n if (FOOTER_LEAVES_OUT.has(block.kind)) {\n page.settled.push({\n at: 'footer',\n what: `a ${block.kind} in the footer; left out`,\n })\n return []\n }\n const pageLinks =\n block.kind === 'list' &&\n (block.items ?? []).some((item) => /^page:/.test(item.to ?? ''))\n if (!pageLinks) return [block]\n if (linksPages || !navItems.length) {\n page.settled.push({ at: 'footer', what: 'a second list of page links; left out' })\n return []\n }\n linksPages = true\n return [{ ...block, items: navItems }]\n })\n const cols = footer?.cols && footer.cols.length >= 2 ? footer.cols : null\n const columns: AiLayoutBlock[][] = cols ? cols.map(() => []) : [[]]\n for (const block of blocks)\n columns[\n cols && block.col !== undefined ? Math.min(block.col, cols.length - 1) : 0\n ].push(block)\n const weights = cols ? [...cols] : [2]\n if (!linksPages && plan.navPages.length) {\n columns.push([\n {\n kind: 'list',\n items: plan.navPages.map((entry) => ({\n title: entry.label,\n text: '',\n to: `page:${entry.id}`,\n })),\n },\n ])\n weights.push(1)\n }\n const filled = columns\n .map((column, position) => ({ column, weight: weights[position] ?? 1 }))\n .filter((entry) => entry.column.length)\n const top =\n filled.length > 1\n ? compileRow(scope, filled)\n : filled.length === 1\n ? compileFlow(scope, filled[0].column, 'full')\n : null\n const copyright = tree.add(\n 'muiTypography',\n { children: `© ${name}`, variant: 'body2', component: 'p' },\n scope.band === 'brand' ? null : { color: 'text.secondary' },\n null,\n 'copyright',\n )\n const bottom = tree.add(\n 'muiBox',\n null,\n { pt: 3, borderTop: 1, borderColor: 'divider' },\n [copyright],\n 'footerBottom',\n )\n const stack = tree.add(\n 'muiStack',\n { spacing: '6' },\n null,\n [top, bottom],\n 'footerStack',\n )\n const container = tree.add(\n 'muiContainer',\n { maxWidth: 'lg' },\n { py: { xs: 6, md: 8 } },\n [stack],\n 'footerInner',\n )\n return tree.add(\n 'section',\n {\n element: 'footer',\n ariaLabel: 'Site footer',\n ...(scope.band === 'dark' ? { colorScheme: 'dark' } : {}),\n },\n scope.band === 'soft'\n ? { bgcolor: 'background.paper', borderTop: 1, borderColor: 'divider' }\n : bandSx(scope.band),\n [container],\n 'footer',\n )\n}\n"],"names":["AI_ICON_LIBRARY","AI_LAYOUT_MAX_IMAGES","bandSx","compileFlow","compileRow","destinationProps","aiLayoutFitText","AI_LAYOUT_MAX_ITEMS","aiLayoutResolveLink","AiLayoutTreeBuilder","headerLook","band","props","color","sx","boxShadow","bgcolor","aiLayoutIsCopyright","text","test","FOOTER_LEAVES_OUT","Set","frameScope","page","at","centered","index","level","headed","itemsLeft","frame","link","sections","from","formSection","aiCompileLayoutFrame","plan","targets","header","tree","title","siteName","pageId","options","reusableComponents","settled","scrollTo","itemIds","images","strong","brand","dark","pageIcon","sparkle","formsPlaced","design","pictures","features","splitAt","name","blocks","look","scope","navLink","entry","role","interactions","id","add","children","label","slice","href","screenId","renderAs","textDecoration","nodes","pages","navPages","filter","trim","centeredNav","align","length","brandSx","typography","fontWeight","whiteSpace","overflow","textOverflow","minWidth","flexGrow","homeId","variant","component","nav","ariaLabel","direction","spacing","alignItems","display","xs","md","map","ctaBlock","find","block","kind","push","what","ctaLabel","ctaTo","to","ctaProps","cta","sm","flexShrink","close","AI_LAYOUT_CLOSE_DRAWER","drawer","anchor","flexDirection","gap","p","fullWidth","mt","toggle","middle","justifyContent","row","maxWidth","columnGap","toolbar","disableGutters","minHeight","bar","position","overHero","slot","footer","compileFooter","column","prune","rootId","enabled","trigger","event","everyTime","steps","type","asked","closesDark","navItems","linksPages","flatMap","given","notice","item","items","some","has","pageLinks","cols","columns","col","undefined","Math","min","weights","filled","weight","top","copyright","bottom","pt","borderTop","borderColor","stack","container","py","element","colorScheme"],"mappings":";AAAA;;;;;;;;;;;;;;;CAeC,GAED,SAASA,eAAe,QAAQ,gCAA4B;AAC5D,SACEC,oBAAoB,EACpBC,MAAM,EACNC,WAAW,EACXC,UAAU,EACVC,gBAAgB,QAGX,0BAAsB;AAC7B,SAASC,eAAe,QAAQ,sBAAkB;AAClD,SACEC,mBAAmB,QAKd,0BAAsB;AAC7B,SACEC,mBAAmB,QAGd,uBAAmB;AAC1B,SAASC,mBAAmB,QAA8B,sBAAkB;AAqC5E,mDAAmD,GACnD,SAASC,WAAWC,IAAkB;IAIpC,IAAIA,SAAS,SACX,OAAO;QAAEC,OAAO;YAAEC,OAAO;QAAU;QAAGC,IAAI;YAAEC,WAAW;QAAO;IAAE;IAClE,IAAIJ,SAAS,QAAQ;QACnB,OAAO;YACLC,OAAO;gBAAEC,OAAO;YAAU;YAC1BC,IAAI;gBACFC,WAAW;gBACXC,SAAS;gBACTH,OAAO;YACT;QACF;IACF;IACA,uEAAuE;IACvE,2DAA2D;IAC3D,OAAO;QACLD,OAAO;YAAEC,OAAO;QAAU;QAC1BC,IAAI;YAAEE,SAAS;QAAmB;IACpC;AACF;AAEA,+FAA+F,GAC/F,OAAO,SAASC,oBAAoBC,IAAa;IAC/C,OAAO,OAAOA,SAAS,YAAY,mDAAmDC,IAAI,CAACD;AAC7F;AAEA,8FAA8F,GAC9F,MAAME,oBAAwD,IAAIC,IAAI;IACpE;IACA;IACA;IACA;IACA;IACA;IACA;IACA;CACD;AAED,sFAAsF,GACtF,SAASC,WACPC,IAAe,EACfC,EAAU,EACVb,IAAkB,EAClBc,QAAiB;IAEjB,OAAO;QACLF;QACAG,OAAOF,OAAO,WAAW,IAAI;QAC7BA;QACAb;QACAc;QACAE,OAAO;QACPC,QAAQ;QACRC,WAAWtB;QACXuB,OAAO;QACPC,MAAM;YAAEC,UAAU,EAAE;YAAEC,MAAM,CAAC;YAAGC,aAAa;QAAK;IACpD;AACF;AAEA,2FAA2F,GAC3F,OAAO,SAASC,qBACdL,KAAyE,EACzEM,IAAuB,EACvBC,OAAwB;QA0BTP,eACFQ;IAzBb,MAAMC,OAAO,IAAI9B,oBAAoB;IACrC,MAAMc,OAAkB;QACtBgB;QACAH,MAAM;YAAEI,OAAOJ,KAAKK,QAAQ;YAAET,UAAU,EAAE;QAAC;QAC3CK,SAAS,aAAKA;YAASK,QAAQ;;QAC/B,0FAA0F;QAC1FC,SAAS;YAAEC,oBAAoB;QAAK;QACpCC,SAAS,EAAE;QACXC,UAAU,CAAC;QACX,6DAA6D;QAC7DC,SAAS,EAAE;QACX,mFAAmF;QACnFC,QAAQ/C;QACRgD,QAAQ;YAAEC,OAAO;YAAGC,MAAM;QAAE;QAC5BjB,aAAa;QACbkB,UAAUpD,gBAAgBqD,OAAO;QACjCC,aAAa,IAAIjC;QACjB,yDAAyD;QACzDkC,QAAQ;QACRC,UAAU;QACVC,UAAU;QACVC,SAAS,CAAC;IACZ;IACA,MAAMC,OAAOrD,gBAAgB8B,KAAKK,QAAQ,EAAE,cAAc;IAC1D,MAAMH,UAASR,gBAAAA,MAAMQ,MAAM,YAAZR,gBAAgB;QAAE8B,QAAQ,EAAE;IAAC;IAC5C,MAAMjD,QAAO2B,eAAAA,OAAO3B,IAAI,YAAX2B,eAAe;IAC5B,MAAMuB,OAAOnD,WAAWC;IACxB,MAAMmD,QAAQxC,WACZC,MACA,UACAZ,SAAS,WAAWA,SAAS,SAAS,UAAU,SAChD;IAEF,MAAMoD,UAAU,CACdC,OACAC,MACAC;QAEA,MAAMC,KAAK5B,KAAK6B,GAAG,CACjB,iBACA;YACEC,UACE/D,gBAAgB0D,MAAMM,KAAK,EAAE,YAAYN,MAAMM,KAAK,CAACC,KAAK,CAAC,GAAG;WAE5DP,MAAMQ,IAAI,GAAG;YAAEA,MAAMR,MAAMQ,IAAI;QAAC,IAAI;YAAEC,UAAUT,MAAMG,EAAE;QAAC;YAC7DO,UAAU;YACV7D,OAAO;YAET;YAAE8D,gBAAgB;QAAO,GACzB,MACAV;QAEF,IAAIC,cAAc3B,KAAKqC,KAAK,CAACT,GAAG,CAACD,YAAY,GAAGA;QAChD,OAAOC;IACT;IACA,MAAMU,QAAQzC,KAAK0C,QAAQ,CAACC,MAAM,CAAC,CAACf,QAAUA,MAAMM,KAAK,CAACU,IAAI;IAC9D,MAAMC,cAAc3C,OAAO4C,KAAK,KAAK,YAAYL,MAAMM,MAAM,GAAG;IAChE,yEAAyE;IACzE,+EAA+E;IAC/E,MAAMC,UAAU;QACdC,YAAY;QACZC,YAAY;QACZX,gBAAgB;QAChBY,YAAY;QACZC,UAAU;QACVC,cAAc;QACdC,UAAU;OACNT,cAAc,CAAC,IAAI;QAAEU,UAAU;IAAE;IAEvC,MAAMzC,QAAQd,KAAKwD,MAAM,GACrBrD,KAAK6B,GAAG,CACN,iBACA;QACEC,UAAUV,KAAKY,KAAK,CAAC,GAAG;QACxBE,UAAUrC,KAAKwD,MAAM;QACrBlB,UAAU;QACV7D,OAAO;IACT,GACAuE,SACA,MACA,WAEF7C,KAAK6B,GAAG,CACN,iBACA;QAAEC,UAAUV;QAAMkC,SAAS;QAAMC,WAAW;IAAI,GAChDV,SACA,MACA;IAEN,MAAMW,MAAMlB,MAAMM,MAAM,GACpB5C,KAAK6B,GAAG,CACN,YACA;QACE0B,WAAW;QACXE,WAAW;QACXC,WAAW;QACXC,SAAS;QACTC,YAAY;IACd,GACA;QAAEC,SAAS;YAAEC,IAAI;YAAQC,IAAI;QAAO;IAAE,GACtCzB,MAAM0B,GAAG,CAAC,CAACvC,QAAUD,QAAQC,OAAO,aACpC,SAEF;IACJ,0EAA0E;IAC1E,MAAMwC,WAAWlE,OAAOsB,MAAM,CAAC6C,IAAI,CAAC,CAACC,QAAUA,MAAMC,IAAI,KAAK;IAC9D,KAAK,MAAMD,SAASpE,OAAOsB,MAAM,CAAE;QACjC,IAAI8C,UAAUF,UACZjF,KAAKsB,OAAO,CAAC+D,IAAI,CAAC;YAChBpF,IAAI;YACJqF,MAAM,CAAC,EAAE,EAAEH,MAAMC,IAAI,CAAC,wBAAwB,CAAC;QACjD;IACJ;IACA,MAAMG,WAAWN,WAAWlG,gBAAgBkG,SAAStF,IAAI,EAAE,WAAW;IACtE,MAAM6F,QACJP,YAAYM,WACRtG,oBAAoBgG,SAASQ,EAAE,EAAEF,UAAUhD,MAAM/B,IAAI,EAAER,KAAKc,OAAO,IACnE;IACN,IAAImE,YAAY,CAACO,OACfxF,KAAKsB,OAAO,CAAC+D,IAAI,CAAC;QAChBpF,IAAI;QACJqF,MAAM,CAAC,YAAY,EAAEC,SAAS,6BAA6B,CAAC;IAC9D;IACF,MAAMG,WAAWF,QACb;QACE1C,UAAUyC;QACVjB,SAAS;QACThF,OAAOiD,MAAMnD,IAAI,KAAK,UAAU,cAAc;OAC3CN,iBAAiB0G,UAEtB;IACJ,iEAAiE;IACjE,MAAMG,MAAMD,WACR1E,KAAK6B,GAAG,CACN,aACA6C,UACA;QACEb,SAAS;YAAEC,IAAI;YAAQc,IAAI;QAAc;QACzC5B,YAAY;QACZ6B,YAAY;IACd,GACA,MACA,SAEF;IACJ,2EAA2E;IAC3E,8BAA8B;IAC9B,MAAMC,QAAQ;QAACC;KAAuB;IACtC,MAAMC,SAAS1C,MAAMM,MAAM,GACvB5C,KAAK6B,GAAG,CACN,aACA;QAAEoD,QAAQ;IAAQ,GAClB,MACA;QACEjF,KAAK6B,GAAG,CACN,YACA;YAAE0B,WAAW;YAAOE,WAAW;QAAO,GACtC;YACEI,SAAS;YACTqB,eAAe;YACfC,KAAK;YACLC,GAAG;YACHtC,YAAY;QACd,GACA;eACKR,MAAM0B,GAAG,CAAC,CAACvC,QAAUD,QAAQC,OAAO,YAAYqD;YACnDJ,WACI1E,KAAK6B,GAAG,CACN,aACA,aAAK6C;gBAAUW,WAAW;gBAC1B;gBAAEC,IAAI;YAAE,GACR,MACA,aAEF;SACL,EACD;KAEH,EACD,YAEF;IACJ,MAAMC,SAASP,SACXhF,KAAK6B,GAAG,CACN,mBACA;QAAE4B,WAAW;IAAgB,GAC7B;QAAEI,SAAS;YAAEC,IAAI;YAAeC,IAAI;QAAO;QAAGc,YAAY;IAAE,GAC5D,MACA,gBAEF;IACJ,MAAMW,SACJhC,OAAOd,cACH1C,KAAK6B,GAAG,CACN,UACA,MACA;QAAEuB,UAAU;QAAGS,SAAS;QAAQ4B,gBAAgB;IAAS,GACzD;QAACjC;KAAI,EACL,aAEFA;IACN,6FAA6F;IAC7F,MAAMkC,MAAM1F,KAAK6B,GAAG,CAClB,gBACA;QAAE8D,UAAU;IAAK,GACjB;QAAE9B,SAAS;QAAQD,YAAY;QAAUgC,WAAW;IAAE,GACtD;QAACjF;QAAO6E;QAAQb;QAAKY;QAAQP;KAAO,EACpC;IAEF,MAAMa,UAAU7F,KAAK6B,GAAG,CACtB,cACA;QAAEiE,gBAAgB;IAAK,GACvB;QAAEC,WAAW;YAAEjC,IAAI;YAAIC,IAAI;QAAG;IAAE,GAChC;QAAC2B;KAAI,EACL;IAEF,MAAMM,MAAMhG,KAAK6B,GAAG,CAClB,aACA,aACKP,KAAKjD,KAAK;QACb4H,UAAU;QACV,mFAAmF;QACnFC,UAAU;QACV3C,WAAW;QACXE,WAAW;QAEbnC,KAAK/C,EAAE,EACP;QAACsH;KAAQ,EACT;IAEF,wEAAwE;IACxE,4BAA4B;IAC5B,MAAMM,OAAOnG,KAAK6B,GAAG,CACnB,cACA;QAAE0B,WAAW;IAAO,GACpB;QAAEH,UAAU;IAAE,GACd,MACA;IAEF,MAAMgD,SAASC,cAAcrH,MAAMO,MAAM6G,MAAM,EAAEvG,MAAMuB;IACvD,MAAMkF,SAAStG,KAAK6B,GAAG,CACrB,YACA,MACA;QAAEkE,WAAW;IAAQ,GACrB;QAACC;QAAKG;QAAMC;KAAO,EACnB;IAEFpG,KAAKuG,KAAK,CAACD,QAAQ,IAAIxH,IAAI;QAACqH;KAAK;IACjC,MAAMK,SAASxG,KAAK6B,GAAG,CAAC,OAAO,MAAM,MAAM;QAACyE;KAAO,EAAE;IACrD,OAAO;QAAEtG,MAAM;YAAEwG;YAAQnE,OAAOrC,KAAKqC,KAAK;QAAC;QAAG/B,SAAStB,KAAKsB,OAAO;IAAC;AACtE;AAEA,mDAAmD,GACnD,MAAMyE,yBAAyB;IAC7BnD,IAAI;IACJR,MAAM;IACNqF,SAAS;IACTC,SAAS;QAAEC,OAAO;QAAgBC,WAAW;IAAK;IAClDC,OAAO;QAAC;YAAEC,MAAM;QAAc;KAAE;AAClC;AAEA;;;;CAIC,GACD,SAAST,cACPrH,IAAe,EACfoH,MAA8B,EAC9BvG,IAAuB,EACvBuB,IAAY;;IAEZ,MAAMpB,OAAOhB,KAAKgB,IAAI;IACtB,MAAM+G,gBAAQX,0BAAAA,OAAQhI,IAAI,mBAAI;IAC9B,MAAMA,OAAOyB,KAAKmH,UAAU,IAAKD,CAAAA,UAAU,WAAWA,UAAU,MAAK,IAAK,SAASA;IACnF,IAAI3I,SAAS2I,OAAO/H,KAAKsB,OAAO,CAAC+D,IAAI,CAAC;QAAEpF,IAAI;QAAUqF,MAAM,CAAC,EAAE,EAAEyC,MAAM,4CAA4C,CAAC;IAAC;IACrH,MAAMxF,QAAQxC,WAAWC,MAAM,UAAUZ,MAAMgI,CAAAA,0BAAAA,OAAQzD,KAAK,MAAK;IACjE,2EAA2E;IAC3E,0EAA0E;IAC1E,4EAA4E;IAC5E,yCAAyC;IACzC,MAAMsE,WAAWpH,KAAK0C,QAAQ,CAACyB,GAAG,CAAC,CAACvC,QAAW,CAAA;YAC7CxB,OAAOwB,MAAMM,KAAK;YAClBpD,MAAM;YACN8F,IAAI,CAAC,KAAK,EAAEhD,MAAMG,EAAE,EAAE;QACxB,CAAA;IACA,IAAIsF,aAAa;IACjB,MAAM7F,SAAS,UAAC+E,0BAAAA,OAAQ/E,MAAM,oBAAI,EAAE,EAAE8F,OAAO,CAAC,CAACC;YAmB1CjD;YAXWiD;QAPd,uEAAuE;QACvE,uDAAuD;QACvD,IAAI1I,oBAAoB0I,MAAMzI,IAAI,GAAG;YACnCK,KAAKsB,OAAO,CAAC+D,IAAI,CAAC;gBAAEpF,IAAI;gBAAUqF,MAAM;YAA8C;YACtF,OAAO,EAAE;QACX;QACA,MAAM+C,SAAS,CAACC,OAA0C5I,oBAAoB4I,KAAKrH,KAAK,KAAKvB,oBAAoB4I,KAAK3I,IAAI;QAC1H,MAAMwF,QAAQiD,EAAAA,eAAAA,MAAMG,KAAK,qBAAXH,aAAaI,IAAI,CAACH,WAAU,aAAKD;YAAOG,OAAOH,MAAMG,KAAK,CAAC/E,MAAM,CAAC,CAAC8E,OAAS,CAACD,OAAOC;aAAWF;QAC7G,IAAIjD,UAAUiD,OAAOpI,KAAKsB,OAAO,CAAC+D,IAAI,CAAC;YAAEpF,IAAI;YAAUqF,MAAM;QAAwD;QACrH,IAAIzF,kBAAkB4I,GAAG,CAACtD,MAAMC,IAAI,GAAG;YACrCpF,KAAKsB,OAAO,CAAC+D,IAAI,CAAC;gBAChBpF,IAAI;gBACJqF,MAAM,CAAC,EAAE,EAAEH,MAAMC,IAAI,CAAC,wBAAwB,CAAC;YACjD;YACA,OAAO,EAAE;QACX;QACA,MAAMsD,YACJvD,MAAMC,IAAI,KAAK,UACf,EAACD,eAAAA,MAAMoD,KAAK,YAAXpD,eAAe,EAAE,EAAEqD,IAAI,CAAC,CAACF;gBAAuBA;mBAAd,SAAS1I,IAAI,EAAC0I,WAAAA,KAAK7C,EAAE,YAAP6C,WAAW;;QAC9D,IAAI,CAACI,WAAW,OAAO;YAACvD;SAAM;QAC9B,IAAI+C,cAAc,CAACD,SAASrE,MAAM,EAAE;YAClC5D,KAAKsB,OAAO,CAAC+D,IAAI,CAAC;gBAAEpF,IAAI;gBAAUqF,MAAM;YAAwC;YAChF,OAAO,EAAE;QACX;QACA4C,aAAa;QACb,OAAO;YAAC,aAAK/C;gBAAOoD,OAAON;;SAAW;IACxC;IACA,MAAMU,OAAOvB,CAAAA,0BAAAA,OAAQuB,IAAI,KAAIvB,OAAOuB,IAAI,CAAC/E,MAAM,IAAI,IAAIwD,OAAOuB,IAAI,GAAG;IACrE,MAAMC,UAA6BD,OAAOA,KAAK3D,GAAG,CAAC,IAAM,EAAE,IAAI;QAAC,EAAE;KAAC;IACnE,KAAK,MAAMG,SAAS9C,OAClBuG,OAAO,CACLD,QAAQxD,MAAM0D,GAAG,KAAKC,YAAYC,KAAKC,GAAG,CAAC7D,MAAM0D,GAAG,EAAEF,KAAK/E,MAAM,GAAG,KAAK,EAC1E,CAACyB,IAAI,CAACF;IACT,MAAM8D,UAAUN,OAAO;WAAIA;KAAK,GAAG;QAAC;KAAE;IACtC,IAAI,CAACT,cAAcrH,KAAK0C,QAAQ,CAACK,MAAM,EAAE;QACvCgF,QAAQvD,IAAI,CAAC;YACX;gBACED,MAAM;gBACNmD,OAAO1H,KAAK0C,QAAQ,CAACyB,GAAG,CAAC,CAACvC,QAAW,CAAA;wBACnCxB,OAAOwB,MAAMM,KAAK;wBAClBpD,MAAM;wBACN8F,IAAI,CAAC,KAAK,EAAEhD,MAAMG,EAAE,EAAE;oBACxB,CAAA;YACF;SACD;QACDqG,QAAQ5D,IAAI,CAAC;IACf;IACA,MAAM6D,SAASN,QACZ5D,GAAG,CAAC,CAACsC,QAAQL;YAAgCgC;eAAlB;YAAE3B;YAAQ6B,MAAM,GAAEF,oBAAAA,OAAO,CAAChC,SAAS,YAAjBgC,oBAAqB;QAAE;OACpEzF,MAAM,CAAC,CAACf,QAAUA,MAAM6E,MAAM,CAAC1D,MAAM;IACxC,MAAMwF,MACJF,OAAOtF,MAAM,GAAG,IACZ/E,WAAW0D,OAAO2G,UAClBA,OAAOtF,MAAM,KAAK,IAChBhF,YAAY2D,OAAO2G,MAAM,CAAC,EAAE,CAAC5B,MAAM,EAAE,UACrC;IACR,MAAM+B,YAAYrI,KAAK6B,GAAG,CACxB,iBACA;QAAEC,UAAU,CAAC,EAAE,EAAEV,MAAM;QAAEkC,SAAS;QAASC,WAAW;IAAI,GAC1DhC,MAAMnD,IAAI,KAAK,UAAU,OAAO;QAAEE,OAAO;IAAiB,GAC1D,MACA;IAEF,MAAMgK,SAAStI,KAAK6B,GAAG,CACrB,UACA,MACA;QAAE0G,IAAI;QAAGC,WAAW;QAAGC,aAAa;IAAU,GAC9C;QAACJ;KAAU,EACX;IAEF,MAAMK,QAAQ1I,KAAK6B,GAAG,CACpB,YACA;QAAE8B,SAAS;IAAI,GACf,MACA;QAACyE;QAAKE;KAAO,EACb;IAEF,MAAMK,YAAY3I,KAAK6B,GAAG,CACxB,gBACA;QAAE8D,UAAU;IAAK,GACjB;QAAEiD,IAAI;YAAE9E,IAAI;YAAGC,IAAI;QAAE;IAAE,GACvB;QAAC2E;KAAM,EACP;IAEF,OAAO1I,KAAK6B,GAAG,CACb,WACA;QACEgH,SAAS;QACTpF,WAAW;OACPlC,MAAMnD,IAAI,KAAK,SAAS;QAAE0K,aAAa;IAAO,IAAI,CAAC,IAEzDvH,MAAMnD,IAAI,KAAK,SACX;QAAEK,SAAS;QAAoB+J,WAAW;QAAGC,aAAa;IAAU,IACpE9K,OAAO4D,MAAMnD,IAAI,GACrB;QAACuK;KAAU,EACX;AAEJ"}
@@ -150,6 +150,8 @@ export interface AiLayoutPageReading {
150
150
  export interface AiLayoutPlannedSection {
151
151
  name: string;
152
152
  items: number;
153
+ /** What the plan places in it: a form it binds there is one of these. */
154
+ uses?: readonly string[];
153
155
  }
154
156
  /**
155
157
  * The page an answer describes, against the sections its plan names: each
@@ -395,6 +395,12 @@ function readBlock(raw, at, settled) {
395
395
  });
396
396
  return section.blocks.length ? section : null;
397
397
  }
398
+ /** A planned section's name that asks for a form: a contact, an inquiry, a booking. */ const FORM_SECTION = /\b(form|contact|get in touch|reach|write|message|inquir(?:y|ies|e)|enquir(?:y|ies|e)|book(?:ing|ings)?|reserv(?:e|ation|ations)|quote|appointments?|rsvp|sign ?up|subscribe|newsletter|apply|application|register)\b/i;
399
+ /** Whether a planned section is one a form may fill: it names one, or the plan binds a form there. */ function takesForm(planned) {
400
+ var _planned_uses;
401
+ return FORM_SECTION.test(planned.name) || ((_planned_uses = planned.uses) != null ? _planned_uses : []).some((ref)=>/\bform\b/i.test(ref));
402
+ }
403
+ /** What a form's section scores against a planned section that asks for no form: never chosen while another fits. */ const AI_LAYOUT_FORM_MISFIT = -100;
398
404
  const WORD = /[a-z0-9]+/g;
399
405
  const STOP = new Set([
400
406
  'a',
@@ -430,6 +436,12 @@ const wordsOf = (text)=>{
430
436
  * How well an answer's section fits a planned one: the words its headings
431
437
  * share with the plan's name, and whether it shows items where the plan
432
438
  * asked for them and none where it asked for none.
439
+ *
440
+ * A shared heading word outweighs the items (AGL-3660): a live portfolio's
441
+ * plan gave "Works Gallery" no items, and its "Selected works" cards lost the
442
+ * place to an inquiry form that shared no word with it. A section holding a
443
+ * form fills only a planned section that asks for one — by its name, or a
444
+ * form the plan binds there — so the form is never a page's stand-in.
433
445
  */ function fit(raw, planned) {
434
446
  const blocks = isRecord(raw) && Array.isArray(raw['blocks']) ? raw['blocks'].filter(isRecord) : [];
435
447
  const said = wordsOf(blocks.filter((block)=>block['kind'] === 'heading' || block['kind'] === 'eyebrow').map((block)=>{
@@ -443,7 +455,9 @@ const wordsOf = (text)=>{
443
455
  ].some((other)=>other.startsWith(word) || word.startsWith(other))) shared += 1;
444
456
  const showsItems = blocks.some((block)=>ITEM_KINDS.has(String(block['kind'])) && (block['kind'] === 'component' || Array.isArray(block['items']) && block['items'].length > 0));
445
457
  const wantsItems = planned.items > 0;
446
- return shared * 2 + (wantsItems === showsItems ? 1.5 : -1.5);
458
+ const holdsForm = blocks.some((block)=>block['kind'] === 'form');
459
+ if (holdsForm && !takesForm(planned)) return AI_LAYOUT_FORM_MISFIT;
460
+ return shared * 2 + (wantsItems === showsItems ? 0.5 : -0.5);
447
461
  }
448
462
  /**
449
463
  * The answer's sections that best match the plan, in order, where it gave