feat: implement targetologist dashboard module with competitor analysis and campaign management tools
This commit is contained in:
+2
-1
@@ -3,5 +3,6 @@
|
||||
"allow": [
|
||||
"Bash(npm run *)"
|
||||
]
|
||||
}
|
||||
},
|
||||
"$version": 3
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Bash(npm run *)"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -430,8 +430,8 @@ export default {
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="kai-logo">KAI</div>
|
||||
<div>
|
||||
<h1 class="kai-section-title">Создание ИИ-Брифа</h1>
|
||||
<p class="kai-section-sub">KAI Prime · Intelligent Onboarding</p>
|
||||
<h1 class="kai-section-title">Бриф маркетингового анализа</h1>
|
||||
<p class="kai-section-sub">KAI Intelligence · Сбор данных для стратегии</p>
|
||||
</div>
|
||||
</div>
|
||||
<Button icon="pi pi-times" text rounded size="small" class="text-surface-500" @click="$router.push('/marketing-analysis')" />
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script setup>
|
||||
import SectionWrapper from './SectionWrapper.vue';
|
||||
import { computed, ref } from 'vue';
|
||||
import SectionWrapper from './SectionWrapper.vue';
|
||||
|
||||
const props = defineProps({ id: String, data: [Array, Object] });
|
||||
|
||||
@@ -26,61 +26,72 @@ const platColor = (p) => PLAT_COLORS[p] || '#808080';
|
||||
|
||||
const sorted = computed(() => [...normalizedData.value].sort((a, b) => b.followersVal - a.followersVal));
|
||||
|
||||
// Scatter data
|
||||
const scatter = computed(() => normalizedData.value.map((c, i) => ({
|
||||
x: c.postsVal || 0,
|
||||
y: c.erVal || 0,
|
||||
r: Math.max(Math.sqrt((c.followersVal || 0) / 1000) * 5, 4),
|
||||
name: c.name,
|
||||
color: platColor(c.platform)
|
||||
})));
|
||||
const maxX = computed(() => Math.max(Math.max(...scatter.value.map(d => d.x)) * 1.1, 10));
|
||||
const maxY = computed(() => Math.max(Math.max(...scatter.value.map(d => d.y)) * 1.2, 5));
|
||||
// New Summary Metrics
|
||||
const topFollowers = computed(() => sorted.value.length > 0 ? sorted.value[0] : null);
|
||||
const topEr = computed(() => {
|
||||
if (!normalizedData.value.length) return null;
|
||||
return [...normalizedData.value].sort((a, b) => b.erVal - a.erVal)[0];
|
||||
});
|
||||
const avgPosts = computed(() => {
|
||||
if (!normalizedData.value.length) return 0;
|
||||
const sum = normalizedData.value.reduce((acc, c) => acc + c.postsVal, 0);
|
||||
return Math.round(sum / normalizedData.value.length);
|
||||
});
|
||||
const avgRating = computed(() => {
|
||||
if (!normalizedData.value.length) return 0;
|
||||
const sum = normalizedData.value.reduce((acc, c) => acc + c.ratingVal, 0);
|
||||
return (sum / normalizedData.value.length).toFixed(1);
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<SectionWrapper :id="id" title="Карта конкурентов" subtitle="Позиционирование игроков: Активность vs Вовлечённость" num="3">
|
||||
<SectionWrapper :id="id" title="Анализ конкурентов" subtitle="Главные игроки на рынке и их показатели" num="3">
|
||||
|
||||
<!-- Scatter plot -->
|
||||
<div class="competitor-plot-card">
|
||||
<div class="plot-header">
|
||||
<span class="plot-axis-label">Активность (посты/мес) ➔</span>
|
||||
<span class="plot-axis-label vertical">Вовлечённость (ER %) ↑</span>
|
||||
<i class="pi pi-info-circle kai-term-icon" v-tooltip.right="'Лидеры (справа вверху) делают много постов и получают высокий отклик.'"></i>
|
||||
<!-- Summary Cards -->
|
||||
<div class="summary-cards" v-if="normalizedData.length > 0">
|
||||
<div class="summary-card">
|
||||
<div class="card-icon blue"><i class="pi pi-users" /></div>
|
||||
<div class="card-info">
|
||||
<span class="card-label">Лидер рынка</span>
|
||||
<span class="card-value">{{ topFollowers?.name || '—' }}</span>
|
||||
<span class="card-desc">{{ fmtNum(topFollowers?.followersVal) }} подписчиков</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="plot-leader-zone">ЛИДЕРЫ</div>
|
||||
<div class="summary-card">
|
||||
<div class="card-icon green"><i class="pi pi-heart-fill" /></div>
|
||||
<div class="card-info">
|
||||
<span class="card-label">Самый вовлекающий (ER)</span>
|
||||
<span class="card-value">{{ topEr?.name || '—' }}</span>
|
||||
<span class="card-desc" :style="{ color: topEr ? erColor(topEr.erVal) : '' }">{{ fmtPct(topEr?.erVal) }} ER</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="plot-container">
|
||||
<svg class="plot-svg" :viewBox="`0 0 500 220`" preserveAspectRatio="none">
|
||||
<!-- Grid -->
|
||||
<line x1="50" y1="10" x2="50" y2="200" class="plot-grid-line"/>
|
||||
<line x1="50" y1="200" x2="490" y2="200" class="plot-grid-line"/>
|
||||
|
||||
<!-- Leader quadrant -->
|
||||
<rect x="270" y="10" width="220" height="100" class="plot-leader-rect" rx="8"/>
|
||||
|
||||
<!-- Data points -->
|
||||
<g v-for="(pt, i) in scatter" :key="i" class="plot-point-group">
|
||||
<circle
|
||||
:cx="50 + (pt.x / maxX) * 440"
|
||||
:cy="195 - (pt.y / maxY) * 185"
|
||||
:r="Math.max(pt.r, 5)"
|
||||
:fill="pt.color"
|
||||
class="plot-point"
|
||||
/>
|
||||
<text
|
||||
:x="50 + (pt.x / maxX) * 440"
|
||||
:y="195 - (pt.y / maxY) * 185 - Math.max(pt.r, 5) - 6"
|
||||
class="plot-point-text"
|
||||
>{{ pt.name }}</text>
|
||||
</g>
|
||||
</svg>
|
||||
<div class="summary-card">
|
||||
<div class="card-icon yellow"><i class="pi pi-calendar" /></div>
|
||||
<div class="card-info">
|
||||
<span class="card-label">Средняя активность</span>
|
||||
<span class="card-value">{{ avgPosts }}</span>
|
||||
<span class="card-desc">постов в месяц</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="summary-card">
|
||||
<div class="card-icon purple"><i class="pi pi-star-fill" /></div>
|
||||
<div class="card-info">
|
||||
<span class="card-label">Средний рейтинг</span>
|
||||
<span class="card-value text-yellow-500">★ {{ avgRating }}</span>
|
||||
<span class="card-desc">по отзывам клиентов</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Table -->
|
||||
<div class="table-container">
|
||||
<div class="table-header">
|
||||
<h4 class="table-title">Подробная таблица</h4>
|
||||
<span class="table-count">{{ sorted.length }} конкурентов</span>
|
||||
</div>
|
||||
<table class="kai-table">
|
||||
<thead>
|
||||
<tr>
|
||||
@@ -96,8 +107,13 @@ const maxY = computed(() => Math.max(Math.max(...scatter.value.map(d => d.y)) *
|
||||
</thead>
|
||||
<tbody>
|
||||
<template v-for="(c, i) in sorted" :key="c.name">
|
||||
<tr :class="{ 'top-row': i < 3 }" :style="i < 3 ? { '--top-color': ['#F59E0B','#939393','#CD7F32'][i] } : {}">
|
||||
<td class="row-num">{{ String(i+1).padStart(2,'0') }}</td>
|
||||
<tr :class="{ 'top-row': i < 3 }" :style="i < 3 ? { '--top-color': ['#F59E0B','#9CA3AF','#D97706'][i] } : {}">
|
||||
<td class="row-num">
|
||||
<span v-if="i === 0" class="medal gold">🥇</span>
|
||||
<span v-else-if="i === 1" class="medal silver">🥈</span>
|
||||
<span v-else-if="i === 2" class="medal bronze">🥉</span>
|
||||
<span v-else>{{ String(i+1).padStart(2,'0') }}</span>
|
||||
</td>
|
||||
<td>
|
||||
<div class="brand-cell">
|
||||
<div class="brand-avatar" :style="{ background: platColor(c.platform) + '20', color: platColor(c.platform), borderColor: platColor(c.platform) + '40' }">
|
||||
@@ -117,23 +133,35 @@ const maxY = computed(() => Math.max(Math.max(...scatter.value.map(d => d.y)) *
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
|
||||
<!-- Expanded content -->
|
||||
<tr v-if="expanded === c.name" class="expanded-row">
|
||||
<td colspan="8">
|
||||
<div class="expand-content">
|
||||
<div class="sw-grid">
|
||||
<div class="sw-col">
|
||||
<div class="sw-title green">Сильные стороны</div>
|
||||
<div class="sw-title green">
|
||||
<i class="pi pi-check-circle"></i>
|
||||
Сильные стороны
|
||||
</div>
|
||||
<ul class="sw-list">
|
||||
<li v-for="(s, si) in (c.strengths||[])" :key="si">{{ s }}</li>
|
||||
<li v-for="(s, si) in (c.strengths||[])" :key="si">
|
||||
<span class="sw-icon green">+</span>
|
||||
{{ s }}
|
||||
</li>
|
||||
<li v-if="!c.strengths?.length" class="empty-sw">Нет данных</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="sw-col">
|
||||
<div class="sw-title red">Слабые стороны</div>
|
||||
<div class="sw-title red">
|
||||
<i class="pi pi-times-circle"></i>
|
||||
Слабые стороны
|
||||
</div>
|
||||
<ul class="sw-list">
|
||||
<li v-for="(w, wi) in (c.weaknesses||[])" :key="wi">{{ w }}</li>
|
||||
<li v-for="(w, wi) in (c.weaknesses||[])" :key="wi">
|
||||
<span class="sw-icon red">−</span>
|
||||
{{ w }}
|
||||
</li>
|
||||
<li v-if="!c.weaknesses?.length" class="empty-sw">Нет данных</li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -142,7 +170,12 @@ const maxY = computed(() => Math.max(Math.max(...scatter.value.map(d => d.y)) *
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
<tr v-if="!sorted.length"><td colspan="8" class="empty-table">Нет данных</td></tr>
|
||||
<tr v-if="!sorted.length"><td colspan="8" class="empty-table">
|
||||
<div class="empty-state">
|
||||
<i class="pi pi-search text-4xl mb-3"></i>
|
||||
<p>Данные о конкурентах не найдены</p>
|
||||
</div>
|
||||
</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@@ -150,80 +183,78 @@ const maxY = computed(() => Math.max(Math.max(...scatter.value.map(d => d.y)) *
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.competitor-plot-card {
|
||||
/* ==================== SUMMARY CARDS ==================== */
|
||||
.summary-cards {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
||||
gap: 16px;
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
.summary-card {
|
||||
background: var(--kai-bg2);
|
||||
border: 1px solid var(--kai-border);
|
||||
border-radius: 20px;
|
||||
padding: 24px;
|
||||
margin-bottom: 32px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.plot-header {
|
||||
border-radius: 16px;
|
||||
padding: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
margin-bottom: 24px;
|
||||
gap: 16px;
|
||||
transition: transform 0.3s ease, box-shadow 0.3s ease;
|
||||
}
|
||||
|
||||
.plot-axis-label {
|
||||
font-size: 9.5px;
|
||||
font-weight: 800;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.1em;
|
||||
color: var(--kai-txt3);
|
||||
.summary-card:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.plot-leader-zone {
|
||||
position: absolute;
|
||||
top: 60px;
|
||||
right: 40px;
|
||||
font-family: 'Unbounded', sans-serif;
|
||||
font-size: 10px;
|
||||
font-weight: 800;
|
||||
color: var(--kai-blue);
|
||||
opacity: 0.4;
|
||||
letter-spacing: 0.2em;
|
||||
pointer-events: none;
|
||||
html[data-theme='light'] .summary-card:hover {
|
||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.plot-container {
|
||||
padding-left: 20px;
|
||||
.card-icon {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 20px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.plot-grid-line {
|
||||
stroke: var(--kai-border);
|
||||
stroke-width: 1;
|
||||
.card-icon.blue { background: rgba(45,123,255,0.1); color: #2D7BFF; }
|
||||
.card-icon.green { background: rgba(16,185,129,0.1); color: #10B981; }
|
||||
.card-icon.yellow { background: rgba(245,158,11,0.1); color: #F59E0B; }
|
||||
.card-icon.purple { background: rgba(139,92,246,0.1); color: #8B5CF6; }
|
||||
|
||||
.card-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.plot-leader-rect {
|
||||
fill: rgba(var(--kai-blue-rgb), 0.05);
|
||||
}
|
||||
|
||||
.plot-point {
|
||||
fill-opacity: 0.8;
|
||||
stroke: rgba(255,255,255,0.2);
|
||||
stroke-width: 1;
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
.plot-point-group:hover .plot-point {
|
||||
fill-opacity: 1;
|
||||
r: 10;
|
||||
stroke-width: 2;
|
||||
filter: drop-shadow(0 0 8px currentColor);
|
||||
}
|
||||
|
||||
.plot-point-text {
|
||||
font-size: 8px;
|
||||
.card-label {
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
fill: var(--kai-txt2);
|
||||
text-anchor: middle;
|
||||
pointer-events: none;
|
||||
text-transform: uppercase;
|
||||
color: var(--kai-txt3);
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
|
||||
/* Table Stylings */
|
||||
.card-value {
|
||||
font-family: 'Unbounded', sans-serif;
|
||||
font-size: 16px;
|
||||
font-weight: 800;
|
||||
color: var(--kai-txt);
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.card-desc {
|
||||
font-size: 11px;
|
||||
color: var(--kai-txt2);
|
||||
}
|
||||
|
||||
/* ==================== TABLE ==================== */
|
||||
.table-container {
|
||||
border-radius: 16px;
|
||||
overflow: hidden;
|
||||
@@ -231,6 +262,31 @@ const maxY = computed(() => Math.max(Math.max(...scatter.value.map(d => d.y)) *
|
||||
border: 1px solid var(--kai-border);
|
||||
}
|
||||
|
||||
.table-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 16px 20px;
|
||||
border-bottom: 1px solid var(--kai-border);
|
||||
}
|
||||
|
||||
.table-title {
|
||||
font-family: 'Unbounded', sans-serif;
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
color: var(--kai-txt);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.table-count {
|
||||
font-size: 11px;
|
||||
color: var(--kai-txt3);
|
||||
background: var(--kai-border);
|
||||
padding: 4px 10px;
|
||||
border-radius: 20px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.top-row {
|
||||
background: linear-gradient(to right, rgba(var(--kai-blue-rgb), 0.03), transparent);
|
||||
border-left: 3px solid var(--top-color);
|
||||
@@ -241,7 +297,12 @@ const maxY = computed(() => Math.max(Math.max(...scatter.value.map(d => d.y)) *
|
||||
font-size: 9px;
|
||||
font-weight: 800;
|
||||
color: var(--kai-txt3);
|
||||
opacity: 0.6;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.medal {
|
||||
font-size: 16px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.brand-cell {
|
||||
@@ -304,6 +365,9 @@ const maxY = computed(() => Math.max(Math.max(...scatter.value.map(d => d.y)) *
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.1em;
|
||||
margin-bottom: 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.sw-title.green { color: var(--kai-green); }
|
||||
@@ -315,7 +379,7 @@ const maxY = computed(() => Math.max(Math.max(...scatter.value.map(d => d.y)) *
|
||||
margin: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.sw-list li {
|
||||
@@ -326,13 +390,16 @@ const maxY = computed(() => Math.max(Math.max(...scatter.value.map(d => d.y)) *
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.sw-list li::before {
|
||||
content: '→';
|
||||
color: var(--kai-txt3);
|
||||
font-size: 10px;
|
||||
margin-top: 1px;
|
||||
.sw-icon {
|
||||
font-weight: 800;
|
||||
font-size: 14px;
|
||||
line-height: 1.4;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.sw-icon.green { color: var(--kai-green); }
|
||||
.sw-icon.red { color: var(--kai-red); }
|
||||
|
||||
.empty-sw {
|
||||
color: var(--kai-txt3) !important;
|
||||
font-style: italic;
|
||||
@@ -345,4 +412,36 @@ const maxY = computed(() => Math.max(Math.max(...scatter.value.map(d => d.y)) *
|
||||
padding: 40px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
color: var(--kai-txt3);
|
||||
}
|
||||
|
||||
.empty-state i {
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
.empty-state p {
|
||||
margin: 0;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
/* ==================== RESPONSIVE ==================== */
|
||||
@media (max-width: 768px) {
|
||||
.summary-cards {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.sw-grid {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.expand-content {
|
||||
padding: 16px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -308,6 +308,12 @@ const router = createRouter({
|
||||
component: () => import('@/views/pages/marketing/MarketingStrategyV3.vue'),
|
||||
meta: { requiresAuth: true }
|
||||
},
|
||||
{
|
||||
path: '/marketing-analysis/ai-strategy',
|
||||
name: 'marketing-ai-strategy',
|
||||
component: () => import('@/views/pages/marketing/AIStrategyGeneratorPage.vue'),
|
||||
meta: { requiresAuth: true }
|
||||
},
|
||||
{
|
||||
path: '/marketing-analysis/v3/:id',
|
||||
name: 'marketing-v3-analysis',
|
||||
|
||||
@@ -8,9 +8,10 @@ const navExternal = (url) => window.open(url, '_blank');
|
||||
<template>
|
||||
<div class="mkt-hub">
|
||||
|
||||
<!-- AMBIENT GLOW -->
|
||||
<!-- AMBIENT GLOW (Premium Soft Mesh) -->
|
||||
<div class="mkt-hub__glow1"></div>
|
||||
<div class="mkt-hub__glow2"></div>
|
||||
<div class="mkt-hub__glow3"></div>
|
||||
|
||||
<div class="mkt-hub__inner">
|
||||
|
||||
@@ -21,7 +22,7 @@ const navExternal = (url) => window.open(url, '_blank');
|
||||
KAI Intelligence · Платформа 2026
|
||||
</div>
|
||||
<h1 class="mkt-hub__title">Маркетинг-Хаб</h1>
|
||||
<p class="mkt-hub__sub">Автономное управление маркетингом: от глубоких исследований рынка до генерации контента и запуска рекламных кампаний.</p>
|
||||
<p class="mkt-hub__sub">Исследования рынка, генерация стратегий и управление рекламными кампаниями — всё в одном месте. Управляйте ростом вашего бизнеса легко и эффективно.</p>
|
||||
</div>
|
||||
|
||||
<!-- GRID -->
|
||||
@@ -48,14 +49,14 @@ const navExternal = (url) => window.open(url, '_blank');
|
||||
</div>
|
||||
|
||||
<!-- Сгенерировать стратегию -->
|
||||
<div class="mkt-card mkt-card--accent" @click="nav('/marketing-analysis/v3')">
|
||||
<div class="mkt-card__accent-glow"></div>
|
||||
<div class="mkt-card__icon" style="background:rgba(0,212,139,0.15);color:#00D48B;">
|
||||
<div class="mkt-card mkt-card--green" @click="nav('/marketing-analysis/v3/new')">
|
||||
<div class="mkt-card__glow"></div>
|
||||
<div class="mkt-card__icon" style="background:rgba(16,185,129,0.15);color:#10B981;">
|
||||
<i class="pi pi-bolt"></i>
|
||||
</div>
|
||||
<h3 class="mkt-card__title">Сгенерировать стратегию</h3>
|
||||
<p class="mkt-card__desc">Контент-фабрика. ИИ выберет соцсети, напишет посты и отрисует картинки на основе анализа</p>
|
||||
<div class="mkt-card__tag">Требуется анализ</div>
|
||||
<div class="mkt-card__tag mkt-card__tag--green">Требуется анализ</div>
|
||||
</div>
|
||||
|
||||
<!-- Мои стратегии -->
|
||||
@@ -68,48 +69,32 @@ const navExternal = (url) => window.open(url, '_blank');
|
||||
<div class="mkt-card__arrow"><i class="pi pi-arrow-right"></i></div>
|
||||
</div>
|
||||
|
||||
<!-- ИИ-Таргетолог -->
|
||||
<div class="mkt-card mkt-card--blue" @click="navExternal('https://call-center.konturai.kz')">
|
||||
<!-- ИИ-Таргетолог (внутренний) - Поменяли местами, теперь идет перед CRM -->
|
||||
<div class="mkt-card mkt-card--blue" @click="nav('/marketing-analysis/v3/targetologist/dashboard')">
|
||||
<div class="mkt-card__glow"></div>
|
||||
<div class="mkt-card__icon" style="background:rgba(45,123,255,0.15);color:#2D7BFF;">
|
||||
<i class="pi pi-bullseye"></i>
|
||||
</div>
|
||||
<h3 class="mkt-card__title">ИИ-Таргетолог</h3>
|
||||
<p class="mkt-card__desc">Развёртывание рекламных кампаний — ИИ создаст аудитории, оптимизирует бюджет и запустит рекламу в Facebook, Instagram и TikTok</p>
|
||||
<div class="mkt-card__tag">Бета-режим API</div>
|
||||
<div class="mkt-card__tag mkt-card__tag--blue">Бета-режим API</div>
|
||||
<div class="mkt-card__arrow"><i class="pi pi-arrow-right"></i></div>
|
||||
</div>
|
||||
|
||||
<!-- CRM -->
|
||||
<div class="mkt-card mkt-card--teal" @click="nav('/crm')">
|
||||
<!-- CRM-система (внешняя ссылка) - Поменяли местами, теперь в конце -->
|
||||
<div class="mkt-card mkt-card--teal" @click="navExternal('https://call-center.konturai.kz/')">
|
||||
<div class="mkt-card__glow"></div>
|
||||
<div class="mkt-card__icon" style="background:rgba(20,184,166,0.15);color:#14B8A6;">
|
||||
<i class="pi pi-users"></i>
|
||||
</div>
|
||||
<h3 class="mkt-card__title">CRM-система</h3>
|
||||
<p class="mkt-card__desc">Управление клиентской базой, сделками и воронкой продаж в едином пространстве</p>
|
||||
<div class="mkt-card__arrow" style="background:rgba(20,184,166,0.1);color:#14B8A6;"><i class="pi pi-arrow-right"></i></div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- AI TARGETOLOGIST — full-width banner -->
|
||||
<div class="mkt-banner" @click="nav('/marketing-analysis/v3/targetologist/dashboard')">
|
||||
<div class="mkt-banner__glow"></div>
|
||||
<div class="mkt-banner__left">
|
||||
<div class="mkt-banner__icon">
|
||||
<i class="pi pi-bullseye"></i>
|
||||
</div>
|
||||
<div>
|
||||
<div class="mkt-banner__label">
|
||||
<span class="mkt-banner__dot"></span>
|
||||
Бета-режим API
|
||||
</div>
|
||||
<h2 class="mkt-banner__title">ИИ-Таргетолог</h2>
|
||||
<p class="mkt-banner__desc">Развёртывание рекламных кампаний — ИИ создаст аудитории, оптимизирует бюджет и запустит рекламу в Facebook, Instagram и TikTok</p>
|
||||
<div class="mkt-card__external-badge">
|
||||
<i class="pi pi-external-link"></i>
|
||||
Внешний сервис
|
||||
</div>
|
||||
</div>
|
||||
<div class="mkt-banner__cta">
|
||||
<span>Открыть кабинет</span>
|
||||
<i class="pi pi-arrow-right"></i>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
@@ -119,36 +104,54 @@ const navExternal = (url) => window.open(url, '_blank');
|
||||
<style scoped>
|
||||
/* ───── Page layout ───── */
|
||||
.mkt-hub {
|
||||
background: #F5F8FF;
|
||||
background: #F8FAFC; /* Clean, slightly cool white */
|
||||
min-height: 100vh;
|
||||
font-family: 'Onest', sans-serif;
|
||||
font-family: 'Onest', -apple-system, BlinkMacSystemFont, sans-serif;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
padding: 48px 24px 80px;
|
||||
padding: 80px 32px 100px;
|
||||
color: #0F172A;
|
||||
}
|
||||
|
||||
/* Ambient premium mesh background effects */
|
||||
.mkt-hub__glow1 {
|
||||
position: absolute;
|
||||
top: -120px;
|
||||
right: -100px;
|
||||
top: -150px;
|
||||
left: -100px;
|
||||
width: 600px;
|
||||
height: 600px;
|
||||
background: radial-gradient(circle, rgba(45,123,255,0.08) 0%, transparent 70%);
|
||||
background: radial-gradient(circle, rgba(45, 123, 255, 0.08) 0%, transparent 70%);
|
||||
pointer-events: none;
|
||||
animation: float-glow 15s ease-in-out infinite alternate;
|
||||
}
|
||||
.mkt-hub__glow2 {
|
||||
position: absolute;
|
||||
bottom: -80px;
|
||||
left: -60px;
|
||||
width: 400px;
|
||||
height: 400px;
|
||||
background: radial-gradient(circle, rgba(0,212,139,0.06) 0%, transparent 70%);
|
||||
top: 20%;
|
||||
right: -200px;
|
||||
width: 800px;
|
||||
height: 800px;
|
||||
background: radial-gradient(circle, rgba(168, 85, 247, 0.06) 0%, transparent 60%);
|
||||
pointer-events: none;
|
||||
animation: float-glow 20s ease-in-out infinite alternate-reverse;
|
||||
}
|
||||
.mkt-hub__glow3 {
|
||||
position: absolute;
|
||||
bottom: -100px;
|
||||
left: 20%;
|
||||
width: 700px;
|
||||
height: 700px;
|
||||
background: radial-gradient(circle, rgba(16, 185, 129, 0.05) 0%, transparent 70%);
|
||||
pointer-events: none;
|
||||
animation: float-glow 18s ease-in-out infinite alternate;
|
||||
}
|
||||
|
||||
@keyframes float-glow {
|
||||
0% { transform: translate(0, 0) scale(1); }
|
||||
100% { transform: translate(40px, -40px) scale(1.1); }
|
||||
}
|
||||
|
||||
.mkt-hub__inner {
|
||||
max-width: 1100px;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
@@ -157,52 +160,59 @@ const navExternal = (url) => window.open(url, '_blank');
|
||||
/* ───── Header ───── */
|
||||
.mkt-hub__header {
|
||||
text-align: center;
|
||||
margin-bottom: 52px;
|
||||
margin-bottom: 72px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.mkt-hub__badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 4px 12px;
|
||||
gap: 8px;
|
||||
padding: 6px 18px;
|
||||
border-radius: 999px;
|
||||
border: 1px solid rgba(45,123,255,0.18);
|
||||
background: rgba(45,123,255,0.08);
|
||||
font-size: 9px;
|
||||
font-weight: 800;
|
||||
font-family: 'Unbounded', sans-serif;
|
||||
border: 1px solid rgba(45,123,255,0.2);
|
||||
background: rgba(255, 255, 255, 0.8);
|
||||
backdrop-filter: blur(10px);
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
font-family: 'Onest', sans-serif;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.1em;
|
||||
letter-spacing: 0.12em;
|
||||
color: #2D7BFF;
|
||||
margin-bottom: 16px;
|
||||
margin-bottom: 24px;
|
||||
box-shadow: 0 4px 12px rgba(45,123,255,0.05);
|
||||
}
|
||||
|
||||
.mkt-hub__badge-dot {
|
||||
width: 5px;
|
||||
height: 5px;
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
border-radius: 50%;
|
||||
background: #2D7BFF;
|
||||
box-shadow: 0 0 8px #2D7BFF;
|
||||
animation: pulse-dot 2s infinite;
|
||||
box-shadow: 0 0 10px rgba(45,123,255,0.6);
|
||||
animation: pulse-dot 2s ease-in-out infinite;
|
||||
}
|
||||
@keyframes pulse-dot {
|
||||
0%,100% { opacity:1; transform: scale(1); }
|
||||
50% { opacity:0.5; transform: scale(0.8); }
|
||||
0%, 100% { opacity: 1; transform: scale(1); }
|
||||
50% { opacity: 0.5; transform: scale(0.85); }
|
||||
}
|
||||
|
||||
.mkt-hub__title {
|
||||
font-family: 'Unbounded', sans-serif;
|
||||
font-size: 32px;
|
||||
font-weight: 900;
|
||||
font-family: 'Onest', sans-serif;
|
||||
font-size: 52px;
|
||||
font-weight: 800;
|
||||
color: #0F172A;
|
||||
letter-spacing: -0.03em;
|
||||
line-height: 1.1;
|
||||
margin-bottom: 12px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.mkt-hub__sub {
|
||||
font-size: 13px;
|
||||
color: #334155;
|
||||
max-width: 500px;
|
||||
font-size: 16px;
|
||||
color: #475569;
|
||||
max-width: 620px;
|
||||
margin: 0 auto;
|
||||
line-height: 1.6;
|
||||
font-weight: 400;
|
||||
@@ -212,243 +222,226 @@ const navExternal = (url) => window.open(url, '_blank');
|
||||
.mkt-hub__grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 16px;
|
||||
margin-bottom: 16px;
|
||||
gap: 24px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
@media (max-width: 900px) {
|
||||
.mkt-hub__grid { grid-template-columns: repeat(2, 1fr); }
|
||||
@media (max-width: 1024px) {
|
||||
.mkt-hub__grid { grid-template-columns: repeat(2, 1fr); gap: 20px; }
|
||||
}
|
||||
@media (max-width: 640px) {
|
||||
.mkt-hub__grid { grid-template-columns: 1fr; }
|
||||
.mkt-hub { padding: 40px 20px 70px; }
|
||||
.mkt-hub__title { font-size: 36px; }
|
||||
.mkt-hub__header { margin-bottom: 40px; }
|
||||
}
|
||||
|
||||
/* ───── Cards ───── */
|
||||
/* ───── Glassmorphism Cards ───── */
|
||||
.mkt-card {
|
||||
background: #FFFFFF;
|
||||
border: 1px solid rgba(45,123,255,0.18);
|
||||
border-radius: 20px;
|
||||
padding: 28px;
|
||||
background: rgba(255, 255, 255, 0.75);
|
||||
border: 1px solid rgba(255, 255, 255, 1);
|
||||
backdrop-filter: blur(20px);
|
||||
border-radius: 28px;
|
||||
padding: 36px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s cubic-bezier(0.4,0,0.2,1);
|
||||
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2px 8px rgba(45,123,255,0.05);
|
||||
box-shadow:
|
||||
0 4px 6px rgba(0, 0, 0, 0.02),
|
||||
0 12px 24px rgba(0, 0, 0, 0.03);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 250px;
|
||||
}
|
||||
|
||||
/* Card hover overlay */
|
||||
.mkt-card::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s;
|
||||
border-radius: 20px;
|
||||
transition: opacity 0.4s;
|
||||
border-radius: 28px;
|
||||
pointer-events: none;
|
||||
z-index: 0;
|
||||
}
|
||||
.mkt-card::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 4px;
|
||||
background: linear-gradient(90deg, transparent, currentColor, transparent);
|
||||
opacity: 0;
|
||||
transition: opacity 0.4s;
|
||||
}
|
||||
.mkt-card:hover {
|
||||
transform: translateY(-4px);
|
||||
border-color: rgba(45,123,255,0.35);
|
||||
background: #F8FBFF;
|
||||
box-shadow: 0 16px 36px rgba(15,23,42,0.1), 0 0 28px rgba(45,123,255,0.12);
|
||||
transform: translateY(-8px);
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
box-shadow:
|
||||
0 24px 48px rgba(0, 0, 0, 0.06),
|
||||
0 12px 20px rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
.mkt-card:hover::before { opacity: 1; }
|
||||
.mkt-card:hover::after { opacity: 1; }
|
||||
|
||||
.mkt-card--blue::before { background: radial-gradient(circle at top right, rgba(45,123,255,0.1), transparent 60%); }
|
||||
.mkt-card--purple::before { background: radial-gradient(circle at top right, rgba(168,85,247,0.1), transparent 60%); }
|
||||
.mkt-card--orange::before { background: radial-gradient(circle at top right, rgba(255,122,45,0.1), transparent 60%); }
|
||||
.mkt-card--accent::before { background: radial-gradient(circle at top right, rgba(0,212,139,0.1), transparent 60%); }
|
||||
.mkt-card--teal::before { background: radial-gradient(circle at top right, rgba(20,184,166,0.12), transparent 60%); }
|
||||
/* Card color variants */
|
||||
.mkt-card--blue { color: #2D7BFF; }
|
||||
.mkt-card--blue::before { background: radial-gradient(circle at top right, rgba(45,123,255,0.06), transparent 70%); }
|
||||
|
||||
.mkt-card--purple { color: #8B5CF6; }
|
||||
.mkt-card--purple::before { background: radial-gradient(circle at top right, rgba(139,92,246,0.06), transparent 70%); }
|
||||
|
||||
.mkt-card--green { color: #10B981; }
|
||||
.mkt-card--green::before { background: radial-gradient(circle at top right, rgba(16,185,129,0.06), transparent 70%); }
|
||||
|
||||
.mkt-card--orange { color: #F97316; }
|
||||
.mkt-card--orange::before { background: radial-gradient(circle at top right, rgba(249,115,22,0.06), transparent 70%); }
|
||||
|
||||
.mkt-card--teal { color: #14B8A6; }
|
||||
.mkt-card--teal::before { background: radial-gradient(circle at top right, rgba(20,184,166,0.06), transparent 70%); }
|
||||
|
||||
/* Card content */
|
||||
.mkt-card > * { position: relative; z-index: 2; }
|
||||
|
||||
.mkt-card__glow {
|
||||
position: absolute;
|
||||
top: -40px;
|
||||
right: -40px;
|
||||
width: 160px;
|
||||
height: 160px;
|
||||
background: radial-gradient(circle, currentColor, transparent 70%);
|
||||
opacity: 0.05;
|
||||
pointer-events: none;
|
||||
transition: opacity 0.4s, transform 0.4s;
|
||||
z-index: 1;
|
||||
}
|
||||
.mkt-card:hover .mkt-card__glow {
|
||||
opacity: 0.12;
|
||||
transform: scale(1.3);
|
||||
}
|
||||
|
||||
.mkt-card__icon {
|
||||
width: 52px;
|
||||
height: 52px;
|
||||
border-radius: 14px;
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
border-radius: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 22px;
|
||||
margin-bottom: 20px;
|
||||
transition: transform 0.3s;
|
||||
font-size: 28px;
|
||||
margin-bottom: 28px;
|
||||
transition: transform 0.4s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.04);
|
||||
}
|
||||
.mkt-card:hover .mkt-card__icon {
|
||||
transform: scale(1.1) rotate(-5deg);
|
||||
}
|
||||
.mkt-card:hover .mkt-card__icon { transform: scale(1.1) rotate(3deg); }
|
||||
|
||||
.mkt-card__title {
|
||||
font-family: 'Unbounded', sans-serif;
|
||||
font-size: 14px;
|
||||
font-family: 'Onest', sans-serif;
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
color: #0F172A;
|
||||
margin-bottom: 8px;
|
||||
line-height: 1.25;
|
||||
margin-bottom: 14px;
|
||||
line-height: 1.3;
|
||||
letter-spacing: -0.01em;
|
||||
}
|
||||
|
||||
.mkt-card__desc {
|
||||
font-size: 11.5px;
|
||||
color: #334155;
|
||||
line-height: 1.55;
|
||||
font-size: 14px;
|
||||
color: #475569;
|
||||
line-height: 1.7;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.mkt-card__arrow {
|
||||
position: absolute;
|
||||
bottom: 24px;
|
||||
right: 24px;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
bottom: 32px;
|
||||
right: 32px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
background: rgba(45,123,255,0.1);
|
||||
background: rgba(0,0,0,0.03);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--kai-blue);
|
||||
font-size: 13px;
|
||||
color: currentColor;
|
||||
font-size: 16px;
|
||||
opacity: 0;
|
||||
transform: translateX(-6px);
|
||||
transition: all 0.3s;
|
||||
transform: translateX(-12px);
|
||||
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
.mkt-card:hover .mkt-card__arrow {
|
||||
opacity: 1;
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
.mkt-card__accent-glow {
|
||||
position: absolute;
|
||||
top: -30px;
|
||||
right: -30px;
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
background: radial-gradient(circle, rgba(0,212,139,0.15), transparent 70%);
|
||||
pointer-events: none;
|
||||
background: currentColor;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
.mkt-card__tag {
|
||||
display: inline-block;
|
||||
margin-top: 12px;
|
||||
padding: 2px 8px;
|
||||
border-radius: 999px;
|
||||
border: 1px solid rgba(16,185,129,0.25);
|
||||
background: rgba(16,185,129,0.07);
|
||||
color: #10B981;
|
||||
font-size: 8.5px;
|
||||
font-weight: 800;
|
||||
font-family: 'Unbounded', sans-serif;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.08em;
|
||||
}
|
||||
|
||||
/* ───── Targetologist Banner ───── */
|
||||
.mkt-banner {
|
||||
background: linear-gradient(135deg, rgba(45,123,255,0.08) 0%, rgba(139,92,246,0.05) 100%);
|
||||
border: 1px solid rgba(45,123,255,0.18);
|
||||
border-radius: 20px;
|
||||
padding: 24px 32px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 24px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 4px 16px rgba(45,123,255,0.06);
|
||||
}
|
||||
.mkt-banner:hover {
|
||||
border-color: rgba(45,123,255,0.6);
|
||||
background: linear-gradient(135deg, rgba(45,123,255,0.18) 0%, rgba(99,51,220,0.12) 100%);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 24px 60px rgba(45,123,255,0.15);
|
||||
}
|
||||
.mkt-banner__glow {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: -60px;
|
||||
width: 300px;
|
||||
height: 200px;
|
||||
background: radial-gradient(circle, rgba(45,123,255,0.15), transparent 70%);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.mkt-banner__left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 24px;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.mkt-banner__icon {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
border-radius: 18px;
|
||||
background: rgba(45,123,255,0.15);
|
||||
border: 1px solid rgba(45,123,255,0.3);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 28px;
|
||||
color: var(--kai-blue);
|
||||
flex-shrink: 0;
|
||||
transition: transform 0.3s;
|
||||
}
|
||||
.mkt-banner:hover .mkt-banner__icon { transform: scale(1.08) rotate(3deg); }
|
||||
|
||||
.mkt-banner__label {
|
||||
display: flex;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
font-size: 9px;
|
||||
font-weight: 800;
|
||||
font-family: 'Unbounded', sans-serif;
|
||||
margin-top: auto;
|
||||
padding: 6px 12px;
|
||||
border-radius: 999px;
|
||||
font-size: 10px;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.1em;
|
||||
color: var(--kai-blue);
|
||||
margin-bottom: 4px;
|
||||
letter-spacing: 0.08em;
|
||||
align-self: flex-start;
|
||||
}
|
||||
.mkt-banner__dot {
|
||||
width: 5px;
|
||||
height: 5px;
|
||||
border-radius: 50%;
|
||||
background: var(--kai-blue);
|
||||
animation: pulse-dot 2s infinite;
|
||||
.mkt-card__tag--green {
|
||||
border: 1px solid rgba(16,185,129,0.3);
|
||||
background: rgba(16,185,129,0.1);
|
||||
color: #10B981;
|
||||
}
|
||||
.mkt-card__tag--blue {
|
||||
border: 1px solid rgba(45,123,255,0.3);
|
||||
background: rgba(45,123,255,0.1);
|
||||
color: #2D7BFF;
|
||||
}
|
||||
|
||||
.mkt-banner__title {
|
||||
font-family: 'Unbounded', sans-serif;
|
||||
font-size: 20px;
|
||||
font-weight: 900;
|
||||
color: #0F172A;
|
||||
margin-bottom: 6px;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.mkt-banner__desc {
|
||||
font-size: 12px;
|
||||
color: #334155;
|
||||
line-height: 1.5;
|
||||
max-width: 520px;
|
||||
}
|
||||
|
||||
.mkt-banner__cta {
|
||||
display: flex;
|
||||
.mkt-card__external-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 10px 20px;
|
||||
border-radius: 10px;
|
||||
background: var(--kai-blue);
|
||||
color: #fff;
|
||||
font-size: 11.5px;
|
||||
font-weight: 800;
|
||||
font-family: 'Unbounded', sans-serif;
|
||||
white-space: nowrap;
|
||||
flex-shrink: 0;
|
||||
margin-top: auto;
|
||||
padding: 8px 14px;
|
||||
border-radius: 12px;
|
||||
background: rgba(255, 255, 255, 0.8);
|
||||
border: 1px solid rgba(15, 23, 42, 0.1);
|
||||
color: #475569;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
align-self: flex-start;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
.mkt-banner:hover .mkt-banner__cta {
|
||||
background: #1a6bff;
|
||||
box-shadow: 0 0 24px rgba(45,123,255,0.45);
|
||||
transform: translateX(4px);
|
||||
.mkt-card:hover .mkt-card__external-badge {
|
||||
background: #0F172A;
|
||||
color: #FFFFFF;
|
||||
border-color: #0F172A;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.mkt-banner { flex-direction: column; align-items: flex-start; }
|
||||
.mkt-banner__cta { align-self: stretch; justify-content: center; }
|
||||
.mkt-hub { padding: 32px 16px 60px; }
|
||||
/* Card entrance animation */
|
||||
@keyframes card-enter {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(30px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
.mkt-card {
|
||||
animation: card-enter 0.6s cubic-bezier(0.16, 1, 0.3, 1) backwards;
|
||||
}
|
||||
.mkt-card:nth-child(1) { animation-delay: 0.05s; }
|
||||
.mkt-card:nth-child(2) { animation-delay: 0.1s; }
|
||||
.mkt-card:nth-child(3) { animation-delay: 0.15s; }
|
||||
.mkt-card:nth-child(4) { animation-delay: 0.2s; }
|
||||
.mkt-card:nth-child(5) { animation-delay: 0.25s; }
|
||||
.mkt-card:nth-child(6) { animation-delay: 0.3s; }
|
||||
</style>
|
||||
|
||||
@@ -783,17 +783,6 @@ onUnmounted(() => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="quick-actions-bar">
|
||||
<Button label="Опубликовать в Facebook" icon="pi pi-facebook"
|
||||
class="prime-action-btn fb"
|
||||
:loading="isAutoPublishing"
|
||||
@click="autoPublishFirstPost" />
|
||||
|
||||
<Button label="Запустить ИИ Таргет" icon="pi pi-bolt"
|
||||
class="prime-action-btn target"
|
||||
@click="openTargetingWizard" />
|
||||
</div>
|
||||
|
||||
<!-- Weekly Sprints -->
|
||||
<div v-if="strategyData.weeklyPlans?.length" class="sprints-section">
|
||||
<div class="section-title-row">
|
||||
@@ -902,31 +891,24 @@ onUnmounted(() => {
|
||||
@click="handlePublishNow(post)" />
|
||||
<div v-else class="card-status-label published"><i class="pi pi-check"></i> OK</div>
|
||||
</div>
|
||||
|
||||
<button class="card-target-btn" @click="launchPostTarget(post)">
|
||||
<i class="pi pi-bolt"></i> Запустить таргет
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Actions Footer -->
|
||||
<div class="results-footer-bar">
|
||||
<Button label="Сохранить как PDF" icon="pi pi-file-pdf" class="footer-btn pdf-btn" />
|
||||
|
||||
<Button
|
||||
v-if="!isAutoPostingStarted"
|
||||
class="footer-btn main-run-btn"
|
||||
@click="handleStartStrategy"
|
||||
:loading="startingStrategy"
|
||||
>
|
||||
<i class="pi pi-rocket"></i>
|
||||
<span>Активировать стратегию продвижения</span>
|
||||
</Button>
|
||||
<div v-else class="footer-success-status">
|
||||
<i class="pi pi-check-circle"></i>
|
||||
<span>Стратегия успешно активирована</span>
|
||||
<!-- Bottom Action: Launch Targeting -->
|
||||
<div class="bottom-action-section">
|
||||
<div class="bottom-action-card">
|
||||
<div class="bottom-action-icon">
|
||||
<i class="pi pi-bullseye"></i>
|
||||
</div>
|
||||
<div class="bottom-action-text">
|
||||
<h4>Готовы запустить рекламу?</h4>
|
||||
<p>ИИ создаст аудитории, оптимизирует бюджет и запустит рекламу в Facebook, Instagram и TikTok</p>
|
||||
</div>
|
||||
<Button label="Запустить ИИ Таргет" icon="pi pi-rocket"
|
||||
class="bottom-action-btn"
|
||||
@click="openTargetingWizard" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1932,6 +1914,243 @@ onUnmounted(() => {
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
/* ───── WEEKLY SPRINTS (План реализации по неделям) ───── */
|
||||
.sprints-section {
|
||||
margin-top: 48px;
|
||||
margin-bottom: 48px;
|
||||
}
|
||||
|
||||
.sprints-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.sprint-item {
|
||||
background: var(--kai-bg2);
|
||||
border: 1px solid var(--kai-border);
|
||||
border-radius: 16px;
|
||||
overflow: hidden;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.sprint-item:hover {
|
||||
border-color: var(--kai-blue);
|
||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.sprint-item.is-active {
|
||||
border-color: var(--kai-blue);
|
||||
background: rgba(var(--kai-blue-rgb), 0.03);
|
||||
}
|
||||
|
||||
.sprint-summary {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
padding: 20px 24px;
|
||||
cursor: pointer;
|
||||
transition: background 0.2s ease;
|
||||
}
|
||||
|
||||
.sprint-summary:hover {
|
||||
background: rgba(var(--kai-blue-rgb), 0.05);
|
||||
}
|
||||
|
||||
.week-num {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
background: linear-gradient(135deg, var(--kai-blue), var(--kai-green));
|
||||
color: #fff;
|
||||
border-radius: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-family: 'Unbounded', sans-serif;
|
||||
font-size: 16px;
|
||||
font-weight: 800;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.week-info {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.week-title {
|
||||
font-family: 'Unbounded', sans-serif;
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
color: var(--kai-txt);
|
||||
margin: 0 0 8px 0;
|
||||
}
|
||||
|
||||
.week-tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.week-tag {
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
color: var(--kai-txt2);
|
||||
background: rgba(var(--kai-blue-rgb), 0.08);
|
||||
border: 1px solid rgba(var(--kai-blue-rgb), 0.15);
|
||||
padding: 4px 10px;
|
||||
border-radius: 6px;
|
||||
white-space: nowrap;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.week-tag:hover {
|
||||
background: rgba(var(--kai-blue-rgb), 0.15);
|
||||
border-color: var(--kai-blue);
|
||||
color: var(--kai-blue);
|
||||
}
|
||||
|
||||
.chevron {
|
||||
font-size: 14px;
|
||||
color: var(--kai-txt3);
|
||||
transition: transform 0.3s ease;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* Expand animation */
|
||||
.expand-enter-active,
|
||||
.expand-leave-active {
|
||||
transition: all 0.3s ease;
|
||||
max-height: 500px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.expand-enter-from,
|
||||
.expand-leave-to {
|
||||
max-height: 0;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.sprint-details {
|
||||
padding: 0 24px 24px;
|
||||
border-top: 1px solid var(--kai-border);
|
||||
}
|
||||
|
||||
.details-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 24px;
|
||||
padding-top: 20px;
|
||||
}
|
||||
|
||||
.detail-col label {
|
||||
display: block;
|
||||
font-size: 10px;
|
||||
font-weight: 800;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.1em;
|
||||
color: var(--kai-txt3);
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.themes-cloud {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.cloud-tag {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: var(--kai-txt2);
|
||||
background: rgba(var(--kai-green-rgb), 0.08);
|
||||
border: 1px solid rgba(var(--kai-green-rgb), 0.15);
|
||||
padding: 6px 12px;
|
||||
border-radius: 8px;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.cloud-tag:hover {
|
||||
background: rgba(var(--kai-green-rgb), 0.15);
|
||||
border-color: var(--kai-green);
|
||||
color: var(--kai-green);
|
||||
}
|
||||
|
||||
/* ───── BOTTOM ACTION (Запуск таргета) ───── */
|
||||
.bottom-action-section {
|
||||
margin-top: 64px;
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
.bottom-action-card {
|
||||
background: linear-gradient(135deg, rgba(var(--kai-blue-rgb), 0.08) 0%, rgba(var(--kai-green-rgb), 0.08) 100%);
|
||||
border: 1px solid rgba(var(--kai-blue-rgb), 0.2);
|
||||
border-radius: 24px;
|
||||
padding: 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 32px;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.bottom-action-card:hover {
|
||||
border-color: var(--kai-blue);
|
||||
box-shadow: 0 12px 40px rgba(var(--kai-blue-rgb), 0.15);
|
||||
transform: translateY(-4px);
|
||||
}
|
||||
|
||||
.bottom-action-icon {
|
||||
width: 72px;
|
||||
height: 72px;
|
||||
background: linear-gradient(135deg, var(--kai-blue), var(--kai-green));
|
||||
border-radius: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 32px;
|
||||
color: #fff;
|
||||
flex-shrink: 0;
|
||||
box-shadow: 0 8px 24px rgba(var(--kai-blue-rgb), 0.3);
|
||||
}
|
||||
|
||||
.bottom-action-text {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.bottom-action-text h4 {
|
||||
font-family: 'Unbounded', sans-serif;
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
color: var(--kai-txt);
|
||||
margin: 0 0 8px 0;
|
||||
}
|
||||
|
||||
.bottom-action-text p {
|
||||
font-size: 13px;
|
||||
color: var(--kai-txt2);
|
||||
line-height: 1.6;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.bottom-action-btn {
|
||||
font-size: 14px !important;
|
||||
font-weight: 800 !important;
|
||||
padding: 14px 32px !important;
|
||||
border-radius: 16px !important;
|
||||
background: linear-gradient(135deg, var(--kai-blue), var(--kai-green)) !important;
|
||||
border: none !important;
|
||||
color: #fff !important;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
box-shadow: 0 8px 24px rgba(var(--kai-blue-rgb), 0.3) !important;
|
||||
transition: all 0.3s ease !important;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.bottom-action-btn:hover {
|
||||
transform: translateY(-2px) !important;
|
||||
box-shadow: 0 12px 32px rgba(var(--kai-blue-rgb), 0.4) !important;
|
||||
}
|
||||
|
||||
/* ───── POST CALENDAR ───── */
|
||||
.calendar-section { margin-top: 48px; }
|
||||
.calendar-header { margin-bottom: 24px; border-bottom: 1px solid var(--kai-border); padding-bottom: 16px; }
|
||||
@@ -1962,13 +2181,23 @@ onUnmounted(() => {
|
||||
|
||||
.post-media {
|
||||
position: relative;
|
||||
aspect-ratio: 4/5;
|
||||
background: #000;
|
||||
min-height: 200px;
|
||||
max-height: 400px;
|
||||
background: var(--kai-bg3);
|
||||
cursor: pointer;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.preview-obj { width: 100%; height: 100%; object-fit: cover; transition: transform 0.6s; }
|
||||
.preview-obj {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
transition: transform 0.6s;
|
||||
background: transparent;
|
||||
}
|
||||
.post-card:hover .preview-obj { transform: scale(1.05); }
|
||||
|
||||
.preview-overlay {
|
||||
@@ -2042,21 +2271,6 @@ onUnmounted(() => {
|
||||
|
||||
.card-status-label { font-size: 9px; font-weight: 900; color: var(--kai-green); display: flex; align-items: center; gap: 4px; text-transform: uppercase; }
|
||||
|
||||
.card-target-btn {
|
||||
width: 100%; margin-top: 12px; background: var(--kai-bg3); border: 1px solid var(--kai-border);
|
||||
padding: 10px; border-radius: 12px; color: var(--kai-txt2); font-size: 10px; font-weight: 800;
|
||||
text-transform: uppercase; display: flex; align-items: center; justify-content: center; gap: 8px;
|
||||
transition: all 0.2s; cursor: pointer;
|
||||
}
|
||||
.card-target-btn:hover { background: var(--kai-blue); color: #fff; border-color: var(--kai-blue); }
|
||||
|
||||
/* ───── FOOTER ───── */
|
||||
.results-footer-bar { margin-top: 48px; padding-top: 32px; border-top: 1px solid var(--kai-border); display: flex; gap: 16px; }
|
||||
.footer-btn { border-radius: 18px !important; font-family: 'Unbounded', sans-serif !important; text-transform: uppercase; font-size: 11px !important; font-weight: 800 !important; padding: 16px 32px !important; }
|
||||
.pdf-btn { background: var(--kai-bg2) !important; border: 1px solid var(--kai-border) !important; color: var(--kai-txt) !important; }
|
||||
.main-run-btn { flex: 1; background: linear-gradient(135deg, var(--kai-green), #10b981) !important; box-shadow: 0 10px 30px rgba(16, 185, 129, 0.3); border: none !important; }
|
||||
.footer-success-status { flex: 1; background: rgba(var(--kai-green-rgb), 0.1); border: 1px solid var(--kai-green); border-radius: 18px; display: flex; align-items: center; justify-content: center; gap: 12px; color: var(--kai-green); font-weight: 800; text-transform: uppercase; font-size: 11px; }
|
||||
|
||||
/* ───── MODAL ───── */
|
||||
.premium-modal {
|
||||
:deep(.p-dialog-content) { background: transparent !important; }
|
||||
@@ -2084,7 +2298,14 @@ onUnmounted(() => {
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.strategy-page { padding: 16px; }
|
||||
.results-footer-bar { flex-direction: column; }
|
||||
.posts-grid { grid-template-columns: 1fr; }
|
||||
.bottom-action-card {
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
padding: 32px 24px;
|
||||
}
|
||||
.bottom-action-btn {
|
||||
width: 100% !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -362,7 +362,7 @@ onMounted(() => {
|
||||
{{ sm(statusRef).label }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-wrap items-center gap-4 text-sm" style="color: var(--kai-txt2);">
|
||||
<div class="flex flex-wrap items-center gap-4 text-sm" style="color: #64748B;">
|
||||
<span><i class="pi pi-calendar mr-1"></i> {{ formatDateTime(campaign.createdAt) }}</span>
|
||||
<span v-if="campaign.objective"><i class="pi pi-bullseye mr-1"></i> {{ objectiveLabel(campaign.objective) }}</span>
|
||||
<span class="flex items-center gap-2">
|
||||
@@ -381,37 +381,6 @@ onMounted(() => {
|
||||
<i :class="publishingCampaign ? 'pi pi-spin pi-spinner' : 'pi pi-facebook'"></i>
|
||||
Запустить стратегию
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="mkt-banner__cta"
|
||||
style="border:none;"
|
||||
:disabled="launchTargetDisabled"
|
||||
:style="{ opacity: launchTargetDisabled ? 0.6 : 1 }"
|
||||
@click="launchTarget"
|
||||
>
|
||||
<i :class="launchingTarget ? 'pi pi-spin pi-spinner' : (launchTargetDisabled ? 'pi pi-check' : 'pi pi-play')"></i>
|
||||
{{ launchTargetLabel }}
|
||||
</button>
|
||||
<template v-if="statusRef === 'ACTIVE'">
|
||||
<button class="wiz-btn-sec" style="padding: 10px 20px;" @click="pauseCampaign">
|
||||
<i class="pi pi-pause"></i> Пауза
|
||||
</button>
|
||||
<button class="mkt-banner__cta" style="border:none;" @click="openInMeta">
|
||||
Meta Ads <i class="pi pi-external-link"></i>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<button v-else-if="statusRef === 'PAUSED'" class="mkt-banner__cta" style="border:none; background: var(--kai-green);" @click="resumeCampaign">
|
||||
<i class="pi pi-play"></i> Возобновить
|
||||
</button>
|
||||
|
||||
<button v-else-if="statusRef === 'FAILED'" class="mkt-banner__cta" style="border:none; background: var(--kai-red);" @click="retryCampaign">
|
||||
<i class="pi pi-refresh"></i> Повторить запуск
|
||||
</button>
|
||||
|
||||
<button v-if="statusRef === 'ACTIVE'" class="wiz-btn-sec" style="padding: 10px 20px;" @click="syncInsights" :disabled="actionLoading">
|
||||
<i :class="actionLoading ? 'pi pi-spin pi-spinner' : 'pi pi-sync'"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -427,8 +396,8 @@ onMounted(() => {
|
||||
</div>
|
||||
|
||||
<div v-else-if="statusRef === 'ACTIVE'" class="mkt-card" style="margin-bottom: 32px; padding: 12px 24px; border-color: rgba(0,212,139,0.3); background: rgba(0,212,139,0.05); display: flex; align-items: center; gap: 12px;">
|
||||
<i class="pi pi-check-circle" style="color: var(--kai-green); font-size: 20px;"></i>
|
||||
<span style="font-weight: 700; color: var(--kai-green);">Кампания успешно запущена {{ formatDateTime(launchTimestamp) }}</span>
|
||||
<i class="pi pi-check-circle" style="color: #10B981; font-size: 20px;"></i>
|
||||
<span style="font-weight: 700; color: #10B981;">Кампания успешно запущена {{ formatDateTime(launchTimestamp) }}</span>
|
||||
</div>
|
||||
|
||||
<!-- KPI GRID -->
|
||||
@@ -453,21 +422,21 @@ onMounted(() => {
|
||||
|
||||
<!-- MAIN CONTENT -->
|
||||
<div class="mkt-card td-main-tabs" style="padding: 12px;">
|
||||
<TabView v-model:activeIndex="activeTab">
|
||||
<TabPanel header="Метрики" value="0">
|
||||
<CampaignMetrics :performance-metrics="campaign.performanceMetrics" :predicted-metrics="predictionMetrics" />
|
||||
<TabView v-model:activeIndex="activeTab" :lazy="true">
|
||||
<TabPanel header="Метрики">
|
||||
<CampaignMetrics v-if="activeTab === 0" :performance-metrics="campaign.performanceMetrics" :predicted-metrics="predictionMetrics" />
|
||||
</TabPanel>
|
||||
<TabPanel header="ИИ Предсказание" value="1">
|
||||
<CampaignPrediction :campaign-id="campaignId" />
|
||||
<TabPanel header="ИИ Предсказание">
|
||||
<CampaignPrediction v-if="activeTab === 1" :campaign-id="campaignId" />
|
||||
</TabPanel>
|
||||
<TabPanel header="Инсайты" value="2">
|
||||
<CampaignInsights :campaign-id="campaignId" />
|
||||
<TabPanel header="Инсайты">
|
||||
<CampaignInsights v-if="activeTab === 2" :campaign-id="campaignId" />
|
||||
</TabPanel>
|
||||
<TabPanel header="Аудитория" value="3">
|
||||
<div class="grid gap-6 md:grid-cols-2 p-4">
|
||||
<TabPanel header="Аудитория">
|
||||
<div v-if="activeTab === 3" class="grid gap-6 md:grid-cols-2 p-4">
|
||||
<div class="mkt-card" style="background: rgba(45,123,255,0.02);">
|
||||
<h3 class="wiz-label" style="opacity:0.8"><i class="pi pi-users"></i> Профиль аудитории</h3>
|
||||
<div class="space-y-3 pt-4 text-sm" style="color: var(--kai-txt2);">
|
||||
<div class="space-y-3 pt-4 text-sm" style="color: #64748B;">
|
||||
<div><span class="info-label">Сегмент</span> {{ audienceProfile?.primarySegment || '—' }}</div>
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div><span class="info-label">Возраст</span> {{ audienceProfile?.ageRange || '—' }}</div>
|
||||
@@ -479,7 +448,7 @@ onMounted(() => {
|
||||
</div>
|
||||
<div class="mkt-card" style="background: rgba(45,123,255,0.02);">
|
||||
<h3 class="wiz-label" style="opacity:0.8"><i class="pi pi-th-large"></i> Плейсменты и форматы</h3>
|
||||
<div class="space-y-3 pt-4 text-sm" style="color: var(--kai-txt2);">
|
||||
<div class="space-y-3 pt-4 text-sm" style="color: #64748B;">
|
||||
<div><span class="info-label">Рекомендованные площадки</span> {{ formatArrayField(recommendation?.recommendedPlacements ?? null) }}</div>
|
||||
<div><span class="info-label">Форматы</span> {{ formatArrayField(recommendation?.recommendedAdFormats ?? null) }}</div>
|
||||
<div><span class="info-label">Тип таргетинга</span> {{ targetingTypeLabel(recommendation?.recommendedType ?? null) }}</div>
|
||||
@@ -487,8 +456,8 @@ onMounted(() => {
|
||||
</div>
|
||||
</div>
|
||||
</TabPanel>
|
||||
<TabPanel header="Структура" value="4">
|
||||
<CampaignAdSets :ad-sets="campaign.adSets" />
|
||||
<TabPanel header="Структура">
|
||||
<CampaignAdSets v-if="activeTab === 4" :ad-sets="campaign.adSets" />
|
||||
</TabPanel>
|
||||
</TabView>
|
||||
</div>
|
||||
|
||||
@@ -98,10 +98,10 @@ const isError = ref(false);
|
||||
const errorMessage = ref('');
|
||||
|
||||
const orchestratorPhases = [
|
||||
{ code: 'ORCHESTRATING', label: 'ИИ рассчитывает сегменты таргета...', icon: 'pi pi-microchip-ai' },
|
||||
{ code: 'MAPPING', label: 'Собираем группы объявлений по стратегии...', icon: 'pi pi-sitemap' },
|
||||
{ code: 'PUBLISHING', label: 'Публикуем в Meta/TikTok...', icon: 'pi pi-send' },
|
||||
{ code: 'ACTIVE', label: 'Готово: кампания запущена.', icon: 'pi pi-check-circle' }
|
||||
{ code: 'ORCHESTRATING', label: 'Анализ аудитории и создание сегментов', icon: 'pi pi-microchip-ai' },
|
||||
{ code: 'MAPPING', label: 'Формирование групп объявлений', icon: 'pi pi-sitemap' },
|
||||
{ code: 'PUBLISHING', label: 'Запуск рекламы в Facebook, Instagram и TikTok', icon: 'pi pi-send' },
|
||||
{ code: 'ACTIVE', label: 'Кампания успешно запущена', icon: 'pi pi-check-circle' }
|
||||
];
|
||||
|
||||
const startGeneration = async () => {
|
||||
@@ -295,7 +295,7 @@ const togglePlat = (val) => {
|
||||
<div class="wiz-check"><i class="pi pi-check" v-if="selectedPlatforms.includes('FB_IG')"></i></div>
|
||||
</div>
|
||||
<div class="wiz-platform" :class="{ 'wiz-platform--active': selectedPlatforms.includes('TIKTOK') }" @click="togglePlat('TIKTOK')">
|
||||
<i class="pi pi-tiktok" style="color: #fff;"></i>
|
||||
<i class="pi pi-tiktok" style="color: #010101;"></i>
|
||||
<span class="font-bold">TikTok</span>
|
||||
<div class="wiz-check"><i class="pi pi-check" v-if="selectedPlatforms.includes('TIKTOK')"></i></div>
|
||||
</div>
|
||||
@@ -332,8 +332,8 @@ const togglePlat = (val) => {
|
||||
<div class="wiz-loading-orb"></div>
|
||||
|
||||
<div class="text-center mb-10 relative z-10">
|
||||
<div class="wiz-loading-icon"><i class="pi pi-bolt"></i></div>
|
||||
<h2 class="wiz-loading-title">Запуск Машины</h2>
|
||||
<div class="wiz-loading-icon"><i class="pi pi-sparkles"></i></div>
|
||||
<h2 class="wiz-loading-title">Запуск рекламной кампании</h2>
|
||||
</div>
|
||||
|
||||
<div v-if="isError" class="wiz-error">
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<script setup>
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { useTargeting } from './composables/useTargeting';
|
||||
import TargetingService from '@/service/TargetingService';
|
||||
import { useToast } from 'primevue/usetoast';
|
||||
import Toast from 'primevue/toast';
|
||||
import { useToast } from 'primevue/usetoast';
|
||||
import { onMounted, ref } from 'vue';
|
||||
import OAuthConnectionWidget from './components/OAuthConnectionWidget.vue';
|
||||
import { useTargeting } from './composables/useTargeting';
|
||||
|
||||
const { state, fetchCampaigns, connectAccount, formatCurrency } = useTargeting();
|
||||
const toast = useToast();
|
||||
@@ -17,12 +17,12 @@ onMounted(async () => {
|
||||
});
|
||||
|
||||
const STATUS_META = {
|
||||
ACTIVE: { color: '#10B981', bg: 'rgba(16,185,129,0.1)', border: 'rgba(16,185,129,0.28)', icon: 'pi pi-bolt', label: 'АКТИВНА', accent: '#10B981' },
|
||||
PAUSED: { color: '#F59E0B', bg: 'rgba(245,158,11,0.1)', border: 'rgba(245,158,11,0.28)', icon: 'pi pi-pause', label: 'НА ПАУЗЕ ', accent: '#F59E0B' },
|
||||
FAILED: { color: '#EF4444', bg: 'rgba(239,68,68,0.1)', border: 'rgba(239,68,68,0.28)', icon: 'pi pi-exclamation-triangle', label: 'ОШИБКА', accent: '#EF4444' },
|
||||
GENERATING: { color: '#2D7BFF', bg: 'rgba(45,123,255,0.1)', border: 'rgba(45,123,255,0.28)', icon: 'pi pi-spin pi-cog', label: 'ЗАПУСК', accent: '#2D7BFF' },
|
||||
ACTIVE: { color: '#10B981', bg: 'rgba(16,185,129,0.1)', border: 'rgba(16,185,129,0.25)', icon: 'pi pi-bolt', label: 'АКТИВНА', accent: '#10B981' },
|
||||
PAUSED: { color: '#F59E0B', bg: 'rgba(245,158,11,0.1)', border: 'rgba(245,158,11,0.25)', icon: 'pi pi-pause', label: 'НА ПАУЗЕ', accent: '#F59E0B' },
|
||||
FAILED: { color: '#EF4444', bg: 'rgba(239,68,68,0.1)', border: 'rgba(239,68,68,0.25)', icon: 'pi pi-exclamation-triangle', label: 'ОШИБКА', accent: '#EF4444' },
|
||||
GENERATING: { color: '#2D7BFF', bg: 'rgba(45,123,255,0.1)', border: 'rgba(45,123,255,0.25)', icon: 'pi pi-spin pi-cog', label: 'ЗАПУСК', accent: '#2D7BFF' },
|
||||
};
|
||||
const sm = s => STATUS_META[(s||'').toUpperCase()] || { color: '#64748B', bg: 'rgba(100,116,139,0.08)', border: 'rgba(100,116,139,0.2)', icon: 'pi pi-circle', label: s || '—', accent: '#64748B' };
|
||||
const sm = s => STATUS_META[(s||'').toUpperCase()] || { color: '#64748B', bg: 'rgba(100,116,139,0.08)', border: 'rgba(100,116,139,0.15)', icon: 'pi pi-circle', label: s || '—', accent: '#64748B' };
|
||||
|
||||
const totalBudget = () => campaigns.value.reduce((sum, c) => sum + (c.budgetKzt || 0), 0);
|
||||
const totalSpent = () => campaigns.value.reduce((sum, c) => sum + (c.spentKzt || 0), 0);
|
||||
@@ -54,7 +54,7 @@ const filteredCampaigns = () => campaigns.value.filter(c =>
|
||||
<div class="td-page">
|
||||
<Toast />
|
||||
<div class="td-glow td-glow--blue"></div>
|
||||
<div class="td-glow td-glow--purple"></div>
|
||||
<div class="td-glow td-glow--green"></div>
|
||||
|
||||
<div class="td-inner">
|
||||
|
||||
@@ -85,47 +85,47 @@ const filteredCampaigns = () => campaigns.value.filter(c =>
|
||||
<!-- KPI CARDS -->
|
||||
<div class="td-kpi-grid">
|
||||
<div class="td-kpi-card">
|
||||
<div class="td-kpi-card__icon" style="background: rgba(45,123,255,0.12); color: #2D7BFF;">
|
||||
<div class="td-kpi-card__icon" style="background: rgba(45,123,255,0.1); color: #2D7BFF;">
|
||||
<i class="pi pi-list"></i>
|
||||
</div>
|
||||
<div class="td-kpi-card__body">
|
||||
<div class="td-kpi-card__num">{{ campaigns.length }}</div>
|
||||
<div class="td-kpi-card__label">Всего кампаний</div>
|
||||
</div>
|
||||
<div class="td-kpi-card__accent" style="background: rgba(45,123,255,0.06);"></div>
|
||||
<div class="td-kpi-card__accent" style="background: rgba(45,123,255,0.05);"></div>
|
||||
</div>
|
||||
|
||||
<div class="td-kpi-card">
|
||||
<div class="td-kpi-card__icon" style="background: rgba(16,185,129,0.12); color: #10B981;">
|
||||
<div class="td-kpi-card__icon" style="background: rgba(16,185,129,0.1); color: #10B981;">
|
||||
<i class="pi pi-bolt"></i>
|
||||
</div>
|
||||
<div class="td-kpi-card__body">
|
||||
<div class="td-kpi-card__num" style="color: #10B981;">{{ activeCnt() }}</div>
|
||||
<div class="td-kpi-card__label">Активно сейчас</div>
|
||||
</div>
|
||||
<div class="td-kpi-card__accent" style="background: rgba(16,185,129,0.06);"></div>
|
||||
<div class="td-kpi-card__accent" style="background: rgba(16,185,129,0.05);"></div>
|
||||
</div>
|
||||
|
||||
<div class="td-kpi-card">
|
||||
<div class="td-kpi-card__icon" style="background: rgba(249,115,22,0.12); color: #F97316;">
|
||||
<div class="td-kpi-card__icon" style="background: rgba(249,115,22,0.1); color: #F97316;">
|
||||
<i class="pi pi-dollar"></i>
|
||||
</div>
|
||||
<div class="td-kpi-card__body">
|
||||
<div class="td-kpi-card__num">{{ formatCurrency(totalSpent()) }}</div>
|
||||
<div class="td-kpi-card__label">Потрачено</div>
|
||||
</div>
|
||||
<div class="td-kpi-card__accent" style="background: rgba(249,115,22,0.06);"></div>
|
||||
<div class="td-kpi-card__accent" style="background: rgba(249,115,22,0.05);"></div>
|
||||
</div>
|
||||
|
||||
<div class="td-kpi-card">
|
||||
<div class="td-kpi-card__icon" style="background: rgba(139,92,246,0.12); color: #8B5CF6;">
|
||||
<div class="td-kpi-card__icon" style="background: rgba(139,92,246,0.1); color: #8B5CF6;">
|
||||
<i class="pi pi-wallet"></i>
|
||||
</div>
|
||||
<div class="td-kpi-card__body">
|
||||
<div class="td-kpi-card__num">{{ formatCurrency(totalBudget()) }}</div>
|
||||
<div class="td-kpi-card__label">Общий лимит</div>
|
||||
</div>
|
||||
<div class="td-kpi-card__accent" style="background: rgba(139,92,246,0.06);"></div>
|
||||
<div class="td-kpi-card__accent" style="background: rgba(139,92,246,0.05);"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -298,92 +298,75 @@ const filteredCampaigns = () => campaigns.value.filter(c =>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
/* ──────────────────── PAGE ──────────────────── */
|
||||
/* ──────────────────── PAGE (СВЕТЛАЯ ТЕМА, КАК В МАРКЕТИНГЕ) ──────────────────── */
|
||||
.td-page {
|
||||
background: #F5F8FF;
|
||||
background-image:
|
||||
radial-gradient(at 10% 0%, rgba(45,123,255,0.07) 0px, transparent 50%),
|
||||
radial-gradient(at 90% 100%, rgba(139,92,246,0.05) 0px, transparent 50%);
|
||||
background: linear-gradient(180deg, #F8FAFF 0%, #F0F4FF 100%);
|
||||
min-height: 100vh;
|
||||
font-family: 'Onest', sans-serif;
|
||||
padding: 40px 24px 100px;
|
||||
position: relative;
|
||||
overflow-x: hidden;
|
||||
color: #0F172A;
|
||||
}
|
||||
|
||||
.td-glow { position: absolute; border-radius: 50%; pointer-events: none; z-index: 0; }
|
||||
.td-glow--blue { top: -180px; right: -120px; width: 600px; height: 600px; background: radial-gradient(circle, rgba(45,123,255,0.08) 0%, transparent 70%); }
|
||||
.td-glow--purple{ bottom: -100px; left: -80px; width: 400px; height: 400px; background: radial-gradient(circle, rgba(139,92,246,0.06) 0%, transparent 70%); }
|
||||
.td-glow--blue { top: -180px; right: -120px; width: 600px; height: 600px; background: radial-gradient(circle, rgba(45,123,255,0.08) 0%, transparent 70%); animation: float-glow 20s ease-in-out infinite; }
|
||||
.td-glow--green { bottom: -100px; left: -80px; width: 500px; height: 500px; background: radial-gradient(circle, rgba(16,185,129,0.06) 0%, transparent 70%); animation: float-glow 25s ease-in-out infinite reverse; }
|
||||
@keyframes float-glow { 0%, 100% { transform: translate(0, 0) scale(1); } 50% { transform: translate(30px, -20px) scale(1.1); } }
|
||||
|
||||
.td-inner { max-width: 1100px; margin: 0 auto; position: relative; z-index: 1; }
|
||||
|
||||
/* ──────────────────── HEADER ──────────────────── */
|
||||
.td-header { margin-bottom: 40px; }
|
||||
.td-header__top {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.td-header__top { display: flex; align-items: center; gap: 16px; margin-bottom: 20px; }
|
||||
|
||||
.td-back-btn {
|
||||
width: 40px; height: 40px; border-radius: 12px;
|
||||
border: 1px solid rgba(45,123,255,0.2); background: #fff; color: #64748B;
|
||||
border: 1px solid rgba(45,123,255,0.15); background: #fff; color: #64748B;
|
||||
display: flex; align-items: center; justify-content: center; cursor: pointer;
|
||||
transition: all 0.2s; box-shadow: 0 2px 8px rgba(45,123,255,0.06); flex-shrink: 0;
|
||||
transition: all 0.2s; box-shadow: 0 2px 8px rgba(45,123,255,0.05); flex-shrink: 0;
|
||||
}
|
||||
.td-back-btn:hover { background: rgba(45,123,255,0.06); color: #2D7BFF; border-color: rgba(45,123,255,0.35); transform: translateX(-2px); }
|
||||
.td-back-btn:hover { background: rgba(45,123,255,0.06); color: #2D7BFF; border-color: rgba(45,123,255,0.3); transform: translateX(-2px); }
|
||||
|
||||
.td-badge {
|
||||
display: inline-flex; align-items: center; gap: 7px;
|
||||
padding: 5px 14px; border-radius: 999px;
|
||||
border: 1px solid rgba(45,123,255,0.25); background: rgba(45,123,255,0.07);
|
||||
font-size: 10px; font-weight: 800; font-family: 'Unbounded', sans-serif;
|
||||
border: 1px solid rgba(45,123,255,0.2);
|
||||
background: linear-gradient(135deg, rgba(45,123,255,0.08) 0%, rgba(45,123,255,0.04) 100%);
|
||||
font-size: 10px; font-weight: 700; font-family: 'Onest', sans-serif;
|
||||
text-transform: uppercase; letter-spacing: 0.08em; color: #2D7BFF;
|
||||
}
|
||||
.td-badge__dot {
|
||||
width: 6px; height: 6px; border-radius: 50%; background: #2D7BFF;
|
||||
box-shadow: 0 0 8px rgba(45,123,255,0.5); animation: td-blink 2s infinite;
|
||||
}
|
||||
@keyframes td-blink { 0%,100%{opacity:1}50%{opacity:0.4} }
|
||||
@keyframes td-blink { 0%,100%{opacity:1} 50%{opacity:0.4} }
|
||||
|
||||
.td-header__main { display: flex; align-items: flex-end; justify-content: space-between; gap: 20px; flex-wrap: wrap; }
|
||||
|
||||
.td-header__main {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
justify-content: space-between;
|
||||
gap: 20px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.td-title {
|
||||
font-family: 'Unbounded', sans-serif;
|
||||
font-family: 'Onest', sans-serif;
|
||||
font-size: clamp(22px, 3.5vw, 32px);
|
||||
font-weight: 900;
|
||||
color: #0F172A;
|
||||
letter-spacing: -0.02em;
|
||||
line-height: 1.15;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.td-subtitle {
|
||||
font-size: 14px;
|
||||
color: #64748B;
|
||||
line-height: 1.55;
|
||||
max-width: 480px;
|
||||
margin: 0 0 8px 0;
|
||||
}
|
||||
.td-subtitle { font-size: 14px; color: #64748B; line-height: 1.55; max-width: 480px; margin: 0; }
|
||||
|
||||
.td-launch-btn {
|
||||
display: inline-flex; align-items: center; gap: 9px;
|
||||
padding: 13px 24px; border-radius: 14px;
|
||||
background: #2D7BFF; color: #fff;
|
||||
font-size: 13px; font-weight: 700; font-family: 'Unbounded', sans-serif;
|
||||
font-size: 13px; font-weight: 700; font-family: 'Onest', sans-serif;
|
||||
border: none; cursor: pointer; transition: all 0.3s;
|
||||
box-shadow: 0 6px 24px rgba(45,123,255,0.35); white-space: nowrap; flex-shrink: 0;
|
||||
box-shadow: 0 6px 24px rgba(45,123,255,0.3); white-space: nowrap; flex-shrink: 0;
|
||||
}
|
||||
.td-launch-btn:hover { background: #1a6bff; box-shadow: 0 10px 36px rgba(45,123,255,0.5); transform: translateY(-3px); }
|
||||
.td-launch-btn:hover { background: #1a6bff; box-shadow: 0 10px 36px rgba(45,123,255,0.4); transform: translateY(-3px); }
|
||||
|
||||
/* ──────────────────── KPI GRID ──────────────────── */
|
||||
.td-kpi-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 14px;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
.td-kpi-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 16px; margin-bottom: 40px; }
|
||||
@media (max-width: 900px) { .td-kpi-grid { grid-template-columns: repeat(2, 1fr); } }
|
||||
@media (max-width: 480px) { .td-kpi-grid { grid-template-columns: 1fr; } }
|
||||
|
||||
@@ -391,100 +374,73 @@ const filteredCampaigns = () => campaigns.value.filter(c =>
|
||||
background: #fff;
|
||||
border: 1px solid rgba(45,123,255,0.12);
|
||||
border-radius: 18px;
|
||||
padding: 22px 20px;
|
||||
padding: 24px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
transition: all 0.25s cubic-bezier(0.4,0,0.2,1);
|
||||
box-shadow: 0 2px 10px rgba(45,123,255,0.05);
|
||||
}
|
||||
.td-kpi-card:hover { transform: translateY(-4px); box-shadow: 0 16px 40px rgba(45,123,255,0.12); }
|
||||
.td-kpi-card__accent {
|
||||
position: absolute;
|
||||
top: 0; right: 0;
|
||||
width: 80px; height: 80px;
|
||||
border-radius: 0 18px 0 80px;
|
||||
box-shadow: 0 4px 12px rgba(45,123,255,0.05);
|
||||
}
|
||||
.td-kpi-card:hover { transform: translateY(-4px); box-shadow: 0 12px 32px rgba(45,123,255,0.1); border-color: rgba(45,123,255,0.25); }
|
||||
.td-kpi-card__accent { position: absolute; top: 0; right: 0; width: 80px; height: 80px; border-radius: 0 18px 0 80px; }
|
||||
.td-kpi-card__icon {
|
||||
width: 44px; height: 44px; border-radius: 12px;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
font-size: 18px; margin-bottom: 16px; position: relative; z-index: 1;
|
||||
transition: transform 0.3s;
|
||||
width: 48px; height: 48px; border-radius: 14px;
|
||||
display: flex; align-items: center; justify-content: center; font-size: 20px; margin-bottom: 16px;
|
||||
position: relative; z-index: 1; transition: transform 0.3s;
|
||||
}
|
||||
.td-kpi-card:hover .td-kpi-card__icon { transform: scale(1.12) rotate(5deg); }
|
||||
.td-kpi-card:hover .td-kpi-card__icon { transform: scale(1.1); }
|
||||
.td-kpi-card__body { position: relative; z-index: 1; }
|
||||
.td-kpi-card__num {
|
||||
font-family: 'Unbounded', sans-serif;
|
||||
font-size: 22px; font-weight: 900; color: #0F172A; line-height: 1; margin-bottom: 6px;
|
||||
}
|
||||
.td-kpi-card__label {
|
||||
font-size: 10px; color: #94A3B8; font-weight: 700;
|
||||
text-transform: uppercase; letter-spacing: 0.07em;
|
||||
}
|
||||
.td-kpi-card__num { font-family: 'Onest', sans-serif; font-size: 24px; font-weight: 900; color: #0F172A; line-height: 1; margin-bottom: 6px; }
|
||||
.td-kpi-card__label { font-size: 10px; color: #94A3B8; font-weight: 700; text-transform: uppercase; letter-spacing: 0.07em; }
|
||||
|
||||
/* ──────────────────── SECTION HEADER ──────────────────── */
|
||||
.td-section-header { margin-bottom: 16px; }
|
||||
.td-section-label {
|
||||
display: flex; align-items: center; gap: 8px;
|
||||
font-size: 11px; font-weight: 800; font-family: 'Unbounded', sans-serif;
|
||||
text-transform: uppercase; letter-spacing: 0.1em; color: #94A3B8;
|
||||
font-size: 11px; font-weight: 700; font-family: 'Onest', sans-serif;
|
||||
text-transform: uppercase; letter-spacing: 0.1em; color: #64748B;
|
||||
}
|
||||
.td-section-label i { font-size: 12px; }
|
||||
|
||||
/* ──────────────────── INTEGRATIONS ──────────────────── */
|
||||
.td-integrations-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 14px;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
.td-integrations-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 16px; margin-bottom: 40px; }
|
||||
@media (max-width: 640px) { .td-integrations-grid { grid-template-columns: 1fr; } }
|
||||
|
||||
/* ──────────────────── CAMPAIGNS HEADER ──────────────────── */
|
||||
.td-campaigns-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
margin-bottom: 16px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.td-campaigns-header { display: flex; align-items: center; justify-content: space-between; gap: 16px; margin-bottom: 16px; flex-wrap: wrap; }
|
||||
.td-search { position: relative; }
|
||||
.td-search__icon {
|
||||
position: absolute; left: 14px; top: 50%; transform: translateY(-50%);
|
||||
color: #94A3B8; font-size: 13px; pointer-events: none;
|
||||
}
|
||||
.td-search__icon { position: absolute; left: 14px; top: 50%; transform: translateY(-50%); color: #94A3B8; font-size: 13px; pointer-events: none; }
|
||||
.td-search__input {
|
||||
padding: 10px 38px 10px 42px; border-radius: 12px;
|
||||
background: #fff; border: 1px solid rgba(45,123,255,0.16); color: #0F172A;
|
||||
background: #fff; border: 1px solid rgba(45,123,255,0.15); color: #0F172A;
|
||||
font-size: 13.5px; font-family: 'Onest', sans-serif; outline: none;
|
||||
width: 280px; transition: all 0.2s; box-shadow: 0 2px 8px rgba(45,123,255,0.05);
|
||||
width: 280px; transition: all 0.2s; box-shadow: 0 2px 8px rgba(45,123,255,0.04);
|
||||
}
|
||||
.td-search__input:focus { border-color: rgba(45,123,255,0.4); width: 320px; box-shadow: 0 0 0 3px rgba(45,123,255,0.08); }
|
||||
.td-search__input:focus { border-color: #2D7BFF; width: 320px; box-shadow: 0 0 0 3px rgba(45,123,255,0.1); }
|
||||
.td-search__input::placeholder { color: #94A3B8; }
|
||||
.td-search__clear {
|
||||
position: absolute; right: 10px; top: 50%; transform: translateY(-50%);
|
||||
width: 22px; height: 22px; border-radius: 50%; border: none;
|
||||
background: rgba(100,116,139,0.12); color: #64748B;
|
||||
background: rgba(100,116,139,0.1); color: #64748B;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
cursor: pointer; font-size: 10px; transition: all 0.2s;
|
||||
}
|
||||
.td-search__clear:hover { background: rgba(239,68,68,0.12); color: #EF4444; }
|
||||
.td-search__clear:hover { background: rgba(239,68,68,0.1); color: #EF4444; }
|
||||
|
||||
/* ──────────────────── SKELETON ──────────────────── */
|
||||
.td-skeleton-list { display: flex; flex-direction: column; gap: 12px; }
|
||||
.td-skeleton-item {
|
||||
display: flex; background: #fff;
|
||||
border: 1px solid rgba(45,123,255,0.1); border-radius: 18px; overflow: hidden; height: 130px;
|
||||
display: flex; background: #fff; border: 1px solid rgba(45,123,255,0.1);
|
||||
border-radius: 18px; overflow: hidden; height: 130px;
|
||||
}
|
||||
.td-skeleton-stripe { width: 5px; flex-shrink: 0; background: rgba(45,123,255,0.1); }
|
||||
.td-skeleton-stripe { width: 5px; flex-shrink: 0; background: rgba(45,123,255,0.08); }
|
||||
.td-skeleton-body { flex: 1; padding: 22px 24px; }
|
||||
.td-skeleton-line { height: 12px; border-radius: 6px; background: rgba(45,123,255,0.07); }
|
||||
.td-skeleton-line { height: 12px; border-radius: 6px; background: rgba(45,123,255,0.06); }
|
||||
.td-skeleton-metrics { display: flex; gap: 24px; margin-top: 16px; }
|
||||
.td-skeleton-metric { width: 80px; height: 40px; border-radius: 10px; background: rgba(45,123,255,0.07); }
|
||||
.td-skeleton-metric { width: 80px; height: 40px; border-radius: 10px; background: rgba(45,123,255,0.06); }
|
||||
.shimmer { position: relative; overflow: hidden; }
|
||||
.shimmer::after {
|
||||
content: ''; position: absolute; inset: 0;
|
||||
background: linear-gradient(90deg, transparent, rgba(45,123,255,0.07), transparent);
|
||||
background: linear-gradient(90deg, transparent, rgba(45,123,255,0.06), transparent);
|
||||
animation: shimmer 1.6s infinite;
|
||||
}
|
||||
@keyframes shimmer { 0%{transform:translateX(-100%)} 100%{transform:translateX(100%)} }
|
||||
@@ -492,100 +448,73 @@ const filteredCampaigns = () => campaigns.value.filter(c =>
|
||||
/* ──────────────────── EMPTY ──────────────────── */
|
||||
.td-empty {
|
||||
text-align: center; padding: 80px 24px; background: #fff;
|
||||
border: 1px solid rgba(45,123,255,0.14); border-radius: 24px;
|
||||
border: 1px solid rgba(45,123,255,0.12); border-radius: 24px;
|
||||
box-shadow: 0 4px 24px rgba(45,123,255,0.06);
|
||||
display: flex; flex-direction: column; align-items: center; gap: 16px;
|
||||
}
|
||||
.td-empty__visual { position: relative; width: 84px; height: 84px; }
|
||||
.td-empty__ring {
|
||||
position: absolute; inset: -7px; border-radius: 50%;
|
||||
border: 2px solid rgba(45,123,255,0.12); animation: spin-slow 8s linear infinite;
|
||||
border: 2px solid rgba(45,123,255,0.1); animation: spin-slow 8s linear infinite;
|
||||
}
|
||||
@keyframes spin-slow { from{transform:rotate(0deg)} to{transform:rotate(360deg)} }
|
||||
.td-empty__icon {
|
||||
width: 84px; height: 84px; border-radius: 22px;
|
||||
background: rgba(45,123,255,0.08); border: 1px solid rgba(45,123,255,0.18);
|
||||
background: rgba(45,123,255,0.08); border: 1px solid rgba(45,123,255,0.15);
|
||||
display: flex; align-items: center; justify-content: center; font-size: 34px; color: #2D7BFF;
|
||||
}
|
||||
.td-empty__title { font-family: 'Unbounded', sans-serif; font-size: 20px; font-weight: 800; color: #0F172A; }
|
||||
.td-empty__desc { font-size: 13.5px; color: #64748B; max-width: 420px; line-height: 1.65; }
|
||||
.td-empty__title { font-family: 'Onest', sans-serif; font-size: 20px; font-weight: 800; color: #0F172A; margin: 0; }
|
||||
.td-empty__desc { font-size: 13.5px; color: #64748B; max-width: 420px; line-height: 1.65; margin: 0; }
|
||||
|
||||
/* ──────────────────── CAMPAIGN LIST ──────────────────── */
|
||||
.td-campaign-list { display: flex; flex-direction: column; gap: 12px; }
|
||||
|
||||
.td-campaign {
|
||||
display: flex;
|
||||
background: #fff;
|
||||
display: flex; background: #fff;
|
||||
border: 1px solid rgba(45,123,255,0.12);
|
||||
border-radius: 18px;
|
||||
cursor: pointer;
|
||||
overflow: hidden;
|
||||
border-radius: 18px; cursor: pointer; overflow: hidden;
|
||||
transition: all 0.25s cubic-bezier(0.4,0,0.2,1);
|
||||
box-shadow: 0 2px 10px rgba(45,123,255,0.05);
|
||||
box-shadow: 0 2px 10px rgba(45,123,255,0.04);
|
||||
}
|
||||
.td-campaign:hover { transform: translateY(-3px); box-shadow: 0 14px 40px rgba(45,123,255,0.12); border-color: rgba(45,123,255,0.28); }
|
||||
.td-campaign:hover { transform: translateY(-3px); box-shadow: 0 12px 32px rgba(45,123,255,0.1); border-color: rgba(45,123,255,0.25); }
|
||||
|
||||
.td-campaign__stripe { width: 5px; flex-shrink: 0; transition: width 0.2s; }
|
||||
.td-campaign:hover .td-campaign__stripe { width: 6px; }
|
||||
|
||||
.td-campaign__body { flex: 1; padding: 22px 24px; }
|
||||
|
||||
.td-campaign__top { margin-bottom: 18px; }
|
||||
.td-campaign__name-row {
|
||||
display: flex; align-items: center; gap: 12px; flex-wrap: wrap; margin-bottom: 8px;
|
||||
}
|
||||
.td-campaign__name {
|
||||
font-family: 'Unbounded', sans-serif; font-size: 15px; font-weight: 700; color: #0F172A; line-height: 1.3; flex: 1;
|
||||
}
|
||||
.td-campaign__name-row { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; margin-bottom: 8px; }
|
||||
.td-campaign__name { font-family: 'Onest', sans-serif; font-size: 15px; font-weight: 700; color: #0F172A; line-height: 1.3; flex: 1; margin: 0; }
|
||||
.td-campaign__status {
|
||||
display: inline-flex; align-items: center; gap: 5px;
|
||||
padding: 4px 12px; border-radius: 999px; border: 1px solid;
|
||||
font-size: 9.5px; font-weight: 800; font-family: 'Unbounded', sans-serif;
|
||||
text-transform: uppercase; letter-spacing: 0.07em; flex-shrink: 0;
|
||||
font-size: 9.5px; font-weight: 700; font-family: 'Onest', sans-serif;
|
||||
text-transform: uppercase; letter-spacing: 0.05em; flex-shrink: 0;
|
||||
}
|
||||
.td-campaign__status i { font-size: 9px; }
|
||||
|
||||
.td-campaign__meta {
|
||||
display: flex; align-items: center; gap: 10px; font-size: 12px; color: #94A3B8; flex-wrap: wrap;
|
||||
}
|
||||
.td-campaign__meta { display: flex; align-items: center; gap: 10px; font-size: 12px; color: #64748B; flex-wrap: wrap; }
|
||||
.td-campaign__meta i { font-size: 11px; margin-right: 3px; }
|
||||
.td-plat-chips { display: flex; gap: 6px; flex-wrap: wrap; }
|
||||
.td-plat-chip {
|
||||
display: inline-flex; align-items: center; gap: 4px;
|
||||
padding: 3px 9px; border-radius: 8px; font-size: 11px; font-weight: 600;
|
||||
}
|
||||
.td-plat-chip { display: inline-flex; align-items: center; gap: 4px; padding: 3px 9px; border-radius: 8px; font-size: 11px; font-weight: 600; }
|
||||
.td-plat-chip i { font-size: 10px; }
|
||||
|
||||
.td-campaign__metrics {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 28px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.td-campaign__metrics { display: flex; align-items: center; gap: 28px; flex-wrap: wrap; }
|
||||
.td-metric { display: flex; flex-direction: column; gap: 4px; min-width: 100px; }
|
||||
.td-metric__label {
|
||||
font-size: 9.5px; font-weight: 800; text-transform: uppercase; letter-spacing: 0.08em; color: #94A3B8;
|
||||
}
|
||||
.td-metric__val {
|
||||
font-family: 'Unbounded', sans-serif; font-size: 17px; font-weight: 900; color: #0F172A; line-height: 1;
|
||||
}
|
||||
.td-metric__bar {
|
||||
height: 4px; background: rgba(45,123,255,0.1); border-radius: 999px; overflow: hidden; margin-top: 4px;
|
||||
}
|
||||
.td-metric__bar-fill {
|
||||
height: 100%; border-radius: 999px; transition: width 0.9s cubic-bezier(0.4,0,0.2,1);
|
||||
}
|
||||
.td-metric__label { font-size: 9.5px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.08em; color: #94A3B8; }
|
||||
.td-metric__val { font-family: 'Onest', sans-serif; font-size: 17px; font-weight: 900; color: #0F172A; line-height: 1; }
|
||||
.td-metric__bar { height: 4px; background: rgba(45,123,255,0.08); border-radius: 999px; overflow: hidden; margin-top: 4px; }
|
||||
.td-metric__bar-fill { height: 100%; border-radius: 999px; transition: width 0.9s cubic-bezier(0.4,0,0.2,1); }
|
||||
|
||||
.td-campaign__actions { display: flex; gap: 8px; align-items: center; margin-left: auto; }
|
||||
.td-act-btn {
|
||||
width: 36px; height: 36px; border-radius: 10px;
|
||||
border: 1px solid rgba(45,123,255,0.14); background: rgba(45,123,255,0.04);
|
||||
border: 1px solid rgba(45,123,255,0.12); background: rgba(45,123,255,0.04);
|
||||
color: #64748B; display: flex; align-items: center; justify-content: center;
|
||||
cursor: pointer; transition: all 0.2s; font-size: 13px;
|
||||
}
|
||||
.td-act-btn:hover { transform: scale(1.08); }
|
||||
.td-act-btn--pause:hover { background: rgba(245,158,11,0.1); color: #F59E0B; border-color: rgba(245,158,11,0.3); }
|
||||
.td-act-btn--play:hover { background: rgba(16,185,129,0.1); color: #10B981; border-color: rgba(16,185,129,0.3); }
|
||||
.td-act-btn--open:hover { background: rgba(45,123,255,0.1); color: #2D7BFF; border-color: rgba(45,123,255,0.3); }
|
||||
.td-act-btn--pause:hover { background: rgba(245,158,11,0.1); color: #F59E0B; border-color: rgba(245,158,11,0.25); }
|
||||
.td-act-btn--play:hover { background: rgba(16,185,129,0.1); color: #10B981; border-color: rgba(16,185,129,0.25); }
|
||||
.td-act-btn--open:hover { background: rgba(45,123,255,0.1); color: #2D7BFF; border-color: rgba(45,123,255,0.25); }
|
||||
</style>
|
||||
|
||||
@@ -59,7 +59,7 @@ onMounted(() => { void loadInsights(); });
|
||||
<div v-for="i in 4" :key="i" class="ci-skeleton"></div>
|
||||
</div>
|
||||
|
||||
<div v-else-if="insights.length === 0" class="ci-empty">
|
||||
<div v-else-if="!(insights?.length)" class="ci-empty">
|
||||
<i class="pi pi-microchip-ai ci-empty__icon"></i>
|
||||
<p>ИИ анализирует кампанию. Первые инсайты появятся через 24–48 часов после запуска.</p>
|
||||
<button class="ci-empty__btn" @click="syncInsights" :disabled="loading">
|
||||
@@ -70,7 +70,7 @@ onMounted(() => { void loadInsights(); });
|
||||
|
||||
<div v-else class="ci-grid">
|
||||
<article
|
||||
v-for="insight in insights"
|
||||
v-for="insight in (insights || []).filter(i => i)"
|
||||
:key="insight.id"
|
||||
class="ci-card"
|
||||
:style="{ background: insightMeta(insight.type).bg, borderColor: insightMeta(insight.type).border }"
|
||||
@@ -108,16 +108,16 @@ onMounted(() => { void loadInsights(); });
|
||||
header="Действие по инсайту"
|
||||
:style="{ width: '32rem' }"
|
||||
:pt="{
|
||||
root: { style: 'background:#0F1628;border:1px solid rgba(45,123,255,0.2);border-radius:20px;' },
|
||||
header: { style: 'background:#0F1628;border-bottom:1px solid rgba(45,123,255,0.15);color:rgba(255,255,255,0.9);font-family:Unbounded,sans-serif;' },
|
||||
content: { style: 'background:#0F1628;' }
|
||||
root: { style: 'background:#FFFFFF;border:1px solid rgba(45,123,255,0.2);border-radius:24px;box-shadow: 0 10px 40px rgba(0,0,0,0.1);' },
|
||||
header: { style: 'background:#FFFFFF;border-bottom:1px solid rgba(45,123,255,0.1);color:#0F172A;font-family:Onest,sans-serif;' },
|
||||
content: { style: 'background:#FFFFFF;padding:24px;' }
|
||||
}"
|
||||
>
|
||||
<div v-if="selectedInsight" class="ci-modal">
|
||||
<h4 class="ci-modal__title">{{ selectedInsight.title }}</h4>
|
||||
<p class="ci-modal__desc">{{ selectedInsight.description }}</p>
|
||||
<div class="ci-modal__action">
|
||||
<i class="pi pi-check-circle" style="color:#00D48B; flex-shrink:0;"></i>
|
||||
<i class="pi pi-check-circle" style="color:#10B981; flex-shrink:0;"></i>
|
||||
{{ selectedInsight.suggestedAction || 'Рекомендация будет добавлена позже.' }}
|
||||
</div>
|
||||
</div>
|
||||
@@ -146,11 +146,12 @@ onMounted(() => { void loadInsights(); });
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
transition: transform 0.2s, box-shadow 0.2s;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.ci-card:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 12px 40px rgba(0, 0, 0, 0.3);
|
||||
box-shadow: 0 4px 20px rgba(45, 123, 255, 0.08);
|
||||
}
|
||||
|
||||
.ci-card__stripe {
|
||||
@@ -176,10 +177,10 @@ onMounted(() => { void loadInsights(); });
|
||||
}
|
||||
|
||||
.ci-card__title {
|
||||
font-family: 'Unbounded', sans-serif;
|
||||
font-size: 13px;
|
||||
font-family: 'Onest', sans-serif;
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
color: rgba(255, 255, 255, 0.92);
|
||||
color: #0F172A;
|
||||
flex: 1;
|
||||
line-height: 1.3;
|
||||
}
|
||||
@@ -187,7 +188,7 @@ onMounted(() => { void loadInsights(); });
|
||||
.ci-card__type {
|
||||
font-size: 9px;
|
||||
font-weight: 700;
|
||||
font-family: 'Unbounded', sans-serif;
|
||||
font-family: 'Onest', sans-serif;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.08em;
|
||||
padding: 3px 10px;
|
||||
@@ -198,7 +199,7 @@ onMounted(() => { void loadInsights(); });
|
||||
|
||||
.ci-card__desc {
|
||||
font-size: 13px;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
color: #64748B;
|
||||
line-height: 1.6;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
@@ -207,10 +208,10 @@ onMounted(() => { void loadInsights(); });
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
font-size: 12px;
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
font-family: 'Unbounded', sans-serif;
|
||||
background: transparent;
|
||||
font-family: 'Onest', sans-serif;
|
||||
background: #fff;
|
||||
border: 1px solid;
|
||||
border-radius: 10px;
|
||||
padding: 6px 16px;
|
||||
@@ -230,10 +231,10 @@ onMounted(() => { void loadInsights(); });
|
||||
gap: 12px;
|
||||
padding: 48px 24px;
|
||||
border-radius: 18px;
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
background: #fff;
|
||||
border: 1px solid rgba(45, 123, 255, 0.12);
|
||||
text-align: center;
|
||||
color: rgba(255, 255, 255, 0.45);
|
||||
color: #64748B;
|
||||
font-size: 13px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
@@ -250,11 +251,11 @@ onMounted(() => { void loadInsights(); });
|
||||
gap: 8px;
|
||||
padding: 10px 16px;
|
||||
border-radius: 12px;
|
||||
border: 1px solid rgba(45, 123, 255, 0.25);
|
||||
background: rgba(45, 123, 255, 0.08);
|
||||
color: rgba(255, 255, 255, 0.85);
|
||||
border: 1px solid rgba(45, 123, 255, 0.2);
|
||||
background: rgba(45, 123, 255, 0.05);
|
||||
color: #0F172A;
|
||||
font-weight: 700;
|
||||
font-family: 'Unbounded', sans-serif;
|
||||
font-family: 'Onest', sans-serif;
|
||||
cursor: pointer;
|
||||
transition: transform 0.2s, opacity 0.2s;
|
||||
}
|
||||
@@ -266,46 +267,48 @@ onMounted(() => { void loadInsights(); });
|
||||
|
||||
.ci-empty__btn:not(:disabled):hover {
|
||||
transform: translateY(-1px);
|
||||
background: rgba(45, 123, 255, 0.08);
|
||||
}
|
||||
|
||||
.ci-skeleton {
|
||||
height: 100px;
|
||||
border-radius: 18px;
|
||||
background: rgba(255, 255, 255, 0.04);
|
||||
border: 1px solid rgba(45, 123, 255, 0.1);
|
||||
background: #fff;
|
||||
border: 1px solid rgba(45, 123, 255, 0.08);
|
||||
animation: ci-pulse 2s infinite;
|
||||
}
|
||||
|
||||
@keyframes ci-pulse {
|
||||
0%, 100% { opacity: 0.6; }
|
||||
50% { opacity: 0.3; }
|
||||
0%, 100% { opacity: 0.8; }
|
||||
50% { opacity: 0.4; }
|
||||
}
|
||||
|
||||
.ci-modal__title {
|
||||
font-family: 'Unbounded', sans-serif;
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
color: rgba(255, 255, 255, 0.92);
|
||||
margin-bottom: 10px;
|
||||
font-family: 'Onest', sans-serif;
|
||||
font-size: 18px;
|
||||
font-weight: 800;
|
||||
color: #0F172A;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.ci-modal__desc {
|
||||
font-size: 13px;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
font-size: 14px;
|
||||
color: #475569;
|
||||
line-height: 1.6;
|
||||
margin-bottom: 16px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.ci-modal__action {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 10px;
|
||||
padding: 14px;
|
||||
border-radius: 12px;
|
||||
background: rgba(0, 212, 139, 0.07);
|
||||
border: 1px solid rgba(0, 212, 139, 0.2);
|
||||
font-size: 13px;
|
||||
color: rgba(0, 212, 139, 0.9);
|
||||
gap: 12px;
|
||||
padding: 16px;
|
||||
border-radius: 16px;
|
||||
background: rgba(16, 185, 129, 0.08);
|
||||
border: 1px solid rgba(16, 185, 129, 0.2);
|
||||
font-size: 14px;
|
||||
color: #065F46;
|
||||
line-height: 1.5;
|
||||
font-weight: 500;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -129,11 +129,11 @@ const trendLabel = (trend: MetricTrend): string => {
|
||||
gap: 10px;
|
||||
padding: 12px 20px;
|
||||
border-radius: 14px;
|
||||
background: rgba(45, 123, 255, 0.07);
|
||||
background: rgba(45, 123, 255, 0.08);
|
||||
border: 1px solid rgba(45, 123, 255, 0.2);
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: rgba(45, 123, 255, 0.9);
|
||||
font-weight: 600;
|
||||
color: #2D7BFF;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
@@ -153,13 +153,14 @@ const trendLabel = (trend: MetricTrend): string => {
|
||||
}
|
||||
|
||||
.cm-card {
|
||||
background: rgba(255, 255, 255, 0.04);
|
||||
background: #fff;
|
||||
border: 1px solid rgba(45, 123, 255, 0.15);
|
||||
border-radius: 18px;
|
||||
padding: 20px;
|
||||
transition: all 0.3s;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 4px 12px rgba(45, 123, 255, 0.04);
|
||||
}
|
||||
|
||||
.cm-card::before {
|
||||
@@ -167,15 +168,16 @@ const trendLabel = (trend: MetricTrend): string => {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
opacity: 0;
|
||||
background: radial-gradient(circle at top right, rgba(45, 123, 255, 0.08), transparent 60%);
|
||||
background: radial-gradient(circle at top right, rgba(45, 123, 255, 0.06), transparent 60%);
|
||||
pointer-events: none;
|
||||
transition: opacity 0.3s;
|
||||
}
|
||||
|
||||
.cm-card:hover {
|
||||
border-color: rgba(45, 123, 255, 0.35);
|
||||
background: rgba(255, 255, 255, 0.06);
|
||||
border-color: rgba(45, 123, 255, 0.3);
|
||||
background: #fff;
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 24px rgba(45, 123, 255, 0.08);
|
||||
}
|
||||
|
||||
.cm-card:hover::before {
|
||||
@@ -192,10 +194,10 @@ const trendLabel = (trend: MetricTrend): string => {
|
||||
.cm-card__name {
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
font-family: 'Unbounded', sans-serif;
|
||||
font-family: 'Onest', sans-serif;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.06em;
|
||||
color: rgba(255, 255, 255, 0.45);
|
||||
color: #64748B;
|
||||
}
|
||||
|
||||
.cm-card__trend {
|
||||
@@ -204,31 +206,33 @@ const trendLabel = (trend: MetricTrend): string => {
|
||||
}
|
||||
|
||||
.cm-card__val {
|
||||
font-family: 'Unbounded', sans-serif;
|
||||
font-family: 'Onest', sans-serif;
|
||||
font-size: 24px;
|
||||
font-weight: 900;
|
||||
color: rgba(255, 255, 255, 0.92);
|
||||
color: #0F172A;
|
||||
line-height: 1;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.cm-card__val--empty {
|
||||
color: rgba(255, 255, 255, 0.25);
|
||||
color: #CBD5E1;
|
||||
}
|
||||
|
||||
.cm-card__range {
|
||||
font-size: 11px;
|
||||
color: rgba(255, 255, 255, 0.35);
|
||||
color: #94A3B8;
|
||||
margin-bottom: 4px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.cm-card__helper {
|
||||
font-size: 11px;
|
||||
color: #F59E0B;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.cm-skel {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
background: #F1F5F9;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 8px;
|
||||
animation: cm-pulse 2s infinite;
|
||||
@@ -245,7 +249,7 @@ const trendLabel = (trend: MetricTrend): string => {
|
||||
}
|
||||
|
||||
@keyframes cm-pulse {
|
||||
0%, 100% { opacity: 0.5; }
|
||||
50% { opacity: 0.25; }
|
||||
0%, 100% { opacity: 0.8; }
|
||||
50% { opacity: 0.4; }
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<script setup lang="ts">
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, ref, watch } from 'vue';
|
||||
import { format } from 'date-fns';
|
||||
import { ru } from 'date-fns/locale';
|
||||
@@ -107,23 +107,23 @@ const weeklyChartOptions = {
|
||||
type: 'linear',
|
||||
position: 'left',
|
||||
beginAtZero: true,
|
||||
ticks: { color: 'rgba(255,255,255,0.4)', font: { family: 'Onest' } },
|
||||
grid: { color: 'rgba(255,255,255,0.06)' }
|
||||
ticks: { color: '#64748B', font: { family: 'Onest', size: 10 } },
|
||||
grid: { color: 'rgba(45,123,255,0.06)' }
|
||||
},
|
||||
y1: {
|
||||
type: 'linear',
|
||||
position: 'right',
|
||||
beginAtZero: true,
|
||||
ticks: { color: 'rgba(255,255,255,0.4)', font: { family: 'Onest' } },
|
||||
ticks: { color: '#64748B', font: { family: 'Onest', size: 10 } },
|
||||
grid: { drawOnChartArea: false }
|
||||
},
|
||||
x: {
|
||||
ticks: { color: 'rgba(255,255,255,0.4)', font: { family: 'Onest' } },
|
||||
grid: { color: 'rgba(255,255,255,0.04)' }
|
||||
ticks: { color: '#64748B', font: { family: 'Onest', size: 10 } },
|
||||
grid: { color: 'rgba(45,123,255,0.04)' }
|
||||
}
|
||||
},
|
||||
plugins: {
|
||||
legend: { labels: { color: 'rgba(255,255,255,0.6)', font: { family: 'Onest', size: 12 } } }
|
||||
legend: { labels: { color: '#0F172A', font: { family: 'Onest', size: 12, weight: '700' } } }
|
||||
}
|
||||
};
|
||||
|
||||
@@ -164,7 +164,7 @@ const priorityMeta = (priority: string): { color: string; bg: string; border: st
|
||||
const v = priority.toUpperCase();
|
||||
if (v === 'HIGH') return { color: '#EF4444', bg: 'rgba(239,68,68,0.1)', border: 'rgba(239,68,68,0.25)' };
|
||||
if (v === 'MEDIUM') return { color: '#F59E0B', bg: 'rgba(245,158,11,0.1)', border: 'rgba(245,158,11,0.25)' };
|
||||
return { color: 'rgba(255,255,255,0.4)', bg: 'rgba(255,255,255,0.04)', border: 'rgba(255,255,255,0.1)' };
|
||||
return { color: '#64748B', bg: '#f1f5f9', border: '#e2e8f0' };
|
||||
};
|
||||
|
||||
const marketSaturationMeta = computed(() => {
|
||||
@@ -281,7 +281,7 @@ onMounted(() => { void hydratePrediction(); });
|
||||
<div class="cp-score-wrap">
|
||||
<svg width="160" height="160" viewBox="0 0 190 190" class="cp-gauge">
|
||||
<g transform="translate(95,95)">
|
||||
<circle r="70" fill="none" stroke="rgba(255,255,255,0.06)" stroke-width="12" />
|
||||
<circle r="70" fill="none" stroke="rgba(45,123,255,0.06)" stroke-width="12" />
|
||||
<circle
|
||||
r="70"
|
||||
fill="none"
|
||||
@@ -297,14 +297,14 @@ onMounted(() => { void hydratePrediction(); });
|
||||
</svg>
|
||||
<div class="cp-score-inner">
|
||||
<div class="cp-score-val" :style="{ color: scoreColor }">{{ score }}</div>
|
||||
<div class="cp-score-sub">/100</div>
|
||||
<div class="cp-score-sub" style="color: #64748B;">/100</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="cp-score-label" :style="{ color: scoreColor }">{{ predictionLabel }}</div>
|
||||
<div class="cp-score-conf">Уверенность: <b>{{ confidence }}%</b></div>
|
||||
<div class="cp-score-conf" style="color: #64748B;">Уверенность: <b style="color: #0F172A;">{{ confidence }}%</b></div>
|
||||
<div class="cp-score-date">Обновлено: {{ formatDateTime(lastUpdated) }}</div>
|
||||
<button class="cp-btn-secondary mt-3" @click="refreshPrediction" :disabled="loading || generationRunning">
|
||||
<i :class="generationRunning ? 'pi pi-spin pi-spinner' : 'pi pi-refresh'"></i>
|
||||
<i :class="generationRunning ? 'pi pi-spin pi-spinner' : 'pi pi-sync'"></i>
|
||||
Обновить
|
||||
</button>
|
||||
</div>
|
||||
@@ -327,7 +327,7 @@ onMounted(() => { void hydratePrediction(); });
|
||||
<!-- WEEKLY CHART -->
|
||||
<div class="cp-card mb-4">
|
||||
<div class="cp-card__label">Прогноз по неделям</div>
|
||||
<div class="cp-chart-wrap">
|
||||
<div v-if="weeklyLabels.length" class="cp-chart-wrap">
|
||||
<Chart type="bar" :data="weeklyChartData" :options="weeklyChartOptions" style="height:100%;" />
|
||||
</div>
|
||||
<div v-if="weeklyPhases.length" class="cp-phases-grid mt-4">
|
||||
@@ -341,16 +341,16 @@ onMounted(() => { void hydratePrediction(); });
|
||||
<!-- STRENGTHS & RISKS -->
|
||||
<div class="cp-grid-2 mb-4">
|
||||
<div class="cp-card cp-card--green">
|
||||
<div class="cp-card__label" style="color:#00D48B;">Сильные стороны</div>
|
||||
<div class="cp-card__label" style="color:#059669;">Сильные стороны</div>
|
||||
<ul class="cp-factor-list">
|
||||
<li v-for="(factor, idx) in prediction?.strengthFactors ?? []" :key="`s-${idx}`">
|
||||
<i class="pi pi-check" style="color:#00D48B;"></i> {{ factor }}
|
||||
<i class="pi pi-check" style="color:#10B981;"></i> {{ factor }}
|
||||
</li>
|
||||
<li v-if="!(prediction?.strengthFactors?.length)" class="cp-factor-empty">—</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="cp-card cp-card--amber">
|
||||
<div class="cp-card__label" style="color:#F59E0B;">Риски</div>
|
||||
<div class="cp-card__label" style="color:#D97706;">Риски</div>
|
||||
<ul class="cp-factor-list">
|
||||
<li v-for="(factor, idx) in prediction?.riskFactors ?? []" :key="`r-${idx}`">
|
||||
<i class="pi pi-exclamation-triangle" style="color:#F59E0B;"></i> {{ factor }}
|
||||
@@ -415,8 +415,8 @@ onMounted(() => { void hydratePrediction(); });
|
||||
</div>
|
||||
</div>
|
||||
<div class="cp-cpl-reduction">
|
||||
<i class="pi pi-arrow-down-right" style="color:#00D48B;"></i>
|
||||
Потенциальное снижение CPL: <b>{{ budgetOptimization.potentialCplReduction || '—' }}</b>
|
||||
<i class="pi pi-arrow-down-right" style="color:#059669;"></i>
|
||||
Потенциальное снижение CPL: <b style="color: #059669;">{{ budgetOptimization.potentialCplReduction || '—' }}</b>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -427,7 +427,7 @@ onMounted(() => { void hydratePrediction(); });
|
||||
<div class="cp-comp-gauge">
|
||||
<svg width="144" height="144" viewBox="0 0 190 190">
|
||||
<g transform="translate(95,95)">
|
||||
<circle r="70" fill="none" stroke="rgba(255,255,255,0.06)" stroke-width="12" />
|
||||
<circle r="70" fill="none" stroke="rgba(45,123,255,0.06)" stroke-width="12" />
|
||||
<circle
|
||||
r="70"
|
||||
fill="none"
|
||||
@@ -446,12 +446,12 @@ onMounted(() => { void hydratePrediction(); });
|
||||
<div class="cp-comp-info">
|
||||
<div class="cp-comp-saturation">
|
||||
Насыщенность рынка:
|
||||
<span :style="{ color: marketSaturationMeta.color, fontWeight: '700' }">{{ marketSaturationMeta.label }}</span>
|
||||
<span :style="{ color: marketSaturationMeta.color, fontWeight: '800' }">{{ marketSaturationMeta.label }}</span>
|
||||
</div>
|
||||
<div class="cp-card__label mt-3" style="margin-bottom:8px;">Конкурентные преимущества</div>
|
||||
<ul class="cp-factor-list">
|
||||
<li v-for="(advantage, index) in competitiveAdvantages" :key="`adv-${index}`">
|
||||
<i class="pi pi-check" style="color:#2D7BFF;"></i> {{ advantage }}
|
||||
<i class="pi pi-check-circle" style="color:#2D7BFF;"></i> {{ advantage }}
|
||||
</li>
|
||||
<li v-if="!competitiveAdvantages.length" class="cp-factor-empty">—</li>
|
||||
</ul>
|
||||
@@ -470,25 +470,26 @@ onMounted(() => { void hydratePrediction(); });
|
||||
|
||||
/* ─── Cards ─── */
|
||||
.cp-card {
|
||||
background: rgba(255, 255, 255, 0.04);
|
||||
background: #fff;
|
||||
border: 1px solid rgba(45, 123, 255, 0.15);
|
||||
border-radius: 20px;
|
||||
padding: 24px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
transition: border-color 0.3s;
|
||||
transition: all 0.3s;
|
||||
box-shadow: 0 4px 12px rgba(45, 123, 255, 0.04);
|
||||
}
|
||||
|
||||
.cp-card--green { border-color: rgba(0, 212, 139, 0.2); background: rgba(0, 212, 139, 0.04); }
|
||||
.cp-card--green { border-color: rgba(16, 185, 129, 0.2); background: rgba(16, 185, 129, 0.04); }
|
||||
.cp-card--amber { border-color: rgba(245, 158, 11, 0.2); background: rgba(245, 158, 11, 0.04); }
|
||||
|
||||
.cp-card__label {
|
||||
font-family: 'Unbounded', sans-serif;
|
||||
font-family: 'Onest', sans-serif;
|
||||
font-size: 10px;
|
||||
font-weight: 700;
|
||||
font-weight: 800;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.1em;
|
||||
color: rgba(255, 255, 255, 0.35);
|
||||
color: #64748B;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
@@ -547,29 +548,29 @@ onMounted(() => { void hydratePrediction(); });
|
||||
}
|
||||
|
||||
.cp-score-val {
|
||||
font-family: 'Unbounded', sans-serif;
|
||||
font-size: 36px;
|
||||
font-family: 'Onest', sans-serif;
|
||||
font-size: 42px;
|
||||
font-weight: 900;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.cp-score-sub {
|
||||
font-size: 12px;
|
||||
color: rgba(255, 255, 255, 0.35);
|
||||
font-weight: 600;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.cp-score-label {
|
||||
font-family: 'Unbounded', sans-serif;
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
font-family: 'Onest', sans-serif;
|
||||
font-size: 14px;
|
||||
font-weight: 800;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.cp-score-conf, .cp-score-date {
|
||||
font-size: 12px;
|
||||
color: rgba(255, 255, 255, 0.4);
|
||||
font-size: 11px;
|
||||
margin-bottom: 2px;
|
||||
font-weight: 500;
|
||||
color: #64748B;
|
||||
}
|
||||
|
||||
/* ─── Metrics ─── */
|
||||
@@ -587,27 +588,27 @@ onMounted(() => { void hydratePrediction(); });
|
||||
}
|
||||
|
||||
.cp-metric-name {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: rgba(255, 255, 255, 0.65);
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
color: #475569;
|
||||
}
|
||||
|
||||
.cp-metric-icon {
|
||||
font-size: 12px;
|
||||
margin-right: 8px;
|
||||
opacity: 0.85;
|
||||
color: #2D7BFF;
|
||||
}
|
||||
|
||||
.cp-metric-range {
|
||||
font-family: 'Unbounded', sans-serif;
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
color: rgba(255, 255, 255, 0.85);
|
||||
font-family: 'Onest', sans-serif;
|
||||
font-size: 12px;
|
||||
font-weight: 800;
|
||||
color: #0F172A;
|
||||
}
|
||||
|
||||
:deep(.cp-progress .p-progressbar) {
|
||||
height: 6px !important;
|
||||
background: rgba(255, 255, 255, 0.06) !important;
|
||||
background: #F1F5F9 !important;
|
||||
border-radius: 999px !important;
|
||||
border: none !important;
|
||||
}
|
||||
@@ -619,10 +620,11 @@ onMounted(() => { void hydratePrediction(); });
|
||||
|
||||
/* ─── Chart ─── */
|
||||
.cp-chart-wrap {
|
||||
height: 260px;
|
||||
background: rgba(255, 255, 255, 0.02);
|
||||
height: 280px;
|
||||
background: rgba(45, 123, 255, 0.02);
|
||||
border-radius: 14px;
|
||||
padding: 12px;
|
||||
padding: 16px;
|
||||
border: 1px dashed rgba(45, 123, 255, 0.1);
|
||||
}
|
||||
|
||||
.cp-phases-grid {
|
||||
@@ -637,7 +639,7 @@ onMounted(() => { void hydratePrediction(); });
|
||||
|
||||
.cp-phase-item {
|
||||
padding: 10px 14px;
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
background: #fff;
|
||||
border: 1px solid rgba(45, 123, 255, 0.12);
|
||||
border-radius: 12px;
|
||||
}
|
||||
@@ -646,15 +648,16 @@ onMounted(() => { void hydratePrediction(); });
|
||||
font-size: 10px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
color: rgba(255, 255, 255, 0.35);
|
||||
color: #94A3B8;
|
||||
margin-bottom: 4px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.cp-phase-val {
|
||||
font-family: 'Unbounded', sans-serif;
|
||||
font-family: 'Onest', sans-serif;
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
color: rgba(255, 255, 255, 0.85);
|
||||
font-weight: 800;
|
||||
color: #0F172A;
|
||||
}
|
||||
|
||||
/* ─── Factors ─── */
|
||||
@@ -672,13 +675,14 @@ onMounted(() => { void hydratePrediction(); });
|
||||
align-items: flex-start;
|
||||
gap: 8px;
|
||||
font-size: 13px;
|
||||
color: rgba(255, 255, 255, 0.65);
|
||||
color: #475569;
|
||||
line-height: 1.5;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.cp-factor-empty {
|
||||
font-size: 12px;
|
||||
color: rgba(255, 255, 255, 0.25);
|
||||
color: #94A3B8;
|
||||
}
|
||||
|
||||
/* ─── Recommendations ─── */
|
||||
@@ -690,14 +694,15 @@ onMounted(() => { void hydratePrediction(); });
|
||||
|
||||
.cp-rec-item {
|
||||
padding: 18px;
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
background: #fff;
|
||||
border: 1px solid rgba(45, 123, 255, 0.12);
|
||||
border-radius: 16px;
|
||||
transition: border-color 0.2s;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.cp-rec-item:hover {
|
||||
border-color: rgba(45, 123, 255, 0.3);
|
||||
box-shadow: 0 4px 15px rgba(45, 123, 255, 0.06);
|
||||
}
|
||||
|
||||
.cp-rec-badges {
|
||||
@@ -709,8 +714,8 @@ onMounted(() => { void hydratePrediction(); });
|
||||
|
||||
.cp-rec-priority {
|
||||
font-size: 9px;
|
||||
font-weight: 700;
|
||||
font-family: 'Unbounded', sans-serif;
|
||||
font-weight: 800;
|
||||
font-family: 'Onest', sans-serif;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.08em;
|
||||
padding: 3px 10px;
|
||||
@@ -723,27 +728,27 @@ onMounted(() => { void hydratePrediction(); });
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
color: rgba(45, 123, 255, 0.8);
|
||||
background: rgba(45, 123, 255, 0.1);
|
||||
font-weight: 700;
|
||||
color: #2D7BFF;
|
||||
background: rgba(45, 123, 255, 0.08);
|
||||
border-radius: 999px;
|
||||
padding: 3px 10px;
|
||||
}
|
||||
|
||||
.cp-rec-title {
|
||||
font-family: 'Unbounded', sans-serif;
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
margin-bottom: 6px;
|
||||
font-family: 'Onest', sans-serif;
|
||||
font-size: 15px;
|
||||
font-weight: 800;
|
||||
color: #0F172A;
|
||||
margin-bottom: 8px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.cp-rec-desc {
|
||||
font-size: 13px;
|
||||
color: rgba(255, 255, 255, 0.55);
|
||||
color: #64748B;
|
||||
line-height: 1.6;
|
||||
margin-bottom: 8px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.cp-rec-impact {
|
||||
@@ -751,21 +756,22 @@ onMounted(() => { void hydratePrediction(); });
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: #00D48B;
|
||||
font-weight: 800;
|
||||
color: #059669;
|
||||
}
|
||||
|
||||
/* ─── Info ─── */
|
||||
.cp-info-text {
|
||||
font-size: 13px;
|
||||
color: rgba(255, 255, 255, 0.65);
|
||||
font-size: 14px;
|
||||
color: #475569;
|
||||
line-height: 1.6;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.cp-info-block {
|
||||
padding: 14px;
|
||||
background: rgba(255, 255, 255, 0.02);
|
||||
border: 1px solid rgba(45, 123, 255, 0.1);
|
||||
background: rgba(248, 250, 252, 0.8);
|
||||
border: 1px solid #E2E8F0;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
@@ -773,15 +779,16 @@ onMounted(() => { void hydratePrediction(); });
|
||||
font-size: 10px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.06em;
|
||||
color: rgba(255, 255, 255, 0.35);
|
||||
color: #94A3B8;
|
||||
margin-bottom: 6px;
|
||||
font-weight: 700;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.cp-info-block__val {
|
||||
font-size: 13px;
|
||||
color: rgba(255, 255, 255, 0.75);
|
||||
color: #1E293B;
|
||||
line-height: 1.5;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.cp-cpl-reduction {
|
||||
@@ -790,15 +797,79 @@ onMounted(() => { void hydratePrediction(); });
|
||||
gap: 8px;
|
||||
margin-top: 12px;
|
||||
padding: 12px 16px;
|
||||
background: rgba(0, 212, 139, 0.07);
|
||||
border: 1px solid rgba(0, 212, 139, 0.2);
|
||||
background: rgba(16, 185, 129, 0.08);
|
||||
border: 1px solid rgba(16, 185, 129, 0.2);
|
||||
border-radius: 12px;
|
||||
font-size: 13px;
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
color: #065F46;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.cp-cpl-reduction b {
|
||||
color: #00D48B;
|
||||
/* ─── Competitive ─── */
|
||||
.cp-comp-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.cp-comp-gauge {
|
||||
position: relative;
|
||||
width: 144px;
|
||||
height: 144px;
|
||||
}
|
||||
|
||||
.cp-comp-gauge__val {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-family: 'Onest', sans-serif;
|
||||
font-size: 20px;
|
||||
font-weight: 900;
|
||||
color: #0F172A;
|
||||
}
|
||||
|
||||
.cp-comp-saturation {
|
||||
font-size: 14px;
|
||||
color: #64748B;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
/* ─── Misc ─── */
|
||||
.cp-empty {
|
||||
padding: 60px 40px;
|
||||
text-align: center;
|
||||
background: #fff;
|
||||
border-radius: 24px;
|
||||
border: 1px dashed rgba(45, 123, 255, 0.3);
|
||||
}
|
||||
|
||||
.cp-empty__icon { font-size: 40px; color: #E2E8F0; margin-bottom: 20px; }
|
||||
.cp-empty__title { font-family: 'Onest', sans-serif; font-size: 20px; font-weight: 900; color: #0F172A; margin-bottom: 8px; }
|
||||
.cp-empty__sub { font-size: 14px; color: #94A3B8; margin-bottom: 24px; }
|
||||
|
||||
.cp-btn-primary {
|
||||
display: inline-flex; align-items: center; gap: 10px;
|
||||
padding: 12px 28px; border-radius: 12px; border: none;
|
||||
background: #2D7BFF; color: #fff; font-weight: 800;
|
||||
font-family: 'Onest', sans-serif; cursor: pointer; transition: all 0.2s;
|
||||
}
|
||||
.cp-btn-primary:hover { transform: translateY(-2px); box-shadow: 0 4px 15px rgba(45,123,255,0.4); }
|
||||
|
||||
.cp-btn-secondary {
|
||||
display: inline-flex; align-items: center; gap: 8px;
|
||||
padding: 10px 20px; border-radius: 10px; border: 1px solid #E2E8F0;
|
||||
background: #fff; color: #475569; font-weight: 700;
|
||||
font-family: 'Onest', sans-serif; font-size: 12px; cursor: pointer; transition: all 0.2s;
|
||||
}
|
||||
.cp-btn-secondary:hover { border-color: #2D7BFF; color: #2D7BFF; }
|
||||
|
||||
.cp-skel-block {
|
||||
background: #F8FAFF !important;
|
||||
animation: cm-pulse 2s infinite;
|
||||
}
|
||||
</style> color: #00D48B;
|
||||
}
|
||||
|
||||
/* ─── Competitive ─── */
|
||||
|
||||
@@ -154,8 +154,13 @@ const handleAccountSelection = async () => {
|
||||
|
||||
<style scoped>
|
||||
.oauth-card {
|
||||
background: #fff;
|
||||
border: 1px solid rgba(45,123,255,0.12);
|
||||
border-radius: 24px;
|
||||
padding: 32px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 4px 12px rgba(45,123,255,0.05);
|
||||
}
|
||||
|
||||
.oauth-glow {
|
||||
@@ -165,40 +170,41 @@ const handleAccountSelection = async () => {
|
||||
width: 140px;
|
||||
height: 140px;
|
||||
filter: blur(40px);
|
||||
opacity: 0.3;
|
||||
opacity: 0.15;
|
||||
transition: opacity 0.5s;
|
||||
pointer-events: none;
|
||||
}
|
||||
.group:hover .oauth-glow { opacity: 0.5; }
|
||||
.group:hover .oauth-glow { opacity: 0.25; }
|
||||
|
||||
.oauth-glow.blue-glow { background: var(--kai-blue); }
|
||||
.oauth-glow.rose-glow { background: #ff2d55; }
|
||||
.oauth-glow.blue { background: #2D7BFF; }
|
||||
.oauth-glow.rose { background: #EF4444; }
|
||||
|
||||
.oauth-icon {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
border-radius: 20px;
|
||||
background: rgba(255,255,255,0.03);
|
||||
border: 1px solid var(--kai-border);
|
||||
background: #fff;
|
||||
border: 1px solid rgba(45,123,255,0.15);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 28px;
|
||||
transition: all 0.3s;
|
||||
box-shadow: 0 4px 10px rgba(0,0,0,0.03);
|
||||
}
|
||||
.oauth-icon.blue-glow { color: #1877F2; }
|
||||
.oauth-icon.rose-glow { color: #fff; }
|
||||
.oauth-icon.blue { color: #1877F2; }
|
||||
.oauth-icon.rose { color: #000; }
|
||||
|
||||
.oauth-title {
|
||||
font-family: 'Unbounded', sans-serif;
|
||||
font-family: 'Onest', sans-serif;
|
||||
font-size: 20px;
|
||||
font-weight: 800;
|
||||
color: #fff;
|
||||
color: #0F172A;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.oauth-sub {
|
||||
font-size: 13px;
|
||||
color: var(--kai-txt3);
|
||||
color: #64748B;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
@@ -207,33 +213,33 @@ const handleAccountSelection = async () => {
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 10px 16px;
|
||||
background: rgba(0,212,139,0.08);
|
||||
border: 1px solid rgba(0,212,139,0.2);
|
||||
background: rgba(16,185,129,0.08);
|
||||
border: 1px solid rgba(16,185,129,0.2);
|
||||
border-radius: 14px;
|
||||
color: var(--kai-green);
|
||||
color: #10B981;
|
||||
}
|
||||
.oauth-connected i { font-size: 18px; }
|
||||
|
||||
.oauth-dialog :deep(.p-dialog-header) {
|
||||
background: var(--kai-card);
|
||||
color: #fff;
|
||||
font-family: 'Unbounded', sans-serif;
|
||||
background: #fff;
|
||||
color: #0F172A;
|
||||
font-family: 'Onest', sans-serif;
|
||||
font-size: 18px;
|
||||
padding: 24px;
|
||||
}
|
||||
.oauth-dialog :deep(.p-dialog-content) {
|
||||
background: var(--kai-card);
|
||||
background: #fff;
|
||||
padding: 0 24px 24px;
|
||||
}
|
||||
.oauth-dialog :deep(.p-dialog-footer) {
|
||||
background: var(--kai-card);
|
||||
border-top: 1px solid var(--kai-border);
|
||||
background: #fff;
|
||||
border-top: 1px solid #f1f5f9;
|
||||
padding: 16px 24px;
|
||||
}
|
||||
|
||||
.oauth-dialog-sub {
|
||||
font-size: 13px;
|
||||
color: var(--kai-txt3);
|
||||
color: #64748B;
|
||||
margin-bottom: 20px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
@@ -243,47 +249,47 @@ const handleAccountSelection = async () => {
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 14px;
|
||||
background: rgba(255,255,255,0.02);
|
||||
border: 1px solid var(--kai-border);
|
||||
background: #f8fafc;
|
||||
border: 1px solid #e2e8f0;
|
||||
border-radius: 14px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
.oauth-acc-item:hover {
|
||||
background: rgba(255,255,255,0.04);
|
||||
border-color: var(--kai-border-hover);
|
||||
background: #f1f5f9;
|
||||
border-color: #cbd5e1;
|
||||
}
|
||||
.oauth-acc-item--active {
|
||||
background: rgba(45,123,255,0.08);
|
||||
border-color: var(--kai-blue);
|
||||
border-color: #2D7BFF;
|
||||
}
|
||||
.oauth-acc-id {
|
||||
font-size: 10px;
|
||||
background: rgba(255,255,255,0.05);
|
||||
background: rgba(0,0,0,0.05);
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
color: var(--kai-txt3);
|
||||
color: #64748B;
|
||||
}
|
||||
|
||||
/* Reuse from global/others */
|
||||
.td-launch-btn {
|
||||
background: #fff;
|
||||
color: #000;
|
||||
font-family: 'Unbounded', sans-serif;
|
||||
background: #f1f5f9;
|
||||
color: #0F172A;
|
||||
font-family: 'Onest', sans-serif;
|
||||
font-weight: 700;
|
||||
font-size: 12px;
|
||||
padding: 12px 24px;
|
||||
border-radius: 12px;
|
||||
border: none;
|
||||
border: 1px solid #e2e8f0;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
.td-launch-btn:hover:not(:disabled) {
|
||||
background: #e2e8f0;
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 0 20px rgba(255,255,255,0.2);
|
||||
}
|
||||
.td-launch-btn:disabled { opacity: 0.5; cursor: not-allowed; }
|
||||
|
||||
.wiz-btn-pri { background: #fff; color: #000; border: none; font-family: 'Unbounded', sans-serif; font-weight: 700; border-radius: 8px; cursor: pointer; }
|
||||
.wiz-btn-sec { background: transparent; border: 1px solid var(--kai-border); color: var(--kai-txt2); font-weight: 600; border-radius: 8px; cursor: pointer; }
|
||||
.wiz-btn-pri { background: #2D7BFF; color: #fff; border: none; font-family: 'Onest', sans-serif; font-weight: 700; border-radius: 8px; cursor: pointer; }
|
||||
.wiz-btn-sec { background: transparent; border: 1px solid #e2e8f0; color: #64748B; font-weight: 600; border-radius: 8px; cursor: pointer; }
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user