Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions .changeset/fullpage-logo-icon-and-logo-url.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
'@smooai/chat-widget': minor
---

Full-page header: square Smooth icon default + customizable `logoUrl`.

**New default icon.** The full-page header avatar previously rendered the full "smooth" wordmark (a wide 550×135 SVG) crammed into the square tile, so it overflowed and looked broken — and it stamped Smoo branding onto customers' pages. It now renders the square Smooth icon (the stylized `th` glyph, `assets/smooth-icon.svg`, 150×150) which sits cleanly `contain`ed and centered in the tile.

**New `logoUrl` config key.** Host pages can now brand the full-page header with their own logo:

```js
mountFullPageChat({ endpoint: 'wss://ai.smoo.ai/ws', agentId: '…', logoUrl: 'https://cdn.acme.com/logo.svg' });
// or declaratively:
// <smooth-agent-chat mode="fullpage" logo-url="https://cdn.acme.com/logo.svg" …></smooth-agent-chat>
```

When set, the header renders `<img class="logo-img">` sized to `contain` within the tile; otherwise it falls back to the Smooth icon. **Security:** `logoUrl` is validated to absolute `http(s)` only (via the existing `safeHttpUrl` guard) — `javascript:`/`data:`/relative URLs are dropped — and escaped into the `src` attribute, so a hostile config can't inject script.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,7 @@ Declarative attributes mirror the programmatic [`ChatWidgetConfig`](./src/config
| `endpoint` | `endpoint` | **Required.** smooth-operator WebSocket URL. |
| `agent-id` | `agentId` | **Required.** UUID of the agent. |
| `agent-name` | `agentName` | Header label + monogram. Default `Assistant`. |
| `logo-url` | `logoUrl` | Brand logo in the full-page header tile (`http(s)` only). Falls back to the Smooth icon. |
| `placeholder` | `placeholder` | Composer placeholder. |
| `greeting` | `greeting` | Shown before the first message. |
| `mode` | `mode` | `popover` (default) or `fullpage`. |
Expand Down
15 changes: 15 additions & 0 deletions assets/smooth-icon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 9 additions & 0 deletions src/config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,15 @@ describe('resolveConfig', () => {
const r = resolveConfig({ ...base, examplePrompts: ['a', ' ', 'b', 'c', 'd', 'e', 'f'] });
expect(r.examplePrompts).toEqual(['a', 'b', 'c', 'd', 'e']);
});

it('keeps a safe http(s) logoUrl and drops dangerous/relative ones (XSS guard)', () => {
expect(resolveConfig({ ...base, logoUrl: 'https://cdn.example.com/l.png' }).logoUrl).toBe('https://cdn.example.com/l.png');
// eslint-disable-next-line no-script-url
expect(resolveConfig({ ...base, logoUrl: 'javascript:alert(1)' }).logoUrl).toBeUndefined();
expect(resolveConfig({ ...base, logoUrl: 'data:text/html,<script>' }).logoUrl).toBeUndefined();
expect(resolveConfig({ ...base, logoUrl: '/relative/logo.png' }).logoUrl).toBeUndefined();
expect(resolveConfig(base).logoUrl).toBeUndefined();
});
});

describe('needsUserInfo', () => {
Expand Down
16 changes: 15 additions & 1 deletion src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
* `<smooth-agent-chat>` element) or programmatically (passing this object to
* {@link mountChatWidget} / `element.configure(...)`).
*/
import { safeHttpUrl } from './markdown.js';

export interface ChatWidgetTheme {
/** Foreground text color for the widget chrome. */
text?: string;
Expand Down Expand Up @@ -65,6 +67,13 @@ export interface ChatWidgetConfig {
agentId: string;
/** Display name for the agent (header label). Defaults to "Assistant". */
agentName?: string;
/**
* Brand logo shown in the full-page header avatar tile; falls back to the
* Smooth icon. SECURITY: only absolute `http(s)` URLs are honored — any other
* scheme (`javascript:`/`data:`/…) is ignored, so a hostile config can't
* inject script.
*/
logoUrl?: string;
/** Optional display name for the user participant. */
userName?: string;
/** Optional email address for the user participant. */
Expand Down Expand Up @@ -127,12 +136,14 @@ export interface ChatWidgetConfig {
/** The fully-resolved theme (canonical keys only — aliases are folded in). */
export type ResolvedTheme = Required<Omit<ChatWidgetTheme, 'chatBubbleInbound' | 'chatBubbleInboundText' | 'chatBubbleOutbound' | 'chatBubbleOutboundText'>>;

export type ResolvedConfig = Required<Omit<ChatWidgetConfig, 'theme' | 'userName' | 'userEmail' | 'userPhone' | 'authContext'>> & {
export type ResolvedConfig = Required<Omit<ChatWidgetConfig, 'theme' | 'userName' | 'userEmail' | 'userPhone' | 'authContext' | 'logoUrl'>> & {
theme: ResolvedTheme;
userName?: string;
userEmail?: string;
userPhone?: string;
authContext?: { userId: string; signature: string; timestamp: number };
/** Sanitized brand logo URL (`http(s)` only) or `undefined` — see {@link ChatWidgetConfig.logoUrl}. */
logoUrl?: string;
};

/** Resolve a partial config against the built-in defaults. */
Expand All @@ -150,6 +161,9 @@ export function resolveConfig(config: ChatWidgetConfig): ResolvedConfig {
mode: config.mode ?? 'popover',
agentId: config.agentId,
agentName: config.agentName ?? 'Assistant',
// Only absolute http(s) URLs survive — anything else (javascript:/data:/
// relative) is dropped so the header can never render a hostile logo src.
logoUrl: safeHttpUrl(config.logoUrl) ?? undefined,
userName: config.userName,
userEmail: config.userEmail,
userPhone: config.userPhone,
Expand Down
29 changes: 29 additions & 0 deletions src/element.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,35 @@ describe('<smooth-agent-chat> render', () => {
expect(sr.querySelector('.close')).toBeNull();
});

it('full-page header defaults to the square Smooth icon (not the wide wordmark)', () => {
const sr = mount({ endpoint: 'wss://e/ws', 'agent-id': 'a1', mode: 'fullpage' }).shadowRoot!;
const svg = sr.querySelector('.header .avatar .logo-wrap svg');
expect(svg).not.toBeNull();
// The square icon has a 150×150 viewBox; the retired wordmark was 550×135.
expect(svg?.getAttribute('viewBox')).toBe('0 0 150 150');
// No brand <img> when no logoUrl is set.
expect(sr.querySelector('.header .logo-img')).toBeNull();
});

it('full-page header renders a brand <img> when a safe logoUrl is set', () => {
const sr = mountCfg({ mode: 'fullpage', logoUrl: 'https://cdn.example.com/logo.png' }).shadowRoot!;
const img = sr.querySelector('.header .avatar .logo-img') as HTMLImageElement | null;
expect(img).not.toBeNull();
expect(img?.getAttribute('src')).toBe('https://cdn.example.com/logo.png');
// The default icon is not rendered when a logo image takes over.
expect(sr.querySelector('.header .avatar .logo-wrap svg')).toBeNull();
});

it('ignores a javascript:/data: logoUrl (XSS guard) and falls back to the icon', () => {
// eslint-disable-next-line no-script-url
const sr = mountCfg({ mode: 'fullpage', logoUrl: 'javascript:alert(1)' }).shadowRoot!;
expect(sr.querySelector('.header .logo-img')).toBeNull();
expect(sr.querySelector('.header .avatar .logo-wrap svg')?.getAttribute('viewBox')).toBe('0 0 150 150');

const sr2 = mountCfg({ mode: 'fullpage', logoUrl: 'data:text/html,<script>alert(1)</script>' }).shadowRoot!;
expect(sr2.querySelector('.header .logo-img')).toBeNull();
});

// Configure non-attribute options (examplePrompts / require*) before mount.
function mountCfg(cfg: Record<string, unknown>): HTMLElement {
defineChatWidget();
Expand Down
17 changes: 12 additions & 5 deletions src/element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import { AsYouType, isValidPhoneNumber, parsePhoneNumber } from 'libphonenumber-
import type { ChatWidgetConfig, ChatWidgetMode, ChatWidgetTheme } from './config.js';
import { needsUserInfo, resolveConfig } from './config.js';
import { type ChatMessage, type Citation, type ConnectionStatus, ConversationController, type IdentityRestore, type Interrupt } from './conversation.js';
import { SMOOTH_LOGO_SVG } from './logo.js';
import { SMOOTH_ICON_SVG } from './logo.js';
import { cleanCitationSnippet, escapeHtml, renderMarkdown, safeHttpUrl } from './markdown.js';
import { buildStyles } from './styles.js';

Expand Down Expand Up @@ -49,7 +49,7 @@ function phoneToE164(value: string): string | null {
}
}

const OBSERVED = ['endpoint', 'agent-id', 'agent-name', 'placeholder', 'greeting', 'start-open', 'mode'] as const;
const OBSERVED = ['endpoint', 'agent-id', 'agent-name', 'logo-url', 'placeholder', 'greeting', 'start-open', 'mode'] as const;

/**
* Inline SVG icons (static, trusted strings — never interpolated with user data).
Expand Down Expand Up @@ -195,6 +195,7 @@ export class SmoothAgentChatElement extends HTMLElement {
mode,
agentId,
agentName: this.overrides.agentName ?? this.getAttribute('agent-name') ?? undefined,
logoUrl: this.overrides.logoUrl ?? this.getAttribute('logo-url') ?? undefined,
userName: this.overrides.userName,
userEmail: this.overrides.userEmail,
userPhone: this.overrides.userPhone,
Expand Down Expand Up @@ -264,13 +265,19 @@ export class SmoothAgentChatElement extends HTMLElement {
const style = document.createElement('style');
style.textContent = buildStyles(resolved.theme, resolved.mode);

// Header: in full-page mode lead with the Smooth logo in the avatar tile
// Header: in full-page mode lead with the brand logo in the avatar tile
// and a subtle "powered by" tag; in popover mode show a brand-colored
// monogram avatar + a compact close (collapse) button.
// monogram avatar + a compact close (collapse) button. The logo defaults
// to the square Smooth icon, but a host page can override it with
// `logoUrl` (already sanitized to http(s)-only by resolveConfig; escaped
// here so it can't break out of the src attribute).
const monogram = escapeHtml((resolved.agentName.trim().charAt(0) || 'A').toUpperCase());
const headerLogo = resolved.logoUrl
? `<img src="${escapeHtml(resolved.logoUrl)}" alt="" class="logo-img" />`
: SMOOTH_ICON_SVG;
const header = fullpage
? `<div class="header">
<div class="avatar"><span class="logo-wrap">${SMOOTH_LOGO_SVG}</span></div>
<div class="avatar"><span class="logo-wrap">${headerLogo}</span></div>
<div class="meta">
<span class="title">${escapeHtml(resolved.agentName)}</span>
<span class="status"><span class="dot off"></span><span class="status-text"></span></span>
Expand Down
13 changes: 9 additions & 4 deletions src/logo.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
/**
* The Smooth logo, inlined as an SVG string so the full-page header can render
* it without a separate network fetch (the IIFE bundle is self-contained).
* The Smooth logo + icon, inlined as SVG strings so the full-page header can
* render them without a separate network fetch (the IIFE bundle is
* self-contained).
*
* GENERATED from `assets/smooth-logo.svg` — do not edit by hand. Regenerate with:
* node -e ... (see the commit that added this file)
* GENERATED from `assets/smooth-logo.svg` / `assets/smooth-icon.svg` — do not
* edit by hand. Regenerate with:
* node -e 'const fs=require("fs");process.stdout.write(JSON.stringify(fs.readFileSync("assets/smooth-icon.svg","utf8")))'
*/
/* eslint-disable */
export const SMOOTH_LOGO_SVG = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<svg id=\"Layer_1\" data-name=\"Layer 1\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" viewBox=\"0 0 550 135\">\n <defs>\n <style>\n .cls-1 {\n fill: url(#linear-gradient-3);\n }\n\n .cls-2 {\n fill: url(#linear-gradient-2);\n }\n\n .cls-3 {\n fill: url(#linear-gradient);\n fill-rule: evenodd;\n }\n </style>\n <linearGradient id=\"linear-gradient\" x1=\"115.59\" y1=\"112.81\" x2=\"25.08\" y2=\"22.3\" gradientUnits=\"userSpaceOnUse\">\n <stop offset=\".3\" stop-color=\"#f49f0a\"/>\n <stop offset=\".79\" stop-color=\"#fb7a4d\"/>\n <stop offset=\"1\" stop-color=\"#ff6b6c\"/>\n </linearGradient>\n <linearGradient id=\"linear-gradient-2\" x1=\"360.91\" y1=\"152.01\" x2=\"202.32\" y2=\"-6.59\" xlink:href=\"#linear-gradient\"/>\n <linearGradient id=\"linear-gradient-3\" x1=\"443.91\" y1=\"30.15\" x2=\"531.36\" y2=\"117.59\" gradientUnits=\"userSpaceOnUse\">\n <stop offset=\".43\" stop-color=\"#00a6a6\"/>\n <stop offset=\"1\" stop-color=\"#1238dd\"/>\n </linearGradient>\n </defs>\n <path class=\"cls-3\" d=\"M48.28,14.96c-12.39,5.21-22.54,14.64-28.65,26.61-6.12,11.97-7.8,25.72-4.77,38.81,3.04,13.09,10.6,24.69,21.36,32.75,10.76,8.06,24.02,12.05,37.44,11.28,13.42-.77,26.13-6.26,35.9-15.5,9.76-9.24,15.95-21.63,17.46-34.99,1.51-13.36-1.74-26.82-9.19-38.01-1.07-1.61-.64-3.78.97-4.85,1.61-1.07,3.78-.64,4.85.97,8.36,12.56,12.02,27.68,10.32,42.67-1.7,15-8.64,28.91-19.61,39.28-10.96,10.37-25.24,16.54-40.31,17.4-15.07.87-29.96-3.62-42.04-12.66-12.08-9.05-20.58-22.07-23.99-36.77-3.41-14.7-1.51-30.14,5.35-43.58,6.87-13.44,18.26-24.02,32.17-29.87,13.91-5.85,29.44-6.6,43.85-2.11,1.85.57,2.88,2.54,2.3,4.38-.57,1.85-2.54,2.88-4.38,2.3-12.83-4-26.67-3.33-39.06,1.88ZM111.39,19.75c0,2.07-1.68,3.75-3.75,3.75s-3.75-1.68-3.75-3.75,1.68-3.75,3.75-3.75,3.75,1.68,3.75,3.75ZM64.64,59.93c0,1.91,2.39,2.56,7.69,3.88,3.89.97,6.6,2.18,8.15,3.63,1.53,1.45,2.29,3.53,2.29,6.25,0,3.57-1.03,6.26-3.11,8.08-2.07,1.82-5.09,2.73-9.09,2.73h-9.6c-1.97,0-3.57-1.6-3.59-3.57-.01-1.99,1.6-3.61,3.59-3.61h9.41c3.15-.12,4.79-.95,4.91-2.47,0-1.3-1.03-2.21-3.07-2.73-6.91-1.72-11.11-3.44-12.6-5.15-1.48-1.71-2.23-3.77-2.23-6.19,0-6.59,3.2-9.85,9.59-9.8h10.77c1.99,0,3.6,1.61,3.6,3.59s-1.61,3.59-3.6,3.59h-9.69c-1.83,0-3.43.06-3.43,1.77Z\"/>\n <path class=\"cls-2\" d=\"M205.52,48.44h-8.86c-.44-3.75-2.23-6.65-5.38-8.72-3.16-2.07-7.03-3.1-11.6-3.1h0c-3.35,0-6.27.54-8.78,1.62-2.49,1.09-4.44,2.59-5.84,4.48-1.39,1.89-2.08,4.05-2.08,6.46h0c0,2.01.49,3.75,1.46,5.2.97,1.44,2.22,2.63,3.74,3.58,1.53.95,3.13,1.72,4.8,2.32,1.68.6,3.22,1.09,4.62,1.46h0l7.68,2.06c1.97.52,4.17,1.23,6.6,2.14,2.43.92,4.75,2.16,6.98,3.72,2.23,1.56,4.07,3.56,5.52,6,1.45,2.44,2.18,5.43,2.18,8.98h0c0,4.08-1.07,7.77-3.2,11.08-2.12,3.29-5.22,5.91-9.3,7.86-4.08,1.95-9.02,2.92-14.82,2.92h0c-5.43,0-10.11-.87-14.06-2.62-3.95-1.75-7.05-4.19-9.3-7.32-2.25-3.12-3.53-6.75-3.84-10.88h9.46c.25,2.85,1.22,5.21,2.9,7.06,1.69,1.87,3.83,3.25,6.42,4.14,2.6.89,5.41,1.34,8.42,1.34h0c3.49,0,6.63-.57,9.4-1.72,2.79-1.13,4.99-2.73,6.62-4.8,1.63-2.05,2.44-4.46,2.44-7.22h0c0-2.51-.7-4.55-2.1-6.12-1.41-1.57-3.26-2.85-5.54-3.84-2.29-.99-4.77-1.85-7.44-2.58h0l-9.3-2.66c-5.91-1.71-10.59-4.13-14.04-7.28-3.44-3.16-5.16-7.29-5.16-12.38h0c0-4.23,1.15-7.93,3.46-11.1,2.29-3.16,5.39-5.62,9.3-7.38,3.91-1.76,8.27-2.64,13.08-2.64h0c4.88,0,9.21.87,13,2.6,3.8,1.73,6.81,4.11,9.04,7.12,2.23,3,3.4,6.41,3.52,10.22h0ZM229.16,105.18h-8.72v-56.74h8.42v8.86h.74c1.19-3.03,3.1-5.38,5.74-7.06,2.63-1.69,5.79-2.54,9.48-2.54h0c3.75,0,6.87.85,9.36,2.54,2.51,1.68,4.46,4.03,5.86,7.06h.58c1.45-2.92,3.63-5.25,6.54-7,2.91-1.73,6.39-2.6,10.46-2.6h0c5.07,0,9.21,1.58,12.44,4.74,3.23,3.17,4.84,8.09,4.84,14.76h0v37.98h-8.72v-37.98c0-4.19-1.14-7.18-3.42-8.98-2.29-1.79-4.99-2.68-8.1-2.68h0c-3.99,0-7.07,1.2-9.26,3.6-2.2,2.4-3.3,5.43-3.3,9.1h0v36.94h-8.86v-38.86c0-3.23-1.05-5.83-3.14-7.82-2.09-1.97-4.79-2.96-8.08-2.96h0c-2.27,0-4.38.6-6.34,1.8-1.96,1.21-3.53,2.88-4.72,5-1.2,2.13-1.8,4.59-1.8,7.38h0v35.46ZM333.9,106.36h0c-5.12,0-9.61-1.22-13.46-3.66-3.85-2.44-6.86-5.85-9.02-10.24-2.15-4.37-3.22-9.49-3.22-15.36h0c0-5.91,1.07-11.07,3.22-15.48,2.16-4.4,5.17-7.82,9.02-10.26,3.85-2.44,8.34-3.66,13.46-3.66h0c5.12,0,9.61,1.22,13.46,3.66,3.85,2.44,6.86,5.86,9.02,10.26,2.15,4.41,3.22,9.57,3.22,15.48h0c0,5.87-1.07,10.99-3.22,15.36-2.16,4.39-5.17,7.8-9.02,10.24-3.85,2.44-8.34,3.66-13.46,3.66ZM333.9,98.52h0c3.89,0,7.09-.99,9.6-2.98,2.52-2,4.38-4.63,5.58-7.88,1.21-3.25,1.82-6.77,1.82-10.56h0c0-3.79-.61-7.32-1.82-10.6-1.2-3.27-3.06-5.91-5.58-7.94-2.51-2.01-5.71-3.02-9.6-3.02h0c-3.89,0-7.09,1.01-9.6,3.02-2.51,2.03-4.37,4.67-5.58,7.94-1.2,3.28-1.8,6.81-1.8,10.6h0c0,3.79.6,7.31,1.8,10.56,1.21,3.25,3.07,5.88,5.58,7.88,2.51,1.99,5.71,2.98,9.6,2.98ZM395.94,106.36h0c-5.12,0-9.61-1.22-13.46-3.66-3.85-2.44-6.85-5.85-9-10.24-2.16-4.37-3.24-9.49-3.24-15.36h0c0-5.91,1.08-11.07,3.24-15.48,2.15-4.4,5.15-7.82,9-10.26,3.85-2.44,8.34-3.66,13.46-3.66h0c5.12,0,9.61,1.22,13.46,3.66,3.85,2.44,6.86,5.86,9.02,10.26,2.16,4.41,3.24,9.57,3.24,15.48h0c0,5.87-1.08,10.99-3.24,15.36-2.16,4.39-5.17,7.8-9.02,10.24-3.85,2.44-8.34,3.66-13.46,3.66ZM395.94,98.52h0c3.89,0,7.09-.99,9.6-2.98,2.52-2,4.38-4.63,5.58-7.88,1.21-3.25,1.82-6.77,1.82-10.56h0c0-3.79-.61-7.32-1.82-10.6-1.2-3.27-3.06-5.91-5.58-7.94-2.51-2.01-5.71-3.02-9.6-3.02h0c-3.88,0-7.08,1.01-9.6,3.02-2.51,2.03-4.37,4.67-5.58,7.94-1.2,3.28-1.8,6.81-1.8,10.6h0c0,3.79.6,7.31,1.8,10.56,1.21,3.25,3.07,5.88,5.58,7.88,2.52,1.99,5.72,2.98,9.6,2.98Z\"/>\n <path class=\"cls-1\" d=\"M467.88,48.02v13.28h-35.79v-13.28h35.79ZM439.68,34.38h17.89v53.42c0,1.5.36,2.62,1.08,3.36.72.74,1.88,1.1,3.49,1.1.62,0,1.48-.07,2.59-.21,1.11-.14,1.91-.27,2.38-.41l2.31,13.02c-2.02.58-3.97.97-5.84,1.18-1.88.21-3.66.31-5.33.31-6.08,0-10.7-1.43-13.84-4.28-3.15-2.85-4.72-7.01-4.72-12.48v-55.01ZM506.59,72.63v32.71h-17.89V28.95h17.53v33.53h-1.13c1.4-4.55,3.6-8.21,6.59-11,2.99-2.79,7.01-4.18,12.07-4.18,4,0,7.48.89,10.46,2.67,2.97,1.78,5.28,4.29,6.92,7.54,1.64,3.25,2.46,7.02,2.46,11.33v36.5h-17.89v-33.02c0-3.21-.82-5.73-2.46-7.56-1.64-1.83-3.93-2.74-6.87-2.74-1.92,0-3.62.42-5.1,1.26-1.49.84-2.64,2.04-3.46,3.61-.82,1.57-1.23,3.49-1.23,5.74Z\"/>\n</svg>";

/** The square Smooth icon (the stylized `th` glyph) — used as the default full-page header avatar. */
export const SMOOTH_ICON_SVG = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<svg id=\"Layer_1\" data-name=\"Layer 1\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" viewBox=\"0 0 150 150\">\n <defs>\n <style>\n .cls-1 {\n fill: url(#linear-gradient);\n }\n </style>\n <linearGradient id=\"linear-gradient\" x1=\"31.06\" y1=\"37.6\" x2=\"118.5\" y2=\"125.04\" gradientUnits=\"userSpaceOnUse\">\n <stop offset=\".43\" stop-color=\"#00a6a6\"/>\n <stop offset=\"1\" stop-color=\"#1238dd\"/>\n </linearGradient>\n </defs>\n <path class=\"cls-1\" d=\"M55.03,55.47v13.28H19.24v-13.28h35.79ZM26.83,41.83h17.89v53.42c0,1.5.36,2.62,1.08,3.36.72.74,1.88,1.1,3.49,1.1.62,0,1.48-.07,2.59-.21,1.11-.14,1.91-.27,2.38-.41l2.31,13.02c-2.02.58-3.97.97-5.84,1.18-1.88.21-3.66.31-5.33.31-6.08,0-10.7-1.43-13.84-4.28-3.15-2.85-4.72-7.01-4.72-12.48v-55.01ZM93.74,80.08v32.71h-17.89V36.39h17.53v33.53h-1.13c1.4-4.55,3.6-8.21,6.59-11,2.99-2.79,7.01-4.18,12.07-4.18,4,0,7.48.89,10.46,2.67,2.97,1.78,5.28,4.29,6.92,7.54,1.64,3.25,2.46,7.02,2.46,11.33v36.5h-17.89v-33.02c0-3.21-.82-5.73-2.46-7.56-1.64-1.83-3.93-2.74-6.87-2.74-1.92,0-3.62.42-5.1,1.26-1.49.84-2.64,2.04-3.46,3.61-.82,1.57-1.23,3.49-1.23,5.74Z\"/>\n</svg>";
Loading
Loading