@assemble-inc/chat-widget 0.1.2 → 0.1.4

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/server.cjs CHANGED
@@ -27,18 +27,40 @@ var import_anthropic = require("@ai-sdk/anthropic");
27
27
  var import_ai = require("ai");
28
28
  var import_streamableHttp = require("@modelcontextprotocol/sdk/client/streamableHttp.js");
29
29
  var import_client = require("@modelcontextprotocol/sdk/client/index.js");
30
- var SYSTEM_PROMPT = `You are a helpful assistant. You must ONLY answer questions using information returned by the tools available to you \u2014 never draw on your own training knowledge. If the tools do not return information relevant to the user's question, respond with: "I don't have that information in the knowledge base." Do not guess or speculate.`;
30
+ var DEFAULT_SYSTEM_PROMPT = `You are a helpful assistant. You must ONLY answer questions using information returned by the tools available to you \u2014 never draw on your own training knowledge. If the tools do not return relevant information, respond with: "I don't have that information available." Do not guess or speculate.`;
31
31
  function createChatHandler(config) {
32
32
  const anthropicClient = (0, import_anthropic.createAnthropic)({ apiKey: config.apiKey });
33
- const model = config.model ?? "claude-sonnet-4-5";
34
- const transportOptions = config.mcpBearerToken ? { requestInit: { headers: { Authorization: `Bearer ${config.mcpBearerToken}` } } } : {};
33
+ const defaultModel = "claude-sonnet-4-5";
35
34
  return async (req, res) => {
36
- const { messages: uiMessages } = req.body;
35
+ const {
36
+ messages: uiMessages,
37
+ systemPrompt: widgetSystemPrompt,
38
+ context,
39
+ mcpServerUrl: requestedUrl,
40
+ model: widgetModel,
41
+ temperature: widgetTemperature,
42
+ user
43
+ } = req.body;
37
44
  const messages = await (0, import_ai.convertToModelMessages)(uiMessages);
45
+ const allowlist = config.mcpCredentials ?? {};
46
+ const resolvedUrl = requestedUrl && (requestedUrl === config.mcpServerUrl || Object.prototype.hasOwnProperty.call(allowlist, requestedUrl)) ? requestedUrl : config.mcpServerUrl;
47
+ const resolvedToken = resolvedUrl === config.mcpServerUrl ? config.mcpBearerToken : allowlist[resolvedUrl];
48
+ const base = config.systemPrompt ?? widgetSystemPrompt ?? DEFAULT_SYSTEM_PROMPT;
49
+ const userContext = [
50
+ context,
51
+ user ? `User: ${JSON.stringify(user)}` : void 0
52
+ ].filter(Boolean).join("\n");
53
+ const system = userContext ? `${base}
54
+
55
+ ## Context
56
+ ${userContext}` : base;
57
+ const resolvedModel = config.model ?? widgetModel ?? defaultModel;
58
+ const temperature = config.temperature ?? widgetTemperature;
59
+ const transportOptions = resolvedToken ? { requestInit: { headers: { Authorization: `Bearer ${resolvedToken}` } } } : {};
38
60
  const mcpClient = new import_client.Client({ name: "@assemble-inc/chat-widget", version: "0.1.0" });
39
61
  try {
40
62
  await mcpClient.connect(
41
- new import_streamableHttp.StreamableHTTPClientTransport(new URL(config.mcpServerUrl), transportOptions)
63
+ new import_streamableHttp.StreamableHTTPClientTransport(new URL(resolvedUrl), transportOptions)
42
64
  );
43
65
  const { tools: mcpTools } = await mcpClient.listTools();
44
66
  const tools = Object.fromEntries(
@@ -58,17 +80,18 @@ function createChatHandler(config) {
58
80
  ])
59
81
  );
60
82
  const result = (0, import_ai.streamText)({
61
- model: anthropicClient(model),
62
- system: SYSTEM_PROMPT,
83
+ model: anthropicClient(resolvedModel),
84
+ system,
63
85
  messages,
64
86
  tools,
87
+ temperature,
65
88
  stopWhen: (0, import_ai.stepCountIs)(10)
66
89
  });
67
90
  result.pipeUIMessageStreamToResponse(res);
68
91
  } catch (err) {
69
92
  console.error("[chat handler] error:", err);
70
93
  if (!res.headersSent) {
71
- res.status(502).json({ error: "Failed to connect to knowledge base. Check MCP server credentials." });
94
+ res.status(502).json({ error: "Failed to connect. Check server credentials." });
72
95
  }
73
96
  } finally {
74
97
  await mcpClient.close();
package/dist/server.d.cts CHANGED
@@ -3,16 +3,40 @@ import { Request, Response } from 'express';
3
3
  interface ChatHandlerConfig {
4
4
  /** Anthropic API key */
5
5
  apiKey: string;
6
- /** URL of the coherence-engine-mcp-server Streamable HTTP endpoint */
6
+ /** Default MCP server URL used when no per-embed URL is provided */
7
7
  mcpServerUrl: string;
8
- /** Bearer token for authenticating with the MCP server */
8
+ /** Bearer token for the default mcpServerUrl */
9
9
  mcpBearerToken?: string;
10
- /** Claude model to use. Defaults to 'claude-sonnet-4-5'. */
10
+ /**
11
+ * Claude model to use. Defaults to 'claude-sonnet-4-5'.
12
+ * Takes precedence over any model sent by the widget embed.
13
+ */
11
14
  model?: string;
15
+ /**
16
+ * Operator-defined system prompt. Replaces the built-in default when set.
17
+ * Takes precedence over any systemPrompt sent by the widget embed.
18
+ */
19
+ systemPrompt?: string;
20
+ /**
21
+ * Model temperature (0–1). Overrides any temperature sent by the widget when set.
22
+ */
23
+ temperature?: number;
24
+ /**
25
+ * Allowlist of additional MCP server URLs that embeds may request,
26
+ * mapped to their bearer tokens. Credentials never leave the server.
27
+ * When omitted, only mcpServerUrl is accepted from embeds.
28
+ *
29
+ * @example
30
+ * { 'https://mcp.acme.com/hr': process.env.MCP_HR_TOKEN }
31
+ */
32
+ mcpCredentials?: Record<string, string | undefined>;
12
33
  }
13
34
  /**
14
- * Returns an Express-compatible request handler that streams chat responses
15
- * grounded exclusively in the coherence-engine-mcp-server.
35
+ * Returns an Express-compatible request handler that streams chat responses.
36
+ *
37
+ * The server-side config (systemPrompt, temperature, mcpCredentials) always takes
38
+ * precedence over values sent by the widget embed, allowing operators to lock
39
+ * behaviour while still enabling per-embed customisation where desired.
16
40
  *
17
41
  * @example
18
42
  * ```ts
package/dist/server.d.ts CHANGED
@@ -3,16 +3,40 @@ import { Request, Response } from 'express';
3
3
  interface ChatHandlerConfig {
4
4
  /** Anthropic API key */
5
5
  apiKey: string;
6
- /** URL of the coherence-engine-mcp-server Streamable HTTP endpoint */
6
+ /** Default MCP server URL used when no per-embed URL is provided */
7
7
  mcpServerUrl: string;
8
- /** Bearer token for authenticating with the MCP server */
8
+ /** Bearer token for the default mcpServerUrl */
9
9
  mcpBearerToken?: string;
10
- /** Claude model to use. Defaults to 'claude-sonnet-4-5'. */
10
+ /**
11
+ * Claude model to use. Defaults to 'claude-sonnet-4-5'.
12
+ * Takes precedence over any model sent by the widget embed.
13
+ */
11
14
  model?: string;
15
+ /**
16
+ * Operator-defined system prompt. Replaces the built-in default when set.
17
+ * Takes precedence over any systemPrompt sent by the widget embed.
18
+ */
19
+ systemPrompt?: string;
20
+ /**
21
+ * Model temperature (0–1). Overrides any temperature sent by the widget when set.
22
+ */
23
+ temperature?: number;
24
+ /**
25
+ * Allowlist of additional MCP server URLs that embeds may request,
26
+ * mapped to their bearer tokens. Credentials never leave the server.
27
+ * When omitted, only mcpServerUrl is accepted from embeds.
28
+ *
29
+ * @example
30
+ * { 'https://mcp.acme.com/hr': process.env.MCP_HR_TOKEN }
31
+ */
32
+ mcpCredentials?: Record<string, string | undefined>;
12
33
  }
13
34
  /**
14
- * Returns an Express-compatible request handler that streams chat responses
15
- * grounded exclusively in the coherence-engine-mcp-server.
35
+ * Returns an Express-compatible request handler that streams chat responses.
36
+ *
37
+ * The server-side config (systemPrompt, temperature, mcpCredentials) always takes
38
+ * precedence over values sent by the widget embed, allowing operators to lock
39
+ * behaviour while still enabling per-embed customisation where desired.
16
40
  *
17
41
  * @example
18
42
  * ```ts
package/dist/server.js CHANGED
@@ -3,18 +3,40 @@ import { createAnthropic } from "@ai-sdk/anthropic";
3
3
  import { streamText, tool, jsonSchema, stepCountIs, convertToModelMessages } from "ai";
4
4
  import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
5
5
  import { Client } from "@modelcontextprotocol/sdk/client/index.js";
6
- var SYSTEM_PROMPT = `You are a helpful assistant. You must ONLY answer questions using information returned by the tools available to you \u2014 never draw on your own training knowledge. If the tools do not return information relevant to the user's question, respond with: "I don't have that information in the knowledge base." Do not guess or speculate.`;
6
+ var DEFAULT_SYSTEM_PROMPT = `You are a helpful assistant. You must ONLY answer questions using information returned by the tools available to you \u2014 never draw on your own training knowledge. If the tools do not return relevant information, respond with: "I don't have that information available." Do not guess or speculate.`;
7
7
  function createChatHandler(config) {
8
8
  const anthropicClient = createAnthropic({ apiKey: config.apiKey });
9
- const model = config.model ?? "claude-sonnet-4-5";
10
- const transportOptions = config.mcpBearerToken ? { requestInit: { headers: { Authorization: `Bearer ${config.mcpBearerToken}` } } } : {};
9
+ const defaultModel = "claude-sonnet-4-5";
11
10
  return async (req, res) => {
12
- const { messages: uiMessages } = req.body;
11
+ const {
12
+ messages: uiMessages,
13
+ systemPrompt: widgetSystemPrompt,
14
+ context,
15
+ mcpServerUrl: requestedUrl,
16
+ model: widgetModel,
17
+ temperature: widgetTemperature,
18
+ user
19
+ } = req.body;
13
20
  const messages = await convertToModelMessages(uiMessages);
21
+ const allowlist = config.mcpCredentials ?? {};
22
+ const resolvedUrl = requestedUrl && (requestedUrl === config.mcpServerUrl || Object.prototype.hasOwnProperty.call(allowlist, requestedUrl)) ? requestedUrl : config.mcpServerUrl;
23
+ const resolvedToken = resolvedUrl === config.mcpServerUrl ? config.mcpBearerToken : allowlist[resolvedUrl];
24
+ const base = config.systemPrompt ?? widgetSystemPrompt ?? DEFAULT_SYSTEM_PROMPT;
25
+ const userContext = [
26
+ context,
27
+ user ? `User: ${JSON.stringify(user)}` : void 0
28
+ ].filter(Boolean).join("\n");
29
+ const system = userContext ? `${base}
30
+
31
+ ## Context
32
+ ${userContext}` : base;
33
+ const resolvedModel = config.model ?? widgetModel ?? defaultModel;
34
+ const temperature = config.temperature ?? widgetTemperature;
35
+ const transportOptions = resolvedToken ? { requestInit: { headers: { Authorization: `Bearer ${resolvedToken}` } } } : {};
14
36
  const mcpClient = new Client({ name: "@assemble-inc/chat-widget", version: "0.1.0" });
15
37
  try {
16
38
  await mcpClient.connect(
17
- new StreamableHTTPClientTransport(new URL(config.mcpServerUrl), transportOptions)
39
+ new StreamableHTTPClientTransport(new URL(resolvedUrl), transportOptions)
18
40
  );
19
41
  const { tools: mcpTools } = await mcpClient.listTools();
20
42
  const tools = Object.fromEntries(
@@ -34,17 +56,18 @@ function createChatHandler(config) {
34
56
  ])
35
57
  );
36
58
  const result = streamText({
37
- model: anthropicClient(model),
38
- system: SYSTEM_PROMPT,
59
+ model: anthropicClient(resolvedModel),
60
+ system,
39
61
  messages,
40
62
  tools,
63
+ temperature,
41
64
  stopWhen: stepCountIs(10)
42
65
  });
43
66
  result.pipeUIMessageStreamToResponse(res);
44
67
  } catch (err) {
45
68
  console.error("[chat handler] error:", err);
46
69
  if (!res.headersSent) {
47
- res.status(502).json({ error: "Failed to connect to knowledge base. Check MCP server credentials." });
70
+ res.status(502).json({ error: "Failed to connect. Check server credentials." });
48
71
  }
49
72
  } finally {
50
73
  await mcpClient.close();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@assemble-inc/chat-widget",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "Embeddable AI chat widget powered by Anthropic and MCP",
5
5
  "keywords": [
6
6
  "chat",
@@ -26,7 +26,7 @@
26
26
  "require": "./dist/server.cjs"
27
27
  },
28
28
  "./style.css": "./dist/index.css",
29
- "./embed": "./dist/embed.js"
29
+ "./embed": "./dist/embed.iife.js"
30
30
  },
31
31
  "files": [
32
32
  "dist"
@@ -74,6 +74,7 @@
74
74
  "tsx": "^4.19.2",
75
75
  "typescript": "5.8.3",
76
76
  "vite": "^6.3.4",
77
+ "vite-plugin-css-injected-by-js": "^3.5.2",
77
78
  "vite-plugin-dts": "^4.5.4"
78
79
  }
79
80
  }
package/dist/index.css DELETED
@@ -1 +0,0 @@
1
- /*! tailwindcss v4.2.4 | MIT License | https://tailwindcss.com */@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-scale-x:1;--tw-scale-y:1;--tw-scale-z:1;--tw-border-style:solid;--tw-gradient-position:initial;--tw-gradient-from:#0000;--tw-gradient-via:#0000;--tw-gradient-to:#0000;--tw-gradient-stops:initial;--tw-gradient-via-stops:initial;--tw-gradient-from-position:0%;--tw-gradient-via-position:50%;--tw-gradient-to-position:100%;--tw-leading:initial;--tw-font-weight:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-duration:initial;--tw-ease:initial}}}@layer theme{:root,:host{--font-sans:ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--font-mono:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--color-slate-400:oklch(70.4% .04 256.788);--color-gray-200:oklch(92.8% .006 264.531);--color-gray-300:oklch(87.2% .01 258.338);--color-gray-500:oklch(55.1% .027 264.364);--color-gray-600:oklch(44.6% .03 256.802);--color-neutral-300:oklch(87% 0 0);--color-white:#fff;--spacing:.25rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-lg:1.125rem;--text-lg--line-height:calc(1.75 / 1.125);--text-2xl:1.5rem;--text-2xl--line-height:calc(2 / 1.5);--font-weight-semibold:600;--font-weight-bold:700;--radius-lg:.5rem;--radius-xl:.75rem;--radius-3xl:1.5rem;--ease-in-out:cubic-bezier(.4, 0, .2, 1);--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono);--color-asm-white:#fff;--color-asm-purple:#2e2438;--color-asm-pink-400:#ffa2f6;--color-asm-blue:#a5d5d8;--color-asm-gray-500:#f1f1f1}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;-moz-tab-size:4;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab,red,red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){-webkit-appearance:button;-moz-appearance:button;appearance:button}::file-selector-button{-webkit-appearance:button;-moz-appearance:button;appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.sr-only{clip-path:inset(50%);white-space:nowrap;border-width:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.start{inset-inline-start:var(--spacing)}.top-1\/2{top:50%}.top-2{top:calc(var(--spacing) * 2)}.top-4{top:calc(var(--spacing) * 4)}.right-0{right:calc(var(--spacing) * 0)}.right-2{right:calc(var(--spacing) * 2)}.right-4{right:calc(var(--spacing) * 4)}.right-6{right:calc(var(--spacing) * 6)}.-bottom-4{bottom:calc(var(--spacing) * -4)}.bottom-0{bottom:calc(var(--spacing) * 0)}.bottom-1{bottom:calc(var(--spacing) * 1)}.bottom-2{bottom:calc(var(--spacing) * 2)}.bottom-6{bottom:calc(var(--spacing) * 6)}.bottom-20{bottom:calc(var(--spacing) * 20)}.left-0{left:calc(var(--spacing) * 0)}.left-1\/2{left:50%}.left-4{left:calc(var(--spacing) * 4)}.z-10{z-index:10}.z-40{z-index:40}.z-50{z-index:50}.z-\[1\]{z-index:1}.z-\[2\]{z-index:2}.z-\[3\]{z-index:3}.-mt-2{margin-top:calc(var(--spacing) * -2)}.mt-2{margin-top:calc(var(--spacing) * 2)}.-mr-\[2px\]{margin-right:-2px}.-mr-\[4\.5rem\]{margin-right:-4.5rem}.mr-0{margin-right:calc(var(--spacing) * 0)}.mr-1{margin-right:calc(var(--spacing) * 1)}.mb-24{margin-bottom:calc(var(--spacing) * 24)}.ml-0{margin-left:calc(var(--spacing) * 0)}.ml-1{margin-left:calc(var(--spacing) * 1)}.ml-2{margin-left:calc(var(--spacing) * 2)}.flex{display:flex}.grid{display:grid}.inline-block{display:inline-block}.h-0{height:calc(var(--spacing) * 0)}.h-3\/4{height:75%}.h-5\/6{height:83.3333%}.h-6{height:calc(var(--spacing) * 6)}.h-10{height:calc(var(--spacing) * 10)}.h-16{height:calc(var(--spacing) * 16)}.h-24{height:calc(var(--spacing) * 24)}.h-full{height:100%}.max-h-\[500px\]{max-height:500px}.max-h-\[800px\]{max-height:800px}.min-h-\[46px\]{min-height:46px}.w-0{width:calc(var(--spacing) * 0)}.w-6{width:calc(var(--spacing) * 6)}.w-10{width:calc(var(--spacing) * 10)}.w-16{width:calc(var(--spacing) * 16)}.w-56{width:calc(var(--spacing) * 56)}.w-80{width:calc(var(--spacing) * 80)}.w-full{width:100%}.shrink-0{flex-shrink:0}.origin-bottom-right{transform-origin:100% 100%}.-translate-x-1\/2{--tw-translate-x: -50% ;translate:var(--tw-translate-x) var(--tw-translate-y)}.-translate-x-\[2rem\]{--tw-translate-x: -2rem ;translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-x-0{--tw-translate-x:calc(var(--spacing) * 0);translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-y-0{--tw-translate-y:calc(var(--spacing) * 0);translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-y-28{--tw-translate-y:calc(var(--spacing) * 28);translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-y-full{--tw-translate-y:100%;translate:var(--tw-translate-x) var(--tw-translate-y)}.scale-0{--tw-scale-x:0%;--tw-scale-y:0%;--tw-scale-z:0%;scale:var(--tw-scale-x) var(--tw-scale-y)}.scale-100{--tw-scale-x:100%;--tw-scale-y:100%;--tw-scale-z:100%;scale:var(--tw-scale-x) var(--tw-scale-y)}.-rotate-45{rotate:-45deg}.transform-gpu{transform:translateZ(0) var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.cursor-pointer{cursor:pointer}.flex-col{flex-direction:column}.flex-row{flex-direction:row}.flex-row-reverse{flex-direction:row-reverse}.place-content-center{place-content:center}.items-center{align-items:center}.items-end{align-items:flex-end}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.justify-end{justify-content:flex-end}.gap-2{gap:calc(var(--spacing) * 2)}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.rounded-3xl{border-radius:var(--radius-3xl)}.rounded-\[43px\]{border-radius:43px}.rounded-full{border-radius:3.40282e38px}.rounded-lg{border-radius:var(--radius-lg)}.rounded-xl{border-radius:var(--radius-xl)}.rounded-t-\[43px\]{border-top-left-radius:43px;border-top-right-radius:43px}.rounded-tl-\[50\%\]{border-top-left-radius:50%}.rounded-tl-none{border-top-left-radius:0}.rounded-tr-\[50\%\]{border-top-right-radius:50%}.rounded-tr-none{border-top-right-radius:0}.rounded-br{border-bottom-right-radius:.25rem}.rounded-bl-\[50\%\]{border-bottom-left-radius:50%}.border{border-style:var(--tw-border-style);border-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-b-0{border-bottom-style:var(--tw-border-style);border-bottom-width:0}.border-gray-300{border-color:var(--color-gray-300)}.border-slate-400{border-color:var(--color-slate-400)}.bg-asm-gray-500{background-color:var(--color-asm-gray-500)}.bg-asm-pink-400{background-color:var(--color-asm-pink-400)}.bg-asm-purple{background-color:var(--color-asm-purple)}.bg-asm-white{background-color:var(--color-asm-white)}.bg-gray-200{background-color:var(--color-gray-200)}.bg-transparent{background-color:#0000}.bg-white{background-color:var(--color-white)}.bg-gradient-to-br{--tw-gradient-position:to bottom right in oklab;background-image:linear-gradient(var(--tw-gradient-stops))}.from-gray-500{--tw-gradient-from:var(--color-gray-500);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.to-neutral-300{--tw-gradient-to:var(--color-neutral-300);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.p-2{padding:calc(var(--spacing) * 2)}.p-3{padding:calc(var(--spacing) * 3)}.p-4{padding:calc(var(--spacing) * 4)}.px-2{padding-inline:calc(var(--spacing) * 2)}.px-4{padding-inline:calc(var(--spacing) * 4)}.py-1{padding-block:calc(var(--spacing) * 1)}.py-2{padding-block:calc(var(--spacing) * 2)}.py-6{padding-block:calc(var(--spacing) * 6)}.py-\[\.625rem\]{padding-block:.625rem}.pb-2{padding-bottom:calc(var(--spacing) * 2)}.text-center{text-align:center}.text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.leading-5{--tw-leading:calc(var(--spacing) * 5);line-height:calc(var(--spacing) * 5)}.leading-none{--tw-leading:1;line-height:1}.font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.whitespace-nowrap{white-space:nowrap}.text-asm-purple{color:var(--color-asm-purple)}.text-gray-600{color:var(--color-gray-600)}.text-white{color:var(--color-white)}.opacity-8{opacity:.08}.shadow{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-lg{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a), 0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-md{--tw-shadow:0 4px 6px -1px var(--tw-shadow-color,#0000001a), 0 2px 4px -2px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.transition-all{transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.duration-300{--tw-duration:.3s;transition-duration:.3s}.ease-in-out{--tw-ease:var(--ease-in-out);transition-timing-function:var(--ease-in-out)}.first\:pt-4:first-child{padding-top:calc(var(--spacing) * 4)}.last\:pb-4:last-child{padding-bottom:calc(var(--spacing) * 4)}@media(hover:hover){.hover\:bg-asm-blue\/30:hover{background-color:#a5d5d84d}@supports (color:color-mix(in lab,red,red)){.hover\:bg-asm-blue\/30:hover{background-color:color-mix(in oklab,var(--color-asm-blue) 30%,transparent)}}}.focus\:outline-asm-gray-500:focus{outline-color:var(--color-asm-gray-500)}@media(prefers-reduced-motion:reduce){.motion-reduce\:transition-none{transition-property:none}}}html,body{height:100%;font-family:Inter,sans-serif}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0}@property --tw-scale-x{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-y{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-z{syntax:"*";inherits:false;initial-value:1}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-gradient-position{syntax:"*";inherits:false}@property --tw-gradient-from{syntax:"<color>";inherits:false;initial-value:#0000}@property --tw-gradient-via{syntax:"<color>";inherits:false;initial-value:#0000}@property --tw-gradient-to{syntax:"<color>";inherits:false;initial-value:#0000}@property --tw-gradient-stops{syntax:"*";inherits:false}@property --tw-gradient-via-stops{syntax:"*";inherits:false}@property --tw-gradient-from-position{syntax:"<length-percentage>";inherits:false;initial-value:0%}@property --tw-gradient-via-position{syntax:"<length-percentage>";inherits:false;initial-value:50%}@property --tw-gradient-to-position{syntax:"<length-percentage>";inherits:false;initial-value:100%}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-duration{syntax:"*";inherits:false}@property --tw-ease{syntax:"*";inherits:false}