|
| 1 | +<script setup lang="ts"> |
| 2 | +import { onMounted, ref, type Component } from 'vue'; |
| 3 | +import DocsIcon from '~icons/material-symbols/docs-outline-rounded'; |
| 4 | +import ArrowForwardIcon from '~icons/material-symbols/arrow-forward-rounded'; |
| 5 | +import CodeIcon from '~icons/material-symbols/code-blocks-outline'; |
| 6 | +import EditIcon from '~icons/material-symbols/rebase-edit-outline-rounded'; |
| 7 | +import { |
| 8 | + landingPopUpStorageKey, |
| 9 | + markLandingPopUpSeen, |
| 10 | + type LandingPopUpChoice, |
| 11 | + shouldShowLandingPopUp, |
| 12 | +} from '../landingPopUp.js'; |
| 13 | +import { trackPlausibleEvent } from '../theme/plausible.js'; |
| 14 | +
|
| 15 | +const isVisible = ref(false); |
| 16 | +
|
| 17 | +type LandingOption = { |
| 18 | + choice: Exclude<LandingPopUpChoice, 'skip'>; |
| 19 | + title: string; |
| 20 | + description: string; |
| 21 | + cta: string; |
| 22 | + rowClass: string; |
| 23 | + buttonClass: string; |
| 24 | + icon: Component; |
| 25 | +}; |
| 26 | +
|
| 27 | +const links: Record<Exclude<LandingPopUpChoice, 'skip'>, string> = { |
| 28 | + 'full-editor': |
| 29 | + 'https://mermaid.ai/app/sign-up?utm_source=mermaid_js&utm_medium=landing_pop_up&utm_campaign=editor_v1', |
| 30 | + 'browse-docs': |
| 31 | + 'https://mermaid.ai/open-source/intro/index.html?utm_source=mermaid_js&utm_medium=landing_pop_up&utm_campaign=docs_v1', |
| 32 | + 'live-editor': 'https://mermaid.live', |
| 33 | +}; |
| 34 | +
|
| 35 | +const options: LandingOption[] = [ |
| 36 | + { |
| 37 | + choice: 'full-editor', |
| 38 | + title: "Mermaid's full editor", |
| 39 | + description: 'Render existing diagrams or build with AI, code, drag-and-drop, or voice.', |
| 40 | + cta: 'Start free', |
| 41 | + rowClass: 'border-[#e80962]', |
| 42 | + buttonClass: 'bg-[#e80962] text-white group-hover:bg-[#ff1e7a]', |
| 43 | + icon: EditIcon, |
| 44 | + }, |
| 45 | + { |
| 46 | + choice: 'browse-docs', |
| 47 | + title: 'Browse the docs', |
| 48 | + description: 'Mermaid syntax reference, diagram guides, and contributor docs.', |
| 49 | + cta: 'Read docs', |
| 50 | + rowClass: 'border-[#2b2542]', |
| 51 | + buttonClass: 'bg-[#332a54] text-[#ddedf0] group-hover:bg-[#3f3568]', |
| 52 | + icon: DocsIcon, |
| 53 | + }, |
| 54 | + { |
| 55 | + choice: 'live-editor', |
| 56 | + title: 'Code in the live editor', |
| 57 | + description: 'Write Mermaid syntax with live preview — no account needed.', |
| 58 | + cta: 'Mermaid.live', |
| 59 | + rowClass: 'border-[#2b2542]', |
| 60 | + buttonClass: 'bg-[#332a54] text-[#ddedf0] group-hover:bg-[#3f3568]', |
| 61 | + icon: CodeIcon, |
| 62 | + }, |
| 63 | +]; |
| 64 | +
|
| 65 | +const rememberSeen = () => { |
| 66 | + markLandingPopUpSeen((value) => { |
| 67 | + window.localStorage.setItem(landingPopUpStorageKey, value); |
| 68 | + }); |
| 69 | +}; |
| 70 | +
|
| 71 | +const handleChoice = (choice: LandingPopUpChoice) => { |
| 72 | + void trackPlausibleEvent('landingPopUp', { props: { choice } }); |
| 73 | + isVisible.value = false; |
| 74 | + rememberSeen(); |
| 75 | +
|
| 76 | + if (choice === 'skip') { |
| 77 | + return; |
| 78 | + } |
| 79 | +
|
| 80 | + window.location.href = links[choice]; |
| 81 | +}; |
| 82 | +
|
| 83 | +onMounted(() => { |
| 84 | + const visible = shouldShowLandingPopUp({ |
| 85 | + hostname: window.location.hostname, |
| 86 | + referrer: document.referrer, |
| 87 | + getLastSeen: () => window.localStorage.getItem(landingPopUpStorageKey), |
| 88 | + }); |
| 89 | +
|
| 90 | + if (!visible) { |
| 91 | + return; |
| 92 | + } |
| 93 | +
|
| 94 | + isVisible.value = true; |
| 95 | +}); |
| 96 | +</script> |
| 97 | + |
| 98 | +<template> |
| 99 | + <div |
| 100 | + v-if="isVisible" |
| 101 | + class="fixed inset-0 z-50 flex items-center justify-center bg-[#070916]/70 p-4" |
| 102 | + @click.self="handleChoice('skip')" |
| 103 | + > |
| 104 | + <div |
| 105 | + class="w-full max-w-[654px] rounded-2xl border border-[#374151] bg-[#1a1625] p-8 text-white shadow-[0_20px_80px_rgba(0,0,0,0.55)]" |
| 106 | + > |
| 107 | + <div class="flex flex-col items-center text-center"> |
| 108 | + <img src="/favicon.svg" alt="Mermaid" class="h-[41px] w-[41px] rounded-[8px]" /> |
| 109 | + <div class="h-2 w-px" /> |
| 110 | + <h2 class="mt-2 text-2xl font-bold text-white">What are you looking for?</h2> |
| 111 | + </div> |
| 112 | + |
| 113 | + <div class="mt-5 grid gap-3"> |
| 114 | + <button |
| 115 | + v-for="option in options" |
| 116 | + :key="option.choice" |
| 117 | + class="group flex items-center gap-3 overflow-hidden rounded-lg border bg-transparent py-[14px] pl-3 pr-2 text-left transition-colors hover:border-[#f84594] hover:bg-[#211c31] hover:shadow-[0_0_0_1px_#f84594]" |
| 118 | + :class="option.rowClass" |
| 119 | + @click="handleChoice(option.choice)" |
| 120 | + > |
| 121 | + <div class="flex h-5 w-5 shrink-0 items-center justify-center text-[#f5f5f5]"> |
| 122 | + <component :is="option.icon" class="h-5 w-5" /> |
| 123 | + </div> |
| 124 | + |
| 125 | + <div class="min-w-0 flex-1"> |
| 126 | + <div class="text-[14px] font-medium text-white">{{ option.title }}</div> |
| 127 | + <p class="mt-0.5 text-xs font-light leading-[1.2] text-[#9ca3af]"> |
| 128 | + {{ option.description }} |
| 129 | + </p> |
| 130 | + </div> |
| 131 | + |
| 132 | + <div class="flex shrink-0 items-center pl-2"> |
| 133 | + <span |
| 134 | + class="inline-flex items-center gap-2 rounded-[12px] px-4 py-2 text-sm font-semibold transition-colors" |
| 135 | + :class="option.buttonClass" |
| 136 | + > |
| 137 | + {{ option.cta }} |
| 138 | + <ArrowForwardIcon class="h-4 w-4" /> |
| 139 | + </span> |
| 140 | + </div> |
| 141 | + </button> |
| 142 | + </div> |
| 143 | + |
| 144 | + <div class="mt-6 text-center text-xs text-[#6b7280]"> |
| 145 | + <button class="underline" @click="handleChoice('skip')"> |
| 146 | + Just exploring, skip for now |
| 147 | + </button> |
| 148 | + <p class="mt-3">The full editor is part of Mermaid.ai. Start free today.</p> |
| 149 | + </div> |
| 150 | + </div> |
| 151 | + </div> |
| 152 | +</template> |
0 commit comments