Initial commit
This commit is contained in:
9
.dockerignore
Normal file
9
.dockerignore
Normal file
@@ -0,0 +1,9 @@
|
||||
node_modules
|
||||
.nuxt
|
||||
.output
|
||||
.git
|
||||
.gitignore
|
||||
Dockerfile
|
||||
README.md
|
||||
npm-debug.log
|
||||
.DS_Store
|
||||
24
.gitignore
vendored
Normal file
24
.gitignore
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
# Nuxt dev/build outputs
|
||||
.output
|
||||
.data
|
||||
.nuxt
|
||||
.nitro
|
||||
.cache
|
||||
dist
|
||||
|
||||
# Node dependencies
|
||||
node_modules
|
||||
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
|
||||
# Misc
|
||||
.DS_Store
|
||||
.fleet
|
||||
.idea
|
||||
|
||||
# Local env files
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
22
Dockerfile
Normal file
22
Dockerfile
Normal file
@@ -0,0 +1,22 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
|
||||
FROM node:20-alpine AS build
|
||||
WORKDIR /app
|
||||
|
||||
COPY package.json package-lock.json ./
|
||||
RUN npm ci
|
||||
|
||||
COPY . .
|
||||
RUN npm run build
|
||||
|
||||
FROM node:20-alpine AS runner
|
||||
WORKDIR /app
|
||||
|
||||
ENV NODE_ENV=production
|
||||
ENV PORT=3000
|
||||
|
||||
COPY --from=build /app/.output ./.output
|
||||
|
||||
EXPOSE 3000
|
||||
|
||||
CMD ["node", ".output/server/index.mjs"]
|
||||
75
README.md
Normal file
75
README.md
Normal file
@@ -0,0 +1,75 @@
|
||||
# Nuxt Minimal Starter
|
||||
|
||||
Look at the [Nuxt documentation](https://nuxt.com/docs/getting-started/introduction) to learn more.
|
||||
|
||||
## Setup
|
||||
|
||||
Make sure to install dependencies:
|
||||
|
||||
```bash
|
||||
# npm
|
||||
npm install
|
||||
|
||||
# pnpm
|
||||
pnpm install
|
||||
|
||||
# yarn
|
||||
yarn install
|
||||
|
||||
# bun
|
||||
bun install
|
||||
```
|
||||
|
||||
## Development Server
|
||||
|
||||
Start the development server on `http://localhost:3000`:
|
||||
|
||||
```bash
|
||||
# npm
|
||||
npm run dev
|
||||
|
||||
# pnpm
|
||||
pnpm dev
|
||||
|
||||
# yarn
|
||||
yarn dev
|
||||
|
||||
# bun
|
||||
bun run dev
|
||||
```
|
||||
|
||||
## Production
|
||||
|
||||
Build the application for production:
|
||||
|
||||
```bash
|
||||
# npm
|
||||
npm run build
|
||||
|
||||
# pnpm
|
||||
pnpm build
|
||||
|
||||
# yarn
|
||||
yarn build
|
||||
|
||||
# bun
|
||||
bun run build
|
||||
```
|
||||
|
||||
Locally preview production build:
|
||||
|
||||
```bash
|
||||
# npm
|
||||
npm run preview
|
||||
|
||||
# pnpm
|
||||
pnpm preview
|
||||
|
||||
# yarn
|
||||
yarn preview
|
||||
|
||||
# bun
|
||||
bun run preview
|
||||
```
|
||||
|
||||
Check out the [deployment documentation](https://nuxt.com/docs/getting-started/deployment) for more information.
|
||||
679
app/app.vue
Normal file
679
app/app.vue
Normal file
@@ -0,0 +1,679 @@
|
||||
<template>
|
||||
<div data-theme="acid" class="min-h-screen">
|
||||
<NuxtRouteAnnouncer />
|
||||
<aside
|
||||
class="glass-card fixed left-4 top-4 z-20 flex h-[calc(100vh-2rem)] w-[320px] flex-col rounded-3xl p-5"
|
||||
>
|
||||
<div class="mb-4 flex items-center justify-between">
|
||||
<div>
|
||||
<h2 class="font-display text-lg font-semibold">Навигатор вакансии</h2>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="glass-pane mb-4 rounded-2xl p-4">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="avatar">
|
||||
<div class="w-12 rounded-full bg-gradient-to-br from-fuchsia-400 via-violet-400 to-cyan-400 p-[2px]">
|
||||
<div class="flex h-full w-full items-center justify-center rounded-full bg-base-100">
|
||||
<span class="text-lg">🤖</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-sm font-semibold">Хантик</p>
|
||||
<p class="text-xs text-slate-400">AI-ассистент вакансии</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex-1 space-y-4 overflow-auto pr-1">
|
||||
<div
|
||||
v-for="message in chatHistory"
|
||||
:key="message.id"
|
||||
class="chat"
|
||||
:class="message.from === 'ai' ? 'chat-start' : 'chat-end'"
|
||||
>
|
||||
<div v-if="message.from === 'ai'" class="chat-image avatar">
|
||||
<div class="w-8 rounded-full bg-slate-800">
|
||||
<span class="flex h-full items-center justify-center text-xs">AI</span>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="chat-bubble text-sm"
|
||||
:class="message.from === 'ai' ? 'glass-pane' : 'bg-sky-500/20'"
|
||||
@click="message.step !== undefined && (currentStep = message.step)"
|
||||
>
|
||||
{{ message.text }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-5">
|
||||
<label class="input input-bordered glass-pane flex items-center gap-2 rounded-xl">
|
||||
<span class="text-xs text-slate-400">Вы:</span>
|
||||
<input class="grow text-base" placeholder="Напишите уточнение" />
|
||||
<button class="btn btn-primary btn-lg">Отправить</button>
|
||||
</label>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<main
|
||||
class="min-h-screen w-full px-4 py-6 md:px-8 lg:pl-[360px]"
|
||||
>
|
||||
<section class="glass-card flex min-h-[calc(100vh-3rem)] flex-col gap-6 rounded-3xl p-6">
|
||||
<div class="flex flex-wrap items-center justify-between gap-4">
|
||||
<div>
|
||||
<h1 class="section-title">{{ steps[currentStep].title }}</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="currentStep === 0" class="grid gap-4">
|
||||
<div class="glass-pane rounded-2xl p-5">
|
||||
<div class="flex flex-wrap items-center justify-between gap-3">
|
||||
<div>
|
||||
<h3 class="font-display text-lg font-semibold">Должностная инструкция</h3>
|
||||
</div>
|
||||
<button class="btn btn-lg btn-ghost">Пройти голосовое интервью</button>
|
||||
</div>
|
||||
<div class="mt-5 space-y-4">
|
||||
<label class="form-control">
|
||||
<span class="label-text text-xs uppercase tracking-[0.2em] text-slate-400">Роль</span>
|
||||
<input class="input input-bordered glass-pane" value="Head of Growth" />
|
||||
</label>
|
||||
|
||||
<div class="glass-card rounded-2xl p-4">
|
||||
<div class="flex flex-wrap items-center justify-between gap-3">
|
||||
<div>
|
||||
<p class="text-xs uppercase tracking-[0.2em] text-slate-400">Голосовое интервью</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-4 rounded-2xl border border-sky-300/30 bg-gradient-to-br from-slate-900/70 via-slate-800/40 to-slate-900/80 p-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="space-y-1">
|
||||
<p class="text-sm font-semibold">Сессия интервью</p>
|
||||
</div>
|
||||
<span class="text-xs text-slate-400">00:00</span>
|
||||
</div>
|
||||
<div class="mt-4 grid gap-2">
|
||||
<div class="h-2 w-full overflow-hidden rounded-full bg-slate-800/80">
|
||||
<div class="h-full w-2/5 rounded-full bg-gradient-to-r from-sky-400 to-cyan-300"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-4 flex items-center justify-center">
|
||||
<button class="btn btn-ghost btn-circle btn-lg">▶</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="glass-pane rounded-2xl p-5">
|
||||
<div class="mt-4 space-y-4">
|
||||
<div>
|
||||
<p class="text-sm font-semibold">Общее описание</p>
|
||||
<p class="text-sm text-slate-300">
|
||||
Роль отвечает за системный рост продуктовой воронки от активации до удержания. Основной фокус —
|
||||
масштабируемые эксперименты, которые увеличивают выручку и улучшают ключевые метрики продукта.
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-sm font-semibold">Основные цели</p>
|
||||
<ul class="mt-2 list-disc space-y-1 pl-5 text-sm text-slate-300">
|
||||
<li>Запускать 3–5 гипотез в месяц и доводить их до измеримых результатов.</li>
|
||||
<li>Синхронизировать маркетинг, продукт и продажи вокруг единого North Star.</li>
|
||||
<li>Настроить аналитическую систему, которая показывает, где теряем пользователей.</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-sm font-semibold">Пример рабочего дня</p>
|
||||
<p class="text-sm text-slate-300">
|
||||
Утро — разбор когорт и планирование экспериментов с продактом. Днем — созвоны с командами
|
||||
маркетинга и аналитики, согласование приоритетов. Вечером — подготовка отчета по результатам
|
||||
спринта и формирование гипотез на следующую неделю.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-else-if="currentStep === 1" class="grid gap-4">
|
||||
<div class="glass-pane rounded-2xl p-5">
|
||||
<h3 class="font-display text-lg font-semibold">Видео по сценарию</h3>
|
||||
<ol class="mt-4 list-decimal space-y-2 pl-5 text-sm text-slate-300">
|
||||
<li>Контекст продукта и стадии роста.</li>
|
||||
<li>Что значит успех для роли в первые 90 дней.</li>
|
||||
<li>Как выглядит идеальное взаимодействие в команде.</li>
|
||||
</ol>
|
||||
<div class="mt-5 rounded-2xl border border-dashed border-white/20 p-6 text-center">
|
||||
<p class="text-sm text-slate-300">Перетащите видео (до 3 минут) или загрузите вручную</p>
|
||||
<button class="btn btn-lg btn-ghost mt-3">Загрузить видео</button>
|
||||
</div>
|
||||
<div class="mt-6">
|
||||
<h4 class="text-sm font-semibold uppercase tracking-[0.2em] text-slate-400">Видео команды</h4>
|
||||
<div class="mt-4 grid gap-3 sm:grid-cols-2 xl:grid-cols-3">
|
||||
<div class="glass-card rounded-2xl p-4">
|
||||
<div class="flex items-start justify-between">
|
||||
<div>
|
||||
<p class="text-sm font-semibold">Аня · Product</p>
|
||||
<p class="text-xs text-slate-400">1:12 · Вовлеченность</p>
|
||||
</div>
|
||||
<span class="badge badge-outline">Готово</span>
|
||||
</div>
|
||||
<div class="mt-3 h-24 rounded-xl bg-gradient-to-br from-slate-800/60 to-slate-900/80"></div>
|
||||
</div>
|
||||
<div class="glass-card rounded-2xl p-4">
|
||||
<div class="flex items-start justify-between">
|
||||
<div>
|
||||
<p class="text-sm font-semibold">Сергей · Growth</p>
|
||||
<p class="text-xs text-slate-400">1:48 · Фокус</p>
|
||||
</div>
|
||||
<span class="badge badge-outline">Готово</span>
|
||||
</div>
|
||||
<div class="mt-3 h-24 rounded-xl bg-gradient-to-br from-slate-800/60 to-slate-900/80"></div>
|
||||
</div>
|
||||
<div class="glass-card rounded-2xl p-4">
|
||||
<div class="flex items-start justify-between">
|
||||
<div>
|
||||
<p class="text-sm font-semibold">Кира · Ops</p>
|
||||
<p class="text-xs text-slate-400">0:56 · Структура</p>
|
||||
</div>
|
||||
<span class="badge badge-outline">Готово</span>
|
||||
</div>
|
||||
<div class="mt-3 h-24 rounded-xl bg-gradient-to-br from-slate-800/60 to-slate-900/80"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-else-if="currentStep === 2" class="grid gap-4">
|
||||
<div class="glass-pane rounded-2xl p-5">
|
||||
<div class="flex items-center justify-between">
|
||||
<h3 class="font-display text-lg font-semibold">Анализ видео</h3>
|
||||
</div>
|
||||
<div class="mt-4 flex flex-wrap items-center gap-4">
|
||||
<span class="loading loading-spinner loading-lg text-info"></span>
|
||||
<progress class="progress progress-info w-48" value="72" max="100"></progress>
|
||||
</div>
|
||||
</div>
|
||||
<div class="glass-pane rounded-2xl p-5">
|
||||
<h3 class="font-display text-lg font-semibold">Карточка коллектива</h3>
|
||||
<div class="mt-4 grid gap-4 md:grid-cols-[1.1fr_0.9fr]">
|
||||
<div class="space-y-3">
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-sm">Тональность</span>
|
||||
<div class="badge badge-outline">Дружелюбная + прямолинейная</div>
|
||||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-sm">Частота синков</span>
|
||||
<div class="badge badge-outline">2-3 раза в неделю</div>
|
||||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-sm">Решения</span>
|
||||
<div class="badge badge-outline">Через эксперименты</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="glass-card rounded-2xl p-4">
|
||||
<p class="text-xs uppercase tracking-[0.2em] text-slate-400">Группа</p>
|
||||
<p class="mt-2 text-lg font-semibold">Growth Core</p>
|
||||
<p class="text-xs text-slate-400">4 участника · Ритм 2-3 синка</p>
|
||||
<div class="mt-3 flex flex-wrap gap-2">
|
||||
<span class="badge badge-outline">Темп</span>
|
||||
<span class="badge badge-outline">Эксперименты</span>
|
||||
<span class="badge badge-outline">Прямота</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-else-if="currentStep === 3" class="space-y-4">
|
||||
<div class="glass-pane rounded-2xl p-5">
|
||||
<div class="flex flex-wrap items-center justify-between gap-3">
|
||||
<div>
|
||||
<h3 class="font-display text-lg font-semibold">Рекомендации кандидатов</h3>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="glass-pane rounded-2xl p-5">
|
||||
<div class="flex flex-wrap items-center justify-between gap-3">
|
||||
<div>
|
||||
<h3 class="font-display text-lg font-semibold">Методология совпадений</h3>
|
||||
</div>
|
||||
<div class="tabs tabs-boxed bg-transparent">
|
||||
<a
|
||||
v-for="method in methodologies"
|
||||
:key="method.id"
|
||||
class="tab"
|
||||
:class="currentMethodology === method.id ? 'tab-active' : ''"
|
||||
@click="currentMethodology = method.id"
|
||||
>
|
||||
{{ method.label }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-5 grid gap-4 md:grid-cols-2 xl:grid-cols-3">
|
||||
<div
|
||||
v-for="candidate in currentCandidates"
|
||||
:key="candidate.id"
|
||||
class="glass-card rounded-2xl p-4"
|
||||
>
|
||||
<div class="flex items-start justify-between">
|
||||
<div>
|
||||
<h4 class="font-display text-lg font-semibold">{{ candidate.name }}</h4>
|
||||
<p class="text-sm text-slate-300">{{ candidate.role }}</p>
|
||||
<p class="text-xs text-slate-400">{{ candidate.location }}</p>
|
||||
</div>
|
||||
<div class="radial-progress text-sky-300" :style="`--value:${candidate.match}; --size:3.2rem;`">
|
||||
<span class="text-xs text-slate-100">{{ candidate.match }}%</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-4 grid grid-cols-[90px_1fr] gap-3">
|
||||
<svg viewBox="0 0 100 100" class="h-24 w-24">
|
||||
<circle cx="50" cy="50" r="38" fill="none" stroke="rgba(148, 163, 184, 0.2)" />
|
||||
<circle cx="50" cy="50" r="24" fill="none" stroke="rgba(148, 163, 184, 0.2)" />
|
||||
<circle cx="50" cy="50" r="10" fill="none" stroke="rgba(148, 163, 184, 0.2)" />
|
||||
<g v-for="(axis, index) in radarAxes" :key="axis">
|
||||
<line
|
||||
:x1="50"
|
||||
:y1="50"
|
||||
:x2="axisPoints[index].x"
|
||||
:y2="axisPoints[index].y"
|
||||
stroke="rgba(148, 163, 184, 0.25)"
|
||||
/>
|
||||
</g>
|
||||
<polygon
|
||||
:points="radarPoints(candidate.values)"
|
||||
fill="rgba(125, 211, 252, 0.35)"
|
||||
stroke="rgba(125, 211, 252, 0.9)"
|
||||
stroke-width="1.5"
|
||||
/>
|
||||
</svg>
|
||||
<div class="space-y-2">
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<span v-for="tag in candidate.tags" :key="tag" class="badge badge-outline">
|
||||
{{ tag }}
|
||||
</span>
|
||||
</div>
|
||||
<p class="text-xs text-slate-400">Сильнее всего: {{ candidate.highlight }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-4 flex items-center justify-between">
|
||||
<div class="flex gap-2">
|
||||
<button class="btn btn-lg btn-ghost">Профиль</button>
|
||||
<button class="btn btn-lg btn-ghost">Сравнить</button>
|
||||
</div>
|
||||
<button class="btn btn-lg btn-ghost">Запросить интро</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-else class="space-y-4">
|
||||
<div class="glass-pane rounded-2xl p-5">
|
||||
<div class="flex flex-wrap items-center justify-between gap-3">
|
||||
<div>
|
||||
<h3 class="font-display text-lg font-semibold">Мэтчи и диалоги</h3>
|
||||
</div>
|
||||
<button class="btn btn-lg btn-ghost">Сгенерировать ссылку для встреч</button>
|
||||
</div>
|
||||
<div class="mt-5 space-y-3">
|
||||
<div class="glass-card rounded-2xl p-4">
|
||||
<div class="flex items-center justify-between gap-4">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="avatar">
|
||||
<div class="w-10 rounded-full bg-slate-800">
|
||||
<span class="flex h-full items-center justify-center text-xs">ИС</span>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-sm font-semibold">Ирина Соколова · Head of Growth</p>
|
||||
<p class="text-xs text-slate-400">Последнее: «Готова к короткому созвону» · 2ч назад</p>
|
||||
</div>
|
||||
</div>
|
||||
<span class="badge badge-success badge-outline">Match 91%</span>
|
||||
</div>
|
||||
<div class="mt-3 flex flex-wrap gap-2">
|
||||
<button class="btn btn-lg btn-ghost">Открыть диалог</button>
|
||||
<button class="btn btn-lg btn-ghost">Назначить встречу</button>
|
||||
<button class="btn btn-lg btn-ghost">Предложить слоты</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="glass-card rounded-2xl p-4">
|
||||
<div class="flex items-center justify-between gap-4">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="avatar">
|
||||
<div class="w-10 rounded-full bg-slate-800">
|
||||
<span class="flex h-full items-center justify-center text-xs">КД</span>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-sm font-semibold">Кира Дуброва · Product Growth</p>
|
||||
<p class="text-xs text-slate-400">Последнее: «Вижу слоты на четверг» · 5ч назад</p>
|
||||
</div>
|
||||
</div>
|
||||
<span class="badge badge-success badge-outline">Match 92%</span>
|
||||
</div>
|
||||
<div class="mt-3 flex flex-wrap gap-2">
|
||||
<button class="btn btn-lg btn-ghost">Открыть диалог</button>
|
||||
<button class="btn btn-lg btn-ghost">Назначить встречу</button>
|
||||
<button class="btn btn-lg btn-ghost">Предложить слоты</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="glass-card rounded-2xl p-4">
|
||||
<div class="flex items-center justify-between gap-4">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="avatar">
|
||||
<div class="w-10 rounded-full bg-slate-800">
|
||||
<span class="flex h-full items-center justify-center text-xs">АМ</span>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-sm font-semibold">Алексей Марков · Growth Manager</p>
|
||||
<p class="text-xs text-slate-400">Последнее: «Можно быстро согласовать календарь» · 1д назад</p>
|
||||
</div>
|
||||
</div>
|
||||
<span class="badge badge-success badge-outline">Match 88%</span>
|
||||
</div>
|
||||
<div class="mt-3 flex flex-wrap gap-2">
|
||||
<button class="btn btn-lg btn-ghost">Открыть диалог</button>
|
||||
<button class="btn btn-lg btn-ghost">Назначить встречу</button>
|
||||
<button class="btn btn-lg btn-ghost">Предложить слоты</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-auto flex justify-end">
|
||||
<button class="btn btn-primary btn-lg" @click="nextStep">
|
||||
Далее
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
const steps = [
|
||||
{
|
||||
id: 'requirements',
|
||||
title: 'Функциональные требования к вакансии',
|
||||
},
|
||||
{
|
||||
id: 'video',
|
||||
title: 'Видео по сценарию',
|
||||
},
|
||||
{
|
||||
id: 'fingerprint',
|
||||
title: 'AI-отпечаток коллектива',
|
||||
},
|
||||
{
|
||||
id: 'recommendations',
|
||||
title: 'Рекомендации кандидатов',
|
||||
},
|
||||
{
|
||||
id: 'matches',
|
||||
title: 'Мэтчи и чат',
|
||||
}
|
||||
]
|
||||
|
||||
const currentStep = ref(0)
|
||||
const chatHistory = computed(() => chatByStep.slice(0, currentStep.value + 1).flat())
|
||||
const nextStep = () => {
|
||||
currentStep.value = Math.min(currentStep.value + 1, steps.length - 1)
|
||||
}
|
||||
|
||||
const methodologies = [
|
||||
{ id: 'coaching', label: 'Бизнес-коучинг' },
|
||||
{ id: 'therapy', label: 'Психотерапия' },
|
||||
{ id: 'product', label: 'Продуктовый мэтч' }
|
||||
]
|
||||
|
||||
const candidatesByMethodology = {
|
||||
coaching: [
|
||||
{
|
||||
id: 1,
|
||||
name: 'Ирина Соколова',
|
||||
role: 'Growth Lead · B2B SaaS',
|
||||
location: 'Берлин / Remote',
|
||||
match: 91,
|
||||
values: [88, 62, 79, 71, 66],
|
||||
tags: ['Experimentation', 'B2B', 'Lifecycle'],
|
||||
highlight: 'темп запуска'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'Максим Орлов',
|
||||
role: 'Head of Growth',
|
||||
location: 'Лондон / Remote',
|
||||
match: 87,
|
||||
values: [76, 58, 82, 68, 73],
|
||||
tags: ['SQL', 'Funnels', 'Team Lead'],
|
||||
highlight: 'структура'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: 'Лия Вернер',
|
||||
role: 'Revenue Strategist',
|
||||
location: 'Амстердам',
|
||||
match: 85,
|
||||
values: [72, 74, 65, 70, 81],
|
||||
tags: ['PLG', 'Retention', 'Ops'],
|
||||
highlight: 'удержание'
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: 'Алексей Нестеров',
|
||||
role: 'Growth Manager',
|
||||
location: 'Барселона',
|
||||
match: 83,
|
||||
values: [70, 60, 78, 64, 69],
|
||||
tags: ['Paid', 'Analytics', 'B2B'],
|
||||
highlight: 'скорость экспериментов'
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
name: 'Анна Прайс',
|
||||
role: 'Lifecycle Director',
|
||||
location: 'Прага',
|
||||
match: 81,
|
||||
values: [68, 72, 59, 66, 84],
|
||||
tags: ['CRM', 'Email', 'Segmentation'],
|
||||
highlight: 'процессы'
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
name: 'Давид Клайн',
|
||||
role: 'Growth Partner',
|
||||
location: 'Цюрих',
|
||||
match: 79,
|
||||
values: [66, 64, 73, 62, 70],
|
||||
tags: ['Strategy', 'OKR', 'B2B'],
|
||||
highlight: 'стратегия'
|
||||
}
|
||||
],
|
||||
therapy: [
|
||||
{
|
||||
id: 1,
|
||||
name: 'Юлия Ланская',
|
||||
role: 'Growth Coach',
|
||||
location: 'Варшава',
|
||||
match: 89,
|
||||
values: [70, 86, 60, 75, 78],
|
||||
tags: ['Care', 'Leadership', 'Empathy'],
|
||||
highlight: 'эмпатия'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'Николай Протасов',
|
||||
role: 'Team Therapist',
|
||||
location: 'Мюнхен',
|
||||
match: 86,
|
||||
values: [64, 82, 58, 72, 83],
|
||||
tags: ['Feedback', 'Culture', 'People'],
|
||||
highlight: 'культура'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: 'Леа Хольм',
|
||||
role: 'Org Development',
|
||||
location: 'Копенгаген',
|
||||
match: 84,
|
||||
values: [62, 80, 61, 69, 79],
|
||||
tags: ['Mentoring', 'Soft Skills', 'Sync'],
|
||||
highlight: 'синхронизация'
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: 'Роман Игнатьев',
|
||||
role: 'Leadership Coach',
|
||||
location: 'Рига',
|
||||
match: 82,
|
||||
values: [66, 78, 63, 67, 74],
|
||||
tags: ['Coaching', 'Trust', 'Care'],
|
||||
highlight: 'поддержка'
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
name: 'София Брукс',
|
||||
role: 'Culture Partner',
|
||||
location: 'Лиссабон',
|
||||
match: 80,
|
||||
values: [60, 76, 57, 70, 77],
|
||||
tags: ['Culture', 'Energy', 'Rhythm'],
|
||||
highlight: 'тональность'
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
name: 'Тимур Аббасов',
|
||||
role: 'People Growth',
|
||||
location: 'Стамбул',
|
||||
match: 78,
|
||||
values: [62, 74, 56, 68, 72],
|
||||
tags: ['Well-being', 'Coaching', 'Values'],
|
||||
highlight: 'ценности'
|
||||
}
|
||||
],
|
||||
product: [
|
||||
{
|
||||
id: 1,
|
||||
name: 'Кира Дуброва',
|
||||
role: 'Product Growth',
|
||||
location: 'Стокгольм',
|
||||
match: 92,
|
||||
values: [84, 64, 78, 76, 71],
|
||||
tags: ['PLG', 'Activation', 'Metrics'],
|
||||
highlight: 'продуктовый мэтч'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'Сергей Пак',
|
||||
role: 'Growth PM',
|
||||
location: 'Хельсинки',
|
||||
match: 88,
|
||||
values: [80, 62, 74, 72, 69],
|
||||
tags: ['Experiment', 'North Star', 'Team'],
|
||||
highlight: 'структура роста'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: 'Мария Йенс',
|
||||
role: 'Product Strategy',
|
||||
location: 'Вена',
|
||||
match: 86,
|
||||
values: [78, 66, 69, 74, 70],
|
||||
tags: ['Insight', 'Journey', 'B2B'],
|
||||
highlight: 'customer insight'
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: 'Эмиль Громов',
|
||||
role: 'Lifecycle PM',
|
||||
location: 'Таллин',
|
||||
match: 84,
|
||||
values: [74, 60, 71, 68, 73],
|
||||
tags: ['Retention', 'PLG', 'SQL'],
|
||||
highlight: 'ретеншн'
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
name: 'Дарья Нор',
|
||||
role: 'Growth Analyst',
|
||||
location: 'Краков',
|
||||
match: 82,
|
||||
values: [70, 58, 75, 66, 68],
|
||||
tags: ['Analytics', 'BI', 'Funnels'],
|
||||
highlight: 'данные'
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
name: 'Феликс Штайн',
|
||||
role: 'Growth Ops',
|
||||
location: 'Женева',
|
||||
match: 80,
|
||||
values: [68, 56, 73, 64, 66],
|
||||
tags: ['Process', 'Automation', 'B2B'],
|
||||
highlight: 'операционка'
|
||||
}
|
||||
]
|
||||
} as const
|
||||
|
||||
const currentMethodology = ref<keyof typeof candidatesByMethodology>('coaching')
|
||||
const currentCandidates = computed(() => candidatesByMethodology[currentMethodology.value])
|
||||
|
||||
const radarAxes = ['Стратегия', 'Эмпатия', 'Темп', 'Креатив', 'Стабильность']
|
||||
|
||||
const axisPoints = radarAxes.map((_, index) => {
|
||||
const angle = (-90 + (360 / radarAxes.length) * index) * (Math.PI / 180)
|
||||
const radius = 38
|
||||
return {
|
||||
x: 50 + radius * Math.cos(angle),
|
||||
y: 50 + radius * Math.sin(angle)
|
||||
}
|
||||
})
|
||||
|
||||
const radarPoints = (values: number[]) => {
|
||||
const radius = 38
|
||||
return values
|
||||
.map((value, index) => {
|
||||
const angle = (-90 + (360 / values.length) * index) * (Math.PI / 180)
|
||||
const r = (value / 100) * radius
|
||||
const x = 50 + r * Math.cos(angle)
|
||||
const y = 50 + r * Math.sin(angle)
|
||||
return `${x.toFixed(1)},${y.toFixed(1)}`
|
||||
})
|
||||
.join(' ')
|
||||
}
|
||||
|
||||
const chatByStep = [
|
||||
[
|
||||
{ id: 'r1', from: 'ai', text: 'Опиши требования к вакансии. Заполни форму.', step: 0 },
|
||||
{ id: 'r2', from: 'user', text: 'Ок, заполняю.' }
|
||||
],
|
||||
[
|
||||
{ id: 'v1', from: 'ai', text: 'Запиши видео по сценарию и загрузи.', step: 1 },
|
||||
{ id: 'v2', from: 'user', text: 'Ок, записываю.' }
|
||||
],
|
||||
[
|
||||
{ id: 'f1', from: 'ai', text: 'AI сформировал отпечаток коллектива по видео.', step: 2 },
|
||||
{ id: 'f2', from: 'user', text: 'Смотрю профиль.' }
|
||||
],
|
||||
[
|
||||
{ id: 'p1', from: 'ai', text: 'Рекомендации готовы. Откликов нет.', step: 3 },
|
||||
{ id: 'p2', from: 'user', text: 'Покажи топ.' }
|
||||
],
|
||||
[
|
||||
{ id: 'm1', from: 'ai', text: 'Созданы мэтчи. Можно открыть чат или интро.', step: 4 },
|
||||
{ id: 'm2', from: 'user', text: 'Открываю чат.' }
|
||||
]
|
||||
]
|
||||
</script>
|
||||
78
app/assets/css/main.css
Normal file
78
app/assets/css/main.css
Normal file
@@ -0,0 +1,78 @@
|
||||
@import "tailwindcss";
|
||||
@plugin "daisyui";
|
||||
@plugin "daisyui/theme" {
|
||||
name: "acid";
|
||||
default: true;
|
||||
prefersdark: false;
|
||||
color-scheme: "light";
|
||||
--color-base-100: oklch(98% 0 0);
|
||||
--color-base-200: oklch(95% 0 0);
|
||||
--color-base-300: oklch(91% 0 0);
|
||||
--color-base-content: oklch(0% 0 0);
|
||||
--color-primary: oklch(71.9% 0.357 330.759);
|
||||
--color-primary-content: oklch(14.38% 0.071 330.759);
|
||||
--color-secondary: oklch(73.37% 0.224 48.25);
|
||||
--color-secondary-content: oklch(14.674% 0.044 48.25);
|
||||
--color-accent: oklch(92.78% 0.264 122.962);
|
||||
--color-accent-content: oklch(18.556% 0.052 122.962);
|
||||
--color-neutral: oklch(21.31% 0.128 278.68);
|
||||
--color-neutral-content: oklch(84.262% 0.025 278.68);
|
||||
--color-info: oklch(60.72% 0.227 252.05);
|
||||
--color-info-content: oklch(12.144% 0.045 252.05);
|
||||
--color-success: oklch(85.72% 0.266 158.53);
|
||||
--color-success-content: oklch(17.144% 0.053 158.53);
|
||||
--color-warning: oklch(91.01% 0.212 100.5);
|
||||
--color-warning-content: oklch(18.202% 0.042 100.5);
|
||||
--color-error: oklch(64.84% 0.293 29.349);
|
||||
--color-error-content: oklch(12.968% 0.058 29.349);
|
||||
--radius-selector: 2rem;
|
||||
--radius-field: 2rem;
|
||||
--radius-box: 2rem;
|
||||
--size-selector: 0.3125rem;
|
||||
--size-field: 0.3125rem;
|
||||
--border: 0.5px;
|
||||
--depth: 1;
|
||||
--noise: 0;
|
||||
}
|
||||
|
||||
html,
|
||||
body,
|
||||
#__nuxt {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: "IBM Plex Sans", sans-serif;
|
||||
background:
|
||||
radial-gradient(1200px 600px at 10% 10%, rgba(219, 39, 119, 0.16), transparent 60%),
|
||||
radial-gradient(900px 500px at 90% 15%, rgba(250, 204, 21, 0.2), transparent 55%),
|
||||
radial-gradient(900px 700px at 50% 100%, rgba(74, 222, 128, 0.12), transparent 60%),
|
||||
oklch(98% 0 0);
|
||||
}
|
||||
|
||||
.glass-card {
|
||||
background: rgba(255, 255, 255, 0.7);
|
||||
border: 1px solid rgba(255, 255, 255, 0.6);
|
||||
box-shadow: 0 12px 40px rgba(15, 23, 42, 0.18);
|
||||
backdrop-filter: blur(18px);
|
||||
}
|
||||
|
||||
.glass-pane {
|
||||
background: rgba(255, 255, 255, 0.55);
|
||||
border: 1px solid rgba(255, 255, 255, 0.5);
|
||||
backdrop-filter: blur(18px);
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-family: "Space Grotesk", sans-serif;
|
||||
font-size: 1.5rem;
|
||||
font-weight: 600;
|
||||
letter-spacing: -0.01em;
|
||||
}
|
||||
|
||||
.radar-label {
|
||||
font-size: 10px;
|
||||
letter-spacing: 0.2em;
|
||||
text-transform: uppercase;
|
||||
color: rgb(100 116 139);
|
||||
}
|
||||
11
nuxt.config.ts
Normal file
11
nuxt.config.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
// https://nuxt.com/docs/api/configuration/nuxt-config
|
||||
import tailwindcss from '@tailwindcss/vite'
|
||||
|
||||
export default defineNuxtConfig({
|
||||
compatibilityDate: '2025-07-15',
|
||||
devtools: { enabled: true },
|
||||
css: ['~/assets/css/main.css'],
|
||||
vite: {
|
||||
plugins: [tailwindcss()],
|
||||
},
|
||||
})
|
||||
10635
package-lock.json
generated
Normal file
10635
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
22
package.json
Normal file
22
package.json
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"name": "hr",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "nuxt build",
|
||||
"dev": "nuxt dev",
|
||||
"generate": "nuxt generate",
|
||||
"preview": "nuxt preview",
|
||||
"postinstall": "nuxt prepare"
|
||||
},
|
||||
"dependencies": {
|
||||
"nuxt": "^4.3.1",
|
||||
"vue": "^3.5.28",
|
||||
"vue-router": "^4.6.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/vite": "^4.1.18",
|
||||
"daisyui": "^5.5.18",
|
||||
"tailwindcss": "^4.1.18"
|
||||
}
|
||||
}
|
||||
BIN
public/favicon.ico
Normal file
BIN
public/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
2
public/robots.txt
Normal file
2
public/robots.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
User-Agent: *
|
||||
Disallow:
|
||||
18
tsconfig.json
Normal file
18
tsconfig.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
// https://nuxt.com/docs/guide/concepts/typescript
|
||||
"files": [],
|
||||
"references": [
|
||||
{
|
||||
"path": "./.nuxt/tsconfig.app.json"
|
||||
},
|
||||
{
|
||||
"path": "./.nuxt/tsconfig.server.json"
|
||||
},
|
||||
{
|
||||
"path": "./.nuxt/tsconfig.shared.json"
|
||||
},
|
||||
{
|
||||
"path": "./.nuxt/tsconfig.node.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user