feat: implementa landing page, inclui loading indicator e retorno para home

This commit is contained in:
2026-05-28 18:33:45 -05:00
parent dfe8eb3a80
commit f5375c42ee
6 changed files with 324 additions and 51 deletions

View File

@@ -4,6 +4,11 @@
<div
class="pointer-events-none absolute left-1/2 top-10 h-[420px] w-[min(92vw,760px)] -translate-x-1/2 rounded-[48px] bg-[radial-gradient(circle_at_20%_30%,rgba(232,184,196,0.58),transparent_28%),radial-gradient(circle_at_72%_24%,rgba(167,229,211,0.56),transparent_30%),radial-gradient(circle_at_52%_78%,rgba(244,197,168,0.5),transparent_34%)] blur-2xl"
aria-hidden="true"></div>
<NuxtLink to="/"
class="absolute left-4 top-4 z-10 inline-flex h-10 items-center justify-center gap-2 rounded-full border border-[#d6d3d1] bg-white/80 px-4 text-[14px] font-medium leading-none text-[#0c0a09] shadow-[0_4px_16px_rgba(0,0,0,0.04)] backdrop-blur transition hover:border-[#0c0a09] md:left-8 md:top-8">
<Icon name="mdi:arrow-left" class="text-base" />
Início
</NuxtLink>
<main
class="relative grid w-full max-w-[1120px] grid-cols-1 items-center gap-8 md:grid-cols-[minmax(0,1fr)_minmax(340px,440px)] md:gap-16"

View File

@@ -4,6 +4,11 @@
<div
class="pointer-events-none absolute left-1/2 top-10 h-[420px] w-[min(92vw,760px)] -translate-x-1/2 rounded-[48px] bg-[radial-gradient(circle_at_20%_30%,rgba(167,229,211,0.62),transparent_28%),radial-gradient(circle_at_72%_24%,rgba(244,197,168,0.56),transparent_30%),radial-gradient(circle_at_52%_78%,rgba(200,184,224,0.5),transparent_34%)] blur-2xl"
aria-hidden="true"></div>
<NuxtLink to="/"
class="absolute left-4 top-4 z-10 inline-flex h-10 items-center justify-center gap-2 rounded-full border border-[#d6d3d1] bg-white/80 px-4 text-[14px] font-medium leading-none text-[#0c0a09] shadow-[0_4px_16px_rgba(0,0,0,0.04)] backdrop-blur transition hover:border-[#0c0a09] md:left-8 md:top-8">
<Icon name="mdi:arrow-left" class="text-base" />
Início
</NuxtLink>
<main
class="relative grid w-full max-w-[1120px] grid-cols-1 items-center gap-8 md:grid-cols-[minmax(0,1fr)_minmax(340px,420px)] md:gap-16"
@@ -96,8 +101,8 @@ const toStr = (val) => (val == null ? '' : String(val))
const schema = toTypedSchema(
z.object({
email: z.preprocess(toStr, z.string().min(1, 'Email é obrigatório').email('Email inválido')),
password: z.preprocess(toStr, z.string().min(8, 'A senha deve ter no mínimo 8 caracteres')),
}),
password: z.preprocess(toStr, z.string().min(8, 'A senha deve ter no mínimo 8 caracteres'))
})
)
async function criarConta({ email, password }) {
@@ -105,7 +110,7 @@ async function criarConta({ email, password }) {
try {
await $fetch('/auth/register', {
method: 'POST',
body: { email, password },
body: { email, password }
})
$toast.success('Conta criada com sucesso!', { duration: 8000 })

View File

@@ -4,6 +4,11 @@
<div
class="pointer-events-none absolute left-1/2 top-10 h-[420px] w-[min(92vw,760px)] -translate-x-1/2 rounded-[48px] bg-[radial-gradient(circle_at_20%_30%,rgba(167,229,211,0.62),transparent_28%),radial-gradient(circle_at_72%_24%,rgba(244,197,168,0.56),transparent_30%),radial-gradient(circle_at_52%_78%,rgba(200,184,224,0.5),transparent_34%)] blur-2xl"
aria-hidden="true"></div>
<NuxtLink to="/"
class="absolute left-4 top-4 z-10 inline-flex h-10 items-center justify-center gap-2 rounded-full border border-[#d6d3d1] bg-white/80 px-4 text-[14px] font-medium leading-none text-[#0c0a09] shadow-[0_4px_16px_rgba(0,0,0,0.04)] backdrop-blur transition hover:border-[#0c0a09] md:left-8 md:top-8">
<Icon name="mdi:arrow-left" class="text-base" />
Início
</NuxtLink>
<main
class="relative grid w-full max-w-[1120px] grid-cols-1 items-center gap-8 md:grid-cols-[minmax(0,1fr)_minmax(340px,420px)] md:gap-16"
aria-labelledby="create-account-title">

View File

@@ -4,6 +4,11 @@
<div
class="pointer-events-none absolute left-1/2 top-10 h-[420px] w-[min(92vw,760px)] -translate-x-1/2 rounded-[48px] bg-[radial-gradient(circle_at_20%_30%,rgba(167,229,211,0.62),transparent_28%),radial-gradient(circle_at_72%_24%,rgba(168,200,232,0.56),transparent_30%),radial-gradient(circle_at_52%_78%,rgba(232,184,196,0.5),transparent_34%)] blur-2xl"
aria-hidden="true"></div>
<NuxtLink to="/"
class="absolute left-4 top-4 z-10 inline-flex h-10 items-center justify-center gap-2 rounded-full border border-[#d6d3d1] bg-white/80 px-4 text-[14px] font-medium leading-none text-[#0c0a09] shadow-[0_4px_16px_rgba(0,0,0,0.04)] backdrop-blur transition hover:border-[#0c0a09] md:left-8 md:top-8">
<Icon name="mdi:arrow-left" class="text-base" />
Início
</NuxtLink>
<main
class="relative grid w-full max-w-[1120px] grid-cols-1 items-center gap-8 md:grid-cols-[minmax(0,1fr)_minmax(340px,440px)] md:gap-16"

View File

@@ -1,57 +1,215 @@
<template>
<div
class="relative grid min-h-screen place-items-center overflow-hidden bg-[#f5f5f5] px-4 py-12 font-sans text-[#0c0a09] md:py-24"
>
<div class="relative min-h-screen overflow-hidden bg-[#f5f5f5] px-4 py-8 font-sans text-[#0c0a09] md:px-8 md:py-12">
<div
class="pointer-events-none absolute left-1/2 top-10 h-[420px] w-[min(92vw,760px)] -translate-x-1/2 rounded-[48px] bg-[radial-gradient(circle_at_20%_30%,rgba(167,229,211,0.62),transparent_28%),radial-gradient(circle_at_72%_24%,rgba(244,197,168,0.56),transparent_30%),radial-gradient(circle_at_52%_78%,rgba(200,184,224,0.5),transparent_34%)] blur-2xl"
aria-hidden="true"
></div>
class="pointer-events-none absolute left-1/2 top-8 h-[560px] w-[min(94vw,1040px)] -translate-x-1/2 rounded-[48px] bg-[radial-gradient(circle_at_18%_24%,rgba(167,229,211,0.62),transparent_28%),radial-gradient(circle_at_78%_28%,rgba(244,197,168,0.5),transparent_30%),radial-gradient(circle_at_52%_86%,rgba(168,200,232,0.52),transparent_34%)] blur-2xl"
aria-hidden="true"></div>
<main
class="relative grid w-full max-w-[960px] gap-8 rounded-2xl border border-[#e7e5e4] bg-white p-6 shadow-[0_4px_16px_rgba(0,0,0,0.04)] md:grid-cols-[minmax(0,1fr)_auto] md:items-end md:p-8"
aria-labelledby="landing-title"
>
<section>
<p
class="m-0 inline-flex rounded-full bg-[rgba(167,229,211,0.62)] px-3 py-1 text-xs font-semibold uppercase leading-[1.4] tracking-[0.96px] text-[#0c0a09]"
>
Autenticação
</p>
<h1
id="landing-title"
class="my-5 max-w-[640px] font-serif text-[40px] font-light leading-[1.05] tracking-[-1.2px] text-[#0c0a09] md:text-[64px] md:tracking-[-1.92px]"
>
Acesse sua conta
</h1>
<p
class="m-0 max-w-[520px] text-base font-normal leading-6 tracking-[0.16px] text-[#4e4e4e]"
>
Entre para ver sua área autenticada ou crie uma conta nova com email e senha.
</p>
<main class="relative mx-auto grid w-full max-w-[1180px] gap-10" aria-labelledby="landing-title">
<nav class="flex items-center justify-between gap-4" aria-label="Acesso publico">
<NuxtLink to="/"
class="flex min-w-0 items-center gap-3 text-base font-semibold tracking-[0.16px] text-[#0c0a09]">
<span class="inline-flex h-11 w-11 shrink-0 items-center justify-center rounded-xl bg-[#292524] text-white">
<Icon name="mdi:gamepad-variant" class="text-xl" />
</span>
<span>GameVerse</span>
</NuxtLink>
<div class="flex shrink-0 items-center gap-2">
<NuxtLink :to="hasToken ? '/home' : '/login'"
class="inline-flex h-10 items-center justify-center rounded-full bg-[#292524] px-5 text-[15px] font-medium leading-none text-white transition hover:bg-[#0c0a09] active:bg-[#0c0a09]">
{{ hasToken ? 'Ir para home' : 'Entrar' }}
</NuxtLink>
<NuxtLink to="/criar-conta"
class="hidden h-10 items-center justify-center rounded-full border border-[#d6d3d1] bg-white/70 px-5 text-[15px] font-medium leading-none text-[#0c0a09] transition hover:border-[#0c0a09] sm:inline-flex">
Criar conta
</NuxtLink>
</div>
</nav>
<section class="grid gap-8 py-6 lg:grid-cols-[minmax(0,1.05fr)_minmax(360px,0.95fr)] lg:items-center lg:py-10">
<div class="grid gap-6">
<div>
<p
class="m-0 inline-flex rounded-full bg-[rgba(167,229,211,0.62)] px-3 py-1 text-xs font-semibold uppercase leading-[1.4] tracking-[0.96px] text-[#0c0a09]">
Sistema distribuído gamer
</p>
<h1 id="landing-title"
class="my-5 max-w-[760px] break-words font-serif text-[44px] font-light leading-[1.02] tracking-[-1.2px] text-[#0c0a09] md:text-[72px] md:tracking-[-1.92px]">
GameVerse conecta sua jornada gamer em um lugar.
</h1>
<p
class="m-0 max-w-[620px] text-base font-normal leading-6 tracking-[0.16px] text-[#4e4e4e] md:text-lg md:leading-7">
Uma plataforma integrada para descobrir jogos, acompanhar rankings, salvar favoritos,
montar perfil gamer e gerenciar gift cards com acesso autenticado.
</p>
</div>
<div class="flex flex-col gap-3 sm:flex-row">
<NuxtLink :to="hasToken ? '/home' : '/login'"
class="inline-flex h-11 items-center justify-center gap-2 rounded-full bg-[#292524] px-6 text-[15px] font-medium leading-none text-white transition hover:bg-[#0c0a09] active:bg-[#0c0a09]">
{{ hasToken ? 'Ir para minha home' : 'Entrar na plataforma' }}
<Icon name="mdi:arrow-right" class="text-base" />
</NuxtLink>
<NuxtLink to="/criar-conta"
class="inline-flex h-11 items-center justify-center rounded-full border border-[#d6d3d1] bg-white/70 px-6 text-[15px] font-medium leading-none text-[#0c0a09] transition hover:border-[#0c0a09]">
Criar conta
</NuxtLink>
</div>
<div class="grid gap-3 sm:grid-cols-3">
<div v-for="metric in metrics" :key="metric.label"
class="rounded-2xl border border-[#e7e5e4] bg-white/80 p-4">
<strong class="block text-2xl font-medium leading-tight tracking-[0.16px] text-[#0c0a09]">
{{ metric.value }}
</strong>
<span class="mt-1 block text-xs font-semibold uppercase leading-[1.4] tracking-[0.96px] text-[#777169]">
{{ metric.label }}
</span>
</div>
</div>
</div>
<aside
class="grid gap-4 rounded-2xl border border-[#e7e5e4] bg-white p-5 shadow-[0_12px_34px_rgba(0,0,0,0.08)] md:p-6">
<div class="flex items-center justify-between gap-4">
<div>
<span
class="inline-flex min-h-6 items-center rounded-full bg-[rgba(244,197,168,0.56)] px-2.5 py-1 text-xs font-semibold uppercase leading-none tracking-[0.96px] text-[#0c0a09]">
Prévia
</span>
<h2 class="mt-4 font-serif text-3xl font-light leading-[1.13] tracking-[-0.32px] text-[#0c0a09]">
Painel integrado
</h2>
</div>
<span class="inline-flex h-11 w-11 items-center justify-center rounded-xl bg-[#292524] text-white">
<Icon name="mdi:view-dashboard-outline" class="text-xl" />
</span>
</div>
<div class="grid gap-3 sm:grid-cols-2">
<article v-for="preview in dashboardPreview" :key="preview.label"
class="grid min-h-[138px] content-between rounded-xl border border-[#e7e5e4] bg-[#fafafa] p-4">
<div class="flex items-center justify-between gap-3">
<span class="inline-flex h-10 w-10 items-center justify-center rounded-xl" :class="preview.accent">
<Icon :name="preview.icon" class="text-xl text-[#0c0a09]" />
</span>
<span class="text-xs font-semibold uppercase tracking-[0.96px] text-[#777169]">
{{ preview.badge }}
</span>
</div>
<div class="grid gap-1">
<strong class="text-xl font-medium leading-[1.18] tracking-[0.16px] text-[#0c0a09]">
{{ preview.value }}
</strong>
<p class="m-0 text-[13px] leading-[1.5] tracking-[0.13px] text-[#777169]">
{{ preview.label }}
</p>
</div>
</article>
</div>
<div class="rounded-xl border border-[#e7e5e4] bg-white p-4">
<div class="flex items-center justify-between gap-3">
<div>
<p class="m-0 text-xs font-semibold uppercase leading-[1.4] tracking-[0.96px] text-[#777169]">
Ranking semanal
</p>
<p class="m-0 mt-1 text-base font-medium leading-6 tracking-[0.16px] text-[#0c0a09]">
Counter-Strike 2 liderando a semana
</p>
</div>
<span
class="inline-flex h-9 w-9 items-center justify-center rounded-full bg-[#292524] text-[15px] font-medium text-white">
1
</span>
</div>
</div>
</aside>
</section>
<nav class="flex flex-col gap-3 sm:flex-row md:flex-col" aria-label="Acesso a conta">
<NuxtLink
v-if="hasToken"
to="/home"
class="inline-flex h-10 items-center justify-center rounded-full bg-[#292524] px-5 text-[15px] font-medium leading-none text-white transition hover:bg-[#0c0a09] active:bg-[#0c0a09]"
>
Ir para home
<section class="grid gap-5" aria-labelledby="features-title">
<div>
<p
class="m-0 inline-flex rounded-full bg-[rgba(168,200,232,0.56)] px-3 py-1 text-xs font-semibold uppercase leading-[1.4] tracking-[0.96px] text-[#0c0a09]">
Recursos
</p>
<h2 id="features-title"
class="mt-4 font-serif text-3xl font-light leading-[1.13] tracking-[-0.32px] text-[#0c0a09] md:text-[40px]">
O que você encontra
</h2>
</div>
<div class="grid gap-4 md:grid-cols-2 xl:grid-cols-3">
<article v-for="feature in features" :key="feature.title"
class="grid min-h-[180px] content-between rounded-2xl border border-[#e7e5e4] bg-white p-5 shadow-[0_4px_16px_rgba(0,0,0,0.04)]">
<div class="grid gap-4">
<span class="inline-flex h-11 w-11 items-center justify-center rounded-xl" :class="feature.accent">
<Icon :name="feature.icon" class="text-xl text-[#0c0a09]" />
</span>
<div>
<h3 class="m-0 text-lg font-medium leading-6 tracking-[0.16px] text-[#0c0a09]">
{{ feature.title }}
</h3>
<p class="m-0 mt-2 text-[15px] leading-[1.47] tracking-[0.15px] text-[#777169]">
{{ feature.description }}
</p>
</div>
</div>
</article>
</div>
</section>
<section
class="grid gap-6 rounded-2xl border border-[#e7e5e4] bg-white p-6 shadow-[0_4px_16px_rgba(0,0,0,0.04)] md:grid-cols-[minmax(0,1fr)_minmax(260px,360px)] md:p-8"
aria-labelledby="architecture-title">
<div>
<p
class="m-0 inline-flex rounded-full bg-[rgba(200,184,224,0.5)] px-3 py-1 text-xs font-semibold uppercase leading-[1.4] tracking-[0.96px] text-[#0c0a09]">
Arquitetura distribuída
</p>
<h2 id="architecture-title"
class="mt-4 font-serif text-3xl font-light leading-[1.13] tracking-[-0.32px] text-[#0c0a09] md:text-[40px]">
Microsserviços conectados por uma experiência única.
</h2>
<p class="m-0 mt-4 max-w-[720px] text-base leading-6 tracking-[0.16px] text-[#4e4e4e]">
O GameVerse reúne serviços independentes para autenticação, catálogo, ranking,
favoritos, perfil gamer e gift cards. Cada área protege suas operações com token e
entrega uma parte do ecossistema.
</p>
</div>
<div class="grid gap-3">
<div v-for="service in services" :key="service"
class="flex items-center gap-3 rounded-xl border border-[#e7e5e4] bg-[#fafafa] p-3">
<span class="h-2 w-2 shrink-0 rounded-full bg-emerald-500"></span>
<span class="text-[15px] font-medium leading-[1.47] tracking-[0.15px] text-[#292524]">
{{ service }}
</span>
</div>
</div>
</section>
<section
class="flex flex-col gap-4 rounded-2xl border border-[#e7e5e4] bg-[#292524] p-6 text-white md:flex-row md:items-center md:justify-between md:p-8"
aria-label="Acesso autenticado">
<div>
<p class="m-0 text-xs font-semibold uppercase leading-[1.4] tracking-[0.96px] text-white/70">
Pronto para entrar
</p>
<h2 class="m-0 mt-2 font-serif text-3xl font-light leading-[1.13] tracking-[-0.32px]">
Sistema de validação de token funcional
</h2>
<p class="m-0 mt-3 text-[15px] leading-[1.47] tracking-[0.15px] text-white/70">
Acesse sua área autenticada e navegue por todos os módulos do GameVerse.
</p>
</div>
<NuxtLink :to="hasToken ? '/home' : '/login'"
class="inline-flex h-11 shrink-0 items-center justify-center gap-2 rounded-full bg-white px-6 text-[15px] font-medium leading-none text-[#0c0a09] transition hover:bg-[#f5f5f5]">
{{ hasToken ? 'Abrir dashboard' : 'Fazer login' }}
<Icon name="mdi:arrow-right" class="text-base" />
</NuxtLink>
<NuxtLink
v-else
to="/login"
class="inline-flex h-10 items-center justify-center rounded-full bg-[#292524] px-5 text-[15px] font-medium leading-none text-white transition hover:bg-[#0c0a09] active:bg-[#0c0a09]"
>
Entrar na conta
</NuxtLink>
<NuxtLink
to="/criar-conta"
class="inline-flex h-10 items-center justify-center rounded-full border border-[#d6d3d1] bg-transparent px-5 text-[15px] font-medium leading-none text-[#0c0a09] transition hover:border-[#0c0a09]"
>
Criar conta
</NuxtLink>
</nav>
</section>
</main>
</div>
</template>
@@ -60,4 +218,98 @@
const token = useCookie('token')
const hasToken = computed(() => Boolean(token.value))
const metrics = [
{
value: '6',
label: 'Módulos integrados'
},
{
value: 'JWT',
label: 'Sessão validada'
},
{
value: '24h',
label: 'Experiência gamer'
}
]
const dashboardPreview = [
{
badge: 'Ranking',
value: '#1 CS2',
label: 'Jogos mais fortes da semana',
icon: 'mdi:trophy-outline',
accent: 'bg-[rgba(244,197,168,0.56)]'
},
{
badge: 'Favoritos',
value: '3 salvos',
label: 'Lista pessoal de jogos',
icon: 'mdi:heart-outline',
accent: 'bg-[rgba(232,184,196,0.55)]'
},
{
badge: 'Catálogo',
value: 'Jogos ativos',
label: 'Busca e gerenciamento',
icon: 'mdi:controller-classic-outline',
accent: 'bg-[rgba(196,184,232,0.45)]'
},
{
badge: 'Carteira',
value: 'Gift cards',
label: 'Criação, saldo e resgate',
icon: 'mdi:gift-outline',
accent: 'bg-[rgba(167,229,211,0.62)]'
}
]
const features = [
{
title: 'Ranking de jogos',
description: 'Acompanhe jogos por semana, mês, ano, plataforma e volume de jogadores ativos.',
icon: 'mdi:trophy-outline',
accent: 'bg-[rgba(244,197,168,0.56)]'
},
{
title: 'Perfil gamer',
description: 'Cadastre nickname, bio, país, plataformas favoritas e jogos preferidos.',
icon: 'mdi:account-star-outline',
accent: 'bg-[rgba(232,184,196,0.55)]'
},
{
title: 'Gift cards',
description: 'Crie cartões, consulte saldo e resgate valores em uma área autenticada.',
icon: 'mdi:gift-outline',
accent: 'bg-[rgba(167,229,211,0.62)]'
},
{
title: 'Favoritos',
description: 'Salve jogos do ranking ou catálogo e mantenha sua lista sempre à mão.',
icon: 'mdi:heart-outline',
accent: 'bg-[rgba(232,184,196,0.45)]'
},
{
title: 'Catálogo',
description: 'Explore, filtre, detalhe e gerencie jogos com gêneros, plataformas e imagens.',
icon: 'mdi:controller-classic-outline',
accent: 'bg-[rgba(196,184,232,0.45)]'
},
{
title: 'Dados da conta',
description: 'Consulte informações da sessão autenticada, usuário e token de acesso.',
icon: 'mdi:account-circle-outline',
accent: 'bg-[rgba(168,200,232,0.56)]'
}
]
const services = [
'Autenticação com token JWT',
'Ranking de jogos',
'Catálogo de jogos',
'Lista de favoritos',
'Perfil gamer',
'Gift cards'
]
</script>