From c1d429cb0533bd7963d65e08bcd3c982b8653d88 Mon Sep 17 00:00:00 2001 From: root Date: Sun, 14 Dec 2025 21:54:20 +0500 Subject: [PATCH] . --- .../pages/marketing/MarketingAnalysis.vue | 1026 ++++++++++++++++- Промпт для Cursor (Vue.js).md | 1 + 2 files changed, 1008 insertions(+), 19 deletions(-) create mode 100644 Промпт для Cursor (Vue.js).md diff --git a/src/views/pages/marketing/MarketingAnalysis.vue b/src/views/pages/marketing/MarketingAnalysis.vue index f05fc73..ece6631 100644 --- a/src/views/pages/marketing/MarketingAnalysis.vue +++ b/src/views/pages/marketing/MarketingAnalysis.vue @@ -184,6 +184,16 @@ + +
+
+

Краткое резюме

+
+
+
+
+
+
@@ -215,64 +225,346 @@
-

Сегментация целевой аудитории

+

Сегментация целевой аудитории

-

По возрасту

+
+ +

По возрасту

+
-

По полу

+
+ +

По полу

+
+ + +
+
+ +

Матрица каналов по сегментам

+
+
+ + + + + + + + + + + + + +
Сегмент + {{ channel }} +
{{ row.segmentName }} + + {{ row[channel] || '-' }} + +
+
+
+ + +
+
+ +

Ключевые выводы

+
+
+
    +
  • + + {{ takeaway }} +
  • +
+
+
-

Анализ конкурентов

+

Анализ конкурентов

+ +
+
+ +

{{ segment.marketShareChart.title }}

+
+
+ +
+
+ + +
+
+ +

Сравнительная таблица

+
+
+ + + + + + + + + + + + + + + + + +
ХарактеристикаМыКонкурент AКонкурент B
{{ row.feature }} + + + + + +
+
+
-
-
-

{{ competitor.name }}

-
-
- Охват: +
+
+

{{ competitor.name }}

+
+
+ Охват: {{ competitor.reach?.toLocaleString() || 0 }}
-
- Отзывы: - {{ competitor.reviews?.toLocaleString() || 0 }} +
+ Подписчики: + {{ competitor.followers?.toLocaleString() || 0 }}
-
- Активность: +
+ Активность: {{ competitor.activity }}%
+
+ Ценовая стратегия: + +
-

Сильные стороны:

+

Сильные стороны:

    -
  • {{ strength }}
  • +
  • + + {{ strength }} +
-

Слабые стороны:

+

Слабые стороны:

    -
  • {{ weakness }}
  • +
  • + + {{ weakness }} +
+ + +
+
+ +

Тепловая карта каналов конкурентов

+
+
+ + + + + + + + + + + + + +
Конкурент + {{ channel }} +
{{ compData.name }} + + {{ compData.channelsHeatmap[channel] || 0 }} + +
+
+
+
+
+ + +
+
+

{{ segment.title }}

+
+
+
+ +
+
+
+ + +
+
+

Сравнительная таблица

+
+
+
+ + + + + + + + + + + + + + + + + +
ХарактеристикаМыКонкурент AКонкурент B
{{ row.feature }} + + + + + +
+
+
+
+ + +
+
+

SWOT-анализ конкурентов

+
+
+
+
+
+

Сильные стороны

+
    +
  • + + {{ strength }} +
  • +
+
+
+
+
+

Слабые стороны

+
    +
  • + + {{ weakness }} +
  • +
+
+
+
+
+
+ + +
+
+

Анализ рынка

+
+
+
+
+
+
+ +

Размер рынка

+
+

{{ segment.data.size || 'Не указано' }}

+
+
+
+
+
+ +

Темп роста

+
+

{{ segment.data.growthRate || 'Не указано' }}

+
+
+
+ +
+
+ +

Тренды

+
+
+
    +
  • + + {{ trend }} +
  • +
+
+
+ +
+
+
+

Возможности

+
    +
  • {{ opportunity }}
  • +
+
+
+
+
+

Угрозы

+
    +
  • {{ threat }}
  • +
+
+
+
@@ -944,6 +1236,246 @@ const buildAudienceSegmentationChartData = (segmentation) => { return { ageGroupsChartData, gendersChartData }; }; +// Build segments channel matrix data for heatmap +const buildSegmentsChannelMatrix = (matrix) => { + if (!Array.isArray(matrix) || matrix.length === 0) return null; + + // Extract all unique channels from matrix + const channels = new Set(); + matrix.forEach((row) => { + Object.keys(row).forEach((key) => { + if (key !== 'segmentName') { + channels.add(key); + } + }); + }); + + return { + segments: matrix.map((row) => row.segmentName), + channels: Array.from(channels), + data: matrix + }; +}; + +// Build market share chart data +const buildMarketShareChartData = (marketShareChart) => { + if (!Array.isArray(marketShareChart) || marketShareChart.length === 0) return null; + const colors = ['#3B82F6', '#10B981', '#F59E0B', '#EF4444', '#8B5CF6', '#EC4899', '#F97316', '#06B6D4']; + + return { + labels: marketShareChart.map((item) => item.name), + datasets: [ + { + data: marketShareChart.map((item) => item.value), + backgroundColor: marketShareChart.map((_, idx) => colors[idx % colors.length]), + borderColor: '#fff', + borderWidth: 2 + } + ] + }; +}; + +// Build competitors heatmap data +const buildCompetitorsHeatmapData = (competitors) => { + if (!Array.isArray(competitors) || competitors.length === 0) return null; + + const channels = new Set(); + competitors.forEach((comp) => { + if (comp.channelsHeatmap && typeof comp.channelsHeatmap === 'object') { + Object.keys(comp.channelsHeatmap).forEach((channel) => channels.add(channel)); + } + }); + + return { + competitors: competitors.map((comp) => comp.name), + channels: Array.from(channels), + data: competitors.map((comp) => ({ + name: comp.name, + channelsHeatmap: comp.channelsHeatmap || {} + })) + }; +}; + +// Get heatmap color intensity based on value (0-100) +const getHeatmapColor = (value) => { + if (value >= 80) return 'rgba(16, 185, 129, 0.9)'; // green-500 + if (value >= 60) return 'rgba(245, 158, 11, 0.9)'; // amber-500 + if (value >= 40) return 'rgba(251, 191, 36, 0.9)'; // yellow-500 + if (value >= 20) return 'rgba(249, 115, 22, 0.9)'; // orange-500 + return 'rgba(239, 68, 68, 0.9)'; // red-500 +}; + +// Get channel matrix color based on value (high/medium/low) +const getChannelMatrixColor = (value) => { + if (!value) return 'rgba(156, 163, 175, 0.5)'; // gray + const normalizedValue = String(value).toLowerCase(); + if (normalizedValue === 'high' || normalizedValue === 'высокий' || normalizedValue === 'высокая') return 'rgba(16, 185, 129, 0.9)'; // green + if (normalizedValue === 'medium' || normalizedValue === 'средний' || normalizedValue === 'средняя') return 'rgba(245, 158, 11, 0.9)'; // amber + if (normalizedValue === 'low' || normalizedValue === 'низкий' || normalizedValue === 'низкая') return 'rgba(239, 68, 68, 0.9)'; // red + return 'rgba(156, 163, 175, 0.5)'; // gray +}; + +// Main function to map chartsData to segments +const mapChartsDataToSegments = (chartsData) => { + if (!chartsData || typeof chartsData !== 'object') return []; + + const segments = []; + + // 1. Audience Segmentation + if (chartsData.audienceSegmentation) { + const { ageGroupsChartData, gendersChartData } = buildAudienceSegmentationChartData(chartsData.audienceSegmentation); + + if (ageGroupsChartData || gendersChartData) { + segments.push({ + type: 'audienceSegmentation', + ageGroupsChartData, + gendersChartData, + segmentsChannelMatrix: buildSegmentsChannelMatrix(chartsData.audienceSegmentation.segmentsChannelMatrix), + keyTakeaways: chartsData.audienceSegmentation.keyTakeaways || [] + }); + } + } + + // 2. Market Share Chart (in root of chartsData) + if (chartsData.marketShareChart && Array.isArray(chartsData.marketShareChart)) { + const marketShareChartData = buildMarketShareChartData(chartsData.marketShareChart); + if (marketShareChartData) { + segments.push({ + type: 'marketShareChart', + title: 'Доли рынка', + chartType: 'pie', + data: marketShareChartData, + options: pieChartOptions, + chartHeight: 'height: 300px' + }); + } + } + + // 3. Competitors Analysis + // Check if competitors is an array (new format) or object with competitors array (old format) + if (chartsData.competitors) { + let competitorsArray = null; + + if (Array.isArray(chartsData.competitors)) { + // New format: competitors is directly an array + competitorsArray = chartsData.competitors; + } else if (chartsData.competitors.competitors && Array.isArray(chartsData.competitors.competitors)) { + // Old format: competitors is an object with competitors array inside + competitorsArray = chartsData.competitors.competitors; + } + + if (competitorsArray && competitorsArray.length > 0) { + segments.push({ + type: 'competitors', + competitors: competitorsArray, + competitorsHeatmap: buildCompetitorsHeatmapData(competitorsArray) + }); + } + + // SWOT Competitors (if exists in competitors object) + if (chartsData.competitors.swotCompetitors && typeof chartsData.competitors.swotCompetitors === 'object') { + segments.push({ + type: 'swotCompetitors', + data: { + strengths: chartsData.competitors.swotCompetitors.strengths || [], + weaknesses: chartsData.competitors.swotCompetitors.weaknesses || [] + } + }); + } + } + + // 4. Comparison Table (in root of chartsData) + if (chartsData.comparisonTable && Array.isArray(chartsData.comparisonTable) && chartsData.comparisonTable.length > 0) { + segments.push({ + type: 'comparisonTable', + data: chartsData.comparisonTable + }); + } + + // 5. SWOT Competitors (in root of chartsData, if exists) + if (chartsData.swotCompetitors && typeof chartsData.swotCompetitors === 'object') { + segments.push({ + type: 'swotCompetitors', + data: { + strengths: chartsData.swotCompetitors.strengths || [], + weaknesses: chartsData.swotCompetitors.weaknesses || [] + } + }); + } + + // 6. Seasonality + if (chartsData.seasonality && typeof chartsData.seasonality === 'object') { + const chartData = buildSeasonalityChartData(chartsData.seasonality); + if (chartData) { + segments.push({ + type: 'chart', + key: 'seasonality', + title: 'Сезонность спроса', + chartType: 'line', + data: chartData, + options: seasonalityChartOptions, + chartHeight: 'height: 300px' + }); + } + } + + // 7. Market + if (chartsData.market && typeof chartsData.market === 'object') { + segments.push({ + type: 'market', + data: chartsData.market + }); + } + + // 8. Conversion Funnel (if exists for backward compatibility) + if (chartsData.conversionFunnel && Array.isArray(chartsData.conversionFunnel)) { + const chartData = buildConversionFunnelChartData(chartsData.conversionFunnel); + if (chartData) { + segments.push({ + type: 'chart', + key: 'funnel', + title: 'Воронка конверсии', + chartType: 'bar', + data: chartData, + options: conversionFunnelChartOptions, + chartHeight: 'height: 400px' + }); + } + } + + // 9. Channels Potential (if exists for backward compatibility) + if (chartsData.channelsPotential && Array.isArray(chartsData.channelsPotential)) { + const chartData = buildChannelsPotentialChartData(chartsData.channelsPotential); + if (chartData) { + segments.push({ + type: 'chart', + key: 'channelsPotential', + title: 'Потенциал каналов продвижения', + chartType: 'bar', + data: chartData, + options: channelsPotentialChartOptions, + chartHeight: 'height: 300px', + channels: chartsData.channelsPotential + }); + } + } + + // 10. SWOT (if exists for backward compatibility) + if (chartsData.swot && typeof chartsData.swot === 'object') { + segments.push({ + type: 'swot', + data: { + strengths: chartsData.swot.strengths || [], + weaknesses: chartsData.swot.weaknesses || [], + opportunities: chartsData.swot.opportunities || [], + threats: chartsData.swot.threats || [] + } + }); + } + + return segments; +}; + const mapJsonBlockToSegment = (identifier, data, rawText) => { const key = identifier?.toLowerCase(); switch (key) { @@ -1025,7 +1557,347 @@ const mapJsonBlockToSegment = (identifier, data, rawText) => { } }; +// Helper function to get chart segment by placeholder +const getChartSegmentByPlaceholder = (placeholder, chartsData) => { + if (!chartsData) return null; + + switch (placeholder) { + case 'CHART_AUDIENCE': + if (chartsData.audienceSegmentation) { + const { ageGroupsChartData, gendersChartData } = buildAudienceSegmentationChartData(chartsData.audienceSegmentation); + if (ageGroupsChartData || gendersChartData) { + return { + type: 'audienceSegmentation', + ageGroupsChartData, + gendersChartData, + segmentsChannelMatrix: buildSegmentsChannelMatrix(chartsData.audienceSegmentation.segmentsChannelMatrix), + keyTakeaways: chartsData.audienceSegmentation.keyTakeaways || [] + }; + } + } + return null; + + case 'CHART_COMPETITORS': + const competitorsArray = Array.isArray(chartsData.competitors) ? chartsData.competitors : chartsData.competitors?.competitors || []; + + if (competitorsArray.length > 0) { + const segment = { + type: 'competitors', + competitors: competitorsArray, + competitorsHeatmap: buildCompetitorsHeatmapData(competitorsArray) + }; + + // Add marketShareChart if exists + if (chartsData.marketShareChart && Array.isArray(chartsData.marketShareChart)) { + const marketShareChartData = buildMarketShareChartData(chartsData.marketShareChart); + if (marketShareChartData) { + segment.marketShareChart = { + type: 'marketShareChart', + title: 'Доли рынка', + chartType: 'pie', + data: marketShareChartData, + options: pieChartOptions, + chartHeight: 'height: 300px' + }; + } + } + + // Add comparisonTable if exists + if (chartsData.comparisonTable && Array.isArray(chartsData.comparisonTable) && chartsData.comparisonTable.length > 0) { + segment.comparisonTable = { + type: 'comparisonTable', + data: chartsData.comparisonTable + }; + } + + return segment; + } + return null; + + case 'CHART_SWOT': + // Check for swotCompetitors first + if (chartsData.swotCompetitors && typeof chartsData.swotCompetitors === 'object') { + return { + type: 'swotCompetitors', + data: { + strengths: chartsData.swotCompetitors.strengths || [], + weaknesses: chartsData.swotCompetitors.weaknesses || [] + } + }; + } + // Fallback to swot + if (chartsData.swot && typeof chartsData.swot === 'object') { + return { + type: 'swot', + data: { + strengths: chartsData.swot.strengths || [], + weaknesses: chartsData.swot.weaknesses || [], + opportunities: chartsData.swot.opportunities || [], + threats: chartsData.swot.threats || [] + } + }; + } + return null; + + case 'CHART_CHANNELS': + if (chartsData.channelsPotential && Array.isArray(chartsData.channelsPotential)) { + const chartData = buildChannelsPotentialChartData(chartsData.channelsPotential); + if (chartData) { + return { + type: 'chart', + key: 'channelsPotential', + title: 'Потенциал каналов продвижения', + chartType: 'bar', + data: chartData, + options: channelsPotentialChartOptions, + chartHeight: 'height: 300px', + channels: chartsData.channelsPotential + }; + } + } + return null; + + case 'CHART_FUNNEL': + if (chartsData.conversionFunnel && Array.isArray(chartsData.conversionFunnel)) { + const chartData = buildConversionFunnelChartData(chartsData.conversionFunnel); + if (chartData) { + return { + type: 'chart', + key: 'funnel', + title: 'Воронка конверсии', + chartType: 'bar', + data: chartData, + options: conversionFunnelChartOptions, + chartHeight: 'height: 400px' + }; + } + } + return null; + + default: + return null; + } +}; + +// Helper function to get chart segment by JSON block identifier +const getChartSegmentByIdentifier = (identifier, chartsData) => { + if (!chartsData) return null; + const key = identifier?.toLowerCase(); + + switch (key) { + case 'audiencesegmentation': + if (chartsData.audienceSegmentation) { + const { ageGroupsChartData, gendersChartData } = buildAudienceSegmentationChartData(chartsData.audienceSegmentation); + if (ageGroupsChartData || gendersChartData) { + return { + type: 'audienceSegmentation', + ageGroupsChartData, + gendersChartData, + segmentsChannelMatrix: buildSegmentsChannelMatrix(chartsData.audienceSegmentation.segmentsChannelMatrix), + keyTakeaways: chartsData.audienceSegmentation.keyTakeaways || [] + }; + } + } + return null; + + case 'competitors': + const competitorsArray = Array.isArray(chartsData.competitors) ? chartsData.competitors : chartsData.competitors?.competitors || []; + if (competitorsArray.length > 0) { + const segment = { + type: 'competitors', + competitors: competitorsArray, + competitorsHeatmap: buildCompetitorsHeatmapData(competitorsArray) + }; + + // Add marketShareChart if exists + if (chartsData.marketShareChart && Array.isArray(chartsData.marketShareChart)) { + const marketShareChartData = buildMarketShareChartData(chartsData.marketShareChart); + if (marketShareChartData) { + segment.marketShareChart = { + type: 'marketShareChart', + title: 'Доли рынка', + chartType: 'pie', + data: marketShareChartData, + options: pieChartOptions, + chartHeight: 'height: 300px' + }; + } + } + + // Add comparisonTable if exists + if (chartsData.comparisonTable && Array.isArray(chartsData.comparisonTable) && chartsData.comparisonTable.length > 0) { + segment.comparisonTable = { + type: 'comparisonTable', + data: chartsData.comparisonTable + }; + } + + return segment; + } + return null; + + case 'swot': + // Check for swotCompetitors first + if (chartsData.swotCompetitors && typeof chartsData.swotCompetitors === 'object') { + return { + type: 'swotCompetitors', + data: { + strengths: chartsData.swotCompetitors.strengths || [], + weaknesses: chartsData.swotCompetitors.weaknesses || [] + } + }; + } + // Fallback to swot + if (chartsData.swot && typeof chartsData.swot === 'object') { + return { + type: 'swot', + data: { + strengths: chartsData.swot.strengths || [], + weaknesses: chartsData.swot.weaknesses || [], + opportunities: chartsData.swot.opportunities || [], + threats: chartsData.swot.threats || [] + } + }; + } + return null; + + case 'channelspotential': + case 'channels': + if (chartsData.channelsPotential && Array.isArray(chartsData.channelsPotential)) { + const chartData = buildChannelsPotentialChartData(chartsData.channelsPotential); + if (chartData) { + return { + type: 'chart', + key: 'channelsPotential', + title: 'Потенциал каналов продвижения', + chartType: 'bar', + data: chartData, + options: channelsPotentialChartOptions, + chartHeight: 'height: 300px', + channels: chartsData.channelsPotential + }; + } + } + return null; + + case 'funnel': + case 'conversionfunnel': + if (chartsData.conversionFunnel && Array.isArray(chartsData.conversionFunnel)) { + const chartData = buildConversionFunnelChartData(chartsData.conversionFunnel); + if (chartData) { + return { + type: 'chart', + key: 'funnel', + title: 'Воронка конверсии', + chartType: 'bar', + data: chartData, + options: conversionFunnelChartOptions, + chartHeight: 'height: 400px' + }; + } + } + return null; + + case 'seasonality': + if (chartsData.seasonality && typeof chartsData.seasonality === 'object') { + const chartData = buildSeasonalityChartData(chartsData.seasonality); + if (chartData) { + return { + type: 'chart', + key: 'seasonality', + title: 'Сезонность спроса', + chartType: 'line', + data: chartData, + options: seasonalityChartOptions, + chartHeight: 'height: 300px' + }; + } + } + return null; + + default: + return null; + } +}; + +// Parse fullAnalysis and replace placeholders and JSON blocks with charts from chartsData +const parseFullAnalysisWithCharts = (fullAnalysis, chartsData) => { + if (!fullAnalysis) return []; + + const segments = []; + // Combined regex for both placeholders [[CHART_*]] and JSON blocks ```json:identifier + const combinedRegex = /(\[\[CHART_(\w+)\]\]|```json:(\w+)\n([\s\S]*?)\n```)/g; + let lastIndex = 0; + let match; + + while ((match = combinedRegex.exec(fullAnalysis)) !== null) { + const [fullMatch, , chartTypePlaceholder, jsonIdentifier, jsonBlock] = match; + + // Add markdown before match + if (match.index > lastIndex) { + const textBefore = fullAnalysis.slice(lastIndex, match.index); + if (textBefore.trim()) { + segments.push({ type: 'markdown', html: renderMarkdown(textBefore) }); + } + } + + // Handle placeholder [[CHART_*]] + if (chartTypePlaceholder) { + const chartSegment = getChartSegmentByPlaceholder(chartTypePlaceholder, chartsData); + if (chartSegment) { + segments.push(chartSegment); + } + } + // Handle JSON block ```json:identifier + else if (jsonIdentifier) { + const chartSegment = getChartSegmentByIdentifier(jsonIdentifier, chartsData); + if (chartSegment) { + segments.push(chartSegment); + } else { + // If no chart found, try to parse JSON and use old logic as fallback + try { + const parsedData = JSON.parse(jsonBlock); + const segment = mapJsonBlockToSegment(jsonIdentifier, parsedData, fullMatch); + if (segment) { + segments.push(segment); + } + } catch (error) { + console.error(`Не удалось распарсить блок ${jsonIdentifier}:`, error); + } + } + } + + lastIndex = combinedRegex.lastIndex; + } + + // Add remaining markdown + const remaining = fullAnalysis.slice(lastIndex); + if (remaining.trim()) { + segments.push({ type: 'markdown', html: renderMarkdown(remaining) }); + } + + return segments; +}; + +// New computed property for chartsData +const chartsDataSegments = computed(() => { + if (!report.value?.chartsData) return []; + return mapChartsDataToSegments(report.value.chartsData); +}); + +// Main parsedContent that combines fullAnalysis with chartsData const parsedContent = computed(() => { + // If we have both fullAnalysis and chartsData, parse fullAnalysis and inject charts + if (report.value?.fullAnalysis && report.value?.chartsData) { + return parseFullAnalysisWithCharts(report.value.fullAnalysis, report.value.chartsData); + } + + // If only chartsData exists, use it + if (report.value?.chartsData) { + return chartsDataSegments.value; + } + + // Otherwise, fall back to parsing fullAnalysis with JSON blocks (for old data) if (!report.value?.fullAnalysis) return []; const content = report.value.fullAnalysis; @@ -1358,4 +2230,120 @@ onBeforeUnmount(() => { .chart-container { position: relative; } + +/* Channel Matrix Table Styles */ +.channel-matrix-table { + border: 1px solid var(--surface-border); + border-radius: 0.5rem; + overflow: hidden; +} + +.channel-matrix-th { + padding: 0.75rem 1rem; + text-align: left; + font-weight: 600; + color: var(--text-color); + background-color: var(--surface-100); + border-bottom: 2px solid var(--surface-border); + white-space: nowrap; +} + +.channel-matrix-td { + padding: 0.75rem 1rem; + border-bottom: 1px solid var(--surface-border); + color: var(--text-color); +} + +.channel-matrix-tr:last-child .channel-matrix-td { + border-bottom: none; +} + +.channel-matrix-tr:hover { + background-color: var(--surface-50); +} + +.channel-matrix-badge { + display: inline-block; + padding: 0.375rem 0.75rem; + border-radius: 0.375rem; + color: white; + font-weight: 600; + font-size: 0.875rem; + min-width: 70px; + text-align: center; + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1); +} + +/* Competitors Heatmap Table Styles */ +.competitors-heatmap-table { + border: 1px solid var(--surface-border); + border-radius: 0.5rem; + overflow: hidden; +} + +.competitors-heatmap-th { + padding: 0.75rem 1rem; + text-align: left; + font-weight: 600; + color: var(--text-color); + background-color: var(--surface-100); + border-bottom: 2px solid var(--surface-border); + white-space: nowrap; +} + +.competitors-heatmap-td { + padding: 0.75rem 1rem; + border-bottom: 1px solid var(--surface-border); + color: var(--text-color); +} + +.competitors-heatmap-tr:last-child .competitors-heatmap-td { + border-bottom: none; +} + +.competitors-heatmap-tr:hover { + background-color: var(--surface-50); +} + +.competitors-heatmap-badge { + display: inline-block; + padding: 0.5rem 0.875rem; + border-radius: 0.375rem; + color: white; + font-weight: 600; + font-size: 0.875rem; + min-width: 55px; + text-align: center; + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1); +} + +/* Comparison Table Styles */ +.comparison-table { + border: 1px solid var(--surface-border); + border-radius: 0.5rem; + overflow: hidden; +} + +.comparison-table-th { + padding: 0.75rem 1rem; + text-align: left; + font-weight: 600; + color: var(--text-color); + background-color: var(--surface-100); + border-bottom: 2px solid var(--surface-border); +} + +.comparison-table-td { + padding: 0.75rem 1rem; + border-bottom: 1px solid var(--surface-border); + color: var(--text-color); +} + +.comparison-table-tr:last-child .comparison-table-td { + border-bottom: none; +} + +.comparison-table-tr:hover { + background-color: var(--surface-50); +} diff --git a/Промпт для Cursor (Vue.js).md b/Промпт для Cursor (Vue.js).md new file mode 100644 index 0000000..4c2165d --- /dev/null +++ b/Промпт для Cursor (Vue.js).md @@ -0,0 +1 @@ +I. Краткое резюме\n\nДанный маркетинговый анализ содержит детальную информацию о бизнесе, целевой аудитории, конкурентах, рыночной ситуации и рекомендациях по продвижению.\n\nII. Анализ продукта\n\nИнформация о продукте доступна в детальном JSON-анализе.\n\nIII. Анализ рынка и сезонности\n\n\nIV. Анализ целевой аудитории\n\n### Сегмент: Пенсионеры, заботящиеся о здоровье (40%)\nВозраст: 60+\n\nМотивация:\nЭтот сегмент стремится улучшить общее состояние здоровья и предотвратить возрастные заболевания. Например, многие пенсионеры начинают заниматься фитнесом после того, как у них диагностируют гипертонию или диабет, и они понимают, что физическая активность может помочь в управлении этими состояниями. Эмоционально они могут чувствовать страх перед ухудшением здоровья, что подталкивает их к занятиям спортом.\n\nТриггеры:\nЖизненные события, такие как уход на пенсию или потеря близкого человека, могут стать катализаторами для начала занятий фитнесом. Эмоциональные состояния, такие как тревога о здоровье, могут побудить их искать способы улучшить физическую форму. Также важным триггером может быть участие в социальных мероприятиях, где они видят других людей, занимающихся спортом.\n\nБарьеры:\nОсновные барьеры включают финансовые ограничения, так как многие пенсионеры живут на фиксированный доход. Также они могут испытывать страх перед травмами или недомоганием во время занятий, что может вызывать сомнения в своей способности заниматься спортом. Нехватка информации о доступных фитнес-программах также может стать препятствием.\n\n### Сегмент: Активные пожилые люди (30%)\nВозраст: 60+\n\nМотивация:\nЭти люди стремятся поддерживать активный образ жизни и участвовать в социальных мероприятиях. Они могут быть вдохновлены примерами своих сверстников, которые занимаются спортом и выглядят здоровыми. Психологически они ищут способы оставаться независимыми и активными, что придаёт им уверенность в себе.\n\nТриггеры:\nСобытия, такие как юбилеи или семейные праздники, могут побуждать их заниматься фитнесом, чтобы выглядеть лучше и чувствовать себя энергичнее. Эмоции, связанные с желанием быть в форме для внуков или участия в общественных мероприятиях, также могут служить триггерами. Появление новых фитнес-программ или классов в их районе может стать дополнительным стимулом.\n\nБарьеры:\nОни могут столкнуться с проблемами доступности фитнес-центров, особенно если они находятся далеко от дома. Также возможны сомнения в качестве предлагаемых услуг и страх перед новыми формами физической активности. Нехватка времени из-за семейных обязанностей может стать дополнительным препятствием.\n\n### Сегмент: Заботливые бабушки и дедушки (20%)\nВозраст: 60+\n\nМотивация:\nЭтот сегмент часто мотивирован желанием быть в хорошей физической форме для своих внуков. Они могут чувствовать необходимость поддерживать активность, чтобы участвовать в жизни семьи и заботиться о внуках. Эмоционально они стремятся к признанию и уважению со стороны близких.\n\nТриггеры:\nСобытия, такие как дни рождения внуков или семейные праздники, могут побуждать их заниматься фитнесом. Эмоциональные состояния, такие как радость от общения с внуками, могут стать дополнительным стимулом к активному образу жизни. Появление новых фитнес-программ, ориентированных на пожилых людей, также может привлечь их внимание.\n\nБарьеры:\nФинансовые ограничения могут быть значительным препятствием, так как многие бабушки и дедушки живут на небольшие пенсии. Также они могут испытывать страх перед травмами или недомоганием во время занятий, что может вызывать сомнения в своей способности заниматься спортом. Нехватка информации о доступных фитнес-программах также может стать препятствием.\n\n### Сегмент: Заботящиеся о здоровье активные мужчины (10%)\nВозраст: 60+\n\nМотивация:\nЭти мужчины стремятся поддерживать физическую форму и здоровье, чтобы избежать возрастных заболеваний. Они могут быть мотивированы примерами успешных сверстников, которые активно занимаются спортом и выглядят молодо. Психологически они ищут способы сохранить свою независимость и активность.\n\nТриггеры:\nЖизненные события, такие как уход на пенсию или появление внуков, могут стать катализаторами для начала занятий фитнесом. Эмоции, связанные с желанием быть в форме для семьи, могут побудить их к занятиям спортом. Также важным триггером может быть участие в спортивных мероприятиях или соревнованиях для пожилых людей.\n\nБарьеры:\nОсновные барьеры включают финансовые ограничения, так как многие мужчины на пенсии могут иметь ограниченный доход. Также они могут испытывать страх перед травмами или недомоганием во время занятий, что может вызывать сомнения в своей способности заниматься спортом. Нехватка информации о доступных фитнес-программах также может стать препятствием.\n\n[[CHART_AUDIENCE]]\n\nV. Анализ конкурентов\n\nНа текущий момент наш бренд занимает 25% рынка фитнес-услуг в Алматы и Астане, что позволяет нам конкурировать с более крупными игроками. Конкурент А, имеющий 35% рынка, успешно использует стратегию премиум-сегмента, в то время как Конкурент B с 20% фокусируется на доступности цен.\n\n### Конкурент А\nЦеновая стратегия: Премиум\n\nКонтент-стратегия:\nКонкурент А активно использует Instagram для продвижения, публикуя высококачественные фотографии и видео с тренировками и отзывами клиентов. Частота публикаций составляет 5 раз в неделю, что позволяет поддерживать высокий уровень вовлеченности. Основные темы включают фитнес-программы, советы по питанию и мотивацию, что создает положительный имидж бренда.\n\nСильные стороны:\n- Сильный бренд\n- Высокое качество услуг\n- Широкий выбор программ\n\nСлабые стороны:\n- Высокие цены\n- Ограниченная доступность для низкодоходных клиентов\n- Низкая активность на YouTube\n\n### Конкурент B\nЦеновая стратегия: Низкая\n\nКонтент-стратегия:\nКонкурент B делает акцент на TikTok, где публикует короткие видео с тренировками и челленджами. Частота публикаций составляет 3-4 раза в неделю, что привлекает молодежную аудиторию. Основные темы включают доступные тренировки и советы по здоровому образу жизни, что делает их контент привлекательным для широкой аудитории.\n\nСильные стороны:\n- Доступные цены\n- Широкая аудитория\n- Инновационный подход к контенту\n\nСлабые стороны:\n- Низкое качество услуг\n- Ограниченный выбор программ\n- Слабый имидж бренда\n\n### Конкурент C\nЦеновая стратегия: Средняя\n\nКонтент-стратегия:\nКонкурент C использует разнообразные каналы, включая Instagram и YouTube, для публикации обучающих видео и вебинаров. Частота публикаций составляет 4 раза в неделю, что помогает поддерживать интерес к их услугам. Основные темы включают фитнес-тренировки, советы по питанию и истории успеха клиентов, что создает доверие и лояльность.\n\nСильные стороны:\n- Разнообразие контента\n- Умеренные цены\n- Хорошая репутация\n\nСлабые стороны:\n- Низкая активность в TikTok\n- Слабая работа с отзывами\n- Ограниченные акции и скидки\n\n[[CHART_COMPETITORS]]\n\nVI. SWOT-анализ\n\n[[CHART_SWOT]]\n\nVII. Каналы продвижения и их потенциал\n\n[[CHART_CHANNELS]]\n\nVIII. Воронка спроса\n\n[[CHART_FUNNEL]]\n\nIX. Рекомендации по позиционированию и УТП\n\nX. Рекомендации по контенту\n\nXI. Итоговая стратегия: что делать в первую очередь\n\nНа основе проведенного анализа рекомендуется:\n\n1. Сфокусироваться на ключевых сегментах целевой аудитории\n2. Использовать наиболее эффективные каналы продвижения\n3. Разработать контент-стратегию с учетом потребностей аудитории\n4. Усилить позиционирование и уникальное торговое предложение\n5. Мониторить конкурентов и адаптировать стратегию\n\n