@aglyn/plugins-ai 1.0.0-beta.292 → 1.0.0-beta.293

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 (149) hide show
  1. package/package.json +17 -17
  2. package/src/lib/components/ai-site-import-job.component.js +5 -4
  3. package/src/lib/components/ai-site-import-job.component.js.map +1 -1
  4. package/src/lib/components/ai-site-import-start.component.js +3 -3
  5. package/src/lib/components/ai-site-import-start.component.js.map +1 -1
  6. package/src/lib/components/ai-site-start-card.component.d.ts +1 -1
  7. package/src/lib/components/ai-site-start-card.component.js +129 -29
  8. package/src/lib/components/ai-site-start-card.component.js.map +1 -1
  9. package/src/lib/components/ai-site-start-steps.component.d.ts +15 -3
  10. package/src/lib/components/ai-site-start-steps.component.js +42 -25
  11. package/src/lib/components/ai-site-start-steps.component.js.map +1 -1
  12. package/src/lib/components/ai-site-style-previews.d.ts +17 -0
  13. package/src/lib/components/ai-site-style-previews.js +26 -95
  14. package/src/lib/components/ai-site-style-previews.js.map +1 -1
  15. package/src/lib/declarations.server.js +22 -0
  16. package/src/lib/declarations.server.js.map +1 -1
  17. package/src/lib/jobs/ai-job-import-step.d.ts +21 -2
  18. package/src/lib/jobs/ai-job-import-step.js +179 -49
  19. package/src/lib/jobs/ai-job-import-step.js.map +1 -1
  20. package/src/lib/jobs/ai-job-layout-language.d.ts +27 -3
  21. package/src/lib/jobs/ai-job-layout-language.js +156 -113
  22. package/src/lib/jobs/ai-job-layout-language.js.map +1 -1
  23. package/src/lib/jobs/ai-job-layout-step.js +56 -17
  24. package/src/lib/jobs/ai-job-layout-step.js.map +1 -1
  25. package/src/lib/jobs/ai-job-page-language.d.ts +25 -15
  26. package/src/lib/jobs/ai-job-page-language.js +120 -66
  27. package/src/lib/jobs/ai-job-page-language.js.map +1 -1
  28. package/src/lib/jobs/ai-job-page-step.js +27 -7
  29. package/src/lib/jobs/ai-job-page-step.js.map +1 -1
  30. package/src/lib/jobs/ai-job-site-datasets.d.ts +12 -4
  31. package/src/lib/jobs/ai-job-site-datasets.js +36 -16
  32. package/src/lib/jobs/ai-job-site-datasets.js.map +1 -1
  33. package/src/lib/jobs/ai-jobs.js +6 -2
  34. package/src/lib/jobs/ai-jobs.js.map +1 -1
  35. package/src/lib/jobs/ai-layout-stock-photos.d.ts +50 -1
  36. package/src/lib/jobs/ai-layout-stock-photos.js +586 -35
  37. package/src/lib/jobs/ai-layout-stock-photos.js.map +1 -1
  38. package/src/lib/jobs/ai-site-import-compile.d.ts +147 -0
  39. package/src/lib/jobs/ai-site-import-compile.js +1143 -0
  40. package/src/lib/jobs/ai-site-import-compile.js.map +1 -0
  41. package/src/lib/jobs/ai-site-import-document.d.ts +9 -36
  42. package/src/lib/jobs/ai-site-import-document.js +23 -489
  43. package/src/lib/jobs/ai-site-import-document.js.map +1 -1
  44. package/src/lib/jobs/ai-site-import-look.d.ts +54 -7
  45. package/src/lib/jobs/ai-site-import-look.js +163 -52
  46. package/src/lib/jobs/ai-site-import-look.js.map +1 -1
  47. package/src/lib/jobs/ai-site-import-publish.d.ts +7 -1
  48. package/src/lib/jobs/ai-site-import-publish.js +19 -5
  49. package/src/lib/jobs/ai-site-import-publish.js.map +1 -1
  50. package/src/lib/jobs/ai-site-late-photos.js +4 -2
  51. package/src/lib/jobs/ai-site-late-photos.js.map +1 -1
  52. package/src/lib/jobs/ai-site-publish.d.ts +10 -1
  53. package/src/lib/jobs/ai-site-publish.js +8 -3
  54. package/src/lib/jobs/ai-site-publish.js.map +1 -1
  55. package/src/lib/jobs/fixtures/ai-stock-photo-agl4092-1029-recordings.d.ts +23 -0
  56. package/src/lib/jobs/fixtures/ai-stock-photo-agl4092-1029-recordings.js +677 -0
  57. package/src/lib/jobs/fixtures/ai-stock-photo-agl4092-1029-recordings.js.map +1 -0
  58. package/src/lib/layout-language/ai-layout-compiler.d.ts +26 -0
  59. package/src/lib/layout-language/ai-layout-compiler.js +677 -33
  60. package/src/lib/layout-language/ai-layout-compiler.js.map +1 -1
  61. package/src/lib/layout-language/ai-layout-frame-hollows.js +5 -1
  62. package/src/lib/layout-language/ai-layout-frame-hollows.js.map +1 -1
  63. package/src/lib/layout-language/ai-layout-frame-source.d.ts +196 -0
  64. package/src/lib/layout-language/ai-layout-frame-source.js +726 -0
  65. package/src/lib/layout-language/ai-layout-frame-source.js.map +1 -0
  66. package/src/lib/layout-language/ai-layout-frame.d.ts +22 -0
  67. package/src/lib/layout-language/ai-layout-frame.js +121 -29
  68. package/src/lib/layout-language/ai-layout-frame.js.map +1 -1
  69. package/src/lib/layout-language/ai-layout-language.d.ts +103 -37
  70. package/src/lib/layout-language/ai-layout-language.js +85 -120
  71. package/src/lib/layout-language/ai-layout-language.js.map +1 -1
  72. package/src/lib/model/ai-credit-estimate.d.ts +11 -7
  73. package/src/lib/model/ai-credit-estimate.js +11 -7
  74. package/src/lib/model/ai-credit-estimate.js.map +1 -1
  75. package/src/lib/model/ai-site-import.d.ts +307 -13
  76. package/src/lib/model/ai-site-import.js +227 -19
  77. package/src/lib/model/ai-site-import.js.map +1 -1
  78. package/src/lib/model/ai-site-look.d.ts +65 -1
  79. package/src/lib/model/ai-site-look.js +141 -23
  80. package/src/lib/model/ai-site-look.js.map +1 -1
  81. package/src/lib/model/ai-site-photos.d.ts +2 -0
  82. package/src/lib/model/ai-site-photos.js.map +1 -1
  83. package/src/lib/model/ai-site-start-steps.d.ts +4 -3
  84. package/src/lib/model/ai-site-start-steps.js +11 -3
  85. package/src/lib/model/ai-site-start-steps.js.map +1 -1
  86. package/src/lib/model/ai-site-start.d.ts +23 -2
  87. package/src/lib/model/ai-site-start.js +25 -3
  88. package/src/lib/model/ai-site-start.js.map +1 -1
  89. package/src/lib/providers/routing.d.ts +11 -0
  90. package/src/lib/providers/routing.js +23 -0
  91. package/src/lib/providers/routing.js.map +1 -1
  92. package/src/lib/runtime/ai-doctrine-validators.d.ts +4 -2
  93. package/src/lib/runtime/ai-doctrine-validators.js +7 -2
  94. package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
  95. package/src/lib/runtime/ai-icon-library.d.ts +4 -0
  96. package/src/lib/runtime/ai-icon-library.js +4 -0
  97. package/src/lib/runtime/ai-icon-library.js.map +1 -1
  98. package/src/lib/runtime/ai-node-tree.js +5 -1
  99. package/src/lib/runtime/ai-node-tree.js.map +1 -1
  100. package/src/lib/runtime/ai-palette.d.ts +2 -0
  101. package/src/lib/runtime/ai-palette.generated.js +19 -3
  102. package/src/lib/runtime/ai-palette.generated.js.map +1 -1
  103. package/src/lib/runtime/ai-palette.js +2 -1
  104. package/src/lib/runtime/ai-palette.js.map +1 -1
  105. package/src/lib/site-import/fixtures/site-layout-recordings.d.ts +50 -0
  106. package/src/lib/site-import/fixtures/site-layout-recordings.js +86 -0
  107. package/src/lib/site-import/fixtures/site-layout-recordings.js.map +1 -0
  108. package/src/lib/site-import/site-import-crawl.d.ts +18 -1
  109. package/src/lib/site-import/site-import-crawl.js +70 -10
  110. package/src/lib/site-import/site-import-crawl.js.map +1 -1
  111. package/src/lib/site-import/site-import-css.d.ts +110 -0
  112. package/src/lib/site-import/site-import-css.js +826 -0
  113. package/src/lib/site-import/site-import-css.js.map +1 -0
  114. package/src/lib/site-import/site-import-html.d.ts +116 -1
  115. package/src/lib/site-import/site-import-html.js +538 -46
  116. package/src/lib/site-import/site-import-html.js.map +1 -1
  117. package/src/lib/site-import/site-import-layout.d.ts +79 -0
  118. package/src/lib/site-import/site-import-layout.js +1608 -0
  119. package/src/lib/site-import/site-import-layout.js.map +1 -0
  120. package/src/lib/site-import/site-import-nav.d.ts +40 -0
  121. package/src/lib/site-import/site-import-nav.js +437 -0
  122. package/src/lib/site-import/site-import-nav.js.map +1 -0
  123. package/src/lib/site-import/site-import-page.d.ts +8 -1
  124. package/src/lib/site-import/site-import-page.js +24 -2
  125. package/src/lib/site-import/site-import-page.js.map +1 -1
  126. package/src/lib/assets/site-styles/beauty.webp +0 -0
  127. package/src/lib/assets/site-styles/blog.webp +0 -0
  128. package/src/lib/assets/site-styles/business.webp +0 -0
  129. package/src/lib/assets/site-styles/coming-soon.webp +0 -0
  130. package/src/lib/assets/site-styles/education.webp +0 -0
  131. package/src/lib/assets/site-styles/events.webp +0 -0
  132. package/src/lib/assets/site-styles/fitness.webp +0 -0
  133. package/src/lib/assets/site-styles/landing.webp +0 -0
  134. package/src/lib/assets/site-styles/music.webp +0 -0
  135. package/src/lib/assets/site-styles/nonprofit.webp +0 -0
  136. package/src/lib/assets/site-styles/personal.webp +0 -0
  137. package/src/lib/assets/site-styles/photography.webp +0 -0
  138. package/src/lib/assets/site-styles/portfolio.webp +0 -0
  139. package/src/lib/assets/site-styles/professional.webp +0 -0
  140. package/src/lib/assets/site-styles/realestate.webp +0 -0
  141. package/src/lib/assets/site-styles/restaurant.webp +0 -0
  142. package/src/lib/assets/site-styles/store.webp +0 -0
  143. package/src/lib/assets/site-styles/studio.webp +0 -0
  144. package/src/lib/assets/site-styles/trades.webp +0 -0
  145. package/src/lib/assets/site-styles/wellness.webp +0 -0
  146. package/src/lib/assets/site-styles/yoga.webp +0 -0
  147. package/src/lib/site-import/site-import-review.d.ts +0 -40
  148. package/src/lib/site-import/site-import-review.js +0 -149
  149. package/src/lib/site-import/site-import-review.js.map +0 -1
@@ -0,0 +1,1143 @@
1
+ import { _ as _extends } from "@swc/helpers/_/_extends";
2
+ import { _ as _object_without_properties_loose } from "@swc/helpers/_/_object_without_properties_loose";
3
+ /**
4
+ * @license
5
+ * Copyright 2026 Aglyn LLC
6
+ *
7
+ * Licensed under the Apache License, Version 2.0 (the "License");
8
+ * you may not use this file except in compliance with the License.
9
+ * You may obtain a copy of the License at
10
+ *
11
+ * http://www.apache.org/licenses/LICENSE-2.0
12
+ *
13
+ * Unless required by applicable law or agreed to in writing, software
14
+ * distributed under the License is distributed on an "AS IS" BASIS,
15
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ * See the License for the specific language governing permissions and
17
+ * limitations under the License.
18
+ */ /*==========================================
19
+ * A PAGE BROUGHT IN, BUILT BY THE SITE COMPILER (AGL-4107).
20
+ *
21
+ * On 10/29 an import of aglyn.com came out one column of plain lines: the
22
+ * import drew its own stacked text instead of the site's cards, columns,
23
+ * pictures, icons and bands. This module is the page's build now:
24
+ *
25
+ * 1. The source's LAYOUT, as the reader read it (`AiSiteImportPageRead.
26
+ * layout`, AGL-4106) — its sections and bands, rows of columns, groups of
27
+ * cards, steps, figures, questions, quotes, logos and plans, its tables,
28
+ * tabs, pictures and icons — becomes the layout language the guided
29
+ * start's compiler takes (`aiSiteImportDesignedSections`): a group is a
30
+ * `cards` block whose items carry every part (icon, picture, figure,
31
+ * paragraphs, bullet points, links), a table a `table` block, tabs a
32
+ * `tabs` block. Every line is placed exactly once, in its own section; a
33
+ * line the layout placed nowhere is added at its section's end.
34
+ * 2. The site compiler draws it (`aiCompileLayoutPage`, `verbatim`), with
35
+ * the palette's own elements and the theme's tokens: the Card, the
36
+ * Accordion, the Table, the Tabs, the hero around the source's picture.
37
+ * Every word is the source's, as written.
38
+ * 3. A main page's DESIGN is the one question a model answers
39
+ * (`submit_design`, `aiSiteImportDesignText`): which hero the opening
40
+ * becomes, how each group is drawn, a band where the source gave none,
41
+ * the kind of a group the reader could not tell, an alignment. A page of
42
+ * a collection, and any page whose answer is missing, is drawn from the
43
+ * layout with no model.
44
+ *
45
+ * A read made before the reader kept no layout: its page is read into one
46
+ * here, from its lines, one section at each second-level heading.
47
+ *=========================================*/ import { aiCompileLayoutPage } from "../layout-language/ai-layout-compiler.js";
48
+ import { AI_LAYOUT_GROUP_STYLES, AI_LAYOUT_MAX_COLS } from "../layout-language/ai-layout-language.js";
49
+ import { AI_SITE_IMPORT_LABEL_MAX_CHARS } from "../model/ai-site-import.js";
50
+ import { aiModelAtLeastStepTier } from "../providers/routing.js";
51
+ import { AI_SITE_IMPORT_DOC_MAX_NODES, AI_SITE_IMPORT_LABEL_CHARS, aiSiteImportDocLines, aiSiteImportTextParts, aiSiteImportWordsIn } from "./ai-site-import-document.js";
52
+ const clean = (text)=>typeof text === 'string' ? text.replace(/\s+/g, ' ').trim() : '';
53
+ /** Whether a block is a line of the page: a link inside a sentence is in its sentence. */ function isLine(block) {
54
+ if (!block) return false;
55
+ if (block.kind === 'image') return !!block.url;
56
+ if (block.kind === 'link') return !block.inline && !!clean(block.text);
57
+ return !!clean(block.text);
58
+ }
59
+ /*------------------------------------------
60
+ * The page's job: its model and its links
61
+ *-----------------------------------------*/ /**
62
+ * The model a page brought in asks its design answer on (AGL-4107): the page
63
+ * step's own tier, balanced, whatever the deployment's default model is — the
64
+ * job's own pick where it is that tier or above.
65
+ */ export function aiImportPageModel(modelFor) {
66
+ return aiModelAtLeastStepTier('job.page', modelFor == null ? void 0 : modelFor('job.page'));
67
+ }
68
+ /**
69
+ * Every page the import plans that a page brought in may link (AGL-4107), by
70
+ * the id its draft is written under: a link to one in a wave still to come
71
+ * shows its words until that page is live, then goes there.
72
+ */ export function aiSiteImportLinkablePages(doc) {
73
+ if (!doc) return [];
74
+ const seen = new Set();
75
+ return doc.links.flatMap((link)=>{
76
+ if (seen.has(link.id)) return [];
77
+ seen.add(link.id);
78
+ const path = link.path.replace(/\/+$/, '') || '/';
79
+ return [
80
+ {
81
+ id: link.id,
82
+ label: path,
83
+ slug: path
84
+ }
85
+ ];
86
+ });
87
+ }
88
+ /**
89
+ * The listing a page brought in keeps (AGL-4107): its source's own title and
90
+ * description, word for word, never clipped and never rewritten — the 10/29
91
+ * import's Home was retitled "Aglyn: Build a Website with AI, Free to Start".
92
+ * Only a part the source left empty takes what was `written` for it.
93
+ */ export function aiImportedPageListing(screen, written = {}) {
94
+ var _written_title, _written_description;
95
+ return {
96
+ title: screen.seoTitle.trim() || ((_written_title = written.title) != null ? _written_title : '').trim(),
97
+ description: screen.seoDescription.trim() || ((_written_description = written.description) != null ? _written_description : '').trim()
98
+ };
99
+ }
100
+ /** Whether a page brought in still owes a part of its listing: one its source left empty. */ export function aiImportedPageListingOwed(listing) {
101
+ return !listing.title || !listing.description;
102
+ }
103
+ /*------------------------------------------
104
+ * The layout a page is built from
105
+ *-----------------------------------------*/ /**
106
+ * The layout of a page whose read kept none (AGL-4107): its lines in their
107
+ * sections — the opening, then one at each second-level heading — each line
108
+ * where the source put it, in order.
109
+ */ export function aiSiteImportLayoutOfLines(doc) {
110
+ const { lines, sections } = aiSiteImportDocLines(doc.blocks, doc.title);
111
+ // The document's lines are numbered over the page's lines; the layout's are indexes of its blocks.
112
+ const refOf = [];
113
+ doc.blocks.forEach((block, index)=>{
114
+ if (isLine(block)) refOf.push(index);
115
+ });
116
+ return {
117
+ v: 1,
118
+ sections: sections.map((section, index)=>{
119
+ const refs = section.lines.map((n)=>refOf[n - 1]).filter((ref)=>ref !== undefined);
120
+ return {
121
+ name: section.name,
122
+ from: 'heading',
123
+ band: index % 2 === 1 ? 'soft' : 'plain',
124
+ background: null,
125
+ align: 'start',
126
+ nodes: refs.map((ref)=>{
127
+ var _doc_blocks_ref;
128
+ return ((_doc_blocks_ref = doc.blocks[ref]) == null ? void 0 : _doc_blocks_ref.kind) === 'image' ? {
129
+ kind: 'picture',
130
+ line: ref
131
+ } : {
132
+ kind: 'line',
133
+ line: ref
134
+ };
135
+ }),
136
+ lines: refs
137
+ };
138
+ }),
139
+ icons: [],
140
+ look: {
141
+ fonts: {
142
+ body: null,
143
+ headings: null,
144
+ google: []
145
+ },
146
+ background: null,
147
+ text: null,
148
+ buttonFill: null,
149
+ buttonText: null,
150
+ buttonRadius: null,
151
+ link: null,
152
+ header: null
153
+ }
154
+ };
155
+ }
156
+ /** The layout a page is built from: the reader's, else one read from its lines. */ export function aiSiteImportLayoutOf(doc) {
157
+ return doc.layout && doc.layout.sections.length ? doc.layout : aiSiteImportLayoutOfLines(doc);
158
+ }
159
+ /**
160
+ * The page's sections by name, in order (AGL-4107): the source's own, as its
161
+ * layout names them — its `aria-label`s, its headings — two of one name told
162
+ * apart, so a link to either finds it. The page's plan has exactly these.
163
+ */ export function aiSiteImportSectionNames(doc) {
164
+ const named = new Set();
165
+ const names = aiSiteImportLayoutOf(doc).sections.map((section, index)=>{
166
+ const name = clean(section.name).slice(0, AI_SITE_IMPORT_LABEL_MAX_CHARS) || `Section ${index + 1}`;
167
+ let unique = name;
168
+ for(let attempt = 2; named.has(unique.toLowerCase()); attempt += 1){
169
+ unique = `${name.slice(0, AI_SITE_IMPORT_LABEL_MAX_CHARS - 5)} (${attempt})`;
170
+ }
171
+ named.add(unique.toLowerCase());
172
+ return unique;
173
+ });
174
+ return names.length ? names : [
175
+ 'Introduction'
176
+ ];
177
+ }
178
+ /*------------------------------------------
179
+ * The source's icons, on the icon element's square
180
+ *-----------------------------------------*/ /** How many numbers each path command takes, by its lower-case letter. */ const PATH_PARAMS = {
181
+ m: 2,
182
+ l: 2,
183
+ h: 1,
184
+ v: 1,
185
+ c: 6,
186
+ s: 4,
187
+ q: 4,
188
+ t: 2,
189
+ a: 7,
190
+ z: 0
191
+ };
192
+ const NUMBER = /[\s,]*([-+]?(?:\d*\.\d+|\d+\.?)(?:[eE][-+]?\d+)?)/y;
193
+ const FLAG = /[\s,]*([01])/y;
194
+ /** A number of a path, at most three decimals, as short as it reads. */ const round = (value)=>{
195
+ const fixed = Math.round(value * 1000) / 1000;
196
+ return Object.is(fixed, -0) ? '0' : String(fixed);
197
+ };
198
+ /**
199
+ * A path's drawing moved onto the icon element's 24-unit square (AGL-4107):
200
+ * every coordinate scaled from its viewBox and centered, every relative move
201
+ * scaled alone, an arc's radii scaled and its flags and rotation kept.
202
+ * `null` for anything that is not path data.
203
+ */ export function aiSiteImportPathOn24(d, viewBox) {
204
+ const box = viewBox.trim().split(/[\s,]+/).map(Number);
205
+ if (box.length !== 4 || box.some((value)=>!Number.isFinite(value)) || box[2] <= 0 || box[3] <= 0) return null;
206
+ const [minX, minY, width, height] = box;
207
+ const scale = 24 / Math.max(width, height);
208
+ const dx = (24 - width * scale) / 2 - minX * scale;
209
+ const dy = (24 - height * scale) / 2 - minY * scale;
210
+ const identity = Math.abs(scale - 1) < 1e-9 && Math.abs(dx) < 1e-9 && Math.abs(dy) < 1e-9;
211
+ const out = [];
212
+ let at = 0;
213
+ let first = true;
214
+ const source = d.trim();
215
+ while(at < source.length){
216
+ while(at < source.length && /[\s,]/.test(source[at]))at += 1;
217
+ if (at >= source.length) break;
218
+ const command = source[at];
219
+ const lower = command.toLowerCase();
220
+ if (!(lower in PATH_PARAMS)) return null;
221
+ at += 1;
222
+ const count = PATH_PARAMS[lower];
223
+ const relative = command === lower;
224
+ const groups = [];
225
+ if (count === 0) {
226
+ out.push(command);
227
+ first = false;
228
+ continue;
229
+ }
230
+ for(;;){
231
+ const group = [];
232
+ for(let index = 0; index < count; index += 1){
233
+ const pattern = lower === 'a' && (index === 3 || index === 4) ? FLAG : NUMBER;
234
+ pattern.lastIndex = at;
235
+ const match = pattern.exec(source);
236
+ if (!match) break;
237
+ group.push(Number(match[1]));
238
+ at = pattern.lastIndex;
239
+ }
240
+ if (group.length === 0) break;
241
+ if (group.length !== count) return null;
242
+ groups.push(group);
243
+ }
244
+ if (!groups.length) return null;
245
+ const parts = groups.map((group, index)=>{
246
+ // A path that opens on a relative move opens at that point, as an absolute one would.
247
+ const absolute = !relative || first && lower === 'm' && index === 0;
248
+ const x = (value)=>absolute ? value * scale + dx : value * scale;
249
+ const y = (value)=>absolute ? value * scale + dy : value * scale;
250
+ switch(lower){
251
+ case 'h':
252
+ return [
253
+ x(group[0])
254
+ ];
255
+ case 'v':
256
+ return [
257
+ y(group[0])
258
+ ];
259
+ case 'a':
260
+ return [
261
+ group[0] * scale,
262
+ group[1] * scale,
263
+ group[2],
264
+ group[3],
265
+ group[4],
266
+ x(group[5]),
267
+ y(group[6])
268
+ ];
269
+ default:
270
+ return group.map((value, position)=>position % 2 === 0 ? x(value) : y(value));
271
+ }
272
+ });
273
+ out.push(`${command}${parts.map((part)=>part.map(round).join(' ')).join(' ')}`);
274
+ first = false;
275
+ }
276
+ if (!out.length || !/^[Mm]/.test(out[0])) return null;
277
+ return identity ? source : out.join('');
278
+ }
279
+ /** The attributes of a shape in a reader's cleaned drawing. */ function shapeAttributes(source) {
280
+ const out = {};
281
+ for (const match of source.matchAll(/([a-zA-Z-]+)="([^"]*)"/g))out[match[1].toLowerCase()] = match[2];
282
+ return out;
283
+ }
284
+ const num = (value)=>value === undefined ? Number.NaN : Number.parseFloat(value);
285
+ /** A shape of a drawing as path data, or `null` for one that is only a stroke or is moved by a transform. */ function shapePath(tag, attributes) {
286
+ if (attributes['transform'] || attributes['fill'] === 'none') return null;
287
+ switch(tag){
288
+ case 'path':
289
+ var _attributes_d;
290
+ return ((_attributes_d = attributes['d']) == null ? void 0 : _attributes_d.trim()) || null;
291
+ case 'circle':
292
+ case 'ellipse':
293
+ {
294
+ var _attributes_cx, _attributes_cy;
295
+ const cx = num((_attributes_cx = attributes['cx']) != null ? _attributes_cx : '0');
296
+ const cy = num((_attributes_cy = attributes['cy']) != null ? _attributes_cy : '0');
297
+ const rx = tag === 'circle' ? num(attributes['r']) : num(attributes['rx']);
298
+ const ry = tag === 'circle' ? rx : num(attributes['ry']);
299
+ if (![
300
+ cx,
301
+ cy,
302
+ rx,
303
+ ry
304
+ ].every(Number.isFinite) || rx <= 0 || ry <= 0) return null;
305
+ return `M${cx - rx} ${cy}A${rx} ${ry} 0 1 0 ${cx + rx} ${cy}A${rx} ${ry} 0 1 0 ${cx - rx} ${cy}Z`;
306
+ }
307
+ case 'rect':
308
+ {
309
+ var _attributes_x, _attributes_y;
310
+ const x = num((_attributes_x = attributes['x']) != null ? _attributes_x : '0');
311
+ const y = num((_attributes_y = attributes['y']) != null ? _attributes_y : '0');
312
+ const width = num(attributes['width']);
313
+ const height = num(attributes['height']);
314
+ if (![
315
+ x,
316
+ y,
317
+ width,
318
+ height
319
+ ].every(Number.isFinite) || width <= 0 || height <= 0) return null;
320
+ return `M${x} ${y}H${x + width}V${y + height}H${x}Z`;
321
+ }
322
+ case 'polygon':
323
+ {
324
+ var _attributes_points;
325
+ const points = ((_attributes_points = attributes['points']) != null ? _attributes_points : '').trim().split(/[\s,]+/).map(Number);
326
+ if (points.length < 4 || points.length % 2 || points.some((value)=>!Number.isFinite(value))) return null;
327
+ return `M${points.slice(0, 2).join(' ')}L${points.slice(2).join(' ')}Z`;
328
+ }
329
+ default:
330
+ // A line or an open polyline is a stroke: filled, it draws nothing.
331
+ return null;
332
+ }
333
+ }
334
+ /**
335
+ * The source's icon as the icon element draws one (AGL-4107): one path on its
336
+ * 24-unit square. A one-path icon is its path, moved onto the square; a small
337
+ * drawing of several filled shapes is their paths together. A drawing made of
338
+ * strokes, or of shapes a transform moves, has no one path, and is left out:
339
+ * an icon is no word of the page.
340
+ */ export function aiSiteImportIconPath(shape) {
341
+ if (!shape) return null;
342
+ if (shape.path) return aiSiteImportPathOn24(shape.path, shape.viewBox);
343
+ if (!shape.svg) return null;
344
+ const paths = [];
345
+ for (const match of shape.svg.matchAll(/<(path|circle|ellipse|rect|polygon|polyline|line)\b([^>]*)\/?>/gi)){
346
+ const path = shapePath(match[1].toLowerCase(), shapeAttributes(match[2]));
347
+ if (!path) return null;
348
+ paths.push(path);
349
+ }
350
+ return paths.length ? aiSiteImportPathOn24(paths.join(''), shape.viewBox) : null;
351
+ }
352
+ /*------------------------------------------
353
+ * The layout in the layout language
354
+ *-----------------------------------------*/ /** The groups a design answer may read as another kind: the ones whose kind the reader told by their shape alone. */ const OPEN_KINDS = new Set([
355
+ 'cards',
356
+ 'steps',
357
+ 'stats',
358
+ 'list',
359
+ 'text'
360
+ ]);
361
+ /** The hero each place of the source's picture opens as, where no answer chose one. */ const HERO_FOR = {
362
+ beside: 'split',
363
+ behind: 'cover',
364
+ below: 'column',
365
+ above: 'column'
366
+ };
367
+ /** Where a link of the source goes on the new site, in the compiler's words; `undefined` where it can go nowhere a page may send a visitor. */ function linkTo(scope, href) {
368
+ const value = (href != null ? href : '').trim();
369
+ if (/^mailto:[^\s@]+@[^\s@]+$/i.test(value) || /^tel:\+?[\d\s().-]{3,}$/i.test(value)) return value;
370
+ let url;
371
+ try {
372
+ url = new URL(value);
373
+ } catch (unused) {
374
+ return undefined;
375
+ }
376
+ if (url.protocol !== 'https:' && url.protocol !== 'http:') return undefined;
377
+ if (scope.doc.origins.includes(url.origin)) {
378
+ // The old site's own page: the page it becomes, any page the import plans
379
+ // (AGL-4107), which a visitor reaches once it is live.
380
+ const path = url.pathname.replace(/\/+$/, '') || '/';
381
+ const id = scope.pathTo.get(path);
382
+ return id ? `page:${id}` : undefined;
383
+ }
384
+ return url.protocol === 'https:' ? url.toString() : undefined;
385
+ }
386
+ /** A line's words, as written; marks it taken. */ function take(scope, ref) {
387
+ if (ref === undefined || scope.used.has(ref)) return '';
388
+ const block = scope.doc.blocks[ref];
389
+ if (!isLine(block)) return '';
390
+ scope.used.add(ref);
391
+ return block.kind === 'image' ? '' : clean(block.text);
392
+ }
393
+ /** A picture line as the library holds it; marks it taken, and missing where the library holds none. */ function takePicture(scope, ref, href) {
394
+ if (ref === undefined || scope.used.has(ref)) return null;
395
+ const block = scope.doc.blocks[ref];
396
+ if (!block || block.kind !== 'image') return null;
397
+ scope.used.add(ref);
398
+ const photo = scope.photos.get(block.url);
399
+ if (!photo) {
400
+ scope.missing.add(ref);
401
+ return null;
402
+ }
403
+ const alt = clean(block.alt || photo.alt || '').slice(0, 200);
404
+ const ratio = photo.width && photo.height ? photo.width / photo.height : 0;
405
+ const to = linkTo(scope, href);
406
+ return _extends({
407
+ src: photo.src,
408
+ alt
409
+ }, ratio > 0 ? {
410
+ ratio
411
+ } : {}, to ? {
412
+ to
413
+ } : {});
414
+ }
415
+ /** Words as text blocks, a paragraph longer than a text element holds split between its sentences. */ function textBlocks(kind, text) {
416
+ return aiSiteImportTextParts(text).map((part)=>({
417
+ kind,
418
+ text: part
419
+ }));
420
+ }
421
+ /** A link of the source: a button where its words fit one, a link in a list where they run longer, its words where it goes nowhere. */ function linkBlocks(scope, text, href, style) {
422
+ const to = linkTo(scope, href);
423
+ if (text.length <= AI_SITE_IMPORT_LABEL_CHARS) return [
424
+ _extends({
425
+ kind: 'button',
426
+ text
427
+ }, to ? {
428
+ to
429
+ } : {}, {
430
+ style
431
+ })
432
+ ];
433
+ if (to) return [
434
+ {
435
+ kind: 'list',
436
+ items: [
437
+ {
438
+ title: text,
439
+ text: '',
440
+ to
441
+ }
442
+ ]
443
+ }
444
+ ];
445
+ return textBlocks('text', text);
446
+ }
447
+ /** One line in place, as the kind of block its source and its role say. */ function lineBlocks(scope, node) {
448
+ const block = scope.doc.blocks[node.line];
449
+ if (!block || scope.used.has(node.line)) return [];
450
+ if (block.kind === 'image') {
451
+ const picture = takePicture(scope, node.line);
452
+ return picture ? [
453
+ {
454
+ kind: 'image',
455
+ picture
456
+ }
457
+ ] : [];
458
+ }
459
+ const text = take(scope, node.line);
460
+ if (!text) return [];
461
+ switch(block.kind){
462
+ case 'heading':
463
+ return [
464
+ {
465
+ kind: 'heading',
466
+ text
467
+ }
468
+ ];
469
+ case 'link':
470
+ var _node_button;
471
+ return linkBlocks(scope, text, block.href, (_node_button = node.button) != null ? _node_button : 'quiet');
472
+ case 'list-item':
473
+ return [
474
+ {
475
+ kind: 'list',
476
+ items: [
477
+ {
478
+ title: text,
479
+ text: ''
480
+ }
481
+ ]
482
+ }
483
+ ];
484
+ case 'quote':
485
+ return [
486
+ {
487
+ kind: 'quotes',
488
+ style: 'quiet',
489
+ items: [
490
+ {
491
+ title: '',
492
+ text
493
+ }
494
+ ]
495
+ }
496
+ ];
497
+ default:
498
+ if (node.role === 'eyebrow') return [
499
+ {
500
+ kind: 'eyebrow',
501
+ text
502
+ }
503
+ ];
504
+ return textBlocks(node.role === 'lede' ? 'lede' : node.role === 'note' ? 'note' : 'text', text);
505
+ }
506
+ }
507
+ /** The icon a group item or a bullet carries, by its index into the page's icons. */ function iconAt(scope, index) {
508
+ var _aiSiteImportIconPath;
509
+ return index === undefined ? undefined : (_aiSiteImportIconPath = aiSiteImportIconPath(scope.layout.icons[index])) != null ? _aiSiteImportIconPath : undefined;
510
+ }
511
+ /** The style of a link inside an item: a plan's own button is filled outlined, any other a quiet link. */ function itemLinkStyle(kind) {
512
+ return kind === 'pricing' ? 'secondary' : 'quiet';
513
+ }
514
+ /** One item of a group, every line of it in exactly one of its parts. */ function itemOf(scope, group, item) {
515
+ var _item_text, _item_list, _item_link;
516
+ var _out_list;
517
+ const out = {
518
+ title: '',
519
+ text: ''
520
+ };
521
+ const named = new Set([
522
+ ...item.title !== undefined ? [
523
+ item.title
524
+ ] : [],
525
+ ...item.figure !== undefined ? [
526
+ item.figure
527
+ ] : [],
528
+ ...item.image !== undefined ? [
529
+ item.image
530
+ ] : [],
531
+ ...(_item_text = item.text) != null ? _item_text : [],
532
+ ...(_item_list = item.list) != null ? _item_list : [],
533
+ ...(_item_link = item.link) != null ? _item_link : []
534
+ ]);
535
+ const paragraphs = [];
536
+ const links = [];
537
+ const addLink = (ref)=>{
538
+ const block = scope.doc.blocks[ref];
539
+ const text = take(scope, ref);
540
+ if (!text) return;
541
+ const to = (block == null ? void 0 : block.kind) === 'link' ? linkTo(scope, block.href) : undefined;
542
+ if (text.length <= AI_SITE_IMPORT_LABEL_CHARS) links.push(_extends({
543
+ text
544
+ }, to ? {
545
+ to
546
+ } : {}, {
547
+ style: itemLinkStyle(group.group)
548
+ }));
549
+ else paragraphs.push(text);
550
+ };
551
+ // The item's lines in the source's order: each where its part says.
552
+ for (const ref of item.lines){
553
+ var _item_list1, _item_link1;
554
+ if (scope.used.has(ref)) continue;
555
+ const block = scope.doc.blocks[ref];
556
+ if (!block) continue;
557
+ if (ref === item.title) out.title = take(scope, ref);
558
+ else if (ref === item.figure) out.figure = take(scope, ref);
559
+ else if (ref === item.image || block.kind === 'image' && !named.has(ref)) {
560
+ const picture = takePicture(scope, ref);
561
+ if (picture && !out.picture) out.picture = picture;
562
+ } else if ((_item_list1 = item.list) == null ? void 0 : _item_list1.includes(ref)) {
563
+ var _out_list1;
564
+ const line = take(scope, ref);
565
+ if (line) out.list = [
566
+ ...(_out_list1 = out.list) != null ? _out_list1 : [],
567
+ line
568
+ ];
569
+ } else if (((_item_link1 = item.link) == null ? void 0 : _item_link1.includes(ref)) || !named.has(ref) && block.kind === 'link') addLink(ref);
570
+ else {
571
+ const line = take(scope, ref);
572
+ if (line) paragraphs.push(...aiSiteImportTextParts(line));
573
+ }
574
+ }
575
+ if (paragraphs.length) out.text = paragraphs[0];
576
+ if (paragraphs.length > 1) out.more = paragraphs.slice(1);
577
+ if (links.length) out.links = links;
578
+ const icon = iconAt(scope, item.icon);
579
+ if (icon) out.iconPath = icon;
580
+ const mark = iconAt(scope, item.listIcon);
581
+ if (mark && ((_out_list = out.list) == null ? void 0 : _out_list.length)) out.listIconPath = mark;
582
+ const to = linkTo(scope, item.to);
583
+ if (to) out.to = to;
584
+ return out;
585
+ }
586
+ /** The style a group of cards is drawn in where its design answer chose none: the source's own box, icons or pictures. */ function defaultStyle(group, items) {
587
+ const pictured = items.length > 0 && items.every((item)=>item.picture);
588
+ if (pictured) return group.boxed ? 'articles' : 'pictures';
589
+ if (group.boxed) return 'cards';
590
+ return items.length > 0 && items.every((item)=>item.iconPath) ? 'icons' : 'ruled';
591
+ }
592
+ /** The style an answer chose, where the group can be drawn so: pictures need a picture on every item, icons an icon. */ function styleFor(asked, group, items) {
593
+ const fallback = defaultStyle(group, items);
594
+ if (!asked || !AI_LAYOUT_GROUP_STYLES.includes(asked)) return fallback;
595
+ if ((asked === 'pictures' || asked === 'articles') && !items.every((item)=>item.picture)) return fallback;
596
+ if (asked === 'icons' && !items.every((item)=>item.iconPath)) return fallback;
597
+ return asked;
598
+ }
599
+ /** A group of the source, in the layout language: every item of it, in the kind and style its answer chose or its source shows. */ function groupBlocks(scope, group) {
600
+ var _ref;
601
+ var _scope_answer;
602
+ const answer = (_ref = (_scope_answer = scope.answer) == null ? void 0 : _scope_answer.groups[scope.groupAt]) != null ? _ref : null;
603
+ scope.groupAt += 1;
604
+ const kind = (answer == null ? void 0 : answer.kind) && OPEN_KINDS.has(group.group) ? answer.kind : group.group;
605
+ if (kind !== group.group) scope.settled.push({
606
+ at: scope.at,
607
+ what: `a ${group.group} group drawn as ${kind}, as its design answer read it`
608
+ });
609
+ const columns = Math.max(1, Math.min(AI_LAYOUT_MAX_COLS, group.columns || 1));
610
+ if (kind === 'text') {
611
+ // Lines that only share a shape: each where it stands.
612
+ return group.items.flatMap((item)=>item.lines.flatMap((ref)=>lineBlocks(scope, {
613
+ kind: 'line',
614
+ line: ref
615
+ })));
616
+ }
617
+ if (kind === 'list') {
618
+ const items = group.items.flatMap((item)=>{
619
+ var _item_to;
620
+ const linked = item.lines.map((ref)=>scope.doc.blocks[ref]).find((block)=>(block == null ? void 0 : block.kind) === 'link');
621
+ const words = item.lines.map((ref)=>take(scope, ref)).filter(Boolean).join(' ');
622
+ for (const ref of item.lines){
623
+ var _scope_doc_blocks_ref;
624
+ if (((_scope_doc_blocks_ref = scope.doc.blocks[ref]) == null ? void 0 : _scope_doc_blocks_ref.kind) === 'image') takePicture(scope, ref);
625
+ }
626
+ if (!words) return [];
627
+ const icon = iconAt(scope, item.icon);
628
+ const to = linkTo(scope, (_item_to = item.to) != null ? _item_to : (linked == null ? void 0 : linked.kind) === 'link' ? linked.href : undefined);
629
+ return [
630
+ _extends({
631
+ title: words,
632
+ text: ''
633
+ }, icon ? {
634
+ iconPath: icon
635
+ } : {}, to ? {
636
+ to
637
+ } : {})
638
+ ];
639
+ });
640
+ return items.length ? [
641
+ {
642
+ kind: 'list',
643
+ items,
644
+ columns
645
+ }
646
+ ] : [];
647
+ }
648
+ if (kind === 'logos') {
649
+ const items = group.items.flatMap((item)=>{
650
+ let picture = null;
651
+ const words = [];
652
+ for (const ref of item.lines){
653
+ var _scope_doc_blocks_ref;
654
+ if (((_scope_doc_blocks_ref = scope.doc.blocks[ref]) == null ? void 0 : _scope_doc_blocks_ref.kind) === 'image') picture = picture != null ? picture : takePicture(scope, ref);
655
+ else {
656
+ const line = take(scope, ref);
657
+ if (line) words.push(line);
658
+ }
659
+ }
660
+ if (picture && !words.length) return [
661
+ {
662
+ title: '',
663
+ text: '',
664
+ picture
665
+ }
666
+ ];
667
+ return words.length ? [
668
+ _extends({
669
+ title: words.join(' '),
670
+ text: ''
671
+ }, picture ? {
672
+ picture
673
+ } : {})
674
+ ] : picture ? [
675
+ {
676
+ title: '',
677
+ text: '',
678
+ picture
679
+ }
680
+ ] : [];
681
+ });
682
+ return items.length ? [
683
+ {
684
+ kind: 'logos',
685
+ items
686
+ }
687
+ ] : [];
688
+ }
689
+ const items = group.items.map((item)=>itemOf(scope, group, item));
690
+ if (kind === 'quotes') {
691
+ // The reader names a quote's words its title and who said it its text.
692
+ const quotes = items.map((item)=>{
693
+ var _item_more;
694
+ return _extends({}, item, {
695
+ title: [
696
+ item.text,
697
+ ...(_item_more = item.more) != null ? _item_more : []
698
+ ].filter(Boolean).join(' '),
699
+ text: item.title,
700
+ more: []
701
+ });
702
+ });
703
+ return [
704
+ _extends({
705
+ kind: 'quotes',
706
+ items: quotes.map((_0)=>{
707
+ let { more: _more } = _0, item = _object_without_properties_loose(_0, [
708
+ "more"
709
+ ]);
710
+ return item;
711
+ }),
712
+ columns
713
+ }, group.boxed ? {
714
+ variant: 'cards'
715
+ } : {
716
+ style: 'quiet'
717
+ })
718
+ ];
719
+ }
720
+ if (kind === 'faq') return [
721
+ {
722
+ kind: 'faq',
723
+ items,
724
+ columns
725
+ }
726
+ ];
727
+ if (kind === 'stats') return [
728
+ _extends({
729
+ kind: 'stats',
730
+ items,
731
+ columns
732
+ }, group.boxed ? {
733
+ variant: 'cards'
734
+ } : {})
735
+ ];
736
+ const style = styleFor(answer == null ? void 0 : answer.style, group, items);
737
+ return [
738
+ {
739
+ kind: kind === 'steps' ? 'steps' : 'cards',
740
+ items,
741
+ columns,
742
+ variant: style
743
+ }
744
+ ];
745
+ }
746
+ /** A section's nodes in the layout language, a row of columns placed by `col` where the section may still take one. */ function nodeBlocks(scope, nodes, row) {
747
+ const out = [];
748
+ for (const node of nodes){
749
+ switch(node.kind){
750
+ case 'line':
751
+ out.push(...lineBlocks(scope, node));
752
+ break;
753
+ case 'picture':
754
+ {
755
+ const picture = takePicture(scope, node.line, node.button ? undefined : node.href);
756
+ if (picture) out.push({
757
+ kind: 'image',
758
+ picture
759
+ });
760
+ break;
761
+ }
762
+ case 'icon':
763
+ break;
764
+ case 'form':
765
+ if (scope.formId && !scope.formPlaced) {
766
+ scope.formPlaced = true;
767
+ out.push({
768
+ kind: 'form',
769
+ to: scope.formId
770
+ });
771
+ }
772
+ break;
773
+ case 'row':
774
+ {
775
+ const cells = node.cells.filter((cell)=>cell.nodes.length);
776
+ const byline = bylineOf(scope, cells);
777
+ if (byline) {
778
+ // A portrait beside a name and a date: the portrait small, over its words.
779
+ out.push(...byline);
780
+ break;
781
+ }
782
+ if (row.allowed && !row.cols && cells.length >= 2 && cells.length <= AI_LAYOUT_MAX_COLS) {
783
+ // The section's row: each cell a column, by the source's widths.
784
+ const weights = node.cells.map((cell, index)=>{
785
+ var _node_cols_index;
786
+ return {
787
+ cell,
788
+ weight: (_node_cols_index = node.cols[index]) != null ? _node_cols_index : 1
789
+ };
790
+ }).filter((entry)=>entry.cell.nodes.length);
791
+ row.cols = weights.map((entry)=>Math.min(12, Math.max(1, Math.round(entry.weight))));
792
+ weights.forEach((entry, col)=>{
793
+ for (const block of nodeBlocks(scope, entry.cell.nodes, {
794
+ cols: null,
795
+ allowed: false
796
+ }))out.push(_extends({}, block, {
797
+ col
798
+ }));
799
+ });
800
+ } else {
801
+ // A second row of a section, or one inside a column: its cells one under another.
802
+ for (const cell of node.cells)out.push(...nodeBlocks(scope, cell.nodes, {
803
+ cols: null,
804
+ allowed: false
805
+ }));
806
+ }
807
+ break;
808
+ }
809
+ case 'group':
810
+ out.push(...groupBlocks(scope, node));
811
+ break;
812
+ case 'table':
813
+ {
814
+ const rows = node.rows.map((tableRow)=>({
815
+ cells: tableRow.cells.map((cell)=>cell.lines.map((ref)=>take(scope, ref)).filter(Boolean).join(' '))
816
+ }));
817
+ if (rows.some((tableRow)=>tableRow.cells.some(Boolean))) out.push({
818
+ kind: 'table',
819
+ header: node.header,
820
+ rows
821
+ });
822
+ break;
823
+ }
824
+ case 'tabs':
825
+ {
826
+ const tabs = node.tabs.map((tab)=>_extends({
827
+ label: tab.label === null ? '' : take(scope, tab.label)
828
+ }, tab.selected ? {
829
+ selected: true
830
+ } : {}, {
831
+ blocks: mergeLists(nodeBlocks(scope, tab.panel, {
832
+ cols: null,
833
+ allowed: false
834
+ }))
835
+ }));
836
+ if (tabs.some((tab)=>tab.label || tab.blocks.length)) out.push({
837
+ kind: 'tabs',
838
+ tabs
839
+ });
840
+ break;
841
+ }
842
+ default:
843
+ break;
844
+ }
845
+ }
846
+ return out;
847
+ }
848
+ /** The most words beside a lone picture that make a row a byline: a name, a date, a role. */ const BYLINE_MAX_WORDS = 12;
849
+ /**
850
+ * A row that is a byline (AGL-4107): one cell a lone picture, the other a few
851
+ * words — a writer's portrait beside their name and date. Drawn as columns it
852
+ * set a portrait across half the page; it is the portrait, small, and its words.
853
+ */ function bylineOf(scope, cells) {
854
+ if (cells.length !== 2) return null;
855
+ const at = cells.findIndex((cell)=>cell.nodes.length === 1 && cell.nodes[0].kind === 'picture');
856
+ if (at === -1) return null;
857
+ const words = cells[1 - at].nodes;
858
+ if (!words.every((node)=>node.kind === 'line')) return null;
859
+ const count = words.reduce((total, node)=>{
860
+ const block = scope.doc.blocks[node.line];
861
+ return total + (block && block.kind !== 'image' ? aiSiteImportWordsIn(clean(block.text)) : 0);
862
+ }, 0);
863
+ if (count === 0 || count > BYLINE_MAX_WORDS) return null;
864
+ const node = cells[at].nodes[0];
865
+ const picture = takePicture(scope, node.line, node.button ? undefined : node.href);
866
+ return [
867
+ ...picture ? [
868
+ {
869
+ kind: 'image',
870
+ picture: _extends({}, picture, {
871
+ small: true
872
+ })
873
+ }
874
+ ] : [],
875
+ ...nodeBlocks(scope, words, {
876
+ cols: null,
877
+ allowed: false
878
+ })
879
+ ];
880
+ }
881
+ /** Paragraphs past which a section is reading copy, set at the start whatever its source centers. */ const READING_PARAGRAPHS = 4;
882
+ /** Neighboring one-line lists of one column as one list, as the source's list items read. */ function mergeLists(blocks) {
883
+ const out = [];
884
+ for (const block of blocks){
885
+ const last = out[out.length - 1];
886
+ const plain = (entry)=>{
887
+ var _entry_items;
888
+ return (entry == null ? void 0 : entry.kind) === 'list' && entry.columns === undefined && !((_entry_items = entry.items) == null ? void 0 : _entry_items.some((item)=>item.iconPath));
889
+ };
890
+ if (plain(block) && plain(last) && (last == null ? void 0 : last.col) === block.col) {
891
+ var _last_items, _block_items;
892
+ out[out.length - 1] = _extends({}, last, {
893
+ items: [
894
+ ...(_last_items = last.items) != null ? _last_items : [],
895
+ ...(_block_items = block.items) != null ? _block_items : []
896
+ ]
897
+ });
898
+ continue;
899
+ }
900
+ out.push(block);
901
+ }
902
+ return out;
903
+ }
904
+ /** The hero a section's opening takes: the first section's, around its picture beside, behind or under its words. */ function heroOf(section, index, answer) {
905
+ var _ref;
906
+ var _section_hero;
907
+ if (index !== 0 || !((_section_hero = section.hero) == null ? void 0 : _section_hero.picture)) return undefined;
908
+ // An answer of "none" keeps the opening as the source lays it out.
909
+ if ((answer == null ? void 0 : answer.hero) === 'none') return undefined;
910
+ return (_ref = answer == null ? void 0 : answer.hero) != null ? _ref : HERO_FOR[section.hero.picture];
911
+ }
912
+ /**
913
+ * The page's sections in the layout language (AGL-4107): the source's layout,
914
+ * each section in its own band (or the answer's, where the source gave none),
915
+ * its row, its groups and their items, its tables, tabs and pictures, every
916
+ * line taken exactly once and every line the layout placed nowhere added at
917
+ * its section's end.
918
+ */ export function aiSiteImportDesignedSections(doc, answer, options = {}) {
919
+ var _options_formId;
920
+ const layout = aiSiteImportLayoutOf(doc);
921
+ const scope = {
922
+ doc,
923
+ layout,
924
+ used: new Set(),
925
+ missing: new Set(),
926
+ photos: new Map(doc.photos.flatMap((photo)=>photo.from ? [
927
+ [
928
+ photo.from,
929
+ photo
930
+ ]
931
+ ] : [])),
932
+ pathTo: new Map(doc.links.map((link)=>[
933
+ link.path.replace(/\/+$/, '') || '/',
934
+ link.id
935
+ ])),
936
+ formId: (_options_formId = options.formId) != null ? _options_formId : null,
937
+ formPlaced: false,
938
+ answer: null,
939
+ groupAt: 0,
940
+ settled: [],
941
+ at: ''
942
+ };
943
+ const sections = layout.sections.map((section, index)=>{
944
+ var _ref, _ref1;
945
+ const given = (_ref = answer == null ? void 0 : answer.sections[index]) != null ? _ref : null;
946
+ scope.answer = given;
947
+ scope.groupAt = 0;
948
+ scope.at = `sections[${index}]`;
949
+ const row = {
950
+ cols: null,
951
+ allowed: true
952
+ };
953
+ const blocks = nodeBlocks(scope, section.nodes, row);
954
+ // Every line the layout placed nowhere, at the section's end, as its kind says.
955
+ for (const ref of section.lines){
956
+ if (scope.used.has(ref)) continue;
957
+ const block = doc.blocks[ref];
958
+ if (!isLine(block)) continue;
959
+ blocks.push(...lineBlocks(scope, {
960
+ kind: 'line',
961
+ line: ref
962
+ }));
963
+ }
964
+ const hero = heroOf(section, index, given);
965
+ // A band only where the source gave the section none of its own.
966
+ const band = section.background === null && !section.backdrop && (given == null ? void 0 : given.band) ? given.band : section.band;
967
+ // A long read is set at the start, as text is read, whatever its source centers.
968
+ const reading = blocks.filter((block)=>block.kind === 'text').length > READING_PARAGRAPHS;
969
+ return _extends({
970
+ band,
971
+ align: (_ref1 = given == null ? void 0 : given.align) != null ? _ref1 : reading ? 'start' : section.align
972
+ }, row.cols && blocks.some((block)=>block.col !== undefined) ? {
973
+ cols: row.cols
974
+ } : {}, {
975
+ blocks: mergeLists(blocks)
976
+ }, hero ? {
977
+ hero
978
+ } : {});
979
+ });
980
+ return {
981
+ sections,
982
+ used: scope.used,
983
+ missing: scope.missing,
984
+ settled: scope.settled
985
+ };
986
+ }
987
+ /**
988
+ * The page brought in, built by the site compiler (AGL-4107): every line on
989
+ * it exactly once, every word as its source wrote it, laid out as its source
990
+ * lays it out and drawn in the site's own theme.
991
+ */ export function aiCompileImportedPage(input) {
992
+ var _input_answer;
993
+ const { doc } = input;
994
+ const formId = input.targets.forms.length === 1 ? input.targets.forms[0].id : null;
995
+ const designed = aiSiteImportDesignedSections(doc, (_input_answer = input.answer) != null ? _input_answer : null, {
996
+ formId
997
+ });
998
+ const compile = (sections)=>aiCompileLayoutPage(sections, input.plan, input.targets, _extends({
999
+ // A page brought in draws its repeats as its source does (AGL-4107).
1000
+ reusableComponents: false,
1001
+ sectionIds: input.sectionIds,
1002
+ verbatim: true
1003
+ }, input.design ? {
1004
+ design: input.design
1005
+ } : {}, input.language ? {
1006
+ language: input.language
1007
+ } : {}));
1008
+ let sections = designed.sections;
1009
+ let compiled = compile(sections);
1010
+ let cut = false;
1011
+ // A page past what one page may weigh keeps its last sections by name.
1012
+ for(let last = sections.length - 1; Object.keys(compiled.tree.nodes).length > AI_SITE_IMPORT_DOC_MAX_NODES && last > 0; last -= 1){
1013
+ cut = true;
1014
+ sections = sections.map((section, index)=>index >= last ? _extends({}, section, {
1015
+ blocks: [],
1016
+ cols: undefined
1017
+ }) : section);
1018
+ compiled = compile(sections);
1019
+ }
1020
+ const dropped = new Set();
1021
+ if (cut) {
1022
+ const layout = aiSiteImportLayoutOf(doc);
1023
+ layout.sections.forEach((section, index)=>{
1024
+ var _sections_index;
1025
+ if (!((_sections_index = sections[index]) == null ? void 0 : _sections_index.blocks.length)) for (const ref of section.lines)dropped.add(ref);
1026
+ });
1027
+ }
1028
+ const lineRefs = doc.blocks.flatMap((block, index)=>isLine(block) ? [
1029
+ index
1030
+ ] : []);
1031
+ const placed = lineRefs.filter((ref)=>designed.used.has(ref) && !designed.missing.has(ref) && !dropped.has(ref));
1032
+ const wordsOf = (ref)=>{
1033
+ const block = doc.blocks[ref];
1034
+ return block && block.kind !== 'image' ? aiSiteImportWordsIn(clean(block.text)) : 0;
1035
+ };
1036
+ return {
1037
+ tree: compiled.tree,
1038
+ sectionRoots: compiled.sectionRoots,
1039
+ sections,
1040
+ placed,
1041
+ lines: lineRefs.length,
1042
+ words: placed.reduce((total, ref)=>total + wordsOf(ref), 0),
1043
+ sourceWords: lineRefs.reduce((total, ref)=>total + wordsOf(ref), 0),
1044
+ cut,
1045
+ settled: [
1046
+ ...designed.settled,
1047
+ ...compiled.settled
1048
+ ]
1049
+ };
1050
+ }
1051
+ /*------------------------------------------
1052
+ * What the design answer is shown
1053
+ *-----------------------------------------*/ /** The most characters of a page's structure a design prompt shows. */ export const AI_SITE_IMPORT_DESIGN_MAX_CHARS = 12000;
1054
+ /** What a line is, as the design answer reads its place. */ function lineKind(block, node) {
1055
+ var _node_role;
1056
+ if (!block) return 'line';
1057
+ if (block.kind === 'heading') return 'heading';
1058
+ if (block.kind === 'link') return node.button ? `${node.button === 'primary' ? 'filled' : node.button === 'secondary' ? 'outlined' : 'quiet'} button` : 'link';
1059
+ if (block.kind === 'list-item') return 'list item';
1060
+ if (block.kind === 'quote') return 'quote';
1061
+ if (block.kind === 'image') return 'picture';
1062
+ return (_node_role = node.role) != null ? _node_role : 'text';
1063
+ }
1064
+ /** What an item of a group shows, as one phrase: "icon + title + text + link". */ function itemShape(item) {
1065
+ var _item_text, _item_list, _item_link;
1066
+ return [
1067
+ item.image !== undefined ? 'picture' : '',
1068
+ item.icon !== undefined ? 'icon' : '',
1069
+ item.figure !== undefined ? 'figure' : '',
1070
+ item.title !== undefined ? 'title' : '',
1071
+ ((_item_text = item.text) == null ? void 0 : _item_text.length) ? 'text' : '',
1072
+ ((_item_list = item.list) == null ? void 0 : _item_list.length) ? `${item.listIcon !== undefined ? 'checked ' : ''}list` : '',
1073
+ ((_item_link = item.link) == null ? void 0 : _item_link.length) ? 'link' : ''
1074
+ ].filter(Boolean);
1075
+ }
1076
+ /** A run of one kind of part counted: "2 buttons", not "button, button". */ function counted(parts) {
1077
+ const out = [];
1078
+ for(let at = 0; at < parts.length;){
1079
+ let run = 1;
1080
+ while(parts[at + run] === parts[at])run += 1;
1081
+ out.push(run > 1 ? `${run} ${parts[at]}s` : parts[at]);
1082
+ at += run;
1083
+ }
1084
+ return out.join(', ');
1085
+ }
1086
+ /** A section's nodes as the design answer reads them, its groups numbered in order. */ function describeNodes(doc, nodes, groups) {
1087
+ const parts = [];
1088
+ for (const node of nodes){
1089
+ switch(node.kind){
1090
+ case 'line':
1091
+ parts.push(lineKind(doc.blocks[node.line], node));
1092
+ break;
1093
+ case 'picture':
1094
+ parts.push('picture');
1095
+ break;
1096
+ case 'form':
1097
+ parts.push('form');
1098
+ break;
1099
+ case 'row':
1100
+ parts.push(`a row of ${node.cells.length} columns (${node.cells.map((cell, index)=>`column ${index + 1}: ${describeNodes(doc, cell.nodes, groups).join(', ') || 'empty'}`).join('; ')})`);
1101
+ break;
1102
+ case 'group':
1103
+ {
1104
+ groups.count += 1;
1105
+ const shape = [
1106
+ ...new Set(node.items.map((item)=>itemShape(item).join(' + ')).filter(Boolean))
1107
+ ].slice(0, 2).join(' or ');
1108
+ parts.push(`group ${groups.count}: ${node.items.length} ${node.group} in ${node.columns} column${node.columns === 1 ? '' : 's'}${node.boxed ? ', boxed' : ''}${shape ? `, each ${shape}` : ''}`);
1109
+ break;
1110
+ }
1111
+ case 'table':
1112
+ parts.push(`a table of ${node.rows.length} rows`);
1113
+ break;
1114
+ case 'tabs':
1115
+ parts.push(`${node.tabs.length} tabs (${node.tabs.map((tab, index)=>`tab ${index + 1}: ${describeNodes(doc, tab.panel, groups).join(', ') || 'not drawn'}`).join('; ')})`);
1116
+ break;
1117
+ default:
1118
+ break;
1119
+ }
1120
+ }
1121
+ return parts;
1122
+ }
1123
+ /**
1124
+ * The page's structure as its design answer reads it (AGL-4107): each
1125
+ * section's name, band, alignment and what it shows, its groups numbered as
1126
+ * the answer names them. No word of the page is in it but its sections'
1127
+ * names, which are fenced where the prompt shows them.
1128
+ */ export function aiSiteImportDesignText(doc) {
1129
+ const layout = aiSiteImportLayoutOf(doc);
1130
+ const names = aiSiteImportSectionNames(doc);
1131
+ return layout.sections.map((section, index)=>{
1132
+ var _section_backdrop, _section_hero;
1133
+ const groups = {
1134
+ count: 0
1135
+ };
1136
+ const shows = describeNodes(doc, section.nodes, groups);
1137
+ const band = section.background === null && !section.backdrop ? 'no band of its own' : `${section.band} band${((_section_backdrop = section.backdrop) == null ? void 0 : _section_backdrop.kind) === 'gradient' ? ' with a gradient' : ''}`;
1138
+ const hero = index === 0 && ((_section_hero = section.hero) == null ? void 0 : _section_hero.picture) ? `; opens the page, its picture ${section.hero.picture} its words` : index === 0 ? '; opens the page, with no picture' : '';
1139
+ return `Section ${index + 1} "${names[index]}": ${band}, ${section.align === 'center' ? 'centered' : 'aligned to the start'}${hero}. Groups: ${groups.count}. Shows: ${counted(shows)}.`;
1140
+ }).join('\n');
1141
+ }
1142
+
1143
+ //# sourceMappingURL=ai-site-import-compile.js.map