diff --git a/src/lib/components/AIPromptPopup.svelte b/src/lib/components/AIPromptPopup.svelte new file mode 100644 index 00000000..ded983f1 --- /dev/null +++ b/src/lib/components/AIPromptPopup.svelte @@ -0,0 +1,174 @@ + + + + +{#if show} + +{/if} + + diff --git a/src/lib/components/DesktopEditor.svelte b/src/lib/components/DesktopEditor.svelte index cb54ac5b..ea7b4868 100644 --- a/src/lib/components/DesktopEditor.svelte +++ b/src/lib/components/DesktopEditor.svelte @@ -1,7 +1,8 @@ -
+
+
+
+ aiPromptManager.updateHeight(height)} + onClose={closePopup} + onTryFree={() => { + window.open($urlsStore.mermaidChart({ medium: 'vibe_diagramming' }).save, '_blank'); + closePopup(); + }} /> +
+
+ + diff --git a/src/lib/constants.ts b/src/lib/constants.ts index 2e71e336..9a83ddba 100644 --- a/src/lib/constants.ts +++ b/src/lib/constants.ts @@ -10,5 +10,6 @@ export const TID = { } as const; export const C = { + aiLiveEditor: 'ai_live_editor', utmSource: 'mermaid_live_editor' } as const; diff --git a/src/lib/util/AIPromptViewZoneManager.ts b/src/lib/util/AIPromptViewZoneManager.ts new file mode 100644 index 00000000..483363eb --- /dev/null +++ b/src/lib/util/AIPromptViewZoneManager.ts @@ -0,0 +1,71 @@ +import type * as monaco from 'monaco-editor'; + +export class AIPromptViewZoneManager { + private editor: monaco.editor.IStandaloneCodeEditor | null = null; + private viewZoneId: string | null = null; + private viewZoneNode: HTMLElement | null = null; + private viewZone: monaco.editor.IViewZone | null = null; + private topPopupSpace = 8; + + public setEditor(editor: monaco.editor.IStandaloneCodeEditor): void { + this.editor = editor; + } + + public show(lineNumber: number, node: HTMLElement, height: number): void { + if (!this.editor) return; + + this.viewZoneNode = node; + + this.editor.changeViewZones((changeAccessor) => { + if (this.viewZoneId) { + changeAccessor.removeZone(this.viewZoneId); + } + + const domNode = document.createElement('div'); + domNode.style.top = '0px'; + domNode.style.paddingTop = '8px'; + domNode.style.paddingBottom = '0px'; + domNode.appendChild(this.viewZoneNode as Node); + + this.viewZone = { + afterLineNumber: lineNumber, + heightInPx: height + this.topPopupSpace, + domNode: domNode + }; + + this.viewZoneId = changeAccessor.addZone(this.viewZone); + }); + } + + public updateHeight(height: number): void { + if (!this.editor || !this.viewZoneId || !this.viewZone) return; + + this.viewZone.heightInPx = height + this.topPopupSpace; + this.editor.changeViewZones((changeAccessor) => { + if (this.viewZoneId) { + changeAccessor.layoutZone(this.viewZoneId); + } + }); + } + + public hide(): void { + if (!this.editor || !this.viewZoneId) return; + + this.editor.changeViewZones((changeAccessor) => { + if (this.viewZoneId) { + changeAccessor.removeZone(this.viewZoneId); + this.viewZoneId = null; + this.viewZone = null; + } + }); + + if (this.viewZoneNode) { + this.viewZoneNode = null; + } + } + + public destroy(): void { + this.hide(); + this.editor = null; + } +} diff --git a/src/lib/util/promos/NewYear2026.svelte b/src/lib/util/promos/NewYear2026.svelte index 0b6a947c..d9d5112e 100644 --- a/src/lib/util/promos/NewYear2026.svelte +++ b/src/lib/util/promos/NewYear2026.svelte @@ -9,68 +9,29 @@ let { closeBanner }: Props = $props(); - interface VariantConfig { - bannerCopy: string; - buttonCopy: string; - campaign: 'variant_a' | 'variant_b' | 'variant_c'; - } + const utmSource = env.domain === 'mermaid.ai' ? 'mermaid_ai_live' : 'mermaid_live_editor'; - const variants: VariantConfig[] = [ - { - bannerCopy: 'Use code NEWYEAR for 10% off Mermaid Advanced Editor (limited time)', - buttonCopy: 'Try now', - campaign: 'variant_a' - }, - { - bannerCopy: 'Limited time: 10% off Mermaid Advanced Editor with code NEWYEAR', - buttonCopy: 'Claim discount', - campaign: 'variant_b' - }, - { - bannerCopy: 'Try Mermaid Advanced Editor — get 10% off with code NEWYEAR', - buttonCopy: 'Get started', - campaign: 'variant_c' - } - ]; - - // Determine UTM source based on deployment domain - const getUtmSource = (): string => { - const domain = env.domain; - if (domain === 'mermaid.ai') { - return 'mermaid_ai_live'; - } - return 'mermaid_live_editor'; - }; - - // Randomly select a variant on component mount - const getRandomVariant = (): VariantConfig => { - const index = Math.floor(Math.random() * variants.length); - return variants[index]; - }; - - const selectedVariant = getRandomVariant(); - const utmSource = getUtmSource(); - - const buildUrl = (variant: VariantConfig): string => { - const params = new URLSearchParams({ - utm_medium: 'banner_ad', - utm_campaign: variant.campaign, - utm_source: utmSource - }); - return `https://mermaid.ai/?${params.toString()}`; - }; + const url = `https://mermaid.ai/app/user/billing/checkout?${new URLSearchParams({ + coupon: 'dmIuNbqx', + tier: 'plus', + utm_campaign: 'newyear', + utm_medium: 'banner_ad', + utm_source: utmSource + }).toString()}`;