@aglyn/plugins-ai 1.0.0-beta.237 → 1.0.0-beta.238
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +17 -17
- package/src/lib/components/ai-credits-prompt.component.d.ts +11 -4
- package/src/lib/components/ai-credits-prompt.component.js +84 -34
- package/src/lib/components/ai-credits-prompt.component.js.map +1 -1
- package/src/lib/components/ai-job-cancel.component.d.ts +38 -0
- package/src/lib/components/ai-job-cancel.component.js +156 -0
- package/src/lib/components/ai-job-cancel.component.js.map +1 -0
- package/src/lib/components/ai-job-plan.component.js +12 -1
- package/src/lib/components/ai-job-plan.component.js.map +1 -1
- package/src/lib/components/ai-jobs-list-page.component.js +141 -72
- package/src/lib/components/ai-jobs-list-page.component.js.map +1 -1
- package/src/lib/components/ai-site-build-page.component.js +22 -2
- package/src/lib/components/ai-site-build-page.component.js.map +1 -1
- package/src/lib/components/ai-usage-strip.component.d.ts +19 -2
- package/src/lib/components/ai-usage-strip.component.js +40 -9
- package/src/lib/components/ai-usage-strip.component.js.map +1 -1
- package/src/lib/components/assist-jobs-drawer.component.js +12 -16
- package/src/lib/components/assist-jobs-drawer.component.js.map +1 -1
- package/src/lib/components/assist-panel.component.js +87 -51
- package/src/lib/components/assist-panel.component.js.map +1 -1
- package/src/lib/jobs/ai-job-build-step.js +4 -1
- package/src/lib/jobs/ai-job-build-step.js.map +1 -1
- package/src/lib/jobs/ai-job-cancel.d.ts +71 -0
- package/src/lib/jobs/ai-job-cancel.js +102 -0
- package/src/lib/jobs/ai-job-cancel.js.map +1 -0
- package/src/lib/jobs/ai-job-component-step.js +4 -1
- package/src/lib/jobs/ai-job-component-step.js.map +1 -1
- package/src/lib/jobs/ai-job-drafts.js +2 -0
- package/src/lib/jobs/ai-job-drafts.js.map +1 -1
- package/src/lib/jobs/ai-job-form-step.js +4 -1
- package/src/lib/jobs/ai-job-form-step.js.map +1 -1
- package/src/lib/jobs/ai-job-layout-language.d.ts +2 -0
- package/src/lib/jobs/ai-job-layout-language.js +13 -1
- package/src/lib/jobs/ai-job-layout-language.js.map +1 -1
- package/src/lib/jobs/ai-job-layout-step.js +6 -1
- package/src/lib/jobs/ai-job-layout-step.js.map +1 -1
- package/src/lib/jobs/ai-job-page-language.js +8 -0
- package/src/lib/jobs/ai-job-page-language.js.map +1 -1
- package/src/lib/jobs/ai-job-page-step.js +12 -1
- package/src/lib/jobs/ai-job-page-step.js.map +1 -1
- package/src/lib/jobs/ai-job-plan-step.d.ts +20 -1
- package/src/lib/jobs/ai-job-plan-step.js +49 -9
- package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
- package/src/lib/jobs/ai-job-site-content.d.ts +15 -0
- package/src/lib/jobs/ai-job-site-content.js +66 -7
- package/src/lib/jobs/ai-job-site-content.js.map +1 -1
- package/src/lib/jobs/ai-job-site-step.js +4 -1
- package/src/lib/jobs/ai-job-site-step.js.map +1 -1
- package/src/lib/jobs/ai-job-template-step.js +4 -1
- package/src/lib/jobs/ai-job-template-step.js.map +1 -1
- package/src/lib/jobs/ai-jobs.d.ts +37 -12
- package/src/lib/jobs/ai-jobs.js +184 -28
- package/src/lib/jobs/ai-jobs.js.map +1 -1
- package/src/lib/jobs/ai-layout-stock-photos.d.ts +143 -70
- package/src/lib/jobs/ai-layout-stock-photos.js +649 -176
- package/src/lib/jobs/ai-layout-stock-photos.js.map +1 -1
- package/src/lib/jobs/fixtures/ai-ceramics-portfolio-site-plan.d.ts +30 -0
- package/src/lib/jobs/fixtures/ai-ceramics-portfolio-site-plan.js +220 -0
- package/src/lib/jobs/fixtures/ai-ceramics-portfolio-site-plan.js.map +1 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-pixabay-recordings.d.ts +27 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-pixabay-recordings.js +151 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-pixabay-recordings.js.map +1 -0
- package/src/lib/layout-language/ai-layout-compiler.d.ts +6 -0
- package/src/lib/layout-language/ai-layout-compiler.js +304 -8
- package/src/lib/layout-language/ai-layout-compiler.js.map +1 -1
- package/src/lib/layout-language/ai-layout-design.d.ts +6 -3
- package/src/lib/layout-language/ai-layout-design.js +5 -3
- package/src/lib/layout-language/ai-layout-design.js.map +1 -1
- package/src/lib/layout-language/ai-layout-frame.d.ts +12 -0
- package/src/lib/layout-language/ai-layout-frame.js +58 -3
- package/src/lib/layout-language/ai-layout-frame.js.map +1 -1
- package/src/lib/layout-language/ai-layout-listings.d.ts +13 -1
- package/src/lib/layout-language/ai-layout-listings.js +61 -46
- package/src/lib/layout-language/ai-layout-listings.js.map +1 -1
- package/src/lib/layout-language/ai-layout-pictures.d.ts +8 -0
- package/src/lib/layout-language/ai-layout-pictures.js.map +1 -1
- package/src/lib/layout-language/ai-layout-storefront.d.ts +82 -0
- package/src/lib/layout-language/ai-layout-storefront.js +223 -0
- package/src/lib/layout-language/ai-layout-storefront.js.map +1 -0
- package/src/lib/model/ai-build-job.d.ts +9 -1
- package/src/lib/model/ai-build-job.js +19 -5
- package/src/lib/model/ai-build-job.js.map +1 -1
- package/src/lib/model/ai-build-progress.d.ts +2 -2
- package/src/lib/model/ai-build-progress.js +6 -4
- package/src/lib/model/ai-build-progress.js.map +1 -1
- package/src/lib/model/ai-credit-estimate.d.ts +69 -2
- package/src/lib/model/ai-credit-estimate.js +91 -3
- package/src/lib/model/ai-credit-estimate.js.map +1 -1
- package/src/lib/model/ai-job-cancel-copy.d.ts +44 -0
- package/src/lib/model/ai-job-cancel-copy.js +53 -0
- package/src/lib/model/ai-job-cancel-copy.js.map +1 -0
- package/src/lib/model/ai-job-failure-copy.d.ts +4 -1
- package/src/lib/model/ai-job-failure-copy.js +8 -3
- package/src/lib/model/ai-job-failure-copy.js.map +1 -1
- package/src/lib/model/ai-jobs.types.d.ts +20 -0
- package/src/lib/model/ai-jobs.types.js.map +1 -1
- package/src/lib/model/ai-site-build-progress.js +7 -4
- package/src/lib/model/ai-site-build-progress.js.map +1 -1
- package/src/lib/model/ai-site-job.d.ts +19 -2
- package/src/lib/model/ai-site-job.js +29 -8
- package/src/lib/model/ai-site-job.js.map +1 -1
- package/src/lib/model/ai-site-kinds.d.ts +4 -1
- package/src/lib/model/ai-site-kinds.js +34 -6
- package/src/lib/model/ai-site-kinds.js.map +1 -1
- package/src/lib/runtime/ai-doctrine-validators.d.ts +37 -1
- package/src/lib/runtime/ai-doctrine-validators.js +151 -6
- package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
- package/src/lib/runtime/ai-doctrine.js +4 -3
- package/src/lib/runtime/ai-doctrine.js.map +1 -1
- package/src/lib/runtime/ai-palette.generated.js +53 -7
- package/src/lib/runtime/ai-palette.generated.js.map +1 -1
- package/src/lib/server/ai-jobs-cancel.d.ts +13 -5
- package/src/lib/server/ai-jobs-cancel.js +27 -9
- package/src/lib/server/ai-jobs-cancel.js.map +1 -1
- package/src/lib/server/ai-jobs-gate.d.ts +12 -2
- package/src/lib/server/ai-jobs-gate.js +20 -9
- package/src/lib/server/ai-jobs-gate.js.map +1 -1
- package/src/lib/usage/ai-usage-meter.d.ts +2 -1
- package/src/lib/usage/ai-usage-meter.js +17 -1
- package/src/lib/usage/ai-usage-meter.js.map +1 -1
- package/src/lib/usage/ai-usage-wire.d.ts +6 -0
- package/src/lib/usage/ai-usage-wire.js.map +1 -1
- package/src/lib/usage/assist-usage.d.ts +7 -0
- package/src/lib/usage/assist-usage.js +14 -4
- package/src/lib/usage/assist-usage.js.map +1 -1
- package/src/lib/usage/free-ai-credits-left.d.ts +7 -0
- package/src/lib/usage/free-ai-credits-left.js +4 -2
- package/src/lib/usage/free-ai-credits-left.js.map +1 -1
|
@@ -66,16 +66,19 @@ export type AiLayoutHeroVariant = 'cover' | 'split' | 'editorial';
|
|
|
66
66
|
* - `pictures`: a picture over each item's title and caption, in an uneven
|
|
67
67
|
* grid (work, products);
|
|
68
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)
|
|
69
|
+
* - `menu`: two columns of lines under rules (dishes, prices left out);
|
|
70
|
+
* - `icons`: open columns, each led by an icon in a soft disc over its title
|
|
71
|
+
* and words — a storefront's reasons to buy (shipping, small batch,
|
|
72
|
+
* returns), never a block of text (AGL-3676).
|
|
70
73
|
*/
|
|
71
|
-
export type AiLayoutGroupVariant = 'cards' | 'ruled' | 'pictures' | 'articles' | 'menu';
|
|
74
|
+
export type AiLayoutGroupVariant = 'cards' | 'ruled' | 'pictures' | 'articles' | 'menu' | 'icons';
|
|
72
75
|
/** How a numbered group is drawn: a row of numbers, or a ruled list beside its heading. */
|
|
73
76
|
export type AiLayoutStepsVariant = 'numbers' | 'timeline';
|
|
74
77
|
/** The site-level choices a page is drawn with. */
|
|
75
78
|
export interface AiLayoutDesignChoices {
|
|
76
79
|
hero: AiLayoutHeroVariant;
|
|
77
80
|
/** The group style of a section that names no subject the kind draws its own way. */
|
|
78
|
-
group: 'cards' | 'ruled';
|
|
81
|
+
group: 'cards' | 'ruled' | 'icons';
|
|
79
82
|
steps: AiLayoutStepsVariant;
|
|
80
83
|
/** Whether a section of words takes a photo beside it. */
|
|
81
84
|
features: boolean;
|
|
@@ -158,9 +158,11 @@ const pick = (next, list)=>list[Math.floor(next() * list.length) % list.length];
|
|
|
158
158
|
return {
|
|
159
159
|
hero,
|
|
160
160
|
// A store's reasons to buy — the making, the shipping, the care — read
|
|
161
|
-
// as open
|
|
162
|
-
// page's feature cards
|
|
163
|
-
|
|
161
|
+
// as open columns each led by an icon, the way a storefront theme draws
|
|
162
|
+
// them: never a SaaS page's feature cards, and never the plain columns
|
|
163
|
+
// of a title and a sentence the beta.237 Willow Wick home showed four
|
|
164
|
+
// bands of (AGL-3676).
|
|
165
|
+
group: family === 'retail' ? 'icons' : group,
|
|
164
166
|
steps: pick(next, [
|
|
165
167
|
'timeline',
|
|
166
168
|
'numbers',
|
|
@@ -1 +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 * How a blog's own posts are listed where a page shows them (AGL-3676):\n * `grid`, a cover over each post's date, title and excerpt in columns; or\n * `ruled`, a list under thin rules, the date in its own column and a small\n * cover beside each title — the two ways an editorial site lists writing.\n */\n writing: 'grid' | 'ruled'\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 const group = family === 'calm' ? pick(next, ['cards', 'cards', 'ruled'] as const) : pick(next, ['ruled', 'cards', 'ruled'] as const)\n return {\n hero,\n // A store's reasons to buy — the making, the shipping, the care — read\n // as open ruled columns beside its product photos, never as a SaaS\n // page's feature cards (AGL-3676).\n group: family === 'retail' ? 'ruled' : group,\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 // Drawn last, so every choice above stays what it was for a seed.\n writing: pick(next, ['grid', 'ruled'] 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\n// A store's own products are listed by its Product grid (AGL-3676), never drawn\n// from words: what a store shows as pictures is how its range is grouped — its\n// collections, gift sets, occasions and scents. \"Candle care and burn notes\" is no band of pictures.\nconst CATEGORIES = /\\b(collection|collections|range|ranges|categor(?:y|ies)|gift|gifts|gift sets?|sets|occasion|occasions|scent|scents|edit|edits)\\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' && CATEGORIES.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","writing","WORK","WRITING","DISHES","CATEGORIES","aiLayoutGroupVariant","choices","words","test"],"mappings":"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"}
|
|
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 * - `icons`: open columns, each led by an icon in a soft disc over its title\n * and words — a storefront's reasons to buy (shipping, small batch,\n * returns), never a block of text (AGL-3676).\n */\nexport type AiLayoutGroupVariant = 'cards' | 'ruled' | 'pictures' | 'articles' | 'menu' | 'icons'\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' | 'icons'\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 * How a blog's own posts are listed where a page shows them (AGL-3676):\n * `grid`, a cover over each post's date, title and excerpt in columns; or\n * `ruled`, a list under thin rules, the date in its own column and a small\n * cover beside each title — the two ways an editorial site lists writing.\n */\n writing: 'grid' | 'ruled'\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 const group = family === 'calm' ? pick(next, ['cards', 'cards', 'ruled'] as const) : pick(next, ['ruled', 'cards', 'ruled'] as const)\n return {\n hero,\n // A store's reasons to buy — the making, the shipping, the care — read\n // as open columns each led by an icon, the way a storefront theme draws\n // them: never a SaaS page's feature cards, and never the plain columns\n // of a title and a sentence the beta.237 Willow Wick home showed four\n // bands of (AGL-3676).\n group: family === 'retail' ? 'icons' : group,\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 // Drawn last, so every choice above stays what it was for a seed.\n writing: pick(next, ['grid', 'ruled'] 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\n// A store's own products are listed by its Product grid (AGL-3676), never drawn\n// from words: what a store shows as pictures is how its range is grouped — its\n// collections, gift sets, occasions and scents. \"Candle care and burn notes\" is no band of pictures.\nconst CATEGORIES = /\\b(collection|collections|range|ranges|categor(?:y|ies)|gift|gifts|gift sets?|sets|occasion|occasions|scent|scents|edit|edits)\\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' && CATEGORIES.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","writing","WORK","WRITING","DISHES","CATEGORIES","aiLayoutGroupVariant","choices","words","test"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;;;;;;;;;;;;;;;;;;;;;;;CA2BC,GAED,gEAAgE,GA+DhE,MAAMA,WAA6C;IACjDC,WAAW;IACXC,aAAa;IACbC,QAAQ;IACRC,MAAM;IACNC,YAAY;IACZC,QAAQ;IACRC,MAAM;IACNC,UAAU;IACVC,QAAQ;IACRC,OAAO;AACT;AAEA,wDAAwD,GACxD,MAAMC,SAAmE;IACvET,aAAa;QAAC;QAAS;KAAY;IACnCD,WAAW;QAAC;QAAa;KAAQ;IACjCE,QAAQ;QAAC;QAAa;KAAQ;IAC9BC,MAAM;QAAC;QAAa;QAAS;KAAQ;IACrCC,YAAY;QAAC;QAAS;KAAQ;IAC9BC,QAAQ;QAAC;QAAS;KAAQ;IAC1BM,SAAS;QAAC;QAAS;KAAQ;IAC3BC,OAAO;QAAC;QAAS;KAAY;IAC7BC,WAAW;QAAC;QAAS;KAAQ;IAC7BC,YAAY;QAAC;QAAS;KAAQ;IAC9BC,QAAQ;QAAC;QAAS;KAAQ;IAC1BT,MAAM;QAAC;QAAS;KAAQ;IACxBC,UAAU;QAAC;QAAS;KAAQ;IAC5BC,QAAQ;QAAC;QAAS;QAAS;KAAY;IACvCC,OAAO;QAAC;QAAS;KAAY;AAC/B;AACA,MAAMO,iBAAiD;IAAC;IAAS;IAAS;CAAY;AAEtF,8DAA8D,GAC9D,SAASC,OAAOC,IAAY;IAC1B,IAAIC,QAAQD,SAAS;IACrB,OAAO;QACLC,QAAQ,AAACA,QAAQ,eAAgB;QACjC,IAAIC,IAAID;QACRC,IAAIC,KAAKC,IAAI,CAACF,IAAKA,MAAM,IAAKA,IAAI;QAClCA,KAAKA,IAAIC,KAAKC,IAAI,CAACF,IAAKA,MAAM,GAAIA,IAAI;QACtC,OAAO,AAAC,CAAA,AAACA,CAAAA,IAAKA,MAAM,EAAE,MAAO,CAAA,IAAK;IACpC;AACF;AAEA,MAAMG,OAAO,CAAIC,MAAoBC,OAA0BA,IAAI,CAACJ,KAAKK,KAAK,CAACF,SAASC,KAAKE,MAAM,IAAIF,KAAKE,MAAM,CAAC;AAEnH,4CAA4C,GAC5C,OAAO,SAASC,eAAeC,IAAY;QAClC9B;IAAP,QAAOA,iBAAAA,QAAQ,CAAC8B,KAAK,YAAd9B,iBAAkB;AAC3B;AAEA;;;;CAIC,GACD,OAAO,SAAS+B,sBAAsBC,MAAsB;QAG3CrB;IAFf,MAAMc,OAAOP,OAAOc,OAAOb,IAAI,GAAG;IAClC,MAAMc,SAASJ,eAAeG,OAAOF,IAAI;IACzC,MAAMI,UAASvB,sBAAAA,MAAM,CAACqB,OAAOF,IAAI,CAAC,YAAnBnB,sBAAuBM;IACtC,MAAMkB,OAAOV,SAAS,OAAQS,MAAM,CAAC,EAAE,GAA2BV,KAAKC,MAAMS;IAC7E,MAAME,QAAQH,WAAW,SAAST,KAAKC,MAAM;QAAC;QAAS;QAAS;KAAQ,IAAaD,KAAKC,MAAM;QAAC;QAAS;QAAS;KAAQ;IAC3H,OAAO;QACLU;QACA,uEAAuE;QACvE,wEAAwE;QACxE,uEAAuE;QACvE,sEAAsE;QACtE,uBAAuB;QACvBC,OAAOH,WAAW,WAAW,UAAUG;QACvCC,OAAOb,KAAKC,MAAM;YAAC;YAAY;YAAW;SAAW;QACrDa,UAAU;QACVC,aAAad,SAAS;QACtBe,YAAYN,OAAOO,QAAQ,CAAC,YAAYhB,SAAS;QACjDiB,YAAYT,WAAW,eAAeA,WAAW,aAAaR,SAAS,MAAMA,SAAS;QACtFkB,UAAUX,OAAOF,IAAI,KAAK,gBAAgBN,KAAKC,MAAM;YAAC;YAAQ;SAAS,IAAaD,KAAKC,MAAM;YAAC;YAAU;YAAU;SAAO;QAC3H,kEAAkE;QAClEmB,SAASpB,KAAKC,MAAM;YAAC;YAAQ;SAAQ;IACvC;AACF;AAEA,MAAMoB,OAAO;AACb,MAAMC,UAAU;AAChB,MAAMC,SAAS;AACf,gFAAgF;AAChF,+EAA+E;AAC/E,qGAAqG;AACrG,MAAMC,aAAa;AAEnB;;;;;CAKC,GACD,OAAO,SAASC,qBACdjB,MAAsB,EACtBkB,OAA8B,EAC9BC,KAAa;IAEb,MAAMlB,SAASJ,eAAeG,OAAOF,IAAI;IACzC,IAAIG,WAAW,aAAcY,CAAAA,KAAKO,IAAI,CAACD,UAAUnB,OAAOF,IAAI,KAAK,aAAY,GAAI,OAAO;IACxF,IAAIG,WAAW,eAAea,QAAQM,IAAI,CAACD,QAAQ,OAAO;IAC1D,IAAIlB,WAAW,iBAAiBc,OAAOK,IAAI,CAACD,QAAQ,OAAO;IAC3D,IAAIlB,WAAW,YAAYe,WAAWI,IAAI,CAACD,QAAQ,OAAO;IAC1D,OAAOD,QAAQd,KAAK;AACtB"}
|
|
@@ -50,7 +50,19 @@ export interface AiLayoutFramePlan {
|
|
|
50
50
|
* from every page — beside the navigation.
|
|
51
51
|
*/
|
|
52
52
|
cart?: boolean;
|
|
53
|
+
/**
|
|
54
|
+
* A selling store's Shop page (AGL-3676), by its id. Its header is a
|
|
55
|
+
* storefront's: the cart beside the navigation and no call-to-action button
|
|
56
|
+
* repeating a link the navigation already has, and a thin announcement bar
|
|
57
|
+
* above it — the line the design gave its header as a note, else one that
|
|
58
|
+
* opens the shop — linking the Shop page.
|
|
59
|
+
*/
|
|
60
|
+
shopId?: string | null;
|
|
53
61
|
}
|
|
62
|
+
/** What a store's announcement bar says where its design gave it no line (AGL-3676): true of every store. */
|
|
63
|
+
export declare const AI_LAYOUT_STORE_ANNOUNCEMENT = "Shop the full collection";
|
|
64
|
+
/** The way into the shop beside the design's own announcement (AGL-3676). */
|
|
65
|
+
export declare const AI_LAYOUT_STORE_ANNOUNCEMENT_LINK = "Shop now";
|
|
54
66
|
export interface AiLayoutCompiledFrame {
|
|
55
67
|
tree: AiLayoutRawTree;
|
|
56
68
|
settled: AiLayoutSettlement[];
|
|
@@ -21,6 +21,8 @@ import { AI_LAYOUT_CART_ELEMENT } from "./ai-layout-listings.js";
|
|
|
21
21
|
import { AI_LAYOUT_MAX_ITEMS } from "./ai-layout-language.js";
|
|
22
22
|
import { aiLayoutRenamedLabel, aiLayoutResolveLink } from "./ai-layout-links.js";
|
|
23
23
|
import { AiLayoutTreeBuilder } from "./ai-layout-tree.js";
|
|
24
|
+
/** What a store's announcement bar says where its design gave it no line (AGL-3676): true of every store. */ export const AI_LAYOUT_STORE_ANNOUNCEMENT = 'Shop the full collection';
|
|
25
|
+
/** The way into the shop beside the design's own announcement (AGL-3676). */ export const AI_LAYOUT_STORE_ANNOUNCEMENT_LINK = 'Shop now';
|
|
24
26
|
/** The header's colors by band, as palette roles. */ function headerLook(band) {
|
|
25
27
|
if (band === 'brand') return {
|
|
26
28
|
props: {
|
|
@@ -182,18 +184,31 @@ import { AiLayoutTreeBuilder } from "./ai-layout-tree.js";
|
|
|
182
184
|
}, pages.map((entry)=>navLink(entry, 'navLink')), 'nav') : null;
|
|
183
185
|
// The design's call to action: its first button, where it goes somewhere.
|
|
184
186
|
const ctaBlock = header.blocks.find((block)=>block.kind === 'button');
|
|
187
|
+
// A store's announcement: the first short line the design gave its header (AGL-3676).
|
|
188
|
+
const store = !!plan.shopId;
|
|
189
|
+
const noteBlock = store ? header.blocks.find((block)=>block.kind === 'note' || block.kind === 'text' || block.kind === 'lede') : undefined;
|
|
185
190
|
for (const block of header.blocks){
|
|
186
|
-
if (block !== ctaBlock) page.settled.push({
|
|
191
|
+
if (block !== ctaBlock && block !== noteBlock) page.settled.push({
|
|
187
192
|
at: 'header',
|
|
188
193
|
what: `a ${block.kind} in the header; left out`
|
|
189
194
|
});
|
|
190
195
|
}
|
|
191
196
|
const ctaLabel = ctaBlock ? aiLayoutFitText(ctaBlock.text, 'label') : '';
|
|
192
|
-
const
|
|
193
|
-
if (ctaBlock && !
|
|
197
|
+
const ctaResolved = ctaBlock && ctaLabel ? aiLayoutResolveLink(ctaBlock.to, ctaLabel, scope.link, page.targets) : null;
|
|
198
|
+
if (ctaBlock && !ctaResolved) page.settled.push({
|
|
194
199
|
at: 'header',
|
|
195
200
|
what: `the button "${ctaLabel}" has nowhere to go; left out`
|
|
196
201
|
});
|
|
202
|
+
// A storefront's header is its navigation and its cart (AGL-3676): the
|
|
203
|
+
// live Willow Wick header's "Shop candles" button repeated its nav's Shop.
|
|
204
|
+
// A store's button that only repeats a page the navigation links is left
|
|
205
|
+
// out, and a store that sells carries its cart in that place instead.
|
|
206
|
+
const repeatsNav = (ctaResolved == null ? void 0 : ctaResolved.kind) === 'page' && pages.some((entry)=>!entry.href && entry.id === ctaResolved.screenId);
|
|
207
|
+
const ctaTo = ctaResolved && store && (plan.cart || repeatsNav) ? null : ctaResolved;
|
|
208
|
+
if (ctaResolved && !ctaTo) page.settled.push({
|
|
209
|
+
at: 'header',
|
|
210
|
+
what: `the button "${ctaLabel}" left out: a store's header carries its ${plan.cart ? 'cart' : 'navigation'}, not a button repeating it`
|
|
211
|
+
});
|
|
197
212
|
const ctaProps = ctaTo ? _extends({
|
|
198
213
|
children: aiLayoutRenamedLabel(ctaLabel, ctaTo),
|
|
199
214
|
variant: 'contained',
|
|
@@ -290,6 +305,45 @@ import { AiLayoutTreeBuilder } from "./ai-layout-tree.js";
|
|
|
290
305
|
}), look.sx, [
|
|
291
306
|
toolbar
|
|
292
307
|
], 'header');
|
|
308
|
+
// A selling store's announcement bar, above the header, linking its shop (AGL-3676).
|
|
309
|
+
const announcement = store && plan.cart && plan.shopId ? (()=>{
|
|
310
|
+
const said = aiLayoutFitText(noteBlock == null ? void 0 : noteBlock.text, 'note');
|
|
311
|
+
if (noteBlock && !said) page.settled.push({
|
|
312
|
+
at: 'header',
|
|
313
|
+
what: 'an announcement with no words left; the shop’s own line instead'
|
|
314
|
+
});
|
|
315
|
+
// The design's line, then the way into the shop; else the way in alone.
|
|
316
|
+
const link = tree.add('muiScreenLink', {
|
|
317
|
+
children: said ? AI_LAYOUT_STORE_ANNOUNCEMENT_LINK : AI_LAYOUT_STORE_ANNOUNCEMENT,
|
|
318
|
+
screenId: plan.shopId,
|
|
319
|
+
renderAs: 'link',
|
|
320
|
+
color: 'inherit'
|
|
321
|
+
}, {
|
|
322
|
+
fontWeight: 700
|
|
323
|
+
}, null, 'announcementLink');
|
|
324
|
+
const words = said ? tree.add('muiTypography', {
|
|
325
|
+
children: said,
|
|
326
|
+
variant: 'body2',
|
|
327
|
+
component: 'p'
|
|
328
|
+
}, null, null, 'announcementText') : null;
|
|
329
|
+
return tree.add('muiStack', {
|
|
330
|
+
direction: 'row',
|
|
331
|
+
spacing: '1.5',
|
|
332
|
+
justifyContent: 'center',
|
|
333
|
+
alignItems: 'baseline',
|
|
334
|
+
useFlexGap: true,
|
|
335
|
+
flexWrap: 'wrap'
|
|
336
|
+
}, {
|
|
337
|
+
bgcolor: 'primary.main',
|
|
338
|
+
color: 'primary.contrastText',
|
|
339
|
+
typography: 'body2',
|
|
340
|
+
py: 1,
|
|
341
|
+
px: 2
|
|
342
|
+
}, [
|
|
343
|
+
words,
|
|
344
|
+
link
|
|
345
|
+
], 'announcement');
|
|
346
|
+
})() : null;
|
|
293
347
|
// The page grows into the room the window leaves, so a short page keeps
|
|
294
348
|
// its footer at the bottom.
|
|
295
349
|
const slot = tree.add('layoutSlot', {
|
|
@@ -301,6 +355,7 @@ import { AiLayoutTreeBuilder } from "./ai-layout-tree.js";
|
|
|
301
355
|
const column = tree.add('muiStack', null, {
|
|
302
356
|
minHeight: '100vh'
|
|
303
357
|
}, [
|
|
358
|
+
announcement,
|
|
304
359
|
bar,
|
|
305
360
|
slot,
|
|
306
361
|
footer
|
|
@@ -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 { AI_LAYOUT_CART_ELEMENT } from './ai-layout-listings'\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 aiLayoutRenamedLabel,\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 * Whether the site sells (AGL-3676): its header carries the store's cart\n * button — the commerce plugin's Cart, which opens the cart in a drawer\n * from every page — beside the navigation.\n */\n cart?: 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 quotesOnly: [],\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: aiLayoutRenamedLabel(ctaLabel, ctaTo),\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 // A store's cart, on every page, where a shopper looks for it (AGL-3676).\n const cart = plan.cart\n ? tree.add(AI_LAYOUT_CART_ELEMENT, { variant: 'button' }, { flexShrink: 0 }, null, 'cart')\n : null\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, cart, 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_CART_ELEMENT","AI_LAYOUT_MAX_ITEMS","aiLayoutRenamedLabel","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","quotesOnly","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","cart","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":"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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 { AI_LAYOUT_CART_ELEMENT } from './ai-layout-listings'\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 aiLayoutRenamedLabel,\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 * Whether the site sells (AGL-3676): its header carries the store's cart\n * button — the commerce plugin's Cart, which opens the cart in a drawer\n * from every page — beside the navigation.\n */\n cart?: boolean\n /**\n * A selling store's Shop page (AGL-3676), by its id. Its header is a\n * storefront's: the cart beside the navigation and no call-to-action button\n * repeating a link the navigation already has, and a thin announcement bar\n * above it — the line the design gave its header as a note, else one that\n * opens the shop — linking the Shop page.\n */\n shopId?: string | null\n}\n\n/** What a store's announcement bar says where its design gave it no line (AGL-3676): true of every store. */\nexport const AI_LAYOUT_STORE_ANNOUNCEMENT = 'Shop the full collection'\n\n/** The way into the shop beside the design's own announcement (AGL-3676). */\nexport const AI_LAYOUT_STORE_ANNOUNCEMENT_LINK = 'Shop now'\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 quotesOnly: [],\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 // A store's announcement: the first short line the design gave its header (AGL-3676).\n const store = !!plan.shopId\n const noteBlock = store ? header.blocks.find((block) => block.kind === 'note' || block.kind === 'text' || block.kind === 'lede') : undefined\n for (const block of header.blocks) {\n if (block !== ctaBlock && block !== noteBlock)\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 ctaResolved =\n ctaBlock && ctaLabel\n ? aiLayoutResolveLink(ctaBlock.to, ctaLabel, scope.link, page.targets)\n : null\n if (ctaBlock && !ctaResolved)\n page.settled.push({\n at: 'header',\n what: `the button \"${ctaLabel}\" has nowhere to go; left out`,\n })\n // A storefront's header is its navigation and its cart (AGL-3676): the\n // live Willow Wick header's \"Shop candles\" button repeated its nav's Shop.\n // A store's button that only repeats a page the navigation links is left\n // out, and a store that sells carries its cart in that place instead.\n const repeatsNav =\n ctaResolved?.kind === 'page' && pages.some((entry) => !entry.href && entry.id === ctaResolved.screenId)\n const ctaTo = ctaResolved && store && (plan.cart || repeatsNav) ? null : ctaResolved\n if (ctaResolved && !ctaTo)\n page.settled.push({\n at: 'header',\n what: `the button \"${ctaLabel}\" left out: a store's header carries its ${plan.cart ? 'cart' : 'navigation'}, not a button repeating it`,\n })\n const ctaProps = ctaTo\n ? {\n children: aiLayoutRenamedLabel(ctaLabel, ctaTo),\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 // A store's cart, on every page, where a shopper looks for it (AGL-3676).\n const cart = plan.cart\n ? tree.add(AI_LAYOUT_CART_ELEMENT, { variant: 'button' }, { flexShrink: 0 }, null, 'cart')\n : null\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, cart, 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 // A selling store's announcement bar, above the header, linking its shop (AGL-3676).\n const announcement =\n store && plan.cart && plan.shopId\n ? (() => {\n const said = aiLayoutFitText(noteBlock?.text, 'note')\n if (noteBlock && !said) page.settled.push({ at: 'header', what: 'an announcement with no words left; the shop’s own line instead' })\n // The design's line, then the way into the shop; else the way in alone.\n const link = tree.add(\n 'muiScreenLink',\n {\n children: said ? AI_LAYOUT_STORE_ANNOUNCEMENT_LINK : AI_LAYOUT_STORE_ANNOUNCEMENT,\n screenId: plan.shopId,\n renderAs: 'link',\n color: 'inherit',\n },\n { fontWeight: 700 },\n null,\n 'announcementLink',\n )\n const words = said\n ? tree.add('muiTypography', { children: said, variant: 'body2', component: 'p' }, null, null, 'announcementText')\n : null\n return tree.add(\n 'muiStack',\n { direction: 'row', spacing: '1.5', justifyContent: 'center', alignItems: 'baseline', useFlexGap: true, flexWrap: 'wrap' },\n { bgcolor: 'primary.main', color: 'primary.contrastText', typography: 'body2', py: 1, px: 2 },\n [words, link],\n 'announcement',\n )\n })()\n : null\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 [announcement, 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_CART_ELEMENT","AI_LAYOUT_MAX_ITEMS","aiLayoutRenamedLabel","aiLayoutResolveLink","AiLayoutTreeBuilder","AI_LAYOUT_STORE_ANNOUNCEMENT","AI_LAYOUT_STORE_ANNOUNCEMENT_LINK","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","quotesOnly","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","store","shopId","noteBlock","undefined","push","what","ctaLabel","ctaResolved","to","repeatsNav","some","ctaTo","cart","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","announcement","said","words","useFlexGap","flexWrap","py","px","slot","footer","compileFooter","column","prune","rootId","enabled","trigger","event","everyTime","steps","type","asked","closesDark","navItems","linksPages","flatMap","given","notice","item","items","has","pageLinks","cols","columns","col","Math","min","weights","filled","weight","top","copyright","bottom","pt","borderTop","borderColor","stack","container","element","colorScheme"],"mappings":"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"}
|
|
@@ -44,6 +44,16 @@
|
|
|
44
44
|
* music site, placed EMPTY: its "add your tracks" state, for the owner to
|
|
45
45
|
* fill from their own media library. No job ever sources a recording, so
|
|
46
46
|
* a tracks listing has no records and the player no source.
|
|
47
|
+
* - `reviews` — a selling store's own customer reviews, through the commerce
|
|
48
|
+
* plugin's Product reviews in its store-wide scope (`product-reviews`,
|
|
49
|
+
* AGL-3676): approved reviews across the catalog with their star ratings.
|
|
50
|
+
* A store that opens has none, and the element then shows nothing at all
|
|
51
|
+
* on the published page, so no review is ever written for a shopper who
|
|
52
|
+
* did not leave one (the FTC's fake-review rule; rule 14's "never invent
|
|
53
|
+
* a customer").
|
|
54
|
+
* - `signup` — a selling store's newsletter sign-up, through the commerce
|
|
55
|
+
* plugin's Newsletter signup (`newsletter-signup`, AGL-3676): a consented
|
|
56
|
+
* email field into the store's contacts, where a section says newsletter.
|
|
47
57
|
*
|
|
48
58
|
* The elements are named by their persisted ids, as the form binding names
|
|
49
59
|
* `form`: a plugin never imports another's internals, and
|
|
@@ -52,7 +62,7 @@
|
|
|
52
62
|
*
|
|
53
63
|
* Model only: shapes and the inputs reader; the compiler draws them.
|
|
54
64
|
*/
|
|
55
|
-
export type AiLayoutListingKind = 'products' | 'posts' | 'tracks';
|
|
65
|
+
export type AiLayoutListingKind = 'products' | 'posts' | 'tracks' | 'reviews' | 'signup';
|
|
56
66
|
/** How a section shows a listing: a few of its records on a page about something else, or all of them on its own page. */
|
|
57
67
|
export type AiLayoutListingRole = 'featured' | 'index';
|
|
58
68
|
/** One section of one page that places a listing. */
|
|
@@ -105,6 +115,8 @@ export declare const AI_LAYOUT_FEATURED_RECORDS: {
|
|
|
105
115
|
readonly products: 4;
|
|
106
116
|
readonly posts: 3;
|
|
107
117
|
readonly tracks: 0;
|
|
118
|
+
readonly reviews: 3;
|
|
119
|
+
readonly signup: 0;
|
|
108
120
|
};
|
|
109
121
|
/** A site's listings as its unit inputs carry them; nothing that does not read is kept. */
|
|
110
122
|
export declare function aiLayoutListingsOf(inputs: Readonly<Record<string, unknown>> | null | undefined): AiLayoutListing[];
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
1
2
|
/**
|
|
2
3
|
* @license
|
|
3
4
|
* Copyright 2026 Aglyn LLC
|
|
@@ -13,44 +14,7 @@
|
|
|
13
14
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
15
|
* See the License for the specific language governing permissions and
|
|
15
16
|
* limitations under the License.
|
|
16
|
-
*/
|
|
17
|
-
* A SITE'S OWN RECORDS, PLACED BY THE PLATFORM (AGL-3676).
|
|
18
|
-
*
|
|
19
|
-
* A guided start writes a store's first products and a blog's first posts,
|
|
20
|
-
* then built the pages around them in the layout language — and the pages
|
|
21
|
-
* showed neither. "Featured candles" and "The candle range" on the live
|
|
22
|
-
* Ember & Wick start (2026-10-08) were cards the model wrote, a name and a
|
|
23
|
-
* line each: no photo, no price, no link, nothing a shopper could open; the
|
|
24
|
-
* Clay Notes blog's "Featured writing" was three cards that linked to no
|
|
25
|
-
* post. The real records were on the site, one click away from nowhere.
|
|
26
|
-
*
|
|
27
|
-
* A LISTING is one kind of record a site keeps — its catalog, its blog —
|
|
28
|
-
* placed by code in the sections whose plan says they show it, through the
|
|
29
|
-
* element the plugin that keeps the records renders them with, bound to the
|
|
30
|
-
* records themselves rather than to words about them. It is the form binding
|
|
31
|
-
* one step on (a section whose plan `uses` a form places that form by its
|
|
32
|
-
* id, whatever the answer remembered): the model writes the section's
|
|
33
|
-
* heading and words, the platform places the records.
|
|
34
|
-
*
|
|
35
|
-
* - `products` — the commerce plugin's Product grid (`product-grid`), over
|
|
36
|
-
* the site's whole active catalog: a photo, the name, the price (or
|
|
37
|
-
* "Price coming soon" where the owner cleared it), each card linking its
|
|
38
|
-
* product's page.
|
|
39
|
-
* - `posts` — the content plugin's Collection Entries (`collectionEntries`)
|
|
40
|
-
* over the blog's collection, its card a cover, the date and byline, the
|
|
41
|
-
* title and the excerpt, linking each post.
|
|
42
|
-
* - `tracks` — the music plugin's Music player (`musicPlayer`, AGL-3716) on a
|
|
43
|
-
* music site, placed EMPTY: its "add your tracks" state, for the owner to
|
|
44
|
-
* fill from their own media library. No job ever sources a recording, so
|
|
45
|
-
* a tracks listing has no records and the player no source.
|
|
46
|
-
*
|
|
47
|
-
* The elements are named by their persisted ids, as the form binding names
|
|
48
|
-
* `form`: a plugin never imports another's internals, and
|
|
49
|
-
* `ai-layout-listings.spec.ts` holds the ids and props named here to the
|
|
50
|
-
* source of the plugins that declare them.
|
|
51
|
-
*
|
|
52
|
-
* Model only: shapes and the inputs reader; the compiler draws them.
|
|
53
|
-
*/ import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
17
|
+
*/ import { aiStorefrontSays } from "./ai-layout-storefront.js";
|
|
54
18
|
/** The unit input a site's listings travel in, to its layout and its pages. */ export const AI_LAYOUT_LISTINGS_INPUT = 'siteListings';
|
|
55
19
|
/** The id a listing of a kind is placed by. */ export function aiLayoutListingId(kind) {
|
|
56
20
|
return `listing:${kind}`;
|
|
@@ -58,13 +22,17 @@
|
|
|
58
22
|
/** The persisted element a listing is drawn with, by kind (held to its plugin's source by a spec). */ export const AI_LAYOUT_LISTING_ELEMENTS = {
|
|
59
23
|
products: 'product-grid',
|
|
60
24
|
posts: 'collectionEntries',
|
|
61
|
-
tracks: 'musicPlayer'
|
|
25
|
+
tracks: 'musicPlayer',
|
|
26
|
+
reviews: 'product-reviews',
|
|
27
|
+
signup: 'newsletter-signup'
|
|
62
28
|
};
|
|
63
29
|
/** The header's cart button: the commerce plugin's Cart in its `button` variant. */ export const AI_LAYOUT_CART_ELEMENT = 'cart';
|
|
64
30
|
/** The most records a featured band shows; a page of its own shows them all. */ export const AI_LAYOUT_FEATURED_RECORDS = {
|
|
65
31
|
products: 4,
|
|
66
32
|
posts: 3,
|
|
67
|
-
tracks: 0
|
|
33
|
+
tracks: 0,
|
|
34
|
+
reviews: 3,
|
|
35
|
+
signup: 0
|
|
68
36
|
};
|
|
69
37
|
const PLACEMENT_ROLES = [
|
|
70
38
|
'featured',
|
|
@@ -73,9 +41,18 @@ const PLACEMENT_ROLES = [
|
|
|
73
41
|
const KINDS = [
|
|
74
42
|
'products',
|
|
75
43
|
'posts',
|
|
76
|
-
'tracks'
|
|
44
|
+
'tracks',
|
|
45
|
+
'reviews',
|
|
46
|
+
'signup'
|
|
77
47
|
];
|
|
78
48
|
const text = (value)=>typeof value === 'string' ? value.trim() : '';
|
|
49
|
+
/** What a visitor calls each kind's whole list, where a unit's input names none. */ const LISTING_NAMES = {
|
|
50
|
+
products: 'the shop',
|
|
51
|
+
posts: 'the blog',
|
|
52
|
+
tracks: 'the music',
|
|
53
|
+
reviews: 'the store',
|
|
54
|
+
signup: 'the newsletter'
|
|
55
|
+
};
|
|
79
56
|
/** A path on the site, as a listing's links are kept. */ const SITE_PATH = /^\/[a-z0-9-]+(?:\/[a-z0-9-]+)*$/;
|
|
80
57
|
/** A site's listings as its unit inputs carry them; nothing that does not read is kept. */ export function aiLayoutListingsOf(inputs) {
|
|
81
58
|
const raw = inputs == null ? void 0 : inputs[AI_LAYOUT_LISTINGS_INPUT];
|
|
@@ -110,9 +87,10 @@ const text = (value)=>typeof value === 'string' ? value.trim() : '';
|
|
|
110
87
|
listings.push(_extends({
|
|
111
88
|
id: aiLayoutListingId(kind),
|
|
112
89
|
kind,
|
|
113
|
-
name: text(record['name']) ||
|
|
114
|
-
// A tracks listing names no records: no job sources a recording (AGL-3716)
|
|
115
|
-
|
|
90
|
+
name: text(record['name']) || LISTING_NAMES[kind],
|
|
91
|
+
// A tracks listing names no records: no job sources a recording (AGL-3716);
|
|
92
|
+
// nor do a store's reviews and its sign-up, which shoppers fill (AGL-3676).
|
|
93
|
+
records: kind !== 'products' && kind !== 'posts' ? [] : (Array.isArray(record['records']) ? record['records'] : []).map(text).filter(Boolean).slice(0, 12)
|
|
116
94
|
}, SITE_PATH.test(href) ? {
|
|
117
95
|
href
|
|
118
96
|
} : {}, kind === 'posts' ? {
|
|
@@ -158,6 +136,29 @@ const POST_SECTION = /\b(blog|posts?|articles?|writing|journal|stories|essays?|l
|
|
|
158
136
|
const byItems = most > 0 ? sections.findIndex((section)=>section.items === most) : -1;
|
|
159
137
|
return byItems !== -1 ? byItems : sections.length > 1 ? 1 : 0;
|
|
160
138
|
}
|
|
139
|
+
/**
|
|
140
|
+
* The section of a store's home its bestsellers fill (AGL-3676): the first
|
|
141
|
+
* after the hero whose name says them ("Bestsellers", "Featured candles", "New
|
|
142
|
+
* arrivals"), never one about the store's collections, its reviews or its
|
|
143
|
+
* sign-up; else the first after the hero naming products at all; else the
|
|
144
|
+
* first after the hero planned with items that is none of those. The
|
|
145
|
+
* beta.237 Willow Wick home would otherwise have listed products in its
|
|
146
|
+
* "Shop by collection" tiles.
|
|
147
|
+
*/ function aiLayoutFeaturedSection(screen) {
|
|
148
|
+
// A section about the collections that does not also name the bestsellers.
|
|
149
|
+
const other = (name)=>aiStorefrontSays('collections', name) && !aiStorefrontSays('featured', name) || aiStorefrontSays('reviews', name) || aiStorefrontSays('signup', name);
|
|
150
|
+
const first = (test)=>screen.sections.findIndex((section, index)=>index > 0 && test(section.name, section.items));
|
|
151
|
+
for (const test of [
|
|
152
|
+
(name)=>aiStorefrontSays('featured', name) && !other(name),
|
|
153
|
+
(name)=>PRODUCT_SECTION.test(name) && !other(name),
|
|
154
|
+
(name, items)=>items > 0 && !other(name),
|
|
155
|
+
(name)=>PRODUCT_SECTION.test(name)
|
|
156
|
+
]){
|
|
157
|
+
const found = first(test);
|
|
158
|
+
if (found !== -1) return found;
|
|
159
|
+
}
|
|
160
|
+
return -1;
|
|
161
|
+
}
|
|
161
162
|
/** A section of a shop page that only opens or closes it: "Shop intro heading", "Gift help call to action". */ const SHOP_FRAMING = /\b(hero|intro(?:duction)?|heading|banner|cta|call to action|contact)\b/i;
|
|
162
163
|
/** Whether a planned page is the store's own Shop page: its address or its name says it sells. */ export function aiLayoutIsShopPage(screen) {
|
|
163
164
|
return !isHome(screen.slug) && PRODUCT_PAGE.test(`${firstSegment(screen.slug)} ${screen.title}`);
|
|
@@ -211,11 +212,25 @@ const isHome = (slug)=>firstSegment(slug) === '';
|
|
|
211
212
|
}
|
|
212
213
|
return placements;
|
|
213
214
|
}
|
|
215
|
+
if (kind === 'reviews' || kind === 'signup') {
|
|
216
|
+
// A store's own reviews, on its home, in the section named for them; its
|
|
217
|
+
// newsletter sign-up in any page's section named for it that places no
|
|
218
|
+
// form of the plan's (AGL-3676). Neither is ever a page's opening.
|
|
219
|
+
for (const screen of screens){
|
|
220
|
+
if (kind === 'reviews' && !isHome(screen.slug)) continue;
|
|
221
|
+
place(screen, afterHero(screen, (name)=>aiStorefrontSays(kind, name)), kind === 'reviews' ? 'featured' : 'index');
|
|
222
|
+
}
|
|
223
|
+
return kind === 'signup' ? placements.filter((placement)=>{
|
|
224
|
+
var _ref;
|
|
225
|
+
var _screen_sections_placement_section;
|
|
226
|
+
const screen = screens.find((entry)=>entry.id === placement.screenId);
|
|
227
|
+
return !((_ref = screen == null ? void 0 : (_screen_sections_placement_section = screen.sections[placement.section]) == null ? void 0 : _screen_sections_placement_section.uses) != null ? _ref : []).some((use)=>/form/i.test(use));
|
|
228
|
+
}) : placements;
|
|
229
|
+
}
|
|
214
230
|
if (kind === 'products') {
|
|
215
231
|
for (const screen of screens){
|
|
216
232
|
if (isHome(screen.slug)) {
|
|
217
|
-
|
|
218
|
-
place(screen, named !== -1 ? named : afterHero(screen, (_name, items)=>items > 0), 'featured');
|
|
233
|
+
place(screen, aiLayoutFeaturedSection(screen), 'featured');
|
|
219
234
|
continue;
|
|
220
235
|
}
|
|
221
236
|
if (!aiLayoutIsShopPage(screen)) continue;
|