Compare commits
12
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d3c30859b2 | ||
|
|
2f8dcd66ad | ||
|
|
a590a506f8 | ||
|
|
86544c260a | ||
|
|
c70b50b867 | ||
|
|
7e1f1d6388 | ||
|
|
bbdef0faa6 | ||
|
|
d6040e2a09 | ||
|
|
d90d2ad9eb | ||
|
|
572d8eaa94 | ||
|
|
dcb70f9188 | ||
|
|
03cf4f9a86 |
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"version": "0.0.1",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "medscribe-web",
|
||||
"runtimeExecutable": "npm",
|
||||
"runtimeArgs": ["run", "dev"],
|
||||
"port": 3000
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
import type { CSSProperties } from "react";
|
||||
|
||||
import type { Metadata } from "next";
|
||||
import { redirect } from "next/navigation";
|
||||
import Image from "next/image";
|
||||
|
||||
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 {
|
||||
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 =
|
||||
"Epic is the definitive record of care inside the network. What no institutional EHR is built to be is the record the patient owns and carries everywhere else: the out-of-network ER, the specialist on another Epic instance, the months between visits at home. That out-of-hospital stretch is a structural blind spot, not a flaw in any one system. Medscribe fills it on the patient's own device, and — only with the patient's consent — hands that context back, so the gap becomes an ongoing connection instead of a black box.";
|
||||
|
||||
const forHealthSystems = [
|
||||
{
|
||||
title: "Reaches where the EHR can't",
|
||||
body: "The record lives on the patient's device and travels with them: the out-of-network ER, the covering specialist, the weeks at home between visits. It stays current exactly where an institutional system loses sight of the patient.",
|
||||
},
|
||||
{
|
||||
title: "Feeds context back into Epic",
|
||||
body: "With the patient's consent, the out-of-network and between-visit context Medscribe captures returns into the workflows you already run. It raises the value of the EHR investment rather than competing for budget or attention.",
|
||||
},
|
||||
{
|
||||
title: "Patient-owned, so it persists",
|
||||
body: "A branded, patient-facing surface that works the levers systems are measured on — in-network retention, medication adherence, avoidable readmissions. Because the record belongs to the patient, it holds even if the system changes EHRs.",
|
||||
},
|
||||
];
|
||||
|
||||
const printLightTokens = {
|
||||
"--color-foreground": "#0f172a",
|
||||
"--color-primary": "#0f766e",
|
||||
@@ -108,6 +132,11 @@ const printLightTokens = {
|
||||
} as CSSProperties;
|
||||
|
||||
export default function ExecutiveSummaryPage() {
|
||||
// The home page is now the executive summary (single-page site). This deep
|
||||
// page is retired but kept in place — its content (incl. the print one-pager)
|
||||
// is a one-line revert away if we want a longer version back.
|
||||
redirect("/");
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* ===================== SCREEN ===================== */}
|
||||
@@ -188,6 +217,15 @@ export default function ExecutiveSummaryPage() {
|
||||
</Card>
|
||||
))}
|
||||
</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 */}
|
||||
@@ -234,12 +272,31 @@ 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'd like to try it, reach out.
|
||||
{BETA_SUMMARY} If you'd like to try it, reach out.
|
||||
</p>
|
||||
<PrintButton size="lg" className="shrink-0" />
|
||||
</section>
|
||||
@@ -252,29 +309,29 @@ export default function ExecutiveSummaryPage() {
|
||||
{/* ===================== 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 +346,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'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 +386,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 +405,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 +425,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'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>
|
||||
</>
|
||||
|
||||
@@ -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'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>
|
||||
);
|
||||
}
|
||||
+188
-304
@@ -1,345 +1,229 @@
|
||||
import {
|
||||
AlertTriangle,
|
||||
ChevronRight,
|
||||
ClipboardList,
|
||||
HeartPulse,
|
||||
Lock,
|
||||
Mic,
|
||||
Pill,
|
||||
Shield,
|
||||
Users,
|
||||
} from "lucide-react";
|
||||
import { HeartPulse, Layers, ShieldCheck, Users } from "lucide-react";
|
||||
|
||||
import { FeatureShowcase } from "~/components/feature-showcase";
|
||||
import { PhoneMockup } from "~/components/phone-mockup";
|
||||
import { SectionLabel } from "~/components/section-label";
|
||||
import { BETA_INVITE_MAILTO } from "~/lib/beta";
|
||||
import { Badge } from "~/components/ui/badge";
|
||||
import { Button } from "~/components/ui/button";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "~/components/ui/card";
|
||||
|
||||
const quickActions = [
|
||||
const fit = [
|
||||
{
|
||||
icon: HeartPulse,
|
||||
title: "Know Me card",
|
||||
meta: "Baseline, conditions, allergies",
|
||||
featured: true,
|
||||
},
|
||||
{
|
||||
icon: Pill,
|
||||
title: "Time-critical meds",
|
||||
meta: "Schedules caregivers can trust",
|
||||
},
|
||||
{
|
||||
icon: Mic,
|
||||
title: "Visit capture",
|
||||
meta: "Consent-gated, on-device",
|
||||
icon: Layers,
|
||||
title: "Complements Epic, doesn't compete",
|
||||
body: "Different job. Epic is the record of care inside Northwell. Medscribe is the patient-owned layer beside it, holding what Epic can't reach: the non-Epic care, the between-visit context, the emergency handoff. With the patient's consent, that context flows back into the workflows Northwell already runs, raising the value of the Epic investment rather than competing for it.",
|
||||
},
|
||||
{
|
||||
icon: Users,
|
||||
title: "Caregiver handoff",
|
||||
meta: "One current shared truth",
|
||||
},
|
||||
];
|
||||
|
||||
const pillars = [
|
||||
{
|
||||
icon: ClipboardList,
|
||||
title: "Keep the record current",
|
||||
body: "Capture visits, scan labels, and keep conditions, medications, allergies, contacts, and caregiver notes in one portable record.",
|
||||
title: "Keeps patients active in their own care",
|
||||
body: "Reminders and refills the app sets for them, dose tracking, and, between visits, questions routed back to their own care team rather than out to a search engine. Low-friction by design, because a tool the patient actually uses is what turns into adherence, engagement, and in-network follow-through, the things Northwell is measured on.",
|
||||
},
|
||||
{
|
||||
icon: AlertTriangle,
|
||||
title: "Make the critical context obvious",
|
||||
body: "The emergency view prioritizes the facts clinicians need fast: baseline, time-critical meds, active conditions, allergies, and who to call.",
|
||||
icon: HeartPulse,
|
||||
title: "Patient-centered where it counts",
|
||||
body: "The emergency handoff, the family caregiver covering a shift, the newly-diagnosed patient who half-remembers the plan. Fewer blank handoffs, safer medication moments, patients who stay engaged instead of going dark.",
|
||||
},
|
||||
{
|
||||
icon: Shield,
|
||||
title: "Stay private by architecture",
|
||||
body: "Transcription, OCR, and medication chat run locally. No cloud AI processing. Sharing should be explicit, revocable, and encrypted.",
|
||||
},
|
||||
];
|
||||
|
||||
const impact = [
|
||||
{
|
||||
title: "Fewer blank handoffs",
|
||||
body: "When a caregiver is overwhelmed, the important facts are already assembled: conditions, medications, allergies, baseline, contacts, and recent changes.",
|
||||
},
|
||||
{
|
||||
title: "Safer medication moments",
|
||||
body: "Time-critical schedules and dose history stay visible, so a missed or delayed medication is easier to catch before it becomes a crisis.",
|
||||
},
|
||||
{
|
||||
title: "More confident families",
|
||||
body: "Caregivers can see what changed, where it came from, and what still needs a clinician or pharmacist instead of guessing from memory.",
|
||||
icon: ShieldCheck,
|
||||
title: "Private by architecture",
|
||||
body: "The sensitive work (summarizing visits, answering medication questions, building the record) runs entirely on the device, not the cloud. No health data sent to a server for AI processing, no new inbox to breach, no BAA to negotiate.",
|
||||
},
|
||||
];
|
||||
|
||||
export default function HomePage() {
|
||||
const today = new Date().toLocaleDateString("en-US", {
|
||||
weekday: "long",
|
||||
month: "long",
|
||||
day: "numeric",
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="mx-auto max-w-6xl space-y-14 px-4 py-8 sm:px-6 sm:py-12 lg:space-y-20 lg:py-14">
|
||||
<div className="mx-auto max-w-5xl px-4 py-10 sm:px-6 lg:py-14">
|
||||
{/* Phone floats right on sm+; prose sections wrap around it like a newspaper column */}
|
||||
<div className="animate-fade-in-up-delay-1 mx-auto mb-8 flex justify-center sm:float-right sm:mx-0 sm:mb-4 sm:ml-8 lg:ml-10">
|
||||
<PhoneMockup
|
||||
src="/screenshots/home.png"
|
||||
alt="Medscribe home screen"
|
||||
width={200}
|
||||
priority
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Hero */}
|
||||
<section className="grid items-center gap-8 lg:grid-cols-[minmax(0,1fr)_minmax(230px,280px)] lg:gap-14">
|
||||
<div className="space-y-6">
|
||||
<div className="animate-fade-in-up space-y-5">
|
||||
<SectionLabel>Medscribe · Private complex-care context</SectionLabel>
|
||||
<h1 className="max-w-2xl font-serif text-4xl leading-[1.05] font-semibold tracking-tight text-foreground sm:text-5xl lg:text-6xl">
|
||||
Be known when care gets complicated
|
||||
</h1>
|
||||
<p className="max-w-xl text-base leading-relaxed text-muted-foreground sm:text-lg">
|
||||
A private medical context app for families managing complex care.
|
||||
It keeps visits, medications, baseline, contacts, allergies, and
|
||||
emergency facts ready before the chart catches up.
|
||||
<div className="space-y-10 lg:space-y-12">
|
||||
{/* Origin */}
|
||||
<section className="animate-fade-in-up space-y-5">
|
||||
<SectionLabel>Executive summary · Northwell Health</SectionLabel>
|
||||
<h1 className="text-foreground font-serif text-4xl leading-[1.05] font-semibold tracking-tight sm:text-5xl lg:text-6xl">
|
||||
Be known when care gets complicated
|
||||
</h1>
|
||||
<div className="text-muted-foreground space-y-4 text-base leading-relaxed sm:text-lg">
|
||||
<p>
|
||||
A grandfather with three adult children, each living twenty to
|
||||
fifty minutes away. None able to be at every appointment. No
|
||||
shared, current view of his medications, his visits, or what had
|
||||
changed.
|
||||
</p>
|
||||
<p>
|
||||
Then came the emergency. A fall. A hospital that did not
|
||||
understand his condition. Some of his medications were
|
||||
time-critical. They were administered incorrectly, his decline was
|
||||
misread, the wrong path was taken, and he passed six weeks later.
|
||||
The clinicians were not negligent. The system was blind to who he
|
||||
was, and no trustworthy picture of him existed to hand over.
|
||||
</p>
|
||||
<p className="text-foreground font-medium">
|
||||
Medscribe is the private, patient-owned record built for that gap:
|
||||
the whole picture, held by the patient, ready to hand over the
|
||||
moment the chart in front of you doesn't have it.
|
||||
</p>
|
||||
<p className="text-sm text-muted-foreground">{today}</p>
|
||||
</div>
|
||||
|
||||
<div className="animate-fade-in-up-delay-1 flex flex-wrap gap-3">
|
||||
<Button asChild size="lg">
|
||||
<a href="/executive-summary">
|
||||
Read executive summary
|
||||
<ChevronRight className="size-4" />
|
||||
</a>
|
||||
</Button>
|
||||
<Button asChild variant="outline" size="lg">
|
||||
<a href="#features">See how it works</a>
|
||||
</Button>
|
||||
</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>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<Badge variant="success">On-device · no cloud AI</Badge>
|
||||
<Badge variant="outline">Emergency-ready</Badge>
|
||||
<Badge variant="outline">No cloud AI</Badge>
|
||||
<Badge variant="outline">Complements Epic</Badge>
|
||||
<Badge variant="outline">iOS · Android beta</Badge>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div className="animate-fade-in-up-delay-3 flex justify-center lg:justify-end">
|
||||
<div className="flex w-fit items-start justify-center">
|
||||
<PhoneMockup
|
||||
src="/screenshots/home.png"
|
||||
alt="Medscribe home dashboard"
|
||||
priority
|
||||
width={190}
|
||||
/>
|
||||
{/* What it is */}
|
||||
<section className="animate-fade-in-up space-y-3">
|
||||
<SectionLabel>What it is</SectionLabel>
|
||||
<h2 className="font-serif text-3xl font-semibold tracking-tight sm:text-4xl">
|
||||
A medical record the patient owns and carries
|
||||
</h2>
|
||||
<p className="text-muted-foreground leading-relaxed">
|
||||
Current medications, allergies, baseline, visit summaries, and an
|
||||
emergency “know-me” record, kept current through
|
||||
everyday use, and ready to hand to a caregiver, a new doctor, or an
|
||||
ER in seconds. Everything runs on the device. Nothing is uploaded.
|
||||
</p>
|
||||
<div className="border-primary/30 space-y-2 border-l-2 pl-4">
|
||||
<p className="text-foreground text-sm font-medium">
|
||||
How the record stays current (live in beta today)
|
||||
</p>
|
||||
<ul className="text-muted-foreground space-y-1.5 text-sm leading-relaxed">
|
||||
<li>
|
||||
<span className="text-foreground">Visit summaries: </span>type
|
||||
or paste notes, import MyChart and portal after-visit summaries
|
||||
from a screenshot, or attach photos and documents; optional
|
||||
consent-gated audio recording for patients who want it. Any path
|
||||
feeds the same on-device summary.
|
||||
</li>
|
||||
<li>
|
||||
<span className="text-foreground">Medication management: </span>
|
||||
pill-bottle label recognition, reminders, and on-device Q&A
|
||||
grounded in FDA drug facts
|
||||
</li>
|
||||
<li>
|
||||
<span className="text-foreground">Emergency handoff: </span>a
|
||||
know-me card and encrypted caregiver sharing, patient-controlled
|
||||
</li>
|
||||
</ul>
|
||||
<p className="text-muted-foreground text-sm leading-relaxed">
|
||||
Structured MyChart / FHIR export import is on the roadmap:
|
||||
import-and-own, not write-back.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
{/* Strategic spine */}
|
||||
<section className="animate-fade-in-up space-y-5">
|
||||
<SectionLabel>The story</SectionLabel>
|
||||
<div className="grid gap-6 lg:grid-cols-[minmax(0,0.9fr)_minmax(0,1.1fr)] lg:items-start">
|
||||
<div className="space-y-3">
|
||||
{/* The gap Epic's own tools can't close */}
|
||||
<section className="animate-fade-in-up space-y-3">
|
||||
<SectionLabel>Where the picture breaks</SectionLabel>
|
||||
<h2 className="font-serif text-3xl font-semibold tracking-tight sm:text-4xl">
|
||||
Epic knows what Epic sees
|
||||
</h2>
|
||||
<p className="text-muted-foreground leading-relaxed">
|
||||
MyChart is excellent inside the Epic walls. It puts the chart on the
|
||||
patient's phone, links their Epic portals through MyChart
|
||||
Central, and lets them share with a caregiver or a provider. What no
|
||||
institutional record can be is the whole picture: the urgent care,
|
||||
the community specialist, the facility that isn't on Epic; the
|
||||
changes between visits; the context a patient keeps themselves. Epic
|
||||
aggregates Epic. And when it matters most, at an out-of-network ER,
|
||||
or when the patient can't generate a share code, the record
|
||||
that's actually there is the one they already carry.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
{/* What makes it different */}
|
||||
<section className="animate-fade-in-up space-y-3">
|
||||
<SectionLabel>What makes it different</SectionLabel>
|
||||
<h2 className="font-serif text-3xl font-semibold tracking-tight sm:text-4xl">
|
||||
No one else owns the whole job
|
||||
</h2>
|
||||
<p className="text-muted-foreground leading-relaxed">
|
||||
Existing Electronic Health Record (EHR) systems owns the
|
||||
institutional view, and every institutional tool stops at its own
|
||||
walls. Around them, the market is full of single-purpose apps: visit
|
||||
notetakers, medication trackers, record vaults, emergency-ID cards.
|
||||
Each owns one slice. None holds a private, patient-owned record that
|
||||
spans every system, Epic and non-Epic alike, stays current through
|
||||
daily use, and is ready to hand over in a crisis, all on the
|
||||
patient's own device. Medscribe is that combination, beside
|
||||
Epic, not instead of it.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
{/* Why it fits Northwell: clear float so the card grid spans full width */}
|
||||
<section className="animate-fade-in-up clear-both space-y-6">
|
||||
<div className="space-y-2">
|
||||
<SectionLabel>Why it fits Northwell</SectionLabel>
|
||||
<h2 className="font-serif text-3xl font-semibold tracking-tight sm:text-4xl">
|
||||
The story scatters right when it matters
|
||||
Built to strengthen the Epic investment, not compete with it
|
||||
</h2>
|
||||
<p className="max-w-xl leading-relaxed text-muted-foreground">
|
||||
A daughter remembers the medication schedule. A spouse knows what
|
||||
normal looks like. A specialist mentioned a change three visits ago.
|
||||
In a crisis, those details are scattered across people, portals, and
|
||||
memory.
|
||||
</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">
|
||||
<HeartPulse className="mt-0.5 size-5 shrink-0 text-primary" />
|
||||
<div className="space-y-2">
|
||||
<h3 className="font-serif text-xl font-semibold tracking-tight">
|
||||
One current story
|
||||
</h3>
|
||||
<p className="text-sm leading-relaxed text-muted-foreground">
|
||||
Medscribe gives the family a private place to keep that context
|
||||
alive: current meds, timing, baseline, allergies, recent visit
|
||||
summaries, emergency contacts, and the source trail behind each
|
||||
fact.
|
||||
</p>
|
||||
<div className="grid gap-4 md:grid-cols-2">
|
||||
{fit.map((item) => {
|
||||
const Icon = item.icon;
|
||||
|
||||
return (
|
||||
<Card key={item.title} className="card-hover">
|
||||
<CardHeader>
|
||||
<div className="bg-primary-light flex size-10 items-center justify-center rounded-[10px]">
|
||||
<Icon className="text-primary size-5" />
|
||||
</div>
|
||||
<CardTitle className="text-lg">{item.title}</CardTitle>
|
||||
<CardDescription>{item.body}</CardDescription>
|
||||
</CardHeader>
|
||||
</Card>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Who's behind it + the ask */}
|
||||
<section className="animate-fade-in-up">
|
||||
<Card className="border-primary-button/20 bg-primary-button overflow-hidden text-white">
|
||||
<CardHeader className="gap-4">
|
||||
<div className="space-y-3">
|
||||
<CardTitle className="text-white">
|
||||
Who's behind it
|
||||
</CardTitle>
|
||||
<CardDescription className="leading-relaxed text-white/80">
|
||||
Sean O'Connor, a Bucknell computer science and engineering
|
||||
graduate, built Medscribe after watching his own family try to
|
||||
hold this together for his grandfather, when the record
|
||||
scattered exactly at the moment it mattered most. It is live in
|
||||
beta on iOS and Android today.
|
||||
</CardDescription>
|
||||
<CardDescription className="leading-relaxed text-white/80">
|
||||
The ask is a short conversation about whether a patient-owned
|
||||
record that spans the systems Epic can't reach, and feeds
|
||||
that context back into Northwell's Epic, is worth
|
||||
exploring together.
|
||||
</CardDescription>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-3 sm:grid-cols-2 lg:grid-cols-4">
|
||||
{quickActions.map((action) => {
|
||||
const Icon = action.icon;
|
||||
|
||||
return (
|
||||
<Card
|
||||
key={action.title}
|
||||
className={`card-hover ${action.featured ? "border-primary-button/20 bg-primary-button text-white" : ""}`}
|
||||
<Button
|
||||
asChild
|
||||
size="lg"
|
||||
variant="outline"
|
||||
className="text-primary-button w-fit border-white/30 bg-white hover:bg-white/90"
|
||||
>
|
||||
<CardHeader className="gap-3">
|
||||
<div
|
||||
className={`flex size-10 items-center justify-center rounded-[10px] ${
|
||||
action.featured ? "bg-white/18" : "bg-primary-light"
|
||||
}`}
|
||||
>
|
||||
<Icon
|
||||
className={`size-5 ${action.featured ? "text-white" : "text-primary"}`}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<CardTitle className={action.featured ? "text-white" : ""}>
|
||||
{action.title}
|
||||
</CardTitle>
|
||||
<CardDescription
|
||||
className={action.featured ? "text-white/72" : ""}
|
||||
>
|
||||
{action.meta}
|
||||
</CardDescription>
|
||||
</div>
|
||||
</CardHeader>
|
||||
</Card>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Interactive feature showcase */}
|
||||
<section id="features" className="animate-fade-in-up scroll-mt-24 space-y-5">
|
||||
<div className="space-y-2">
|
||||
<SectionLabel>The record</SectionLabel>
|
||||
<h2 className="font-serif text-3xl font-semibold tracking-tight sm:text-4xl">
|
||||
The pieces that keep the story current
|
||||
</h2>
|
||||
<p className="max-w-2xl text-muted-foreground">
|
||||
Visits, medications, reminders, search, and questions all point toward
|
||||
the same outcome: a record the family can trust when the stakes rise.
|
||||
</p>
|
||||
</div>
|
||||
<FeatureShowcase />
|
||||
</section>
|
||||
|
||||
{/* Privacy */}
|
||||
<section id="privacy" className="animate-fade-in-up scroll-mt-24 space-y-6">
|
||||
<div className="space-y-3">
|
||||
<SectionLabel>Why this is different</SectionLabel>
|
||||
<h2 className="font-serif text-3xl font-semibold tracking-tight sm:text-4xl">
|
||||
Built for the family record, not another cloud inbox
|
||||
</h2>
|
||||
<p className="max-w-2xl text-muted-foreground">
|
||||
The crowded space already has visit recorders, medication apps, and
|
||||
caregiver portals. Medscribe's wedge is the private, portable
|
||||
context layer families control before an institution is ready.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-4 md:grid-cols-3">
|
||||
{pillars.map((pillar) => {
|
||||
const Icon = pillar.icon;
|
||||
|
||||
return (
|
||||
<Card key={pillar.title} className="card-hover">
|
||||
<CardHeader>
|
||||
<div className="flex size-10 items-center justify-center rounded-[10px] bg-primary-light">
|
||||
<Icon className="size-5 text-primary" />
|
||||
</div>
|
||||
<CardTitle>{pillar.title}</CardTitle>
|
||||
<CardDescription>{pillar.body}</CardDescription>
|
||||
</CardHeader>
|
||||
</Card>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
<Card className="border-primary/15 bg-primary-light/40">
|
||||
<CardContent className="flex items-start gap-4 pt-5">
|
||||
<Lock className="mt-0.5 size-5 shrink-0 text-primary" />
|
||||
<p className="text-sm leading-relaxed text-foreground">
|
||||
All AI models run on your device. No health data is uploaded for
|
||||
processing. The medication chatbot stays in drug-information
|
||||
territory. It defers dosing and diagnosis questions to your
|
||||
clinician or pharmacist.
|
||||
</p>
|
||||
</CardContent>
|
||||
</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">
|
||||
<SectionLabel>Potential impact</SectionLabel>
|
||||
<h2 className="font-serif text-3xl font-semibold tracking-tight sm:text-4xl">
|
||||
Less panic, more context, better handoffs
|
||||
</h2>
|
||||
<p className="max-w-2xl text-muted-foreground">
|
||||
The goal is simple: make the person easier to understand at the exact
|
||||
moment their care team has the least context.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-4 md:grid-cols-3">
|
||||
{impact.map((item) => (
|
||||
<Card key={item.title} className="card-hover">
|
||||
<CardHeader>
|
||||
<CardTitle>{item.title}</CardTitle>
|
||||
<CardDescription>{item.body}</CardDescription>
|
||||
</CardHeader>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* CTA */}
|
||||
<section className="animate-fade-in-up">
|
||||
<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>
|
||||
<CardDescription className="text-white/75">
|
||||
The app is live on TestFlight. Reach out for an invite if
|
||||
you're managing medications, visits, or emergency context for
|
||||
someone you love.
|
||||
</CardDescription>
|
||||
</div>
|
||||
<Button
|
||||
asChild
|
||||
size="lg"
|
||||
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>
|
||||
</Button>
|
||||
</CardHeader>
|
||||
</Card>
|
||||
</section>
|
||||
|
||||
<a href={BETA_INVITE_MAILTO}>Start the conversation</a>
|
||||
</Button>
|
||||
</CardHeader>
|
||||
</Card>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
+6
-1
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
export function SiteFooter() {
|
||||
return (
|
||||
<footer className="print-hidden border-t border-border-soft bg-surface/70">
|
||||
<div className="mx-auto flex max-w-6xl flex-col gap-4 px-4 py-10 text-sm text-muted-foreground sm:px-6 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div className="mx-auto flex max-w-5xl flex-col gap-4 px-4 py-10 text-sm text-muted-foreground sm:px-6 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div>
|
||||
<p className="font-serif text-base font-semibold text-foreground">Medscribe</p>
|
||||
<p className="mt-1 max-w-md leading-relaxed">
|
||||
Be known when care gets complicated.
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<div className="sm:text-right">
|
||||
<p>
|
||||
Built by{" "}
|
||||
<a
|
||||
@@ -18,6 +18,9 @@ export function SiteFooter() {
|
||||
Sean O'Connor
|
||||
</a>
|
||||
</p>
|
||||
<p className="mt-1 text-xs text-muted-foreground/80">
|
||||
© {new Date().getFullYear()} Hadlock Holdings LLC
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
@@ -1,133 +1,27 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { FileText, Home, Menu, X } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import { usePathname } from "next/navigation";
|
||||
|
||||
import { MedscribeLogo } from "~/components/medscribe-logo";
|
||||
import { cn } from "~/lib/utils";
|
||||
|
||||
const navLinks = [
|
||||
{ href: "/", label: "About", icon: Home },
|
||||
{ href: "/executive-summary", label: "Summary", icon: FileText },
|
||||
];
|
||||
|
||||
export function SiteHeader() {
|
||||
const pathname = usePathname();
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<header className="print-hidden sticky top-3 z-50 px-3 sm:px-6">
|
||||
<div className="relative mx-auto max-w-5xl">
|
||||
<div className="flex h-14 items-center justify-between gap-4 rounded-full border border-border-soft/80 bg-canvas/82 px-4 shadow-[0_10px_30px_rgba(15,23,42,0.08)] backdrop-blur-md sm:px-5">
|
||||
<div className="relative flex h-14 items-center rounded-full border border-border-soft/80 bg-canvas/82 px-4 shadow-[0_10px_30px_rgba(15,23,42,0.08)] backdrop-blur-md sm:px-5">
|
||||
<Link
|
||||
href="/"
|
||||
className="flex items-center text-foreground transition-opacity hover:opacity-80"
|
||||
className="relative z-10 flex items-center text-foreground transition-opacity hover:opacity-80"
|
||||
aria-label="Medscribe home"
|
||||
onClick={() => setIsOpen(false)}
|
||||
>
|
||||
<MedscribeLogo className="h-7 w-[88px]" />
|
||||
<MedscribeLogo className="h-7 w-auto" />
|
||||
</Link>
|
||||
|
||||
{/* Desktop nav */}
|
||||
<nav className="hidden items-center gap-6 text-sm sm:flex">
|
||||
{navLinks.map((link) => {
|
||||
const isActive = link.href === pathname;
|
||||
|
||||
return (
|
||||
<Link
|
||||
key={link.href}
|
||||
href={link.href}
|
||||
aria-current={isActive ? "page" : undefined}
|
||||
className={cn(
|
||||
"relative py-1 transition-colors",
|
||||
isActive
|
||||
? "text-foreground"
|
||||
: "text-muted-foreground hover:text-foreground",
|
||||
)}
|
||||
>
|
||||
{link.label}
|
||||
<span
|
||||
className={cn(
|
||||
"absolute inset-x-0 -bottom-0.5 h-0.5 origin-center rounded-full bg-primary transition-transform duration-200",
|
||||
isActive ? "scale-x-100" : "scale-x-0",
|
||||
)}
|
||||
aria-hidden
|
||||
/>
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
</nav>
|
||||
|
||||
{/* Mobile menu toggle */}
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setIsOpen((v) => !v)}
|
||||
className="relative size-6 text-muted-foreground transition-colors hover:text-foreground focus:outline-none sm:hidden"
|
||||
aria-label={isOpen ? "Close menu" : "Open menu"}
|
||||
aria-expanded={isOpen}
|
||||
>
|
||||
<Menu
|
||||
className={cn(
|
||||
"absolute inset-0 transition-opacity duration-200",
|
||||
isOpen ? "opacity-0" : "opacity-100",
|
||||
)}
|
||||
/>
|
||||
<X
|
||||
className={cn(
|
||||
"absolute inset-0 transition-opacity duration-200",
|
||||
isOpen ? "opacity-100" : "opacity-0",
|
||||
)}
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Mobile dropdown */}
|
||||
<div
|
||||
className={cn(
|
||||
"absolute inset-x-0 top-16 overflow-hidden rounded-2xl border border-border-soft/80 bg-canvas/95 shadow-[0_10px_30px_rgba(15,23,42,0.12)] backdrop-blur-md transition-all duration-200 sm:hidden",
|
||||
isOpen
|
||||
? "pointer-events-auto opacity-100"
|
||||
: "pointer-events-none -translate-y-1 opacity-0",
|
||||
)}
|
||||
>
|
||||
<nav className="flex flex-col p-2">
|
||||
{navLinks.map((link) => {
|
||||
const isActive = link.href === pathname;
|
||||
const Icon = link.icon;
|
||||
|
||||
return (
|
||||
<Link
|
||||
key={link.href}
|
||||
href={link.href}
|
||||
aria-current={isActive ? "page" : undefined}
|
||||
onClick={() => setIsOpen(false)}
|
||||
className={cn(
|
||||
"flex items-center gap-3 rounded-xl px-3 py-2.5 text-sm font-medium transition-colors",
|
||||
isActive
|
||||
? "bg-primary-light/60 text-foreground"
|
||||
: "text-muted-foreground hover:bg-surface-soft hover:text-foreground",
|
||||
)}
|
||||
>
|
||||
<Icon className="size-4" />
|
||||
{link.label}
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
</nav>
|
||||
<p className="pointer-events-none absolute inset-x-0 text-center font-serif text-lg font-semibold tracking-tight text-foreground">
|
||||
Medscribe
|
||||
</p>
|
||||
<span className="relative z-10 ml-auto hidden text-xs font-medium tracking-[0.14em] text-muted-foreground uppercase sm:inline">
|
||||
Executive summary
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Backdrop */}
|
||||
<div
|
||||
className={cn(
|
||||
"fixed inset-0 -z-10 bg-canvas/30 backdrop-blur-sm transition-opacity duration-200 sm:hidden",
|
||||
isOpen ? "opacity-100" : "pointer-events-none opacity-0",
|
||||
)}
|
||||
onClick={() => setIsOpen(false)}
|
||||
aria-hidden
|
||||
/>
|
||||
</header>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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).";
|
||||
Reference in New Issue
Block a user