From d4429cfed539d63ff1ee1a91982ed5619c38815d Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Fri, 21 Aug 2026 02:42:08 +0000 Subject: [PATCH 1/2] feat: add cinematic TrustDesk marketing landing at / Move the executive dashboard to /demo so the public route can be a portfolio hero: FPL navy/ivory/gold, one light, grain, and a live product window driven by the confidence engine. Co-authored-by: Trevor Fischer --- DESIGN.md | 37 ++++ README.md | 22 ++- app/{ => (demo)}/about/page.tsx | 2 +- app/{ => (demo)}/brief/page.tsx | 0 app/{ => (demo)}/controls/page.tsx | 0 app/{ => (demo)/demo}/page.tsx | 0 app/(demo)/layout.tsx | 13 ++ app/{ => (demo)}/questionnaires/[id]/page.tsx | 0 app/{ => (demo)}/questionnaires/page.tsx | 0 app/(marketing)/page.tsx | 5 + app/globals.css | 182 +++++++++++++++--- app/layout.tsx | 21 +- components/layout/nav-items.ts | 2 +- components/marketing/landing-page.tsx | 134 +++++++++++++ components/marketing/product-window.tsx | 171 ++++++++++++++++ 15 files changed, 549 insertions(+), 40 deletions(-) create mode 100644 DESIGN.md rename app/{ => (demo)}/about/page.tsx (99%) rename app/{ => (demo)}/brief/page.tsx (100%) rename app/{ => (demo)}/controls/page.tsx (100%) rename app/{ => (demo)/demo}/page.tsx (100%) create mode 100644 app/(demo)/layout.tsx rename app/{ => (demo)}/questionnaires/[id]/page.tsx (100%) rename app/{ => (demo)}/questionnaires/page.tsx (100%) create mode 100644 app/(marketing)/page.tsx create mode 100644 components/marketing/landing-page.tsx create mode 100644 components/marketing/product-window.tsx diff --git a/DESIGN.md b/DESIGN.md new file mode 100644 index 0000000..463ae0c --- /dev/null +++ b/DESIGN.md @@ -0,0 +1,37 @@ +# TrustDesk — visual language + +Portfolio surface for Fischer Product Lab. This is a cinematic, read-only demonstration — not a night SaaS, not a template. + +## Tokens + +| Token | Hex | Role | +| --- | --- | --- | +| Navy | `#0B1220` | Void. Page, canvas, chrome. | +| Ivory | `#F4EFE4` | Type and the primary CTA fill. | +| Gold | `#C4A35A` | The only accent. Rules, meta, rim-light, secondary CTA. | + +Atmosphere is one thing: **film grain + a single gold/ivory light** sitting over the product window. Status colors (approved / review / danger) exist only inside the product chrome, where they mean something. + +## Type + +Face: **Geist**. Mono: **Geist Mono**. Loaded via `next/font`. The live demo screens keep Hanken Grotesk + Fraunces so the product chrome stays itself. + +Four sizes, no more: + +| Name | Use | +| --- | --- | +| Display | Hero headline only | +| Title | Section heads | +| Body | Subcopy and how-it-works | +| Meta | Eyebrows, nav, footer — mono, tracked, uppercase | + +## Kill list + +- No purple. No indigo mesh. No default shadcn violet. +- No Inter as the face. +- No emoji icons. +- No Spline / R3F orb. +- No fake “trusted by” logos. +- No five CTAs. Two: **Open the demo** and **See how it decides**. +- No auth, Stripe, CMS, or a second product. +- Do not copy BridgeMind copy, logo, or blue beam. Steal the register: void, one light, grain, grotesque headline, real product under the light. diff --git a/README.md b/README.md index 1cf8c07..511ba24 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,8 @@ Five screens: | Route | Screen | Purpose | | --- | --- | --- | -| `/` | **Executive Dashboard** | Open questionnaires, automation rate, hours saved, revenue supported, legal/privacy escalations, SLA risk, and top control categories. | +| `/` | **Marketing** | Cinematic portfolio landing — hero, product chrome, how the engine decides. Visual language in [`DESIGN.md`](./DESIGN.md). | +| `/demo` | **Executive Dashboard** | Open questionnaires, automation rate, hours saved, revenue supported, legal/privacy escalations, SLA risk, and top control categories. | | `/questionnaires` | **Questionnaire Workspace** | Every in-flight question with confidence, status, mapped evidence, and reviewer — searchable and filterable. | | `/questionnaires/[id]` | **Questionnaire Detail** | One customer's request in full: each drafted answer, its five confidence factors, mapped evidence with live freshness, routing, and reasons. | | `/controls` | **Control & Evidence Library** | Approved response language with framework mappings, owners, and freshness — stale evidence surfaced first. | @@ -86,17 +87,20 @@ pnpm test ``` trustdesk/ +├── DESIGN.md # Locked tokens, type scale, kill list ├── app/ -│ ├── page.tsx # Executive dashboard (/) -│ ├── questionnaires/ -│ │ ├── page.tsx # Questionnaire workspace -│ │ └── [id]/page.tsx # Questionnaire detail -│ ├── controls/page.tsx # Control & evidence library -│ ├── brief/page.tsx # Executive brief -│ ├── about/page.tsx # About this demo -│ ├── layout.tsx # Root layout + app shell +│ ├── (marketing)/page.tsx # Cinematic landing (/) +│ ├── (demo)/ +│ │ ├── layout.tsx # App shell for the live demo +│ │ ├── demo/page.tsx # Executive dashboard (/demo) +│ │ ├── questionnaires/ # Workspace + detail +│ │ ├── controls/page.tsx +│ │ ├── brief/page.tsx +│ │ └── about/page.tsx +│ ├── layout.tsx # Root fonts + metadata │ └── globals.css # Design system (Tailwind v4 theme) ├── components/ +│ ├── marketing/ # Landing hero + product window │ ├── layout/ # Sidebar, top bar, app shell │ ├── dashboard/ # Status donut, category bar │ ├── question/ # Confidence ring, factor breakdown diff --git a/app/about/page.tsx b/app/(demo)/about/page.tsx similarity index 99% rename from app/about/page.tsx rename to app/(demo)/about/page.tsx index 1b21352..634ed21 100644 --- a/app/about/page.tsx +++ b/app/(demo)/about/page.tsx @@ -243,7 +243,7 @@ export default function AboutPage() { Built by Trevor Fischer · Fischer Product Lab

Back to dashboard diff --git a/app/brief/page.tsx b/app/(demo)/brief/page.tsx similarity index 100% rename from app/brief/page.tsx rename to app/(demo)/brief/page.tsx diff --git a/app/controls/page.tsx b/app/(demo)/controls/page.tsx similarity index 100% rename from app/controls/page.tsx rename to app/(demo)/controls/page.tsx diff --git a/app/page.tsx b/app/(demo)/demo/page.tsx similarity index 100% rename from app/page.tsx rename to app/(demo)/demo/page.tsx diff --git a/app/(demo)/layout.tsx b/app/(demo)/layout.tsx new file mode 100644 index 0000000..cbf4c60 --- /dev/null +++ b/app/(demo)/layout.tsx @@ -0,0 +1,13 @@ +import { AppShell } from "@/components/layout/app-shell"; + +export default function DemoLayout({ + children, +}: Readonly<{ + children: React.ReactNode; +}>) { + return ( +
+ {children} +
+ ); +} diff --git a/app/questionnaires/[id]/page.tsx b/app/(demo)/questionnaires/[id]/page.tsx similarity index 100% rename from app/questionnaires/[id]/page.tsx rename to app/(demo)/questionnaires/[id]/page.tsx diff --git a/app/questionnaires/page.tsx b/app/(demo)/questionnaires/page.tsx similarity index 100% rename from app/questionnaires/page.tsx rename to app/(demo)/questionnaires/page.tsx diff --git a/app/(marketing)/page.tsx b/app/(marketing)/page.tsx new file mode 100644 index 0000000..a8f150c --- /dev/null +++ b/app/(marketing)/page.tsx @@ -0,0 +1,5 @@ +import { LandingPage } from "@/components/marketing/landing-page"; + +export default function MarketingPage() { + return ; +} diff --git a/app/globals.css b/app/globals.css index fda7d35..6f716a0 100644 --- a/app/globals.css +++ b/app/globals.css @@ -1,57 +1,64 @@ @import "tailwindcss"; /* - TrustDesk design system — "executive trust command center". - Defining colors/fonts here (Tailwind v4 @theme) generates utilities - like bg-canvas, text-ink, border-hairline, font-display, etc. - Shares the Fischer Product Lab palette: near-black navy, ivory, gold. + TrustDesk design system — Fischer Product Lab. + Tokens live here so Tailwind v4 emits utilities like bg-canvas, text-ink, + border-hairline, font-display, font-marketing. + See DESIGN.md for the locked language and the kill list. */ @theme { - /* Surfaces (near-black navy, layered) */ - --color-canvas: #0b0f17; + /* Surfaces — void navy, layered */ + --color-navy: #0b1220; + --color-canvas: #0b1220; --color-surface: #0f1521; --color-surface-2: #141c2b; --color-hairline: #1e2738; --color-hairline-strong: #2b3754; - /* Text (ivory) */ - --color-ink: #f5efe1; + /* Text — ivory */ + --color-ivory: #f4efe4; + --color-ink: #f4efe4; --color-ink-muted: #a7aebc; --color-ink-faint: #6c7689; - /* Accents */ - --color-gold: #c9a45c; + /* One accent — gold */ + --color-gold: #c4a35a; --color-gold-soft: #e3c894; - --color-data: #9cc9ff; - /* Answer-status + evidence colors */ - --color-approved: #5fd6a0; /* Auto-Approved / Current evidence */ - --color-suggested: #9cc9ff; /* Suggested draft */ - --color-review: #e6a560; /* Needs Review / Expiring Soon */ - --color-danger: #e8736f; /* Escalate / Expired evidence */ + /* Product semantics (status only — never atmosphere) */ + --color-data: #9cc9ff; + --color-approved: #5fd6a0; + --color-suggested: #9cc9ff; + --color-review: #e6a560; + --color-danger: #e8736f; - /* Fonts (wired to next/font variables in layout.tsx) */ + /* Type */ --font-sans: var(--font-hanken), ui-sans-serif, system-ui, sans-serif; --font-display: var(--font-fraunces), Georgia, "Times New Roman", serif; + --font-marketing: var(--font-geist), ui-sans-serif, system-ui, sans-serif; + --font-mono: var(--font-geist-mono), ui-monospace, "IBM Plex Mono", monospace; } html { color-scheme: dark; + scroll-behavior: smooth; +} + +@media (prefers-reduced-motion: reduce) { + html { + scroll-behavior: auto; + } } body { - background-color: var(--color-canvas); - background-image: - radial-gradient(60rem 60rem at 12% -10%, rgba(201, 164, 92, 0.06), transparent 60%), - radial-gradient(50rem 50rem at 100% 0%, rgba(156, 201, 255, 0.05), transparent 55%); - background-attachment: fixed; + background-color: var(--color-navy); color: var(--color-ink); font-family: var(--font-sans); -webkit-font-smoothing: antialiased; } ::selection { - background-color: rgba(201, 164, 92, 0.28); + background-color: rgba(196, 163, 90, 0.28); color: var(--color-ink); } @@ -69,3 +76,132 @@ body { border: 2px solid transparent; background-clip: content-box; } + +/* Demo shell — faint gold wash only. No indigo mesh. */ +.demo-shell { + background-image: radial-gradient( + 50rem 36rem at 8% -8%, + rgba(196, 163, 90, 0.06), + transparent 58% + ); + background-attachment: fixed; +} + +/* -------------------------------------------------------------------------- */ +/* Marketing landing — one light, grain, four type sizes */ +/* -------------------------------------------------------------------------- */ + +.marketing { + --type-display: clamp(3.25rem, 9vw, 6.25rem); + --type-title: clamp(1.375rem, 2.4vw, 1.75rem); + --type-body: 1.0625rem; + --type-meta: 0.75rem; + position: relative; + isolation: isolate; + min-height: 100dvh; + font-family: var(--font-marketing); + color: var(--color-ivory); + background-color: var(--color-navy); +} + +.marketing-type-display { + font-size: var(--type-display); + line-height: 0.92; + letter-spacing: -0.045em; + font-weight: 500; +} + +.marketing-type-title { + font-size: var(--type-title); + line-height: 1.2; + letter-spacing: -0.03em; + font-weight: 500; +} + +.marketing-type-body { + font-size: var(--type-body); + line-height: 1.55; +} + +.marketing-type-meta { + font-family: var(--font-mono); + font-size: var(--type-meta); + line-height: 1.4; + letter-spacing: 0.16em; + text-transform: uppercase; +} + +/* Single light event — gold/ivory, centered over the product window */ +.marketing-light { + position: absolute; + top: -8%; + left: 50%; + z-index: 0; + width: min(72rem, 120vw); + height: 62vh; + transform: translateX(-50%); + pointer-events: none; + background: radial-gradient( + ellipse 55% 70% at 50% 18%, + rgba(244, 239, 228, 0.16) 0%, + rgba(196, 163, 90, 0.14) 28%, + rgba(196, 163, 90, 0.04) 52%, + transparent 72% + ); +} + +.marketing-grain { + position: fixed; + inset: 0; + z-index: 40; + pointer-events: none; + opacity: 0.2; + mix-blend-mode: overlay; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='g'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23g)'/%3E%3C/svg%3E"); +} + +.product-window { + box-shadow: + 0 0 0 1px rgba(196, 163, 90, 0.42), + 0 0 48px -10px rgba(196, 163, 90, 0.28), + 0 -28px 90px -24px rgba(244, 239, 228, 0.1), + 0 48px 90px -28px rgba(0, 0, 0, 0.7); +} + +@keyframes marketing-rise { + from { + opacity: 0; + transform: translateY(22px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.marketing-rise { + animation: marketing-rise 0.9s cubic-bezier(0.22, 1, 0.36, 1) both; +} + +.marketing-rise-delay-1 { + animation-delay: 0.08s; +} +.marketing-rise-delay-2 { + animation-delay: 0.16s; +} +.marketing-rise-delay-3 { + animation-delay: 0.28s; +} +.marketing-rise-delay-4 { + animation-delay: 0.42s; +} + +@media (prefers-reduced-motion: reduce) { + .marketing-rise, + .marketing-rise-delay-1, + .marketing-rise-delay-2, + .marketing-rise-delay-3, + .marketing-rise-delay-4 { + animation: none; + } +} diff --git a/app/layout.tsx b/app/layout.tsx index 8a05472..38847f7 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -1,7 +1,18 @@ import type { Metadata } from "next"; -import { Fraunces, Hanken_Grotesk } from "next/font/google"; +import { Fraunces, Geist, Geist_Mono, Hanken_Grotesk } from "next/font/google"; import "./globals.css"; -import { AppShell } from "@/components/layout/app-shell"; + +const geist = Geist({ + subsets: ["latin"], + variable: "--font-geist", + display: "swap", +}); + +const geistMono = Geist_Mono({ + subsets: ["latin"], + variable: "--font-geist-mono", + display: "swap", +}); const fraunces = Fraunces({ subsets: ["latin"], @@ -29,11 +40,9 @@ export default function RootLayout({ return ( - - {children} - + {children} ); } diff --git a/components/layout/nav-items.ts b/components/layout/nav-items.ts index 9152de5..f609479 100644 --- a/components/layout/nav-items.ts +++ b/components/layout/nav-items.ts @@ -15,7 +15,7 @@ export interface NavItem { } export const navItems: NavItem[] = [ - { href: "/", label: "Dashboard", icon: LayoutDashboard, exact: true }, + { href: "/demo", label: "Dashboard", icon: LayoutDashboard, exact: true }, { href: "/questionnaires", label: "Questionnaires", icon: ClipboardList }, { href: "/controls", label: "Control Library", icon: Library }, { href: "/brief", label: "Executive Brief", icon: FileText }, diff --git a/components/marketing/landing-page.tsx b/components/marketing/landing-page.tsx new file mode 100644 index 0000000..75a37dd --- /dev/null +++ b/components/marketing/landing-page.tsx @@ -0,0 +1,134 @@ +import Link from "next/link"; +import { ProductWindow } from "./product-window"; + +const STEPS = [ + { + n: "01", + label: "Ingest", + copy: "A customer question enters the queue — encryption, privacy, incident response, the same asks that stall a deal.", + }, + { + n: "02", + label: "Map", + copy: "Each prompt is matched to approved control evidence. Freshness is derived live from expiry dates, so it cannot be faked.", + }, + { + n: "03", + label: "Draft", + copy: "Response language comes from the library. Security, Sales, Legal, and Privacy stop rewriting it under deadline.", + }, + { + n: "04", + label: "Score & route", + copy: "A deterministic confidence score. Sensitive or stale-backed items go to a human. A high score can never override a gate.", + }, +]; + +export function LandingPage() { + return ( +
+
+
+ +
+
+ + + TrustDesk + + + Fischer Product Lab + + + + Open the demo + +
+ +
+
+

+ Portfolio demonstration · read-only · synthetic data +

+

+ Questionnaires, +
+ answered with evidence. +

+

+ Security, Sales, Legal, and Privacy stop rewriting the same + answers under deal pressure. +

+
+ + Open the demo + + + See how it decides + +
+
+ +
+ + + Open the Northwind Capital questionnaire in the live demo + + + +
+ +
+

How it decides

+

+ Confidence is a pure function. Same inputs, same verdict. No + model in the loop. +

+
    + {STEPS.map((step) => ( +
  1. + + {step.n} + + + {step.label} + +

    + {step.copy} +

    +
  2. + ))} +
+
+
+ +
+

+ Fischer Product Lab · portfolio demonstration · © Trevor Fischer +

+
+
+
+ ); +} diff --git a/components/marketing/product-window.tsx b/components/marketing/product-window.tsx new file mode 100644 index 0000000..672d014 --- /dev/null +++ b/components/marketing/product-window.tsx @@ -0,0 +1,171 @@ +import { FileText, ShieldCheck, UserCheck } from "lucide-react"; +import { questionnaires } from "@/data/questionnaires"; +import { controls } from "@/data/controls"; +import { + computeConfidence, + controlsForQuestion, + evidenceStatusFor, +} from "@/lib/confidence"; +import { slaStatusFor } from "@/lib/metrics"; +import { StatusBadge } from "@/components/status-badge"; +import { EvidenceBadge } from "@/components/evidence-badge"; +import { SlaBadge } from "@/components/sla-badge"; +import { ConfidenceRing } from "@/components/question/confidence-ring"; + +const QUESTIONNAIRE_ID = "qn-001"; +const QUESTION_ID = "q-001"; + +/** + * Faithful in-page chrome of the Northwind questionnaire detail — + * the real engine, the real evidence, the real verdict. Not a generic dashboard. + */ +export function ProductWindow() { + const questionnaire = questionnaires.find((q) => q.id === QUESTIONNAIRE_ID); + const question = questionnaire?.questions.find((q) => q.id === QUESTION_ID); + if (!questionnaire || !question) return null; + + const result = computeConfidence(question, controls); + const mapped = controlsForQuestion(question, controls); + const sla = slaStatusFor(questionnaire); + + return ( +
+ {/* Browser chrome */} +
+
+ + + +
+
+ trustdesk · questionnaires / {questionnaire.id} +
+
+ +
+ {/* Mini sidebar — same rail as the live demo */} + + + {/* Question detail — mirrors /questionnaires/qn-001 */} +
+
+
+

+ {questionnaire.customer} +

+ +
+

+ {questionnaire.id} · {questionnaire.industry} +

+
+ +
+
+
+ + {question.category} · {question.id} + +

+ {question.prompt} +

+
+
+ + +
+
+ +
+
+ + Drafted response +
+

+ {question.suggestedAnswer} +

+
+ +
+
+
+ Mapped evidence +
+
    + {mapped.map((control) => ( +
  • + + + {control.id} + + + {control.title} + + + +
  • + ))} +
+
+ +
+
+ + Routed to + {result.reviewer} +
+

+ {result.reasons[0]} +

+
+
+
+
+
+
+ ); +} From ecea2a6ea996a61f1ef4ce460ec0b16a2dd7e114 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Fri, 21 Aug 2026 02:48:27 +0000 Subject: [PATCH 2/2] polish: sit the gold light on the product window Move the single light event over the TrustDesk chrome and raise grain so the hero reads as void + one atmosphere, not a flat navy page. Co-authored-by: Trevor Fischer --- app/globals.css | 22 +++++++++++----------- components/marketing/landing-page.tsx | 10 +++++----- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/app/globals.css b/app/globals.css index 6f716a0..a8114fe 100644 --- a/app/globals.css +++ b/app/globals.css @@ -108,7 +108,7 @@ body { font-size: var(--type-display); line-height: 0.92; letter-spacing: -0.045em; - font-weight: 500; + font-weight: 600; } .marketing-type-title { @@ -131,22 +131,22 @@ body { text-transform: uppercase; } -/* Single light event — gold/ivory, centered over the product window */ +/* Single light event — gold/ivory pool sitting on the product window */ .marketing-light { position: absolute; - top: -8%; + top: -28%; left: 50%; z-index: 0; - width: min(72rem, 120vw); - height: 62vh; + width: min(56rem, 100vw); + height: 70%; transform: translateX(-50%); pointer-events: none; background: radial-gradient( - ellipse 55% 70% at 50% 18%, - rgba(244, 239, 228, 0.16) 0%, - rgba(196, 163, 90, 0.14) 28%, - rgba(196, 163, 90, 0.04) 52%, - transparent 72% + ellipse 58% 62% at 50% 38%, + rgba(244, 239, 228, 0.28) 0%, + rgba(196, 163, 90, 0.2) 26%, + rgba(196, 163, 90, 0.07) 52%, + transparent 70% ); } @@ -155,7 +155,7 @@ body { inset: 0; z-index: 40; pointer-events: none; - opacity: 0.2; + opacity: 0.32; mix-blend-mode: overlay; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='g'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23g)'/%3E%3C/svg%3E"); } diff --git a/components/marketing/landing-page.tsx b/components/marketing/landing-page.tsx index 75a37dd..0398bea 100644 --- a/components/marketing/landing-page.tsx +++ b/components/marketing/landing-page.tsx @@ -27,7 +27,6 @@ const STEPS = [ export function LandingPage() { return (
-
@@ -65,13 +64,13 @@ export function LandingPage() {
Open the demo See how it decides @@ -79,12 +78,13 @@ export function LandingPage() {
+
Open the Northwind Capital questionnaire in the live demo