diff --git a/.gitignore b/.gitignore
index c84f02edfe..8e1e4f57fc 100644
--- a/.gitignore
+++ b/.gitignore
@@ -14,6 +14,7 @@ supabase/.seed/
*storybook.log
.cache/
+.cursor/
internal
.turbo
diff --git a/apps/web/content/solutions/founders.mdx b/apps/web/content/solutions/founders.mdx
new file mode 100644
index 0000000000..b5b683c577
--- /dev/null
+++ b/apps/web/content/solutions/founders.mdx
@@ -0,0 +1,55 @@
+---
+label: "For Founders"
+icon: "mdi:rocket-launch"
+order: 2
+metaTitle: "AI Meeting Notes for Founders - Char"
+metaDescription: "For founders running investor calls, customer discovery, team 1:1s, and board updates. Char keeps every conversation in one place with the context you need to act."
+hero:
+ badgeText: "For Founders"
+ title: "One source of truth\nfor every conversation you run"
+ description: "Investor calls, customer discovery, team 1:1s, hiring loops, board updates. Char captures it all with zero overhead and keeps the context you actually need to act."
+ primaryCTA:
+ label: "Download for free"
+ to: "/download/"
+ secondaryCTA:
+ label: "See how it works"
+ to: "/product/ai-notetaking/"
+features:
+ title: "Built for high-agency founders"
+ description: "Every founder has more context than time. Char keeps what happened, why it mattered, and what comes next."
+ items:
+ - icon: "mdi:source-branch"
+ title: "One source of truth"
+ description: "Collapse Notion docs, email threads, and Slack DMs into a single searchable memory tied to the meeting it came from."
+ - icon: "mdi:account-multiple-plus"
+ title: "Investor & customer memory"
+ description: "Never re-introduce yourself to an investor or repeat a discovery question. Char surfaces the last conversation before the next one starts."
+ - icon: "mdi:clipboard-list"
+ title: "Cross-context agendas"
+ description: "Switch from board prep to a customer call to a 1:1 without losing the thread. Char builds the agenda from what actually happened."
+ - icon: "mdi:robot-happy"
+ title: "Hand off to AI agents"
+ description: "Turn takeaways into follow-ups, drafts, and tickets — pass them to Claude, Cursor, or your own agents with one click."
+ - icon: "mdi:shield-lock"
+ title: "Your data, your rules"
+ description: "No bots on calls. Meetings stay on your device. Bring your own keys. Share only what you choose."
+ - icon: "mdi:chart-line"
+ title: "Built for speed, not seats"
+ description: "Designed for founders who move fast, without admin overhead, approval queues, or complicated permission models."
+useCases:
+ title: "Every conversation a founder runs"
+ description: "From fundraising to hiring to shipping, Char keeps it all in one place."
+ items:
+ - title: "Investor calls"
+ description: "Capture every pitch, commitment, and follow-up. Walk into the next round with the full history in hand."
+ - title: "Customer discovery"
+ description: "Turn raw interview audio into themes, quotes, and a live picture of what customers actually want."
+ - title: "Team 1:1s & stand-ups"
+ description: "Remember what you promised, what they raised, and what changed between syncs."
+ - title: "Hiring & board updates"
+ description: "Interview loops, board prep, and investor updates drafted straight from the meetings that informed them."
+cta:
+ title: "Run every conversation like your best one"
+ description: "Join founders using Char to keep the context that makes fast companies move."
+---
+
diff --git a/apps/web/package.json b/apps/web/package.json
index 257198a6c2..330df4235e 100644
--- a/apps/web/package.json
+++ b/apps/web/package.json
@@ -7,7 +7,8 @@
"build": "NODE_OPTIONS='--max-old-space-size=8192' vite build && cp public/sitemap.xml dist/client/sitemap.xml && pagefind --site ./dist/client",
"serve": "vite preview",
"test": "node --test",
- "typecheck": "tsc --project tsconfig.json --noEmit",
+ "gen:content-collections": "node scripts/build-content-collections.js",
+ "typecheck": "pnpm run gen:content-collections && tsc --project tsconfig.json --noEmit",
"gen:agents": "node scripts/gen-agents.js"
},
"dependencies": {
diff --git a/apps/web/public/handdrawing/bracket-left.svg b/apps/web/public/handdrawing/bracket-left.svg
new file mode 100644
index 0000000000..9344c703d8
--- /dev/null
+++ b/apps/web/public/handdrawing/bracket-left.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/apps/web/public/handdrawing/bracket-right.svg b/apps/web/public/handdrawing/bracket-right.svg
new file mode 100644
index 0000000000..caa86b3bc1
--- /dev/null
+++ b/apps/web/public/handdrawing/bracket-right.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/apps/web/public/handdrawing/char-signature.svg b/apps/web/public/handdrawing/char-signature.svg
new file mode 100644
index 0000000000..287add07c2
--- /dev/null
+++ b/apps/web/public/handdrawing/char-signature.svg
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/apps/web/public/handdrawing/hyprnote-signature.svg b/apps/web/public/handdrawing/hyprnote-signature.svg
new file mode 100644
index 0000000000..cef6ee4727
--- /dev/null
+++ b/apps/web/public/handdrawing/hyprnote-signature.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/apps/web/public/handdrawing/important.svg b/apps/web/public/handdrawing/important.svg
new file mode 100644
index 0000000000..05af89d41d
--- /dev/null
+++ b/apps/web/public/handdrawing/important.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/apps/web/public/handdrawing/scribbling.svg b/apps/web/public/handdrawing/scribbling.svg
new file mode 100644
index 0000000000..1bb15962d1
--- /dev/null
+++ b/apps/web/public/handdrawing/scribbling.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/apps/web/public/handdrawing/sleeping.svg b/apps/web/public/handdrawing/sleeping.svg
new file mode 100644
index 0000000000..1e5ae0efc3
--- /dev/null
+++ b/apps/web/public/handdrawing/sleeping.svg
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/apps/web/public/handdrawing/sunset.svg b/apps/web/public/handdrawing/sunset.svg
new file mode 100644
index 0000000000..9afcb56cd7
--- /dev/null
+++ b/apps/web/public/handdrawing/sunset.svg
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/apps/web/public/handdrawing/thinking.svg b/apps/web/public/handdrawing/thinking.svg
new file mode 100644
index 0000000000..773bc73906
--- /dev/null
+++ b/apps/web/public/handdrawing/thinking.svg
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/apps/web/public/icons/bracket-left.svg b/apps/web/public/icons/bracket-left.svg
new file mode 100644
index 0000000000..4e8e1bfe0b
--- /dev/null
+++ b/apps/web/public/icons/bracket-left.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/apps/web/public/icons/bracket-right.svg b/apps/web/public/icons/bracket-right.svg
new file mode 100644
index 0000000000..4dad56d8d8
--- /dev/null
+++ b/apps/web/public/icons/bracket-right.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/apps/web/public/icons/claude.svg b/apps/web/public/icons/claude.svg
new file mode 100644
index 0000000000..879ad81261
--- /dev/null
+++ b/apps/web/public/icons/claude.svg
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/apps/web/public/icons/cursor.svg b/apps/web/public/icons/cursor.svg
new file mode 100644
index 0000000000..383af65661
--- /dev/null
+++ b/apps/web/public/icons/cursor.svg
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/apps/web/scripts/build-content-collections.js b/apps/web/scripts/build-content-collections.js
new file mode 100644
index 0000000000..198db12091
--- /dev/null
+++ b/apps/web/scripts/build-content-collections.js
@@ -0,0 +1,27 @@
+import {
+ createBuilder,
+ suppressDeprecatedWarnings,
+} from "@content-collections/core";
+import { dirname, join } from "path";
+import { fileURLToPath } from "url";
+
+const __filename = fileURLToPath(import.meta.url);
+const __dirname = dirname(__filename);
+const configPath = join(__dirname, "../content-collections.ts");
+
+// Keep typecheck output focused on actual failures until the deprecated
+// content-collections config shape is migrated separately.
+suppressDeprecatedWarnings(
+ "collectionsConfigProperty",
+ "implicitContentProperty",
+);
+
+async function main() {
+ const builder = await createBuilder(configPath);
+ await builder.build();
+}
+
+main().catch((error) => {
+ console.error(error);
+ process.exit(1);
+});
diff --git a/apps/web/src/components/daily-note-mock.tsx b/apps/web/src/components/daily-note-mock.tsx
new file mode 100644
index 0000000000..25ba330ab8
--- /dev/null
+++ b/apps/web/src/components/daily-note-mock.tsx
@@ -0,0 +1,172 @@
+import { Icon } from "@iconify-icon/react";
+
+import { cn } from "@hypr/utils";
+
+export function DailyNoteMock({ className }: { className?: string }) {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ Team Standup
+
+
+
+ Design review
+
+
+
+
+
+
+
+
+
+
+ );
+}
+
+function DayHeader({
+ label,
+ today,
+ muted,
+}: {
+ label: string;
+ today?: boolean;
+ muted?: boolean;
+}) {
+ return (
+
+
+ {label}
+
+ {today && (
+
+ today
+
+ )}
+
+ );
+}
+
+function DaySeparator() {
+ return
;
+}
+
+function MeetingTaskRow({
+ title,
+ time,
+ done,
+}: {
+ title: string;
+ time: string;
+ done?: boolean;
+}) {
+ return (
+
+
+
+
+ {title}
+
+
+ {time}
+
+
+ );
+}
+
+function ActionTaskRow({ label, due }: { label: string; due?: boolean }) {
+ return (
+
+
+ {label}
+ {due && (
+
+ Due
+
+ )}
+
+ );
+}
+
+function TaskCheckbox({ done }: { done?: boolean }) {
+ return (
+
+ {done && }
+
+ );
+}
diff --git a/apps/web/src/components/download-button.tsx b/apps/web/src/components/download-button.tsx
index 85eaf8dcd2..1890cb4a62 100644
--- a/apps/web/src/components/download-button.tsx
+++ b/apps/web/src/components/download-button.tsx
@@ -82,7 +82,7 @@ export function DownloadButton({
download
onClick={handleClick}
className={cn([
- "group relative flex h-9 items-center justify-center overflow-hidden px-5 text-sm",
+ "group relative flex h-9 items-center justify-center overflow-hidden px-6 text-sm",
"surface-dark rounded-full text-white",
])}
>
@@ -109,7 +109,7 @@ export function DownloadButton({
download
onClick={handleClick}
className={cn([
- "group relative flex h-14 items-center justify-center overflow-hidden pr-8 pl-4",
+ "group relative flex h-14 items-center justify-center overflow-hidden pr-8 pl-8",
"surface-dark rounded-full text-white",
])}
>
diff --git a/apps/web/src/components/header.tsx b/apps/web/src/components/header.tsx
index 89e3cbc1a5..1866247792 100644
--- a/apps/web/src/components/header.tsx
+++ b/apps/web/src/components/header.tsx
@@ -45,6 +45,7 @@ function getMaxWidthClass(pathname: string): string {
}
const featuresList = [
+ { to: "/product/daily-notes", label: "Daily Notes" },
{ to: "/product/ai-notetaking", label: "AI Notetaking" },
{ to: "/product/search", label: "Searchable Notes" },
{ to: "/gallery?type=template", label: "Custom Templates" },
diff --git a/apps/web/src/components/jira-workflow-mock.tsx b/apps/web/src/components/jira-workflow-mock.tsx
new file mode 100644
index 0000000000..36df0eb107
--- /dev/null
+++ b/apps/web/src/components/jira-workflow-mock.tsx
@@ -0,0 +1,101 @@
+import { Icon } from "@iconify-icon/react";
+import { AnimatePresence, motion } from "motion/react";
+import { useEffect, useState } from "react";
+
+import { JiraToolCall } from "./ai-feature-panel";
+
+const CYCLE_MS = 6500;
+
+export function JiraWorkflowMock() {
+ const [step, setStep] = useState(0);
+ const [loopKey, setLoopKey] = useState(0);
+
+ useEffect(() => {
+ setStep(0);
+ const t1 = setTimeout(() => setStep(1), 400);
+ const t2 = setTimeout(() => setStep(2), 1200);
+ const t3 = setTimeout(() => setStep(3), 3800);
+ const restart = setTimeout(() => setLoopKey((k) => k + 1), CYCLE_MS);
+ return () => {
+ clearTimeout(t1);
+ clearTimeout(t2);
+ clearTimeout(t3);
+ clearTimeout(restart);
+ };
+ }, [loopKey]);
+
+ return (
+
+
+
+
+
+ {step >= 1 && (
+
+
+
+ Create a Jira ticket for the mobile bug and assign to Sarah
+
+
+
+ )}
+ {step >= 2 && (
+
+
+
+ )}
+ {step >= 3 && (
+
+
+
+ Jira ticket ENG-247 created and assigned to Sarah.
+
+
+ )}
+
+
+
+
+
+
+ Ask about your notes...
+
+
+ Send
+
+
+
+
+ );
+}
diff --git a/apps/web/src/components/sidebar.tsx b/apps/web/src/components/sidebar.tsx
index d5b5038d9f..a30c253847 100644
--- a/apps/web/src/components/sidebar.tsx
+++ b/apps/web/src/components/sidebar.tsx
@@ -41,6 +41,7 @@ type MenuGroup = {
};
const featuresList: MenuItem[] = [
+ { to: "/product/daily-notes", label: "Daily Notes" },
{ to: "/product/ai-notetaking", label: "AI Notetaking" },
{ to: "/product/search", label: "Searchable Notes" },
{ to: "/gallery?type=template", label: "Custom Templates" },
diff --git a/apps/web/src/components/stickers.tsx b/apps/web/src/components/stickers.tsx
new file mode 100644
index 0000000000..a072778062
--- /dev/null
+++ b/apps/web/src/components/stickers.tsx
@@ -0,0 +1,83 @@
+import { cn } from "@hypr/utils";
+
+import {
+ GITHUB_LAST_SEEN_STARS,
+ GITHUB_ORG_REPO,
+ useGitHubStats,
+} from "../queries";
+
+export function YCombinatorSticker({ className }: { className?: string }) {
+ return (
+
+ );
+}
+
+function formatStars(n: number) {
+ return n >= 1000 ? `${(n / 1000).toFixed(1)}k` : `${n}`;
+}
+
+export function GitHubStarsSticker({ className }: { className?: string }) {
+ const githubStats = useGitHubStats();
+ const starCount = githubStats.data?.stars ?? GITHUB_LAST_SEEN_STARS;
+
+ return (
+
+ );
+}
diff --git a/apps/web/src/components/timeline-recall-mock.tsx b/apps/web/src/components/timeline-recall-mock.tsx
new file mode 100644
index 0000000000..53864cc04c
--- /dev/null
+++ b/apps/web/src/components/timeline-recall-mock.tsx
@@ -0,0 +1,83 @@
+import { motion } from "motion/react";
+
+const timelineBlocks = [
+ {
+ time: "10:00 – 11:30 AM",
+ title: "Finances & Admin",
+ body: "Checked Mercury banking and PostHog analytics, then shifted to tax and payroll admin.",
+ bullets: [
+ "Pulled contractor documents from Gusto",
+ "Drafted W-9 request emails from Mail",
+ ],
+ },
+ {
+ time: "11:50 AM – 12:25 PM",
+ title: 'Writing "Moats Suck"',
+ body: "Drafted an essay in Obsidian, then cross-published to LinkedIn and X.",
+ bullets: ["~600–780 words", "Shared in #general and #uchar"],
+ },
+ {
+ time: "1:00 – 3:30 PM",
+ title: "Coding — Char v0.0.8",
+ body: "Updated Char and configured transcription settings.",
+ bullets: [
+ "Added created_at timestamp display",
+ "Explored Soniqo Docs for speech enhancement",
+ ],
+ },
+ {
+ time: "3:30 – 4:30 PM",
+ title: "Discord & Team",
+ body: "Active in the CEO's Office channel, directing the AI agent.",
+ bullets: ["Reviewed SQLite migration work", "Triaged GitHub issue #5047"],
+ },
+];
+
+export function TimelineRecallMock() {
+ return (
+
+
+
+
+
+ {[...timelineBlocks, ...timelineBlocks].map((block, i) => (
+
+ ))}
+
+
+ );
+}
+
+function TimelineBlock({
+ time,
+ title,
+ body,
+ bullets,
+}: {
+ time: string;
+ title: string;
+ body: string;
+ bullets: string[];
+}) {
+ return (
+
+
+ {time}
+
+
{title}
+
{body}
+
+ {bullets.map((b) => (
+
+ •
+ {b}
+
+ ))}
+
+
+ );
+}
diff --git a/apps/web/src/routeTree.gen.ts b/apps/web/src/routeTree.gen.ts
index 54ce54be88..3355d4cbe4 100644
--- a/apps/web/src/routeTree.gen.ts
+++ b/apps/web/src/routeTree.gen.ts
@@ -83,6 +83,7 @@ import { Route as ViewProductLocalAiRouteImport } from './routes/_view/product/l
import { Route as ViewProductIntegrationsRouteImport } from './routes/_view/product/integrations'
import { Route as ViewProductFlexibleAiRouteImport } from './routes/_view/product/flexible-ai'
import { Route as ViewProductExtensionsRouteImport } from './routes/_view/product/extensions'
+import { Route as ViewProductDailyNotesRouteImport } from './routes/_view/product/daily-notes'
import { Route as ViewProductBotRouteImport } from './routes/_view/product/bot'
import { Route as ViewProductApiRouteImport } from './routes/_view/product/api'
import { Route as ViewProductAiNotetakingRouteImport } from './routes/_view/product/ai-notetaking'
@@ -521,6 +522,11 @@ const ViewProductExtensionsRoute = ViewProductExtensionsRouteImport.update({
path: '/product/extensions',
getParentRoute: () => ViewRouteRoute,
} as any)
+const ViewProductDailyNotesRoute = ViewProductDailyNotesRouteImport.update({
+ id: '/product/daily-notes',
+ path: '/product/daily-notes',
+ getParentRoute: () => ViewRouteRoute,
+} as any)
const ViewProductBotRoute = ViewProductBotRouteImport.update({
id: '/product/bot',
path: '/product/bot',
@@ -924,6 +930,7 @@ export interface FileRoutesByFullPath {
'/product/ai-notetaking': typeof ViewProductAiNotetakingRoute
'/product/api': typeof ViewProductApiRoute
'/product/bot': typeof ViewProductBotRoute
+ '/product/daily-notes': typeof ViewProductDailyNotesRoute
'/product/extensions': typeof ViewProductExtensionsRoute
'/product/flexible-ai': typeof ViewProductFlexibleAiRoute
'/product/integrations': typeof ViewProductIntegrationsRoute
@@ -1059,6 +1066,7 @@ export interface FileRoutesByTo {
'/product/ai-notetaking': typeof ViewProductAiNotetakingRoute
'/product/api': typeof ViewProductApiRoute
'/product/bot': typeof ViewProductBotRoute
+ '/product/daily-notes': typeof ViewProductDailyNotesRoute
'/product/extensions': typeof ViewProductExtensionsRoute
'/product/flexible-ai': typeof ViewProductFlexibleAiRoute
'/product/integrations': typeof ViewProductIntegrationsRoute
@@ -1201,6 +1209,7 @@ export interface FileRoutesById {
'/_view/product/ai-notetaking': typeof ViewProductAiNotetakingRoute
'/_view/product/api': typeof ViewProductApiRoute
'/_view/product/bot': typeof ViewProductBotRoute
+ '/_view/product/daily-notes': typeof ViewProductDailyNotesRoute
'/_view/product/extensions': typeof ViewProductExtensionsRoute
'/_view/product/flexible-ai': typeof ViewProductFlexibleAiRoute
'/_view/product/integrations': typeof ViewProductIntegrationsRoute
@@ -1343,6 +1352,7 @@ export interface FileRouteTypes {
| '/product/ai-notetaking'
| '/product/api'
| '/product/bot'
+ | '/product/daily-notes'
| '/product/extensions'
| '/product/flexible-ai'
| '/product/integrations'
@@ -1478,6 +1488,7 @@ export interface FileRouteTypes {
| '/product/ai-notetaking'
| '/product/api'
| '/product/bot'
+ | '/product/daily-notes'
| '/product/extensions'
| '/product/flexible-ai'
| '/product/integrations'
@@ -1619,6 +1630,7 @@ export interface FileRouteTypes {
| '/_view/product/ai-notetaking'
| '/_view/product/api'
| '/_view/product/bot'
+ | '/_view/product/daily-notes'
| '/_view/product/extensions'
| '/_view/product/flexible-ai'
| '/_view/product/integrations'
@@ -2275,6 +2287,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof ViewProductExtensionsRouteImport
parentRoute: typeof ViewRouteRoute
}
+ '/_view/product/daily-notes': {
+ id: '/_view/product/daily-notes'
+ path: '/product/daily-notes'
+ fullPath: '/product/daily-notes'
+ preLoaderRoute: typeof ViewProductDailyNotesRouteImport
+ parentRoute: typeof ViewRouteRoute
+ }
'/_view/product/bot': {
id: '/_view/product/bot'
path: '/product/bot'
@@ -2833,6 +2852,7 @@ interface ViewRouteRouteChildren {
ViewProductAiNotetakingRoute: typeof ViewProductAiNotetakingRoute
ViewProductApiRoute: typeof ViewProductApiRoute
ViewProductBotRoute: typeof ViewProductBotRoute
+ ViewProductDailyNotesRoute: typeof ViewProductDailyNotesRoute
ViewProductExtensionsRoute: typeof ViewProductExtensionsRoute
ViewProductFlexibleAiRoute: typeof ViewProductFlexibleAiRoute
ViewProductIntegrationsRoute: typeof ViewProductIntegrationsRoute
@@ -2903,6 +2923,7 @@ const ViewRouteRouteChildren: ViewRouteRouteChildren = {
ViewProductAiNotetakingRoute: ViewProductAiNotetakingRoute,
ViewProductApiRoute: ViewProductApiRoute,
ViewProductBotRoute: ViewProductBotRoute,
+ ViewProductDailyNotesRoute: ViewProductDailyNotesRoute,
ViewProductExtensionsRoute: ViewProductExtensionsRoute,
ViewProductFlexibleAiRoute: ViewProductFlexibleAiRoute,
ViewProductIntegrationsRoute: ViewProductIntegrationsRoute,
diff --git a/apps/web/src/routes/_view/index.tsx b/apps/web/src/routes/_view/index.tsx
index d0adaa7d5e..d00cd42710 100644
--- a/apps/web/src/routes/_view/index.tsx
+++ b/apps/web/src/routes/_view/index.tsx
@@ -4,28 +4,41 @@ import { useForm } from "@tanstack/react-form";
import { useMutation } from "@tanstack/react-query";
import { createFileRoute, Link } from "@tanstack/react-router";
import { allArticles } from "content-collections";
-import { VideoIcon } from "lucide-react";
-import { AnimatePresence, motion, useInView } from "motion/react";
-import { useCallback, useEffect, useRef, useState } from "react";
+import { Mail, PencilLine, VideoIcon, SquareCheck } from "lucide-react";
+import {
+ AnimatePresence,
+ motion,
+ useInView,
+ useMotionValue,
+ useMotionValueEvent,
+ useScroll,
+ useTransform,
+} from "motion/react";
+import React, {
+ useCallback,
+ useEffect,
+ useMemo,
+ useRef,
+ useState,
+} from "react";
import { cn } from "@hypr/utils";
-import { AcquisitionLinkGrid } from "@/components/acquisition-link-grid";
import {
ContactSearchToolCall,
TranscriptToolCall,
} from "@/components/ai-feature-panel";
-import { AppPreviewSection } from "@/components/app-preview";
import { CTASection } from "@/components/cta-section";
+import { DailyNoteMock } from "@/components/daily-note-mock";
import { DownloadButton } from "@/components/download-button";
import { GitHubOpenSource } from "@/components/github-open-source";
-import { GithubStars } from "@/components/github-stars";
import { Image } from "@/components/image";
+import { JiraWorkflowMock } from "@/components/jira-workflow-mock";
import { LogoCloud } from "@/components/logo-cloud";
import { FAQ, FAQItem } from "@/components/mdx-shared";
-import { NotebookGrid } from "@/components/notebook-grid";
import { SocialCard } from "@/components/social-card";
-import { VideoModal } from "@/components/video-modal";
+import { GitHubStarsSticker, YCombinatorSticker } from "@/components/stickers";
+import { TimelineRecallMock } from "@/components/timeline-recall-mock";
import { addContact } from "@/functions/loops";
import { useHeroContext } from "@/hooks/use-hero-context";
import { getHeroCTA, usePlatform } from "@/hooks/use-platform";
@@ -40,8 +53,6 @@ import {
import { AnnouncementBanner } from "./route";
-const MUX_PLAYBACK_ID = "1s01BC9LBwzygOUWk9Pdn011KuxvIQRMbTEfCpOypfdrw";
-
const mainFeatures = [
{
icon: "mdi:text-box-outline",
@@ -133,43 +144,32 @@ function useHasEnteredView(
}
function Component() {
- const [expandedVideo, setExpandedVideo] = useState(null);
const heroInputRef = useRef(null);
return (
- setExpandedVideo(null)}
- />
);
}
function HeroSection({
- onVideoExpand,
heroInputRef,
}: {
- onVideoExpand: (id: string) => void;
heroInputRef: React.RefObject;
}) {
const platform = usePlatform();
@@ -243,21 +243,65 @@ function HeroSection({
id="hero"
className="isolate flex w-full overflow-visible pt-10 text-left"
>
-
-
-
+
+
+
- Meeting Notes You Own
+ AI daily notes that remember and act
-
- Char captures every meeting without a bot and keeps data on
- your device.
+
+ Char records your meetings without bots, pulls action items
+ from your{" "}
+
+ {" "}
+ emails
+
+ , and builds a{" "}
+
+ {" "}
+ daily note
+ {" "}
+ with everything you need to do.
+
+
+ You review it,{" "}
+
+ {" "}
+ tick what's done
+
+ , and hand off the rest to AI agents like{" "}
+
+
+ Claude
+ {" "}
+ or{" "}
+
+
+ Cursor
+
+ .
{heroCTA.showInput ? (
-
-
+
+
+
+
-
-
-
-
-
-
- Backed by
-
- Y Combinator
-
-
-
-
-
-
-
-
onVideoExpand(MUX_PLAYBACK_ID)}
- className="absolute inset-0 flex items-end justify-end p-3 transition-transform duration-200 hover:scale-105"
- aria-label="Open product demo video"
- >
-
-
-
-
+
+
@@ -1819,133 +1811,6 @@ export function AISection() {
);
}
-export function GrowsWithYouSection() {
- return (
-
-
-
-
- Char grows with you
-
-
- Add people from meetings in contacts, grow knowledge about your
- chats and context of previous meetings
-
-
- Explore all features
-
-
-
-
-
-
-
-
-
-
- S
-
-
-
Sarah Chen
-
- Product Lead · Acme Inc
-
-
-
-
- sarah@acme.com · +1 (415) 555-0123
-
-
-
- Last conversation
-
-
- Discussed Q2 roadmap priorities and timeline for the mobile
- redesign. Agreed to share updated specs by Friday.
-
-
-
-
-
-
- Have your contacts in one place
-
-
- Import contacts and watch them come alive with context once you
- actually meet.
-
-
-
-
- All your chats linked
-
-
-
-
- Generate summaries from meetings
-
-
-
-
-
-
-
-
-
-
-
-
- Weekly Team Sync
-
-
- Starting in 2 min
-
-
-
-
- Start listening
-
-
-
-
-
- Work with your calendar
-
-
- Connect your calendar for intelligent meeting preparation and
- automatic note organization.
-
-
-
-
- Automatic meeting linking
-
-
-
-
- Pre-meeting context and preparation
-
-
-
-
- Timeline view with notes
-
-
-
-
-
-
-
-
- );
-}
-
export function MainFeaturesSection({
featuresScrollRef,
selectedFeature,
@@ -2481,16 +2346,16 @@ const solutionColors: Record<
string,
{ accent: string; bg: string; border: string }
> = {
+ founders: {
+ accent: "oklch(0.62 0.17 42)",
+ bg: "#fff7ed",
+ border: "#fed7aa",
+ },
developers: {
accent: "oklch(0.55 0.2245 292.58)",
bg: "#f5f3ff",
border: "#ddd6fe",
},
- enterprise: {
- accent: "#374151",
- bg: "#f9fafb",
- border: "#d1d5db",
- },
research: {
accent: "oklch(0.5471 0.1899 264.38)",
bg: "#eff6ff",
@@ -2499,6 +2364,21 @@ const solutionColors: Record<
};
const solutionScenarios = [
+ {
+ id: "founders",
+ label: "Founders",
+ headline: "One source of truth for every conversation you run",
+ description:
+ "Investor calls, customer discovery, team 1:1s, board updates. Char captures it all with zero overhead and keeps the context you need to act.",
+ pills: [
+ "Multiple agendas",
+ "Investor & customer memory",
+ "Cross-context handoff",
+ "Agent-ready workflows",
+ "Your data, your rules",
+ ],
+ link: "/solution/founders",
+ },
{
id: "developers",
label: "Developers",
@@ -2514,21 +2394,6 @@ const solutionScenarios = [
],
link: "/solution/engineering",
},
- {
- id: "enterprise",
- label: "Enterprise",
- headline: "Meeting AI configured for your organization",
- description:
- "Other AI note-takers ask you to trust their infrastructure, their models, and their policies. We built one where you control all three.",
- pills: [
- "Self-Hosted Deployment",
- "Zero-Knowledge Security",
- "Compliance Ready",
- "Access Control",
- "Open Source",
- ],
- link: "/enterprise",
- },
{
id: "research",
label: "Research",
@@ -2679,60 +2544,6 @@ function SolutionsTabbar() {
);
}
-function ExplorePathsSection() {
- return (
-
- );
-}
-
function FAQSection() {
return (
@@ -2868,3 +2679,594 @@ function BlogSection() {
);
}
+
+type ScrollEffect = "opacity" | "blur" | "blurUp";
+
+function ScrollRevealWord({
+ progress,
+ range,
+ effect,
+ children,
+}: {
+ progress: ReturnType
>;
+ range: [number, number];
+ effect: ScrollEffect;
+ children: React.ReactNode;
+}) {
+ const [rangeStart, rangeEnd] = range;
+ const adjustedStart = Math.max(0, rangeStart - 0.05);
+
+ const opacity = useTransform(progress, [adjustedStart, rangeEnd], [0.15, 1]);
+ const filter = useTransform(
+ progress,
+ [adjustedStart, rangeEnd],
+ ["blur(4px)", "blur(0px)"],
+ );
+ const y = useTransform(progress, [adjustedStart, rangeEnd], [5, 0]);
+
+ const style = useMemo(() => {
+ if (effect === "opacity") return { opacity };
+ if (effect === "blur") return { opacity, filter };
+ if (effect === "blurUp") {
+ return { opacity, filter, y, display: "inline-block" as const };
+ }
+ return {};
+ }, [effect, opacity, filter, y]);
+
+ return {children} ;
+}
+
+function ScrollRevealParagraph({
+ children,
+ effect = "blur",
+ className,
+}: {
+ children: React.ReactNode;
+ effect?: ScrollEffect;
+ className?: string;
+}) {
+ const containerRef = useRef(null);
+ const { scrollYProgress } = useScroll({
+ target: containerRef,
+ offset: ["start 0.8", "end 0.8"],
+ });
+
+ const ratchetedProgress = useMotionValue(0);
+
+ useMotionValueEvent(scrollYProgress, "change", (latest) => {
+ if (latest > ratchetedProgress.get()) {
+ ratchetedProgress.set(latest);
+ }
+ });
+
+ const extractText = (node: React.ReactNode): string => {
+ if (typeof node === "string") return node;
+ if (typeof node === "number") return String(node);
+ if (Array.isArray(node)) return node.map(extractText).join(" ");
+ if (React.isValidElement(node)) {
+ const element = node as React.ReactElement<{
+ children?: React.ReactNode;
+ }>;
+ if (element.props.children) return extractText(element.props.children);
+ }
+ return "";
+ };
+
+ const allText = extractText(children);
+ const allWords = allText.split(/\s+/).filter((w) => w.length > 0);
+ const wordCount = allWords.length;
+
+ let globalWordIndex = 0;
+
+ const processNode = (node: React.ReactNode): React.ReactNode => {
+ if (typeof node === "string") {
+ const words = node.split(/(\s+)/);
+ return words.map((segment, i) => {
+ if (segment.trim().length === 0) return segment;
+
+ const currentWordIndex = globalWordIndex;
+ globalWordIndex++;
+
+ const start = currentWordIndex / wordCount;
+ const end = (currentWordIndex + 1) / wordCount;
+
+ return (
+
+ {segment}
+
+ );
+ });
+ }
+
+ if (React.isValidElement(node)) {
+ const element = node as React.ReactElement<{
+ children?: React.ReactNode;
+ style?: React.CSSProperties;
+ }>;
+
+ if (element.type === "img") {
+ const neighborIndex = Math.max(0, globalWordIndex - 1);
+ const start = neighborIndex / wordCount;
+ const end = (neighborIndex + 1) / wordCount;
+
+ return (
+
+ {element}
+
+ );
+ }
+
+ if (element.props.style?.backgroundImage) {
+ const innerText = extractText(element);
+ const innerWords = innerText.split(/\s+/).filter((w) => w.length > 0);
+ const startIndex = globalWordIndex;
+ globalWordIndex += innerWords.length;
+ const start = startIndex / wordCount;
+ const end = (startIndex + innerWords.length) / wordCount;
+
+ return (
+
+ {element}
+
+ );
+ }
+
+ return React.cloneElement(element, {
+ ...element.props,
+ children: React.Children.map(element.props.children, processNode),
+ });
+ }
+
+ if (Array.isArray(node)) {
+ return node.map((child, i) => (
+ {processNode(child)}
+ ));
+ }
+
+ return node;
+ };
+
+ return (
+
+ {processNode(children)}
+
+ );
+}
+
+function ManifestoSection() {
+ const paragraphClass =
+ "font-sans text-xl leading-relaxed text-fg md:text-3xl lg:text-3xl";
+ const paragraphSpacing = "mt-4 md:mt-6 lg:mt-8";
+ return (
+
+
+ We believe in the power of notetaking, not notetakers. Meetings should
+ be moments of presence, not passive attendance.{" "}
+
+
+
+
+ AI changes it. Instead of{" "}
+
+ {" "}
+ scribbling{" "}
+ {" "}
+ notes, it gives us the power to be present.
+
+
+ But we give it control over our meetings. What happens with all our
+ calls and chats then? Services sunset{" "}
+
+
+ {" "}
+ constantly, models change, progress is unstoppable.
+
+
+ We believe in owning your data, doesn't matter where it lives. More
+
+ {" "}
+ important{" "}
+ {" "}
+ is what you bring from every meeting, every call, every chat.
+
+
+
+
+ Char
+
+ {" "}
+ exists to preserve what makes us human: conversations that spark
+ ideas, collaborations that move work forward. We build tools that
+ amplify human agency, not replace it.
+
+
+ No ghost bots. No silent note lurkers. Just people,{" "}
+
+ thinking{" "}
+ {" "}
+
+ together.
+
+
+
+
+
+
+
+
+
+
+
+
+ Char
+
+
John Jeong, Yujong Lee
+
+
+
+
+
+
+
+
+ );
+}
+
+function RecordYourDaySection() {
+ return (
+
+
+ This is how Char gets your day done
+
+
+
+
+ Screen recording
+
+
+ Record your day
+
+
+ Char remembers your timeline and helps you get any information you
+ want: emails, tasks, conversations.
+
+
+ — Ask Charlie anything, about day, contacts, meetings.
+
+
+
+
+
+
+
+
+ );
+}
+
+function SummariseMeetingsSection() {
+ return (
+
+
+
+
+ Collect meetings
+
+
+ Summarise and transcribe meetings
+
+
+ Char combines your meeting notes with transcripts to create a
+ perfect summary.
+
+
+ Learn more about meeting notetaker
+
+
+
+
+
+
+
+
+
+
+ );
+}
+
+function SummaryPreview() {
+ return (
+
+
+
+
+
+ Mobile UI Update and API Adjustments
+
+
+
+ Sarah presented the new mobile UI update, which includes a
+ streamlined navigation bar and improved button placements for
+ better accessibility.
+
+
+ Ben confirmed that API adjustments are needed to support dynamic
+ UI changes, particularly for fetching personalized user data more
+ efficiently.
+
+
+
+
+
+ New Dashboard – Urgent Priority
+
+
+
+ Alice emphasized that the new analytics dashboard must be
+ prioritized due to increasing stakeholder demand.
+
+
+
+
+
+ );
+}
+
+function ExecuteTasksSection() {
+ return (
+
+
+
+
+ AI chat
+
+
+ Ask questions, execute tasks
+
+
+ Chat with Charlie to find decisions, pull context, and trigger the
+ follow-ups that usually slip — all grounded in your notes.
+
+
+ — Hand off work to AI agents like Claude or Cursor.
+
+
+
+
+
+
+
+
+ );
+}
+
+function PrivateByDesignSection() {
+ return (
+
+
+
+
+ Private by design
+
+
+
+
+
+
+
+
+ Data always stays on your device
+
+
+ Your privacy is our priority. We don't use it for training or
+ collecting any of your meeting content.
+
+
+
+
+
+
+
+
+ sk-
+
+
+ ✱✱✱✱✱✱✱✱✱✱✱✱✱✱✱✱✱✱✱✱✱✱✱✱✱✱✱✱✱✱
+
+
+
+
+
+
+ Bring Your Own Key or use local models
+
+
+ Char supports all major speech-to-text providers and have the
+ best local models build in
+
+
+
+
+
+
+
+
+
+
+
+
+ 1-1 w/ Janice
+
+
+ 3 participants
+
+
+
+
+
+
+ No bots on calls. Hidden during screen share.
+
+
+ Char captures system audio, not bothers people on the call.
+ Works everywhere.
+
+
+
+
+
+
+
+
+
+
+ Screen activity
+
+
+ Processed locally
+
+
+
+
+
+
+ Screen recording stays on your device
+
+
+ We record and analyse your screen locally. It never goes to the
+ cloud.
+
+
+
+
+
+
+ );
+}
diff --git a/apps/web/src/routes/_view/product/ai-notetaking.tsx b/apps/web/src/routes/_view/product/ai-notetaking.tsx
index bd742387e1..babbacd952 100644
--- a/apps/web/src/routes/_view/product/ai-notetaking.tsx
+++ b/apps/web/src/routes/_view/product/ai-notetaking.tsx
@@ -12,7 +12,7 @@ import { CTASection as SharedCTA } from "@/components/cta-section";
import { DownloadButton } from "@/components/download-button";
import { GithubStars } from "@/components/github-stars";
import { Image } from "@/components/image";
-import { HowItWorksSection } from "@/routes/_view/index";
+import { AISection, HowItWorksSection } from "@/routes/_view/index";
export const Route = createFileRoute("/_view/product/ai-notetaking")({
component: Component,
@@ -46,6 +46,7 @@ function Component() {
+
diff --git a/apps/web/src/routes/_view/product/daily-notes.tsx b/apps/web/src/routes/_view/product/daily-notes.tsx
new file mode 100644
index 0000000000..fd63e06794
--- /dev/null
+++ b/apps/web/src/routes/_view/product/daily-notes.tsx
@@ -0,0 +1,43 @@
+import { createFileRoute } from "@tanstack/react-router";
+
+import { DownloadButton } from "@/components/download-button";
+import { GithubStars } from "@/components/github-stars";
+
+export const Route = createFileRoute("/_view/product/daily-notes")({
+ component: Component,
+ head: () => ({
+ meta: [
+ { title: "Daily Notes - Char" },
+ {
+ name: "description",
+ content:
+ "Char builds a daily note from your meetings, emails, and screen activity. Review what happened, tick what's done, and hand off the rest to AI agents.",
+ },
+ { name: "robots", content: "noindex, nofollow" },
+ ],
+ }),
+});
+
+function Component() {
+ return (
+
+
+
+
+ Your day, already written.
+
+
+ Char records meetings without bots, pulls action items from your
+ emails, and builds a daily note with everything you need to do. You
+ review it, tick what's done, and hand off the rest to AI agents like
+ Claude or Cursor.
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/apps/web/src/styles.css b/apps/web/src/styles.css
index f1137e08c5..e96d636170 100644
--- a/apps/web/src/styles.css
+++ b/apps/web/src/styles.css
@@ -80,8 +80,8 @@
/* ── Colors: greys ────────────────────────────── */
--grey-900: oklch(0.3 0.0197 81.53);
--grey-700: oklch(0.4922 0.0127 67.79);
- --grey-600: oklch(0.6182 0.0018 67.8);
- --grey-500: oklch(0.7782 0.0018 67.8);
+ --grey-600: oklch(0.6933 0.0018 68.29);
+ --grey-500: oklch(0.7485 0.0025 67.8);
--grey-300: oklch(0.9213 0.0027 106.45);
--grey-100: oklch(0.9558 0.0045 78.3);
@@ -98,7 +98,7 @@
/* ── Colors: borders ─────────────────────────── */
--color-border: var(--grey-500);
--color-border-subtle: var(--grey-300);
- --color-border-bright: oklch(0.5959 0.0333 78.6);
+ --color-border-bright: var(--grey-600);
--color-border-active: oklch(0.9213 0.0027 106.45);
/* ── Colors: brand ───────────────────────────── */
@@ -402,6 +402,16 @@
);
}
+.bg-lined-notebook-bright {
+ background-image: repeating-linear-gradient(
+ to bottom,
+ transparent,
+ transparent 32px,
+ var(--color-border) 32px,
+ var(--color-border) 33px
+ );
+}
+
.bg-dotted {
background-image: radial-gradient(
circle,
diff --git a/package.json b/package.json
index b7b97dabc0..c145dc40d7 100644
--- a/package.json
+++ b/package.json
@@ -44,16 +44,8 @@
"@tiptap/extension-floating-menu": "3.20.1",
"@tiptap/extensions": "3.20.1",
"prosemirror-view": "1.41.7",
- "lightningcss": "1.30.1"
- },
- "peerDependencyRules": {
- "allowedVersions": {
- "motion": "11",
- "react": "19",
- "@lobehub/ui": "3.4.0",
- "unist-util-visit": "5",
- "esbuild": "0.25"
- }
+ "lightningcss": "1.30.1",
+ "@astrojs/internal-helpers": "0.8.0"
}
},
"packageManager": "pnpm@10.33.0",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 85d724c934..a4d3a3bd0a 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -6,6 +6,7 @@ settings:
overrides:
'@tiptap/extension-paragraph': 3.20.1
+ '@astrojs/internal-helpers': 0.8.0
'@tiptap/extension-blockquote': 3.20.1
'@tiptap/extension-bold': 3.20.1
'@tiptap/extension-code': 3.20.1