+ {/* Numbered circle */}
+
diff --git a/apps/web/src/modules/marketing/home/how-to-read.tsx b/apps/web/src/modules/marketing/home/how-to-read.tsx
new file mode 100644
index 0000000..ee5e443
--- /dev/null
+++ b/apps/web/src/modules/marketing/home/how-to-read.tsx
@@ -0,0 +1,195 @@
+"use client";
+
+import { useTranslation } from "@turbostarter/i18n";
+
+import {
+ Section,
+ SectionBadge,
+ SectionDescription,
+ SectionHeader,
+ SectionTitle,
+} from "~/modules/marketing/layout/section";
+
+// ---------------------------------------------------------------------------
+// Static data
+// ---------------------------------------------------------------------------
+
+const SCORE_BANDS = [
+ { range: "0–39", width: "40%", color: "#ef4444", key: "scoreCritical" },
+ { range: "40–59", width: "20%", color: "#f97316", key: "scorePoor" },
+ { range: "60–74", width: "15%", color: "#f59e0b", key: "scoreFair" },
+ { range: "75–89", width: "15%", color: "#22c55e", key: "scoreGood" },
+ { range: "90–100", width: "10%", color: "#059669", key: "scoreExcellent" },
+] as const;
+
+const VALENCE_MARKERS = [
+ { symbol: "+", color: "#22c55e", bg: "bg-green-50 dark:bg-green-950/30", border: "border-green-300 dark:border-green-800", key: "valencePos" },
+ { symbol: "\u2212", color: "#ef4444", bg: "bg-red-50 dark:bg-red-950/30", border: "border-red-300 dark:border-red-800", key: "valenceNeg" },
+ { symbol: "0", color: "#9ca3af", bg: "bg-gray-50 dark:bg-gray-900/30", border: "border-gray-300 dark:border-gray-700", key: "valenceNeu" },
+ { symbol: "\u00b1", color: "#f59e0b", bg: "bg-amber-50 dark:bg-amber-950/30", border: "border-amber-300 dark:border-amber-800", key: "valenceMix" },
+] as const;
+
+const EXPERIENCE_DOMAINS = [
+ { code: "O", color: "#3b82f6", key: "domainO" },
+ { code: "P", color: "#22c55e", key: "domainP" },
+ { code: "J", color: "#f59e0b", key: "domainJ" },
+ { code: "E", color: "#8b5cf6", key: "domainE" },
+ { code: "V", color: "#f43f5e", key: "domainV" },
+] as const;
+
+const INTENSITY_LEVELS = [
+ { bars: 1, key: "intensity1" },
+ { bars: 2, key: "intensity2" },
+ { bars: 3, key: "intensity3" },
+] as const;
+
+// ---------------------------------------------------------------------------
+// Component
+// ---------------------------------------------------------------------------
+
+export const HowToRead = () => {
+ const { t } = useTranslation("marketing");
+
+ return (
+
+
+ {t("howToRead.label")}
+ {t("howToRead.title")}
+ {t("howToRead.description")}
+
+
+
+ {/* Score Spectrum */}
+
+
+ {t("howToRead.scoreTitle")}
+
+
+ {SCORE_BANDS.map((band) => (
+
+ {t(`howToRead.${band.key}` as const)}
+
+ ))}
+
+
+ 0
+ 40
+ 60
+ 75
+ 90
+ 100
+
+
+ {t("howToRead.scoreDesc")}
+
+
+
+ {/* 2x2 Grid */}
+
+ {/* Sentiment Markers */}
+
+
+ {t("howToRead.valenceTitle")}
+
+
+ {t("howToRead.valenceDesc")}
+
+
+ {VALENCE_MARKERS.map((v) => (
+
+
+ {v.symbol}
+
+
+ {t(`howToRead.${v.key}` as const)}
+
+
+ ))}
+
+
+
+ {/* Experience Domains */}
+
+
+ {t("howToRead.domainsTitle")}
+
+
+ {t("howToRead.domainsDesc")}
+
+
+ {EXPERIENCE_DOMAINS.map((d) => (
+
+
+ {d.code}
+
+
+ {t(`howToRead.${d.key}` as const)}
+
+
+ ))}
+
+
+
+ {/* Intensity Levels */}
+
+
+ {t("howToRead.intensityTitle")}
+
+
+ {t("howToRead.intensityDesc")}
+
+
+ {INTENSITY_LEVELS.map((il) => (
+
+
+ {Array.from({ length: 3 }, (_, i) => (
+
+ ))}
+
+
+ {t(`howToRead.${il.key}` as const)}
+
+
+ ))}
+
+
+
+ {/* Reading Tips */}
+
+
+ {t("howToRead.tipsTitle")}
+
+
+ {(["howToRead.tip1", "howToRead.tip2", "howToRead.tip3", "howToRead.tip4"] as const).map((key, i) => (
+
+
+ {i + 1}.
+
+
+ {t(key)}
+
+
+ ))}
+
+
+
+
+
+ );
+};
diff --git a/apps/web/src/modules/marketing/home/report-fan.tsx b/apps/web/src/modules/marketing/home/report-fan.tsx
index 13ed18f..405183d 100644
--- a/apps/web/src/modules/marketing/home/report-fan.tsx
+++ b/apps/web/src/modules/marketing/home/report-fan.tsx
@@ -2,9 +2,9 @@ import Image from "next/image";
export const ReportFan = () => {
return (
-
+
{/* Ambient glow behind the fan */}
-
+
diff --git a/apps/web/src/modules/marketing/home/report-preview.tsx b/apps/web/src/modules/marketing/home/report-preview.tsx
index 3dca1f1..f90c777 100644
--- a/apps/web/src/modules/marketing/home/report-preview.tsx
+++ b/apps/web/src/modules/marketing/home/report-preview.tsx
@@ -3,6 +3,20 @@
import { useState } from "react";
import { useTranslation } from "@turbostarter/i18n";
import { cn } from "@turbostarter/ui";
+import {
+ ScatterChart,
+ Scatter,
+ XAxis,
+ YAxis,
+ CartesianGrid,
+ Tooltip,
+ ResponsiveContainer,
+ ZAxis,
+ Cell,
+ ReferenceLine,
+ ReferenceArea,
+ LabelList,
+} from "recharts";
import { CtaButton } from "~/modules/marketing/layout/cta-button";
import {
@@ -12,6 +26,11 @@ import {
SectionHeader,
SectionTitle,
} from "~/modules/marketing/layout/section";
+import {
+ DEMO_THEMES,
+ DEMO_ACTIONS,
+ DOMAIN_COLORS as THEME_DOMAIN_COLORS,
+} from "./demo-report-data";
// ---------------------------------------------------------------------------
// Demo data (hardcoded for "Bistro El Sol")
@@ -44,14 +63,27 @@ const SEVERITY_COLORS: Record
= {
};
const EFFORT_COLORS: Record = {
+ low: "#22c55e",
+ medium: "#f59e0b",
+ high: "#ef4444",
Low: "#22c55e",
Medium: "#f59e0b",
High: "#ef4444",
+ Bajo: "#22c55e",
+ Medio: "#f59e0b",
+ Alto: "#ef4444",
};
+
const IMPACT_COLORS: Record = {
+ low: "#6b7280",
+ medium: "#f59e0b",
+ high: "#22c55e",
Low: "#6b7280",
Medium: "#f59e0b",
High: "#22c55e",
+ Bajo: "#6b7280",
+ Medio: "#f59e0b",
+ Alto: "#22c55e",
};
const DOMAIN_COLORS: Record = {
@@ -60,7 +92,7 @@ const DOMAIN_COLORS: Record = {
Environment: "#8b5cf6",
};
-const TABS = ["score", "domains", "issues", "actions"] as const;
+const TABS = ["score", "domains", "issues", "actions", "themes"] as const;
type Tab = (typeof TABS)[number];
// ---------------------------------------------------------------------------
@@ -79,16 +111,16 @@ function ScoreTab() {
return (
{/* Left: Gauge + band */}
-
+
{t("reportPreview.demo.businessName")}
-
+
-
+
-
+
+
+ 0
+ 40
+ 60
+ 75
+ 90
+ 100
+
@@ -292,59 +332,84 @@ function IssuesTab() {
}
// ---------------------------------------------------------------------------
-// Actions Tab
+// Actions Tab (enhanced with real report data)
// ---------------------------------------------------------------------------
function ActionsTab() {
const { t } = useTranslation("marketing");
- const actions = [
- { key: "action1" as const, idx: 0 },
- { key: "action2" as const, idx: 1 },
- { key: "action3" as const, idx: 2 },
- ];
+ // Build mention count map from demo themes
+ const mentionMap = new Map
();
+ for (const theme of DEMO_THEMES) {
+ mentionMap.set(theme.primitive, theme.count);
+ }
return (
- {actions.map(({ key: actionKey, idx }) => {
- const effort = t(`reportPreview.demo.actions.${actionKey}.effort` as const);
- const impact = t(`reportPreview.demo.actions.${actionKey}.impact` as const);
+ {DEMO_ACTIONS.map((action, idx) => {
+ const effortColor = EFFORT_COLORS[action.effort] ?? "#f59e0b";
+ const impactColor = IMPACT_COLORS[action.impact] ?? "#f59e0b";
+ const mentions = mentionMap.get(action.source);
return (
-
- {idx + 1}
-
+
+
+ {idx + 1}
+
-
-
- {t(`reportPreview.demo.actions.${actionKey}.title` as const)}
-
+
+
+ {action.action}
+
-
-
- {effort} effort
-
-
- {impact} impact
-
+
+
+ {action.owner}
+
+ {mentions != null && (
+
+ {mentions} {t("reportPreview.demo.themes.mentions" as const)}
+
+ )}
+
+ {action.timeline}
+
+
+
+
+
+ {action.effort} effort
+
+
+ {action.impact} impact
+
+
+
+ {action.success_metric && (
+
+
+ Target: {action.success_metric}
+
+
+ )}
);
})}
@@ -352,6 +417,149 @@ function ActionsTab() {
);
}
+// ---------------------------------------------------------------------------
+// Themes Tab (ThemeMatrix scatter chart)
+// ---------------------------------------------------------------------------
+
+interface ScatterPoint {
+ x: number;
+ y: number;
+ z: number;
+ name: string;
+ domain: string;
+ color: string;
+}
+
+function ThemesTab() {
+ const { t } = useTranslation("marketing");
+
+ const data: ScatterPoint[] = DEMO_THEMES
+ .filter((theme) => theme.domain !== "meta")
+ .map((theme) => {
+ const total =
+ theme.valence.positive +
+ theme.valence.negative +
+ theme.valence.neutral +
+ theme.valence.mixed;
+ const sentimentRatio =
+ total > 0
+ ? ((theme.valence.positive - theme.valence.negative) / total) * 100
+ : 0;
+ return {
+ x: theme.count,
+ y: Math.round(sentimentRatio),
+ z: theme.weight * theme.count,
+ name: theme.label,
+ domain: theme.domain,
+ color: THEME_DOMAIN_COLORS[theme.domain] ?? "#6b7280",
+ };
+ })
+ .sort((a, b) => b.z - a.z);
+
+ const yValues = data.map((d) => d.y);
+ const yMin = Math.min(-20, ...yValues) - 10;
+ const yMax = Math.max(20, ...yValues) + 10;
+
+ return (
+
+ {/* Domain legend */}
+
+ {[
+ { label: "People/Service", domain: "P" },
+ { label: "Journey", domain: "J" },
+ { label: "Value", domain: "V" },
+ { label: "Product", domain: "O" },
+ { label: "Environment", domain: "E" },
+ ].map((d) => (
+
+
+ {d.label}
+
+ ))}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {
+ if (!active || !payload?.length) return null;
+ const point = (payload[0] as Record)
+ ?.payload as ScatterPoint | undefined;
+ if (!point) return null;
+ return (
+
+
+ {point.name}
+
+
+ {t("reportPreview.demo.themes.mentions" as const)}: {point.x}
+
+
+ {t("reportPreview.demo.themes.netSentiment" as const)}: {point.y}%
+
+
+ );
+ }}
+ />
+
+ {data.map((entry, i) => (
+ |
+ ))}
+
+
+
+
+
+ );
+}
+
// ---------------------------------------------------------------------------
// Main component
// ---------------------------------------------------------------------------
@@ -372,7 +580,7 @@ export const ReportPreview = () => {
{/* Ambient glow */}
-
+
@@ -408,6 +616,7 @@ export const ReportPreview = () => {
{activeTab === "domains" &&
}
{activeTab === "issues" &&
}
{activeTab === "actions" &&
}
+ {activeTab === "themes" &&
}
diff --git a/apps/web/src/modules/marketing/home/testimonials.tsx b/apps/web/src/modules/marketing/home/testimonials.tsx
index 85cfc6e..ea6bccc 100644
--- a/apps/web/src/modules/marketing/home/testimonials.tsx
+++ b/apps/web/src/modules/marketing/home/testimonials.tsx
@@ -89,7 +89,7 @@ export const Testimonials = () => {
-
+
{rows.map((row, index) => (