This commit is contained in:
Leonardo
2026-03-06 06:37:13 -03:00
parent d58dc21297
commit f733db8436
146 changed files with 43436 additions and 12779 deletions
+386 -233
View File
@@ -1,3 +1,4 @@
<!-- src/views/pages/public/LandingPage.vue -->
<template>
<div class="min-h-screen bg-[var(--surface-ground)] text-[var(--text-color)]">
<!-- TOPBAR -->
@@ -5,46 +6,65 @@
class="sticky top-0 z-40 border-b border-[var(--surface-border)] bg-[color-mix(in_srgb,var(--surface-card),transparent_12%)] backdrop-blur"
>
<div class="mx-auto max-w-7xl px-4 md:px-6 py-3 flex items-center justify-between gap-3">
<div class="flex items-center gap-3 min-w-0">
<button class="flex items-center gap-3 min-w-0" @click="scrollTo('top')">
<div
class="h-10 w-10 rounded-2xl border border-[var(--surface-border)] bg-[var(--surface-card)] grid place-items-center shadow-sm"
>
<i class="pi pi-sparkles text-lg opacity-80" />
</div>
<div class="min-w-0">
<div class="min-w-0 text-left">
<div class="font-semibold leading-tight truncate">{{ brandName }}</div>
<div class="text-xs text-[var(--text-color-secondary)] truncate">Gestão clínica sem ruído.</div>
<div class="text-xs text-[var(--text-color-secondary)] truncate">
Gestão clínica sem ruído.
</div>
</div>
</div>
</button>
<div class="flex items-center gap-2">
<Button label="Entrar" icon="pi pi-sign-in" severity="secondary" outlined @click="go('/auth/login')" />
<Button label="Começar" icon="pi pi-bolt" @click="goStart()" />
<Button
label="Entrar"
icon="pi pi-sign-in"
severity="secondary"
outlined
@click="go('/auth/login')"
/>
<Button
label="Começar"
icon="pi pi-bolt"
@click="goStart()"
/>
</div>
</div>
</div>
<!-- HERO -->
<section class="relative overflow-hidden">
<!-- blobs / noir glow -->
<section id="top" class="relative overflow-hidden">
<!-- background: noir grid + glow -->
<div class="pointer-events-none absolute inset-0">
<div class="hero-grid absolute inset-0 opacity-[0.35]" />
<div class="absolute -top-28 -left-28 h-96 w-96 rounded-full blur-3xl opacity-60 bg-indigo-400/10" />
<div class="absolute top-24 -right-24 h-[28rem] w-[28rem] rounded-full blur-3xl opacity-60 bg-emerald-400/10" />
<div class="absolute top-20 -right-24 h-[28rem] w-[28rem] rounded-full blur-3xl opacity-60 bg-emerald-400/10" />
<div class="absolute -bottom-40 left-1/3 h-[34rem] w-[34rem] rounded-full blur-3xl opacity-60 bg-fuchsia-400/10" />
<div class="hero-noise absolute inset-0 opacity-[0.12]" />
</div>
<div class="mx-auto max-w-7xl px-4 md:px-6 pt-10 md:pt-16 pb-8 md:pb-14 relative">
<div class="mx-auto max-w-7xl px-4 md:px-6 pt-10 md:pt-16 pb-10 md:pb-14 relative">
<div class="grid grid-cols-12 gap-6 items-center">
<div class="col-span-12 lg:col-span-7">
<Chip class="mb-4" label="Para psicólogos e clínicas" icon="pi pi-shield" />
<div class="flex flex-wrap items-center gap-2 mb-4">
<Chip label="Para psicólogos e clínicas" icon="pi pi-shield" />
<span class="text-xs text-[var(--text-color-secondary)]">
menos dispersão mais presença mais previsibilidade
</span>
</div>
<h1 class="text-3xl md:text-5xl font-semibold leading-tight">
Uma agenda inteligente, um prontuário organizado, um financeiro respirável.
Um sistema que <span class="hero-underline">reduz ruído</span> sem roubar seu método.
</h1>
<p class="mt-4 text-base md:text-lg text-[var(--text-color-secondary)] max-w-2xl">
Centralize a rotina clínica em um lugar : pacientes, sessões, lembretes e indicadores. Menos dispersão.
Mais presença.
<p class="mt-4 text-base md:text-lg text-[var(--text-color-secondary)] max-w-2xl leading-relaxed">
Centralize a rotina clínica em um lugar : pacientes, sessões, lembretes e indicadores.
O objetivo não é burocratizar: é deixar o consultório respirável.
</p>
<div class="mt-6 flex flex-col sm:flex-row gap-2">
@@ -62,6 +82,14 @@
class="w-full sm:w-auto"
@click="scrollTo('pricing')"
/>
<Button
label="Como funciona"
icon="pi pi-compass"
severity="secondary"
text
class="w-full sm:w-auto"
@click="scrollTo('how')"
/>
</div>
<div class="mt-6 flex flex-wrap gap-2">
@@ -69,18 +97,25 @@
<Tag severity="secondary" value="Controle de sessões" />
<Tag severity="secondary" value="Financeiro integrado" />
<Tag severity="secondary" value="Clínica / multi-profissional" />
<Tag severity="secondary" value="Separação por tenant + RLS" />
</div>
<div class="mt-6 text-xs text-[var(--text-color-secondary)]">
A diferença entre ter uma agenda e ter um sistema mora nos detalhes.
</div>
</div>
<div class="col-span-12 lg:col-span-5">
<Card class="overflow-hidden">
<Card class="overflow-hidden rounded-[2rem] border border-[var(--surface-border)]">
<template #content>
<div class="p-1">
<div class="rounded-2xl border border-[var(--surface-border)] bg-[var(--surface-ground)] p-4">
<div class="flex items-start justify-between gap-3">
<div>
<div class="font-semibold text-lg">Painel de hoje</div>
<div class="text-sm text-[var(--text-color-secondary)]">Um recorte: o essencial, sem excesso.</div>
<div class="text-sm text-[var(--text-color-secondary)]">
Um recorte: o essencial, sem excesso.
</div>
</div>
<i class="pi pi-chart-line opacity-70" />
</div>
@@ -92,7 +127,9 @@
<div class="rounded-2xl border border-[var(--surface-border)] bg-[var(--surface-card)] p-3">
<div class="text-xs text-[var(--text-color-secondary)]">Sessões</div>
<div class="text-2xl font-semibold mt-1">6</div>
<div class="text-xs text-[var(--text-color-secondary)] mt-1">com lembretes automáticos</div>
<div class="text-xs text-[var(--text-color-secondary)] mt-1">
com lembretes automáticos
</div>
</div>
</div>
@@ -100,18 +137,20 @@
<div class="rounded-2xl border border-[var(--surface-border)] bg-[var(--surface-card)] p-3">
<div class="text-xs text-[var(--text-color-secondary)]">Recebimentos</div>
<div class="text-2xl font-semibold mt-1">R$ 840</div>
<div class="text-xs text-[var(--text-color-secondary)] mt-1">visão clara do mês</div>
<div class="text-xs text-[var(--text-color-secondary)] mt-1">
visão clara do mês
</div>
</div>
</div>
<div class="col-span-12">
<div class="rounded-2xl border border-[var(--surface-border)] bg-[var(--surface-card)] p-3">
<div class="flex items-center justify-between">
<div>
<div class="min-w-0">
<div class="text-xs text-[var(--text-color-secondary)]">Prontuário</div>
<div class="font-semibold mt-1">Anotações e histórico</div>
<div class="font-semibold mt-1 truncate">Anotações e histórico</div>
<div class="text-xs text-[var(--text-color-secondary)] mt-1">
organizado por paciente, sessão e linha do tempo
por paciente por sessão linha do tempo
</div>
</div>
<i class="pi pi-file-edit opacity-70" />
@@ -127,21 +166,40 @@
</div>
</template>
</Card>
<div class="mt-4 grid grid-cols-12 gap-3">
<div class="col-span-12 sm:col-span-6">
<div class="rounded-2xl border border-[var(--surface-border)] bg-[color-mix(in_srgb,var(--surface-card),transparent_10%)] p-4">
<div class="text-xs text-[var(--text-color-secondary)]">Promessa</div>
<div class="font-semibold mt-1">Organizar sem invadir.</div>
<div class="text-xs text-[var(--text-color-secondary)] mt-1">
Você define o método. O sistema remove ruído.
</div>
</div>
</div>
<div class="col-span-12 sm:col-span-6">
<div class="rounded-2xl border border-[var(--surface-border)] bg-[color-mix(in_srgb,var(--surface-card),transparent_10%)] p-4">
<div class="text-xs text-[var(--text-color-secondary)]">Arquitetura</div>
<div class="font-semibold mt-1">Multi-tenant de verdade.</div>
<div class="text-xs text-[var(--text-color-secondary)] mt-1">
Menus + guards + RLS alinhados.
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
<!-- TRUST / VALUE STRIP -->
<!-- VALUE STRIP -->
<section class="mx-auto max-w-7xl px-4 md:px-6 pb-10">
<div class="grid grid-cols-12 gap-4">
<div class="col-span-12 md:col-span-4">
<Card class="h-full">
<Card class="h-full rounded-[2rem]">
<template #content>
<div class="flex items-start gap-3">
<div
class="h-10 w-10 rounded-2xl border border-[var(--surface-border)] bg-[var(--surface-ground)] grid place-items-center"
>
<div class="h-10 w-10 rounded-2xl border border-[var(--surface-border)] bg-[var(--surface-ground)] grid place-items-center">
<i class="pi pi-calendar opacity-80" />
</div>
<div>
@@ -156,12 +214,10 @@
</div>
<div class="col-span-12 md:col-span-4">
<Card class="h-full">
<Card class="h-full rounded-[2rem]">
<template #content>
<div class="flex items-start gap-3">
<div
class="h-10 w-10 rounded-2xl border border-[var(--surface-border)] bg-[var(--surface-ground)] grid place-items-center"
>
<div class="h-10 w-10 rounded-2xl border border-[var(--surface-border)] bg-[var(--surface-ground)] grid place-items-center">
<i class="pi pi-wallet opacity-80" />
</div>
<div>
@@ -176,18 +232,16 @@
</div>
<div class="col-span-12 md:col-span-4">
<Card class="h-full">
<Card class="h-full rounded-[2rem]">
<template #content>
<div class="flex items-start gap-3">
<div
class="h-10 w-10 rounded-2xl border border-[var(--surface-border)] bg-[var(--surface-ground)] grid place-items-center"
>
<div class="h-10 w-10 rounded-2xl border border-[var(--surface-border)] bg-[var(--surface-ground)] grid place-items-center">
<i class="pi pi-lock opacity-80" />
</div>
<div>
<div class="font-semibold">Prontuário e controle de sessões</div>
<div class="font-semibold">Prontuário e sessões</div>
<div class="text-sm text-[var(--text-color-secondary)] mt-1">
Registro clínico e histórico acessíveis, com backups e organização.
Registro e histórico acessíveis, com organização e backup.
</div>
</div>
</div>
@@ -197,40 +251,82 @@
</div>
<div class="mt-3 text-xs text-[var(--text-color-secondary)]">
Inspirações de módulos comuns no mercado: agenda online, financeiro, prontuário/controle de sessões e gestão de
clínica.
Módulos: agenda, prontuário/sessões, financeiro e gestão multi-profissional.
</div>
</section>
<!-- FEATURES -->
<section class="mx-auto max-w-7xl px-4 md:px-6 pb-12">
<!-- HOW IT WORKS -->
<section id="how" class="mx-auto max-w-7xl px-4 md:px-6 pb-12 scroll-mt-24">
<div class="flex items-end justify-between gap-3 mb-4">
<div>
<div class="text-2xl md:text-3xl font-semibold">Recursos que sustentam a rotina</div>
<div class="text-2xl md:text-3xl font-semibold">Como funciona</div>
<div class="text-sm text-[var(--text-color-secondary)] mt-1">
O foco é tirar o excesso de fricção sem invadir o que é do seu método.
Três movimentos: preparar, atender, acompanhar sem fricção.
</div>
</div>
<Button label="Ver planos" severity="secondary" outlined icon="pi pi-arrow-down" @click="scrollTo('pricing')" />
</div>
<div class="grid grid-cols-12 gap-4">
<div v-for="f in features" :key="f.title" class="col-span-12 md:col-span-6 lg:col-span-4">
<Card class="h-full">
<div class="col-span-12 md:col-span-4">
<Card class="h-full rounded-[2rem]">
<template #content>
<div class="flex items-start gap-3">
<div
class="h-10 w-10 rounded-2xl border border-[var(--surface-border)] bg-[var(--surface-ground)] grid place-items-center"
>
<i :class="f.icon" class="opacity-80" />
<div class="h-10 w-10 rounded-2xl border border-[var(--surface-border)] bg-[var(--surface-ground)] grid place-items-center">
<i class="pi pi-sliders-h opacity-80" />
</div>
<div class="min-w-0">
<div class="font-semibold">{{ f.title }}</div>
<div class="text-sm text-[var(--text-color-secondary)] mt-1">
{{ f.desc }}
<div>
<div class="font-semibold">1) Preparar</div>
<div class="text-sm text-[var(--text-color-secondary)] mt-1 leading-relaxed">
Configure agenda, encaixes e regras. Se quiser, habilite autoagendamento (PRO).
</div>
<div v-if="f.pro" class="mt-2">
<Tag severity="warning" value="PRO" />
<div class="mt-2">
<Tag severity="secondary" value="Bloqueios" />
<Tag class="ml-2" severity="secondary" value="Encaixes" />
</div>
</div>
</div>
</template>
</Card>
</div>
<div class="col-span-12 md:col-span-4">
<Card class="h-full rounded-[2rem]">
<template #content>
<div class="flex items-start gap-3">
<div class="h-10 w-10 rounded-2xl border border-[var(--surface-border)] bg-[var(--surface-ground)] grid place-items-center">
<i class="pi pi-comments opacity-80" />
</div>
<div>
<div class="font-semibold">2) Atender</div>
<div class="text-sm text-[var(--text-color-secondary)] mt-1 leading-relaxed">
Sessão acontece. Registro fica onde precisa ficar: no prontuário, no tempo certo.
</div>
<div class="mt-2">
<Tag severity="secondary" value="Sessões" />
<Tag class="ml-2" severity="secondary" value="Prontuário" />
</div>
</div>
</div>
</template>
</Card>
</div>
<div class="col-span-12 md:col-span-4">
<Card class="h-full rounded-[2rem]">
<template #content>
<div class="flex items-start gap-3">
<div class="h-10 w-10 rounded-2xl border border-[var(--surface-border)] bg-[var(--surface-ground)] grid place-items-center">
<i class="pi pi-chart-bar opacity-80" />
</div>
<div>
<div class="font-semibold">3) Acompanhar</div>
<div class="text-sm text-[var(--text-color-secondary)] mt-1 leading-relaxed">
Financeiro e indicadores acompanham o movimento. Menos cadê?, mais previsibilidade.
</div>
<div class="mt-2">
<Tag severity="secondary" value="Recebimentos" />
<Tag class="ml-2" severity="secondary" value="Indicadores" />
</div>
</div>
</div>
@@ -244,58 +340,71 @@
<Accordion :activeIndex="0">
<AccordionTab header="Como fica o fluxo na prática?">
<div class="text-sm text-[var(--text-color-secondary)] leading-relaxed">
Você abre a agenda, a sessão acontece, o registro fica no prontuário, e o financeiro acompanha o movimento.
Você abre a agenda, a sessão acontece, o registro vai para o prontuário, e o financeiro acompanha.
O sistema existe para manter o consultório respirando não para virar uma burocracia nova.
</div>
</AccordionTab>
<AccordionTab header="E para clínica (multi-profissionais)?">
<div class="text-sm text-[var(--text-color-secondary)] leading-relaxed">
Perfis por função, agendas separadas, repasses e visão gerencial quando você estiver pronto para crescer.
Perfis por função, agendas separadas, visão gerencial e convites (Modelo B).
Você cresce sem quebrar a estrutura.
</div>
</AccordionTab>
<AccordionTab header="Privacidade e segurança">
<div class="text-sm text-[var(--text-color-secondary)] leading-relaxed">
Controle de acesso por conta, separação por clínica/tenant, e políticas de storage por usuário. (Os detalhes
de conformidade você pode expor numa página própria de segurança/LGPD.)
Separação por clínica/tenant, controle de acesso e políticas no banco (RLS).
(Quando quiser, a gente cria uma página dedicada de LGPD/Segurança.)
</div>
</AccordionTab>
</Accordion>
</section>
<!-- FEATURES -->
<section class="mx-auto max-w-7xl px-4 md:px-6 pb-12">
<div class="flex items-end justify-between gap-3 mb-4">
<div>
<div class="text-2xl md:text-3xl font-semibold">Recursos que sustentam a rotina</div>
<div class="text-sm text-[var(--text-color-secondary)] mt-1">
O foco é tirar fricção sem invadir o que é do seu método.
</div>
</div>
<Button label="Ver planos" severity="secondary" outlined icon="pi pi-arrow-down" @click="scrollTo('pricing')" />
</div>
<div class="grid grid-cols-12 gap-4">
<div v-for="f in features" :key="f.title" class="col-span-12 md:col-span-6 lg:col-span-4">
<Card class="h-full rounded-[2rem]">
<template #content>
<div class="flex items-start gap-3">
<div class="h-10 w-10 rounded-2xl border border-[var(--surface-border)] bg-[var(--surface-ground)] grid place-items-center">
<i :class="f.icon" class="opacity-80" />
</div>
<div class="min-w-0">
<div class="font-semibold">{{ f.title }}</div>
<div class="text-sm text-[var(--text-color-secondary)] mt-1 leading-relaxed">
{{ f.desc }}
</div>
<div v-if="f.pro" class="mt-2">
<Tag severity="warning" value="PRO" />
</div>
</div>
</div>
</template>
</Card>
</div>
</div>
</section>
<!-- PRICING (dinâmico do SaaS) -->
<section id="pricing" class="mx-auto max-w-7xl px-4 md:px-6 pb-14 scroll-mt-24">
<div class="text-5xl md:text-4xl font-semibold text-center">Planos</div>
<div class="text-2xl md:text-2xl text-[var(--text-color-secondary)] mt-1 text-center">
<div class="text-3xl md:text-4xl font-semibold text-center">Planos</div>
<div class="text-base md:text-lg text-[var(--text-color-secondary)] mt-2 text-center">
Comece simples. Suba para PRO quando a agenda pedir automação.
</div>
<!-- header conceitual + toggle -->
<!-- toggle -->
<div class="flex flex-col items-center text-center mt-6">
<div class="flex items-center gap-3 mb-4">
<AvatarGroup>
<Avatar
image="https://fqjltiegiezfetthbags.supabase.co/storage/v1/render/image/public/block.images/blocks/avatars/circle/avatar-m-1.png"
shape="circle"
/>
<Avatar
image="https://fqjltiegiezfetthbags.supabase.co/storage/v1/render/image/public/block.images/blocks/avatars/circle/avatar-f-21.png"
shape="circle"
/>
<Avatar
image="https://fqjltiegiezfetthbags.supabase.co/storage/v1/render/image/public/block.images/blocks/avatars/circle/avatar-f-1.png"
shape="circle"
/>
<Avatar
image="https://fqjltiegiezfetthbags.supabase.co/storage/v1/render/image/public/block.images/blocks/avatars/circle/avatar-m-3.png"
shape="circle"
/>
</AvatarGroup>
<Divider layout="vertical" />
<span class="text-sm text-[var(--text-color-secondary)] font-medium">Happy Customers</span>
</div>
<div class="inline-flex items-center rounded-xl border border-[var(--surface-border)] bg-[var(--surface-50)] p-1">
<div class="inline-flex items-center rounded-xl border border-[var(--surface-border)] bg-[color-mix(in_srgb,var(--surface-card),transparent_10%)] p-1">
<Button
label="Mensal"
size="small"
@@ -312,95 +421,132 @@
@click="billingInterval = 'year'"
/>
</div>
<div v-if="billingInterval === 'year'" class="mt-2">
<Tag severity="success" value="Economize até 20%" />
</div>
</div>
<div v-if="loadingPricing" class="mt-8 text-sm text-[var(--text-color-secondary)]">
Carregando planos...
</div>
<div v-else class="mt-8 grid grid-cols-12 gap-4">
<div v-for="p in pricing" :key="p.plan_id" class="col-span-12 md:col-span-4">
<Card
class="h-full overflow-hidden transition-transform"
:class="p.is_featured ? 'ring-1 ring-emerald-500/30 md:-translate-y-2 md:scale-[1.02]' : ''"
>
<template #content>
<div class="flex items-start justify-between gap-3">
<div>
<div class="text-sm text-[var(--text-color-secondary)]">
{{ p.badge || 'Plano' }}
</div>
<div class="text-xl font-semibold">
{{ p.public_name || p.plan_name || p.plan_key }}
</div>
</div>
<Tag v-if="p.is_featured" severity="success" value="Popular" />
</div>
<div class="mt-4 text-3xl font-semibold leading-none">
{{ formatBRLFromCents(priceFor(p)) }}
<span class="text-sm font-normal text-[var(--text-color-secondary)]">
/{{ billingInterval === 'month' ? 'mês' : 'ano' }}
</span>
</div>
<div
v-if="billingInterval === 'year'"
class="text-xs text-emerald-500 mt-1 font-medium"
>
Melhor custo-benefício
</div>
<div class="mt-2 text-sm text-[var(--text-color-secondary)] min-h-[44px]">
{{ p.public_description }}
</div>
<Divider class="my-4" />
<ul v-if="p.bullets?.length" class="space-y-2 text-sm">
<li v-for="b in p.bullets" :key="b.id" class="flex items-start gap-2">
<i class="pi pi-check mt-1 text-emerald-500"></i>
<span :class="b.highlight ? 'font-semibold' : 'text-[var(--text-color-secondary)]'">
{{ b.text }}
</span>
</li>
</ul>
<div v-else class="text-sm text-[var(--text-color-secondary)]">Benefícios em breve.</div>
<div class="mt-5">
<Button
label="Começar"
class="w-full"
:severity="p.is_featured ? 'success' : 'secondary'"
:outlined="!p.is_featured"
icon="pi pi-arrow-right"
@click="go(`/auth/signup?plan=${p.plan_key}&interval=${billingInterval}`)"
/>
</div>
</template>
</Card>
<div v-if="billingInterval === 'year'" class="mt-2">
<Tag severity="success" value="Economize até 20%" />
</div>
</div>
<div class="mt-6 text-xs text-[var(--text-color-secondary)]">
Dica: estes planos vêm do painel SaaS (vitrine pública) e podem mapear diretamente para entitlements (FREE/PRO)
sem mexer no código.
<div v-if="loadingPricing" class="mt-8">
<div class="grid grid-cols-12 gap-4">
<div v-for="i in 3" :key="i" class="col-span-12 md:col-span-4">
<div class="rounded-[2rem] border border-[var(--surface-border)] bg-[var(--surface-card)] p-5">
<div class="h-4 w-24 bg-[color-mix(in_srgb,var(--surface-200),transparent_40%)] rounded mb-3 animate-pulse" />
<div class="h-7 w-40 bg-[color-mix(in_srgb,var(--surface-200),transparent_40%)] rounded mb-4 animate-pulse" />
<div class="h-10 w-48 bg-[color-mix(in_srgb,var(--surface-200),transparent_40%)] rounded mb-4 animate-pulse" />
<div class="space-y-2">
<div class="h-3 w-full bg-[color-mix(in_srgb,var(--surface-200),transparent_40%)] rounded animate-pulse" />
<div class="h-3 w-11/12 bg-[color-mix(in_srgb,var(--surface-200),transparent_40%)] rounded animate-pulse" />
<div class="h-3 w-10/12 bg-[color-mix(in_srgb,var(--surface-200),transparent_40%)] rounded animate-pulse" />
</div>
<div class="h-10 w-full bg-[color-mix(in_srgb,var(--surface-200),transparent_40%)] rounded-xl mt-6 animate-pulse" />
</div>
</div>
</div>
</div>
<div v-else class="mt-8">
<div v-if="!pricing.length" class="rounded-[2rem] border border-[var(--surface-border)] bg-[var(--surface-card)] p-6 text-center">
<div class="text-lg font-semibold">Planos em preparação</div>
<div class="text-sm text-[var(--text-color-secondary)] mt-1">
Ainda não itens visíveis na vitrine pública. Publique no painel SaaS para aparecer aqui.
</div>
<div class="mt-4 flex justify-center gap-2 flex-wrap">
<Button label="Entrar" severity="secondary" outlined icon="pi pi-sign-in" @click="go('/auth/login')" />
<Button label="Criar conta" icon="pi pi-bolt" @click="goStart()" />
</div>
</div>
<div v-else class="grid grid-cols-12 gap-4">
<div v-for="p in pricing" :key="p.plan_id" class="col-span-12 md:col-span-4">
<div
class="h-full rounded-[2rem] border border-[var(--surface-border)] bg-[var(--surface-card)] overflow-hidden transition-transform duration-300"
:class="p.is_featured ? 'ring-1 ring-emerald-500/30 md:-translate-y-2 md:scale-[1.02]' : 'hover:-translate-y-1'"
>
<div class="relative p-5">
<div v-if="p.is_featured" class="pointer-events-none absolute inset-0 opacity-70">
<div class="absolute -top-20 -right-24 h-72 w-72 rounded-full bg-emerald-400/10 blur-3xl" />
<div class="absolute -bottom-24 left-10 h-72 w-72 rounded-full bg-indigo-400/10 blur-3xl" />
</div>
<div class="relative">
<div class="flex items-start justify-between gap-3">
<div class="min-w-0">
<div class="text-sm text-[var(--text-color-secondary)]">
{{ p.badge || 'Plano' }}
</div>
<div class="text-xl font-semibold truncate">
{{ p.public_name || p.plan_name || p.plan_key }}
</div>
</div>
<Tag v-if="p.is_featured" severity="success" value="Popular" />
</div>
<div class="mt-4 text-3xl font-semibold leading-none">
{{ formatBRLFromCents(priceFor(p)) }}
<span class="text-sm font-normal text-[var(--text-color-secondary)]">
/{{ billingInterval === 'month' ? 'mês' : 'ano' }}
</span>
</div>
<div v-if="billingInterval === 'year'" class="text-xs text-emerald-500 mt-1 font-medium">
Melhor custo-benefício
</div>
<div class="mt-2 text-sm text-[var(--text-color-secondary)] min-h-[44px] leading-relaxed">
{{ p.public_description || '—' }}
</div>
<Divider class="my-4" />
<ul v-if="p.bullets?.length" class="space-y-2 text-sm">
<li v-for="b in p.bullets" :key="b.id" class="flex items-start gap-2">
<i class="pi pi-check mt-1 text-emerald-500"></i>
<span :class="b.highlight ? 'font-semibold' : 'text-[var(--text-color-secondary)]'">
{{ b.text }}
</span>
</li>
</ul>
<div v-else class="text-sm text-[var(--text-color-secondary)]">
Benefícios em breve.
</div>
<div class="mt-5">
<Button
label="Começar"
class="w-full"
:severity="p.is_featured ? 'success' : 'secondary'"
:outlined="!p.is_featured"
icon="pi pi-arrow-right"
@click="go(`/auth/signup?plan=${encodeURIComponent(p.plan_key)}&interval=${billingInterval}`)"
/>
</div>
<div class="mt-3 text-xs text-[var(--text-color-secondary)]">
Plano vem da vitrine SaaS sem mexer no código.
</div>
</div>
</div>
</div>
</div>
</div>
<div class="mt-6 text-xs text-[var(--text-color-secondary)]">
Dica: esses planos podem mapear diretamente para entitlements (FREE/PRO) e features (plan_features).
</div>
</div>
</section>
<!-- FOOTER -->
<footer class="border-t border-[var(--surface-border)]">
<div
class="mx-auto max-w-7xl px-4 md:px-6 py-8 flex flex-col md:flex-row items-start md:items-center justify-between gap-4"
>
<div class="mx-auto max-w-7xl px-4 md:px-6 py-8 flex flex-col md:flex-row items-start md:items-center justify-between gap-4">
<div>
<div class="font-semibold">{{ brandName }}</div>
<div class="text-xs text-[var(--text-color-secondary)] mt-1">© {{ year }} Todos os direitos reservados.</div>
<div class="text-xs text-[var(--text-color-secondary)] mt-1">
© {{ year }} Todos os direitos reservados.
</div>
</div>
<div class="flex flex-wrap gap-2">
@@ -417,77 +563,31 @@ import { computed, ref, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { supabase } from '@/lib/supabase/client'
import Button from 'primevue/button'
import Card from 'primevue/card'
import Divider from 'primevue/divider'
import Tag from 'primevue/tag'
import Chip from 'primevue/chip'
import Accordion from 'primevue/accordion'
import AccordionTab from 'primevue/accordiontab'
import Avatar from 'primevue/avatar'
import AvatarGroup from 'primevue/avatargroup'
const router = useRouter()
const brandName = 'Psi Quasar' // ajuste para o nome final do produto
const brandName = 'Agência PSI' // ajuste para o nome final do produto
const year = computed(() => new Date().getFullYear())
function go(path) {
function go (path) {
router.push(path)
}
function scrollTo(id) {
function scrollTo (id) {
const el = document.getElementById(id)
if (el) el.scrollIntoView({ behavior: 'smooth', block: 'start' })
}
const featuredPlanKey = computed(() => {
const list = Array.isArray(pricing.value) ? pricing.value : []
const featured = list.find(p => p && p.is_featured && p.is_visible)
return featured?.plan_key || null
})
function goStart() {
if (featuredPlanKey.value) {
router.push(`/auth/signup?plan=${featuredPlanKey.value}&interval=${billingInterval.value}`)
return
}
router.push('/auth/signup')
}
const features = ref([
{
title: 'Agenda inteligente',
desc: 'Configure sua semana, encaixes, bloqueios e visão por dia/semana.',
icon: 'pi pi-calendar'
},
{
title: 'Autoagendamento (PRO)',
desc: 'Página para o paciente confirmar, agendar e reagendar sem fricção.',
icon: 'pi pi-globe',
pro: true
},
{
title: 'Prontuário e sessões',
desc: 'Registro por paciente, histórico por sessão e organização por linha do tempo.',
icon: 'pi pi-file-edit'
},
{
title: 'Financeiro integrado',
desc: 'Receitas, despesas e visão do mês conectadas ao que acontece na agenda.',
icon: 'pi pi-wallet'
},
{
title: 'Pacientes e tags',
desc: 'Segmentação por grupos, etiquetas e filtros práticos para achar rápido.',
icon: 'pi pi-users'
},
{
title: 'Clínica / multi-profissional',
desc: 'Múltiplos profissionais, agendas separadas, papéis e visão gerencial.',
icon: 'pi pi-building'
}
{ title: 'Agenda inteligente', desc: 'Configure semana, encaixes, bloqueios e visão por dia/semana.', icon: 'pi pi-calendar' },
{ title: 'Autoagendamento (PRO)', desc: 'Página para o paciente confirmar, agendar e reagendar sem fricção.', icon: 'pi pi-globe', pro: true },
{ title: 'Prontuário e sessões', desc: 'Registro por paciente, histórico por sessão e linha do tempo.', icon: 'pi pi-file-edit' },
{ title: 'Financeiro integrado', desc: 'Receitas e despesas conectadas ao que acontece na agenda.', icon: 'pi pi-wallet' },
{ title: 'Pacientes e tags', desc: 'Segmentação por grupos, etiquetas e filtros para achar rápido.', icon: 'pi pi-users' },
{ title: 'Clínica / multi-profissional', desc: 'Múltiplos profissionais, papéis, convites e visão gerencial.', icon: 'pi pi-building' }
])
/** PRICING dinâmico do SaaS */
@@ -495,29 +595,82 @@ const billingInterval = ref('year') // 'month' | 'year'
const pricing = ref([])
const loadingPricing = ref(false)
function formatBRLFromCents(cents) {
const featuredPlanKey = computed(() => {
const list = Array.isArray(pricing.value) ? pricing.value : []
const featured = list.find(p => p && p.is_featured && p.is_visible)
return featured?.plan_key || null
})
function goStart () {
if (featuredPlanKey.value) {
router.push(`/auth/signup?plan=${encodeURIComponent(featuredPlanKey.value)}&interval=${billingInterval.value}`)
return
}
router.push('/auth/signup')
}
function formatBRLFromCents (cents) {
if (cents == null) return '—'
const v = Number(cents) / 100
if (!Number.isFinite(v)) return '—'
return v.toLocaleString('pt-BR', { style: 'currency', currency: 'BRL' })
}
function priceFor(p) {
return billingInterval.value === 'year' ? p.yearly_cents : p.monthly_cents
function priceFor (p) {
if (!p) return null
const cents = billingInterval.value === 'year' ? p.yearly_cents : p.monthly_cents
// fallback: se não existir anual, mostra mensal (e vice-versa)
if (cents == null) return billingInterval.value === 'year' ? p.monthly_cents : p.yearly_cents
return cents
}
async function fetchPricing() {
async function fetchPricing () {
loadingPricing.value = true
try {
const { data, error } = await supabase
.from('v_public_pricing')
.select('*')
.eq('is_visible', true)
.order('sort_order', { ascending: true })
const { data, error } = await supabase
.from('v_public_pricing')
.select('*')
.eq('is_visible', true)
.order('sort_order', { ascending: true })
loadingPricing.value = false
if (!error) pricing.value = data || []
if (!error) pricing.value = data || []
} finally {
loadingPricing.value = false
}
}
onMounted(fetchPricing)
</script>
<style scoped>
.hero-grid {
background-image:
linear-gradient(to right, rgba(255,255,255,0.06) 1px, transparent 1px),
linear-gradient(to bottom, rgba(255,255,255,0.06) 1px, transparent 1px);
background-size: 44px 44px;
mask-image: radial-gradient(circle at 30% 20%, black 0%, transparent 65%);
}
.hero-noise {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23n)' opacity='.35'/%3E%3C/svg%3E");
background-size: 180px 180px;
}
.hero-underline {
position: relative;
display: inline-block;
white-space: nowrap;
}
.hero-underline::after {
content: "";
position: absolute;
left: -2%;
right: -2%;
bottom: 0.12em;
height: 0.5em;
background: linear-gradient(90deg, rgba(16,185,129,0.0), rgba(16,185,129,0.22), rgba(99,102,241,0.18), rgba(16,185,129,0.0));
border-radius: 999px;
z-index: -1;
filter: blur(0.2px);
}
</style>