From f748db842f213bd7da6b9ecb42ebe33afbd8d2a1 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sat, 4 Jul 2026 13:44:36 +0000 Subject: [PATCH] Apply Google Analytics-style branding to reports and dashboard Switch from dark mode to a light Google Workspace/Analytics aesthetic: Roboto font, four-color palette (blue/red/yellow/green), white cards with Material elevation shadows, and Google logo mark in the sidebar. KPI cards get colored accent bars; charts and status indicators use Google brand colors. Co-authored-by: Nathan Faggian --- apps/delta-command/src/app/dashboard/page.tsx | 12 +- apps/delta-command/src/app/globals.css | 131 ++++++++++-------- apps/delta-command/src/app/layout.tsx | 11 +- .../src/app/opportunities/page.tsx | 4 +- .../components/dashboard/ActionItemsPanel.tsx | 16 +-- .../dashboard/DeliveryOverviewPanel.tsx | 18 +-- .../src/components/dashboard/KPICard.tsx | 39 +++++- .../components/dashboard/PipelineChart.tsx | 31 +++-- .../dashboard/PipelineHealthPanel.tsx | 8 +- .../components/dashboard/UtilizationChart.tsx | 33 +++-- .../src/components/layout/Header.tsx | 10 +- .../src/components/layout/Sidebar.tsx | 44 ++++-- .../src/components/projects/ProjectCard.tsx | 4 +- .../src/components/team/TeamPageClient.tsx | 6 +- .../components/team/UtilizationTimeline.tsx | 10 +- apps/delta-command/src/core/utils.ts | 65 +++++---- apps/delta-command/tailwind.config.ts | 104 +++++++------- 17 files changed, 321 insertions(+), 225 deletions(-) diff --git a/apps/delta-command/src/app/dashboard/page.tsx b/apps/delta-command/src/app/dashboard/page.tsx index 0c2e064..59fdb4d 100644 --- a/apps/delta-command/src/app/dashboard/page.tsx +++ b/apps/delta-command/src/app/dashboard/page.tsx @@ -67,42 +67,42 @@ export default async function DashboardPage() { value={formatCurrency(metrics.pipelineValue)} context={`${formatCurrency(metrics.totalPipeline)} unweighted · ${pipeline.closingWithin30Days} closing in 30d`} icon={DollarSign} - iconColor="bg-blue-500/15 text-blue-400" + accent="blue" /> diff --git a/apps/delta-command/src/app/globals.css b/apps/delta-command/src/app/globals.css index 3b4177e..f8f295f 100644 --- a/apps/delta-command/src/app/globals.css +++ b/apps/delta-command/src/app/globals.css @@ -5,11 +5,11 @@ @layer base { :root { --sidebar-width: 15rem; - color-scheme: dark; + color-scheme: light; } * { - @apply border-outline-variant/50; + @apply border-outline-variant; } body { @@ -28,62 +28,62 @@ @layer components { .card { - @apply rounded-xl border border-white/[0.06] bg-surface-bright/80 shadow-card - backdrop-blur-sm transition-all duration-300 ease-emphasized - hover:border-white/[0.1] hover:bg-surface-bright hover:shadow-elevated; + @apply rounded-lg border border-outline bg-surface-bright shadow-card + transition-all duration-200 ease-standard + hover:shadow-elevated; } .card-flat { - @apply rounded-xl border border-white/[0.06] bg-surface-bright/80; + @apply rounded-lg border border-outline bg-surface-bright; } .badge { - @apply inline-flex items-center rounded-md px-2 py-0.5 text-[11px] font-medium leading-4; + @apply inline-flex items-center rounded px-2 py-0.5 text-[11px] font-medium leading-4; } .chip { - @apply inline-flex items-center rounded-md bg-surface-container/60 px-2 py-0.5 - text-[11px] font-medium leading-4 text-surface-on-variant ring-1 ring-inset ring-white/[0.06]; + @apply inline-flex items-center rounded bg-surface-container px-2 py-0.5 + text-[11px] font-medium leading-4 text-surface-on-variant; } .btn-primary { - @apply inline-flex items-center justify-center gap-2 rounded-lg bg-accent px-5 py-2 + @apply inline-flex items-center justify-center gap-2 rounded bg-accent px-5 py-2 text-sm font-medium text-white shadow-sm transition-all duration-200 ease-standard - hover:bg-blue-400 active:scale-[0.98] + hover:bg-brand-600 active:scale-[0.98] focus-visible:outline-none focus-visible:shadow-glow; } .btn-secondary { - @apply inline-flex items-center justify-center gap-2 rounded-lg border border-white/[0.08] - bg-surface-container px-5 py-2 text-sm font-medium text-surface-on + @apply inline-flex items-center justify-center gap-2 rounded border border-outline + bg-surface-bright px-5 py-2 text-sm font-medium text-surface-on transition-all duration-200 ease-standard - hover:bg-surface-container-high + hover:bg-surface-container focus-visible:outline-none focus-visible:shadow-glow; } .btn-tonal { - @apply inline-flex items-center justify-center gap-2 rounded-lg bg-accent-muted px-5 py-2 + @apply inline-flex items-center justify-center gap-2 rounded bg-accent-muted px-5 py-2 text-sm font-medium text-accent-foreground - transition-all duration-200 ease-standard hover:bg-blue-500/20; + transition-all duration-200 ease-standard hover:bg-blue-100; } .icon-btn { - @apply relative flex h-9 w-9 items-center justify-center rounded-lg + @apply relative flex h-9 w-9 items-center justify-center rounded-full text-surface-on-variant transition-all duration-200 ease-standard - hover:bg-white/[0.06] hover:text-surface-on; + hover:bg-surface-container hover:text-surface-on; } .headline-md { - @apply text-2xl font-semibold leading-8 tracking-tight; + @apply text-2xl font-normal leading-8 tracking-tight text-surface-on; } .title-lg { - @apply text-lg font-semibold leading-7 tracking-tight; + @apply text-lg font-medium leading-7 tracking-tight; } .title-md { - @apply text-sm font-semibold leading-5; + @apply text-sm font-medium leading-5; } .title-sm { @@ -95,61 +95,59 @@ } .label-md { - @apply text-xs font-medium uppercase tracking-wider text-surface-on-variant/70; + @apply text-xs font-medium uppercase tracking-wider text-surface-on-variant; } .section-title { - @apply text-base font-semibold tracking-tight text-surface-on; + @apply text-base font-medium tracking-tight text-surface-on; } .metric-value { - @apply text-3xl font-semibold leading-none tracking-tight text-surface-on; + @apply text-3xl font-normal leading-none tracking-tight text-surface-on; } .metric-label { - @apply text-xs font-medium uppercase tracking-wider text-surface-on-variant/80; + @apply text-xs font-medium uppercase tracking-wider text-surface-on-variant; } .nav-item { - @apply relative flex items-center gap-3 rounded-lg px-3 py-2.5 text-sm font-medium + @apply relative flex items-center gap-3 rounded-full px-4 py-2.5 text-sm font-medium text-sidebar-muted transition-all duration-200 ease-standard - hover:bg-white/[0.05] hover:text-sidebar-foreground; + hover:bg-surface-container hover:text-sidebar-foreground; } .nav-item-active { - @apply bg-white/[0.08] text-sidebar-foreground before:absolute before:left-0 before:top-1/2 - before:h-5 before:w-0.5 before:-translate-y-1/2 before:rounded-full before:bg-accent - before:shadow-[0_0_8px_rgb(59_130_246/0.6)] before:content-['']; + @apply bg-brand-50 text-brand-600; } .text-field-outlined { - @apply rounded-lg border border-white/[0.08] bg-surface-container/80 text-sm text-surface-on + @apply rounded-full border border-outline bg-surface-bright text-sm text-surface-on outline-none transition-all duration-200 - placeholder:text-surface-on-variant/40 - focus:border-accent/60 focus:shadow-glow; + placeholder:text-surface-on-variant/60 + focus:border-accent focus:shadow-glow; } .banner-error { - @apply flex items-center gap-3 rounded-xl border border-red-500/30 bg-red-500/10 px-5 py-3.5 - text-red-300 backdrop-blur-sm; + @apply flex items-center gap-3 rounded-lg border border-google-red/30 bg-google-red/10 px-5 py-3.5 + text-google-red; } .banner-warning { - @apply flex items-center gap-3 rounded-xl border border-amber-500/30 bg-amber-500/10 px-5 py-3.5 - text-amber-200 backdrop-blur-sm; + @apply flex items-center gap-3 rounded-lg border border-google-yellow/40 bg-google-yellow/15 px-5 py-3.5 + text-[#B06000]; } .progress-track { - @apply h-1.5 overflow-hidden rounded-full bg-surface-container-high/80; + @apply h-1.5 overflow-hidden rounded-full bg-surface-container-high; } .progress-indicator { - @apply h-full rounded-full bg-accent transition-all duration-500 ease-emphasized; + @apply h-full rounded-full bg-google-blue transition-all duration-500 ease-emphasized; } .top-app-bar { @apply sticky top-0 z-20 flex h-[4.25rem] items-center justify-between - border-b border-white/[0.06] bg-surface/80 px-6 backdrop-blur-xl md:px-8; + border-b border-outline bg-surface-bright/95 px-6 backdrop-blur-sm md:px-8; } .nav-drawer { @@ -158,21 +156,21 @@ } .segmented-control { - @apply inline-flex rounded-lg bg-surface-container/80 p-1 ring-1 ring-inset ring-white/[0.06]; + @apply inline-flex rounded-full bg-surface-container p-1; } .segmented-item { - @apply flex items-center gap-2 rounded-md px-3.5 py-2 text-sm font-medium + @apply flex items-center gap-2 rounded-full px-3.5 py-2 text-sm font-medium text-surface-on-variant transition-all duration-200 ease-standard hover:text-surface-on; } .segmented-item-active { - @apply bg-surface-bright text-surface-on shadow-sm ring-1 ring-white/[0.08]; + @apply bg-surface-bright text-surface-on shadow-sm; } .link-subtle { - @apply text-xs font-medium text-accent-foreground transition-colors hover:text-accent; + @apply text-xs font-medium text-accent-foreground transition-colors hover:text-accent hover:underline; } .stat-inline { @@ -184,24 +182,49 @@ } .kanban-column { - @apply flex w-72 shrink-0 flex-col rounded-xl border border-white/[0.06] - bg-surface-container-low/60 p-3 backdrop-blur-sm; + @apply flex w-72 shrink-0 flex-col rounded-lg border border-outline + bg-surface-container-low p-3; } .page-shell { - @apply bg-radial-glow bg-grid-pattern bg-grid bg-surface; + @apply bg-surface; } .util-cell { - @apply mx-auto flex h-10 w-[68px] flex-col items-center justify-center rounded-lg - text-sm font-semibold ring-1 ring-inset transition-all duration-200 - hover:scale-[1.02] hover:brightness-110 active:scale-[0.98]; + @apply mx-auto flex h-10 w-[68px] flex-col items-center justify-center rounded + text-sm font-medium ring-1 ring-inset transition-all duration-200 + hover:brightness-95 active:scale-[0.98]; + } + + .report-card-accent { + @apply relative overflow-hidden; + } + + .report-card-accent::before { + content: ""; + @apply absolute left-0 top-0 h-full w-1; + } + + .report-accent-blue::before { + @apply bg-google-blue; + } + + .report-accent-red::before { + @apply bg-google-red; + } + + .report-accent-yellow::before { + @apply bg-google-yellow; + } + + .report-accent-green::before { + @apply bg-google-green; } } ::-webkit-scrollbar { - width: 6px; - height: 6px; + width: 8px; + height: 8px; } ::-webkit-scrollbar-track { @@ -209,9 +232,9 @@ } ::-webkit-scrollbar-thumb { - @apply rounded-full bg-zinc-700; + @apply rounded-full bg-outline; } ::-webkit-scrollbar-thumb:hover { - @apply bg-zinc-600; + @apply bg-surface-container-highest; } diff --git a/apps/delta-command/src/app/layout.tsx b/apps/delta-command/src/app/layout.tsx index 64680f8..35a2f48 100644 --- a/apps/delta-command/src/app/layout.tsx +++ b/apps/delta-command/src/app/layout.tsx @@ -1,11 +1,12 @@ import type { Metadata } from "next"; -import { Inter } from "next/font/google"; +import { Roboto } from "next/font/google"; import { Sidebar } from "@/components/layout/Sidebar"; import "./globals.css"; -const inter = Inter({ - variable: "--font-inter", +const roboto = Roboto({ + variable: "--font-roboto", subsets: ["latin"], + weight: ["300", "400", "500", "700"], display: "swap", }); @@ -21,8 +22,8 @@ export default function RootLayout({ children: React.ReactNode; }) { return ( - - + +
{children}
diff --git a/apps/delta-command/src/app/opportunities/page.tsx b/apps/delta-command/src/app/opportunities/page.tsx index 1ae604b..7dd74be 100644 --- a/apps/delta-command/src/app/opportunities/page.tsx +++ b/apps/delta-command/src/app/opportunities/page.tsx @@ -76,10 +76,10 @@ export default function OpportunitiesPage() {
- +

Won

-

+

{wonOpps.length} · {formatCurrency(wonOpps.reduce((s, o) => s + o.value, 0))}

diff --git a/apps/delta-command/src/components/dashboard/ActionItemsPanel.tsx b/apps/delta-command/src/components/dashboard/ActionItemsPanel.tsx index 3cc7c31..11050f1 100644 --- a/apps/delta-command/src/components/dashboard/ActionItemsPanel.tsx +++ b/apps/delta-command/src/components/dashboard/ActionItemsPanel.tsx @@ -13,15 +13,15 @@ const severityStyles: Record< { container: string; icon: typeof AlertCircle } > = { critical: { - container: "border-red-500/30 bg-red-500/10", + container: "border-google-red/30 bg-red-50", icon: AlertCircle, }, warning: { - container: "border-amber-500/30 bg-amber-500/10", + container: "border-google-yellow/40 bg-yellow-50", icon: AlertTriangle, }, info: { - container: "border-blue-500/30 bg-blue-500/10", + container: "border-google-blue/30 bg-blue-50", icon: Info, }, }; @@ -58,16 +58,16 @@ export function ActionItemsPanel({ items }: ActionItemsPanelProps) { - +
-

{item.title}

-

{item.detail}

+

{item.title}

+

{item.detail}

- + ); diff --git a/apps/delta-command/src/components/dashboard/DeliveryOverviewPanel.tsx b/apps/delta-command/src/components/dashboard/DeliveryOverviewPanel.tsx index 9dbadc7..45989fc 100644 --- a/apps/delta-command/src/components/dashboard/DeliveryOverviewPanel.tsx +++ b/apps/delta-command/src/components/dashboard/DeliveryOverviewPanel.tsx @@ -9,9 +9,9 @@ interface DeliveryOverviewPanelProps { export function DeliveryOverviewPanel({ delivery }: DeliveryOverviewPanelProps) { const statusRows = [ - { label: "Active", count: delivery.activeCount, color: "bg-accent" }, - { label: "Planning", count: delivery.planningCount, color: "bg-zinc-300" }, - { label: "At risk", count: delivery.atRiskCount, color: "bg-red-500" }, + { label: "Active", count: delivery.activeCount, color: "bg-google-blue" }, + { label: "Planning", count: delivery.planningCount, color: "bg-google-yellow" }, + { label: "At risk", count: delivery.atRiskCount, color: "bg-google-red" }, ]; const total = statusRows.reduce((s, r) => s + r.count, 0) || 1; @@ -69,7 +69,7 @@ export function DeliveryOverviewPanel({ delivery }: DeliveryOverviewPanelProps) ))} {delivery.projectsEnding30Days > 0 && ( - + · {delivery.projectsEnding30Days} ending in 30d )} @@ -99,17 +99,17 @@ export function TeamSnapshotPanel({
-
+

{avgUtilization}%

Avg util

-
-

{team.benchCapacityPercent}%

+
+

{team.benchCapacityPercent}%

Bench

-
+

{team.available.length}

Available

@@ -144,7 +144,7 @@ export function TeamSnapshotPanel({ {team.unassigned.map((e) => (
  • {e.name.split(" ")[0]} diff --git a/apps/delta-command/src/components/dashboard/KPICard.tsx b/apps/delta-command/src/components/dashboard/KPICard.tsx index 287af2d..af467ee 100644 --- a/apps/delta-command/src/components/dashboard/KPICard.tsx +++ b/apps/delta-command/src/components/dashboard/KPICard.tsx @@ -2,6 +2,8 @@ import { cn } from "@/core/utils"; import type { LucideIcon } from "lucide-react"; import { TrendingUp, TrendingDown, Minus } from "lucide-react"; +type AccentColor = "blue" | "red" | "yellow" | "green"; + interface KPICardProps { label: string; value: string; @@ -10,8 +12,28 @@ interface KPICardProps { changeLabel?: string; icon: LucideIcon; iconColor?: string; + accent?: AccentColor; } +const accentClasses: Record = { + blue: { + bar: "report-accent-blue", + icon: "bg-blue-50 text-brand-600", + }, + red: { + bar: "report-accent-red", + icon: "bg-red-50 text-google-red", + }, + yellow: { + bar: "report-accent-yellow", + icon: "bg-yellow-50 text-[#B06000]", + }, + green: { + bar: "report-accent-green", + icon: "bg-green-50 text-google-green", + }, +}; + export function KPICard({ label, value, @@ -19,7 +41,8 @@ export function KPICard({ change, changeLabel, icon: Icon, - iconColor = "text-accent bg-accent-muted", + iconColor, + accent = "blue", }: KPICardProps) { const trendIcon = change === undefined ? null : change > 0 ? TrendingUp : change < 0 ? TrendingDown : Minus; @@ -28,19 +51,21 @@ export function KPICard({ change === undefined ? "" : change > 0 - ? "text-emerald-400" + ? "text-google-green" : change < 0 - ? "text-red-400" + ? "text-google-red" : "text-surface-on-variant"; + const accentStyle = accentClasses[accent]; + return ( -
    +

    {label}

    @@ -58,7 +83,7 @@ export function KPICard({ {change}% {changeLabel && ( - {changeLabel} + {changeLabel} )}
    )} diff --git a/apps/delta-command/src/components/dashboard/PipelineChart.tsx b/apps/delta-command/src/components/dashboard/PipelineChart.tsx index 35b4393..8accbb4 100644 --- a/apps/delta-command/src/components/dashboard/PipelineChart.tsx +++ b/apps/delta-command/src/components/dashboard/PipelineChart.tsx @@ -12,24 +12,27 @@ import { } from "recharts"; import { OPPORTUNITY_STAGES } from "@/core/types"; import type { Opportunity } from "@/core/types"; -import { formatCurrency, CHART_GRID, CHART_TICK, CHART_TOOLTIP_STYLE } from "@/core/utils"; +import { + formatCurrency, + CHART_GRID, + CHART_TICK, + CHART_TOOLTIP_STYLE, + GOOGLE_COLORS, +} from "@/core/utils"; interface PipelineChartProps { opportunities: Opportunity[]; } const STAGE_COLORS: Record = { - prospect: "#A1A1AA", - qualified: "#3B82F6", - proposal: "#8B5CF6", - negotiation: "#6366F1", - won: "#10B981", - lost: "#EF4444", + prospect: GOOGLE_COLORS.gray, + qualified: GOOGLE_COLORS.blue, + proposal: GOOGLE_COLORS.yellow, + negotiation: "#1967D2", + won: GOOGLE_COLORS.green, + lost: GOOGLE_COLORS.red, }; -const CHART_GRID_COLOR = CHART_GRID; -const CHART_TICK_COLOR = CHART_TICK; - export function PipelineChart({ opportunities }: PipelineChartProps) { const data = OPPORTUNITY_STAGES.filter((s) => !["lost"].includes(s.id)).map( (stage) => { @@ -52,15 +55,15 @@ export function PipelineChart({ opportunities }: PipelineChartProps) {
    - + @@ -71,7 +74,7 @@ export function PipelineChart({ opportunities }: PipelineChartProps) { return [value, "Count"]; }} /> - + {data.map((entry, index) => ( ))} diff --git a/apps/delta-command/src/components/dashboard/PipelineHealthPanel.tsx b/apps/delta-command/src/components/dashboard/PipelineHealthPanel.tsx index 0dbca95..7e71793 100644 --- a/apps/delta-command/src/components/dashboard/PipelineHealthPanel.tsx +++ b/apps/delta-command/src/components/dashboard/PipelineHealthPanel.tsx @@ -48,7 +48,7 @@ export function PipelineHealthPanel({ pipeline }: PipelineHealthPanelProps) { {stats.map((stat) => (
    @@ -96,10 +96,10 @@ export function MilestonesPanel({ milestones }: MilestonesPanelProps) { className={cn( "badge", m.overdue - ? "bg-red-500/15 text-red-300 ring-1 ring-inset ring-red-500/30" + ? "bg-red-50 text-google-red ring-1 ring-inset ring-google-red/30" : m.daysUntil <= 7 - ? "bg-amber-500/15 text-amber-300 ring-1 ring-inset ring-amber-500/30" - : "bg-emerald-500/15 text-emerald-300 ring-1 ring-inset ring-emerald-500/30" + ? "bg-yellow-50 text-[#B06000] ring-1 ring-inset ring-google-yellow/40" + : "bg-green-50 text-google-green ring-1 ring-inset ring-google-green/30" )} > {m.overdue diff --git a/apps/delta-command/src/components/dashboard/UtilizationChart.tsx b/apps/delta-command/src/components/dashboard/UtilizationChart.tsx index 0f7ead2..7e0b180 100644 --- a/apps/delta-command/src/components/dashboard/UtilizationChart.tsx +++ b/apps/delta-command/src/components/dashboard/UtilizationChart.tsx @@ -12,7 +12,12 @@ import { Cell, } from "recharts"; import type { Engineer } from "@/core/types"; -import { CHART_GRID, CHART_TICK, CHART_TOOLTIP_STYLE } from "@/core/utils"; +import { + CHART_GRID, + CHART_TICK, + CHART_TOOLTIP_STYLE, + GOOGLE_COLORS, +} from "@/core/utils"; interface UtilizationChartProps { engineers: Engineer[]; @@ -20,15 +25,12 @@ interface UtilizationChartProps { } function getBarColor(utilization: number): string { - if (utilization >= 100) return "#EF4444"; - if (utilization >= 85) return "#F59E0B"; - if (utilization >= 60) return "#3B82F6"; - return "#10B981"; + if (utilization >= 100) return GOOGLE_COLORS.red; + if (utilization >= 85) return GOOGLE_COLORS.yellow; + if (utilization >= 60) return GOOGLE_COLORS.blue; + return GOOGLE_COLORS.green; } -const CHART_GRID_COLOR = CHART_GRID; -const CHART_TICK_COLOR = CHART_TICK; - export function UtilizationChart({ engineers, maxVisible = 30 }: UtilizationChartProps) { const data = [...engineers] .sort((a, b) => b.utilization - a.utilization) @@ -54,11 +56,11 @@ export function UtilizationChart({ engineers, maxVisible = 30 }: UtilizationChar
    - + `${v}%`} @@ -66,7 +68,7 @@ export function UtilizationChart({ engineers, maxVisible = 30 }: UtilizationChar [`${value}%`, "Utilization"]} /> - - + + {data.map((entry, index) => ( ))} diff --git a/apps/delta-command/src/components/layout/Header.tsx b/apps/delta-command/src/components/layout/Header.tsx index c569ea4..75301d0 100644 --- a/apps/delta-command/src/components/layout/Header.tsx +++ b/apps/delta-command/src/components/layout/Header.tsx @@ -24,22 +24,22 @@ export function Header({ title, subtitle }: HeaderProps) {
    - +
    -
    +
    NF
    diff --git a/apps/delta-command/src/components/layout/Sidebar.tsx b/apps/delta-command/src/components/layout/Sidebar.tsx index 0e63768..f0686a2 100644 --- a/apps/delta-command/src/components/layout/Sidebar.tsx +++ b/apps/delta-command/src/components/layout/Sidebar.tsx @@ -7,7 +7,6 @@ import { Target, Users, FolderKanban, - Zap, } from "lucide-react"; import { cn } from "@/core/utils"; @@ -18,18 +17,41 @@ const navigation = [ { name: "Projects", href: "/projects", icon: FolderKanban }, ]; +function GoogleLogoMark() { + return ( +
    + +
    + ); +} + export function Sidebar() { const pathname = usePathname(); return (