Skip to content
Draft
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
1 change: 1 addition & 0 deletions public/llms.txt
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ curl -sL https://memoh.sh | bash

## Links
- Website: https://memoh.ai
- Pricing: https://memoh.ai/pricing
- Blog: https://memoh.ai/blogs
- Documentation: https://docs.memoh.ai
- GitHub: https://github.com/memohai/Memoh
Expand Down
6 changes: 6 additions & 0 deletions public/sitemap.xml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@
<changefreq>monthly</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc>https://memoh.ai/pricing</loc>
<lastmod>2026-06-09</lastmod>
<changefreq>monthly</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc>https://memoh.ai/blogs</loc>
<lastmod>2026-06-09</lastmod>
Expand Down
4 changes: 3 additions & 1 deletion src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@ const { isDark } = useThemePreference()

const isDownloadRoute = computed(() => route.path.startsWith('/download'))
const isBlogsRoute = computed(() => route.path.startsWith('/blogs'))
const isPricingRoute = computed(() => route.path.startsWith('/pricing'))
const isHomeRoute = computed(() => route.path === '/')
const isPlainContentRoute = computed(() => isDownloadRoute.value || isBlogsRoute.value)
const isPlainContentRoute = computed(() => isDownloadRoute.value || isBlogsRoute.value || isPricingRoute.value)

// Single owner of the <html> `dark` class. The landing page always *renders*
// dark, but must never overwrite the user's saved preference — so we force the
Expand Down Expand Up @@ -91,6 +92,7 @@ const telecomLicenseUrl = 'https://dxzhgl.miit.gov.cn/'
<div class="flex flex-col gap-3">
<span class="text-[11px] font-medium uppercase tracking-wider text-muted-foreground/55">{{ t('footer.product') }}</span>
<router-link to="/download" class="text-sm text-muted-foreground hover:text-foreground transition-colors">{{ t('nav.download') }}</router-link>
<router-link to="/pricing" class="text-sm text-muted-foreground hover:text-foreground transition-colors">{{ t('nav.pricing') }}</router-link>
<a href="https://github.com/memohai/supermarket" target="_blank" rel="noopener noreferrer" class="text-sm text-muted-foreground hover:text-foreground transition-colors">{{ t('nav.supermarket') }}</a>
</div>
<div class="flex flex-col gap-3">
Expand Down
3 changes: 1 addition & 2 deletions src/components/TopBar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ const { isDark, toggle: toggleDark } = useThemePreference()

const { locale } = useI18n()
const docsUrl = computed(() => locale.value === 'zh' ? 'https://docs.memoh.ai/zh' : 'https://docs.memoh.ai')
const pricingUrl = 'https://memoh.ai/pricing'
const telegramIconSrc = computed(() => props.overlay || props.lightSocialIcons ? '/brands/telegram-white.svg' : '/brands/telegram.svg')

const isLangOpen = ref(false)
Expand Down Expand Up @@ -70,7 +69,7 @@ const selectLang = (lang: string) => {
</RouterLink>
<nav class="hidden md:flex items-center gap-1 sm:gap-2">
<a :href="docsUrl" target="_blank" rel="noopener noreferrer" class="font-medium text-sm rounded-md px-3 py-2 transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 whitespace-nowrap" :class="linkClass">{{ $t('nav.docs') }}</a>
<a :href="pricingUrl" class="font-medium text-sm rounded-md px-3 py-2 transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 whitespace-nowrap" :class="linkClass">{{ $t('nav.pricing') }}</a>
<RouterLink to="/pricing" class="font-medium text-sm rounded-md px-3 py-2 transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 whitespace-nowrap" :class="linkClass" :active-class="overlay ? 'text-white bg-white/10' : 'text-foreground bg-accent'">{{ $t('nav.pricing') }}</RouterLink>
<RouterLink to="/blogs" class="font-medium text-sm rounded-md px-3 py-2 transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 whitespace-nowrap" :class="linkClass" :active-class="overlay ? 'text-white bg-white/10' : 'text-foreground bg-accent'">{{ $t('nav.blogs') }}</RouterLink>
</nav>
</div>
Expand Down
211 changes: 211 additions & 0 deletions src/pages/PricingPage.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
<script setup lang="ts">
import { useSeoMeta } from '@unhead/vue'
import { ArrowRight, Check, FileText } from 'lucide-vue-next'

const plans = [
{
name: 'Starter',
price: '$0',
period: '/ month',
badge: 'Placeholder',
description: 'A quiet placeholder tier for trying the Memoh workspace shape.',
action: 'Start placeholder',
highlighted: false,
features: [
'Lorem workspace quota',
'Ipsum agent runtime',
'Dolor memory window',
'Sit amet support lane',
],
},
{
name: 'Builder',
price: '$29',
period: '/ month',
badge: 'Popular',
description: 'A middle placeholder tier for teams that need more room to test.',
action: 'Choose Builder',
highlighted: true,
features: [
'Consectetur agent seats',
'Adipiscing compute minutes',
'Tempor file retention',
'Incididunt integration slots',
],
},
{
name: 'Scale',
price: 'Custom',
period: '',
badge: 'Team',
description: 'A flexible placeholder tier for bigger deployment conversations.',
action: 'Talk to us',
highlighted: false,
features: [
'Enterprise placeholder limits',
'Dedicated lorem channel',
'Custom ipsum policies',
'Priority dolor review',
],
},
]

const comparisonRows = [
['Agent workspaces', '1 lorem', '8 lorem', 'Custom lorem'],
['Persistent storage', 'Basic ipsum', 'Team ipsum', 'Private ipsum'],
['Automation runs', 'Small dolor', 'More dolor', 'Custom dolor'],
['Support', 'Community sit', 'Priority sit', 'Dedicated sit'],
]

const notes = [
{
title: 'Bring your own keys',
body: 'Placeholder billing language for model routing, API keys, and usage ownership.',
},
{
title: 'Self-host friendly',
body: 'Placeholder deployment language for local, cloud, and hybrid Memoh setups.',
},
{
title: 'Upgrade later',
body: 'Placeholder account language for changing plans when the real pricing lands.',
},
]

useSeoMeta({
title: 'Pricing | Memoh',
description: 'Placeholder pricing plans for Memoh.',
ogTitle: 'Pricing | Memoh',
ogDescription: 'Placeholder pricing plans for Memoh.',
ogType: 'website',
ogUrl: 'https://memoh.ai/pricing',
twitterCard: 'summary_large_image',
twitterTitle: 'Pricing | Memoh',
twitterDescription: 'Placeholder pricing plans for Memoh.',
})
</script>

<template>
<main class="w-full max-w-[1080px] min-h-[calc(100vh-3.5rem)] mx-auto px-4 md:px-8 pt-[112px] md:pt-[148px] pb-[120px] relative z-10">
<section class="flex flex-col gap-12">
<header class="max-w-[760px] flex flex-col gap-5">
<div class="inline-flex w-fit items-center gap-2 rounded-md border border-border bg-background px-3 py-1.5 text-xs font-medium text-muted-foreground">
<FileText class="w-3.5 h-3.5" />
Memoh Pricing
</div>
<div class="flex flex-col gap-4">
<h1 class="text-4xl md:text-6xl font-semibold tracking-tight text-foreground leading-[1.05]">
Pricing that stays out of the way.
</h1>
<p class="text-base md:text-lg text-muted-foreground leading-relaxed max-w-[680px]">
Placeholder plans for agents with their own computers, persistent files, and always-on workspaces.
</p>
</div>
</header>

<section class="grid grid-cols-1 md:grid-cols-3 gap-4">
<article
v-for="plan in plans"
:key="plan.name"
class="flex min-h-[520px] flex-col rounded-xl border bg-background p-5 transition-all duration-300 hover:-translate-y-1 hover:shadow-md"
:class="plan.highlighted ? 'border-foreground shadow-sm' : 'border-border'"
>
<div class="flex items-start justify-between gap-4">
<div class="flex flex-col gap-2">
<span class="text-xs font-medium uppercase tracking-[0.16em] text-primary">{{ plan.badge }}</span>
<h2 class="text-xl font-semibold tracking-tight text-foreground">{{ plan.name }}</h2>
</div>
<span
v-if="plan.highlighted"
class="rounded-full bg-foreground px-2.5 py-1 text-[11px] font-medium text-background"
>
Best fit
</span>
</div>

<p class="mt-5 text-sm text-muted-foreground leading-relaxed">
{{ plan.description }}
</p>

<div class="mt-7 flex items-end gap-1">
<span class="text-4xl font-semibold tracking-tight text-foreground">{{ plan.price }}</span>
<span v-if="plan.period" class="pb-1.5 text-sm text-muted-foreground">{{ plan.period }}</span>
</div>

<a
href="https://docs.memoh.ai"
target="_blank"
rel="noopener noreferrer"
class="btn-pill mt-7 inline-flex h-11 items-center justify-center gap-2 px-5 text-sm font-medium focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
:class="plan.highlighted ? 'btn-pill-primary' : 'btn-pill-secondary'"
>
<span>{{ plan.action }}</span>
<ArrowRight class="w-4 h-4" />
</a>

<div class="mt-8 border-t border-border pt-5">
<p class="mb-4 text-xs font-medium uppercase tracking-[0.14em] text-muted-foreground/75">
Included
</p>
<ul class="flex flex-col gap-3">
<li
v-for="feature in plan.features"
:key="feature"
class="flex items-start gap-2.5 text-sm text-muted-foreground"
>
<Check class="mt-0.5 h-4 w-4 shrink-0 text-foreground" />
<span class="leading-relaxed">{{ feature }}</span>
</li>
</ul>
</div>
</article>
</section>

<section class="overflow-hidden rounded-xl border border-border bg-background">
<div class="border-b border-border p-5 md:p-6">
<h2 class="text-xl font-semibold tracking-tight text-foreground">Compare the placeholders</h2>
<p class="mt-2 max-w-[640px] text-sm text-muted-foreground leading-relaxed">
These entries are intentionally temporary, but the layout is ready for real pricing details.
</p>
</div>
<div class="overflow-x-auto">
<table class="w-full min-w-[720px] text-left text-sm">
<thead class="border-b border-border text-xs uppercase tracking-[0.14em] text-muted-foreground/75">
<tr>
<th class="px-5 py-4 font-medium">Capability</th>
<th class="px-5 py-4 font-medium">Starter</th>
<th class="px-5 py-4 font-medium">Builder</th>
<th class="px-5 py-4 font-medium">Scale</th>
</tr>
</thead>
<tbody>
<tr
v-for="row in comparisonRows"
:key="row[0]"
class="border-b border-border last:border-b-0"
>
<td class="px-5 py-4 font-medium text-foreground">{{ row[0] }}</td>
<td class="px-5 py-4 text-muted-foreground">{{ row[1] }}</td>
<td class="px-5 py-4 text-muted-foreground">{{ row[2] }}</td>
<td class="px-5 py-4 text-muted-foreground">{{ row[3] }}</td>
</tr>
</tbody>
</table>
</div>
</section>

<section class="grid grid-cols-1 md:grid-cols-3 gap-4">
<article
v-for="note in notes"
:key="note.title"
class="rounded-xl border border-border bg-background p-5"
>
<h2 class="text-base font-semibold tracking-tight text-foreground">{{ note.title }}</h2>
<p class="mt-3 text-sm text-muted-foreground leading-relaxed">
{{ note.body }}
</p>
</article>
</section>
</section>
</main>
</template>
2 changes: 2 additions & 0 deletions src/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@ import { createRouter, createWebHistory } from 'vue-router'
const HomePage = () => import('./pages/HomePage.vue')
const DownloadPage = () => import('./pages/DownloadPage.vue')
const BlogsPage = () => import('./pages/BlogsPage.vue')
const PricingPage = () => import('./pages/PricingPage.vue')

export const router = createRouter({
history: createWebHistory(),
routes: [
{ path: '/', name: 'home', component: HomePage },
{ path: '/download', name: 'download', component: DownloadPage },
{ path: '/pricing', name: 'pricing', component: PricingPage },
{ path: '/blogs', name: 'blogs', component: BlogsPage },
{ path: '/blogs/:slug', name: 'blog-post', component: BlogsPage },
// /desktop has been merged into /download; keep a redirect for old links.
Expand Down