@graineai/inapp-react-native 0.39.0 → 0.40.0
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/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/react-native/appearance.d.ts +2 -0
- package/dist/react-native/appearance.js +2 -1
- package/dist/react-native/fab.js +14 -9
- package/dist/react-native/voice-bar.js +31 -11
- package/dist/react-native/voice-launcher.d.ts +5 -0
- package/dist/react-native/voice-launcher.js +38 -0
- package/dist/theme.d.ts +12 -0
- package/dist/theme.js +743 -2
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -35,5 +35,5 @@ export { GraineLauncherButton, type GraineLauncherButtonProps } from "./react-na
|
|
|
35
35
|
export { faceFor, facesForDesign, fontUrl, FONT_ASSET_VERSION, type FontLoader, type FontFace } from "./react-native/fonts.js";
|
|
36
36
|
export { GraineVoiceLauncher, CAPTION_HISTORY, type GraineVoiceLauncherProps, type GraineVoiceApi, } from "./react-native/voice-launcher.js";
|
|
37
37
|
export { LauncherVisibilityTracker, activeRouteName, type LauncherInset, type LauncherContinuity, type LauncherDelayPolicy, type LauncherGroup, type LauncherVisibility, type VisibilityDecision, } from "./react-native/navigation.js";
|
|
38
|
-
export { DESIGN_PRESETS, DEFAULT_DESIGN_PRESET, DESIGN_FONTS, LAUNCHER_ICONS, SDK_ICONS, DESIGN_CHROMES, designPreset, normaliseDesignConfig, designFromAppearance, resolveTokens, resolveDesign, pickTokens, contrastRatio, readableOn, glyphUrl, type DesignConfig, type DesignPreset, type ThemeTokens, type ThemeColors, type ThemeShadow, type ResolvedDesign, type DesignBubbles, type DesignChrome, type LauncherShape, type MotionEasing, type IconStyle, type SdkIconName, } from "./theme.js";
|
|
38
|
+
export { DESIGN_PRESETS, DEFAULT_DESIGN_PRESET, DESIGN_FONTS, LAUNCHER_ICONS, SDK_ICONS, DESIGN_CHROMES, LOGO_PLATES, LOGO_PLATE_WHITE, normaliseLogoPlate, logoBox, logoPlateLook, designPreset, normaliseDesignConfig, designFromAppearance, resolveTokens, resolveDesign, pickTokens, contrastRatio, readableOn, glyphUrl, type DesignConfig, type DesignPreset, type ThemeTokens, type ThemeColors, type ThemeShadow, type ResolvedDesign, type DesignBubbles, type DesignChrome, type LogoPlate, type LauncherShape, type MotionEasing, type IconStyle, type SdkIconName, } from "./theme.js";
|
|
39
39
|
export { sessionDesign } from "./design.js";
|
package/dist/index.js
CHANGED
|
@@ -26,5 +26,5 @@ export { GraineLauncherButton } from "./react-native/fab.js";
|
|
|
26
26
|
export { faceFor, facesForDesign, fontUrl, FONT_ASSET_VERSION } from "./react-native/fonts.js";
|
|
27
27
|
export { GraineVoiceLauncher, CAPTION_HISTORY, } from "./react-native/voice-launcher.js";
|
|
28
28
|
export { LauncherVisibilityTracker, activeRouteName, } from "./react-native/navigation.js";
|
|
29
|
-
export { DESIGN_PRESETS, DEFAULT_DESIGN_PRESET, DESIGN_FONTS, LAUNCHER_ICONS, SDK_ICONS, DESIGN_CHROMES, designPreset, normaliseDesignConfig, designFromAppearance, resolveTokens, resolveDesign, pickTokens, contrastRatio, readableOn, glyphUrl, } from "./theme.js";
|
|
29
|
+
export { DESIGN_PRESETS, DEFAULT_DESIGN_PRESET, DESIGN_FONTS, LAUNCHER_ICONS, SDK_ICONS, DESIGN_CHROMES, LOGO_PLATES, LOGO_PLATE_WHITE, normaliseLogoPlate, logoBox, logoPlateLook, designPreset, normaliseDesignConfig, designFromAppearance, resolveTokens, resolveDesign, pickTokens, contrastRatio, readableOn, glyphUrl, } from "./theme.js";
|
|
30
30
|
export { sessionDesign } from "./design.js";
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { normaliseLogoPlate } from "../theme.js";
|
|
1
2
|
const HEX = /^#?([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/;
|
|
2
3
|
const RADIUS_CEILING = 48;
|
|
3
4
|
const DEFAULT_ORIGIN = "https://www.graine.ai";
|
|
@@ -25,7 +26,7 @@ export function resolveNativeAppearance(appearance, fallback) {
|
|
|
25
26
|
const origin = String(fallback.origin || DEFAULT_ORIGIN).replace(/\/+$/, "");
|
|
26
27
|
const logoUrl = logoKey ? `${origin}/api/embed/logo?k=${encodeURIComponent(logoKey)}` : null;
|
|
27
28
|
const radius = clamp(numeric(a.radius) ?? numeric(fallback.radius) ?? 18, max);
|
|
28
|
-
return { accent, heading, logoUrl, radius };
|
|
29
|
+
return { accent, heading, logoUrl, logoPlate: normaliseLogoPlate(a.logoPlate), radius };
|
|
29
30
|
}
|
|
30
31
|
const LIGHT = { surface: "#FFFFFF", border: "#E5E4E0", text: "#101318", muted: "#5F6672" };
|
|
31
32
|
const DARK = { surface: "#17181B", border: "#2C2E33", text: "#F5F5F4", muted: "#A0A5AE" };
|
package/dist/react-native/fab.js
CHANGED
|
@@ -7,7 +7,7 @@ import { GraineAgentBar } from "./ui.js";
|
|
|
7
7
|
import { resolveNativeAppearance } from "./appearance.js";
|
|
8
8
|
import { useDesign } from "./use-design.js";
|
|
9
9
|
import { glyphPixels, launcherRadius, onAccentFor, shadowStyle } from "./design-math.js";
|
|
10
|
-
import { glyphUrl } from "../theme.js";
|
|
10
|
+
import { glyphUrl, LOGO_PLATE_WHITE, logoBox, logoPlateLook, readableOn } from "../theme.js";
|
|
11
11
|
import { UNNAMED_TAP } from "../protocol.js";
|
|
12
12
|
function AboveNativeScreens({ overlay: Overlay, children, }) {
|
|
13
13
|
if (!Overlay)
|
|
@@ -69,15 +69,18 @@ export function GraineFab({ webView, position = "bottom-right", size = 56, accen
|
|
|
69
69
|
return null;
|
|
70
70
|
const side = position === "bottom-left" ? { left: launcherInset.right } : { right: launcherInset.right };
|
|
71
71
|
if (!look.classic) {
|
|
72
|
-
return (_jsx(DesignedLauncher, { look: look, size: size, accent: accentProp ?? look.tokens.colors.accent, name: name, label: label ?? `Talk to ${name}`, logoUrl: dashboard.logoUrl, origin: client.opts.baseUrl, place: { position: "absolute", bottom: launcherInset.bottom, ...side }, onPress: () => { setOpen(true); api.start(); onOpen?.(); } }));
|
|
72
|
+
return (_jsx(DesignedLauncher, { look: look, size: size, accent: accentProp ?? look.tokens.colors.accent, name: name, label: label ?? `Talk to ${name}`, logoUrl: dashboard.logoUrl, logoPlate: dashboard.logoPlate, origin: client.opts.baseUrl, place: { position: "absolute", bottom: launcherInset.bottom, ...side }, onPress: () => { setOpen(true); api.start(); onOpen?.(); } }));
|
|
73
73
|
}
|
|
74
74
|
return (_jsx(Pressable, { accessibilityRole: "button", accessibilityLabel: label ?? `Talk to ${name}`, onPress: () => { setOpen(true); api.start(); onOpen?.(); }, style: ({ pressed }) => ({
|
|
75
75
|
position: "absolute", bottom: launcherInset.bottom, ...side,
|
|
76
|
-
width: size, height: size, borderRadius: size / 2,
|
|
76
|
+
width: size, height: size, borderRadius: size / 2,
|
|
77
|
+
...(dashboard.logoUrl && dashboard.logoPlate === "white"
|
|
78
|
+
? { backgroundColor: LOGO_PLATE_WHITE, borderWidth: 1, borderColor: "#E5E7EB" }
|
|
79
|
+
: { backgroundColor: dashboardAccent }),
|
|
77
80
|
alignItems: "center", justifyContent: "center",
|
|
78
81
|
opacity: pressed ? 0.85 : 1,
|
|
79
82
|
elevation: 6, shadowColor: "#000", shadowOpacity: 0.25, shadowRadius: 8, shadowOffset: { width: 0, height: 4 },
|
|
80
|
-
}), children: dashboard.logoUrl ? (_jsx(Image, { source: { uri: dashboard.logoUrl }, style: { width: size
|
|
83
|
+
}), children: dashboard.logoUrl ? (_jsx(Image, { source: { uri: dashboard.logoUrl }, resizeMode: "contain", style: { width: logoBox("circle", size), height: logoBox("circle", size) }, accessibilityIgnoresInvertColors: true })) : (_jsx(Text, { style: { color: "#fff", fontSize: size * 0.4, fontWeight: "700" }, children: name.trim().charAt(0).toUpperCase() || "•" })) }));
|
|
81
84
|
};
|
|
82
85
|
if (!prewarm && !open)
|
|
83
86
|
return _jsx(AboveNativeScreens, { overlay: overlay, children: button({ start: () => { } }) });
|
|
@@ -87,9 +90,9 @@ export function GraineLauncherButton({ onPress, style, label, size = 56 }) {
|
|
|
87
90
|
const { appearance, client } = useGraineAgent();
|
|
88
91
|
const look = useDesign();
|
|
89
92
|
const dashboard = resolveNativeAppearance(appearance, { accent: look.tokens.colors.accent, heading: "Assistant" });
|
|
90
|
-
return (_jsx(DesignedLauncher, { look: look, size: size, accent: look.tokens.colors.accent, name: dashboard.heading, label: label ?? `Talk to ${dashboard.heading}`, logoUrl: dashboard.logoUrl, origin: client.opts.baseUrl, place: style ?? {}, onPress: onPress }));
|
|
93
|
+
return (_jsx(DesignedLauncher, { look: look, size: size, accent: look.tokens.colors.accent, name: dashboard.heading, label: label ?? `Talk to ${dashboard.heading}`, logoUrl: dashboard.logoUrl, logoPlate: dashboard.logoPlate, origin: client.opts.baseUrl, place: style ?? {}, onPress: onPress }));
|
|
91
94
|
}
|
|
92
|
-
function DesignedLauncher({ look, size, accent, name, label, logoUrl, origin, place, onPress, }) {
|
|
95
|
+
function DesignedLauncher({ look, size, accent, name, label, logoUrl, logoPlate = "accent", origin, place, onPress, }) {
|
|
93
96
|
const t = look.tokens;
|
|
94
97
|
const [failed, setFailed] = useState([]);
|
|
95
98
|
const fail = (uri) => setFailed((cur) => (cur.includes(uri) ? cur : [...cur, uri]));
|
|
@@ -113,11 +116,12 @@ function DesignedLauncher({ look, size, accent, name, label, logoUrl, origin, pl
|
|
|
113
116
|
const glyph = glyphUri && !failed.includes(glyphUri) ? glyphUri : null;
|
|
114
117
|
const logo = logoUrl && !failed.includes(logoUrl) ? logoUrl : null;
|
|
115
118
|
const hard = t.elevation.hardOffset;
|
|
119
|
+
const plate = logoPlateLook(!glyph && logo ? logoPlate : "accent", { ...t.colors, accent }, t.shape.borderWidth);
|
|
116
120
|
const corners = {
|
|
117
121
|
borderRadius: radius,
|
|
118
122
|
...(t.shape.launcher === "squircle" && Platform.OS === "ios" ? { borderCurve: "continuous" } : null),
|
|
119
123
|
};
|
|
120
|
-
const mark = glyph ? (_jsx(Image, { source: { uri: glyph }, style: { width: g, height: g }, onError: () => fail(glyph), accessibilityIgnoresInvertColors: true }, glyph)) : logo ? (_jsx(Image, { source: { uri: logo }, resizeMode: "contain", style: { width:
|
|
124
|
+
const mark = glyph ? (_jsx(Image, { source: { uri: glyph }, style: { width: g, height: g }, onError: () => fail(glyph), accessibilityIgnoresInvertColors: true }, glyph)) : logo ? (_jsx(Image, { source: { uri: logo }, resizeMode: "contain", style: { width: logoBox(pill ? "circle" : t.shape.launcher, size), height: logoBox(pill ? "circle" : t.shape.launcher, size) }, onError: () => fail(logo), accessibilityIgnoresInvertColors: true }, logo)) : (_jsx(Text, { style: {
|
|
121
125
|
color: onAccent, fontSize: Math.round(size * 0.46), fontWeight: String(t.font.weights.title),
|
|
122
126
|
fontFamily: look.font(t.font.heading),
|
|
123
127
|
}, children: name.trim().charAt(0).toUpperCase() || "\u2022" }));
|
|
@@ -126,11 +130,12 @@ function DesignedLauncher({ look, size, accent, name, label, logoUrl, origin, pl
|
|
|
126
130
|
height: size, minWidth: size, width: pill ? undefined : size,
|
|
127
131
|
flexDirection: "row", alignItems: "center", justifyContent: "center",
|
|
128
132
|
gap: pill ? 8 : 0, paddingHorizontal: pill ? Math.round(size * 0.32) : 0,
|
|
129
|
-
backgroundColor:
|
|
133
|
+
backgroundColor: plate.fill,
|
|
134
|
+
...(plate.outline && !hard ? { borderWidth: plate.outline.width, borderColor: plate.outline.color } : null),
|
|
130
135
|
opacity: pressed ? 0.85 : 1,
|
|
131
136
|
...(hard ? { borderWidth: t.shape.borderWidth, borderColor: t.colors.text } : shadowStyle(t.elevation.floating)),
|
|
132
137
|
}), children: [mark, pill ? (_jsx(Text, { numberOfLines: 1, style: {
|
|
133
|
-
color: onAccent, fontSize: t.font.scale.body, fontWeight: String(t.font.weights.strong),
|
|
138
|
+
color: plate.outline ? readableOn(LOGO_PLATE_WHITE) : onAccent, fontSize: t.font.scale.body, fontWeight: String(t.font.weights.strong),
|
|
134
139
|
fontFamily: look.font(t.font.body),
|
|
135
140
|
}, children: name })) : null] })] }));
|
|
136
141
|
}
|
|
@@ -2,19 +2,29 @@ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-run
|
|
|
2
2
|
import React, { useContext, useEffect, useRef, useState } from "react";
|
|
3
3
|
import { Animated, Easing, Image, PixelRatio, Platform, Pressable, ScrollView, Text, TextInput, View } from "react-native";
|
|
4
4
|
import { Ctx, useGraineAgent } from "./index.js";
|
|
5
|
-
import { GraineLoader } from "./loader.js";
|
|
5
|
+
import { GraineLoader, useGraineLoader } from "./loader.js";
|
|
6
6
|
import { GraineWidget } from "./widget.js";
|
|
7
7
|
import { resolveNativeAppearance } from "./appearance.js";
|
|
8
8
|
import { useDesign } from "./use-design.js";
|
|
9
9
|
import { glyphPixels, launcherRadius, letterSpacing, lineHeight, shadowStyle, surfaceFill } from "./design-math.js";
|
|
10
10
|
import { useKeyboardLift } from "./keyboard.js";
|
|
11
11
|
import { useDraggablePlacement } from "./placement.js";
|
|
12
|
-
import { glyphUrl, SDK_ICONS } from "../theme.js";
|
|
12
|
+
import { glyphUrl, logoBox, logoPlateLook, SDK_ICONS } from "../theme.js";
|
|
13
|
+
import { DEFAULT_LOADER_TEXT } from "../protocol.js";
|
|
13
14
|
import { TitleBar, windowed } from "./window-chrome.js";
|
|
14
15
|
const BAR_HEIGHT = 60;
|
|
15
16
|
const BUTTON = 38;
|
|
16
17
|
const METER = [0.55, 1, 0.7, 0.4];
|
|
17
|
-
function
|
|
18
|
+
function connectingLook(api, connectingText) {
|
|
19
|
+
if (api.connectStage === "retrying" || api.connectStage === "switching") {
|
|
20
|
+
return { text: "Reconnecting…", waiting: "reconnecting" };
|
|
21
|
+
}
|
|
22
|
+
if (api.slowConnect && connectingText === DEFAULT_LOADER_TEXT.connecting) {
|
|
23
|
+
return { text: "Still connecting…", waiting: "connecting", label: "Still connecting…" };
|
|
24
|
+
}
|
|
25
|
+
return { text: connectingText, waiting: "connecting" };
|
|
26
|
+
}
|
|
27
|
+
function statusOf(api, isChat, chatConnected, connectingText = DEFAULT_LOADER_TEXT.connecting) {
|
|
18
28
|
const conn = api.connectivity?.state;
|
|
19
29
|
const say = (text) => ({ text, waiting: null, meter: false });
|
|
20
30
|
if (api.error)
|
|
@@ -29,8 +39,10 @@ function statusOf(api, isChat, chatConnected) {
|
|
|
29
39
|
return say("Needs microphone");
|
|
30
40
|
if (conn === "failed")
|
|
31
41
|
return say("Couldn’t load · tap to retry");
|
|
42
|
+
if (api.callFailed)
|
|
43
|
+
return say("Couldn’t connect · tap to retry");
|
|
32
44
|
if (!api.ready || api.connecting)
|
|
33
|
-
return {
|
|
45
|
+
return { ...connectingLook(api, connectingText), meter: false };
|
|
34
46
|
if (api.muted)
|
|
35
47
|
return say("Muted");
|
|
36
48
|
if (api.connected && api.phase === "thinking")
|
|
@@ -63,7 +75,8 @@ export function GraineVoiceBar({ api, onClose, bottomInset, draggable = true, pl
|
|
|
63
75
|
initialOffset: placement?.bottomOffset,
|
|
64
76
|
onChange: (bottomOffset) => onPlacementChange?.({ bottomOffset }),
|
|
65
77
|
});
|
|
66
|
-
const
|
|
78
|
+
const loader = useGraineLoader();
|
|
79
|
+
const status = statusOf(api, isChat, chatConnected, loader.text.connecting);
|
|
67
80
|
const conn = api.connectivity?.state;
|
|
68
81
|
const micBlocked = !!api.error || api.micDenied || api.connecting || (!api.ready && conn !== "failed");
|
|
69
82
|
const caption = !isChat && api.captionsOn && api.caption?.text ? api.caption : null;
|
|
@@ -79,11 +92,11 @@ export function GraineVoiceBar({ api, onClose, bottomInset, draggable = true, pl
|
|
|
79
92
|
return (_jsxs(View, { pointerEvents: "box-none", ...drag.panHandlers, onLayout: drag.onLayout, style: {
|
|
80
93
|
position: "absolute", zIndex: 900, gap: 8, left: 12, right: 12,
|
|
81
94
|
bottom: baseBottom + drag.offset + keyboardLift,
|
|
82
|
-
}, children: [_jsx(WidgetGuard, { children: _jsx(GraineWidget, {}) }), isChat ? (_jsx(ChatWindow, { look: look, kit: kit, origin: origin, heading: heading, logoUrl: dash.logoUrl ?? undefined, messages: messages, send: send, online: chatConnected, lifted: keyboardLift > 0, onCollapse: () => (canSwitch ? setSurface("voice") : endCall()) })) : caption ? (_jsx(CaptionCard, { look: look, role: caption.role, text: caption.text, live: !!caption.live })) : null, _jsx(Raised, { t: t, level: "floating", radius: t.shape.radius.xl, children: _jsxs(View, { style: {
|
|
95
|
+
}, children: [_jsx(WidgetGuard, { children: _jsx(GraineWidget, {}) }), isChat ? (_jsx(ChatWindow, { look: look, kit: kit, origin: origin, heading: heading, logoUrl: dash.logoUrl ?? undefined, logoPlate: dash.logoPlate, messages: messages, send: send, online: chatConnected, lifted: keyboardLift > 0, onCollapse: () => (canSwitch ? setSurface("voice") : endCall()) })) : caption ? (_jsx(CaptionCard, { look: look, role: caption.role, text: caption.text, live: !!caption.live })) : null, _jsx(Raised, { t: t, level: "floating", radius: t.shape.radius.xl, children: _jsxs(View, { style: {
|
|
83
96
|
height: BAR_HEIGHT, paddingHorizontal: 9, flexDirection: "row", alignItems: "center", gap: 10,
|
|
84
97
|
backgroundColor: surfaceFill(c.surface, t), borderWidth: t.shape.borderWidth, borderColor: c.border,
|
|
85
98
|
borderRadius: t.shape.radius.xl, ...softShadow(t, "floating"),
|
|
86
|
-
}, children: [_jsx(View, { style: { width: BUTTON, height: BUTTON, borderRadius: BUTTON / 2,
|
|
99
|
+
}, children: [_jsx(View, { style: { width: BUTTON, height: BUTTON, borderRadius: BUTTON / 2, ...markFill(t, dash.logoPlate, !t.icons.launcher && !!dash.logoUrl), alignItems: "center", justifyContent: "center", overflow: "hidden" }, children: _jsx(Mark, { t: t, size: BUTTON, glyph: 18, kit: kit, origin: origin, logoUrl: dash.logoUrl ?? undefined, initial: heading.charAt(0).toUpperCase() || "A" }) }), _jsxs(View, { style: { flex: 1, minWidth: 0 }, children: [_jsx(Text, { numberOfLines: 1, style: { ...body(14, t.font.weights.strong), color: c.text, lineHeight: 17.5 }, children: heading }), _jsx(View, { style: { flexDirection: "row", alignItems: "center", gap: 6, marginTop: 1 }, children: status.waiting ? (_jsx(GraineLoader, { moment: status.waiting, label: status.label, size: 14 })) : (_jsxs(_Fragment, { children: [status.meter ? _jsx(Meter, { look: look, color: c.accent }) : null, _jsx(Text, { numberOfLines: 1, style: { ...body(t.font.scale.caption, t.font.weights.body), color: c.textMuted, lineHeight: lineHeight(t.font.scale.caption, 1.3) }, children: status.text })] })) })] }), canSwitch ? (_jsx(RoundButton, { label: isChat ? "Switch to a call" : "Switch to chat", fill: c.accentSoft, onPress: () => setSurface(isChat ? "voice" : "chat"), children: _jsx(Icon, { name: isChat ? "call" : "chat", t: t, color: c.text, kit: kit }) })) : null, isChat ? null : (_jsx(RoundButton, { label: !api.connected ? `Start talking to ${spoken}` : api.muted ? "Unmute" : "Mute", fill: api.muted ? c.danger : c.accent, disabled: micBlocked, onPress: () => {
|
|
87
100
|
if (conn === "failed") {
|
|
88
101
|
api.retry();
|
|
89
102
|
return;
|
|
@@ -125,7 +138,13 @@ function Icon({ name, t, color, size = 18, kit, rotate }) {
|
|
|
125
138
|
const common = { stroke: color, strokeLinecap: sharp ? "square" : "round", strokeLinejoin: sharp ? "miter" : "round" };
|
|
126
139
|
return (_jsx(View, { style: rotate ? { transform: [{ rotate: "180deg" }] } : undefined, children: _jsxs(Svg, { width: size, height: size, viewBox: "0 0 24 24", children: [icon.body ? (_jsx(Path, { d: icon.body, ...common, fill: filled || style === "duotone" ? color : "none", fillOpacity: style === "duotone" ? 0.28 : 1, strokeWidth: filled ? Math.max(1, stroke * 0.75) : stroke })) : null, icon.lines ? _jsx(Path, { d: icon.lines, ...common, fill: "none", strokeWidth: stroke }) : null] }) }));
|
|
127
140
|
}
|
|
128
|
-
function
|
|
141
|
+
function markFill(t, plate, showsLogo) {
|
|
142
|
+
const look = logoPlateLook(showsLogo ? plate : "accent", t.colors, t.shape.borderWidth);
|
|
143
|
+
return look.outline
|
|
144
|
+
? { backgroundColor: look.fill, borderWidth: look.outline.width, borderColor: look.outline.color }
|
|
145
|
+
: { backgroundColor: look.fill };
|
|
146
|
+
}
|
|
147
|
+
function Mark({ t, size, glyph, kit, origin, logoUrl, initial, shape = "circle", }) {
|
|
129
148
|
const on = t.colors.onAccent;
|
|
130
149
|
const g = t.icons.launcher;
|
|
131
150
|
if (g) {
|
|
@@ -136,7 +155,8 @@ function Mark({ t, size, glyph, kit, origin, logoUrl, initial, cover = false, })
|
|
|
136
155
|
return (_jsx(Image, { source: { uri: glyphUrl(origin, g.id, on, glyphPixels(size, PixelRatio.get()), t.icons.stroke) }, style: { width: glyph, height: glyph }, accessibilityIgnoresInvertColors: true }));
|
|
137
156
|
}
|
|
138
157
|
if (logoUrl) {
|
|
139
|
-
|
|
158
|
+
const box = logoBox(shape, size);
|
|
159
|
+
return _jsx(Image, { source: { uri: logoUrl }, style: { width: box, height: box }, resizeMode: "contain", accessibilityIgnoresInvertColors: true });
|
|
140
160
|
}
|
|
141
161
|
return _jsx(Text, { style: { color: on, fontSize: Math.round(size * 0.4), fontWeight: "700" }, children: initial });
|
|
142
162
|
}
|
|
@@ -191,7 +211,7 @@ function CaptionCard({ look, role, text, live }) {
|
|
|
191
211
|
borderRadius: t.shape.radius.lg, paddingHorizontal: 12, paddingVertical: 8, ...softShadow(t, "card"),
|
|
192
212
|
}, children: [win ? (_jsx(View, { style: { marginBottom: 6 }, children: _jsx(TitleBar, { t: t, title: "captions", size: "caption", radius: t.shape.radius.lg, bleed: { horizontal: 12, top: 8 } }) })) : null, role === "customer" ? (_jsx(Text, { style: { ...look.face(t.font.body, t.font.weights.strong), color: c.accent, fontSize: 10.5, letterSpacing: 0.6, textTransform: "uppercase", marginBottom: 2 }, children: "You" })) : null, _jsx(Text, { numberOfLines: 2, style: { ...look.face(t.font.body, t.font.weights.strong), color: c.text, fontSize: 13, lineHeight: 17.5, opacity: live ? 0.75 : 1 }, children: text })] }) }) }));
|
|
193
213
|
}
|
|
194
|
-
function ChatWindow({ look, kit, origin, heading, logoUrl, messages, send, online, lifted, onCollapse, }) {
|
|
214
|
+
function ChatWindow({ look, kit, origin, heading, logoUrl, logoPlate = "accent", messages, send, online, lifted, onCollapse, }) {
|
|
195
215
|
const t = look.tokens;
|
|
196
216
|
const c = t.colors;
|
|
197
217
|
const r = t.shape.radius;
|
|
@@ -217,7 +237,7 @@ function ChatWindow({ look, kit, origin, heading, logoUrl, messages, send, onlin
|
|
|
217
237
|
letterSpacing: letterSpacing(size, t.font.letterSpacing.body),
|
|
218
238
|
});
|
|
219
239
|
const titleSize = Math.min(t.font.scale.title, 16);
|
|
220
|
-
return (_jsx(Animated.View, { style: rise, children: _jsx(Raised, { t: t, level: "floating", radius: r.xl, children: _jsx(View, { style: { backgroundColor: c.background, borderWidth: bw, borderColor: c.border, borderRadius: r.xl, ...softShadow(t, "floating") }, children: _jsxs(View, { style: { borderRadius: Math.max(0, r.xl - bw), overflow: "hidden" }, children: [windowed(t) ? (_jsx(TitleBar, { t: t, title: heading, online: online, radius: r.xl, right: _jsx(Pressable, { onPress: onCollapse, hitSlop: 10, accessibilityRole: "button", accessibilityLabel: "Collapse", children: _jsx(Icon, { name: "chevron", t: t, color: c.textMuted, kit: kit, size: 16, rotate: true }) }) })) : (_jsxs(View, { style: { height: 50, flexDirection: "row", alignItems: "center", gap: 10, paddingHorizontal: 12, backgroundColor: surfaceFill(c.surface, t), borderBottomWidth: bw, borderBottomColor: c.border }, children: [_jsx(View, { style: { width: 30, height: 30, borderRadius: launcherRadius(markShape, 30, t),
|
|
240
|
+
return (_jsx(Animated.View, { style: rise, children: _jsx(Raised, { t: t, level: "floating", radius: r.xl, children: _jsx(View, { style: { backgroundColor: c.background, borderWidth: bw, borderColor: c.border, borderRadius: r.xl, ...softShadow(t, "floating") }, children: _jsxs(View, { style: { borderRadius: Math.max(0, r.xl - bw), overflow: "hidden" }, children: [windowed(t) ? (_jsx(TitleBar, { t: t, title: heading, online: online, radius: r.xl, right: _jsx(Pressable, { onPress: onCollapse, hitSlop: 10, accessibilityRole: "button", accessibilityLabel: "Collapse", children: _jsx(Icon, { name: "chevron", t: t, color: c.textMuted, kit: kit, size: 16, rotate: true }) }) })) : (_jsxs(View, { style: { height: 50, flexDirection: "row", alignItems: "center", gap: 10, paddingHorizontal: 12, backgroundColor: surfaceFill(c.surface, t), borderBottomWidth: bw, borderBottomColor: c.border }, children: [_jsx(View, { style: { width: 30, height: 30, borderRadius: launcherRadius(markShape, 30, t), ...markFill(t, logoPlate, !t.icons.launcher && !!logoUrl), alignItems: "center", justifyContent: "center", overflow: "hidden", ...(Platform.OS === "ios" && markShape === "squircle" ? { borderCurve: "continuous" } : null) }, children: _jsx(Mark, { t: t, size: 30, glyph: 14, kit: kit, origin: origin, logoUrl: logoUrl, initial: heading.charAt(0).toUpperCase() || "A", shape: markShape }) }), _jsxs(View, { style: { flex: 1, minWidth: 0 }, children: [_jsx(Text, { numberOfLines: 1, style: { ...look.face(t.font.heading, t.font.weights.title), color: c.text, fontSize: titleSize, lineHeight: lineHeight(titleSize, t.font.lineHeight.title), letterSpacing: letterSpacing(titleSize, t.font.letterSpacing.title) }, children: heading }), _jsxs(View, { style: { flexDirection: "row", alignItems: "center", gap: 4 }, children: [online ? _jsx(View, { style: { width: 6, height: 6, borderRadius: 3, backgroundColor: c.success } }) : null, _jsx(Text, { style: { ...text(t.font.scale.caption, t.font.weights.body), color: c.textMuted, lineHeight: lineHeight(t.font.scale.caption, 1.2) }, children: online ? "Online" : "Connecting…" })] })] }), _jsx(Pressable, { onPress: onCollapse, hitSlop: 10, accessibilityRole: "button", accessibilityLabel: "Collapse", children: _jsx(Icon, { name: "chevron", t: t, color: c.textMuted, kit: kit, rotate: true }) })] })), _jsxs(ScrollView, { ref: scroll, style: { maxHeight: lifted ? 150 : 260 }, contentContainerStyle: { padding: 12, gap: t.spacing.gap }, keyboardShouldPersistTaps: "handled", children: [messages.length === 0 ? (_jsx(Text, { style: { ...text(t.font.scale.body, t.font.weights.body), color: c.textMuted, lineHeight: lineHeight(t.font.scale.body, t.font.lineHeight.body) }, children: online ? `${heading} can see what you’re looking at — ask it anything.` : "Connecting…" })) : null, messages.map((m, i) => {
|
|
221
241
|
const b = bubble(t, m.role !== "agent");
|
|
222
242
|
return (_jsx(View, { style: [{ maxWidth: "84%" }, b.box], children: _jsx(Text, { style: { ...text(t.font.scale.body, t.font.weights.body), color: b.color, lineHeight: lineHeight(t.font.scale.body, t.font.lineHeight.body), opacity: m.live ? 0.75 : 1 }, children: m.text }) }, i));
|
|
223
243
|
})] }), _jsxs(View, { style: { height: 58, flexDirection: "row", alignItems: "center", gap: 8, paddingHorizontal: 10, backgroundColor: c.background }, children: [_jsx(TextInput, { value: draft, onChangeText: setDraft, onSubmitEditing: submit, returnKeyType: "send", blurOnSubmit: false, placeholder: "Message\u2026", placeholderTextColor: c.textMuted, style: {
|
|
@@ -31,6 +31,7 @@ export interface GraineVoiceLauncherProps {
|
|
|
31
31
|
bootMs?: number;
|
|
32
32
|
hardCapMs?: number;
|
|
33
33
|
endWatchdogMs?: number;
|
|
34
|
+
slowConnectMs?: number;
|
|
34
35
|
};
|
|
35
36
|
children?: (api: GraineVoiceApi) => React.ReactNode;
|
|
36
37
|
}
|
|
@@ -61,6 +62,10 @@ export interface GraineVoiceApi {
|
|
|
61
62
|
reconnecting: boolean;
|
|
62
63
|
error: string | null;
|
|
63
64
|
micDenied: boolean;
|
|
65
|
+
connectStage: "registering" | "dialling" | "retrying" | "switching" | null;
|
|
66
|
+
slowConnect: boolean;
|
|
67
|
+
callFailed: boolean;
|
|
64
68
|
}
|
|
65
69
|
export declare const CAPTION_HISTORY = 12;
|
|
70
|
+
export declare const SLOW_CONNECT_MS = 4000;
|
|
66
71
|
export declare function GraineVoiceLauncher({ webView: WebView, autoStart, active: activeProp, transport, onTransport, onCaption, onCallState, onMicDenied, onError, timing, onEnded, children, }: GraineVoiceLauncherProps): React.JSX.Element;
|
|
@@ -4,6 +4,7 @@ import { AppState, View } from "react-native";
|
|
|
4
4
|
import { useGraineAgent } from "./index.js";
|
|
5
5
|
import { requestMicrophonePermission } from "./permissions.js";
|
|
6
6
|
export const CAPTION_HISTORY = 12;
|
|
7
|
+
export const SLOW_CONNECT_MS = 4000;
|
|
7
8
|
export function GraineVoiceLauncher({ webView: WebView, autoStart = false, active: activeProp = true, transport, onTransport, onCaption, onCallState, onMicDenied, onError, timing, onEnded, children, }) {
|
|
8
9
|
const { client, appearance, surface, surfaces } = useGraineAgent();
|
|
9
10
|
const active = activeProp && surface !== "chat";
|
|
@@ -22,6 +23,9 @@ export function GraineVoiceLauncher({ webView: WebView, autoStart = false, activ
|
|
|
22
23
|
const [muted, setMutedState] = useState(false);
|
|
23
24
|
const [fault, setFault] = useState(null);
|
|
24
25
|
const [micDenied, setMicDenied] = useState(false);
|
|
26
|
+
const [connectStage, setConnectStage] = useState(null);
|
|
27
|
+
const [slowConnect, setSlowConnect] = useState(false);
|
|
28
|
+
const [callFailed, setCallFailed] = useState(false);
|
|
25
29
|
const [ready, setReady] = useState(false);
|
|
26
30
|
const readyRef = useRef(false);
|
|
27
31
|
readyRef.current = ready;
|
|
@@ -291,6 +295,8 @@ export function GraineVoiceLauncher({ webView: WebView, autoStart = false, activ
|
|
|
291
295
|
if (msg.connected) {
|
|
292
296
|
endedNotifiedRef.current = false;
|
|
293
297
|
setReconnecting(false);
|
|
298
|
+
setConnectStage(null);
|
|
299
|
+
setCallFailed(false);
|
|
294
300
|
}
|
|
295
301
|
setConnected(!!msg.connected);
|
|
296
302
|
setConnecting(!!msg.connecting);
|
|
@@ -299,6 +305,22 @@ export function GraineVoiceLauncher({ webView: WebView, autoStart = false, activ
|
|
|
299
305
|
startedRef.current = false;
|
|
300
306
|
onCallState?.({ connected: !!msg.connected, connecting: !!msg.connecting });
|
|
301
307
|
return;
|
|
308
|
+
case "graine:progress": {
|
|
309
|
+
const stage = String(msg.stage || "");
|
|
310
|
+
if (stage === "failed") {
|
|
311
|
+
setConnectStage(null);
|
|
312
|
+
setConnecting(false);
|
|
313
|
+
setCallFailed(true);
|
|
314
|
+
startedRef.current = false;
|
|
315
|
+
pendingStartRef.current = false;
|
|
316
|
+
onCallState?.({ connected: false, connecting: false });
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
if (stage === "registering" || stage === "dialling" || stage === "retrying" || stage === "switching") {
|
|
320
|
+
setConnectStage(stage);
|
|
321
|
+
}
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
302
324
|
case "graine:muted":
|
|
303
325
|
setMutedState(!!msg.muted);
|
|
304
326
|
return;
|
|
@@ -372,6 +394,15 @@ export function GraineVoiceLauncher({ webView: WebView, autoStart = false, activ
|
|
|
372
394
|
}
|
|
373
395
|
}, [autoStart, beginCall, client, onCaption, onCallState, onMicDenied, onTransport, onError, onEnded, post, pushContext, foldCaption]);
|
|
374
396
|
const pendingStartRef = useRef(false);
|
|
397
|
+
const waitingOnConnect = active && !connected && !callFailed && (connecting || !ready);
|
|
398
|
+
useEffect(() => {
|
|
399
|
+
if (!waitingOnConnect) {
|
|
400
|
+
setSlowConnect(false);
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
403
|
+
const t = setTimeout(() => setSlowConnect(true), timing?.slowConnectMs ?? SLOW_CONNECT_MS);
|
|
404
|
+
return () => clearTimeout(t);
|
|
405
|
+
}, [waitingOnConnect]);
|
|
375
406
|
const lastCaption = captions.length ? captions[captions.length - 1] : null;
|
|
376
407
|
const phase = reconnecting || connectivity.state === "failed" || connectivity.state === "retrying" ? "reconnecting"
|
|
377
408
|
: connecting || (!ready && !!voiceAgentId) ? "connecting"
|
|
@@ -394,6 +425,8 @@ export function GraineVoiceLauncher({ webView: WebView, autoStart = false, activ
|
|
|
394
425
|
setConnecting(true);
|
|
395
426
|
setMicDenied(false);
|
|
396
427
|
setFault(null);
|
|
428
|
+
setCallFailed(false);
|
|
429
|
+
setConnectStage(null);
|
|
397
430
|
setCaptions([]);
|
|
398
431
|
setAwaitingAgent(false);
|
|
399
432
|
client.clearPendingWidget();
|
|
@@ -411,6 +444,9 @@ export function GraineVoiceLauncher({ webView: WebView, autoStart = false, activ
|
|
|
411
444
|
reconnecting,
|
|
412
445
|
error: fault,
|
|
413
446
|
micDenied,
|
|
447
|
+
connectStage,
|
|
448
|
+
slowConnect,
|
|
449
|
+
callFailed,
|
|
414
450
|
setMuted: useCallback((m) => {
|
|
415
451
|
setMutedState(m);
|
|
416
452
|
post({ type: "graine:set-muted", muted: m });
|
|
@@ -460,6 +496,8 @@ export function GraineVoiceLauncher({ webView: WebView, autoStart = false, activ
|
|
|
460
496
|
setCaptions([]);
|
|
461
497
|
setMicDenied(false);
|
|
462
498
|
setFault(null);
|
|
499
|
+
setCallFailed(false);
|
|
500
|
+
setConnectStage(null);
|
|
463
501
|
client.clearPendingWidget();
|
|
464
502
|
onCallState?.({ connected: false, connecting: false });
|
|
465
503
|
}, [active]);
|
package/dist/theme.d.ts
CHANGED
|
@@ -163,6 +163,18 @@ export declare const DESIGN_BUBBLES: ReadonlyArray<DesignBubbles>;
|
|
|
163
163
|
export declare const LAUNCHER_SHAPES: ReadonlyArray<LauncherShape>;
|
|
164
164
|
export declare const ICON_STYLES: ReadonlyArray<IconStyle>;
|
|
165
165
|
export declare const DESIGN_CHROMES: ReadonlyArray<DesignChrome>;
|
|
166
|
+
export type LogoPlate = "accent" | "white";
|
|
167
|
+
export declare const LOGO_PLATES: ReadonlyArray<LogoPlate>;
|
|
168
|
+
export declare const LOGO_PLATE_WHITE = "#FFFFFF";
|
|
169
|
+
export declare function normaliseLogoPlate(value: unknown): LogoPlate;
|
|
170
|
+
export declare function logoBox(shape: string, size: number): number;
|
|
171
|
+
export declare function logoPlateLook(plate: LogoPlate, colors: Pick<ThemeColors, "accent" | "border">, borderWidth?: number): {
|
|
172
|
+
fill: string;
|
|
173
|
+
outline: {
|
|
174
|
+
width: number;
|
|
175
|
+
color: string;
|
|
176
|
+
} | null;
|
|
177
|
+
};
|
|
166
178
|
export declare function normaliseHex(value: unknown): string | null;
|
|
167
179
|
export declare function mixHex(a: string, b: string, t: number): string;
|
|
168
180
|
export declare function luminanceOf(hex: string): number;
|
package/dist/theme.js
CHANGED
|
@@ -7,6 +7,20 @@ export const DESIGN_BUBBLES = ["filled", "outline", "minimal"];
|
|
|
7
7
|
export const LAUNCHER_SHAPES = ["circle", "squircle", "rounded-square", "square", "pill"];
|
|
8
8
|
export const ICON_STYLES = ["outline", "filled", "duotone", "sharp"];
|
|
9
9
|
export const DESIGN_CHROMES = ["none", "window"];
|
|
10
|
+
export const LOGO_PLATES = ["accent", "white"];
|
|
11
|
+
export const LOGO_PLATE_WHITE = "#FFFFFF";
|
|
12
|
+
export function normaliseLogoPlate(value) {
|
|
13
|
+
return value === "white" ? "white" : "accent";
|
|
14
|
+
}
|
|
15
|
+
export function logoBox(shape, size) {
|
|
16
|
+
const k = shape === "rounded-square" || shape === "square" ? 0.8 : 0.7;
|
|
17
|
+
return Math.max(1, Math.floor(size * k));
|
|
18
|
+
}
|
|
19
|
+
export function logoPlateLook(plate, colors, borderWidth = 1) {
|
|
20
|
+
return plate === "white"
|
|
21
|
+
? { fill: LOGO_PLATE_WHITE, outline: { width: Math.max(1, borderWidth), color: colors.border } }
|
|
22
|
+
: { fill: colors.accent, outline: null };
|
|
23
|
+
}
|
|
10
24
|
const HEX6 = /^#?([0-9a-f]{6})$/i;
|
|
11
25
|
const HEX3 = /^#?([0-9a-f]{3})$/i;
|
|
12
26
|
export function normaliseHex(value) {
|
|
@@ -79,6 +93,8 @@ export const LAUNCHER_ICONS = [
|
|
|
79
93
|
{ id: "buddy", label: "Buddy", stroke: true, path: "M12 3c4.97 0 8 3.03 8 8.5S16.97 21 12 21 4 17.97 4 11.5 7.03 3 12 3z M9.25 10.25v1.75 M14.75 10.25v1.75 M9.5 15.1c1.4 1.15 3.6 1.15 5 0" },
|
|
80
94
|
{ id: "chat-dots-filled", label: "Chat dots (filled)", stroke: false, path: "M12 2.25A9.75 9.75 0 0 0 3.392 16.582L2.328 19.775a1.5 1.5 0 0 0 1.897 1.897l3.192 -1.064A9.75 9.75 0 1 0 12 2.25ZM7.875 13.125a1.125 1.125 0 1 1 1.125 -1.125A1.125 1.125 0 0 1 7.875 13.125Zm4.125 0a1.125 1.125 0 1 1 1.125 -1.125A1.125 1.125 0 0 1 12 13.125Zm4.125 0a1.125 1.125 0 1 1 1.125 -1.125A1.125 1.125 0 0 1 16.125 13.125Z" },
|
|
81
95
|
{ id: "graphic-eq", label: "Voice equalizer (filled)", stroke: false, path: "M7 17v-10q0 -0.425 0.288 -0.713T8 6q0.425 0 0.713 0.288T9 7v10q0 0.425 -0.288 0.713T8 18q-0.425 0 -0.713 -0.288T7 17Zm4 4v-18q0 -0.425 0.288 -0.713T12 2q0.425 0 0.713 0.288T13 3v18q0 0.425 -0.288 0.713T12 22q-0.425 0 -0.713 -0.288T11 21ZM3 13v-2q0 -0.425 0.288 -0.713T4 10q0.425 0 0.713 0.288T5 11v2q0 0.425 -0.288 0.713T4 14q-0.425 0 -0.713 -0.288T3 13Zm12 4v-10q0 -0.425 0.288 -0.713T16 6q0.425 0 0.713 0.288T17 7v10q0 0.425 -0.288 0.713T16 18q-0.425 0 -0.713 -0.288T15 17Zm4 -4v-2q0 -0.425 0.288 -0.713T20 10q0.425 0 0.713 0.288T21 11v2q0 0.425 -0.288 0.713T20 14q-0.425 0 -0.713 -0.288T19 13Z" },
|
|
96
|
+
{ id: "grain", label: "Grain", stroke: true, path: "M7.34 5.5H16.66M4.69 8.75H19.31M4 12H20M4.69 15.25H19.31M7.34 18.5H16.66" },
|
|
97
|
+
{ id: "grain-voice", label: "Grain voice", stroke: true, path: "M8.5 5.5H15.5M5 8.75H19M7 12H17M3.5 15.25H20.5M8 18.5H16" },
|
|
82
98
|
];
|
|
83
99
|
export const SDK_ICONS = {
|
|
84
100
|
mic: { body: "M12 2.5a3.5 3.5 0 0 0-3.5 3.5v6a3.5 3.5 0 0 0 7 0V6A3.5 3.5 0 0 0 12 2.5z", lines: "M18.5 11.5a6.5 6.5 0 0 1-13 0M12 18v3.5" },
|
|
@@ -1878,10 +1894,735 @@ const P_HIGH_CONTRAST = {
|
|
|
1878
1894
|
}
|
|
1879
1895
|
}
|
|
1880
1896
|
};
|
|
1881
|
-
|
|
1897
|
+
const P_SIGNATURE = {
|
|
1898
|
+
id: "signature",
|
|
1899
|
+
name: "Signature",
|
|
1900
|
+
tagline: "Graine's own look — quiet greys, a slate-and-indigo voice, the grain.",
|
|
1901
|
+
family: "graine",
|
|
1902
|
+
inspiredBy: [
|
|
1903
|
+
"Graine's design system, after Sarvam's (sarvam.ai/brand) — neutral surfaces #FCFCFC / #E6E6E6, ink #1F1F1F, slate #2A2C33, indigo #4250D5, a 0 0 40px ambient shadow, ease cubic-bezier(0.2,0,0,1) 350ms; the grain motif is Graine's own",
|
|
1904
|
+
],
|
|
1905
|
+
font: {
|
|
1906
|
+
body: "DM Sans",
|
|
1907
|
+
heading: "Newsreader",
|
|
1908
|
+
scale: { caption: 12, body: 15, title: 19, display: 30 },
|
|
1909
|
+
weights: { body: 400, strong: 500, title: 400 },
|
|
1910
|
+
letterSpacing: { body: 0, title: -0.015 },
|
|
1911
|
+
lineHeight: { body: 1.5, title: 1.2 },
|
|
1912
|
+
},
|
|
1913
|
+
shape: { radius: { sm: 10, md: 16, lg: 24, xl: 32 }, borderWidth: 1, launcher: "circle" },
|
|
1914
|
+
spacing: { unit: 4, cardPadding: 16, gap: 10 },
|
|
1915
|
+
elevation: {
|
|
1916
|
+
card: { y: 0, blur: 40, spread: 0, opacity: 0.06, color: "#000000" },
|
|
1917
|
+
floating: { y: 12, blur: 40, spread: -8, opacity: 0.18, color: "#000000" },
|
|
1918
|
+
hardOffset: null,
|
|
1919
|
+
},
|
|
1920
|
+
motion: { durationMs: 350, easing: "emphasized", bounce: 0 },
|
|
1921
|
+
icons: { style: "outline", stroke: 1.75, launcher: "grain" },
|
|
1922
|
+
glass: null,
|
|
1923
|
+
bubbles: "filled",
|
|
1924
|
+
colors: {
|
|
1925
|
+
light: {
|
|
1926
|
+
background: "#FCFCFC", surface: "#FFFFFF", surfaceRaised: "#F5F5F5", text: "#1F1F1F", textMuted: "#666666",
|
|
1927
|
+
border: "#E6E6E6", accent: "#4250D5", onAccent: "#FFFFFF", success: "#496D21", warning: "#8F6418", danger: "#C43D2B",
|
|
1928
|
+
userBubble: "#2A2C33", userBubbleText: "#FFFFFF", agentBubble: "#F0F0F0", agentBubbleText: "#1F1F1F", scrim: "#000000",
|
|
1929
|
+
},
|
|
1930
|
+
dark: {
|
|
1931
|
+
background: "#141414", surface: "#1C1C1C", surfaceRaised: "#262626", text: "#F5F5F5", textMuted: "#A3A3A3",
|
|
1932
|
+
border: "#333333", accent: "#81A0E9", onAccent: "#11115B", success: "#90C85B", warning: "#FFB94A", danger: "#EBA18F",
|
|
1933
|
+
userBubble: "#E8EFFC", userBubbleText: "#11115B", agentBubble: "#262626", agentBubbleText: "#F5F5F5", scrim: "#000000",
|
|
1934
|
+
},
|
|
1935
|
+
},
|
|
1936
|
+
};
|
|
1937
|
+
const P_SWISS_GRID = {
|
|
1938
|
+
"id": "swiss-grid",
|
|
1939
|
+
"name": "Swiss Grid",
|
|
1940
|
+
"tagline": "Black type on white, square corners and hairline rules, with Swiss red for the customer's own messages.",
|
|
1941
|
+
"family": "modern",
|
|
1942
|
+
"inspiredBy": [
|
|
1943
|
+
"International Typographic Style (Swiss Style, 1950s)",
|
|
1944
|
+
"Josef Müller-Brockmann, Grid Systems in Graphic Design (1981)",
|
|
1945
|
+
"Swiss red, Pantone 485 C (#DA291C)"
|
|
1946
|
+
],
|
|
1947
|
+
"font": {
|
|
1948
|
+
"body": "Inter",
|
|
1949
|
+
"heading": "Inter",
|
|
1950
|
+
"scale": {
|
|
1951
|
+
"caption": 12,
|
|
1952
|
+
"body": 15,
|
|
1953
|
+
"title": 21,
|
|
1954
|
+
"display": 34
|
|
1955
|
+
},
|
|
1956
|
+
"weights": {
|
|
1957
|
+
"body": 400,
|
|
1958
|
+
"strong": 600,
|
|
1959
|
+
"title": 700
|
|
1960
|
+
},
|
|
1961
|
+
"letterSpacing": {
|
|
1962
|
+
"body": -0.006,
|
|
1963
|
+
"title": -0.022
|
|
1964
|
+
},
|
|
1965
|
+
"lineHeight": {
|
|
1966
|
+
"body": 1.42,
|
|
1967
|
+
"title": 1.12
|
|
1968
|
+
}
|
|
1969
|
+
},
|
|
1970
|
+
"shape": {
|
|
1971
|
+
"radius": {
|
|
1972
|
+
"sm": 0,
|
|
1973
|
+
"md": 0,
|
|
1974
|
+
"lg": 0,
|
|
1975
|
+
"xl": 0
|
|
1976
|
+
},
|
|
1977
|
+
"borderWidth": 1,
|
|
1978
|
+
"launcher": "square"
|
|
1979
|
+
},
|
|
1980
|
+
"spacing": {
|
|
1981
|
+
"unit": 4,
|
|
1982
|
+
"cardPadding": 16,
|
|
1983
|
+
"gap": 8
|
|
1984
|
+
},
|
|
1985
|
+
"elevation": {
|
|
1986
|
+
"card": null,
|
|
1987
|
+
"floating": null,
|
|
1988
|
+
"hardOffset": null
|
|
1989
|
+
},
|
|
1990
|
+
"motion": {
|
|
1991
|
+
"durationMs": 160,
|
|
1992
|
+
"easing": "standard",
|
|
1993
|
+
"bounce": 0
|
|
1994
|
+
},
|
|
1995
|
+
"icons": {
|
|
1996
|
+
"style": "sharp",
|
|
1997
|
+
"stroke": 1.75,
|
|
1998
|
+
"launcher": "audio-lines"
|
|
1999
|
+
},
|
|
2000
|
+
"glass": null,
|
|
2001
|
+
"bubbles": "filled",
|
|
2002
|
+
"chrome": "none",
|
|
2003
|
+
"colors": {
|
|
2004
|
+
"light": {
|
|
2005
|
+
"background": "#FFFFFF",
|
|
2006
|
+
"surface": "#FFFFFF",
|
|
2007
|
+
"surfaceRaised": "#F3F3F3",
|
|
2008
|
+
"text": "#111111",
|
|
2009
|
+
"textMuted": "#545454",
|
|
2010
|
+
"border": "#1A1A1A",
|
|
2011
|
+
"accent": "#111111",
|
|
2012
|
+
"onAccent": "#FFFFFF",
|
|
2013
|
+
"success": "#1C7C3A",
|
|
2014
|
+
"warning": "#9E5200",
|
|
2015
|
+
"danger": "#DA291C",
|
|
2016
|
+
"userBubble": "#DA291C",
|
|
2017
|
+
"userBubbleText": "#FFFFFF",
|
|
2018
|
+
"agentBubble": "#F3F3F3",
|
|
2019
|
+
"agentBubbleText": "#111111",
|
|
2020
|
+
"scrim": "#000000"
|
|
2021
|
+
},
|
|
2022
|
+
"dark": {
|
|
2023
|
+
"background": "#0A0A0A",
|
|
2024
|
+
"surface": "#111111",
|
|
2025
|
+
"surfaceRaised": "#1C1C1C",
|
|
2026
|
+
"text": "#F5F5F5",
|
|
2027
|
+
"textMuted": "#A8A8A8",
|
|
2028
|
+
"border": "#D4D4D4",
|
|
2029
|
+
"accent": "#F5F5F5",
|
|
2030
|
+
"onAccent": "#0A0A0A",
|
|
2031
|
+
"success": "#3DBA68",
|
|
2032
|
+
"warning": "#F0A23B",
|
|
2033
|
+
"danger": "#DA291C",
|
|
2034
|
+
"userBubble": "#DA291C",
|
|
2035
|
+
"userBubbleText": "#FFFFFF",
|
|
2036
|
+
"agentBubble": "#1C1C1C",
|
|
2037
|
+
"agentBubbleText": "#F5F5F5",
|
|
2038
|
+
"scrim": "#000000"
|
|
2039
|
+
}
|
|
2040
|
+
}
|
|
2041
|
+
};
|
|
2042
|
+
const P_BAUHAUS = {
|
|
2043
|
+
"id": "bauhaus",
|
|
2044
|
+
"name": "Bauhaus",
|
|
2045
|
+
"tagline": "Circles and squares in the three primaries, set in geometric type on warm paper.",
|
|
2046
|
+
"family": "expressive",
|
|
2047
|
+
"inspiredBy": [
|
|
2048
|
+
"Bauhaus school, Weimar and Dessau (1919–1933)",
|
|
2049
|
+
"Kandinsky's 1923 colour–form questionnaire (yellow triangle, red square, blue circle)",
|
|
2050
|
+
"Herbert Bayer's universal alphabet (1925), a geometric sans"
|
|
2051
|
+
],
|
|
2052
|
+
"font": {
|
|
2053
|
+
"body": "DM Sans",
|
|
2054
|
+
"heading": "Poppins",
|
|
2055
|
+
"scale": {
|
|
2056
|
+
"caption": 12,
|
|
2057
|
+
"body": 15,
|
|
2058
|
+
"title": 22,
|
|
2059
|
+
"display": 32
|
|
2060
|
+
},
|
|
2061
|
+
"weights": {
|
|
2062
|
+
"body": 400,
|
|
2063
|
+
"strong": 600,
|
|
2064
|
+
"title": 700
|
|
2065
|
+
},
|
|
2066
|
+
"letterSpacing": {
|
|
2067
|
+
"body": 0,
|
|
2068
|
+
"title": -0.01
|
|
2069
|
+
},
|
|
2070
|
+
"lineHeight": {
|
|
2071
|
+
"body": 1.45,
|
|
2072
|
+
"title": 1.15
|
|
2073
|
+
}
|
|
2074
|
+
},
|
|
2075
|
+
"shape": {
|
|
2076
|
+
"radius": {
|
|
2077
|
+
"sm": 0,
|
|
2078
|
+
"md": 24,
|
|
2079
|
+
"lg": 0,
|
|
2080
|
+
"xl": 30
|
|
2081
|
+
},
|
|
2082
|
+
"borderWidth": 0,
|
|
2083
|
+
"launcher": "circle"
|
|
2084
|
+
},
|
|
2085
|
+
"spacing": {
|
|
2086
|
+
"unit": 4,
|
|
2087
|
+
"cardPadding": 16,
|
|
2088
|
+
"gap": 10
|
|
2089
|
+
},
|
|
2090
|
+
"elevation": {
|
|
2091
|
+
"card": {
|
|
2092
|
+
"y": 2,
|
|
2093
|
+
"blur": 6,
|
|
2094
|
+
"spread": 0,
|
|
2095
|
+
"opacity": 0.1,
|
|
2096
|
+
"color": "#000000"
|
|
2097
|
+
},
|
|
2098
|
+
"floating": {
|
|
2099
|
+
"y": 10,
|
|
2100
|
+
"blur": 28,
|
|
2101
|
+
"spread": -6,
|
|
2102
|
+
"opacity": 0.2,
|
|
2103
|
+
"color": "#000000"
|
|
2104
|
+
},
|
|
2105
|
+
"hardOffset": null
|
|
2106
|
+
},
|
|
2107
|
+
"motion": {
|
|
2108
|
+
"durationMs": 200,
|
|
2109
|
+
"easing": "standard",
|
|
2110
|
+
"bounce": 0
|
|
2111
|
+
},
|
|
2112
|
+
"icons": {
|
|
2113
|
+
"style": "filled",
|
|
2114
|
+
"stroke": 2,
|
|
2115
|
+
"launcher": "graphic-eq"
|
|
2116
|
+
},
|
|
2117
|
+
"glass": null,
|
|
2118
|
+
"bubbles": "filled",
|
|
2119
|
+
"chrome": "none",
|
|
2120
|
+
"colors": {
|
|
2121
|
+
"light": {
|
|
2122
|
+
"background": "#F4EFE6",
|
|
2123
|
+
"surface": "#FBF8F2",
|
|
2124
|
+
"surfaceRaised": "#FFFFFF",
|
|
2125
|
+
"text": "#141414",
|
|
2126
|
+
"textMuted": "#55514A",
|
|
2127
|
+
"border": "#D8D0C1",
|
|
2128
|
+
"accent": "#21409A",
|
|
2129
|
+
"onAccent": "#FFFFFF",
|
|
2130
|
+
"success": "#2B7A3D",
|
|
2131
|
+
"warning": "#985A00",
|
|
2132
|
+
"danger": "#C62A1F",
|
|
2133
|
+
"userBubble": "#F2C230",
|
|
2134
|
+
"userBubbleText": "#141414",
|
|
2135
|
+
"agentBubble": "#ECE5D8",
|
|
2136
|
+
"agentBubbleText": "#141414",
|
|
2137
|
+
"scrim": "#000000"
|
|
2138
|
+
},
|
|
2139
|
+
"dark": {
|
|
2140
|
+
"background": "#121212",
|
|
2141
|
+
"surface": "#1B1B1B",
|
|
2142
|
+
"surfaceRaised": "#252525",
|
|
2143
|
+
"text": "#F4EFE6",
|
|
2144
|
+
"textMuted": "#B2ABA0",
|
|
2145
|
+
"border": "#3A3733",
|
|
2146
|
+
"accent": "#F2C230",
|
|
2147
|
+
"onAccent": "#141414",
|
|
2148
|
+
"success": "#5DBE6D",
|
|
2149
|
+
"warning": "#E8A33A",
|
|
2150
|
+
"danger": "#D0332A",
|
|
2151
|
+
"userBubble": "#2F4FB8",
|
|
2152
|
+
"userBubbleText": "#FFFFFF",
|
|
2153
|
+
"agentBubble": "#252525",
|
|
2154
|
+
"agentBubbleText": "#F4EFE6",
|
|
2155
|
+
"scrim": "#000000"
|
|
2156
|
+
}
|
|
2157
|
+
}
|
|
2158
|
+
};
|
|
2159
|
+
const P_PRECISION = {
|
|
2160
|
+
"id": "precision",
|
|
2161
|
+
"name": "Precision",
|
|
2162
|
+
"tagline": "Dark-first and dense: near-black neutrals, hairline borders and a single indigo accent.",
|
|
2163
|
+
"family": "modern",
|
|
2164
|
+
"inspiredBy": [
|
|
2165
|
+
"Linear, How we redesigned the Linear UI (2024)",
|
|
2166
|
+
"Inter and Inter Display by Rasmus Andersson"
|
|
2167
|
+
],
|
|
2168
|
+
"font": {
|
|
2169
|
+
"body": "Inter",
|
|
2170
|
+
"heading": "Inter",
|
|
2171
|
+
"scale": {
|
|
2172
|
+
"caption": 12,
|
|
2173
|
+
"body": 14,
|
|
2174
|
+
"title": 17,
|
|
2175
|
+
"display": 24
|
|
2176
|
+
},
|
|
2177
|
+
"weights": {
|
|
2178
|
+
"body": 400,
|
|
2179
|
+
"strong": 500,
|
|
2180
|
+
"title": 600
|
|
2181
|
+
},
|
|
2182
|
+
"letterSpacing": {
|
|
2183
|
+
"body": -0.005,
|
|
2184
|
+
"title": -0.014
|
|
2185
|
+
},
|
|
2186
|
+
"lineHeight": {
|
|
2187
|
+
"body": 1.5,
|
|
2188
|
+
"title": 1.3
|
|
2189
|
+
}
|
|
2190
|
+
},
|
|
2191
|
+
"shape": {
|
|
2192
|
+
"radius": {
|
|
2193
|
+
"sm": 4,
|
|
2194
|
+
"md": 6,
|
|
2195
|
+
"lg": 8,
|
|
2196
|
+
"xl": 12
|
|
2197
|
+
},
|
|
2198
|
+
"borderWidth": 1,
|
|
2199
|
+
"launcher": "rounded-square"
|
|
2200
|
+
},
|
|
2201
|
+
"spacing": {
|
|
2202
|
+
"unit": 4,
|
|
2203
|
+
"cardPadding": 12,
|
|
2204
|
+
"gap": 8
|
|
2205
|
+
},
|
|
2206
|
+
"elevation": {
|
|
2207
|
+
"card": {
|
|
2208
|
+
"y": 1,
|
|
2209
|
+
"blur": 2,
|
|
2210
|
+
"spread": 0,
|
|
2211
|
+
"opacity": 0.12,
|
|
2212
|
+
"color": "#000000"
|
|
2213
|
+
},
|
|
2214
|
+
"floating": {
|
|
2215
|
+
"y": 12,
|
|
2216
|
+
"blur": 32,
|
|
2217
|
+
"spread": -8,
|
|
2218
|
+
"opacity": 0.28,
|
|
2219
|
+
"color": "#000000"
|
|
2220
|
+
},
|
|
2221
|
+
"hardOffset": null
|
|
2222
|
+
},
|
|
2223
|
+
"motion": {
|
|
2224
|
+
"durationMs": 140,
|
|
2225
|
+
"easing": "standard",
|
|
2226
|
+
"bounce": 0
|
|
2227
|
+
},
|
|
2228
|
+
"icons": {
|
|
2229
|
+
"style": "outline",
|
|
2230
|
+
"stroke": 1.5,
|
|
2231
|
+
"launcher": "sparkles"
|
|
2232
|
+
},
|
|
2233
|
+
"glass": null,
|
|
2234
|
+
"bubbles": "filled",
|
|
2235
|
+
"chrome": "none",
|
|
2236
|
+
"colors": {
|
|
2237
|
+
"light": {
|
|
2238
|
+
"background": "#F7F8F8",
|
|
2239
|
+
"surface": "#FFFFFF",
|
|
2240
|
+
"surfaceRaised": "#FFFFFF",
|
|
2241
|
+
"text": "#0F1011",
|
|
2242
|
+
"textMuted": "#5C616A",
|
|
2243
|
+
"border": "#E2E4E7",
|
|
2244
|
+
"accent": "#5E6AD2",
|
|
2245
|
+
"onAccent": "#FFFFFF",
|
|
2246
|
+
"success": "#2E8A57",
|
|
2247
|
+
"warning": "#B0560B",
|
|
2248
|
+
"danger": "#CF3B41",
|
|
2249
|
+
"userBubble": "#5E6AD2",
|
|
2250
|
+
"userBubbleText": "#FFFFFF",
|
|
2251
|
+
"agentBubble": "#EFF0F2",
|
|
2252
|
+
"agentBubbleText": "#0F1011",
|
|
2253
|
+
"scrim": "#000000"
|
|
2254
|
+
},
|
|
2255
|
+
"dark": {
|
|
2256
|
+
"background": "#08090A",
|
|
2257
|
+
"surface": "#111214",
|
|
2258
|
+
"surfaceRaised": "#191A1D",
|
|
2259
|
+
"text": "#F7F8F8",
|
|
2260
|
+
"textMuted": "#8E939C",
|
|
2261
|
+
"border": "#26282C",
|
|
2262
|
+
"accent": "#5E6AD2",
|
|
2263
|
+
"onAccent": "#FFFFFF",
|
|
2264
|
+
"success": "#4CB782",
|
|
2265
|
+
"warning": "#F2994A",
|
|
2266
|
+
"danger": "#D5434A",
|
|
2267
|
+
"userBubble": "#5E6AD2",
|
|
2268
|
+
"userBubbleText": "#FFFFFF",
|
|
2269
|
+
"agentBubble": "#1A1B1E",
|
|
2270
|
+
"agentBubbleText": "#F7F8F8",
|
|
2271
|
+
"scrim": "#000000"
|
|
2272
|
+
}
|
|
2273
|
+
}
|
|
2274
|
+
};
|
|
2275
|
+
const P_JAPANDI_CALM = {
|
|
2276
|
+
"id": "japandi-calm",
|
|
2277
|
+
"name": "Japandi Calm",
|
|
2278
|
+
"tagline": "Warm stone, muted sage and soft serif titles, with slow, quiet motion that stays out of the way.",
|
|
2279
|
+
"family": "modern",
|
|
2280
|
+
"inspiredBy": [
|
|
2281
|
+
"Leonard Koren, Wabi-Sabi for Artists, Designers, Poets & Philosophers (1994)",
|
|
2282
|
+
"Mark Weiser & John Seely Brown, Designing Calm Technology (Xerox PARC, 1995)",
|
|
2283
|
+
"Amber Case, Principles of Calm Technology"
|
|
2284
|
+
],
|
|
2285
|
+
"font": {
|
|
2286
|
+
"body": "Noto Sans",
|
|
2287
|
+
"heading": "Noto Serif Devanagari",
|
|
2288
|
+
"scale": {
|
|
2289
|
+
"caption": 12,
|
|
2290
|
+
"body": 16,
|
|
2291
|
+
"title": 20,
|
|
2292
|
+
"display": 28
|
|
2293
|
+
},
|
|
2294
|
+
"weights": {
|
|
2295
|
+
"body": 400,
|
|
2296
|
+
"strong": 500,
|
|
2297
|
+
"title": 400
|
|
2298
|
+
},
|
|
2299
|
+
"letterSpacing": {
|
|
2300
|
+
"body": 0,
|
|
2301
|
+
"title": 0
|
|
2302
|
+
},
|
|
2303
|
+
"lineHeight": {
|
|
2304
|
+
"body": 1.6,
|
|
2305
|
+
"title": 1.35
|
|
2306
|
+
}
|
|
2307
|
+
},
|
|
2308
|
+
"shape": {
|
|
2309
|
+
"radius": {
|
|
2310
|
+
"sm": 6,
|
|
2311
|
+
"md": 10,
|
|
2312
|
+
"lg": 14,
|
|
2313
|
+
"xl": 22
|
|
2314
|
+
},
|
|
2315
|
+
"borderWidth": 1,
|
|
2316
|
+
"launcher": "circle"
|
|
2317
|
+
},
|
|
2318
|
+
"spacing": {
|
|
2319
|
+
"unit": 4,
|
|
2320
|
+
"cardPadding": 18,
|
|
2321
|
+
"gap": 14
|
|
2322
|
+
},
|
|
2323
|
+
"elevation": {
|
|
2324
|
+
"card": null,
|
|
2325
|
+
"floating": {
|
|
2326
|
+
"y": 8,
|
|
2327
|
+
"blur": 28,
|
|
2328
|
+
"spread": -8,
|
|
2329
|
+
"opacity": 0.12,
|
|
2330
|
+
"color": "#3B342B"
|
|
2331
|
+
},
|
|
2332
|
+
"hardOffset": null
|
|
2333
|
+
},
|
|
2334
|
+
"motion": {
|
|
2335
|
+
"durationMs": 360,
|
|
2336
|
+
"easing": "standard",
|
|
2337
|
+
"bounce": 0
|
|
2338
|
+
},
|
|
2339
|
+
"icons": {
|
|
2340
|
+
"style": "outline",
|
|
2341
|
+
"stroke": 1.25,
|
|
2342
|
+
"launcher": "waveform"
|
|
2343
|
+
},
|
|
2344
|
+
"glass": null,
|
|
2345
|
+
"bubbles": "minimal",
|
|
2346
|
+
"chrome": "none",
|
|
2347
|
+
"colors": {
|
|
2348
|
+
"light": {
|
|
2349
|
+
"background": "#F5F1EA",
|
|
2350
|
+
"surface": "#FBF8F3",
|
|
2351
|
+
"surfaceRaised": "#FFFFFF",
|
|
2352
|
+
"text": "#2B2824",
|
|
2353
|
+
"textMuted": "#625C54",
|
|
2354
|
+
"border": "#E2DBD0",
|
|
2355
|
+
"accent": "#56654D",
|
|
2356
|
+
"onAccent": "#FBF8F3",
|
|
2357
|
+
"success": "#4A7646",
|
|
2358
|
+
"warning": "#94661C",
|
|
2359
|
+
"danger": "#A5422D",
|
|
2360
|
+
"userBubble": "#E2E7DA",
|
|
2361
|
+
"userBubbleText": "#2B2824",
|
|
2362
|
+
"agentBubble": "#EEE9E0",
|
|
2363
|
+
"agentBubbleText": "#2B2824",
|
|
2364
|
+
"scrim": "#1A1814"
|
|
2365
|
+
},
|
|
2366
|
+
"dark": {
|
|
2367
|
+
"background": "#1A1917",
|
|
2368
|
+
"surface": "#22201D",
|
|
2369
|
+
"surfaceRaised": "#2B2925",
|
|
2370
|
+
"text": "#EEE8DF",
|
|
2371
|
+
"textMuted": "#ADA59A",
|
|
2372
|
+
"border": "#3A3631",
|
|
2373
|
+
"accent": "#A6B596",
|
|
2374
|
+
"onAccent": "#1A1917",
|
|
2375
|
+
"success": "#8DB487",
|
|
2376
|
+
"warning": "#D9A55B",
|
|
2377
|
+
"danger": "#B9503A",
|
|
2378
|
+
"userBubble": "#3A4234",
|
|
2379
|
+
"userBubbleText": "#EEE8DF",
|
|
2380
|
+
"agentBubble": "#2B2925",
|
|
2381
|
+
"agentBubbleText": "#EEE8DF",
|
|
2382
|
+
"scrim": "#000000"
|
|
2383
|
+
}
|
|
2384
|
+
}
|
|
2385
|
+
};
|
|
2386
|
+
const P_GAMIFIED = {
|
|
2387
|
+
"id": "gamified",
|
|
2388
|
+
"name": "Gamified",
|
|
2389
|
+
"tagline": "Chunky and bright, with thick outlines, pressable bottom edges and bold rounded type.",
|
|
2390
|
+
"family": "expressive",
|
|
2391
|
+
"inspiredBy": [
|
|
2392
|
+
"Duolingo brand guidelines, colour (Feather Green #58CC02, Eel, Swan, Polar)",
|
|
2393
|
+
"Yu-kai Chou, Octalysis gamification framework",
|
|
2394
|
+
"Nunito, a rounded sans by Vernon Adams"
|
|
2395
|
+
],
|
|
2396
|
+
"font": {
|
|
2397
|
+
"body": "Nunito",
|
|
2398
|
+
"heading": "Nunito",
|
|
2399
|
+
"scale": {
|
|
2400
|
+
"caption": 13,
|
|
2401
|
+
"body": 16,
|
|
2402
|
+
"title": 20,
|
|
2403
|
+
"display": 30
|
|
2404
|
+
},
|
|
2405
|
+
"weights": {
|
|
2406
|
+
"body": 500,
|
|
2407
|
+
"strong": 700,
|
|
2408
|
+
"title": 800
|
|
2409
|
+
},
|
|
2410
|
+
"letterSpacing": {
|
|
2411
|
+
"body": 0,
|
|
2412
|
+
"title": 0
|
|
2413
|
+
},
|
|
2414
|
+
"lineHeight": {
|
|
2415
|
+
"body": 1.45,
|
|
2416
|
+
"title": 1.2
|
|
2417
|
+
}
|
|
2418
|
+
},
|
|
2419
|
+
"shape": {
|
|
2420
|
+
"radius": {
|
|
2421
|
+
"sm": 8,
|
|
2422
|
+
"md": 14,
|
|
2423
|
+
"lg": 16,
|
|
2424
|
+
"xl": 20
|
|
2425
|
+
},
|
|
2426
|
+
"borderWidth": 2,
|
|
2427
|
+
"launcher": "rounded-square"
|
|
2428
|
+
},
|
|
2429
|
+
"spacing": {
|
|
2430
|
+
"unit": 4,
|
|
2431
|
+
"cardPadding": 16,
|
|
2432
|
+
"gap": 10
|
|
2433
|
+
},
|
|
2434
|
+
"elevation": {
|
|
2435
|
+
"card": {
|
|
2436
|
+
"y": 4,
|
|
2437
|
+
"blur": 0,
|
|
2438
|
+
"spread": 0,
|
|
2439
|
+
"opacity": 0.14,
|
|
2440
|
+
"color": "#000000"
|
|
2441
|
+
},
|
|
2442
|
+
"floating": {
|
|
2443
|
+
"y": 5,
|
|
2444
|
+
"blur": 0,
|
|
2445
|
+
"spread": 0,
|
|
2446
|
+
"opacity": 0.18,
|
|
2447
|
+
"color": "#000000"
|
|
2448
|
+
},
|
|
2449
|
+
"hardOffset": null
|
|
2450
|
+
},
|
|
2451
|
+
"motion": {
|
|
2452
|
+
"durationMs": 280,
|
|
2453
|
+
"easing": "spring",
|
|
2454
|
+
"bounce": 0.4
|
|
2455
|
+
},
|
|
2456
|
+
"icons": {
|
|
2457
|
+
"style": "filled",
|
|
2458
|
+
"stroke": 2.5,
|
|
2459
|
+
"launcher": "star"
|
|
2460
|
+
},
|
|
2461
|
+
"glass": null,
|
|
2462
|
+
"bubbles": "filled",
|
|
2463
|
+
"chrome": "none",
|
|
2464
|
+
"colors": {
|
|
2465
|
+
"light": {
|
|
2466
|
+
"background": "#FFFFFF",
|
|
2467
|
+
"surface": "#FFFFFF",
|
|
2468
|
+
"surfaceRaised": "#F7F7F7",
|
|
2469
|
+
"text": "#3C3C3C",
|
|
2470
|
+
"textMuted": "#646464",
|
|
2471
|
+
"border": "#E5E5E5",
|
|
2472
|
+
"accent": "#3A8400",
|
|
2473
|
+
"onAccent": "#FFFFFF",
|
|
2474
|
+
"success": "#3A8400",
|
|
2475
|
+
"warning": "#A86F00",
|
|
2476
|
+
"danger": "#D83434",
|
|
2477
|
+
"userBubble": "#58CC02",
|
|
2478
|
+
"userBubbleText": "#102B00",
|
|
2479
|
+
"agentBubble": "#F1F1F1",
|
|
2480
|
+
"agentBubbleText": "#3C3C3C",
|
|
2481
|
+
"scrim": "#000000"
|
|
2482
|
+
},
|
|
2483
|
+
"dark": {
|
|
2484
|
+
"background": "#131F24",
|
|
2485
|
+
"surface": "#1A2A31",
|
|
2486
|
+
"surfaceRaised": "#22343C",
|
|
2487
|
+
"text": "#F1F7FB",
|
|
2488
|
+
"textMuted": "#A5B5BD",
|
|
2489
|
+
"border": "#37464F",
|
|
2490
|
+
"accent": "#58CC02",
|
|
2491
|
+
"onAccent": "#102B00",
|
|
2492
|
+
"success": "#79D634",
|
|
2493
|
+
"warning": "#FFC800",
|
|
2494
|
+
"danger": "#D83434",
|
|
2495
|
+
"userBubble": "#58CC02",
|
|
2496
|
+
"userBubbleText": "#102B00",
|
|
2497
|
+
"agentBubble": "#22343C",
|
|
2498
|
+
"agentBubbleText": "#F1F7FB",
|
|
2499
|
+
"scrim": "#000000"
|
|
2500
|
+
}
|
|
2501
|
+
}
|
|
2502
|
+
};
|
|
2503
|
+
const P_FINTECH_CLEAR = {
|
|
2504
|
+
"id": "fintech-clear",
|
|
2505
|
+
"name": "Fintech Clear",
|
|
2506
|
+
"tagline": "Navy ink, a steady teal accent and soft layered shadows, for screens about money.",
|
|
2507
|
+
"family": "enterprise",
|
|
2508
|
+
"inspiredBy": [
|
|
2509
|
+
"Stripe, Designing accessible color systems (2019)",
|
|
2510
|
+
"Stripe Elements Appearance API: themes and variables",
|
|
2511
|
+
"IBM Plex Sans Devanagari (bilingual Latin + Devanagari)"
|
|
2512
|
+
],
|
|
2513
|
+
"font": {
|
|
2514
|
+
"body": "IBM Plex Sans Devanagari",
|
|
2515
|
+
"heading": "IBM Plex Sans Devanagari",
|
|
2516
|
+
"scale": {
|
|
2517
|
+
"caption": 12,
|
|
2518
|
+
"body": 15,
|
|
2519
|
+
"title": 18,
|
|
2520
|
+
"display": 26
|
|
2521
|
+
},
|
|
2522
|
+
"weights": {
|
|
2523
|
+
"body": 400,
|
|
2524
|
+
"strong": 500,
|
|
2525
|
+
"title": 600
|
|
2526
|
+
},
|
|
2527
|
+
"letterSpacing": {
|
|
2528
|
+
"body": 0,
|
|
2529
|
+
"title": 0
|
|
2530
|
+
},
|
|
2531
|
+
"lineHeight": {
|
|
2532
|
+
"body": 1.55,
|
|
2533
|
+
"title": 1.3
|
|
2534
|
+
}
|
|
2535
|
+
},
|
|
2536
|
+
"shape": {
|
|
2537
|
+
"radius": {
|
|
2538
|
+
"sm": 4,
|
|
2539
|
+
"md": 6,
|
|
2540
|
+
"lg": 10,
|
|
2541
|
+
"xl": 14
|
|
2542
|
+
},
|
|
2543
|
+
"borderWidth": 1,
|
|
2544
|
+
"launcher": "circle"
|
|
2545
|
+
},
|
|
2546
|
+
"spacing": {
|
|
2547
|
+
"unit": 4,
|
|
2548
|
+
"cardPadding": 16,
|
|
2549
|
+
"gap": 12
|
|
2550
|
+
},
|
|
2551
|
+
"elevation": {
|
|
2552
|
+
"card": {
|
|
2553
|
+
"y": 2,
|
|
2554
|
+
"blur": 5,
|
|
2555
|
+
"spread": -1,
|
|
2556
|
+
"opacity": 0.25,
|
|
2557
|
+
"color": "#32325D"
|
|
2558
|
+
},
|
|
2559
|
+
"floating": {
|
|
2560
|
+
"y": 13,
|
|
2561
|
+
"blur": 27,
|
|
2562
|
+
"spread": -5,
|
|
2563
|
+
"opacity": 0.25,
|
|
2564
|
+
"color": "#32325D"
|
|
2565
|
+
},
|
|
2566
|
+
"hardOffset": null
|
|
2567
|
+
},
|
|
2568
|
+
"motion": {
|
|
2569
|
+
"durationMs": 200,
|
|
2570
|
+
"easing": "standard",
|
|
2571
|
+
"bounce": 0
|
|
2572
|
+
},
|
|
2573
|
+
"icons": {
|
|
2574
|
+
"style": "outline",
|
|
2575
|
+
"stroke": 1.75,
|
|
2576
|
+
"launcher": "headset"
|
|
2577
|
+
},
|
|
2578
|
+
"glass": null,
|
|
2579
|
+
"bubbles": "filled",
|
|
2580
|
+
"chrome": "none",
|
|
2581
|
+
"colors": {
|
|
2582
|
+
"light": {
|
|
2583
|
+
"background": "#F6F9FC",
|
|
2584
|
+
"surface": "#FFFFFF",
|
|
2585
|
+
"surfaceRaised": "#FFFFFF",
|
|
2586
|
+
"text": "#0A2540",
|
|
2587
|
+
"textMuted": "#425466",
|
|
2588
|
+
"border": "#E3E8EE",
|
|
2589
|
+
"accent": "#0B7A75",
|
|
2590
|
+
"onAccent": "#FFFFFF",
|
|
2591
|
+
"success": "#16794A",
|
|
2592
|
+
"warning": "#A65400",
|
|
2593
|
+
"danger": "#DF1B41",
|
|
2594
|
+
"userBubble": "#0A2540",
|
|
2595
|
+
"userBubbleText": "#FFFFFF",
|
|
2596
|
+
"agentBubble": "#EEF2F6",
|
|
2597
|
+
"agentBubbleText": "#0A2540",
|
|
2598
|
+
"scrim": "#0A2540"
|
|
2599
|
+
},
|
|
2600
|
+
"dark": {
|
|
2601
|
+
"background": "#081624",
|
|
2602
|
+
"surface": "#0E2032",
|
|
2603
|
+
"surfaceRaised": "#15293D",
|
|
2604
|
+
"text": "#F3F6FA",
|
|
2605
|
+
"textMuted": "#A3B3C4",
|
|
2606
|
+
"border": "#22384F",
|
|
2607
|
+
"accent": "#3CC2B8",
|
|
2608
|
+
"onAccent": "#04211F",
|
|
2609
|
+
"success": "#3DCB86",
|
|
2610
|
+
"warning": "#F5A623",
|
|
2611
|
+
"danger": "#D7304A",
|
|
2612
|
+
"userBubble": "#24507A",
|
|
2613
|
+
"userBubbleText": "#FFFFFF",
|
|
2614
|
+
"agentBubble": "#15293D",
|
|
2615
|
+
"agentBubbleText": "#F3F6FA",
|
|
2616
|
+
"scrim": "#000000"
|
|
2617
|
+
}
|
|
2618
|
+
}
|
|
2619
|
+
};
|
|
2620
|
+
export const DESIGN_PRESETS = [GRAINE_CLASSIC, P_SIGNATURE, P_CHAT_NEUTRAL, P_MATERIAL_3, P_IOS_NATIVE, P_MINIMAL_MONO, P_FLUENT_2, P_CARBON, P_FINTECH_CLEAR, P_LIQUID_GLASS, P_SOFT_PLAYFUL, P_DESKTOP_BUDDY, P_SWISS_GRID, P_PRECISION, P_JAPANDI_CALM, P_NEO_BRUTALISM, P_EDITORIAL, P_LUXURY, P_RETRO_TERMINAL, P_WARM_EARTHY, P_BAUHAUS, P_GAMIFIED, P_HIGH_CONTRAST];
|
|
1882
2621
|
export const DEFAULT_DESIGN_PRESET = "graine-classic";
|
|
2622
|
+
const PRESET_ALIASES = { gateway: "signature" };
|
|
1883
2623
|
export function designPreset(id) {
|
|
1884
|
-
const
|
|
2624
|
+
const raw = String(id ?? "").trim().toLowerCase();
|
|
2625
|
+
const want = PRESET_ALIASES[raw] ?? raw;
|
|
1885
2626
|
return DESIGN_PRESETS.find((p) => p.id === want) ?? DESIGN_PRESETS[0];
|
|
1886
2627
|
}
|
|
1887
2628
|
function pick(v, allowed) {
|
package/package.json
CHANGED