9 Commits
Author SHA1 Message Date
soconnor 86544c260a Add founder note to executive summary
Bill's intro email to Northwell primes the reader for the personal story
behind the app; the exec summary is the page that link lands on, and it
only had a bare signature. Adds the same founder line as the homepage
plus a fuller signature (Bucknell, CS&E). Screen version only — the
print one-pager's fixed layout is left untouched.
2026-07-06 23:12:22 -04:00
soconnor c70b50b867 Add founder story, tighten homepage flow
- A brief, grounded note under "The story scatters" about why Medscribe
  exists — the first real, personal detail on the public site rather than
  only the composite family narrative.
- Drop the "What travels with them" screenshot strip: it re-showed screens
  already covered by the interactive feature tour just above it, adding
  scroll length without new information.
2026-07-06 18:02:06 -04:00
soconnor 7e1f1d6388 Unpublish /landscape for now
Redirects to home, drops it from nav, and removes the dangling exec-summary
link. Content stays in the file (not deleted) for an easy revert later.
2026-07-06 17:57:37 -04:00
soconnorandCursor bbdef0faa6 Add Umami analytics and broaden beta copy for Android.
Optional NEXT_PUBLIC_UMAMI_* env vars load tracking when set; marketing pages now mention iOS TestFlight and Google Play internal testing.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-06 17:30:03 -04:00
soconnorandClaude Opus 4.8 d6040e2a09 Remove em-dashes; resolve on-device/sharing tension; concretize health-system levers
- Strip em-dashes from exec-summary copy.
- "An ongoing touchstone": clarify the record stays on-device and the
  patient chooses what to share back (consented, revocable), resolving the
  apparent contradiction with "nothing is uploaded."
- "A branded surface": name the levers a health system is measured on
  (in-network retention, adherence/follow-through, avoidable readmissions)
  instead of an unspecified "revenue-generating" claim.
- Retune print spacing/leading to keep the one-pager on a single page.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 15:37:13 -04:00
soconnorandClaude Opus 4.8 d90d2ad9eb Add preview dev-server launch config
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 13:32:43 -04:00
soconnorandClaude Opus 4.8 572d8eaa94 Broaden exec-summary frame beyond emergency-ready; trim print padding
Medscribe is an everyday companion — capture (record + searchable
transcription), understand (plain-language medication chat grounded in the
record + FDA facts), and manage (OCR, reminders, refills) — that is ALSO
ready to hand to a caregiver, a new specialist, or an ER. Reframe the
snapshot, problem, and lead differentiator so the emergency handoff reads
as the sharp end of the product, not its whole definition.

Also reduce the print one-pager's outside padding (0.5in -> 0.4in sides,
0.26in -> 0.22in top) so it uses the page better. Still one page.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 01:50:47 -04:00
soconnorandClaude Opus 4.8 dcb70f9188 Add "For health systems" and "Who it's for"; rebuild print one-pager
- Add a "For health systems" section (screen + print): ongoing touchstone /
  retention, branded per-provider revenue surface, complements (not competes
  with) the EHR.
- Add a full-width "Who it's for" section to the print PDF.
- Rebuild the print one-pager to fit one page: drop scenarios/impact from
  print, remove the silent overflow-hidden clip, tune typography/spacing,
  bottom-pin the footer, add slight padding above section headers.
- Generalize the scenario card so it doesn't read as diabetes-only.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 01:48:13 -04:00
soconnorandClaude Opus 4.8 03cf4f9a86 Add solution-landscape page and "Where we fit" section
New /landscape page maps the competitive field by category (with named
examples and the gap each leaves), an AlignCare callout, and the combined
job no incumbent owns. Add a compact, competitor-free "Where we fit"
section to the executive summary linking to it, and a Landscape nav item.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 17:14:34 -04:00
10 changed files with 395 additions and 98 deletions
+11
View File
@@ -0,0 +1,11 @@
{
"version": "0.0.1",
"configurations": [
{
"name": "medscribe-web",
"runtimeExecutable": "npm",
"runtimeArgs": ["run", "dev"],
"port": 3000
}
]
}
+4
View File
@@ -16,3 +16,7 @@
# Password required to view the site (login page at /login).
# Leave empty/unset to disable the gate.
SITE_PASSWORD=""
# Umami analytics (optional). Omit NEXT_PUBLIC_UMAMI_WEBSITE_ID to disable tracking.
# NEXT_PUBLIC_UMAMI_WEBSITE_ID="your-website-uuid"
# NEXT_PUBLIC_UMAMI_SCRIPT_URL="https://cloud.umami.is/script.js"
+13 -4
View File
@@ -1,6 +1,6 @@
# Medscribe Web
Marketing homepage for [Medscribe](../README.md) — a privacy-first, on-device medical companion for iOS.
Marketing homepage for [Medscribe](../README.md) — a privacy-first, on-device medical companion for iOS and Android.
Built with [create-t3-app](https://create.t3.gg/) (Next.js + TypeScript + Tailwind). Single-page site; no database or API routes.
@@ -15,6 +15,15 @@ bun run dev
Open [http://localhost:3000](http://localhost:3000).
### Analytics (optional)
Set in `.env` (see `.env.example`):
- `NEXT_PUBLIC_UMAMI_WEBSITE_ID` — website UUID from your Umami dashboard
- `NEXT_PUBLIC_UMAMI_SCRIPT_URL` — optional; defaults to `https://cloud.umami.is/script.js` (use your self-hosted URL if needed)
Leave the website ID unset to disable tracking (default for local dev).
## Build
```bash
@@ -26,10 +35,10 @@ Screenshots live in `public/screenshots/`. They are wrapped in an iPhone-style f
### Capturing raw screenshots for the site
Export **in-app content only** from the simulator or device — no simulator chrome or macOS capture toolbar. A normal in-app iOS status bar/tab bar is fine.
Export **in-app content only** from the simulator or device — no simulator chrome or macOS capture toolbar. A normal in-app status bar/tab bar is fine.
1. Run the app on **iPhone 17 Pro** (or 16 Pro) simulator or device.
2. Capture each screen (⌘S in Simulator saves to Desktop).
1. Run the app on **iPhone 17 Pro** (or 16 Pro) simulator or device, or on an **Android** emulator/device.
2. Capture each screen (⌘S in Simulator saves to Desktop; `adb exec-out screencap -p` on Android).
3. Drop PNGs into `public/screenshots/` and update paths in `src/app/page.tsx` / `src/components/feature-showcase.tsx`.
The frame dimensions assume tall iPhone screenshots and are controlled in `src/components/phone-mockup.tsx`. Use `captureMode="simulator"` only for old captures that already include device chrome.
+133 -60
View File
@@ -7,8 +7,11 @@ import { MedscribeLogo } from "~/components/medscribe-logo";
import { PhoneMockup } from "~/components/phone-mockup";
import { PrintButton } from "~/components/print-button";
import { SectionLabel } from "~/components/section-label";
import { BETA_FOOTER_NOTE, BETA_SUMMARY } from "~/lib/beta";
import { Badge } from "~/components/ui/badge";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
@@ -24,21 +27,24 @@ const TAGLINE =
"Your medical truth, in your pocket, and only where you say it goes.";
const SNAPSHOT =
"Medscribe is a private, emergency-ready medical record for families managing complex care. Patients and caregivers keep medications, visits, timing, and baseline current day to day, so a clinician or provider can be handed exactly what matters when the system does not know the patient.";
"Medscribe is a private, patient-owned medical record and companion for people managing ongoing care and the families beside them. It captures visits, makes medications and instructions easy to understand and keep current day to day, and stays ready to hand to a caregiver, a new specialist, or an ER. Everything runs on the device; nothing is uploaded.";
const PROBLEM =
"Someone managing a serious condition ends up somewhere new: an ER outside their network, a covering doctor, a hospital that runs different software. Their records don't transfer. The team assesses them on how they present, not on their baseline, their exact medication timing, or what changed at their last visit. The family is there and they know, but they can't produce a clear, structured record under pressure.";
"Care is fragmented and hard to carry. Patients forget most of what they're told in a visit, juggle medications with no clear picture of what each is for, and lose track of what changed and why. Then they end up somewhere new: a covering doctor, a specialist, an ER outside their network, and none of it travels with them. The family knows, but can't produce a clear, current record under pressure.";
const PROBLEM_BROADER =
"This is not a rare edge case. It happens every day, to patients with Parkinson's, diabetes, heart disease, dementia, and a hundred other conditions that require precise, continuous management across a fragmented system. The failure is not that families don't care. The failure is that the patient's context does not travel with them.";
const WHO_ITS_FOR =
"Anyone managing serious, ongoing care across a fragmented health system, and the families who coordinate it. It spans conditions and ages, and eventually touches almost everyone. The failure is simple: the patient's context does not travel with them.";
const DIFFERENTIATION =
"The market is full of AI visit recorders, medication trackers, health-record apps, and emergency QR cards, each owning one slice. None owns the whole job. In Medscribe, the daily tools are the maintenance system for one emergency-ready record.";
const differentiators = [
{
title: "The record is the product",
body: "Recording, scanning, reminders, and Q&A keep one private, emergency-ready record accurate. Competitors ship visit summaries; we ship a record you can hand over in a crisis.",
body: "Recording, scanning, reminders, and Q&A all feed one private record you actually keep current, useful every day and ready to hand over when it counts. Competitors ship visit summaries; we ship the record you own and carry.",
},
{
title: "Private by architecture",
@@ -78,8 +84,8 @@ const scenarios = [
body: "An adult child manages a parent's medications, appointments, and history. When they're not there, the context disappears: a sibling visits, a home aide covers, a shift changes. Critical details live in one person's memory.",
},
{
title: "The new diabetic, overwhelmed",
body: "A newly diagnosed patient leaves with insulin timing, diet changes, and follow-ups they half-remember. By the next visit, the provider can't tell what was understood, tried, or missed.",
title: "The newly diagnosed, overwhelmed",
body: "After a new diagnosis, a patient leaves with a changed medication schedule, lifestyle instructions, and follow-ups they half-remember. By the next visit, the provider can't tell what was understood, tried, or missed.",
},
];
@@ -98,6 +104,24 @@ const impact = [
},
];
const FOR_HEALTH_SYSTEMS =
"Out-of-hospital care is the blind spot. A health system loses sight of the patient the moment they leave, and only learns what happened when they return, often in worse shape. Medscribe turns that gap into an ongoing connection the system can offer under its own brand.";
const forHealthSystems = [
{
title: "An ongoing touchstone",
body: "The record lives on the patient's device; they choose what to share back, securely and revocably. That gives the system a consented window into care between visits, and a reason patients return instead of going dark.",
},
{
title: "A branded surface, not a cost center",
body: "Skinned per provider, it is a patient-facing presence the system owns, working the levers it is measured on: keeping patients in-network, improving medication adherence and follow-through, and reducing avoidable readmissions.",
},
{
title: "Complements the EHR, doesn't compete",
body: "It integrates with Epic or any EHR rather than replacing it. The patient stays in control, and because the record is patient-owned, it persists even if the system changes EHRs.",
},
];
const printLightTokens = {
"--color-foreground": "#0f172a",
"--color-primary": "#0f766e",
@@ -190,6 +214,42 @@ export default function ExecutiveSummaryPage() {
</div>
</section>
{/* Where we fit */}
<section className="space-y-5">
<SectionLabel>Where we fit</SectionLabel>
<h2 className="font-serif text-3xl font-semibold tracking-tight sm:text-4xl">
The field is full of slices.
</h2>
<p className="max-w-3xl leading-relaxed text-muted-foreground">
Visit recorders, medication apps, record vaults, emergency IDs, and
caregiver tools each solve one part of the problem. None keeps a
private, patient-owned record current day to day and ready to hand
over in a crisis.
</p>
<div className="flex flex-wrap gap-2">
{[
"Visit recorders",
"Medication apps",
"Record vaults",
"Emergency IDs",
"Caregiver tools",
].map((slice) => (
<Badge key={slice} variant="outline">
{slice}
</Badge>
))}
</div>
<Card className="border-primary-button/20 bg-primary-button text-white">
<CardContent className="pt-5">
<p className="leading-relaxed">
Medscribe connects the daily loop to the emergency handoff: one
private record that stays current through normal use and is ready
when the system does not know the patient.
</p>
</CardContent>
</Card>
</section>
{/* Under the hood */}
<section className="space-y-6">
<SectionLabel>Under the hood</SectionLabel>
@@ -234,47 +294,74 @@ export default function ExecutiveSummaryPage() {
</div>
</section>
{/* For health systems */}
<section className="space-y-6">
<SectionLabel>For health systems</SectionLabel>
<h2 className="font-serif text-3xl font-semibold tracking-tight sm:text-4xl">
An ongoing touchstone, not another portal.
</h2>
<p className="max-w-4xl leading-relaxed text-muted-foreground">
{FOR_HEALTH_SYSTEMS}
</p>
<div className="grid gap-4 md:grid-cols-3">
{forHealthSystems.map((item) => (
<Card key={item.title} className="card-hover">
<CardHeader>
<CardTitle className="text-lg">{item.title}</CardTitle>
<CardDescription>{item.body}</CardDescription>
</CardHeader>
</Card>
))}
</div>
</section>
{/* Beta */}
<section className="flex flex-col gap-5 rounded-xl border border-border-soft bg-surface p-6 sm:flex-row sm:items-center sm:justify-between sm:p-8">
<p className="max-w-xl text-sm leading-relaxed text-muted-foreground">
A beta is available on iOS through TestFlight. Everything runs
privately on the device, so no health data leaves the phone. If
you&apos;d like to try it, reach out.
{BETA_SUMMARY} If you&apos;d like to try it, reach out.
</p>
<PrintButton size="lg" className="shrink-0" />
</section>
<p className="border-t border-border-soft pt-6 text-sm text-muted-foreground">
Sean O&apos;Connor
</p>
<div className="space-y-3 border-t border-border-soft pt-6">
<p className="max-w-2xl text-sm leading-relaxed text-muted-foreground italic">
I built the first version of Medscribe after watching my own family
try to hold this together for my grandfather. The record scattered
right when we needed it most.
</p>
<p className="text-sm text-muted-foreground">
Sean O&apos;Connor · Bucknell University, Computer Science &amp;
Engineering
</p>
</div>
</div>
{/* ===================== PRINT: dense one-page PDF ===================== */}
<article
style={printLightTokens}
className="print-sheet hidden overflow-hidden print:flex print:h-[10.5in] print:flex-col print:justify-between print:px-[0.4in] print:py-[0.25in] print:text-[11pt] print:leading-[1.25]"
className="print-sheet hidden print:flex print:h-[10.5in] print:flex-col print:px-[0.4in] print:pt-[0.22in] print:pb-[0.1in] print:text-[11pt] print:leading-[1.2]"
>
{/* Header */}
<header className="grid grid-cols-[minmax(0,1fr)_1.1in] items-stretch gap-3 border-b border-teal-900/10 pb-2">
<header className="grid grid-cols-[minmax(0,1fr)_0.94in] items-stretch gap-4 border-b border-teal-900/10 pb-1.5">
<div className="flex flex-col justify-between py-0.5">
<p className="text-[11pt] font-semibold tracking-[0.15em] text-primary uppercase">
<p className="text-[10pt] font-semibold tracking-[0.18em] text-primary uppercase">
Executive Summary · June 2026
</p>
<div className="mt-2 flex items-center gap-2.5">
<div className="mt-1.5 flex items-center gap-2.5">
<MedscribeLogo className="h-[20pt] w-auto text-foreground" />
<h1 className="font-serif text-[22pt] leading-none font-semibold tracking-tight text-foreground">
Medscribe
</h1>
</div>
<p className="mt-2 font-serif text-[12pt] leading-snug font-medium tracking-tight text-foreground italic">
<p className="mt-1.5 font-serif text-[12pt] leading-snug font-medium tracking-tight text-foreground italic">
{TAGLINE}
</p>
<p className="mt-3 text-[11pt] leading-[1.25] text-slate-700">
<p className="mt-2 text-[11pt] leading-[1.2] text-slate-700">
{SNAPSHOT}
</p>
</div>
<div className="flex justify-end">
<div className="h-[2in] w-[0.94in] overflow-hidden rounded-[11px] border-[3px] border-slate-950 bg-white shadow-[0_8px_18px_rgba(15,23,42,0.14)]">
<div className="h-[1.78in] w-[0.88in] overflow-hidden rounded-[11px] border-[3px] border-slate-950 bg-white shadow-[0_8px_18px_rgba(15,23,42,0.14)]">
<Image
src="/screenshots/home.png"
alt="Medscribe home screen"
@@ -289,47 +376,35 @@ export default function ExecutiveSummaryPage() {
</header>
{/* The Problem */}
<section className="border-b border-teal-900/10 py-1">
<h2 className="font-serif text-[12pt] font-semibold tracking-tight text-foreground">
<section className="border-b border-teal-900/10 pt-1 pb-1">
<h2 className="font-serif text-[12.5pt] font-semibold tracking-tight text-foreground">
The Problem
</h2>
<p className="mt-0.5 text-[11pt] leading-[1.25] text-slate-700">
{PROBLEM} {PROBLEM_BROADER}
<p className="mt-1 text-[11pt] leading-[1.2] text-slate-700">
{PROBLEM}
</p>
</section>
{/* Who This Is For */}
<section className="border-b border-teal-900/10 py-1">
<h2 className="font-serif text-[12pt] font-semibold tracking-tight text-foreground">
Who This Is For
{/* Who It's For */}
<section className="border-b border-teal-900/10 pt-1 pb-1">
<h2 className="font-serif text-[12.5pt] font-semibold tracking-tight text-foreground">
Who It&apos;s For
</h2>
<div className="mt-0.5 grid grid-cols-3 gap-x-4">
{scenarios.map((scenario) => (
<div key={scenario.title}>
<p className="text-[11pt] font-semibold leading-[1.25] text-foreground">
{scenario.title}
</p>
<p className="text-[11pt] leading-[1.25] text-slate-700">
{scenario.body}
</p>
</div>
))}
</div>
<p className="mt-1 text-[11pt] leading-[1.2] text-slate-700">
{WHO_ITS_FOR}
</p>
</section>
{/* Why This Is Different */}
<section className="border-b border-teal-900/10 py-1">
<h2 className="font-serif text-[12pt] font-semibold tracking-tight text-foreground">
<section className="border-b border-teal-900/10 pt-1 pb-1">
<h2 className="font-serif text-[12.5pt] font-semibold tracking-tight text-foreground">
Why This Is Different
</h2>
<p className="mt-0.5 text-[11pt] leading-[1.25] text-slate-700">
{DIFFERENTIATION}
</p>
<div className="mt-1 grid grid-cols-3 gap-x-4">
<div className="mt-1.5 grid grid-cols-3 gap-x-6">
{differentiators.map((item) => (
<p
key={item.title}
className="text-[11pt] leading-[1.25] text-slate-700"
className="text-[11pt] leading-[1.2] text-slate-700"
>
<span className="font-semibold text-foreground">
{item.title}.
@@ -341,15 +416,15 @@ export default function ExecutiveSummaryPage() {
</section>
{/* Under The Hood */}
<section className="border-b border-teal-900/10 py-1">
<h2 className="font-serif text-[12pt] font-semibold tracking-tight text-foreground">
<section className="border-b border-teal-900/10 pt-1 pb-1">
<h2 className="font-serif text-[12.5pt] font-semibold tracking-tight text-foreground">
Under The Hood
</h2>
<div className="mt-0.5 grid grid-cols-3 gap-x-4">
<div className="mt-1 grid grid-cols-3 gap-x-6">
{moat.map((item) => (
<p
key={item.title}
className="text-[11pt] leading-[1.25] text-slate-700"
className="text-[11pt] leading-[1.2] text-slate-700"
>
<span className="font-semibold text-foreground">
{item.title}.
@@ -360,16 +435,16 @@ export default function ExecutiveSummaryPage() {
</div>
</section>
{/* Impact */}
<section className="py-1">
<h2 className="font-serif text-[12pt] font-semibold tracking-tight text-foreground">
Impact
{/* For Health Systems */}
<section className="pt-1">
<h2 className="font-serif text-[12.5pt] font-semibold tracking-tight text-foreground">
For Health Systems
</h2>
<div className="mt-0.5 grid grid-cols-3 gap-x-4">
{impact.map((item) => (
<div className="mt-1 grid grid-cols-3 gap-x-6">
{forHealthSystems.map((item) => (
<p
key={item.title}
className="text-[11pt] leading-[1.25] text-slate-700"
className="text-[11pt] leading-[1.2] text-slate-700"
>
<span className="font-semibold text-foreground">
{item.title}.
@@ -380,11 +455,9 @@ export default function ExecutiveSummaryPage() {
</div>
</section>
<footer className="flex items-center justify-between border-t border-teal-900/10 pt-1 text-[11pt] text-slate-500">
<footer className="mt-auto flex items-center justify-between border-t border-teal-900/10 pt-1.5 text-[10pt] text-slate-500">
<p>Sean O&apos;Connor</p>
<p className="text-slate-400">
A beta is available on iOS through TestFlight.
</p>
<p className="text-slate-400">{BETA_FOOTER_NOTE}</p>
</footer>
</article>
</>
+166
View File
@@ -0,0 +1,166 @@
import type { Metadata } from "next";
import { redirect } from "next/navigation";
import { SectionLabel } from "~/components/section-label";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "~/components/ui/card";
export const metadata: Metadata = {
title: "Solution Landscape",
description:
"How the medical-context space is divided today, and the combined job no existing product owns.",
};
const INTRO =
"The space is crowded. Visit recorders, medication apps, health-record vaults, emergency IDs, and caregiver tools all exist and are improving. But each owns one slice. None owns the whole job: a private, patient-owned medical context that stays current through daily use and is ready to hand over in a crisis.";
const categories = [
{
category: "Patient AI visit notetakers",
examples: "Kin, AlignCare, Emilia, PatientScribe, Medcorder",
gap: "Capture and summarize the visit, but the summary is the product. It is not turned into an emergency-ready record a family can hand over.",
},
{
category: "Medication trackers",
examples: "Medisafe, MyTherapy, Dosecast",
gap: "Know the pill list and reminders, but never heard the visit. No grounded understanding of why a medication is on the list or what changed.",
},
{
category: "Personal health records",
examples: "KeepMD, Available Health, Replicoo, Guava, DrOwl",
gap: "Store and aggregate documents, but are passive. Nothing keeps them current, and a broad PHR is undifferentiated.",
},
{
category: "Emergency IDs and QR cards",
examples: "Apple Medical ID, MedicAlert, tenderID, Epic Share Everywhere",
gap: "A static snapshot with no daily maintenance loop, so the card is only as accurate as the last manual edit.",
},
{
category: "Caregiver coordination",
examples: "Innerhive, Alula, Connected Caregiver, Caring Village",
gap: "Tend toward tasks, calendars, and messaging. They coordinate people, not the integrity of the medical context itself.",
},
{
category: "Provider-side scribes",
examples: "Abridge, Nuance DAX, Suki",
gap: "Built for the clinician's chart and billing, in clinical language. Not for the patient or the family carrying context across systems.",
},
];
const bundle = [
"Patient- and caregiver-owned mobile app",
"On-device visit transcription",
"Medication OCR and intake tracking",
"Facts-first medication Q&A grounded in trusted drug data",
"Current medications and visit context fused into one local record",
"An emergency “know me” handoff",
"Caregiver sharing designed for complex-care families",
"End-to-end encrypted, peer-to-peer sharing as a core architecture",
];
export default function LandscapePage() {
// Unpublished for now — not part of what external contacts (e.g. the
// Northwell introduction) should see. Content is kept in place, not
// deleted, so this is a one-line revert when we want it public again.
redirect("/");
return (
<div className="mx-auto max-w-6xl space-y-14 px-4 py-10 sm:px-6 lg:space-y-20 lg:py-14">
{/* Hero */}
<section className="space-y-5">
<SectionLabel>Solution landscape</SectionLabel>
<h1 className="max-w-3xl font-serif text-4xl leading-[1.05] font-semibold tracking-tight text-foreground sm:text-5xl">
The field, and the gap
</h1>
<p className="max-w-3xl text-base leading-relaxed text-muted-foreground sm:text-lg">
{INTRO}
</p>
</section>
{/* Category map */}
<section className="space-y-6">
<div className="space-y-2">
<SectionLabel>Who owns which slice</SectionLabel>
<h2 className="font-serif text-3xl font-semibold tracking-tight sm:text-4xl">
Six categories, each solving part of it
</h2>
</div>
<div className="grid gap-4 md:grid-cols-2">
{categories.map((item) => (
<Card key={item.category} className="card-hover">
<CardHeader className="gap-1.5">
<CardTitle className="text-lg">{item.category}</CardTitle>
<p className="text-xs font-medium tracking-wide text-primary">
{item.examples}
</p>
<CardDescription className="pt-1">{item.gap}</CardDescription>
</CardHeader>
</Card>
))}
</div>
</section>
{/* Closest competitor */}
<section className="space-y-4">
<SectionLabel>The closest direct competitor</SectionLabel>
<Card className="border-primary/15 bg-primary-light/40">
<CardContent className="space-y-3 pt-5 text-sm leading-relaxed text-foreground">
<p>
AlignCare matches the consumer bundle on paper: visit recording,
summaries, an AI assistant, medication reminders and logging,
documents, and caregiver sharing. It is real competition, not a
strawman.
</p>
<p>
The openings it leaves: its app-store disclosures indicate cloud
collection and tracking rather than on-device processing; it has no
first-class emergency handoff for a patient who cannot explain
themselves; and its medication answers are not described as
deterministic, on-device, facts-first rendering. Those are
Medscribe&apos;s wedges.
</p>
</CardContent>
</Card>
</section>
{/* The unowned bundle */}
<section className="space-y-6">
<div className="space-y-2">
<SectionLabel>What no one owns as one bundle</SectionLabel>
<h2 className="font-serif text-3xl font-semibold tracking-tight sm:text-4xl">
The combined job is the opening
</h2>
<p className="max-w-3xl text-muted-foreground">
Each box above has partial substitutes. No single incumbent unites
all of this into one private, patient-owned record:
</p>
</div>
<Card>
<CardContent className="pt-5">
<ul className="grid gap-x-8 gap-y-3 sm:grid-cols-2">
{bundle.map((point) => (
<li
key={point}
className="flex items-start gap-3 text-sm leading-snug text-foreground"
>
<span className="mt-1.5 size-1.5 shrink-0 rounded-full bg-primary" />
<span>{point}</span>
</li>
))}
</ul>
</CardContent>
</Card>
<p className="max-w-3xl leading-relaxed text-muted-foreground">
That intersection, the daily maintenance loop that keeps an
emergency-ready record current and private by architecture, is the lane
Medscribe is built to own.
</p>
</section>
</div>
);
}
+16 -31
View File
@@ -13,6 +13,11 @@ import {
import { FeatureShowcase } from "~/components/feature-showcase";
import { PhoneMockup } from "~/components/phone-mockup";
import { SectionLabel } from "~/components/section-label";
import {
BETA_CTA_BODY,
BETA_CTA_TITLE,
BETA_INVITE_MAILTO,
} from "~/lib/beta";
import { Badge } from "~/components/ui/badge";
import { Button } from "~/components/ui/button";
import {
@@ -119,8 +124,9 @@ export default function HomePage() {
</div>
<div className="animate-fade-in-up-delay-2 flex flex-wrap gap-2">
<Badge variant="success">On TestFlight</Badge>
<Badge variant="outline">iOS</Badge>
<Badge variant="success">Beta available</Badge>
<Badge variant="outline">iOS · TestFlight</Badge>
<Badge variant="outline">Android · Play internal</Badge>
<Badge variant="outline">Emergency-ready</Badge>
<Badge variant="outline">No cloud AI</Badge>
</div>
@@ -152,6 +158,11 @@ export default function HomePage() {
In a crisis, those details are scattered across people, portals, and
memory.
</p>
<p className="max-w-xl text-sm leading-relaxed text-muted-foreground/80 italic">
I built the first version of Medscribe after watching my own family
try to hold this together for my grandfather. The record scattered
right when we needed it most.
</p>
</div>
<div className="rounded-lg border border-border-soft bg-surface p-5 shadow-[0_1px_4px_rgba(0,0,0,0.05)]">
<div className="flex items-start gap-3">
@@ -267,28 +278,6 @@ export default function HomePage() {
</Card>
</section>
{/* Screenshot strip */}
<section className="animate-fade-in-up space-y-6">
<SectionLabel>What travels with them</SectionLabel>
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-4">
{[
{ src: "/screenshots/emergency-record.png", label: "Emergency record" },
{ src: "/screenshots/appointment-summary.png", label: "Visit summary" },
{ src: "/screenshots/medications.png", label: "Medication list" },
{ src: "/screenshots/chat.png", label: "Medication questions" },
].map((shot) => (
<Card key={shot.src} className="card-hover overflow-hidden">
<div className="flex min-h-[270px] items-start justify-center bg-surface-soft p-4">
<PhoneMockup src={shot.src} alt={shot.label} width={112} />
</div>
<p className="border-t border-border-soft px-4 py-3 text-center text-sm font-medium text-muted-foreground">
{shot.label}
</p>
</Card>
))}
</div>
</section>
{/* Impact */}
<section id="impact" className="animate-fade-in-up scroll-mt-24 space-y-6">
<div className="space-y-3">
@@ -319,11 +308,9 @@ export default function HomePage() {
<Card className="overflow-hidden border-primary-button/20 bg-primary-button text-white">
<CardHeader className="gap-4 sm:flex-row sm:items-center sm:justify-between">
<div className="space-y-2">
<CardTitle className="text-white">Try Medscribe on iOS</CardTitle>
<CardTitle className="text-white">{BETA_CTA_TITLE}</CardTitle>
<CardDescription className="text-white/75">
The app is live on TestFlight. Reach out for an invite if
you&apos;re managing medications, visits, or emergency context for
someone you love.
{BETA_CTA_BODY}
</CardDescription>
</div>
<Button
@@ -332,9 +319,7 @@ export default function HomePage() {
variant="outline"
className="border-white/30 bg-white text-primary-button hover:bg-white/90"
>
<a href="mailto:sean@soconnor.dev?subject=Medscribe%20TestFlight%20invite%20request&body=Hi%20Sean%2C%0D%0A%0D%0AI%27d%20like%20a%20TestFlight%20invite%20to%20try%20Medscribe.%20I%27m%20managing%20medications%2C%20visits%2C%20or%20emergency%20context%20for%20someone%20I%20care%20for.%0D%0A%0D%0AThanks%21">
Request an invite
</a>
<a href={BETA_INVITE_MAILTO}>Request an invite</a>
</Button>
</CardHeader>
</Card>
+6 -1
View File
@@ -3,6 +3,8 @@ import "~/styles/globals.css";
import { type Metadata } from "next";
import { Instrument_Sans, Playfair_Display } from "next/font/google";
import { UmamiAnalytics } from "~/components/umami-analytics";
const instrumentSans = Instrument_Sans({
subsets: ["latin"],
variable: "--font-instrument-sans",
@@ -42,7 +44,10 @@ export default function RootLayout({
}: Readonly<{ children: React.ReactNode }>) {
return (
<html lang="en" className={`${instrumentSans.variable} ${playfair.variable}`}>
<body className="min-h-screen">{children}</body>
<body className="min-h-screen">
{children}
<UmamiAnalytics />
</body>
</html>
);
}
+25
View File
@@ -0,0 +1,25 @@
import Script from "next/script";
import { env } from "~/env";
const DEFAULT_UMAMI_SCRIPT_URL = "https://cloud.umami.is/script.js";
/**
* Privacy-friendly page analytics via Umami. Enabled when
* `NEXT_PUBLIC_UMAMI_WEBSITE_ID` is set in `.env`.
*/
export function UmamiAnalytics() {
const websiteId = env.NEXT_PUBLIC_UMAMI_WEBSITE_ID;
if (!websiteId) return null;
const src = env.NEXT_PUBLIC_UMAMI_SCRIPT_URL ?? DEFAULT_UMAMI_SCRIPT_URL;
return (
<Script
defer
src={src}
data-website-id={websiteId}
strategy="afterInteractive"
/>
);
}
+6 -2
View File
@@ -18,7 +18,10 @@ export const env = createEnv({
* `NEXT_PUBLIC_`.
*/
client: {
// NEXT_PUBLIC_CLIENTVAR: z.string(),
/** Umami website ID — omit to disable analytics. */
NEXT_PUBLIC_UMAMI_WEBSITE_ID: z.string().optional(),
/** Umami script URL (defaults to Umami Cloud). */
NEXT_PUBLIC_UMAMI_SCRIPT_URL: z.string().url().optional(),
},
/**
@@ -28,7 +31,8 @@ export const env = createEnv({
runtimeEnv: {
NODE_ENV: process.env.NODE_ENV,
SITE_PASSWORD: process.env.SITE_PASSWORD,
// NEXT_PUBLIC_CLIENTVAR: process.env.NEXT_PUBLIC_CLIENTVAR,
NEXT_PUBLIC_UMAMI_WEBSITE_ID: process.env.NEXT_PUBLIC_UMAMI_WEBSITE_ID,
NEXT_PUBLIC_UMAMI_SCRIPT_URL: process.env.NEXT_PUBLIC_UMAMI_SCRIPT_URL,
},
/**
* Run `build` or `dev` with `SKIP_ENV_VALIDATION` to skip env validation. This is especially
+15
View File
@@ -0,0 +1,15 @@
/** Shared beta / early-access copy for the marketing site. */
export const BETA_SUMMARY =
"A beta is available on iOS (TestFlight) and Android (Google Play internal testing). Everything runs privately on the device, so no health data leaves the phone.";
export const BETA_CTA_TITLE = "Try Medscribe";
export const BETA_CTA_BODY =
"The app is in beta on iOS and Android. Reach out for an invite if you're managing medications, visits, or emergency context for someone you love.";
export const BETA_INVITE_MAILTO =
"mailto:sean@soconnor.dev?subject=Medscribe%20beta%20invite%20request&body=Hi%20Sean%2C%0D%0A%0D%0AI%27d%20like%20a%20beta%20invite%20to%20try%20Medscribe%20(iOS%20or%20Android).%20I%27m%20managing%20medications%2C%20visits%2C%20or%20emergency%20context%20for%20someone%20I%20care%20for.%0D%0A%0D%0AThanks%21";
export const BETA_FOOTER_NOTE =
"Beta on iOS (TestFlight) and Android (Google Play internal testing).";