@aglyn/plugins-ai 1.0.0-beta.231 → 1.0.0-beta.232
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/activity/ai-activity-actions.js +8 -4
- package/src/lib/activity/ai-activity-actions.js.map +1 -1
- package/src/lib/components/ai-job-follow.component.js +1 -0
- package/src/lib/components/ai-job-follow.component.js.map +1 -1
- package/src/lib/components/ai-job-links.js +13 -0
- package/src/lib/components/ai-job-links.js.map +1 -1
- package/src/lib/components/ai-jobs-list-page.component.js +1 -0
- package/src/lib/components/ai-jobs-list-page.component.js.map +1 -1
- package/src/lib/components/ai-media-create.component.d.ts +20 -7
- package/src/lib/components/ai-media-create.component.js +107 -55
- package/src/lib/components/ai-media-create.component.js.map +1 -1
- package/src/lib/components/ai-seo-fields-card.component.js +1 -0
- package/src/lib/components/ai-seo-fields-card.component.js.map +1 -1
- package/src/lib/components/ai-site-memory-card.component.d.ts +32 -0
- package/src/lib/components/ai-site-memory-card.component.js +187 -0
- package/src/lib/components/ai-site-memory-card.component.js.map +1 -0
- package/src/lib/components/ai-site-start-card.component.js +80 -77
- package/src/lib/components/ai-site-start-card.component.js.map +1 -1
- package/src/lib/components/ai-theme-proposal-card.component.js +4 -0
- package/src/lib/components/ai-theme-proposal-card.component.js.map +1 -1
- package/src/lib/components/ai-usage-strip.component.js +1 -0
- package/src/lib/components/ai-usage-strip.component.js.map +1 -1
- package/src/lib/components/assist-panel.component.d.ts +10 -0
- package/src/lib/components/assist-panel.component.js +119 -9
- package/src/lib/components/assist-panel.component.js.map +1 -1
- package/src/lib/components/assist-signals-page.component.js +2 -0
- package/src/lib/components/assist-signals-page.component.js.map +1 -1
- package/src/lib/components/billing-ai-allotments.component.js +3 -0
- package/src/lib/components/billing-ai-allotments.component.js.map +1 -1
- package/src/lib/components/billing-ai-top-users.component.js +1 -0
- package/src/lib/components/billing-ai-top-users.component.js.map +1 -1
- package/src/lib/components/billing-assist-overage-card.component.js +2 -0
- package/src/lib/components/billing-assist-overage-card.component.js.map +1 -1
- package/src/lib/components/member-ai-usage-card.component.js +10 -0
- package/src/lib/components/member-ai-usage-card.component.js.map +1 -1
- package/src/lib/components/staff-org-ai-card.component.js +1 -0
- package/src/lib/components/staff-org-ai-card.component.js.map +1 -1
- package/src/lib/components/staff-org-ai-conversations.component.d.ts +23 -0
- package/src/lib/components/staff-org-ai-conversations.component.js +346 -0
- package/src/lib/components/staff-org-ai-conversations.component.js.map +1 -0
- package/src/lib/jobs/ai-build-capabilities.d.ts +13 -1
- package/src/lib/jobs/ai-build-capabilities.js +101 -7
- package/src/lib/jobs/ai-build-capabilities.js.map +1 -1
- package/src/lib/jobs/ai-business-profile-prefill.d.ts +29 -0
- package/src/lib/jobs/ai-business-profile-prefill.js +85 -0
- package/src/lib/jobs/ai-business-profile-prefill.js.map +1 -0
- package/src/lib/jobs/ai-job-build-step.js +3 -1
- package/src/lib/jobs/ai-job-build-step.js.map +1 -1
- package/src/lib/jobs/ai-job-edit-step.d.ts +225 -0
- package/src/lib/jobs/ai-job-edit-step.js +631 -0
- package/src/lib/jobs/ai-job-edit-step.js.map +1 -0
- package/src/lib/jobs/ai-job-insight-step.js +21 -4
- package/src/lib/jobs/ai-job-insight-step.js.map +1 -1
- package/src/lib/jobs/ai-job-layout-language.d.ts +8 -3
- package/src/lib/jobs/ai-job-layout-language.js +8 -2
- package/src/lib/jobs/ai-job-layout-language.js.map +1 -1
- package/src/lib/jobs/ai-job-layout-step.js +7 -6
- package/src/lib/jobs/ai-job-layout-step.js.map +1 -1
- package/src/lib/jobs/ai-job-logic-step.d.ts +20 -0
- package/src/lib/jobs/ai-job-logic-step.js +87 -6
- package/src/lib/jobs/ai-job-logic-step.js.map +1 -1
- package/src/lib/jobs/ai-job-page-language.d.ts +52 -7
- package/src/lib/jobs/ai-job-page-language.js +72 -9
- package/src/lib/jobs/ai-job-page-language.js.map +1 -1
- package/src/lib/jobs/ai-job-page-step.js +12 -2
- package/src/lib/jobs/ai-job-page-step.js.map +1 -1
- package/src/lib/jobs/ai-job-plan-step.d.ts +19 -5
- package/src/lib/jobs/ai-job-plan-step.js +54 -20
- package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
- package/src/lib/jobs/ai-job-site-content.d.ts +163 -0
- package/src/lib/jobs/ai-job-site-content.js +443 -0
- package/src/lib/jobs/ai-job-site-content.js.map +1 -0
- package/src/lib/jobs/ai-job-site-look.d.ts +112 -0
- package/src/lib/jobs/ai-job-site-look.js +322 -0
- package/src/lib/jobs/ai-job-site-look.js.map +1 -0
- package/src/lib/jobs/ai-job-site-step.d.ts +26 -5
- package/src/lib/jobs/ai-job-site-step.js +163 -24
- package/src/lib/jobs/ai-job-site-step.js.map +1 -1
- package/src/lib/jobs/ai-jobs-notify.d.ts +6 -1
- package/src/lib/jobs/ai-jobs-notify.js +15 -3
- package/src/lib/jobs/ai-jobs-notify.js.map +1 -1
- package/src/lib/layout-language/ai-layout-compiler.d.ts +9 -0
- package/src/lib/layout-language/ai-layout-compiler.js +120 -33
- package/src/lib/layout-language/ai-layout-compiler.js.map +1 -1
- package/src/lib/layout-language/ai-layout-frame.js +5 -4
- package/src/lib/layout-language/ai-layout-frame.js.map +1 -1
- package/src/lib/layout-language/ai-layout-language.d.ts +12 -5
- package/src/lib/layout-language/ai-layout-language.js +92 -9
- package/src/lib/layout-language/ai-layout-language.js.map +1 -1
- package/src/lib/layout-language/ai-layout-pictures.d.ts +144 -0
- package/src/lib/layout-language/ai-layout-pictures.js +214 -0
- package/src/lib/layout-language/ai-layout-pictures.js.map +1 -0
- package/src/lib/layout-language/ai-layout-store.d.ts +2 -0
- package/src/lib/layout-language/ai-layout-store.js +7 -1
- package/src/lib/layout-language/ai-layout-store.js.map +1 -1
- package/src/lib/model/ai-build-progress.js +4 -1
- package/src/lib/model/ai-build-progress.js.map +1 -1
- package/src/lib/model/ai-edit-job.d.ts +109 -0
- package/src/lib/model/ai-edit-job.js +157 -0
- package/src/lib/model/ai-edit-job.js.map +1 -0
- package/src/lib/model/ai-job-activity.js +1 -0
- package/src/lib/model/ai-job-activity.js.map +1 -1
- package/src/lib/model/ai-jobs.types.d.ts +3 -1
- package/src/lib/model/ai-jobs.types.js.map +1 -1
- package/src/lib/model/ai-logic-job.d.ts +18 -1
- package/src/lib/model/ai-logic-job.js +24 -3
- package/src/lib/model/ai-logic-job.js.map +1 -1
- package/src/lib/model/ai-media-credits.d.ts +8 -4
- package/src/lib/model/ai-media-credits.js +11 -4
- package/src/lib/model/ai-media-credits.js.map +1 -1
- package/src/lib/model/ai-media-kinds.d.ts +68 -0
- package/src/lib/model/ai-media-kinds.js +98 -0
- package/src/lib/model/ai-media-kinds.js.map +1 -0
- package/src/lib/model/ai-site-build-progress.d.ts +4 -0
- package/src/lib/model/ai-site-build-progress.js +28 -11
- package/src/lib/model/ai-site-build-progress.js.map +1 -1
- package/src/lib/model/ai-site-context.d.ts +111 -0
- package/src/lib/model/ai-site-context.js +222 -0
- package/src/lib/model/ai-site-context.js.map +1 -0
- package/src/lib/model/ai-site-job.d.ts +10 -2
- package/src/lib/model/ai-site-job.js +12 -5
- package/src/lib/model/ai-site-job.js.map +1 -1
- package/src/lib/model/ai-site-kinds.d.ts +164 -0
- package/src/lib/model/ai-site-kinds.js +1942 -0
- package/src/lib/model/ai-site-kinds.js.map +1 -0
- package/src/lib/model/ai-site-look.d.ts +150 -0
- package/src/lib/model/ai-site-look.js +1236 -0
- package/src/lib/model/ai-site-look.js.map +1 -0
- package/src/lib/model/ai-site-memory.d.ts +80 -0
- package/src/lib/model/ai-site-memory.js +171 -0
- package/src/lib/model/ai-site-memory.js.map +1 -0
- package/src/lib/model/ai-site-start.d.ts +10 -1
- package/src/lib/model/ai-site-start.js +16 -2
- package/src/lib/model/ai-site-start.js.map +1 -1
- package/src/lib/model/assist-follow-up.d.ts +92 -0
- package/src/lib/model/assist-follow-up.js +128 -0
- package/src/lib/model/assist-follow-up.js.map +1 -0
- package/src/lib/plugin-config.js +2 -0
- package/src/lib/plugin-config.js.map +1 -1
- package/src/lib/plugin.js +20 -1
- package/src/lib/plugin.js.map +1 -1
- package/src/lib/providers/anthropic.d.ts +9 -1
- package/src/lib/providers/anthropic.js +143 -9
- package/src/lib/providers/anthropic.js.map +1 -1
- package/src/lib/providers/catalog.d.ts +37 -7
- package/src/lib/providers/catalog.js +103 -11
- package/src/lib/providers/catalog.js.map +1 -1
- package/src/lib/providers/contract.d.ts +29 -0
- package/src/lib/providers/contract.js.map +1 -1
- package/src/lib/providers/image-contract.d.ts +22 -1
- package/src/lib/providers/image-contract.js +40 -1
- package/src/lib/providers/image-contract.js.map +1 -1
- package/src/lib/providers/image-endpoints.d.ts +24 -0
- package/src/lib/providers/image-endpoints.js +28 -0
- package/src/lib/providers/image-endpoints.js.map +1 -0
- package/src/lib/providers/model-choice.js +23 -2
- package/src/lib/providers/model-choice.js.map +1 -1
- package/src/lib/providers/routing.js +18 -0
- package/src/lib/providers/routing.js.map +1 -1
- package/src/lib/providers/vertex-image-endpoint.d.ts +48 -0
- package/src/lib/providers/vertex-image-endpoint.js +80 -0
- package/src/lib/providers/vertex-image-endpoint.js.map +1 -0
- package/src/lib/providers/vertex-image.d.ts +13 -27
- package/src/lib/providers/vertex-image.js +15 -42
- package/src/lib/providers/vertex-image.js.map +1 -1
- package/src/lib/runtime/ai-blog-post-generation.d.ts +62 -0
- package/src/lib/runtime/ai-blog-post-generation.js +89 -0
- package/src/lib/runtime/ai-blog-post-generation.js.map +1 -0
- package/src/lib/runtime/ai-dev-env.d.ts +53 -0
- package/src/lib/runtime/ai-dev-env.js +72 -0
- package/src/lib/runtime/ai-dev-env.js.map +1 -0
- package/src/lib/runtime/ai-dev-replay.d.ts +116 -0
- package/src/lib/runtime/ai-dev-replay.js +313 -0
- package/src/lib/runtime/ai-dev-replay.js.map +1 -0
- package/src/lib/runtime/ai-doctrine.js +5 -1
- package/src/lib/runtime/ai-doctrine.js.map +1 -1
- package/src/lib/runtime/ai-eval-live.js +4 -0
- package/src/lib/runtime/ai-eval-live.js.map +1 -1
- package/src/lib/runtime/ai-eval.d.ts +13 -1
- package/src/lib/runtime/ai-eval.js +84 -1
- package/src/lib/runtime/ai-eval.js.map +1 -1
- package/src/lib/runtime/ai-live-batch.d.ts +45 -0
- package/src/lib/runtime/ai-live-batch.js +89 -0
- package/src/lib/runtime/ai-live-batch.js.map +1 -0
- package/src/lib/runtime/ai-runtime.js +46 -2
- package/src/lib/runtime/ai-runtime.js.map +1 -1
- package/src/lib/runtime/site-context.d.ts +58 -0
- package/src/lib/runtime/site-context.js +127 -0
- package/src/lib/runtime/site-context.js.map +1 -0
- package/src/lib/server/ai-admin-conversations.d.ts +22 -0
- package/src/lib/server/ai-admin-conversations.js +197 -0
- package/src/lib/server/ai-admin-conversations.js.map +1 -0
- package/src/lib/server/ai-media-image.d.ts +5 -5
- package/src/lib/server/ai-media-image.js +25 -11
- package/src/lib/server/ai-media-image.js.map +1 -1
- package/src/lib/server/ai-media-raster-prompt.d.ts +34 -0
- package/src/lib/server/ai-media-raster-prompt.js +53 -0
- package/src/lib/server/ai-media-raster-prompt.js.map +1 -0
- package/src/lib/server/assist-chat.d.ts +11 -0
- package/src/lib/server/assist-chat.js +57 -18
- package/src/lib/server/assist-chat.js.map +1 -1
- package/src/lib/server/assist-edit-applied.js +16 -3
- package/src/lib/server/assist-edit-applied.js.map +1 -1
- package/src/lib/server/assist-view-context.d.ts +19 -1
- package/src/lib/server/assist-view-context.js +38 -1
- package/src/lib/server/assist-view-context.js.map +1 -1
- package/src/lib/server.js +10 -0
- package/src/lib/server.js.map +1 -1
- package/src/lib/subprocessors.js +17 -1
- package/src/lib/subprocessors.js.map +1 -1
- package/src/lib/tools/ai-blog-post-tool.d.ts +60 -0
- package/src/lib/tools/ai-blog-post-tool.js +175 -0
- package/src/lib/tools/ai-blog-post-tool.js.map +1 -0
- package/src/lib/usage/assist-usage.d.ts +3 -0
- package/src/lib/usage/assist-usage.js +4 -2
- package/src/lib/usage/assist-usage.js.map +1 -1
- package/src/lib/usage/staff-org-ai-conversations.d.ts +103 -0
- package/src/lib/usage/staff-org-ai-conversations.js +152 -0
- package/src/lib/usage/staff-org-ai-conversations.js.map +1 -0
|
@@ -0,0 +1,1236 @@
|
|
|
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 { contrastRatio, validateThemeForPublish } from "@aglyn/aglyn/app-utils/site-theme";
|
|
18
|
+
import { DEFAULT_SITE_THEME } from "@aglyn/aglyn/app-utils/default-site";
|
|
19
|
+
import { writeThemeFonts } from "@aglyn/shared-ui-theme/util/theme-editor-fields";
|
|
20
|
+
import { AI_SITE_BASES, AI_SITE_CORNERS, AI_SITE_FONT_PAIRINGS, AI_SITE_GROUNDS, AI_SITE_RHYTHMS, aiSiteFontPairing } from "./ai-site-kinds.js";
|
|
21
|
+
/**
|
|
22
|
+
* A site's look (AGL-3660): one of the platform's base themes, and this
|
|
23
|
+
* site's own customizations layered over it as the site theme's override —
|
|
24
|
+
* the same base-and-edits shape a person's pick on Setup → Theme is stored
|
|
25
|
+
* as, so the Theme page names the base it came from and every edit there
|
|
26
|
+
* starts from what the AI chose.
|
|
27
|
+
*
|
|
28
|
+
* Everything the look decides lives in the THEME, never on a node: palette,
|
|
29
|
+
* fonts, type scale, corners, spacing, and the component defaults and styles
|
|
30
|
+
* of buttons, cards, fields, eyebrows, the header bar and the FAQ. So a Card
|
|
31
|
+
* or a Button a person drags in later looks like the rest of the site, and
|
|
32
|
+
* the layout compiler emits plain components.
|
|
33
|
+
*
|
|
34
|
+
* Three inputs decide it:
|
|
35
|
+
* - the KIND (`ai-site-kinds.ts`): the family of looks that suit the site;
|
|
36
|
+
* - the MODEL's choices for this business (`submit_site_look`), any of
|
|
37
|
+
* which may be missing;
|
|
38
|
+
* - the per-job SEED, which fills what the model left out from the kind's
|
|
39
|
+
* options and nudges every hue, chroma and size, so two jobs with the same
|
|
40
|
+
* brief and the same answers still produce two different themes.
|
|
41
|
+
*
|
|
42
|
+
* Colors are built in OKLCH and held to WCAG: body text 4.5:1 on its ground
|
|
43
|
+
* and on paper, every button label 4.5:1 on its fill, the brand color 3:1 on
|
|
44
|
+
* the page — then the whole theme passes `validateThemeForPublish`, or its
|
|
45
|
+
* colors fall back to the starter theme's.
|
|
46
|
+
*/ // ── The tokens ───────────────────────────────────────────────────────────
|
|
47
|
+
/** How cards are drawn. */ export const AI_SITE_CARDS = [
|
|
48
|
+
'flat',
|
|
49
|
+
'outlined',
|
|
50
|
+
'elevated',
|
|
51
|
+
'tinted',
|
|
52
|
+
'rule'
|
|
53
|
+
];
|
|
54
|
+
/** The form field style. */ export const AI_SITE_FIELDS = [
|
|
55
|
+
'outlined',
|
|
56
|
+
'filled',
|
|
57
|
+
'standard'
|
|
58
|
+
];
|
|
59
|
+
/** How an eyebrow above a heading is set. */ export const AI_SITE_EYEBROWS = [
|
|
60
|
+
'caps',
|
|
61
|
+
'small',
|
|
62
|
+
'rule'
|
|
63
|
+
];
|
|
64
|
+
/** What sets a plain header bar off the page. */ export const AI_SITE_HEADERS = [
|
|
65
|
+
'line',
|
|
66
|
+
'shadow',
|
|
67
|
+
'flat'
|
|
68
|
+
];
|
|
69
|
+
/** Button shapes and casing; `link` draws quiet buttons as underlined links. */ export const AI_SITE_BUTTONS = [
|
|
70
|
+
'rounded',
|
|
71
|
+
'pill',
|
|
72
|
+
'square',
|
|
73
|
+
'caps',
|
|
74
|
+
'link'
|
|
75
|
+
];
|
|
76
|
+
const DENSITIES = [
|
|
77
|
+
'compact',
|
|
78
|
+
'regular',
|
|
79
|
+
'airy'
|
|
80
|
+
];
|
|
81
|
+
// ── The seed ─────────────────────────────────────────────────────────────
|
|
82
|
+
/** A job's seed: FNV-1a over its id. */ export function aiSiteSeed(jobId) {
|
|
83
|
+
let hash = 0x811c9dc5;
|
|
84
|
+
for(let index = 0; index < jobId.length; index += 1){
|
|
85
|
+
hash ^= jobId.charCodeAt(index);
|
|
86
|
+
hash = Math.imul(hash, 0x01000193);
|
|
87
|
+
}
|
|
88
|
+
return hash >>> 0;
|
|
89
|
+
}
|
|
90
|
+
/** A small deterministic generator over a seed (mulberry32). */ function random(seed) {
|
|
91
|
+
let state = seed >>> 0;
|
|
92
|
+
return ()=>{
|
|
93
|
+
state = state + 0x6d2b79f5 >>> 0;
|
|
94
|
+
let t = state;
|
|
95
|
+
t = Math.imul(t ^ t >>> 15, t | 1);
|
|
96
|
+
t ^= t + Math.imul(t ^ t >>> 7, t | 61);
|
|
97
|
+
return ((t ^ t >>> 14) >>> 0) / 4294967296;
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
const pick = (next, list)=>list[Math.floor(next() * list.length) % list.length];
|
|
101
|
+
// ── The model's answer ───────────────────────────────────────────────────
|
|
102
|
+
export const AI_SITE_LOOK_TOOL_NAME = 'submit_site_look';
|
|
103
|
+
const enumOf = (values, description)=>({
|
|
104
|
+
type: 'string',
|
|
105
|
+
enum: [
|
|
106
|
+
...values
|
|
107
|
+
],
|
|
108
|
+
description
|
|
109
|
+
});
|
|
110
|
+
/** The strict tool the look is answered with; every field is required and the reader is lenient. */ export const AI_SITE_LOOK_TOOL = {
|
|
111
|
+
name: AI_SITE_LOOK_TOOL_NAME,
|
|
112
|
+
description: "Submit this website's look: the base theme it starts from and the choices that make it this business's own.",
|
|
113
|
+
strict: true,
|
|
114
|
+
inputSchema: {
|
|
115
|
+
type: 'object',
|
|
116
|
+
additionalProperties: false,
|
|
117
|
+
required: [
|
|
118
|
+
'base',
|
|
119
|
+
'hue',
|
|
120
|
+
'accent',
|
|
121
|
+
'ground',
|
|
122
|
+
'fonts',
|
|
123
|
+
'corners',
|
|
124
|
+
'buttons',
|
|
125
|
+
'cards',
|
|
126
|
+
'fields',
|
|
127
|
+
'eyebrow',
|
|
128
|
+
'header',
|
|
129
|
+
'brand'
|
|
130
|
+
],
|
|
131
|
+
properties: {
|
|
132
|
+
base: enumOf(AI_SITE_BASES, 'The base theme to start from, from the list in the request.'),
|
|
133
|
+
hue: {
|
|
134
|
+
type: 'integer',
|
|
135
|
+
description: 'The brand color as a hue from 0 to 359 (0 red, 30 orange, 55 gold, 140 green, 190 teal, 220 blue, 270 violet, 330 pink).'
|
|
136
|
+
},
|
|
137
|
+
accent: {
|
|
138
|
+
type: 'integer',
|
|
139
|
+
description: 'The accent color as a hue from 0 to 359, chosen to sit well beside the brand hue.'
|
|
140
|
+
},
|
|
141
|
+
ground: enumOf(AI_SITE_GROUNDS, 'The page background: white, warm (cream), cool (gray-blue) or tinted (a whisper of the brand color).'),
|
|
142
|
+
fonts: enumOf(AI_SITE_FONT_PAIRINGS.map((pairing)=>pairing.id), 'A font pairing from the list in the request.'),
|
|
143
|
+
corners: enumOf(AI_SITE_CORNERS, 'Corner rounding for cards and fields.'),
|
|
144
|
+
buttons: enumOf(AI_SITE_BUTTONS, 'Buttons: rounded, pill, square, caps (capitals) or link (quiet buttons drawn as underlined links).'),
|
|
145
|
+
cards: enumOf(AI_SITE_CARDS, 'Cards: flat, outlined, elevated (a soft shadow), tinted (a wash of the brand color) or rule (a colored line on top).'),
|
|
146
|
+
fields: enumOf(AI_SITE_FIELDS, 'Form fields: outlined, filled or standard (underlined).'),
|
|
147
|
+
eyebrow: enumOf(AI_SITE_EYEBROWS, 'The small label above headings: caps (spaced capitals), small (quiet small text) or rule (capitals after a short line).'),
|
|
148
|
+
header: enumOf(AI_SITE_HEADERS, 'What sets the header bar off the page: a line, a shadow, or flat.'),
|
|
149
|
+
brand: {
|
|
150
|
+
anyOf: [
|
|
151
|
+
{
|
|
152
|
+
type: 'string'
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
type: 'null'
|
|
156
|
+
}
|
|
157
|
+
],
|
|
158
|
+
description: 'A hex brand color the brief gives, exactly as written, or null.'
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
const oneOf = (value, values)=>typeof value === 'string' && values.includes(value) ? value : undefined;
|
|
164
|
+
const hueOf = (value)=>typeof value === 'number' && Number.isFinite(value) ? (Math.round(value) % 360 + 360) % 360 : undefined;
|
|
165
|
+
/** A brand color as a six-digit hex, or `null`. */ export function aiSiteBrandHex(value) {
|
|
166
|
+
if (typeof value !== 'string') return null;
|
|
167
|
+
const match = /#?([0-9a-f]{6}|[0-9a-f]{3})\b/i.exec(value.trim());
|
|
168
|
+
if (!match) return null;
|
|
169
|
+
const hex = match[1];
|
|
170
|
+
const full = hex.length === 3 ? [
|
|
171
|
+
...hex
|
|
172
|
+
].map((digit)=>digit + digit).join('') : hex;
|
|
173
|
+
return `#${full.toLowerCase()}`;
|
|
174
|
+
}
|
|
175
|
+
/** The model's answer, read leniently: a value outside its vocabulary is left for the seed. */ export function aiReadSiteLook(answer) {
|
|
176
|
+
const raw = answer && typeof answer === 'object' ? answer : {};
|
|
177
|
+
const look = {};
|
|
178
|
+
const base = oneOf(raw['base'], AI_SITE_BASES);
|
|
179
|
+
if (base) look.base = base;
|
|
180
|
+
const hue = hueOf(raw['hue']);
|
|
181
|
+
if (hue !== undefined) look.hue = hue;
|
|
182
|
+
const accent = hueOf(raw['accent']);
|
|
183
|
+
if (accent !== undefined) look.accent = accent;
|
|
184
|
+
const ground = oneOf(raw['ground'], AI_SITE_GROUNDS);
|
|
185
|
+
if (ground) look.ground = ground;
|
|
186
|
+
if (aiSiteFontPairing(raw['fonts'])) look.fonts = raw['fonts'];
|
|
187
|
+
const corners = oneOf(raw['corners'], AI_SITE_CORNERS);
|
|
188
|
+
if (corners) look.corners = corners;
|
|
189
|
+
const buttons = oneOf(raw['buttons'], AI_SITE_BUTTONS);
|
|
190
|
+
if (buttons) look.buttons = buttons;
|
|
191
|
+
const cards = oneOf(raw['cards'], AI_SITE_CARDS);
|
|
192
|
+
if (cards) look.cards = cards;
|
|
193
|
+
const fields = oneOf(raw['fields'], AI_SITE_FIELDS);
|
|
194
|
+
if (fields) look.fields = fields;
|
|
195
|
+
const eyebrow = oneOf(raw['eyebrow'], AI_SITE_EYEBROWS);
|
|
196
|
+
if (eyebrow) look.eyebrow = eyebrow;
|
|
197
|
+
const header = oneOf(raw['header'], AI_SITE_HEADERS);
|
|
198
|
+
if (header) look.header = header;
|
|
199
|
+
const brand = aiSiteBrandHex(raw['brand']);
|
|
200
|
+
if (brand) look.brand = brand;
|
|
201
|
+
return look;
|
|
202
|
+
}
|
|
203
|
+
// ── Kind, model and seed into tokens ─────────────────────────────────────
|
|
204
|
+
/** The building-block styles a kind leans to, before the model or the seed choose. */ const BLOCK_BIAS = {
|
|
205
|
+
portfolio: {
|
|
206
|
+
cards: [
|
|
207
|
+
'rule',
|
|
208
|
+
'flat',
|
|
209
|
+
'outlined'
|
|
210
|
+
],
|
|
211
|
+
eyebrow: [
|
|
212
|
+
'small',
|
|
213
|
+
'rule',
|
|
214
|
+
'caps'
|
|
215
|
+
],
|
|
216
|
+
header: [
|
|
217
|
+
'flat',
|
|
218
|
+
'line',
|
|
219
|
+
'shadow'
|
|
220
|
+
],
|
|
221
|
+
fields: [
|
|
222
|
+
'standard',
|
|
223
|
+
'outlined',
|
|
224
|
+
'filled'
|
|
225
|
+
]
|
|
226
|
+
},
|
|
227
|
+
studio: {
|
|
228
|
+
cards: [
|
|
229
|
+
'flat',
|
|
230
|
+
'rule',
|
|
231
|
+
'outlined'
|
|
232
|
+
],
|
|
233
|
+
eyebrow: [
|
|
234
|
+
'rule',
|
|
235
|
+
'small',
|
|
236
|
+
'caps'
|
|
237
|
+
],
|
|
238
|
+
header: [
|
|
239
|
+
'flat',
|
|
240
|
+
'line',
|
|
241
|
+
'shadow'
|
|
242
|
+
],
|
|
243
|
+
fields: [
|
|
244
|
+
'standard',
|
|
245
|
+
'outlined',
|
|
246
|
+
'filled'
|
|
247
|
+
]
|
|
248
|
+
},
|
|
249
|
+
photography: {
|
|
250
|
+
cards: [
|
|
251
|
+
'flat',
|
|
252
|
+
'rule',
|
|
253
|
+
'outlined'
|
|
254
|
+
],
|
|
255
|
+
eyebrow: [
|
|
256
|
+
'small',
|
|
257
|
+
'rule',
|
|
258
|
+
'caps'
|
|
259
|
+
],
|
|
260
|
+
header: [
|
|
261
|
+
'flat',
|
|
262
|
+
'line',
|
|
263
|
+
'shadow'
|
|
264
|
+
],
|
|
265
|
+
fields: [
|
|
266
|
+
'standard',
|
|
267
|
+
'outlined',
|
|
268
|
+
'filled'
|
|
269
|
+
]
|
|
270
|
+
},
|
|
271
|
+
blog: {
|
|
272
|
+
cards: [
|
|
273
|
+
'rule',
|
|
274
|
+
'outlined',
|
|
275
|
+
'flat',
|
|
276
|
+
'tinted'
|
|
277
|
+
],
|
|
278
|
+
eyebrow: [
|
|
279
|
+
'small',
|
|
280
|
+
'caps',
|
|
281
|
+
'rule'
|
|
282
|
+
],
|
|
283
|
+
header: [
|
|
284
|
+
'line',
|
|
285
|
+
'flat',
|
|
286
|
+
'shadow'
|
|
287
|
+
],
|
|
288
|
+
fields: [
|
|
289
|
+
'outlined',
|
|
290
|
+
'standard',
|
|
291
|
+
'filled'
|
|
292
|
+
]
|
|
293
|
+
},
|
|
294
|
+
trades: {
|
|
295
|
+
cards: [
|
|
296
|
+
'elevated',
|
|
297
|
+
'outlined',
|
|
298
|
+
'rule',
|
|
299
|
+
'flat',
|
|
300
|
+
'tinted'
|
|
301
|
+
],
|
|
302
|
+
eyebrow: [
|
|
303
|
+
'caps',
|
|
304
|
+
'rule',
|
|
305
|
+
'small'
|
|
306
|
+
],
|
|
307
|
+
header: [
|
|
308
|
+
'shadow',
|
|
309
|
+
'line',
|
|
310
|
+
'flat'
|
|
311
|
+
],
|
|
312
|
+
fields: [
|
|
313
|
+
'outlined',
|
|
314
|
+
'filled',
|
|
315
|
+
'standard'
|
|
316
|
+
]
|
|
317
|
+
},
|
|
318
|
+
fitness: {
|
|
319
|
+
cards: [
|
|
320
|
+
'elevated',
|
|
321
|
+
'flat',
|
|
322
|
+
'rule',
|
|
323
|
+
'outlined'
|
|
324
|
+
],
|
|
325
|
+
eyebrow: [
|
|
326
|
+
'caps',
|
|
327
|
+
'rule',
|
|
328
|
+
'small'
|
|
329
|
+
],
|
|
330
|
+
header: [
|
|
331
|
+
'shadow',
|
|
332
|
+
'flat',
|
|
333
|
+
'line'
|
|
334
|
+
],
|
|
335
|
+
fields: [
|
|
336
|
+
'filled',
|
|
337
|
+
'outlined',
|
|
338
|
+
'standard'
|
|
339
|
+
]
|
|
340
|
+
},
|
|
341
|
+
professional: {
|
|
342
|
+
cards: [
|
|
343
|
+
'outlined',
|
|
344
|
+
'rule',
|
|
345
|
+
'flat',
|
|
346
|
+
'elevated'
|
|
347
|
+
],
|
|
348
|
+
eyebrow: [
|
|
349
|
+
'caps',
|
|
350
|
+
'rule',
|
|
351
|
+
'small'
|
|
352
|
+
],
|
|
353
|
+
header: [
|
|
354
|
+
'line',
|
|
355
|
+
'flat',
|
|
356
|
+
'shadow'
|
|
357
|
+
],
|
|
358
|
+
fields: [
|
|
359
|
+
'outlined',
|
|
360
|
+
'standard',
|
|
361
|
+
'filled'
|
|
362
|
+
]
|
|
363
|
+
},
|
|
364
|
+
wellness: {
|
|
365
|
+
cards: [
|
|
366
|
+
'tinted',
|
|
367
|
+
'flat',
|
|
368
|
+
'elevated',
|
|
369
|
+
'outlined'
|
|
370
|
+
],
|
|
371
|
+
eyebrow: [
|
|
372
|
+
'small',
|
|
373
|
+
'caps',
|
|
374
|
+
'rule'
|
|
375
|
+
],
|
|
376
|
+
header: [
|
|
377
|
+
'flat',
|
|
378
|
+
'line',
|
|
379
|
+
'shadow'
|
|
380
|
+
],
|
|
381
|
+
fields: [
|
|
382
|
+
'filled',
|
|
383
|
+
'outlined',
|
|
384
|
+
'standard'
|
|
385
|
+
]
|
|
386
|
+
},
|
|
387
|
+
yoga: {
|
|
388
|
+
cards: [
|
|
389
|
+
'tinted',
|
|
390
|
+
'flat',
|
|
391
|
+
'outlined'
|
|
392
|
+
],
|
|
393
|
+
eyebrow: [
|
|
394
|
+
'small',
|
|
395
|
+
'rule',
|
|
396
|
+
'caps'
|
|
397
|
+
],
|
|
398
|
+
header: [
|
|
399
|
+
'flat',
|
|
400
|
+
'line',
|
|
401
|
+
'shadow'
|
|
402
|
+
],
|
|
403
|
+
fields: [
|
|
404
|
+
'filled',
|
|
405
|
+
'standard',
|
|
406
|
+
'outlined'
|
|
407
|
+
]
|
|
408
|
+
},
|
|
409
|
+
restaurant: {
|
|
410
|
+
cards: [
|
|
411
|
+
'flat',
|
|
412
|
+
'elevated',
|
|
413
|
+
'tinted',
|
|
414
|
+
'rule'
|
|
415
|
+
],
|
|
416
|
+
eyebrow: [
|
|
417
|
+
'caps',
|
|
418
|
+
'rule',
|
|
419
|
+
'small'
|
|
420
|
+
],
|
|
421
|
+
header: [
|
|
422
|
+
'flat',
|
|
423
|
+
'shadow',
|
|
424
|
+
'line'
|
|
425
|
+
],
|
|
426
|
+
fields: [
|
|
427
|
+
'outlined',
|
|
428
|
+
'filled',
|
|
429
|
+
'standard'
|
|
430
|
+
]
|
|
431
|
+
},
|
|
432
|
+
beauty: {
|
|
433
|
+
cards: [
|
|
434
|
+
'tinted',
|
|
435
|
+
'flat',
|
|
436
|
+
'outlined',
|
|
437
|
+
'rule'
|
|
438
|
+
],
|
|
439
|
+
eyebrow: [
|
|
440
|
+
'rule',
|
|
441
|
+
'small',
|
|
442
|
+
'caps'
|
|
443
|
+
],
|
|
444
|
+
header: [
|
|
445
|
+
'flat',
|
|
446
|
+
'line',
|
|
447
|
+
'shadow'
|
|
448
|
+
],
|
|
449
|
+
fields: [
|
|
450
|
+
'standard',
|
|
451
|
+
'filled',
|
|
452
|
+
'outlined'
|
|
453
|
+
]
|
|
454
|
+
},
|
|
455
|
+
music: {
|
|
456
|
+
cards: [
|
|
457
|
+
'flat',
|
|
458
|
+
'rule',
|
|
459
|
+
'elevated'
|
|
460
|
+
],
|
|
461
|
+
eyebrow: [
|
|
462
|
+
'caps',
|
|
463
|
+
'rule',
|
|
464
|
+
'small'
|
|
465
|
+
],
|
|
466
|
+
header: [
|
|
467
|
+
'flat',
|
|
468
|
+
'shadow',
|
|
469
|
+
'line'
|
|
470
|
+
],
|
|
471
|
+
fields: [
|
|
472
|
+
'filled',
|
|
473
|
+
'standard',
|
|
474
|
+
'outlined'
|
|
475
|
+
]
|
|
476
|
+
}
|
|
477
|
+
};
|
|
478
|
+
const ANY_BLOCKS = {
|
|
479
|
+
cards: AI_SITE_CARDS,
|
|
480
|
+
eyebrow: AI_SITE_EYEBROWS,
|
|
481
|
+
header: AI_SITE_HEADERS,
|
|
482
|
+
fields: AI_SITE_FIELDS
|
|
483
|
+
};
|
|
484
|
+
/** The hue family a hue is in: twelve families of thirty degrees, 0 the reds. */ export function aiSiteHueFamily(hue) {
|
|
485
|
+
return Math.floor(((hue + 15) % 360 + 360) % 360 / 30);
|
|
486
|
+
}
|
|
487
|
+
/** Which of a kind's hue ranges a hue falls in, or -1. */ function rangeOf(hue, kind) {
|
|
488
|
+
return kind.look.hues.findIndex(([from, to])=>from <= to ? hue >= from && hue <= to : hue >= from || hue <= to);
|
|
489
|
+
}
|
|
490
|
+
/** The look dimensions two sites are compared by (`ai-site-look.spec.ts`). */ export const AI_SITE_LOOK_DIMENSIONS = [
|
|
491
|
+
'base',
|
|
492
|
+
'hueFamily',
|
|
493
|
+
'fonts',
|
|
494
|
+
'buttons',
|
|
495
|
+
'cards',
|
|
496
|
+
'corners',
|
|
497
|
+
'ground',
|
|
498
|
+
'eyebrow',
|
|
499
|
+
'header',
|
|
500
|
+
'fields',
|
|
501
|
+
'headerAlign',
|
|
502
|
+
'rhythm'
|
|
503
|
+
];
|
|
504
|
+
/** A style's value on each tracked dimension. */ export function aiSiteLookSignature(style) {
|
|
505
|
+
return {
|
|
506
|
+
base: style.base,
|
|
507
|
+
hueFamily: String(aiSiteHueFamily(style.hue)),
|
|
508
|
+
fonts: style.fonts,
|
|
509
|
+
buttons: style.buttons,
|
|
510
|
+
cards: style.cards,
|
|
511
|
+
corners: style.corners,
|
|
512
|
+
ground: style.ground,
|
|
513
|
+
eyebrow: style.eyebrow,
|
|
514
|
+
header: style.header,
|
|
515
|
+
fields: style.fields,
|
|
516
|
+
headerAlign: style.headerAlign,
|
|
517
|
+
rhythm: style.rhythm
|
|
518
|
+
};
|
|
519
|
+
}
|
|
520
|
+
/**
|
|
521
|
+
* The style tokens for a site of this kind (AGL-3660). Every dimension is
|
|
522
|
+
* the SEED's choice among what suits the kind, with the model's choice for
|
|
523
|
+
* this business as a favored candidate — kept at a given chance, not always
|
|
524
|
+
* — so the brief steers the look and two jobs with the same brief and the
|
|
525
|
+
* same answer still come out as two sites: another base, another hue family,
|
|
526
|
+
* another pairing, other buttons. A brand color the brief gives is always
|
|
527
|
+
* kept.
|
|
528
|
+
*/ export function aiSiteStyleFor(input) {
|
|
529
|
+
var _input_avoid;
|
|
530
|
+
const avoid = (_input_avoid = input.avoid) != null ? _input_avoid : [];
|
|
531
|
+
if (!avoid.length) return styleOnce(input, input.seed);
|
|
532
|
+
const signatures = avoid.map(aiSiteLookSignature);
|
|
533
|
+
let best = null;
|
|
534
|
+
for(let attempt = 0; attempt < AI_SITE_LOOK_DRAWS; attempt += 1){
|
|
535
|
+
const style = styleOnce(input, attempt === 0 ? input.seed : Math.imul(input.seed ^ 0x5bd1e995, attempt + 1) >>> 0);
|
|
536
|
+
const own = aiSiteLookSignature(style);
|
|
537
|
+
const least = Math.min(...signatures.map((other)=>TUPLE.every((dimension)=>own[dimension] === other[dimension]) ? -1 : AI_SITE_LOOK_DIMENSIONS.filter((dimension)=>own[dimension] !== other[dimension]).length));
|
|
538
|
+
if (least >= AI_SITE_LOOK_LEAST_DIFFERENCES) return style;
|
|
539
|
+
if (!best || least > best.least) best = {
|
|
540
|
+
style,
|
|
541
|
+
least
|
|
542
|
+
};
|
|
543
|
+
}
|
|
544
|
+
return best.style;
|
|
545
|
+
}
|
|
546
|
+
/** How many seeds a look draws before it settles for the most different. */ export const AI_SITE_LOOK_DRAWS = 32;
|
|
547
|
+
/** The fewest tracked dimensions in which two of a workspace's sites differ. */ export const AI_SITE_LOOK_LEAST_DIFFERENCES = 3;
|
|
548
|
+
/** The dimensions no two of a workspace's sites share all of. */ const TUPLE = [
|
|
549
|
+
'base',
|
|
550
|
+
'hueFamily',
|
|
551
|
+
'fonts',
|
|
552
|
+
'buttons'
|
|
553
|
+
];
|
|
554
|
+
function styleOnce(input, seed) {
|
|
555
|
+
var _BLOCK_BIAS_kind_id, _family_headerAligns, _family_rhythms, _ref, _aiSiteBrandHex;
|
|
556
|
+
const { kind, answer } = input;
|
|
557
|
+
const next = random(seed);
|
|
558
|
+
const family = kind.look;
|
|
559
|
+
const blocks = (_BLOCK_BIAS_kind_id = BLOCK_BIAS[kind.id]) != null ? _BLOCK_BIAS_kind_id : ANY_BLOCKS;
|
|
560
|
+
/** The model's choice at a chance of `keep`, else the seed's among `options`. */ const choose = (model, options, keep)=>model !== undefined && next() < keep ? model : pick(next, options);
|
|
561
|
+
// The hue: a family among the kind's (the brief's industry), the model's
|
|
562
|
+
// own family favored, and a hue within it.
|
|
563
|
+
const modelRange = answer.hue === undefined ? -1 : rangeOf(answer.hue, kind);
|
|
564
|
+
const keepHue = modelRange !== -1 && next() < 0.3;
|
|
565
|
+
const range = keepHue ? modelRange : Math.floor(next() * family.hues.length) % family.hues.length;
|
|
566
|
+
const [from, to] = family.hues[range];
|
|
567
|
+
const span = from <= to ? to - from : 360 - from + to;
|
|
568
|
+
const hue = keepHue ? (answer.hue + (next() < 0.5 ? -1 : 1) * Math.round(4 + next() * 8) + 360) % 360 : Math.round(from + next() * span) % 360;
|
|
569
|
+
const accentFrom = answer.accent !== undefined && next() < 0.4 ? answer.accent : (hue + pick(next, [
|
|
570
|
+
30,
|
|
571
|
+
150,
|
|
572
|
+
180,
|
|
573
|
+
210,
|
|
574
|
+
330
|
|
575
|
+
])) % 360;
|
|
576
|
+
const accent = (accentFrom + (next() < 0.5 ? -1 : 1) * Math.round(8 + next() * 16) + 360) % 360;
|
|
577
|
+
const bases = [
|
|
578
|
+
...new Set([
|
|
579
|
+
...answer.base ? [
|
|
580
|
+
answer.base
|
|
581
|
+
] : [],
|
|
582
|
+
...family.bases
|
|
583
|
+
])
|
|
584
|
+
];
|
|
585
|
+
return {
|
|
586
|
+
v: 1,
|
|
587
|
+
kind: kind.id,
|
|
588
|
+
base: choose(answer.base, bases, 0.3),
|
|
589
|
+
seed,
|
|
590
|
+
hue,
|
|
591
|
+
accent,
|
|
592
|
+
chroma: family.chroma,
|
|
593
|
+
ground: choose(answer.ground, family.grounds, 0.35),
|
|
594
|
+
fonts: choose(answer.fonts, family.fonts, 0.3),
|
|
595
|
+
corners: choose(answer.corners, family.corners, 0.35),
|
|
596
|
+
buttons: choose(answer.buttons, [
|
|
597
|
+
family.buttons,
|
|
598
|
+
...AI_SITE_BUTTONS
|
|
599
|
+
], 0.3),
|
|
600
|
+
cards: choose(answer.cards, blocks.cards, 0.35),
|
|
601
|
+
fields: choose(answer.fields, blocks.fields, 0.35),
|
|
602
|
+
eyebrow: choose(answer.eyebrow, blocks.eyebrow, 0.35),
|
|
603
|
+
header: choose(answer.header, blocks.header, 0.35),
|
|
604
|
+
headerAlign: pick(next, (_family_headerAligns = family.headerAligns) != null ? _family_headerAligns : [
|
|
605
|
+
'start',
|
|
606
|
+
'start',
|
|
607
|
+
'center'
|
|
608
|
+
]),
|
|
609
|
+
rhythm: pick(next, (_family_rhythms = family.rhythms) != null ? _family_rhythms : AI_SITE_RHYTHMS),
|
|
610
|
+
headingScale: Math.round(family.headingScale * (0.94 + next() * 0.12) * 100) / 100,
|
|
611
|
+
density: family.density === 'regular' && next() < 0.25 ? pick(next, DENSITIES) : family.density,
|
|
612
|
+
brand: (_ref = (_aiSiteBrandHex = aiSiteBrandHex(input.brand)) != null ? _aiSiteBrandHex : answer.brand) != null ? _ref : null
|
|
613
|
+
};
|
|
614
|
+
}
|
|
615
|
+
function oklabToLinear(L, a, b) {
|
|
616
|
+
const l = (L + 0.3963377774 * a + 0.2158037573 * b) ** 3;
|
|
617
|
+
const m = (L - 0.1055613458 * a - 0.0638541728 * b) ** 3;
|
|
618
|
+
const s = (L - 0.0894841775 * a - 1.291485548 * b) ** 3;
|
|
619
|
+
return [
|
|
620
|
+
4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s,
|
|
621
|
+
-1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s,
|
|
622
|
+
-0.0041960863 * l - 0.7034186147 * m + 1.707614701 * s
|
|
623
|
+
];
|
|
624
|
+
}
|
|
625
|
+
const toSrgb = (channel)=>channel <= 0.0031308 ? 12.92 * channel : 1.055 * channel ** (1 / 2.4) - 0.055;
|
|
626
|
+
const fromSrgb = (channel)=>channel <= 0.04045 ? channel / 12.92 : ((channel + 0.055) / 1.055) ** 2.4;
|
|
627
|
+
/** An OKLCH color as hex, its chroma reduced until it fits sRGB. */ export function aiOklchHex(L, C, h) {
|
|
628
|
+
const lightness = Math.min(1, Math.max(0, L));
|
|
629
|
+
let chroma = Math.max(0, C);
|
|
630
|
+
for(;;){
|
|
631
|
+
const radians = h * Math.PI / 180;
|
|
632
|
+
const linear = oklabToLinear(lightness, chroma * Math.cos(radians), chroma * Math.sin(radians));
|
|
633
|
+
const fits = linear.every((channel)=>channel >= -0.0005 && channel <= 1.0005);
|
|
634
|
+
if (fits || chroma <= 0) {
|
|
635
|
+
return `#${linear.map((channel)=>Math.round(Math.min(1, Math.max(0, toSrgb(Math.min(1, Math.max(0, channel))))) * 255)).map((value)=>value.toString(16).padStart(2, '0')).join('')}`;
|
|
636
|
+
}
|
|
637
|
+
chroma = Math.max(0, chroma - 0.004);
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
/** A hex color in OKLCH. */ export function aiHexOklch(hex) {
|
|
641
|
+
const value = hex.replace('#', '');
|
|
642
|
+
const [r, g, b] = [
|
|
643
|
+
0,
|
|
644
|
+
2,
|
|
645
|
+
4
|
|
646
|
+
].map((at)=>fromSrgb(parseInt(value.slice(at, at + 2), 16) / 255));
|
|
647
|
+
const l = Math.cbrt(0.4122214708 * r + 0.5363325363 * g + 0.0514459929 * b);
|
|
648
|
+
const m = Math.cbrt(0.2119034982 * r + 0.6806995451 * g + 0.1073969566 * b);
|
|
649
|
+
const s = Math.cbrt(0.0883024619 * r + 0.2817188376 * g + 0.6299787005 * b);
|
|
650
|
+
const L = 0.2104542553 * l + 0.793617785 * m - 0.0040720468 * s;
|
|
651
|
+
const A = 1.9779984951 * l - 2.428592205 * m + 0.4505937099 * s;
|
|
652
|
+
const B = 0.0259040371 * l + 0.7827717662 * m - 0.808675766 * s;
|
|
653
|
+
return {
|
|
654
|
+
L,
|
|
655
|
+
C: Math.hypot(A, B),
|
|
656
|
+
h: (Math.atan2(B, A) * 180 / Math.PI + 360) % 360
|
|
657
|
+
};
|
|
658
|
+
}
|
|
659
|
+
const ratio = (a, b)=>{
|
|
660
|
+
var _contrastRatio;
|
|
661
|
+
return (_contrastRatio = contrastRatio(a, b)) != null ? _contrastRatio : 1;
|
|
662
|
+
};
|
|
663
|
+
/** The lightness, stepped from `L` toward `toward`, at which `hex(L)` first reaches `min` against `against`. */ function untilContrast(L, C, h, against, min, toward) {
|
|
664
|
+
let lightness = L;
|
|
665
|
+
for(let step = 0; step < 60; step += 1){
|
|
666
|
+
if (ratio(aiOklchHex(lightness, C, h), against) >= min) return lightness;
|
|
667
|
+
lightness += toward ? 0.012 : -0.012;
|
|
668
|
+
if (lightness <= 0.05 || lightness >= 0.98) break;
|
|
669
|
+
}
|
|
670
|
+
return lightness;
|
|
671
|
+
}
|
|
672
|
+
/** The label color of a fill: white or a deep ink of its hue, whichever reads at 4.5:1, or better. */ function labelOn(fill, h) {
|
|
673
|
+
const white = '#ffffff';
|
|
674
|
+
const ink = aiOklchHex(0.2, 0.04, h);
|
|
675
|
+
return ratio(white, fill) >= 4.5 ? white : ratio(ink, fill) >= 4.5 ? ink : ratio(white, fill) > ratio(ink, fill) ? white : '#000000';
|
|
676
|
+
}
|
|
677
|
+
const CHROMA = {
|
|
678
|
+
neutral: 0.15,
|
|
679
|
+
muted: 0.085,
|
|
680
|
+
balanced: 0.135,
|
|
681
|
+
vivid: 0.185
|
|
682
|
+
};
|
|
683
|
+
const START_L = {
|
|
684
|
+
neutral: 0.52,
|
|
685
|
+
muted: 0.48,
|
|
686
|
+
balanced: 0.52,
|
|
687
|
+
vivid: 0.56
|
|
688
|
+
};
|
|
689
|
+
/** Both schemes' colors for a style. */ export function aiSiteColors(style) {
|
|
690
|
+
var _ref, _ref1;
|
|
691
|
+
const next = random(style.seed ^ 0x9e3779b9);
|
|
692
|
+
const neutral = style.chroma === 'neutral';
|
|
693
|
+
const brand = style.brand ? aiHexOklch(style.brand) : null;
|
|
694
|
+
const h = (_ref = brand == null ? void 0 : brand.h) != null ? _ref : style.hue;
|
|
695
|
+
const chroma = (_ref1 = brand == null ? void 0 : brand.C) != null ? _ref1 : CHROMA[style.chroma] * (0.9 + next() * 0.2);
|
|
696
|
+
const groundChroma = neutral ? 0.3 : 1;
|
|
697
|
+
const white = '#ffffff';
|
|
698
|
+
// The page grounds.
|
|
699
|
+
const grounds = {
|
|
700
|
+
white: {
|
|
701
|
+
L: 0.985,
|
|
702
|
+
C: 0.004,
|
|
703
|
+
h,
|
|
704
|
+
paper: [
|
|
705
|
+
1,
|
|
706
|
+
0
|
|
707
|
+
]
|
|
708
|
+
},
|
|
709
|
+
warm: {
|
|
710
|
+
L: 0.972,
|
|
711
|
+
C: 0.016,
|
|
712
|
+
h: 78,
|
|
713
|
+
paper: [
|
|
714
|
+
0.993,
|
|
715
|
+
0.007
|
|
716
|
+
]
|
|
717
|
+
},
|
|
718
|
+
cool: {
|
|
719
|
+
L: 0.974,
|
|
720
|
+
C: 0.009,
|
|
721
|
+
h: 250,
|
|
722
|
+
paper: [
|
|
723
|
+
1,
|
|
724
|
+
0
|
|
725
|
+
]
|
|
726
|
+
},
|
|
727
|
+
tinted: {
|
|
728
|
+
L: 0.968,
|
|
729
|
+
C: 0.02,
|
|
730
|
+
h,
|
|
731
|
+
paper: [
|
|
732
|
+
0.994,
|
|
733
|
+
0.006
|
|
734
|
+
]
|
|
735
|
+
}
|
|
736
|
+
};
|
|
737
|
+
const ground = grounds[style.ground];
|
|
738
|
+
const background = aiOklchHex(ground.L, ground.C * groundChroma, ground.h);
|
|
739
|
+
const paper = aiOklchHex(ground.paper[0], ground.paper[1] * groundChroma, ground.h);
|
|
740
|
+
// The brand color: a button label reads on it, so it reads on the page too.
|
|
741
|
+
let primaryMain;
|
|
742
|
+
let primaryL;
|
|
743
|
+
if (brand && ratio(white, style.brand) >= 4.5) {
|
|
744
|
+
primaryMain = style.brand;
|
|
745
|
+
primaryL = brand.L;
|
|
746
|
+
} else {
|
|
747
|
+
primaryL = untilContrast(brand ? Math.min(brand.L, START_L[style.chroma]) : START_L[style.chroma] + (next() - 0.5) * 0.06, chroma, h, white, 4.6, 0);
|
|
748
|
+
primaryMain = aiOklchHex(primaryL, chroma, h);
|
|
749
|
+
}
|
|
750
|
+
const accentChroma = neutral ? 0.17 : Math.max(0.07, chroma * 0.95);
|
|
751
|
+
// The secondary color fills a button ON a brand band, so it stands apart from the brand color.
|
|
752
|
+
const secondary = neutral ? {
|
|
753
|
+
main: aiOklchHex(0.24, 0.012, h),
|
|
754
|
+
h
|
|
755
|
+
} : (()=>{
|
|
756
|
+
// A light wash of the accent rather than the accent at full strength:
|
|
757
|
+
// it reads on the brand color without fighting it.
|
|
758
|
+
const washChroma = Math.min(accentChroma, 0.08);
|
|
759
|
+
let L = 0.9;
|
|
760
|
+
let main = aiOklchHex(L, washChroma, style.accent);
|
|
761
|
+
while(ratio(main, primaryMain) < 3 && L < 0.96){
|
|
762
|
+
L += 0.02;
|
|
763
|
+
main = aiOklchHex(L, washChroma, style.accent);
|
|
764
|
+
}
|
|
765
|
+
return {
|
|
766
|
+
main,
|
|
767
|
+
h: style.accent
|
|
768
|
+
};
|
|
769
|
+
})();
|
|
770
|
+
const tertiaryHue = (style.accent + 60) % 360;
|
|
771
|
+
const tertiaryL = untilContrast(0.52, 0.12, tertiaryHue, white, 4.6, 0);
|
|
772
|
+
const tertiary = aiOklchHex(tertiaryL, 0.12, tertiaryHue);
|
|
773
|
+
const textChroma = neutral ? 0.006 : 0.02;
|
|
774
|
+
const light = {
|
|
775
|
+
primary: {
|
|
776
|
+
main: primaryMain,
|
|
777
|
+
light: aiOklchHex(Math.min(0.92, primaryL + 0.12), chroma, h),
|
|
778
|
+
dark: aiOklchHex(Math.max(0.15, primaryL - 0.1), chroma, h),
|
|
779
|
+
contrastText: labelOn(primaryMain, h)
|
|
780
|
+
},
|
|
781
|
+
secondary: {
|
|
782
|
+
main: secondary.main,
|
|
783
|
+
light: aiOklchHex(0.93, accentChroma * 0.6, secondary.h),
|
|
784
|
+
dark: aiOklchHex(0.42, accentChroma, secondary.h),
|
|
785
|
+
contrastText: labelOn(secondary.main, secondary.h)
|
|
786
|
+
},
|
|
787
|
+
tertiary: {
|
|
788
|
+
main: tertiary,
|
|
789
|
+
light: aiOklchHex(Math.min(0.9, tertiaryL + 0.14), 0.12, tertiaryHue),
|
|
790
|
+
dark: aiOklchHex(Math.max(0.15, tertiaryL - 0.1), 0.12, tertiaryHue),
|
|
791
|
+
contrastText: labelOn(tertiary, tertiaryHue)
|
|
792
|
+
},
|
|
793
|
+
surface: {
|
|
794
|
+
main: aiOklchHex(0.955, 0.012 * groundChroma, h),
|
|
795
|
+
contrastText: aiOklchHex(0.22, textChroma, h)
|
|
796
|
+
},
|
|
797
|
+
background: {
|
|
798
|
+
default: background,
|
|
799
|
+
paper
|
|
800
|
+
},
|
|
801
|
+
text: {
|
|
802
|
+
primary: aiOklchHex(0.22, textChroma, h),
|
|
803
|
+
secondary: aiOklchHex(0.45, textChroma + 0.005, h),
|
|
804
|
+
disabled: aiOklchHex(0.68, 0.01, h)
|
|
805
|
+
},
|
|
806
|
+
tint: {
|
|
807
|
+
primary: aiOklchHex(0.955, neutral ? 0.03 : 0.035, neutral ? style.accent : h),
|
|
808
|
+
secondary: aiOklchHex(0.955, 0.035, style.accent),
|
|
809
|
+
tertiary: aiOklchHex(0.955, 0.03, tertiaryHue)
|
|
810
|
+
},
|
|
811
|
+
divider: aiOklchHex(0.9, 0.012 * groundChroma, h)
|
|
812
|
+
};
|
|
813
|
+
const darkGround = style.ground === 'tinted' ? 0.024 : style.ground === 'warm' ? 0.014 : 0.012;
|
|
814
|
+
const darkBackground = aiOklchHex(0.17, darkGround * groundChroma, style.ground === 'warm' ? 60 : h);
|
|
815
|
+
const darkPaper = aiOklchHex(0.215, darkGround * groundChroma, style.ground === 'warm' ? 60 : h);
|
|
816
|
+
const darkChroma = Math.min(chroma, 0.14);
|
|
817
|
+
const darkPrimaryL = untilContrast(0.78, darkChroma, h, darkBackground, 4.5, 1);
|
|
818
|
+
const darkPrimary = aiOklchHex(darkPrimaryL, darkChroma, h);
|
|
819
|
+
const darkSecondary = neutral ? aiOklchHex(0.93, 0.01, h) : aiOklchHex(0.45, accentChroma * 0.85, style.accent);
|
|
820
|
+
const darkTertiary = aiOklchHex(0.8, 0.1, tertiaryHue);
|
|
821
|
+
const dark = {
|
|
822
|
+
primary: {
|
|
823
|
+
main: darkPrimary,
|
|
824
|
+
light: aiOklchHex(Math.min(0.94, darkPrimaryL + 0.08), darkChroma, h),
|
|
825
|
+
dark: aiOklchHex(darkPrimaryL - 0.1, darkChroma, h),
|
|
826
|
+
contrastText: labelOn(darkPrimary, h)
|
|
827
|
+
},
|
|
828
|
+
secondary: {
|
|
829
|
+
main: darkSecondary,
|
|
830
|
+
light: aiOklchHex(0.6, accentChroma * 0.7, secondary.h),
|
|
831
|
+
dark: aiOklchHex(0.35, accentChroma * 0.7, secondary.h),
|
|
832
|
+
contrastText: labelOn(darkSecondary, secondary.h)
|
|
833
|
+
},
|
|
834
|
+
tertiary: {
|
|
835
|
+
main: darkTertiary,
|
|
836
|
+
light: aiOklchHex(0.88, 0.08, tertiaryHue),
|
|
837
|
+
dark: aiOklchHex(0.7, 0.1, tertiaryHue),
|
|
838
|
+
contrastText: labelOn(darkTertiary, tertiaryHue)
|
|
839
|
+
},
|
|
840
|
+
surface: {
|
|
841
|
+
main: aiOklchHex(0.25, 0.014 * groundChroma, h),
|
|
842
|
+
contrastText: aiOklchHex(0.95, 0.01, h)
|
|
843
|
+
},
|
|
844
|
+
background: {
|
|
845
|
+
default: darkBackground,
|
|
846
|
+
paper: darkPaper
|
|
847
|
+
},
|
|
848
|
+
text: {
|
|
849
|
+
primary: aiOklchHex(0.95, 0.008, h),
|
|
850
|
+
secondary: aiOklchHex(0.77, 0.014, h),
|
|
851
|
+
disabled: aiOklchHex(0.52, 0.01, h)
|
|
852
|
+
},
|
|
853
|
+
tint: {
|
|
854
|
+
primary: aiOklchHex(0.27, 0.05, neutral ? style.accent : h),
|
|
855
|
+
secondary: aiOklchHex(0.27, 0.05, style.accent),
|
|
856
|
+
tertiary: aiOklchHex(0.27, 0.04, tertiaryHue)
|
|
857
|
+
},
|
|
858
|
+
divider: aiOklchHex(0.32, 0.015 * groundChroma, h)
|
|
859
|
+
};
|
|
860
|
+
return {
|
|
861
|
+
light,
|
|
862
|
+
dark
|
|
863
|
+
};
|
|
864
|
+
}
|
|
865
|
+
// ── The theme ────────────────────────────────────────────────────────────
|
|
866
|
+
const RADIUS = {
|
|
867
|
+
sharp: 2,
|
|
868
|
+
soft: 6,
|
|
869
|
+
round: 12,
|
|
870
|
+
pill: 18
|
|
871
|
+
};
|
|
872
|
+
const SPACING = {
|
|
873
|
+
compact: 7,
|
|
874
|
+
regular: 8,
|
|
875
|
+
airy: 9
|
|
876
|
+
};
|
|
877
|
+
/** How much of the heading scale each style takes: a display line grows most, small headings not at all. */ const SCALE_SHARE = {
|
|
878
|
+
displayXl: {
|
|
879
|
+
size: 4,
|
|
880
|
+
share: 1.2
|
|
881
|
+
},
|
|
882
|
+
h1: {
|
|
883
|
+
size: 3.25,
|
|
884
|
+
share: 1
|
|
885
|
+
},
|
|
886
|
+
h2: {
|
|
887
|
+
size: 2.25,
|
|
888
|
+
share: 0.8
|
|
889
|
+
},
|
|
890
|
+
h3: {
|
|
891
|
+
size: 1.75,
|
|
892
|
+
share: 0.5
|
|
893
|
+
},
|
|
894
|
+
h4: {
|
|
895
|
+
size: 1.5,
|
|
896
|
+
share: 0.3
|
|
897
|
+
},
|
|
898
|
+
h5: {
|
|
899
|
+
size: 1.25,
|
|
900
|
+
share: 0
|
|
901
|
+
},
|
|
902
|
+
h6: {
|
|
903
|
+
size: 1.125,
|
|
904
|
+
share: 0
|
|
905
|
+
}
|
|
906
|
+
};
|
|
907
|
+
const rem = (value)=>`${Math.round(value * 1000) / 1000}rem`;
|
|
908
|
+
/** The type a style sets: headings at its scale, weight and tracking, and the eyebrow and button styles. */ function typographyFor(style) {
|
|
909
|
+
var _aiSiteFontPairing, _ref;
|
|
910
|
+
var _DEFAULT_SITE_THEME_typography;
|
|
911
|
+
const pairing = (_aiSiteFontPairing = aiSiteFontPairing(style.fonts)) != null ? _aiSiteFontPairing : AI_SITE_FONT_PAIRINGS[0];
|
|
912
|
+
const variants = _extends({}, (_ref = (_DEFAULT_SITE_THEME_typography = DEFAULT_SITE_THEME.typography) == null ? void 0 : _DEFAULT_SITE_THEME_typography.variants) != null ? _ref : {});
|
|
913
|
+
for (const [key, { size, share }] of Object.entries(SCALE_SHARE)){
|
|
914
|
+
const big = key === 'displayXl' || key === 'h1' || key === 'h2';
|
|
915
|
+
const variant = {
|
|
916
|
+
fontSize: rem(size * (1 + (style.headingScale - 1) * share) * (pairing.caps && big ? 0.92 : 1)),
|
|
917
|
+
fontWeight: big ? pairing.heading.weight : pairing.heading.sub,
|
|
918
|
+
lineHeight: big ? pairing.caps ? 1.05 : 1.1 : 1.25
|
|
919
|
+
};
|
|
920
|
+
if (pairing.tracking !== undefined && (big || key === 'h3')) variant.letterSpacing = `${pairing.tracking}em`;
|
|
921
|
+
if (pairing.caps && (big || key === 'h3')) variant.textTransform = 'uppercase';
|
|
922
|
+
variants[key] = variant;
|
|
923
|
+
}
|
|
924
|
+
const bodyBold = pairing.body.weights.includes(600) ? 600 : 700;
|
|
925
|
+
variants.overline = style.eyebrow === 'small' ? {
|
|
926
|
+
fontSize: '0.875rem',
|
|
927
|
+
fontWeight: bodyBold,
|
|
928
|
+
letterSpacing: '0.01em',
|
|
929
|
+
textTransform: 'none'
|
|
930
|
+
} : {
|
|
931
|
+
fontSize: '0.78rem',
|
|
932
|
+
fontWeight: 700,
|
|
933
|
+
letterSpacing: style.eyebrow === 'rule' ? '0.16em' : '0.12em',
|
|
934
|
+
textTransform: 'uppercase'
|
|
935
|
+
};
|
|
936
|
+
variants.button = _extends({
|
|
937
|
+
fontWeight: style.buttons === 'caps' ? 700 : bodyBold,
|
|
938
|
+
textTransform: style.buttons === 'caps' ? 'uppercase' : 'none'
|
|
939
|
+
}, style.buttons === 'caps' ? {
|
|
940
|
+
letterSpacing: '0.06em'
|
|
941
|
+
} : {});
|
|
942
|
+
return {
|
|
943
|
+
variants
|
|
944
|
+
};
|
|
945
|
+
}
|
|
946
|
+
/** The card's look, in theme terms only: palette paths, shadow indexes and border widths. */ function cardSx(cards) {
|
|
947
|
+
switch(cards){
|
|
948
|
+
case 'flat':
|
|
949
|
+
return {
|
|
950
|
+
boxShadow: 0,
|
|
951
|
+
border: 0,
|
|
952
|
+
bgcolor: 'surface.main'
|
|
953
|
+
};
|
|
954
|
+
case 'outlined':
|
|
955
|
+
return {
|
|
956
|
+
boxShadow: 0,
|
|
957
|
+
border: 1,
|
|
958
|
+
borderColor: 'divider',
|
|
959
|
+
bgcolor: 'background.paper'
|
|
960
|
+
};
|
|
961
|
+
case 'elevated':
|
|
962
|
+
return {
|
|
963
|
+
boxShadow: 3,
|
|
964
|
+
border: 0,
|
|
965
|
+
bgcolor: 'background.paper'
|
|
966
|
+
};
|
|
967
|
+
case 'tinted':
|
|
968
|
+
return {
|
|
969
|
+
boxShadow: 0,
|
|
970
|
+
border: 0,
|
|
971
|
+
bgcolor: 'tint.primary'
|
|
972
|
+
};
|
|
973
|
+
case 'rule':
|
|
974
|
+
return {
|
|
975
|
+
boxShadow: 0,
|
|
976
|
+
border: 0,
|
|
977
|
+
borderTop: 3,
|
|
978
|
+
borderColor: 'primary.main',
|
|
979
|
+
borderRadius: 0,
|
|
980
|
+
bgcolor: 'background.paper'
|
|
981
|
+
};
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
/** The component defaults and styles a style sets: buttons, cards, fields, eyebrows, the header bar and the FAQ. */ function componentsFor(style) {
|
|
985
|
+
var _card_borderTop;
|
|
986
|
+
const radius = RADIUS[style.corners];
|
|
987
|
+
const buttonRadius = style.buttons === 'pill' ? 999 : style.buttons === 'square' ? 2 : radius;
|
|
988
|
+
const card = cardSx(style.cards);
|
|
989
|
+
return _extends({
|
|
990
|
+
MuiButton: _extends({
|
|
991
|
+
defaultProps: {
|
|
992
|
+
disableElevation: true
|
|
993
|
+
},
|
|
994
|
+
// 44px: the touch target a visitor's thumb needs.
|
|
995
|
+
styleOverrides: {
|
|
996
|
+
root: {
|
|
997
|
+
borderRadius: buttonRadius,
|
|
998
|
+
minHeight: 44,
|
|
999
|
+
boxShadow: 'none'
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
}, style.buttons === 'link' ? {
|
|
1003
|
+
variants: [
|
|
1004
|
+
{
|
|
1005
|
+
props: {
|
|
1006
|
+
variant: 'text'
|
|
1007
|
+
},
|
|
1008
|
+
style: {
|
|
1009
|
+
textDecoration: 'underline',
|
|
1010
|
+
textUnderlineOffset: '0.3em',
|
|
1011
|
+
textDecorationThickness: '2px'
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
]
|
|
1015
|
+
} : {}),
|
|
1016
|
+
MuiCard: {
|
|
1017
|
+
defaultProps: style.cards === 'outlined' ? {
|
|
1018
|
+
variant: 'outlined'
|
|
1019
|
+
} : {
|
|
1020
|
+
variant: 'elevation',
|
|
1021
|
+
elevation: style.cards === 'elevated' ? 3 : 0
|
|
1022
|
+
},
|
|
1023
|
+
styleOverrides: {
|
|
1024
|
+
root: {
|
|
1025
|
+
borderRadius: style.cards === 'rule' ? 0 : Math.max(radius, 4)
|
|
1026
|
+
}
|
|
1027
|
+
},
|
|
1028
|
+
sx: {
|
|
1029
|
+
root: card
|
|
1030
|
+
}
|
|
1031
|
+
},
|
|
1032
|
+
MuiAccordion: {
|
|
1033
|
+
defaultProps: {
|
|
1034
|
+
disableGutters: true,
|
|
1035
|
+
elevation: 0
|
|
1036
|
+
},
|
|
1037
|
+
sx: {
|
|
1038
|
+
root: _extends({}, card, {
|
|
1039
|
+
borderTop: style.cards === 'rule' ? 1 : (_card_borderTop = card['borderTop']) != null ? _card_borderTop : card['border'],
|
|
1040
|
+
'&::before': {
|
|
1041
|
+
display: 'none'
|
|
1042
|
+
},
|
|
1043
|
+
'&:not(:last-of-type)': {
|
|
1044
|
+
mb: 1
|
|
1045
|
+
}
|
|
1046
|
+
})
|
|
1047
|
+
}
|
|
1048
|
+
},
|
|
1049
|
+
MuiTextField: {
|
|
1050
|
+
defaultProps: {
|
|
1051
|
+
variant: style.fields
|
|
1052
|
+
}
|
|
1053
|
+
},
|
|
1054
|
+
MuiOutlinedInput: {
|
|
1055
|
+
styleOverrides: {
|
|
1056
|
+
root: {
|
|
1057
|
+
borderRadius: radius
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
},
|
|
1061
|
+
MuiFilledInput: {
|
|
1062
|
+
styleOverrides: {
|
|
1063
|
+
root: {
|
|
1064
|
+
borderTopLeftRadius: radius,
|
|
1065
|
+
borderTopRightRadius: radius
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
},
|
|
1069
|
+
MuiChip: {
|
|
1070
|
+
styleOverrides: {
|
|
1071
|
+
root: {
|
|
1072
|
+
borderRadius: style.corners === 'sharp' ? 2 : 999
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
},
|
|
1076
|
+
MuiAppBar: {
|
|
1077
|
+
defaultProps: {
|
|
1078
|
+
elevation: 0
|
|
1079
|
+
},
|
|
1080
|
+
variants: [
|
|
1081
|
+
{
|
|
1082
|
+
props: {
|
|
1083
|
+
color: 'inherit'
|
|
1084
|
+
},
|
|
1085
|
+
sx: style.header === 'line' ? {
|
|
1086
|
+
boxShadow: 0,
|
|
1087
|
+
borderBottom: 1,
|
|
1088
|
+
borderColor: 'divider'
|
|
1089
|
+
} : style.header === 'shadow' ? {
|
|
1090
|
+
boxShadow: 2,
|
|
1091
|
+
borderBottom: 0
|
|
1092
|
+
} : {
|
|
1093
|
+
boxShadow: 0,
|
|
1094
|
+
borderBottom: 0
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
]
|
|
1098
|
+
}
|
|
1099
|
+
}, style.eyebrow === 'rule' ? {
|
|
1100
|
+
MuiTypography: {
|
|
1101
|
+
variants: [
|
|
1102
|
+
{
|
|
1103
|
+
props: {
|
|
1104
|
+
variant: 'overline'
|
|
1105
|
+
},
|
|
1106
|
+
// A short line in the eyebrow's own color, so it reads on any band.
|
|
1107
|
+
style: {
|
|
1108
|
+
'&::before': {
|
|
1109
|
+
content: '""',
|
|
1110
|
+
display: 'inline-block',
|
|
1111
|
+
width: '1.75em',
|
|
1112
|
+
height: '2px',
|
|
1113
|
+
marginRight: '0.75em',
|
|
1114
|
+
verticalAlign: 'middle',
|
|
1115
|
+
backgroundColor: 'currentColor'
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
]
|
|
1120
|
+
}
|
|
1121
|
+
} : {});
|
|
1122
|
+
}
|
|
1123
|
+
function isRecord(value) {
|
|
1124
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
1125
|
+
}
|
|
1126
|
+
/** `over` merged into `under`, objects key by key, anything else replaced. */ function merged(under, over) {
|
|
1127
|
+
if (!isRecord(under) || !isRecord(over)) return over === undefined ? under : over;
|
|
1128
|
+
const out = _extends({}, under);
|
|
1129
|
+
for (const [key, value] of Object.entries(over))out[key] = key in out ? merged(out[key], value) : value;
|
|
1130
|
+
return out;
|
|
1131
|
+
}
|
|
1132
|
+
/** Whether a theme passes the publish check and the brand-on-page contrast the doctrine asks. */ function themeReads(theme) {
|
|
1133
|
+
if (!validateThemeForPublish(theme).ok) return false;
|
|
1134
|
+
for (const scheme of [
|
|
1135
|
+
'light',
|
|
1136
|
+
'dark'
|
|
1137
|
+
]){
|
|
1138
|
+
var _theme_colorSchemes, _colors_primary, _colors_background;
|
|
1139
|
+
const colors = (_theme_colorSchemes = theme.colorSchemes) == null ? void 0 : _theme_colorSchemes[scheme];
|
|
1140
|
+
const main = colors == null ? void 0 : (_colors_primary = colors.primary) == null ? void 0 : _colors_primary.main;
|
|
1141
|
+
const page = colors == null ? void 0 : (_colors_background = colors.background) == null ? void 0 : _colors_background.default;
|
|
1142
|
+
if (!main || !page || ratio(main, page) < 3) return false;
|
|
1143
|
+
for (const key of [
|
|
1144
|
+
'primary',
|
|
1145
|
+
'secondary',
|
|
1146
|
+
'tertiary'
|
|
1147
|
+
]){
|
|
1148
|
+
const fill = colors == null ? void 0 : colors[key];
|
|
1149
|
+
if ((fill == null ? void 0 : fill.main) && fill.contrastText && ratio(fill.contrastText, fill.main) < 4.5) return false;
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1152
|
+
return true;
|
|
1153
|
+
}
|
|
1154
|
+
/**
|
|
1155
|
+
* The site's theme: the base theme with this style layered over it. Colors,
|
|
1156
|
+
* type, corners, spacing and the building blocks' component styles are the
|
|
1157
|
+
* site's own; whatever the base sets beyond them — its focus rings, its menu
|
|
1158
|
+
* and dialog styles, its control heights — stays the base's.
|
|
1159
|
+
*/ export function aiSiteTheme(style, base) {
|
|
1160
|
+
var _aiSiteFontPairing, _base_typography, _base_shape, _base_components;
|
|
1161
|
+
var _theme_fonts;
|
|
1162
|
+
const pairing = (_aiSiteFontPairing = aiSiteFontPairing(style.fonts)) != null ? _aiSiteFontPairing : AI_SITE_FONT_PAIRINGS[0];
|
|
1163
|
+
const colors = aiSiteColors(style);
|
|
1164
|
+
const keep = (scheme)=>{
|
|
1165
|
+
var _ref;
|
|
1166
|
+
var _base_colorSchemes;
|
|
1167
|
+
const own = (_ref = (_base_colorSchemes = base.colorSchemes) == null ? void 0 : _base_colorSchemes[scheme]) != null ? _ref : {};
|
|
1168
|
+
// The status colors stay the base's: they mean the same thing on every site.
|
|
1169
|
+
return _extends({}, own.error ? {
|
|
1170
|
+
error: own.error
|
|
1171
|
+
} : {}, own.warning ? {
|
|
1172
|
+
warning: own.warning
|
|
1173
|
+
} : {}, own.info ? {
|
|
1174
|
+
info: own.info
|
|
1175
|
+
} : {}, own.success ? {
|
|
1176
|
+
success: own.success
|
|
1177
|
+
} : {}, colors[scheme]);
|
|
1178
|
+
};
|
|
1179
|
+
let theme = _extends({}, base, {
|
|
1180
|
+
colorSchemes: {
|
|
1181
|
+
light: keep('light'),
|
|
1182
|
+
dark: keep('dark')
|
|
1183
|
+
},
|
|
1184
|
+
typography: merged((_base_typography = base.typography) != null ? _base_typography : {}, typographyFor(style)),
|
|
1185
|
+
shape: _extends({}, (_base_shape = base.shape) != null ? _base_shape : {}, {
|
|
1186
|
+
borderRadius: RADIUS[style.corners]
|
|
1187
|
+
}),
|
|
1188
|
+
spacing: SPACING[style.density],
|
|
1189
|
+
components: merged((_base_components = base.components) != null ? _base_components : {}, componentsFor(style))
|
|
1190
|
+
});
|
|
1191
|
+
delete theme.darkScheme;
|
|
1192
|
+
const sameFamily = pairing.heading.family === pairing.body.family;
|
|
1193
|
+
theme = writeThemeFonts(theme, {
|
|
1194
|
+
body: {
|
|
1195
|
+
family: pairing.body.family,
|
|
1196
|
+
category: pairing.body.category,
|
|
1197
|
+
weights: [
|
|
1198
|
+
...pairing.body.weights
|
|
1199
|
+
],
|
|
1200
|
+
source: 'google'
|
|
1201
|
+
},
|
|
1202
|
+
heading: sameFamily ? null : {
|
|
1203
|
+
family: pairing.heading.family,
|
|
1204
|
+
category: pairing.heading.category,
|
|
1205
|
+
weights: [
|
|
1206
|
+
...new Set([
|
|
1207
|
+
pairing.heading.weight,
|
|
1208
|
+
pairing.heading.sub
|
|
1209
|
+
])
|
|
1210
|
+
].sort((a, b)=>a - b),
|
|
1211
|
+
source: 'google'
|
|
1212
|
+
}
|
|
1213
|
+
});
|
|
1214
|
+
// A pairing whose heading is the body family loads the heading weights too.
|
|
1215
|
+
if (sameFamily && ((_theme_fonts = theme.fonts) == null ? void 0 : _theme_fonts[0])) {
|
|
1216
|
+
var _theme_fonts__weights;
|
|
1217
|
+
theme.fonts[0] = _extends({}, theme.fonts[0], {
|
|
1218
|
+
weights: [
|
|
1219
|
+
...new Set([
|
|
1220
|
+
...(_theme_fonts__weights = theme.fonts[0].weights) != null ? _theme_fonts__weights : [],
|
|
1221
|
+
pairing.heading.weight,
|
|
1222
|
+
pairing.heading.sub
|
|
1223
|
+
])
|
|
1224
|
+
].sort((a, b)=>a - b)
|
|
1225
|
+
});
|
|
1226
|
+
}
|
|
1227
|
+
if (!themeReads(theme)) {
|
|
1228
|
+
// Never a theme that fails the reading checks: the starter's colors, with everything else kept.
|
|
1229
|
+
theme = _extends({}, theme, {
|
|
1230
|
+
colorSchemes: DEFAULT_SITE_THEME.colorSchemes
|
|
1231
|
+
});
|
|
1232
|
+
}
|
|
1233
|
+
return theme;
|
|
1234
|
+
}
|
|
1235
|
+
|
|
1236
|
+
//# sourceMappingURL=ai-site-look.js.map
|