+ import { Button } from '$/components/ui/button';
+ import * as Dialog from '$/components/ui/dialog';
+ import { dismissEditorChooser, isOnMermaidAI } from '$/util/migration/domainMigration';
+ import { logEvent } from '$/util/stats';
+ import CodeIcon from '~icons/custom/code';
+ import OpenSourceIcon from '~icons/material-symbols/book-2-outline-rounded';
+ import ChatIcon from '~icons/material-symbols/chat-outline-rounded';
+ import EditIcon from '~icons/material-symbols/edit-outline-rounded';
+ import HistoryIcon from '~icons/material-symbols/history';
+ import HomeIcon from '~icons/material-symbols/home-storage-outline-rounded';
+ import SparklesIcon from '~icons/material-symbols/kid-star-outline';
+ import LanguageIcon from '~icons/material-symbols/language';
+ import WidthIcon from '~icons/material-symbols/width-rounded';
+
+ interface Props {
+ open: boolean;
+ }
+
+ let { open = $bindable() }: Props = $props();
+
+ const close = () => {
+ dismissEditorChooser();
+ open = false;
+ };
+
+ const handleStartTrial = () => {
+ logEvent('editorChooserPlus');
+ close();
+ const utmSource = isOnMermaidAI() ? 'mermaid_ai_live' : 'mermaid_live_editor';
+ window.open(
+ `https://mermaid.ai/app/user/billing/checkout?${new URLSearchParams({
+ coupon: 'dmIuNbqx',
+ tier: 'plus',
+ utm_campaign: 'start_plus',
+ utm_medium: '2_editor_selection',
+ utm_source: utmSource
+ }).toString()}`,
+ '_blank'
+ );
+ };
+
+ const handleStartFree = () => {
+ logEvent('editorChooserOpenSource');
+ close();
+ };
+
+
+
{
+ if (!v) handleStartFree();
+ }}>
+
+
+ Choose your editor
+
+ You'll never see this again
+
+
+
+
+
+
+
+ Recommended
+
+
+
+
Mermaid Plus
+
Unlock AI, storage and collaboration
+
+
+
+ Limited time: 10% off
+
+
+
+
+
+
+ -
+
+ Visual editor
+
+ -
+
+ 300 AI credits
+
+ -
+
+ Unlimited diagram storage
+
+ -
+
+ Limitless diagram size
+
+ -
+
+ View & comment collaboration
+
+
+
+
+
+
+
+
Open Source
+
Code only, no login, always free
+
+
+
+
+ -
+
+ Diagram stored in URL
+
+ -
+
+ Code editor
+
+ -
+
+ Open source
+
+ -
+
+ Version history
+
+
+
+
+
+
diff --git a/src/lib/constants.ts b/src/lib/constants.ts
index 9a83ddba..f9b48755 100644
--- a/src/lib/constants.ts
+++ b/src/lib/constants.ts
@@ -11,5 +11,6 @@ export const TID = {
export const C = {
aiLiveEditor: 'ai_live_editor',
+ editorChooserDismissedKey: 'mermaid-editor-chooser-dismissed',
utmSource: 'mermaid_live_editor'
} as const;
diff --git a/src/lib/util/migration/domainMigration.ts b/src/lib/util/migration/domainMigration.ts
new file mode 100644
index 00000000..17fd8cd3
--- /dev/null
+++ b/src/lib/util/migration/domainMigration.ts
@@ -0,0 +1,62 @@
+import { C } from '$/constants';
+import { env } from '$/util/env';
+
+const mermaidAiDomain = 'mermaid.ai';
+
+/**
+ * Check if we're on mermaid.ai
+ */
+export const isOnMermaidAI = (): boolean => {
+ const domain = window.location.hostname;
+ return domain === mermaidAiDomain || domain.endsWith(`.${mermaidAiDomain}`);
+};
+
+// localStorage keys that indicate a returning user.
+// Note: codeStore is excluded because it's always populated with the default state on first load.
+const userDataStorageKeys = [
+ 'manualHistoryStore', // Manual history entries
+ 'autoHistoryStore' // Auto history entries
+];
+
+/**
+ * Check if user has any stored data in localStorage.
+ * This includes saved diagrams, history entries, etc.
+ */
+const hasStoredUserData = (): boolean => {
+ for (const key of userDataStorageKeys) {
+ const value = window.localStorage.getItem(key);
+ if (value && value !== '[]' && value !== 'null' && value !== '{}') {
+ return true;
+ }
+ }
+ return false;
+};
+
+/**
+ * Check if the current URL has pako data (diagram content from a shared link).
+ */
+const hasPakoData = (): boolean => {
+ const hash = window.location.hash;
+ return (
+ hash.includes('pako:') || hash.includes('base64:') || (hash.length > 10 && hash.startsWith('#'))
+ );
+};
+
+/**
+ * Check if the editor chooser modal should be shown.
+ * Shows for new users who haven't dismissed it and aren't viewing a shared link.
+ */
+export const shouldShowEditorChooser = (): boolean => {
+ if (!env.isEnabledMermaidChartLinks) return false;
+ if (window.localStorage.getItem(C.editorChooserDismissedKey) === 'true') return false;
+ if (hasStoredUserData()) return false;
+ if (hasPakoData()) return false;
+ return true;
+};
+
+/**
+ * Dismiss the editor chooser modal permanently
+ */
+export const dismissEditorChooser = (): void => {
+ window.localStorage.setItem(C.editorChooserDismissedKey, 'true');
+};
diff --git a/src/lib/util/promos/NewYear2026.svelte b/src/lib/util/promos/NewYear2026.svelte
index d9d5112e..e213b330 100644
--- a/src/lib/util/promos/NewYear2026.svelte
+++ b/src/lib/util/promos/NewYear2026.svelte
@@ -1,6 +1,6 @@