@aglyn/plugins-ai 1.0.0-beta.263 → 1.0.0-beta.266
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/assets/site-styles/beauty.webp +0 -0
- package/src/lib/assets/site-styles/blog.webp +0 -0
- package/src/lib/assets/site-styles/business.webp +0 -0
- package/src/lib/assets/site-styles/coming-soon.webp +0 -0
- package/src/lib/assets/site-styles/education.webp +0 -0
- package/src/lib/assets/site-styles/events.webp +0 -0
- package/src/lib/assets/site-styles/fitness.webp +0 -0
- package/src/lib/assets/site-styles/landing.webp +0 -0
- package/src/lib/assets/site-styles/music.webp +0 -0
- package/src/lib/assets/site-styles/nonprofit.webp +0 -0
- package/src/lib/assets/site-styles/personal.webp +0 -0
- package/src/lib/assets/site-styles/photography.webp +0 -0
- package/src/lib/assets/site-styles/portfolio.webp +0 -0
- package/src/lib/assets/site-styles/professional.webp +0 -0
- package/src/lib/assets/site-styles/realestate.webp +0 -0
- package/src/lib/assets/site-styles/restaurant.webp +0 -0
- package/src/lib/assets/site-styles/store.webp +0 -0
- package/src/lib/assets/site-styles/studio.webp +0 -0
- package/src/lib/assets/site-styles/trades.webp +0 -0
- package/src/lib/assets/site-styles/wellness.webp +0 -0
- package/src/lib/assets/site-styles/yoga.webp +0 -0
- package/src/lib/components/ai-site-start-card.component.d.ts +12 -0
- package/src/lib/components/ai-site-start-card.component.js +256 -236
- package/src/lib/components/ai-site-start-card.component.js.map +1 -1
- package/src/lib/components/ai-site-start-steps.component.d.ts +108 -0
- package/src/lib/components/ai-site-start-steps.component.js +451 -0
- package/src/lib/components/ai-site-start-steps.component.js.map +1 -0
- package/src/lib/components/ai-site-start-tracking.d.ts +114 -0
- package/src/lib/components/ai-site-start-tracking.js +277 -0
- package/src/lib/components/ai-site-start-tracking.js.map +1 -0
- package/src/lib/components/ai-site-style-previews.d.ts +26 -0
- package/src/lib/components/ai-site-style-previews.js +115 -0
- package/src/lib/components/ai-site-style-previews.js.map +1 -0
- package/src/lib/insights/ai-insight-digest.d.ts +9 -0
- package/src/lib/insights/ai-insight-digest.js +35 -11
- package/src/lib/insights/ai-insight-digest.js.map +1 -1
- package/src/lib/jobs/ai-job-page-language.d.ts +9 -0
- package/src/lib/jobs/ai-job-page-language.js +20 -2
- package/src/lib/jobs/ai-job-page-language.js.map +1 -1
- package/src/lib/jobs/ai-job-page-sections.js +8 -2
- package/src/lib/jobs/ai-job-page-sections.js.map +1 -1
- package/src/lib/jobs/ai-job-plan-step.d.ts +43 -8
- package/src/lib/jobs/ai-job-plan-step.js +99 -18
- package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
- package/src/lib/jobs/ai-job-site-datasets.d.ts +93 -2
- package/src/lib/jobs/ai-job-site-datasets.js +308 -21
- package/src/lib/jobs/ai-job-site-datasets.js.map +1 -1
- package/src/lib/jobs/ai-layout-stock-photos.d.ts +191 -13
- package/src/lib/jobs/ai-layout-stock-photos.js +1709 -69
- package/src/lib/jobs/ai-layout-stock-photos.js.map +1 -1
- package/src/lib/jobs/fixtures/ai-site-foreign-datasets-recording.d.ts +58 -0
- package/src/lib/jobs/fixtures/ai-site-foreign-datasets-recording.js +678 -0
- package/src/lib/jobs/fixtures/ai-site-foreign-datasets-recording.js.map +1 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-agl3856-beta265-recordings.d.ts +33 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-agl3856-beta265-recordings.js +270 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-agl3856-beta265-recordings.js.map +1 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-agl3856-people-recordings.d.ts +33 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-agl3856-people-recordings.js +277 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-agl3856-people-recordings.js.map +1 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-juniper-records-recordings.d.ts +23 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-juniper-records-recordings.js +163 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-juniper-records-recordings.js.map +1 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-learning-center-recordings.d.ts +23 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-learning-center-recordings.js +53 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-learning-center-recordings.js.map +1 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-synchronicity-recordings.d.ts +21 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-synchronicity-recordings.js +119 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-synchronicity-recordings.js.map +1 -0
- package/src/lib/layout-language/ai-layout-compiler.js +54 -15
- 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 +14 -13
- package/src/lib/layout-language/ai-layout-design.js.map +1 -1
- package/src/lib/layout-language/ai-layout-listings.d.ts +55 -0
- package/src/lib/layout-language/ai-layout-listings.js +103 -5
- package/src/lib/layout-language/ai-layout-listings.js.map +1 -1
- package/src/lib/layout-language/ai-layout-pictures.d.ts +42 -8
- package/src/lib/layout-language/ai-layout-pictures.js +91 -6
- package/src/lib/layout-language/ai-layout-pictures.js.map +1 -1
- package/src/lib/model/ai-site-home-arcs.d.ts +75 -0
- package/src/lib/model/ai-site-home-arcs.js +741 -0
- package/src/lib/model/ai-site-home-arcs.js.map +1 -0
- package/src/lib/model/ai-site-inventory.d.ts +18 -0
- package/src/lib/model/ai-site-inventory.js.map +1 -1
- package/src/lib/model/ai-site-job.d.ts +9 -6
- package/src/lib/model/ai-site-job.js +8 -5
- package/src/lib/model/ai-site-job.js.map +1 -1
- package/src/lib/model/ai-site-kinds.js +24 -24
- package/src/lib/model/ai-site-kinds.js.map +1 -1
- package/src/lib/model/ai-site-start-steps.d.ts +67 -0
- package/src/lib/model/ai-site-start-steps.js +127 -0
- package/src/lib/model/ai-site-start-steps.js.map +1 -0
- package/src/lib/model/ai-site-start.js +4 -1
- package/src/lib/model/ai-site-start.js.map +1 -1
- package/src/lib/runtime/ai-doctrine-validators.d.ts +32 -0
- package/src/lib/runtime/ai-doctrine-validators.js +119 -2
- package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
- package/src/lib/runtime/ai-doctrine.d.ts +6 -1
- package/src/lib/runtime/ai-doctrine.js +9 -5
- package/src/lib/runtime/ai-doctrine.js.map +1 -1
- package/src/lib/runtime/ai-palette.generated.js +5 -2
- package/src/lib/runtime/ai-palette.generated.js.map +1 -1
- package/src/lib/runtime/site-inventory.js +6 -0
- package/src/lib/runtime/site-inventory.js.map +1 -1
- package/src/lib/server/ai-insight-digest-route.js +2 -2
- package/src/lib/server/ai-insight-digest-route.js.map +1 -1
- package/src/lib/usage/assist-free-taste.js +3 -0
- package/src/lib/usage/assist-free-taste.js.map +1 -1
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
2
|
+
/**
|
|
3
|
+
* @license
|
|
4
|
+
* Copyright 2026 Aglyn LLC
|
|
5
|
+
*
|
|
6
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
|
+
* you may not use this file except in compliance with the License.
|
|
8
|
+
* You may obtain a copy of the License at
|
|
9
|
+
*
|
|
10
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
*
|
|
12
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
13
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
14
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
|
+
* See the License for the specific language governing permissions and
|
|
16
|
+
* limitations under the License.
|
|
17
|
+
*/ import { AI_SITE_INPUT_MAX_CHARS, AI_SITE_SUBMISSIONS, aiSitePagesBand } from "./ai-site-job.js";
|
|
18
|
+
/**
|
|
19
|
+
* The guided start's steps (AGL-3898): the same questions `ai-site-start.ts`
|
|
20
|
+
* asks, one decision to a step, so a person answers one thing at a time and
|
|
21
|
+
* the funnel can say which one they stopped at.
|
|
22
|
+
*
|
|
23
|
+
* Splitting the questions changes nothing they become. Every step writes the
|
|
24
|
+
* same {@link AiSiteStartAnswers} the single form did, and only the review's
|
|
25
|
+
* "Plan my site" turns them into the job's brief and inputs, through the same
|
|
26
|
+
* `aiSiteStartBrief` / `aiSiteStartInputs`. Which answers are required is
|
|
27
|
+
* unchanged too: only what kind of site it is.
|
|
28
|
+
*
|
|
29
|
+
* Pure: ids, titles, which answers each step holds, and each step's reasons
|
|
30
|
+
* not to go forward, worded as `aiSiteStartRefusal` words them.
|
|
31
|
+
*/ /** A step of the guided start, in order. `choose` is the starter-or-AI choice the questions follow. */ export const AI_SITE_START_STEPS = [
|
|
32
|
+
'choose',
|
|
33
|
+
'kind',
|
|
34
|
+
'audience',
|
|
35
|
+
'style',
|
|
36
|
+
'details',
|
|
37
|
+
'review'
|
|
38
|
+
];
|
|
39
|
+
/** Each step's short name, as the progress indicator shows it. */ export const AI_SITE_START_STEP_LABELS = {
|
|
40
|
+
choose: 'Start',
|
|
41
|
+
kind: 'Your site',
|
|
42
|
+
audience: 'Audience',
|
|
43
|
+
style: 'Style',
|
|
44
|
+
details: 'Details',
|
|
45
|
+
review: 'Review'
|
|
46
|
+
};
|
|
47
|
+
/** The answers each step holds. */ export const AI_SITE_START_STEP_FIELDS = {
|
|
48
|
+
choose: [],
|
|
49
|
+
kind: [
|
|
50
|
+
'siteType'
|
|
51
|
+
],
|
|
52
|
+
audience: [
|
|
53
|
+
'audience'
|
|
54
|
+
],
|
|
55
|
+
style: [
|
|
56
|
+
'kind'
|
|
57
|
+
],
|
|
58
|
+
details: [
|
|
59
|
+
'submissions',
|
|
60
|
+
'pages',
|
|
61
|
+
'welcomeEmail'
|
|
62
|
+
],
|
|
63
|
+
review: []
|
|
64
|
+
};
|
|
65
|
+
/** The step's place, from 0. */ export function aiSiteStartStepIndex(step) {
|
|
66
|
+
return AI_SITE_START_STEPS.indexOf(step);
|
|
67
|
+
}
|
|
68
|
+
/** The step after this one, or `null` after the review. */ export function aiSiteStartNextStep(step) {
|
|
69
|
+
var _AI_SITE_START_STEPS_;
|
|
70
|
+
return (_AI_SITE_START_STEPS_ = AI_SITE_START_STEPS[aiSiteStartStepIndex(step) + 1]) != null ? _AI_SITE_START_STEPS_ : null;
|
|
71
|
+
}
|
|
72
|
+
/** The step before this one, or `null` before the choice. */ export function aiSiteStartPreviousStep(step) {
|
|
73
|
+
var _AI_SITE_START_STEPS_;
|
|
74
|
+
const index = aiSiteStartStepIndex(step);
|
|
75
|
+
return index > 0 ? (_AI_SITE_START_STEPS_ = AI_SITE_START_STEPS[index - 1]) != null ? _AI_SITE_START_STEPS_ : null : null;
|
|
76
|
+
}
|
|
77
|
+
/** Every answer's reason not to go forward, by field, with nothing for an answer that may. */ export function aiSiteStartFieldErrors(answers, options = {}) {
|
|
78
|
+
const errors = {};
|
|
79
|
+
const siteType = answers.siteType.trim();
|
|
80
|
+
if (!siteType) errors.siteType = {
|
|
81
|
+
message: 'Say what kind of site this is.',
|
|
82
|
+
code: 'required'
|
|
83
|
+
};
|
|
84
|
+
else if (siteType.length > AI_SITE_INPUT_MAX_CHARS) {
|
|
85
|
+
errors.siteType = {
|
|
86
|
+
message: `Keep what kind of site this is under ${AI_SITE_INPUT_MAX_CHARS} characters.`,
|
|
87
|
+
code: 'too_long'
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
if (answers.audience.trim().length > AI_SITE_INPUT_MAX_CHARS) {
|
|
91
|
+
errors.audience = {
|
|
92
|
+
message: `Keep who the site is for under ${AI_SITE_INPUT_MAX_CHARS} characters.`,
|
|
93
|
+
code: 'too_long'
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
const band = aiSitePagesBand(options.freeTaste);
|
|
97
|
+
if (!Number.isInteger(answers.pages) || answers.pages < band.min || answers.pages > band.max) {
|
|
98
|
+
errors.pages = {
|
|
99
|
+
message: `A site is planned with ${band.min} to ${band.max} pages.`,
|
|
100
|
+
code: 'out_of_range'
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
if (!AI_SITE_SUBMISSIONS.includes(answers.submissions)) {
|
|
104
|
+
errors.submissions = {
|
|
105
|
+
message: 'Say where the contact form’s submissions go.',
|
|
106
|
+
code: 'invalid'
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
return errors;
|
|
110
|
+
}
|
|
111
|
+
/** One step's reasons not to go forward, in the order its fields are drawn; empty when it may. */ export function aiSiteStartStepErrors(step, answers, options = {}) {
|
|
112
|
+
const errors = aiSiteStartFieldErrors(answers, options);
|
|
113
|
+
return AI_SITE_START_STEP_FIELDS[step].flatMap((field)=>{
|
|
114
|
+
const error = errors[field];
|
|
115
|
+
return error ? [
|
|
116
|
+
_extends({
|
|
117
|
+
field
|
|
118
|
+
}, error)
|
|
119
|
+
] : [];
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
/** The first step holding an answer that cannot go forward, for a review that was reached with one; `null` for none. */ export function aiSiteStartFirstInvalidStep(answers, options = {}) {
|
|
123
|
+
var _AI_SITE_START_STEPS_find;
|
|
124
|
+
return (_AI_SITE_START_STEPS_find = AI_SITE_START_STEPS.find((step)=>aiSiteStartStepErrors(step, answers, options).length > 0)) != null ? _AI_SITE_START_STEPS_find : null;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
//# sourceMappingURL=ai-site-start-steps.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/model/ai-site-start-steps.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_SITE_INPUT_MAX_CHARS, AI_SITE_SUBMISSIONS, aiSitePagesBand } from './ai-site-job'\nimport type { AiSiteStartAnswers } from './ai-site-start'\n\n/**\n * The guided start's steps (AGL-3898): the same questions `ai-site-start.ts`\n * asks, one decision to a step, so a person answers one thing at a time and\n * the funnel can say which one they stopped at.\n *\n * Splitting the questions changes nothing they become. Every step writes the\n * same {@link AiSiteStartAnswers} the single form did, and only the review's\n * \"Plan my site\" turns them into the job's brief and inputs, through the same\n * `aiSiteStartBrief` / `aiSiteStartInputs`. Which answers are required is\n * unchanged too: only what kind of site it is.\n *\n * Pure: ids, titles, which answers each step holds, and each step's reasons\n * not to go forward, worded as `aiSiteStartRefusal` words them.\n */\n\n/** A step of the guided start, in order. `choose` is the starter-or-AI choice the questions follow. */\nexport const AI_SITE_START_STEPS = ['choose', 'kind', 'audience', 'style', 'details', 'review'] as const\n\nexport type AiSiteStartStep = (typeof AI_SITE_START_STEPS)[number]\n\n/** Each step's short name, as the progress indicator shows it. */\nexport const AI_SITE_START_STEP_LABELS: Readonly<Record<AiSiteStartStep, string>> = {\n choose: 'Start',\n kind: 'Your site',\n audience: 'Audience',\n style: 'Style',\n details: 'Details',\n review: 'Review',\n}\n\n/** An answer a step asks for, by its key on {@link AiSiteStartAnswers}. */\nexport type AiSiteStartField = 'siteType' | 'audience' | 'kind' | 'pages' | 'submissions' | 'welcomeEmail'\n\n/** The answers each step holds. */\nexport const AI_SITE_START_STEP_FIELDS: Readonly<Record<AiSiteStartStep, readonly AiSiteStartField[]>> = {\n choose: [],\n kind: ['siteType'],\n audience: ['audience'],\n style: ['kind'],\n details: ['submissions', 'pages', 'welcomeEmail'],\n review: [],\n}\n\n/** Why an answer cannot go forward, as a closed code the funnel can count. */\nexport type AiSiteStartErrorCode = 'required' | 'too_long' | 'out_of_range' | 'invalid'\n\n/** One answer's reason not to go forward: the sentence beside the field, and its code. */\nexport interface AiSiteStartFieldError {\n message: string\n code: AiSiteStartErrorCode\n}\n\n/** The step's place, from 0. */\nexport function aiSiteStartStepIndex(step: AiSiteStartStep): number {\n return AI_SITE_START_STEPS.indexOf(step)\n}\n\n/** The step after this one, or `null` after the review. */\nexport function aiSiteStartNextStep(step: AiSiteStartStep): AiSiteStartStep | null {\n return AI_SITE_START_STEPS[aiSiteStartStepIndex(step) + 1] ?? null\n}\n\n/** The step before this one, or `null` before the choice. */\nexport function aiSiteStartPreviousStep(step: AiSiteStartStep): AiSiteStartStep | null {\n const index = aiSiteStartStepIndex(step)\n return index > 0 ? (AI_SITE_START_STEPS[index - 1] ?? null) : null\n}\n\n/** Every answer's reason not to go forward, by field, with nothing for an answer that may. */\nexport function aiSiteStartFieldErrors(\n answers: AiSiteStartAnswers,\n options: { freeTaste?: boolean } = {},\n): Partial<Record<AiSiteStartField, AiSiteStartFieldError>> {\n const errors: Partial<Record<AiSiteStartField, AiSiteStartFieldError>> = {}\n const siteType = answers.siteType.trim()\n if (!siteType) errors.siteType = { message: 'Say what kind of site this is.', code: 'required' }\n else if (siteType.length > AI_SITE_INPUT_MAX_CHARS) {\n errors.siteType = { message: `Keep what kind of site this is under ${AI_SITE_INPUT_MAX_CHARS} characters.`, code: 'too_long' }\n }\n if (answers.audience.trim().length > AI_SITE_INPUT_MAX_CHARS) {\n errors.audience = { message: `Keep who the site is for under ${AI_SITE_INPUT_MAX_CHARS} characters.`, code: 'too_long' }\n }\n const band = aiSitePagesBand(options.freeTaste)\n if (!Number.isInteger(answers.pages) || answers.pages < band.min || answers.pages > band.max) {\n errors.pages = { message: `A site is planned with ${band.min} to ${band.max} pages.`, code: 'out_of_range' }\n }\n if (!(AI_SITE_SUBMISSIONS as readonly string[]).includes(answers.submissions)) {\n errors.submissions = { message: 'Say where the contact form’s submissions go.', code: 'invalid' }\n }\n return errors\n}\n\n/** One step's reasons not to go forward, in the order its fields are drawn; empty when it may. */\nexport function aiSiteStartStepErrors(\n step: AiSiteStartStep,\n answers: AiSiteStartAnswers,\n options: { freeTaste?: boolean } = {},\n): Array<{ field: AiSiteStartField } & AiSiteStartFieldError> {\n const errors = aiSiteStartFieldErrors(answers, options)\n return AI_SITE_START_STEP_FIELDS[step].flatMap((field) => {\n const error = errors[field]\n return error ? [{ field, ...error }] : []\n })\n}\n\n/** The first step holding an answer that cannot go forward, for a review that was reached with one; `null` for none. */\nexport function aiSiteStartFirstInvalidStep(\n answers: AiSiteStartAnswers,\n options: { freeTaste?: boolean } = {},\n): AiSiteStartStep | null {\n return AI_SITE_START_STEPS.find((step) => aiSiteStartStepErrors(step, answers, options).length > 0) ?? null\n}\n"],"names":["AI_SITE_INPUT_MAX_CHARS","AI_SITE_SUBMISSIONS","aiSitePagesBand","AI_SITE_START_STEPS","AI_SITE_START_STEP_LABELS","choose","kind","audience","style","details","review","AI_SITE_START_STEP_FIELDS","aiSiteStartStepIndex","step","indexOf","aiSiteStartNextStep","aiSiteStartPreviousStep","index","aiSiteStartFieldErrors","answers","options","errors","siteType","trim","message","code","length","band","freeTaste","Number","isInteger","pages","min","max","includes","submissions","aiSiteStartStepErrors","flatMap","field","error","aiSiteStartFirstInvalidStep","find"],"mappings":"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"}
|
|
@@ -132,7 +132,10 @@ import { AI_SITE_INPUT_MAX_CHARS, AI_SITE_SUBMISSIONS, aiSitePagesBand } from ".
|
|
|
132
132
|
}
|
|
133
133
|
// The kind they picked, or the one their answer suggests (AGL-3660).
|
|
134
134
|
sentences.push(`Make it a ${aiSiteStartKind(answers).label.toLowerCase()} site.`);
|
|
135
|
-
|
|
135
|
+
// The form on the page a visitor opens to get in touch, never every page's
|
|
136
|
+
// last band (AGL-3899): "on the page that asks to be contacted" read as
|
|
137
|
+
// the home's closing band on 16 of the 22 newest production starts.
|
|
138
|
+
sentences.push(answers.pages === 1 ? 'Plan the page around what a visitor comes to do, with a contact form on it.' : 'Plan a page for each thing a visitor comes to do, with the contact form on the page a visitor opens to get in touch.');
|
|
136
139
|
if (audience) {
|
|
137
140
|
// Said of the FORM as well as of the copy: who fills a form in is what
|
|
138
141
|
// decides which fields it asks for, and a brief that named the audience
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/model/ai-site-start.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 { aiSiteKind, aiSiteKindFor, type AiSiteKind } from './ai-site-kinds'\nimport {\n AI_SITE_INPUT_MAX_CHARS,\n AI_SITE_SUBMISSIONS,\n aiSitePagesBand,\n type AiSiteJobInputs,\n type AiSiteSubmissions,\n} from './ai-site-job'\n\n/**\n * The guided start (AGL-2918): the questions a person answers on a site they\n * have just made, and the site job those answers become.\n *\n * A scaffold already exists — the `site` job plans four to eight pages, a\n * layout, a contact form, a palette and a welcome email, and builds every one\n * of them as a draft. What it had no way in through was a brief: somebody had\n * to know what to write, on a site where nothing has happened yet. So this\n * module is the QUESTIONS, and nothing else: what is asked, what an answer may\n * be, and how a set of answers becomes the brief and the scalar inputs the\n * `site` door already takes.\n *\n * ── Answering is never the way out ───────────────────────────────────────\n *\n * There is no answer here that means \"not this\". The way past the questions\n * is the `hostFirstRun` zone's own `startBlank`, which is drawn beside them\n * from the first one and creates nothing at all — see the zone in\n * `CONSOLE_WIDGET_SLOTS`. Putting the exit inside the question set would make\n * leaving a thing you answer your way to, and it is not.\n *\n * ── Every answer but the first is optional ───────────────────────────────\n *\n * Only what a site is FOR has no default worth guessing. The audience narrows\n * the copy, the example steers the shape, the page count and the welcome\n * email are defaults a person may change — so a person who types six words\n * and confirms gets a planned site, and the rest of the questions are there\n * for the person who wants to answer them.\n *\n * Pure: shapes, vocabularies and two string builders. No React, no network,\n * and no import of the starter catalog — {@link AI_SITE_START_EXAMPLES} names\n * the starters rather than reading them, so the console does not carry a\n * catalog of node maps to draw five cards. `ai-site-start.spec.ts` holds the\n * two lists to each other.\n */\n\n/** A worked example a person picks to steer what the site should look like. */\nexport interface AiSiteStartExample {\n /** A `STARTER_TEMPLATES` id, which is a persisted identifier. */\n id: string\n /** The starter as its card names it. */\n label: string\n /** The starter's category, which is how the examples group. */\n category: string\n /** What the example is, in one line a person reads before picking it. */\n blurb: string\n}\n\n/**\n * The examples the start offers, in the order they are shown. The platform's\n * own starter sites: a person who has no words for what they want has seen\n * these five shapes before, and picking one says more than a sentence would.\n */\nexport const AI_SITE_START_EXAMPLES: readonly AiSiteStartExample[] = [\n {\n id: 'business',\n label: 'Business',\n category: 'Business',\n blurb: 'A home page led by what you do, an about page and a contact page',\n },\n {\n id: 'landing',\n label: 'Landing Page',\n category: 'Marketing',\n blurb: 'One page that leads to one action, with the reasons to take it',\n },\n {\n id: 'portfolio',\n label: 'Portfolio',\n category: 'Personal',\n blurb: 'An introduction, a grid of work and a way to get in touch',\n },\n {\n id: 'physical-shop',\n label: 'Shop (physical products)',\n category: 'Commerce',\n blurb: 'Featured products, a shop to browse, and a cart',\n },\n {\n id: 'digital-shop',\n label: 'Shop (digital products)',\n category: 'Commerce',\n blurb: 'Downloads up front, a shop to browse, and a cart',\n },\n]\n\n/**\n * Kinds of site offered beside the first question's box, so answering it can\n * be a tap. They fill the box rather than replacing it: what somebody is\n * building is theirs to say, and a list of eight is a list somebody is not on.\n */\nexport const AI_SITE_START_TYPES: readonly string[] = [\n 'a local service business',\n 'a restaurant or cafe',\n 'a professional practice',\n 'a trades business',\n 'a shop',\n 'a nonprofit',\n 'a personal portfolio',\n 'an event',\n]\n\n/** What a person answered; the shape the card holds and the brief is built from. */\nexport interface AiSiteStartAnswers {\n /** What kind of site this is. The one answer with no default. */\n siteType: string\n /** Who the site is for; empty when they did not say. */\n audience: string\n /** The example they liked, by {@link AiSiteStartExample.id}; `null` for none. */\n example: string | null\n /**\n * The kind of site they picked (AGL-3660), by its `AI_SITE_KINDS` id;\n * `null` until they pick one, when the kind their first answer suggests is\n * the one used ({@link aiSiteStartKind}).\n */\n kind: string | null\n /** How many pages to plan. */\n pages: number\n /** Where the contact form's submissions go. */\n submissions: AiSiteSubmissions\n /** Whether the scaffold also drafts a welcome email. */\n welcomeEmail: boolean\n}\n\n/**\n * What the questions start on. Five pages because it is the middle of the\n * band the scaffold builds, a welcome email because the scaffold drafts one\n * unless it is told not to, and the Inbox because it is what every submission\n * does anyway — filing a lead as well is the addition, and an addition is the\n * thing a person should have to choose.\n */\nexport const AI_SITE_START_ANSWERS: AiSiteStartAnswers = {\n siteType: '',\n audience: '',\n example: null,\n kind: null,\n pages: 5,\n submissions: 'inbox',\n welcomeEmail: true,\n}\n\n/** The kind of site the answers pick, else the one what the site is and who it is for suggest. */\nexport function aiSiteStartKind(\n answers: Pick<AiSiteStartAnswers, 'kind' | 'siteType'> & Partial<Pick<AiSiteStartAnswers, 'audience'>>,\n): AiSiteKind {\n return aiSiteKind(answers.kind) ?? aiSiteKindFor(answers.siteType, answers.audience ?? '')\n}\n\n/** The example an answer names, or `null` when it names none this list knows. */\nexport function aiSiteStartExample(\n answers: AiSiteStartAnswers,\n): AiSiteStartExample | null {\n if (!answers.example) return null\n return AI_SITE_START_EXAMPLES.find((entry) => entry.id === answers.example) ?? null\n}\n\n/**\n * Why these answers cannot start a site, in a sentence a person reads; `null`\n * when they can. The door's own parser is what actually admits the job — this\n * is what the card says before it asks for one.\n */\nexport function aiSiteStartRefusal(\n answers: AiSiteStartAnswers,\n options: { freeTaste?: boolean } = {},\n): string | null {\n // The workspace's own band (AGL-3594): one or two pages on the Free taste.\n const band = aiSitePagesBand(options.freeTaste)\n const siteType = answers.siteType.trim()\n if (!siteType) return 'Say what kind of site this is.'\n if (siteType.length > AI_SITE_INPUT_MAX_CHARS) {\n return `Keep what kind of site this is under ${AI_SITE_INPUT_MAX_CHARS} characters.`\n }\n if (answers.audience.trim().length > AI_SITE_INPUT_MAX_CHARS) {\n return `Keep who the site is for under ${AI_SITE_INPUT_MAX_CHARS} characters.`\n }\n if (!Number.isInteger(answers.pages) || answers.pages < band.min || answers.pages > band.max) {\n return `A site is planned with ${band.min} to ${band.max} pages.`\n }\n if (answers.example !== null && !aiSiteStartExample(answers)) {\n return 'Pick one of the examples, or none of them.'\n }\n if (!(AI_SITE_SUBMISSIONS as readonly string[]).includes(answers.submissions)) {\n return 'Say where the contact form’s submissions go.'\n }\n return null\n}\n\n/**\n * The answers as the brief the `site` job is created with: the plan step reads\n * it as the person's own words, so it says what they said and asks for nothing\n * they did not.\n */\nexport function aiSiteStartBrief(answers: AiSiteStartAnswers): string {\n const siteType = answers.siteType.trim()\n const audience = answers.audience.trim()\n const example = aiSiteStartExample(answers)\n const sentences = [\n `A ${answers.pages}-page website for ${siteType}.`,\n ]\n if (audience) sentences.push(`It is for ${audience}.`)\n if (example) {\n sentences.push(\n `Follow the shape of the ${example.label} starter: ${example.blurb.toLowerCase()}.`,\n )\n }\n // The kind they picked, or the one their answer suggests (AGL-3660).\n sentences.push(`Make it a ${aiSiteStartKind(answers).label.toLowerCase()} site.`)\n sentences.push(\n 'Plan a page for each thing a visitor comes to do, and a contact form on the page that asks to be contacted.',\n )\n if (audience) {\n // Said of the FORM as well as of the copy: who fills a form in is what\n // decides which fields it asks for, and a brief that named the audience\n // once, in a sentence about the pages, left the form to guess.\n sentences.push(`Ask the contact form for what ${audience} would be asked for.`)\n }\n return sentences.join(' ')\n}\n\n/**\n * The answers as the `site` job's scalar inputs. Read by the door's\n * {@link parseAiSiteJobInputs}, and put in front of the model one per line by\n * the plan step, which is how the audience and the example reach the plan\n * without a prompt of their own.\n *\n * The per-site variables an agency batch fills — the business name, the city,\n * the brand — are left empty here: the questions never ask them. The name is\n * the site's own, and the create door fills it from the site\n * (`aiJobAdmittedInputs`, AGL-3596) when the job is started — a job that\n * reached the model with no name had the model invent one for its header and\n * footer.\n */\nexport function aiSiteStartInputs(\n answers: AiSiteStartAnswers,\n): Omit<AiSiteJobInputs, 'batchId'> {\n const example = aiSiteStartExample(answers)\n const kind = aiSiteStartKind(answers)\n return {\n businessType: answers.siteType.trim(),\n audience: answers.audience.trim(),\n starter: example?.id ?? kind.starter,\n siteKind: kind.id,\n pages: answers.pages,\n businessName: '',\n city: '',\n brand: '',\n submissions: answers.submissions,\n welcomeEmail: answers.welcomeEmail,\n }\n}\n\n/** The site inputs a guided start's answers become, by name: what a summary carries back (AGL-3596). */\nexport const AI_SITE_START_INPUT_KEYS = [\n 'businessType',\n 'audience',\n 'starter',\n 'siteKind',\n 'pages',\n 'businessName',\n 'city',\n 'brand',\n 'submissions',\n 'welcomeEmail',\n] as const\n\n/** A site job's scalar inputs, and nothing else its inputs carry; `null` when it has none. */\nexport function aiSiteStartInputsOf(\n inputs: Readonly<Record<string, unknown>> | null | undefined,\n): Record<string, unknown> | null {\n if (!inputs) return null\n const kept: Record<string, unknown> = {}\n for (const key of AI_SITE_START_INPUT_KEYS) {\n const value = inputs[key]\n if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') kept[key] = value\n }\n return Object.keys(kept).length ? kept : null\n}\n\n/**\n * A failed start's answers, back as the questions hold them (AGL-3596), so\n * Try again reopens the guided start filled in for the person to adjust:\n * the inverse of {@link aiSiteStartInputs}. Read defensively — an answer the\n * questions no longer offer goes back to its default — and held to the\n * workspace's page band. `null` when the inputs say no kind of site at all.\n */\nexport function aiSiteStartAnswersFromInputs(\n inputs: Readonly<Record<string, unknown>> | null | undefined,\n options: { freeTaste?: boolean } = {},\n): AiSiteStartAnswers | null {\n const text = (key: string) => (typeof inputs?.[key] === 'string' ? (inputs[key] as string).trim() : '')\n const siteType = text('businessType')\n if (!siteType) return null\n const band = aiSitePagesBand(options.freeTaste)\n const pages = typeof inputs?.['pages'] === 'number' ? Math.round(inputs['pages'] as number) : AI_SITE_START_ANSWERS.pages\n const starter = text('starter')\n const submissions = text('submissions')\n return {\n siteType,\n audience: text('audience'),\n example: AI_SITE_START_EXAMPLES.some((entry) => entry.id === starter) ? starter : null,\n kind: aiSiteKind(text('siteKind'))?.id ?? null,\n pages: Math.min(band.max, Math.max(band.min, pages)),\n submissions: (AI_SITE_SUBMISSIONS as readonly string[]).includes(submissions)\n ? (submissions as AiSiteSubmissions)\n : AI_SITE_START_ANSWERS.submissions,\n welcomeEmail: options.freeTaste ? false : inputs?.['welcomeEmail'] !== false,\n }\n}\n"],"names":["aiSiteKind","aiSiteKindFor","AI_SITE_INPUT_MAX_CHARS","AI_SITE_SUBMISSIONS","aiSitePagesBand","AI_SITE_START_EXAMPLES","id","label","category","blurb","AI_SITE_START_TYPES","AI_SITE_START_ANSWERS","siteType","audience","example","kind","pages","submissions","welcomeEmail","aiSiteStartKind","answers","aiSiteStartExample","find","entry","aiSiteStartRefusal","options","band","freeTaste","trim","length","Number","isInteger","min","max","includes","aiSiteStartBrief","sentences","push","toLowerCase","join","aiSiteStartInputs","businessType","starter","siteKind","businessName","city","brand","AI_SITE_START_INPUT_KEYS","aiSiteStartInputsOf","inputs","kept","key","value","Object","keys","aiSiteStartAnswersFromInputs","text","Math","round","some"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/model/ai-site-start.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 { aiSiteKind, aiSiteKindFor, type AiSiteKind } from './ai-site-kinds'\nimport {\n AI_SITE_INPUT_MAX_CHARS,\n AI_SITE_SUBMISSIONS,\n aiSitePagesBand,\n type AiSiteJobInputs,\n type AiSiteSubmissions,\n} from './ai-site-job'\n\n/**\n * The guided start (AGL-2918): the questions a person answers on a site they\n * have just made, and the site job those answers become.\n *\n * A scaffold already exists — the `site` job plans four to eight pages, a\n * layout, a contact form, a palette and a welcome email, and builds every one\n * of them as a draft. What it had no way in through was a brief: somebody had\n * to know what to write, on a site where nothing has happened yet. So this\n * module is the QUESTIONS, and nothing else: what is asked, what an answer may\n * be, and how a set of answers becomes the brief and the scalar inputs the\n * `site` door already takes.\n *\n * ── Answering is never the way out ───────────────────────────────────────\n *\n * There is no answer here that means \"not this\". The way past the questions\n * is the `hostFirstRun` zone's own `startBlank`, which is drawn beside them\n * from the first one and creates nothing at all — see the zone in\n * `CONSOLE_WIDGET_SLOTS`. Putting the exit inside the question set would make\n * leaving a thing you answer your way to, and it is not.\n *\n * ── Every answer but the first is optional ───────────────────────────────\n *\n * Only what a site is FOR has no default worth guessing. The audience narrows\n * the copy, the example steers the shape, the page count and the welcome\n * email are defaults a person may change — so a person who types six words\n * and confirms gets a planned site, and the rest of the questions are there\n * for the person who wants to answer them.\n *\n * Pure: shapes, vocabularies and two string builders. No React, no network,\n * and no import of the starter catalog — {@link AI_SITE_START_EXAMPLES} names\n * the starters rather than reading them, so the console does not carry a\n * catalog of node maps to draw five cards. `ai-site-start.spec.ts` holds the\n * two lists to each other.\n */\n\n/** A worked example a person picks to steer what the site should look like. */\nexport interface AiSiteStartExample {\n /** A `STARTER_TEMPLATES` id, which is a persisted identifier. */\n id: string\n /** The starter as its card names it. */\n label: string\n /** The starter's category, which is how the examples group. */\n category: string\n /** What the example is, in one line a person reads before picking it. */\n blurb: string\n}\n\n/**\n * The examples the start offers, in the order they are shown. The platform's\n * own starter sites: a person who has no words for what they want has seen\n * these five shapes before, and picking one says more than a sentence would.\n */\nexport const AI_SITE_START_EXAMPLES: readonly AiSiteStartExample[] = [\n {\n id: 'business',\n label: 'Business',\n category: 'Business',\n blurb: 'A home page led by what you do, an about page and a contact page',\n },\n {\n id: 'landing',\n label: 'Landing Page',\n category: 'Marketing',\n blurb: 'One page that leads to one action, with the reasons to take it',\n },\n {\n id: 'portfolio',\n label: 'Portfolio',\n category: 'Personal',\n blurb: 'An introduction, a grid of work and a way to get in touch',\n },\n {\n id: 'physical-shop',\n label: 'Shop (physical products)',\n category: 'Commerce',\n blurb: 'Featured products, a shop to browse, and a cart',\n },\n {\n id: 'digital-shop',\n label: 'Shop (digital products)',\n category: 'Commerce',\n blurb: 'Downloads up front, a shop to browse, and a cart',\n },\n]\n\n/**\n * Kinds of site offered beside the first question's box, so answering it can\n * be a tap. They fill the box rather than replacing it: what somebody is\n * building is theirs to say, and a list of eight is a list somebody is not on.\n */\nexport const AI_SITE_START_TYPES: readonly string[] = [\n 'a local service business',\n 'a restaurant or cafe',\n 'a professional practice',\n 'a trades business',\n 'a shop',\n 'a nonprofit',\n 'a personal portfolio',\n 'an event',\n]\n\n/** What a person answered; the shape the card holds and the brief is built from. */\nexport interface AiSiteStartAnswers {\n /** What kind of site this is. The one answer with no default. */\n siteType: string\n /** Who the site is for; empty when they did not say. */\n audience: string\n /** The example they liked, by {@link AiSiteStartExample.id}; `null` for none. */\n example: string | null\n /**\n * The kind of site they picked (AGL-3660), by its `AI_SITE_KINDS` id;\n * `null` until they pick one, when the kind their first answer suggests is\n * the one used ({@link aiSiteStartKind}).\n */\n kind: string | null\n /** How many pages to plan. */\n pages: number\n /** Where the contact form's submissions go. */\n submissions: AiSiteSubmissions\n /** Whether the scaffold also drafts a welcome email. */\n welcomeEmail: boolean\n}\n\n/**\n * What the questions start on. Five pages because it is the middle of the\n * band the scaffold builds, a welcome email because the scaffold drafts one\n * unless it is told not to, and the Inbox because it is what every submission\n * does anyway — filing a lead as well is the addition, and an addition is the\n * thing a person should have to choose.\n */\nexport const AI_SITE_START_ANSWERS: AiSiteStartAnswers = {\n siteType: '',\n audience: '',\n example: null,\n kind: null,\n pages: 5,\n submissions: 'inbox',\n welcomeEmail: true,\n}\n\n/** The kind of site the answers pick, else the one what the site is and who it is for suggest. */\nexport function aiSiteStartKind(\n answers: Pick<AiSiteStartAnswers, 'kind' | 'siteType'> & Partial<Pick<AiSiteStartAnswers, 'audience'>>,\n): AiSiteKind {\n return aiSiteKind(answers.kind) ?? aiSiteKindFor(answers.siteType, answers.audience ?? '')\n}\n\n/** The example an answer names, or `null` when it names none this list knows. */\nexport function aiSiteStartExample(\n answers: AiSiteStartAnswers,\n): AiSiteStartExample | null {\n if (!answers.example) return null\n return AI_SITE_START_EXAMPLES.find((entry) => entry.id === answers.example) ?? null\n}\n\n/**\n * Why these answers cannot start a site, in a sentence a person reads; `null`\n * when they can. The door's own parser is what actually admits the job — this\n * is what the card says before it asks for one.\n */\nexport function aiSiteStartRefusal(\n answers: AiSiteStartAnswers,\n options: { freeTaste?: boolean } = {},\n): string | null {\n // The workspace's own band (AGL-3594): one or two pages on the Free taste.\n const band = aiSitePagesBand(options.freeTaste)\n const siteType = answers.siteType.trim()\n if (!siteType) return 'Say what kind of site this is.'\n if (siteType.length > AI_SITE_INPUT_MAX_CHARS) {\n return `Keep what kind of site this is under ${AI_SITE_INPUT_MAX_CHARS} characters.`\n }\n if (answers.audience.trim().length > AI_SITE_INPUT_MAX_CHARS) {\n return `Keep who the site is for under ${AI_SITE_INPUT_MAX_CHARS} characters.`\n }\n if (!Number.isInteger(answers.pages) || answers.pages < band.min || answers.pages > band.max) {\n return `A site is planned with ${band.min} to ${band.max} pages.`\n }\n if (answers.example !== null && !aiSiteStartExample(answers)) {\n return 'Pick one of the examples, or none of them.'\n }\n if (!(AI_SITE_SUBMISSIONS as readonly string[]).includes(answers.submissions)) {\n return 'Say where the contact form’s submissions go.'\n }\n return null\n}\n\n/**\n * The answers as the brief the `site` job is created with: the plan step reads\n * it as the person's own words, so it says what they said and asks for nothing\n * they did not.\n */\nexport function aiSiteStartBrief(answers: AiSiteStartAnswers): string {\n const siteType = answers.siteType.trim()\n const audience = answers.audience.trim()\n const example = aiSiteStartExample(answers)\n const sentences = [\n `A ${answers.pages}-page website for ${siteType}.`,\n ]\n if (audience) sentences.push(`It is for ${audience}.`)\n if (example) {\n sentences.push(\n `Follow the shape of the ${example.label} starter: ${example.blurb.toLowerCase()}.`,\n )\n }\n // The kind they picked, or the one their answer suggests (AGL-3660).\n sentences.push(`Make it a ${aiSiteStartKind(answers).label.toLowerCase()} site.`)\n // The form on the page a visitor opens to get in touch, never every page's\n // last band (AGL-3899): \"on the page that asks to be contacted\" read as\n // the home's closing band on 16 of the 22 newest production starts.\n sentences.push(\n answers.pages === 1\n ? 'Plan the page around what a visitor comes to do, with a contact form on it.'\n : 'Plan a page for each thing a visitor comes to do, with the contact form on the page a visitor opens to get in touch.',\n )\n if (audience) {\n // Said of the FORM as well as of the copy: who fills a form in is what\n // decides which fields it asks for, and a brief that named the audience\n // once, in a sentence about the pages, left the form to guess.\n sentences.push(`Ask the contact form for what ${audience} would be asked for.`)\n }\n return sentences.join(' ')\n}\n\n/**\n * The answers as the `site` job's scalar inputs. Read by the door's\n * {@link parseAiSiteJobInputs}, and put in front of the model one per line by\n * the plan step, which is how the audience and the example reach the plan\n * without a prompt of their own.\n *\n * The per-site variables an agency batch fills — the business name, the city,\n * the brand — are left empty here: the questions never ask them. The name is\n * the site's own, and the create door fills it from the site\n * (`aiJobAdmittedInputs`, AGL-3596) when the job is started — a job that\n * reached the model with no name had the model invent one for its header and\n * footer.\n */\nexport function aiSiteStartInputs(\n answers: AiSiteStartAnswers,\n): Omit<AiSiteJobInputs, 'batchId'> {\n const example = aiSiteStartExample(answers)\n const kind = aiSiteStartKind(answers)\n return {\n businessType: answers.siteType.trim(),\n audience: answers.audience.trim(),\n starter: example?.id ?? kind.starter,\n siteKind: kind.id,\n pages: answers.pages,\n businessName: '',\n city: '',\n brand: '',\n submissions: answers.submissions,\n welcomeEmail: answers.welcomeEmail,\n }\n}\n\n/** The site inputs a guided start's answers become, by name: what a summary carries back (AGL-3596). */\nexport const AI_SITE_START_INPUT_KEYS = [\n 'businessType',\n 'audience',\n 'starter',\n 'siteKind',\n 'pages',\n 'businessName',\n 'city',\n 'brand',\n 'submissions',\n 'welcomeEmail',\n] as const\n\n/** A site job's scalar inputs, and nothing else its inputs carry; `null` when it has none. */\nexport function aiSiteStartInputsOf(\n inputs: Readonly<Record<string, unknown>> | null | undefined,\n): Record<string, unknown> | null {\n if (!inputs) return null\n const kept: Record<string, unknown> = {}\n for (const key of AI_SITE_START_INPUT_KEYS) {\n const value = inputs[key]\n if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') kept[key] = value\n }\n return Object.keys(kept).length ? kept : null\n}\n\n/**\n * A failed start's answers, back as the questions hold them (AGL-3596), so\n * Try again reopens the guided start filled in for the person to adjust:\n * the inverse of {@link aiSiteStartInputs}. Read defensively — an answer the\n * questions no longer offer goes back to its default — and held to the\n * workspace's page band. `null` when the inputs say no kind of site at all.\n */\nexport function aiSiteStartAnswersFromInputs(\n inputs: Readonly<Record<string, unknown>> | null | undefined,\n options: { freeTaste?: boolean } = {},\n): AiSiteStartAnswers | null {\n const text = (key: string) => (typeof inputs?.[key] === 'string' ? (inputs[key] as string).trim() : '')\n const siteType = text('businessType')\n if (!siteType) return null\n const band = aiSitePagesBand(options.freeTaste)\n const pages = typeof inputs?.['pages'] === 'number' ? Math.round(inputs['pages'] as number) : AI_SITE_START_ANSWERS.pages\n const starter = text('starter')\n const submissions = text('submissions')\n return {\n siteType,\n audience: text('audience'),\n example: AI_SITE_START_EXAMPLES.some((entry) => entry.id === starter) ? starter : null,\n kind: aiSiteKind(text('siteKind'))?.id ?? null,\n pages: Math.min(band.max, Math.max(band.min, pages)),\n submissions: (AI_SITE_SUBMISSIONS as readonly string[]).includes(submissions)\n ? (submissions as AiSiteSubmissions)\n : AI_SITE_START_ANSWERS.submissions,\n welcomeEmail: options.freeTaste ? false : inputs?.['welcomeEmail'] !== false,\n }\n}\n"],"names":["aiSiteKind","aiSiteKindFor","AI_SITE_INPUT_MAX_CHARS","AI_SITE_SUBMISSIONS","aiSitePagesBand","AI_SITE_START_EXAMPLES","id","label","category","blurb","AI_SITE_START_TYPES","AI_SITE_START_ANSWERS","siteType","audience","example","kind","pages","submissions","welcomeEmail","aiSiteStartKind","answers","aiSiteStartExample","find","entry","aiSiteStartRefusal","options","band","freeTaste","trim","length","Number","isInteger","min","max","includes","aiSiteStartBrief","sentences","push","toLowerCase","join","aiSiteStartInputs","businessType","starter","siteKind","businessName","city","brand","AI_SITE_START_INPUT_KEYS","aiSiteStartInputsOf","inputs","kept","key","value","Object","keys","aiSiteStartAnswersFromInputs","text","Math","round","some"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,SAASA,UAAU,EAAEC,aAAa,QAAyB,qBAAiB;AAC5E,SACEC,uBAAuB,EACvBC,mBAAmB,EACnBC,eAAe,QAGV,mBAAe;AAiDtB;;;;CAIC,GACD,OAAO,MAAMC,yBAAwD;IACnE;QACEC,IAAI;QACJC,OAAO;QACPC,UAAU;QACVC,OAAO;IACT;IACA;QACEH,IAAI;QACJC,OAAO;QACPC,UAAU;QACVC,OAAO;IACT;IACA;QACEH,IAAI;QACJC,OAAO;QACPC,UAAU;QACVC,OAAO;IACT;IACA;QACEH,IAAI;QACJC,OAAO;QACPC,UAAU;QACVC,OAAO;IACT;IACA;QACEH,IAAI;QACJC,OAAO;QACPC,UAAU;QACVC,OAAO;IACT;CACD,CAAA;AAED;;;;CAIC,GACD,OAAO,MAAMC,sBAAyC;IACpD;IACA;IACA;IACA;IACA;IACA;IACA;IACA;CACD,CAAA;AAwBD;;;;;;CAMC,GACD,OAAO,MAAMC,wBAA4C;IACvDC,UAAU;IACVC,UAAU;IACVC,SAAS;IACTC,MAAM;IACNC,OAAO;IACPC,aAAa;IACbC,cAAc;AAChB,EAAC;AAED,gGAAgG,GAChG,OAAO,SAASC,gBACdC,OAAsG;QAE/FpB,aAA4DoB;IAAnE,QAAOpB,cAAAA,WAAWoB,QAAQL,IAAI,aAAvBf,cAA4BC,cAAcmB,QAAQR,QAAQ,GAAEQ,oBAAAA,QAAQP,QAAQ,YAAhBO,oBAAoB;AACzF;AAEA,+EAA+E,GAC/E,OAAO,SAASC,mBACdD,OAA2B;QAGpBf;IADP,IAAI,CAACe,QAAQN,OAAO,EAAE,OAAO;IAC7B,QAAOT,+BAAAA,uBAAuBiB,IAAI,CAAC,CAACC,QAAUA,MAAMjB,EAAE,KAAKc,QAAQN,OAAO,aAAnET,+BAAwE;AACjF;AAEA;;;;CAIC,GACD,OAAO,SAASmB,mBACdJ,OAA2B,EAC3BK,UAAmC,CAAC,CAAC;IAErC,2EAA2E;IAC3E,MAAMC,OAAOtB,gBAAgBqB,QAAQE,SAAS;IAC9C,MAAMf,WAAWQ,QAAQR,QAAQ,CAACgB,IAAI;IACtC,IAAI,CAAChB,UAAU,OAAO;IACtB,IAAIA,SAASiB,MAAM,GAAG3B,yBAAyB;QAC7C,OAAO,CAAC,qCAAqC,EAAEA,wBAAwB,YAAY,CAAC;IACtF;IACA,IAAIkB,QAAQP,QAAQ,CAACe,IAAI,GAAGC,MAAM,GAAG3B,yBAAyB;QAC5D,OAAO,CAAC,+BAA+B,EAAEA,wBAAwB,YAAY,CAAC;IAChF;IACA,IAAI,CAAC4B,OAAOC,SAAS,CAACX,QAAQJ,KAAK,KAAKI,QAAQJ,KAAK,GAAGU,KAAKM,GAAG,IAAIZ,QAAQJ,KAAK,GAAGU,KAAKO,GAAG,EAAE;QAC5F,OAAO,CAAC,uBAAuB,EAAEP,KAAKM,GAAG,CAAC,IAAI,EAAEN,KAAKO,GAAG,CAAC,OAAO,CAAC;IACnE;IACA,IAAIb,QAAQN,OAAO,KAAK,QAAQ,CAACO,mBAAmBD,UAAU;QAC5D,OAAO;IACT;IACA,IAAI,CAAC,AAACjB,oBAA0C+B,QAAQ,CAACd,QAAQH,WAAW,GAAG;QAC7E,OAAO;IACT;IACA,OAAO;AACT;AAEA;;;;CAIC,GACD,OAAO,SAASkB,iBAAiBf,OAA2B;IAC1D,MAAMR,WAAWQ,QAAQR,QAAQ,CAACgB,IAAI;IACtC,MAAMf,WAAWO,QAAQP,QAAQ,CAACe,IAAI;IACtC,MAAMd,UAAUO,mBAAmBD;IACnC,MAAMgB,YAAY;QAChB,CAAC,EAAE,EAAEhB,QAAQJ,KAAK,CAAC,kBAAkB,EAAEJ,SAAS,CAAC,CAAC;KACnD;IACD,IAAIC,UAAUuB,UAAUC,IAAI,CAAC,CAAC,UAAU,EAAExB,SAAS,CAAC,CAAC;IACrD,IAAIC,SAAS;QACXsB,UAAUC,IAAI,CACZ,CAAC,wBAAwB,EAAEvB,QAAQP,KAAK,CAAC,UAAU,EAAEO,QAAQL,KAAK,CAAC6B,WAAW,GAAG,CAAC,CAAC;IAEvF;IACA,qEAAqE;IACrEF,UAAUC,IAAI,CAAC,CAAC,UAAU,EAAElB,gBAAgBC,SAASb,KAAK,CAAC+B,WAAW,GAAG,MAAM,CAAC;IAChF,2EAA2E;IAC3E,wEAAwE;IACxE,oEAAoE;IACpEF,UAAUC,IAAI,CACZjB,QAAQJ,KAAK,KAAK,IACd,gFACA;IAEN,IAAIH,UAAU;QACZ,uEAAuE;QACvE,wEAAwE;QACxE,+DAA+D;QAC/DuB,UAAUC,IAAI,CAAC,CAAC,8BAA8B,EAAExB,SAAS,oBAAoB,CAAC;IAChF;IACA,OAAOuB,UAAUG,IAAI,CAAC;AACxB;AAEA;;;;;;;;;;;;CAYC,GACD,OAAO,SAASC,kBACdpB,OAA2B;;IAE3B,MAAMN,UAAUO,mBAAmBD;IACnC,MAAML,OAAOI,gBAAgBC;IAC7B,OAAO;QACLqB,cAAcrB,QAAQR,QAAQ,CAACgB,IAAI;QACnCf,UAAUO,QAAQP,QAAQ,CAACe,IAAI;QAC/Bc,OAAO,UAAE5B,2BAAAA,QAASR,EAAE,mBAAIS,KAAK2B,OAAO;QACpCC,UAAU5B,KAAKT,EAAE;QACjBU,OAAOI,QAAQJ,KAAK;QACpB4B,cAAc;QACdC,MAAM;QACNC,OAAO;QACP7B,aAAaG,QAAQH,WAAW;QAChCC,cAAcE,QAAQF,YAAY;IACpC;AACF;AAEA,sGAAsG,GACtG,OAAO,MAAM6B,2BAA2B;IACtC;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;CACD,CAAS;AAEV,4FAA4F,GAC5F,OAAO,SAASC,oBACdC,MAA4D;IAE5D,IAAI,CAACA,QAAQ,OAAO;IACpB,MAAMC,OAAgC,CAAC;IACvC,KAAK,MAAMC,OAAOJ,yBAA0B;QAC1C,MAAMK,QAAQH,MAAM,CAACE,IAAI;QACzB,IAAI,OAAOC,UAAU,YAAY,OAAOA,UAAU,YAAY,OAAOA,UAAU,WAAWF,IAAI,CAACC,IAAI,GAAGC;IACxG;IACA,OAAOC,OAAOC,IAAI,CAACJ,MAAMrB,MAAM,GAAGqB,OAAO;AAC3C;AAEA;;;;;;CAMC,GACD,OAAO,SAASK,6BACdN,MAA4D,EAC5DxB,UAAmC,CAAC,CAAC;;QAa7BzB;IAXR,MAAMwD,OAAO,CAACL,MAAiB,QAAOF,0BAAAA,MAAQ,CAACE,IAAI,MAAK,WAAW,AAACF,MAAM,CAACE,IAAI,CAAYvB,IAAI,KAAK;IACpG,MAAMhB,WAAW4C,KAAK;IACtB,IAAI,CAAC5C,UAAU,OAAO;IACtB,MAAMc,OAAOtB,gBAAgBqB,QAAQE,SAAS;IAC9C,MAAMX,QAAQ,QAAOiC,0BAAAA,MAAQ,CAAC,QAAQ,MAAK,WAAWQ,KAAKC,KAAK,CAACT,MAAM,CAAC,QAAQ,IAActC,sBAAsBK,KAAK;IACzH,MAAM0B,UAAUc,KAAK;IACrB,MAAMvC,cAAcuC,KAAK;IACzB,OAAO;QACL5C;QACAC,UAAU2C,KAAK;QACf1C,SAAST,uBAAuBsD,IAAI,CAAC,CAACpC,QAAUA,MAAMjB,EAAE,KAAKoC,WAAWA,UAAU;QAClF3B,IAAI,WAAEf,cAAAA,WAAWwD,KAAK,iCAAhBxD,YAA8BM,EAAE,mBAAI;QAC1CU,OAAOyC,KAAKzB,GAAG,CAACN,KAAKO,GAAG,EAAEwB,KAAKxB,GAAG,CAACP,KAAKM,GAAG,EAAEhB;QAC7CC,aAAa,AAACd,oBAA0C+B,QAAQ,CAACjB,eAC5DA,cACDN,sBAAsBM,WAAW;QACrCC,cAAcO,QAAQE,SAAS,GAAG,QAAQsB,CAAAA,0BAAAA,MAAQ,CAAC,eAAe,MAAK;IACzE;AACF"}
|
|
@@ -1065,6 +1065,38 @@ export declare const AI_PLAN_SETTLED_FORM: {
|
|
|
1065
1065
|
* uses are already full is left for the rule to ask about.
|
|
1066
1066
|
*/
|
|
1067
1067
|
export declare function aiSettlePlanForms(plan: AiBuildPlan, inventory: AiSiteInventory | null, capabilities?: AiPlanCapabilities | null): AiBuildPlan;
|
|
1068
|
+
/**
|
|
1069
|
+
* The home page places no form where another page holds it (AGL-3899).
|
|
1070
|
+
*
|
|
1071
|
+
* Told that one form may be placed on several pages, 17 of the 22 newest
|
|
1072
|
+
* production site starts (2026-10-12 → 10-16) placed the contact form on the
|
|
1073
|
+
* home as well as on its Contact page, 16 of them in the last band, so every
|
|
1074
|
+
* home ended on the same form. The form belongs to the page a visitor opens
|
|
1075
|
+
* to get in touch (`aiLayoutFormPageOfPlan` already sends every call to
|
|
1076
|
+
* action there); the home closes with a call to action whose button links it.
|
|
1077
|
+
*
|
|
1078
|
+
* Settled, never re-asked, after the forms are (`aiSettlePlanForms`), once
|
|
1079
|
+
* the plan's references are resolved:
|
|
1080
|
+
* - where another page places the form, every home section that places it
|
|
1081
|
+
* takes it out of its uses, and a section named for the form is renamed
|
|
1082
|
+
* for what it still is — "Closing call to action with contact form" is
|
|
1083
|
+
* the closing call to action, "Contact us" is get in touch — so rule 3
|
|
1084
|
+
* asks for nothing;
|
|
1085
|
+
* - where no other page places it but one reads as the contact page (its
|
|
1086
|
+
* title or address: Contact, Book, Visit, Enroll…), the form moves there,
|
|
1087
|
+
* into the section that reads as a way to reach the business, else its
|
|
1088
|
+
* last;
|
|
1089
|
+
* - where neither, the home keeps it: a missing form is never invented,
|
|
1090
|
+
* and a form with no page is no form at all.
|
|
1091
|
+
* A section named for a sign-up or a newsletter keeps what it places: that
|
|
1092
|
+
* is a different ask, and a store's sign-up is its own platform part.
|
|
1093
|
+
*
|
|
1094
|
+
* `keep` is a start whose home keeps its form: a one-page site, a landing
|
|
1095
|
+
* or coming-soon page, or a brief that asks for it there.
|
|
1096
|
+
*/
|
|
1097
|
+
export declare function aiSettlePlanHomeForms(plan: AiBuildPlan, inventory: AiSiteInventory | null, options: {
|
|
1098
|
+
keep: boolean;
|
|
1099
|
+
}): AiBuildPlan;
|
|
1068
1100
|
/**
|
|
1069
1101
|
* Rule 2 (plan): every screen declares a layout, no section is a copy of a
|
|
1070
1102
|
* layout region, and no section places a layout, which frames a whole screen
|
|
@@ -4457,6 +4457,117 @@ const FORM_SECTION_NAME = /\bform\b|\bsign[- ]?up\b|\bsubscribe\b|\bnewsletter\b
|
|
|
4457
4457
|
}))
|
|
4458
4458
|
});
|
|
4459
4459
|
}
|
|
4460
|
+
/** A page whose title or address says it is where a visitor gets in touch. */ const CONTACT_PAGE_NAME = /\b(?:contact|get in touch|book(?:ing)?|reserve|reservations?|visit|enroll|enrol|register|rsvp|inquir(?:e|y)|enquir(?:e|y)|quote|appointments?|join|start a project|hire)\b/i;
|
|
4461
|
+
/** A section name's words that ask for a form, and what each reads as once the form is on another page. */ const HOME_FORM_WORDS = [
|
|
4462
|
+
[
|
|
4463
|
+
RegExp("\\s*(?:,\\s*)?\\b(?:with|and|plus|including|via|through)\\s+(?:an?\\s+|the\\s+|our\\s+)?(?:[\\u0041-\\u005A\\u0061-\\u007A\\u00AA\\u00B5\\u00BA\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02C1\\u02C6-\\u02D1\\u02E0-\\u02E4\\u02EC\\u02EE\\u0370-\\u0374\\u0376-\\u0377\\u037A-\\u037D\\u037F\\u0386\\u0388-\\u038A\\u038C\\u038E-\\u03A1\\u03A3-\\u03F5\\u03F7-\\u0481\\u048A-\\u052F\\u0531-\\u0556\\u0559\\u0560-\\u0588\\u05D0-\\u05EA\\u05EF-\\u05F2\\u0620-\\u064A\\u066E-\\u066F\\u0671-\\u06D3\\u06D5\\u06E5-\\u06E6\\u06EE-\\u06EF\\u06FA-\\u06FC\\u06FF\\u0710\\u0712-\\u072F\\u074D-\\u07A5\\u07B1\\u07CA-\\u07EA\\u07F4-\\u07F5\\u07FA\\u0800-\\u0815\\u081A\\u0824\\u0828\\u0840-\\u0858\\u0860-\\u086A\\u0870-\\u0887\\u0889-\\u088F\\u08A0-\\u08C9\\u0904-\\u0939\\u093D\\u0950\\u0958-\\u0961\\u0971-\\u0980\\u0985-\\u098C\\u098F-\\u0990\\u0993-\\u09A8\\u09AA-\\u09B0\\u09B2\\u09B6-\\u09B9\\u09BD\\u09CE\\u09DC-\\u09DD\\u09DF-\\u09E1\\u09F0-\\u09F1\\u09FC\\u0A05-\\u0A0A\\u0A0F-\\u0A10\\u0A13-\\u0A28\\u0A2A-\\u0A30\\u0A32-\\u0A33\\u0A35-\\u0A36\\u0A38-\\u0A39\\u0A59-\\u0A5C\\u0A5E\\u0A72-\\u0A74\\u0A85-\\u0A8D\\u0A8F-\\u0A91\\u0A93-\\u0AA8\\u0AAA-\\u0AB0\\u0AB2-\\u0AB3\\u0AB5-\\u0AB9\\u0ABD\\u0AD0\\u0AE0-\\u0AE1\\u0AF9\\u0B05-\\u0B0C\\u0B0F-\\u0B10\\u0B13-\\u0B28\\u0B2A-\\u0B30\\u0B32-\\u0B33\\u0B35-\\u0B39\\u0B3D\\u0B5C-\\u0B5D\\u0B5F-\\u0B61\\u0B71\\u0B83\\u0B85-\\u0B8A\\u0B8E-\\u0B90\\u0B92-\\u0B95\\u0B99-\\u0B9A\\u0B9C\\u0B9E-\\u0B9F\\u0BA3-\\u0BA4\\u0BA8-\\u0BAA\\u0BAE-\\u0BB9\\u0BD0\\u0C05-\\u0C0C\\u0C0E-\\u0C10\\u0C12-\\u0C28\\u0C2A-\\u0C39\\u0C3D\\u0C58-\\u0C5A\\u0C5C-\\u0C5D\\u0C60-\\u0C61\\u0C80\\u0C85-\\u0C8C\\u0C8E-\\u0C90\\u0C92-\\u0CA8\\u0CAA-\\u0CB3\\u0CB5-\\u0CB9\\u0CBD\\u0CDC-\\u0CDE\\u0CE0-\\u0CE1\\u0CF1-\\u0CF2\\u0D04-\\u0D0C\\u0D0E-\\u0D10\\u0D12-\\u0D3A\\u0D3D\\u0D4E\\u0D54-\\u0D56\\u0D5F-\\u0D61\\u0D7A-\\u0D7F\\u0D85-\\u0D96\\u0D9A-\\u0DB1\\u0DB3-\\u0DBB\\u0DBD\\u0DC0-\\u0DC6\\u0E01-\\u0E30\\u0E32-\\u0E33\\u0E40-\\u0E46\\u0E81-\\u0E82\\u0E84\\u0E86-\\u0E8A\\u0E8C-\\u0EA3\\u0EA5\\u0EA7-\\u0EB0\\u0EB2-\\u0EB3\\u0EBD\\u0EC0-\\u0EC4\\u0EC6\\u0EDC-\\u0EDF\\u0F00\\u0F40-\\u0F47\\u0F49-\\u0F6C\\u0F88-\\u0F8C\\u1000-\\u102A\\u103F\\u1050-\\u1055\\u105A-\\u105D\\u1061\\u1065-\\u1066\\u106E-\\u1070\\u1075-\\u1081\\u108E\\u10A0-\\u10C5\\u10C7\\u10CD\\u10D0-\\u10FA\\u10FC-\\u1248\\u124A-\\u124D\\u1250-\\u1256\\u1258\\u125A-\\u125D\\u1260-\\u1288\\u128A-\\u128D\\u1290-\\u12B0\\u12B2-\\u12B5\\u12B8-\\u12BE\\u12C0\\u12C2-\\u12C5\\u12C8-\\u12D6\\u12D8-\\u1310\\u1312-\\u1315\\u1318-\\u135A\\u1380-\\u138F\\u13A0-\\u13F5\\u13F8-\\u13FD\\u1401-\\u166C\\u166F-\\u167F\\u1681-\\u169A\\u16A0-\\u16EA\\u16F1-\\u16F8\\u1700-\\u1711\\u171F-\\u1731\\u1740-\\u1751\\u1760-\\u176C\\u176E-\\u1770\\u1780-\\u17B3\\u17D7\\u17DC\\u1820-\\u1878\\u1880-\\u1884\\u1887-\\u18A8\\u18AA\\u18B0-\\u18F5\\u1900-\\u191E\\u1950-\\u196D\\u1970-\\u1974\\u1980-\\u19AB\\u19B0-\\u19C9\\u1A00-\\u1A16\\u1A20-\\u1A54\\u1AA7\\u1B05-\\u1B33\\u1B45-\\u1B4C\\u1B83-\\u1BA0\\u1BAE-\\u1BAF\\u1BBA-\\u1BE5\\u1C00-\\u1C23\\u1C4D-\\u1C4F\\u1C5A-\\u1C7D\\u1C80-\\u1C8A\\u1C90-\\u1CBA\\u1CBD-\\u1CBF\\u1CE9-\\u1CEC\\u1CEE-\\u1CF3\\u1CF5-\\u1CF6\\u1CFA\\u1D00-\\u1DBF\\u1E00-\\u1F15\\u1F18-\\u1F1D\\u1F20-\\u1F45\\u1F48-\\u1F4D\\u1F50-\\u1F57\\u1F59\\u1F5B\\u1F5D\\u1F5F-\\u1F7D\\u1F80-\\u1FB4\\u1FB6-\\u1FBC\\u1FBE\\u1FC2-\\u1FC4\\u1FC6-\\u1FCC\\u1FD0-\\u1FD3\\u1FD6-\\u1FDB\\u1FE0-\\u1FEC\\u1FF2-\\u1FF4\\u1FF6-\\u1FFC\\u2071\\u207F\\u2090-\\u209C\\u2102\\u2107\\u210A-\\u2113\\u2115\\u2119-\\u211D\\u2124\\u2126\\u2128\\u212A-\\u212D\\u212F-\\u2139\\u213C-\\u213F\\u2145-\\u2149\\u214E\\u2183-\\u2184\\u2C00-\\u2CE4\\u2CEB-\\u2CEE\\u2CF2-\\u2CF3\\u2D00-\\u2D25\\u2D27\\u2D2D\\u2D30-\\u2D67\\u2D6F\\u2D80-\\u2D96\\u2DA0-\\u2DA6\\u2DA8-\\u2DAE\\u2DB0-\\u2DB6\\u2DB8-\\u2DBE\\u2DC0-\\u2DC6\\u2DC8-\\u2DCE\\u2DD0-\\u2DD6\\u2DD8-\\u2DDE\\u2E2F\\u3005-\\u3006\\u3031-\\u3035\\u303B-\\u303C\\u3041-\\u3096\\u309D-\\u309F\\u30A1-\\u30FA\\u30FC-\\u30FF\\u3105-\\u312F\\u3131-\\u318E\\u31A0-\\u31BF\\u31F0-\\u31FF\\u3400-\\u4DBF\\u4E00-\\uA48C\\uA4D0-\\uA4FD\\uA500-\\uA60C\\uA610-\\uA61F\\uA62A-\\uA62B\\uA640-\\uA66E\\uA67F-\\uA69D\\uA6A0-\\uA6E5\\uA717-\\uA71F\\uA722-\\uA788\\uA78B-\\uA7DC\\uA7F1-\\uA801\\uA803-\\uA805\\uA807-\\uA80A\\uA80C-\\uA822\\uA840-\\uA873\\uA882-\\uA8B3\\uA8F2-\\uA8F7\\uA8FB\\uA8FD-\\uA8FE\\uA90A-\\uA925\\uA930-\\uA946\\uA960-\\uA97C\\uA984-\\uA9B2\\uA9CF\\uA9E0-\\uA9E4\\uA9E6-\\uA9EF\\uA9FA-\\uA9FE\\uAA00-\\uAA28\\uAA40-\\uAA42\\uAA44-\\uAA4B\\uAA60-\\uAA76\\uAA7A\\uAA7E-\\uAAAF\\uAAB1\\uAAB5-\\uAAB6\\uAAB9-\\uAABD\\uAAC0\\uAAC2\\uAADB-\\uAADD\\uAAE0-\\uAAEA\\uAAF2-\\uAAF4\\uAB01-\\uAB06\\uAB09-\\uAB0E\\uAB11-\\uAB16\\uAB20-\\uAB26\\uAB28-\\uAB2E\\uAB30-\\uAB5A\\uAB5C-\\uAB69\\uAB70-\\uABE2\\uAC00-\\uD7A3\\uD7B0-\\uD7C6\\uD7CB-\\uD7FB\\uF900-\\uFA6D\\uFA70-\\uFAD9\\uFB00-\\uFB06\\uFB13-\\uFB17\\uFB1D\\uFB1F-\\uFB28\\uFB2A-\\uFB36\\uFB38-\\uFB3C\\uFB3E\\uFB40-\\uFB41\\uFB43-\\uFB44\\uFB46-\\uFBB1\\uFBD3-\\uFD3D\\uFD50-\\uFD8F\\uFD92-\\uFDC7\\uFDF0-\\uFDFB\\uFE70-\\uFE74\\uFE76-\\uFEFC\\uFF21-\\uFF3A\\uFF41-\\uFF5A\\uFF66-\\uFFBE\\uFFC2-\\uFFC7\\uFFCA-\\uFFCF\\uFFD2-\\uFFD7\\uFFDA-\\uFFDC\\u{10000}-\\u{1000B}\\u{1000D}-\\u{10026}\\u{10028}-\\u{1003A}\\u{1003C}-\\u{1003D}\\u{1003F}-\\u{1004D}\\u{10050}-\\u{1005D}\\u{10080}-\\u{100FA}\\u{10280}-\\u{1029C}\\u{102A0}-\\u{102D0}\\u{10300}-\\u{1031F}\\u{1032D}-\\u{10340}\\u{10342}-\\u{10349}\\u{10350}-\\u{10375}\\u{10380}-\\u{1039D}\\u{103A0}-\\u{103C3}\\u{103C8}-\\u{103CF}\\u{10400}-\\u{1049D}\\u{104B0}-\\u{104D3}\\u{104D8}-\\u{104FB}\\u{10500}-\\u{10527}\\u{10530}-\\u{10563}\\u{10570}-\\u{1057A}\\u{1057C}-\\u{1058A}\\u{1058C}-\\u{10592}\\u{10594}-\\u{10595}\\u{10597}-\\u{105A1}\\u{105A3}-\\u{105B1}\\u{105B3}-\\u{105B9}\\u{105BB}-\\u{105BC}\\u{105C0}-\\u{105F3}\\u{10600}-\\u{10736}\\u{10740}-\\u{10755}\\u{10760}-\\u{10767}\\u{10780}-\\u{10785}\\u{10787}-\\u{107B0}\\u{107B2}-\\u{107BA}\\u{10800}-\\u{10805}\\u{10808}\\u{1080A}-\\u{10835}\\u{10837}-\\u{10838}\\u{1083C}\\u{1083F}-\\u{10855}\\u{10860}-\\u{10876}\\u{10880}-\\u{1089E}\\u{108E0}-\\u{108F2}\\u{108F4}-\\u{108F5}\\u{10900}-\\u{10915}\\u{10920}-\\u{10939}\\u{10940}-\\u{10959}\\u{10980}-\\u{109B7}\\u{109BE}-\\u{109BF}\\u{10A00}\\u{10A10}-\\u{10A13}\\u{10A15}-\\u{10A17}\\u{10A19}-\\u{10A35}\\u{10A60}-\\u{10A7C}\\u{10A80}-\\u{10A9C}\\u{10AC0}-\\u{10AC7}\\u{10AC9}-\\u{10AE4}\\u{10B00}-\\u{10B35}\\u{10B40}-\\u{10B55}\\u{10B60}-\\u{10B72}\\u{10B80}-\\u{10B91}\\u{10C00}-\\u{10C48}\\u{10C80}-\\u{10CB2}\\u{10CC0}-\\u{10CF2}\\u{10D00}-\\u{10D23}\\u{10D4A}-\\u{10D65}\\u{10D6F}-\\u{10D85}\\u{10E80}-\\u{10EA9}\\u{10EB0}-\\u{10EB1}\\u{10EC2}-\\u{10EC7}\\u{10F00}-\\u{10F1C}\\u{10F27}\\u{10F30}-\\u{10F45}\\u{10F70}-\\u{10F81}\\u{10FB0}-\\u{10FC4}\\u{10FE0}-\\u{10FF6}\\u{11003}-\\u{11037}\\u{11071}-\\u{11072}\\u{11075}\\u{11083}-\\u{110AF}\\u{110D0}-\\u{110E8}\\u{11103}-\\u{11126}\\u{11144}\\u{11147}\\u{11150}-\\u{11172}\\u{11176}\\u{11183}-\\u{111B2}\\u{111C1}-\\u{111C4}\\u{111DA}\\u{111DC}\\u{11200}-\\u{11211}\\u{11213}-\\u{1122B}\\u{1123F}-\\u{11240}\\u{11280}-\\u{11286}\\u{11288}\\u{1128A}-\\u{1128D}\\u{1128F}-\\u{1129D}\\u{1129F}-\\u{112A8}\\u{112B0}-\\u{112DE}\\u{11305}-\\u{1130C}\\u{1130F}-\\u{11310}\\u{11313}-\\u{11328}\\u{1132A}-\\u{11330}\\u{11332}-\\u{11333}\\u{11335}-\\u{11339}\\u{1133D}\\u{11350}\\u{1135D}-\\u{11361}\\u{11380}-\\u{11389}\\u{1138B}\\u{1138E}\\u{11390}-\\u{113B5}\\u{113B7}\\u{113D1}\\u{113D3}\\u{11400}-\\u{11434}\\u{11447}-\\u{1144A}\\u{1145F}-\\u{11461}\\u{11480}-\\u{114AF}\\u{114C4}-\\u{114C5}\\u{114C7}\\u{11580}-\\u{115AE}\\u{115D8}-\\u{115DB}\\u{11600}-\\u{1162F}\\u{11644}\\u{11680}-\\u{116AA}\\u{116B8}\\u{11700}-\\u{1171A}\\u{11740}-\\u{11746}\\u{11800}-\\u{1182B}\\u{118A0}-\\u{118DF}\\u{118FF}-\\u{11906}\\u{11909}\\u{1190C}-\\u{11913}\\u{11915}-\\u{11916}\\u{11918}-\\u{1192F}\\u{1193F}\\u{11941}\\u{119A0}-\\u{119A7}\\u{119AA}-\\u{119D0}\\u{119E1}\\u{119E3}\\u{11A00}\\u{11A0B}-\\u{11A32}\\u{11A3A}\\u{11A50}\\u{11A5C}-\\u{11A89}\\u{11A9D}\\u{11AB0}-\\u{11AF8}\\u{11BC0}-\\u{11BE0}\\u{11C00}-\\u{11C08}\\u{11C0A}-\\u{11C2E}\\u{11C40}\\u{11C72}-\\u{11C8F}\\u{11D00}-\\u{11D06}\\u{11D08}-\\u{11D09}\\u{11D0B}-\\u{11D30}\\u{11D46}\\u{11D60}-\\u{11D65}\\u{11D67}-\\u{11D68}\\u{11D6A}-\\u{11D89}\\u{11D98}\\u{11DB0}-\\u{11DDB}\\u{11EE0}-\\u{11EF2}\\u{11F02}\\u{11F04}-\\u{11F10}\\u{11F12}-\\u{11F33}\\u{11FB0}\\u{12000}-\\u{12399}\\u{12480}-\\u{12543}\\u{12F90}-\\u{12FF0}\\u{13000}-\\u{1342F}\\u{13441}-\\u{13446}\\u{13460}-\\u{143FA}\\u{14400}-\\u{14646}\\u{16100}-\\u{1611D}\\u{16800}-\\u{16A38}\\u{16A40}-\\u{16A5E}\\u{16A70}-\\u{16ABE}\\u{16AD0}-\\u{16AED}\\u{16B00}-\\u{16B2F}\\u{16B40}-\\u{16B43}\\u{16B63}-\\u{16B77}\\u{16B7D}-\\u{16B8F}\\u{16D40}-\\u{16D6C}\\u{16E40}-\\u{16E7F}\\u{16EA0}-\\u{16EB8}\\u{16EBB}-\\u{16ED3}\\u{16F00}-\\u{16F4A}\\u{16F50}\\u{16F93}-\\u{16F9F}\\u{16FE0}-\\u{16FE1}\\u{16FE3}\\u{16FF2}-\\u{16FF3}\\u{17000}-\\u{18CD5}\\u{18CFF}-\\u{18D1E}\\u{18D80}-\\u{18DF2}\\u{1AFF0}-\\u{1AFF3}\\u{1AFF5}-\\u{1AFFB}\\u{1AFFD}-\\u{1AFFE}\\u{1B000}-\\u{1B122}\\u{1B132}\\u{1B150}-\\u{1B152}\\u{1B155}\\u{1B164}-\\u{1B167}\\u{1B170}-\\u{1B2FB}\\u{1BC00}-\\u{1BC6A}\\u{1BC70}-\\u{1BC7C}\\u{1BC80}-\\u{1BC88}\\u{1BC90}-\\u{1BC99}\\u{1D400}-\\u{1D454}\\u{1D456}-\\u{1D49C}\\u{1D49E}-\\u{1D49F}\\u{1D4A2}\\u{1D4A5}-\\u{1D4A6}\\u{1D4A9}-\\u{1D4AC}\\u{1D4AE}-\\u{1D4B9}\\u{1D4BB}\\u{1D4BD}-\\u{1D4C3}\\u{1D4C5}-\\u{1D505}\\u{1D507}-\\u{1D50A}\\u{1D50D}-\\u{1D514}\\u{1D516}-\\u{1D51C}\\u{1D51E}-\\u{1D539}\\u{1D53B}-\\u{1D53E}\\u{1D540}-\\u{1D544}\\u{1D546}\\u{1D54A}-\\u{1D550}\\u{1D552}-\\u{1D6A5}\\u{1D6A8}-\\u{1D6C0}\\u{1D6C2}-\\u{1D6DA}\\u{1D6DC}-\\u{1D6FA}\\u{1D6FC}-\\u{1D714}\\u{1D716}-\\u{1D734}\\u{1D736}-\\u{1D74E}\\u{1D750}-\\u{1D76E}\\u{1D770}-\\u{1D788}\\u{1D78A}-\\u{1D7A8}\\u{1D7AA}-\\u{1D7C2}\\u{1D7C4}-\\u{1D7CB}\\u{1DF00}-\\u{1DF1E}\\u{1DF25}-\\u{1DF2A}\\u{1E030}-\\u{1E06D}\\u{1E100}-\\u{1E12C}\\u{1E137}-\\u{1E13D}\\u{1E14E}\\u{1E290}-\\u{1E2AD}\\u{1E2C0}-\\u{1E2EB}\\u{1E4D0}-\\u{1E4EB}\\u{1E5D0}-\\u{1E5ED}\\u{1E5F0}\\u{1E6C0}-\\u{1E6DE}\\u{1E6E0}-\\u{1E6E2}\\u{1E6E4}-\\u{1E6E5}\\u{1E6E7}-\\u{1E6ED}\\u{1E6F0}-\\u{1E6F4}\\u{1E6FE}-\\u{1E6FF}\\u{1E7E0}-\\u{1E7E6}\\u{1E7E8}-\\u{1E7EB}\\u{1E7ED}-\\u{1E7EE}\\u{1E7F0}-\\u{1E7FE}\\u{1E800}-\\u{1E8C4}\\u{1E900}-\\u{1E943}\\u{1E94B}\\u{1EE00}-\\u{1EE03}\\u{1EE05}-\\u{1EE1F}\\u{1EE21}-\\u{1EE22}\\u{1EE24}\\u{1EE27}\\u{1EE29}-\\u{1EE32}\\u{1EE34}-\\u{1EE37}\\u{1EE39}\\u{1EE3B}\\u{1EE42}\\u{1EE47}\\u{1EE49}\\u{1EE4B}\\u{1EE4D}-\\u{1EE4F}\\u{1EE51}-\\u{1EE52}\\u{1EE54}\\u{1EE57}\\u{1EE59}\\u{1EE5B}\\u{1EE5D}\\u{1EE5F}\\u{1EE61}-\\u{1EE62}\\u{1EE64}\\u{1EE67}-\\u{1EE6A}\\u{1EE6C}-\\u{1EE72}\\u{1EE74}-\\u{1EE77}\\u{1EE79}-\\u{1EE7C}\\u{1EE7E}\\u{1EE80}-\\u{1EE89}\\u{1EE8B}-\\u{1EE9B}\\u{1EEA1}-\\u{1EEA3}\\u{1EEA5}-\\u{1EEA9}\\u{1EEAB}-\\u{1EEBB}\\u{20000}-\\u{2A6DF}\\u{2A700}-\\u{2B81D}\\u{2B820}-\\u{2CEAD}\\u{2CEB0}-\\u{2EBE0}\\u{2EBF0}-\\u{2EE5D}\\u{2F800}-\\u{2FA1D}\\u{30000}-\\u{3134A}\\u{31350}-\\u{33479}-]+\\s+){0,3}?form\\b", "giu"),
|
|
4464
|
+
''
|
|
4465
|
+
],
|
|
4466
|
+
[
|
|
4467
|
+
/\b(?:(?:contact|inquiry|enquiry|booking|consultation|request|quote|lead|intake|reservation|appointment)\s+)?form\b/gi,
|
|
4468
|
+
''
|
|
4469
|
+
],
|
|
4470
|
+
[
|
|
4471
|
+
/\bcontact us\b/gi,
|
|
4472
|
+
'get in touch'
|
|
4473
|
+
],
|
|
4474
|
+
[
|
|
4475
|
+
/\b(?:get|request) a quote\b/gi,
|
|
4476
|
+
'quote call to action'
|
|
4477
|
+
],
|
|
4478
|
+
[
|
|
4479
|
+
/\b(?:en|in)quiry\b/gi,
|
|
4480
|
+
'call to action'
|
|
4481
|
+
],
|
|
4482
|
+
[
|
|
4483
|
+
/\bregist(?:er|ration)\b/gi,
|
|
4484
|
+
'call to action'
|
|
4485
|
+
]
|
|
4486
|
+
];
|
|
4487
|
+
/** A home section's name once the form it asked for lives on another page: still what it was about, never a form. */ function homeSectionWithoutForm(name, last) {
|
|
4488
|
+
let next = name;
|
|
4489
|
+
for (const [words, instead] of HOME_FORM_WORDS)next = next.replace(words, instead);
|
|
4490
|
+
next = next.replace(/\s{2,}/g, ' ').replace(/^[\s\-–—:,&+]+|[\s\-–—:,&+]+$/g, '').trim();
|
|
4491
|
+
if (!next || FORM_SECTION_NAME.test(next) || !RegExp("[\\u0041-\\u005A\\u0061-\\u007A\\u00AA\\u00B5\\u00BA\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02C1\\u02C6-\\u02D1\\u02E0-\\u02E4\\u02EC\\u02EE\\u0370-\\u0374\\u0376-\\u0377\\u037A-\\u037D\\u037F\\u0386\\u0388-\\u038A\\u038C\\u038E-\\u03A1\\u03A3-\\u03F5\\u03F7-\\u0481\\u048A-\\u052F\\u0531-\\u0556\\u0559\\u0560-\\u0588\\u05D0-\\u05EA\\u05EF-\\u05F2\\u0620-\\u064A\\u066E-\\u066F\\u0671-\\u06D3\\u06D5\\u06E5-\\u06E6\\u06EE-\\u06EF\\u06FA-\\u06FC\\u06FF\\u0710\\u0712-\\u072F\\u074D-\\u07A5\\u07B1\\u07CA-\\u07EA\\u07F4-\\u07F5\\u07FA\\u0800-\\u0815\\u081A\\u0824\\u0828\\u0840-\\u0858\\u0860-\\u086A\\u0870-\\u0887\\u0889-\\u088F\\u08A0-\\u08C9\\u0904-\\u0939\\u093D\\u0950\\u0958-\\u0961\\u0971-\\u0980\\u0985-\\u098C\\u098F-\\u0990\\u0993-\\u09A8\\u09AA-\\u09B0\\u09B2\\u09B6-\\u09B9\\u09BD\\u09CE\\u09DC-\\u09DD\\u09DF-\\u09E1\\u09F0-\\u09F1\\u09FC\\u0A05-\\u0A0A\\u0A0F-\\u0A10\\u0A13-\\u0A28\\u0A2A-\\u0A30\\u0A32-\\u0A33\\u0A35-\\u0A36\\u0A38-\\u0A39\\u0A59-\\u0A5C\\u0A5E\\u0A72-\\u0A74\\u0A85-\\u0A8D\\u0A8F-\\u0A91\\u0A93-\\u0AA8\\u0AAA-\\u0AB0\\u0AB2-\\u0AB3\\u0AB5-\\u0AB9\\u0ABD\\u0AD0\\u0AE0-\\u0AE1\\u0AF9\\u0B05-\\u0B0C\\u0B0F-\\u0B10\\u0B13-\\u0B28\\u0B2A-\\u0B30\\u0B32-\\u0B33\\u0B35-\\u0B39\\u0B3D\\u0B5C-\\u0B5D\\u0B5F-\\u0B61\\u0B71\\u0B83\\u0B85-\\u0B8A\\u0B8E-\\u0B90\\u0B92-\\u0B95\\u0B99-\\u0B9A\\u0B9C\\u0B9E-\\u0B9F\\u0BA3-\\u0BA4\\u0BA8-\\u0BAA\\u0BAE-\\u0BB9\\u0BD0\\u0C05-\\u0C0C\\u0C0E-\\u0C10\\u0C12-\\u0C28\\u0C2A-\\u0C39\\u0C3D\\u0C58-\\u0C5A\\u0C5C-\\u0C5D\\u0C60-\\u0C61\\u0C80\\u0C85-\\u0C8C\\u0C8E-\\u0C90\\u0C92-\\u0CA8\\u0CAA-\\u0CB3\\u0CB5-\\u0CB9\\u0CBD\\u0CDC-\\u0CDE\\u0CE0-\\u0CE1\\u0CF1-\\u0CF2\\u0D04-\\u0D0C\\u0D0E-\\u0D10\\u0D12-\\u0D3A\\u0D3D\\u0D4E\\u0D54-\\u0D56\\u0D5F-\\u0D61\\u0D7A-\\u0D7F\\u0D85-\\u0D96\\u0D9A-\\u0DB1\\u0DB3-\\u0DBB\\u0DBD\\u0DC0-\\u0DC6\\u0E01-\\u0E30\\u0E32-\\u0E33\\u0E40-\\u0E46\\u0E81-\\u0E82\\u0E84\\u0E86-\\u0E8A\\u0E8C-\\u0EA3\\u0EA5\\u0EA7-\\u0EB0\\u0EB2-\\u0EB3\\u0EBD\\u0EC0-\\u0EC4\\u0EC6\\u0EDC-\\u0EDF\\u0F00\\u0F40-\\u0F47\\u0F49-\\u0F6C\\u0F88-\\u0F8C\\u1000-\\u102A\\u103F\\u1050-\\u1055\\u105A-\\u105D\\u1061\\u1065-\\u1066\\u106E-\\u1070\\u1075-\\u1081\\u108E\\u10A0-\\u10C5\\u10C7\\u10CD\\u10D0-\\u10FA\\u10FC-\\u1248\\u124A-\\u124D\\u1250-\\u1256\\u1258\\u125A-\\u125D\\u1260-\\u1288\\u128A-\\u128D\\u1290-\\u12B0\\u12B2-\\u12B5\\u12B8-\\u12BE\\u12C0\\u12C2-\\u12C5\\u12C8-\\u12D6\\u12D8-\\u1310\\u1312-\\u1315\\u1318-\\u135A\\u1380-\\u138F\\u13A0-\\u13F5\\u13F8-\\u13FD\\u1401-\\u166C\\u166F-\\u167F\\u1681-\\u169A\\u16A0-\\u16EA\\u16F1-\\u16F8\\u1700-\\u1711\\u171F-\\u1731\\u1740-\\u1751\\u1760-\\u176C\\u176E-\\u1770\\u1780-\\u17B3\\u17D7\\u17DC\\u1820-\\u1878\\u1880-\\u1884\\u1887-\\u18A8\\u18AA\\u18B0-\\u18F5\\u1900-\\u191E\\u1950-\\u196D\\u1970-\\u1974\\u1980-\\u19AB\\u19B0-\\u19C9\\u1A00-\\u1A16\\u1A20-\\u1A54\\u1AA7\\u1B05-\\u1B33\\u1B45-\\u1B4C\\u1B83-\\u1BA0\\u1BAE-\\u1BAF\\u1BBA-\\u1BE5\\u1C00-\\u1C23\\u1C4D-\\u1C4F\\u1C5A-\\u1C7D\\u1C80-\\u1C8A\\u1C90-\\u1CBA\\u1CBD-\\u1CBF\\u1CE9-\\u1CEC\\u1CEE-\\u1CF3\\u1CF5-\\u1CF6\\u1CFA\\u1D00-\\u1DBF\\u1E00-\\u1F15\\u1F18-\\u1F1D\\u1F20-\\u1F45\\u1F48-\\u1F4D\\u1F50-\\u1F57\\u1F59\\u1F5B\\u1F5D\\u1F5F-\\u1F7D\\u1F80-\\u1FB4\\u1FB6-\\u1FBC\\u1FBE\\u1FC2-\\u1FC4\\u1FC6-\\u1FCC\\u1FD0-\\u1FD3\\u1FD6-\\u1FDB\\u1FE0-\\u1FEC\\u1FF2-\\u1FF4\\u1FF6-\\u1FFC\\u2071\\u207F\\u2090-\\u209C\\u2102\\u2107\\u210A-\\u2113\\u2115\\u2119-\\u211D\\u2124\\u2126\\u2128\\u212A-\\u212D\\u212F-\\u2139\\u213C-\\u213F\\u2145-\\u2149\\u214E\\u2183-\\u2184\\u2C00-\\u2CE4\\u2CEB-\\u2CEE\\u2CF2-\\u2CF3\\u2D00-\\u2D25\\u2D27\\u2D2D\\u2D30-\\u2D67\\u2D6F\\u2D80-\\u2D96\\u2DA0-\\u2DA6\\u2DA8-\\u2DAE\\u2DB0-\\u2DB6\\u2DB8-\\u2DBE\\u2DC0-\\u2DC6\\u2DC8-\\u2DCE\\u2DD0-\\u2DD6\\u2DD8-\\u2DDE\\u2E2F\\u3005-\\u3006\\u3031-\\u3035\\u303B-\\u303C\\u3041-\\u3096\\u309D-\\u309F\\u30A1-\\u30FA\\u30FC-\\u30FF\\u3105-\\u312F\\u3131-\\u318E\\u31A0-\\u31BF\\u31F0-\\u31FF\\u3400-\\u4DBF\\u4E00-\\uA48C\\uA4D0-\\uA4FD\\uA500-\\uA60C\\uA610-\\uA61F\\uA62A-\\uA62B\\uA640-\\uA66E\\uA67F-\\uA69D\\uA6A0-\\uA6E5\\uA717-\\uA71F\\uA722-\\uA788\\uA78B-\\uA7DC\\uA7F1-\\uA801\\uA803-\\uA805\\uA807-\\uA80A\\uA80C-\\uA822\\uA840-\\uA873\\uA882-\\uA8B3\\uA8F2-\\uA8F7\\uA8FB\\uA8FD-\\uA8FE\\uA90A-\\uA925\\uA930-\\uA946\\uA960-\\uA97C\\uA984-\\uA9B2\\uA9CF\\uA9E0-\\uA9E4\\uA9E6-\\uA9EF\\uA9FA-\\uA9FE\\uAA00-\\uAA28\\uAA40-\\uAA42\\uAA44-\\uAA4B\\uAA60-\\uAA76\\uAA7A\\uAA7E-\\uAAAF\\uAAB1\\uAAB5-\\uAAB6\\uAAB9-\\uAABD\\uAAC0\\uAAC2\\uAADB-\\uAADD\\uAAE0-\\uAAEA\\uAAF2-\\uAAF4\\uAB01-\\uAB06\\uAB09-\\uAB0E\\uAB11-\\uAB16\\uAB20-\\uAB26\\uAB28-\\uAB2E\\uAB30-\\uAB5A\\uAB5C-\\uAB69\\uAB70-\\uABE2\\uAC00-\\uD7A3\\uD7B0-\\uD7C6\\uD7CB-\\uD7FB\\uF900-\\uFA6D\\uFA70-\\uFAD9\\uFB00-\\uFB06\\uFB13-\\uFB17\\uFB1D\\uFB1F-\\uFB28\\uFB2A-\\uFB36\\uFB38-\\uFB3C\\uFB3E\\uFB40-\\uFB41\\uFB43-\\uFB44\\uFB46-\\uFBB1\\uFBD3-\\uFD3D\\uFD50-\\uFD8F\\uFD92-\\uFDC7\\uFDF0-\\uFDFB\\uFE70-\\uFE74\\uFE76-\\uFEFC\\uFF21-\\uFF3A\\uFF41-\\uFF5A\\uFF66-\\uFFBE\\uFFC2-\\uFFC7\\uFFCA-\\uFFCF\\uFFD2-\\uFFD7\\uFFDA-\\uFFDC\\u{10000}-\\u{1000B}\\u{1000D}-\\u{10026}\\u{10028}-\\u{1003A}\\u{1003C}-\\u{1003D}\\u{1003F}-\\u{1004D}\\u{10050}-\\u{1005D}\\u{10080}-\\u{100FA}\\u{10280}-\\u{1029C}\\u{102A0}-\\u{102D0}\\u{10300}-\\u{1031F}\\u{1032D}-\\u{10340}\\u{10342}-\\u{10349}\\u{10350}-\\u{10375}\\u{10380}-\\u{1039D}\\u{103A0}-\\u{103C3}\\u{103C8}-\\u{103CF}\\u{10400}-\\u{1049D}\\u{104B0}-\\u{104D3}\\u{104D8}-\\u{104FB}\\u{10500}-\\u{10527}\\u{10530}-\\u{10563}\\u{10570}-\\u{1057A}\\u{1057C}-\\u{1058A}\\u{1058C}-\\u{10592}\\u{10594}-\\u{10595}\\u{10597}-\\u{105A1}\\u{105A3}-\\u{105B1}\\u{105B3}-\\u{105B9}\\u{105BB}-\\u{105BC}\\u{105C0}-\\u{105F3}\\u{10600}-\\u{10736}\\u{10740}-\\u{10755}\\u{10760}-\\u{10767}\\u{10780}-\\u{10785}\\u{10787}-\\u{107B0}\\u{107B2}-\\u{107BA}\\u{10800}-\\u{10805}\\u{10808}\\u{1080A}-\\u{10835}\\u{10837}-\\u{10838}\\u{1083C}\\u{1083F}-\\u{10855}\\u{10860}-\\u{10876}\\u{10880}-\\u{1089E}\\u{108E0}-\\u{108F2}\\u{108F4}-\\u{108F5}\\u{10900}-\\u{10915}\\u{10920}-\\u{10939}\\u{10940}-\\u{10959}\\u{10980}-\\u{109B7}\\u{109BE}-\\u{109BF}\\u{10A00}\\u{10A10}-\\u{10A13}\\u{10A15}-\\u{10A17}\\u{10A19}-\\u{10A35}\\u{10A60}-\\u{10A7C}\\u{10A80}-\\u{10A9C}\\u{10AC0}-\\u{10AC7}\\u{10AC9}-\\u{10AE4}\\u{10B00}-\\u{10B35}\\u{10B40}-\\u{10B55}\\u{10B60}-\\u{10B72}\\u{10B80}-\\u{10B91}\\u{10C00}-\\u{10C48}\\u{10C80}-\\u{10CB2}\\u{10CC0}-\\u{10CF2}\\u{10D00}-\\u{10D23}\\u{10D4A}-\\u{10D65}\\u{10D6F}-\\u{10D85}\\u{10E80}-\\u{10EA9}\\u{10EB0}-\\u{10EB1}\\u{10EC2}-\\u{10EC7}\\u{10F00}-\\u{10F1C}\\u{10F27}\\u{10F30}-\\u{10F45}\\u{10F70}-\\u{10F81}\\u{10FB0}-\\u{10FC4}\\u{10FE0}-\\u{10FF6}\\u{11003}-\\u{11037}\\u{11071}-\\u{11072}\\u{11075}\\u{11083}-\\u{110AF}\\u{110D0}-\\u{110E8}\\u{11103}-\\u{11126}\\u{11144}\\u{11147}\\u{11150}-\\u{11172}\\u{11176}\\u{11183}-\\u{111B2}\\u{111C1}-\\u{111C4}\\u{111DA}\\u{111DC}\\u{11200}-\\u{11211}\\u{11213}-\\u{1122B}\\u{1123F}-\\u{11240}\\u{11280}-\\u{11286}\\u{11288}\\u{1128A}-\\u{1128D}\\u{1128F}-\\u{1129D}\\u{1129F}-\\u{112A8}\\u{112B0}-\\u{112DE}\\u{11305}-\\u{1130C}\\u{1130F}-\\u{11310}\\u{11313}-\\u{11328}\\u{1132A}-\\u{11330}\\u{11332}-\\u{11333}\\u{11335}-\\u{11339}\\u{1133D}\\u{11350}\\u{1135D}-\\u{11361}\\u{11380}-\\u{11389}\\u{1138B}\\u{1138E}\\u{11390}-\\u{113B5}\\u{113B7}\\u{113D1}\\u{113D3}\\u{11400}-\\u{11434}\\u{11447}-\\u{1144A}\\u{1145F}-\\u{11461}\\u{11480}-\\u{114AF}\\u{114C4}-\\u{114C5}\\u{114C7}\\u{11580}-\\u{115AE}\\u{115D8}-\\u{115DB}\\u{11600}-\\u{1162F}\\u{11644}\\u{11680}-\\u{116AA}\\u{116B8}\\u{11700}-\\u{1171A}\\u{11740}-\\u{11746}\\u{11800}-\\u{1182B}\\u{118A0}-\\u{118DF}\\u{118FF}-\\u{11906}\\u{11909}\\u{1190C}-\\u{11913}\\u{11915}-\\u{11916}\\u{11918}-\\u{1192F}\\u{1193F}\\u{11941}\\u{119A0}-\\u{119A7}\\u{119AA}-\\u{119D0}\\u{119E1}\\u{119E3}\\u{11A00}\\u{11A0B}-\\u{11A32}\\u{11A3A}\\u{11A50}\\u{11A5C}-\\u{11A89}\\u{11A9D}\\u{11AB0}-\\u{11AF8}\\u{11BC0}-\\u{11BE0}\\u{11C00}-\\u{11C08}\\u{11C0A}-\\u{11C2E}\\u{11C40}\\u{11C72}-\\u{11C8F}\\u{11D00}-\\u{11D06}\\u{11D08}-\\u{11D09}\\u{11D0B}-\\u{11D30}\\u{11D46}\\u{11D60}-\\u{11D65}\\u{11D67}-\\u{11D68}\\u{11D6A}-\\u{11D89}\\u{11D98}\\u{11DB0}-\\u{11DDB}\\u{11EE0}-\\u{11EF2}\\u{11F02}\\u{11F04}-\\u{11F10}\\u{11F12}-\\u{11F33}\\u{11FB0}\\u{12000}-\\u{12399}\\u{12480}-\\u{12543}\\u{12F90}-\\u{12FF0}\\u{13000}-\\u{1342F}\\u{13441}-\\u{13446}\\u{13460}-\\u{143FA}\\u{14400}-\\u{14646}\\u{16100}-\\u{1611D}\\u{16800}-\\u{16A38}\\u{16A40}-\\u{16A5E}\\u{16A70}-\\u{16ABE}\\u{16AD0}-\\u{16AED}\\u{16B00}-\\u{16B2F}\\u{16B40}-\\u{16B43}\\u{16B63}-\\u{16B77}\\u{16B7D}-\\u{16B8F}\\u{16D40}-\\u{16D6C}\\u{16E40}-\\u{16E7F}\\u{16EA0}-\\u{16EB8}\\u{16EBB}-\\u{16ED3}\\u{16F00}-\\u{16F4A}\\u{16F50}\\u{16F93}-\\u{16F9F}\\u{16FE0}-\\u{16FE1}\\u{16FE3}\\u{16FF2}-\\u{16FF3}\\u{17000}-\\u{18CD5}\\u{18CFF}-\\u{18D1E}\\u{18D80}-\\u{18DF2}\\u{1AFF0}-\\u{1AFF3}\\u{1AFF5}-\\u{1AFFB}\\u{1AFFD}-\\u{1AFFE}\\u{1B000}-\\u{1B122}\\u{1B132}\\u{1B150}-\\u{1B152}\\u{1B155}\\u{1B164}-\\u{1B167}\\u{1B170}-\\u{1B2FB}\\u{1BC00}-\\u{1BC6A}\\u{1BC70}-\\u{1BC7C}\\u{1BC80}-\\u{1BC88}\\u{1BC90}-\\u{1BC99}\\u{1D400}-\\u{1D454}\\u{1D456}-\\u{1D49C}\\u{1D49E}-\\u{1D49F}\\u{1D4A2}\\u{1D4A5}-\\u{1D4A6}\\u{1D4A9}-\\u{1D4AC}\\u{1D4AE}-\\u{1D4B9}\\u{1D4BB}\\u{1D4BD}-\\u{1D4C3}\\u{1D4C5}-\\u{1D505}\\u{1D507}-\\u{1D50A}\\u{1D50D}-\\u{1D514}\\u{1D516}-\\u{1D51C}\\u{1D51E}-\\u{1D539}\\u{1D53B}-\\u{1D53E}\\u{1D540}-\\u{1D544}\\u{1D546}\\u{1D54A}-\\u{1D550}\\u{1D552}-\\u{1D6A5}\\u{1D6A8}-\\u{1D6C0}\\u{1D6C2}-\\u{1D6DA}\\u{1D6DC}-\\u{1D6FA}\\u{1D6FC}-\\u{1D714}\\u{1D716}-\\u{1D734}\\u{1D736}-\\u{1D74E}\\u{1D750}-\\u{1D76E}\\u{1D770}-\\u{1D788}\\u{1D78A}-\\u{1D7A8}\\u{1D7AA}-\\u{1D7C2}\\u{1D7C4}-\\u{1D7CB}\\u{1DF00}-\\u{1DF1E}\\u{1DF25}-\\u{1DF2A}\\u{1E030}-\\u{1E06D}\\u{1E100}-\\u{1E12C}\\u{1E137}-\\u{1E13D}\\u{1E14E}\\u{1E290}-\\u{1E2AD}\\u{1E2C0}-\\u{1E2EB}\\u{1E4D0}-\\u{1E4EB}\\u{1E5D0}-\\u{1E5ED}\\u{1E5F0}\\u{1E6C0}-\\u{1E6DE}\\u{1E6E0}-\\u{1E6E2}\\u{1E6E4}-\\u{1E6E5}\\u{1E6E7}-\\u{1E6ED}\\u{1E6F0}-\\u{1E6F4}\\u{1E6FE}-\\u{1E6FF}\\u{1E7E0}-\\u{1E7E6}\\u{1E7E8}-\\u{1E7EB}\\u{1E7ED}-\\u{1E7EE}\\u{1E7F0}-\\u{1E7FE}\\u{1E800}-\\u{1E8C4}\\u{1E900}-\\u{1E943}\\u{1E94B}\\u{1EE00}-\\u{1EE03}\\u{1EE05}-\\u{1EE1F}\\u{1EE21}-\\u{1EE22}\\u{1EE24}\\u{1EE27}\\u{1EE29}-\\u{1EE32}\\u{1EE34}-\\u{1EE37}\\u{1EE39}\\u{1EE3B}\\u{1EE42}\\u{1EE47}\\u{1EE49}\\u{1EE4B}\\u{1EE4D}-\\u{1EE4F}\\u{1EE51}-\\u{1EE52}\\u{1EE54}\\u{1EE57}\\u{1EE59}\\u{1EE5B}\\u{1EE5D}\\u{1EE5F}\\u{1EE61}-\\u{1EE62}\\u{1EE64}\\u{1EE67}-\\u{1EE6A}\\u{1EE6C}-\\u{1EE72}\\u{1EE74}-\\u{1EE77}\\u{1EE79}-\\u{1EE7C}\\u{1EE7E}\\u{1EE80}-\\u{1EE89}\\u{1EE8B}-\\u{1EE9B}\\u{1EEA1}-\\u{1EEA3}\\u{1EEA5}-\\u{1EEA9}\\u{1EEAB}-\\u{1EEBB}\\u{20000}-\\u{2A6DF}\\u{2A700}-\\u{2B81D}\\u{2B820}-\\u{2CEAD}\\u{2CEB0}-\\u{2EBE0}\\u{2EBF0}-\\u{2EE5D}\\u{2F800}-\\u{2FA1D}\\u{30000}-\\u{3134A}\\u{31350}-\\u{33479}]", "u").test(next)) return last ? 'Closing call to action' : 'Call to action';
|
|
4492
|
+
return next.charAt(0).toUpperCase() + next.slice(1);
|
|
4493
|
+
}
|
|
4494
|
+
/**
|
|
4495
|
+
* The home page places no form where another page holds it (AGL-3899).
|
|
4496
|
+
*
|
|
4497
|
+
* Told that one form may be placed on several pages, 17 of the 22 newest
|
|
4498
|
+
* production site starts (2026-10-12 → 10-16) placed the contact form on the
|
|
4499
|
+
* home as well as on its Contact page, 16 of them in the last band, so every
|
|
4500
|
+
* home ended on the same form. The form belongs to the page a visitor opens
|
|
4501
|
+
* to get in touch (`aiLayoutFormPageOfPlan` already sends every call to
|
|
4502
|
+
* action there); the home closes with a call to action whose button links it.
|
|
4503
|
+
*
|
|
4504
|
+
* Settled, never re-asked, after the forms are (`aiSettlePlanForms`), once
|
|
4505
|
+
* the plan's references are resolved:
|
|
4506
|
+
* - where another page places the form, every home section that places it
|
|
4507
|
+
* takes it out of its uses, and a section named for the form is renamed
|
|
4508
|
+
* for what it still is — "Closing call to action with contact form" is
|
|
4509
|
+
* the closing call to action, "Contact us" is get in touch — so rule 3
|
|
4510
|
+
* asks for nothing;
|
|
4511
|
+
* - where no other page places it but one reads as the contact page (its
|
|
4512
|
+
* title or address: Contact, Book, Visit, Enroll…), the form moves there,
|
|
4513
|
+
* into the section that reads as a way to reach the business, else its
|
|
4514
|
+
* last;
|
|
4515
|
+
* - where neither, the home keeps it: a missing form is never invented,
|
|
4516
|
+
* and a form with no page is no form at all.
|
|
4517
|
+
* A section named for a sign-up or a newsletter keeps what it places: that
|
|
4518
|
+
* is a different ask, and a store's sign-up is its own platform part.
|
|
4519
|
+
*
|
|
4520
|
+
* `keep` is a start whose home keeps its form: a one-page site, a landing
|
|
4521
|
+
* or coming-soon page, or a brief that asks for it there.
|
|
4522
|
+
*/ export function aiSettlePlanHomeForms(plan, inventory, options) {
|
|
4523
|
+
if (options.keep || plan.screens.length < 2) return plan;
|
|
4524
|
+
const homeIndex = plan.screens.findIndex((screen)=>screen.slug.trim().replace(/^\/+|\/+$/g, '') === '');
|
|
4525
|
+
if (homeIndex < 0) return plan;
|
|
4526
|
+
const kinds = inventoryKinds(inventory);
|
|
4527
|
+
const isForm = (ref)=>refKind(ref, plan, kinds) === 'form';
|
|
4528
|
+
const signup = (name)=>NEWSLETTER_NAME.test(name);
|
|
4529
|
+
const home = plan.screens[homeIndex];
|
|
4530
|
+
const placing = home.sections.flatMap((section, index)=>!signup(section.name) && section.uses.some(isForm) ? [
|
|
4531
|
+
index
|
|
4532
|
+
] : []);
|
|
4533
|
+
if (!placing.length) return plan;
|
|
4534
|
+
let screens = plan.screens;
|
|
4535
|
+
const elsewhere = screens.some((screen, index)=>index !== homeIndex && screen.sections.some((section)=>section.uses.some(isForm)));
|
|
4536
|
+
if (!elsewhere) {
|
|
4537
|
+
var _ref, _ref1, _roomy_find;
|
|
4538
|
+
const target = screens.findIndex((screen, index)=>index !== homeIndex && screen.sections.length > 0 && CONTACT_PAGE_NAME.test(`${screen.title} ${screen.slug.replace(/[-/]+/g, ' ')}`));
|
|
4539
|
+
if (target < 0) return plan;
|
|
4540
|
+
const form = home.sections[placing[0]].uses.find(isForm);
|
|
4541
|
+
const page = screens[target];
|
|
4542
|
+
const roomy = page.sections.flatMap((section, index)=>section.uses.length < AI_BUILD_PLAN_LIMITS.uses ? [
|
|
4543
|
+
index
|
|
4544
|
+
] : []);
|
|
4545
|
+
const at = (_ref = (_ref1 = (_roomy_find = roomy.find((index)=>FORM_SECTION_NAME.test(page.sections[index].name))) != null ? _roomy_find : roomy.find((index)=>index > 0 && REACH_SECTION_NAME.test(page.sections[index].name))) != null ? _ref1 : roomy.filter((index)=>index > 0).at(-1)) != null ? _ref : roomy.at(-1);
|
|
4546
|
+
if (at === undefined) return plan;
|
|
4547
|
+
screens = screens.map((screen, index)=>index !== target ? screen : _extends({}, screen, {
|
|
4548
|
+
sections: screen.sections.map((section, sectionIndex)=>sectionIndex === at ? _extends({}, section, {
|
|
4549
|
+
uses: [
|
|
4550
|
+
...section.uses,
|
|
4551
|
+
form
|
|
4552
|
+
]
|
|
4553
|
+
}) : section)
|
|
4554
|
+
}));
|
|
4555
|
+
}
|
|
4556
|
+
const lastIndex = home.sections.length - 1;
|
|
4557
|
+
return _extends({}, plan, {
|
|
4558
|
+
screens: screens.map((screen, index)=>index !== homeIndex ? screen : _extends({}, screen, {
|
|
4559
|
+
sections: screen.sections.map((section, sectionIndex)=>{
|
|
4560
|
+
if (!placing.includes(sectionIndex) && !(FORM_SECTION_NAME.test(section.name) && !signup(section.name))) return section;
|
|
4561
|
+
const uses = section.uses.filter((ref)=>!isForm(ref));
|
|
4562
|
+
const name = FORM_SECTION_NAME.test(section.name) ? homeSectionWithoutForm(section.name, sectionIndex === lastIndex) : section.name;
|
|
4563
|
+
return _extends({}, section, {
|
|
4564
|
+
name,
|
|
4565
|
+
uses
|
|
4566
|
+
});
|
|
4567
|
+
})
|
|
4568
|
+
}))
|
|
4569
|
+
});
|
|
4570
|
+
}
|
|
4460
4571
|
/** The forms a section that collects answers can place, as the re-ask names them. */ function planFormChoicesText(plan, inventory) {
|
|
4461
4572
|
const choices = planFormChoices(plan, inventory);
|
|
4462
4573
|
if (!choices.length) return 'Plan one in create as a form, and put new:<its name> in the section\'s uses.';
|
|
@@ -4659,7 +4770,9 @@ const REUSE_BEFORE_CREATE_KINDS = new Set([
|
|
|
4659
4770
|
};
|
|
4660
4771
|
plan.create.forEach((entry, index)=>{
|
|
4661
4772
|
if (!REUSE_BEFORE_CREATE_KINDS.has(entry.kind) || entry.duplicateOf) return;
|
|
4662
|
-
|
|
4773
|
+
// A site start's dataset whose records are another site's is made again,
|
|
4774
|
+
// never reused (AGL-3912), so a creation of its name is not a duplicate.
|
|
4775
|
+
const existing = rows[entry.kind].find((row)=>aiNamesMatch(row.name, entry.name) && !row.foreign);
|
|
4663
4776
|
if (!existing) return;
|
|
4664
4777
|
violations.push({
|
|
4665
4778
|
rule: 7,
|
|
@@ -5163,17 +5276,21 @@ function slugKey(slug) {
|
|
|
5163
5276
|
* that plan is still re-asked.
|
|
5164
5277
|
*/ export const AI_FREE_WALL_SETTLE_MAX_OVER = 2;
|
|
5165
5278
|
/** A section through which a visitor reaches the owner: contact, a visit, a booking. */ const REACH_SECTION_NAME = /\b(?:contact|get in touch|visit(?: us)?|find us|locations?|hours|book(?:ing)?|reserve|reservations?)\b/i;
|
|
5279
|
+
/** A section that asks a visitor to act: a call to action, "Start your practice", "Get started". */ const CALL_TO_ACTION_NAME = /\b(?:call to action|cta|get started|start (?:your|a|here|now|today)|join (?:us|now|today|the)|ready to)\b/i;
|
|
5166
5280
|
/**
|
|
5167
5281
|
* Whether the Free wall's trim keeps a section of a page (AGL-3737): the
|
|
5168
5282
|
* page's opening, any section that places something (a dataset it lists, a
|
|
5169
5283
|
* form, a component — dropping it would leave a creation unplaced), one that
|
|
5170
|
-
* collects answers
|
|
5284
|
+
* collects answers, says how to reach the owner or asks a visitor to act
|
|
5285
|
+
* (AGL-3899: a home's call to action is its way to the form), and a storefront's
|
|
5171
5286
|
* listing parts — the bestsellers, the collections, the sign-up, and a Shop
|
|
5172
5287
|
* page's product grid.
|
|
5173
5288
|
*/ function freeWallKeeps(screen, index) {
|
|
5174
5289
|
const section = screen.sections[index];
|
|
5175
5290
|
if (index === 0 || section.uses.length > 0) return true;
|
|
5176
5291
|
if (FORM_SECTION_NAME.test(section.name) || REACH_SECTION_NAME.test(section.name)) return true;
|
|
5292
|
+
// A call to action is how a home with no form reaches the page that has it (AGL-3899).
|
|
5293
|
+
if (CALL_TO_ACTION_NAME.test(section.name)) return true;
|
|
5177
5294
|
if (aiStorefrontSays('featured', section.name) || aiStorefrontSays('collections', section.name) || aiStorefrontSays('signup', section.name)) {
|
|
5178
5295
|
return true;
|
|
5179
5296
|
}
|