Refocus homepage as Northwell exec summary with float layout.
Single-page strategic pitch for health-system readers; phone mockup wraps in newspaper-style columns; retire multi-page nav and redirect /executive-summary to home. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
import type { CSSProperties } from "react";
|
import type { CSSProperties } from "react";
|
||||||
|
|
||||||
import type { Metadata } from "next";
|
import type { Metadata } from "next";
|
||||||
|
import { redirect } from "next/navigation";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
|
|
||||||
import { MedscribeLogo } from "~/components/medscribe-logo";
|
import { MedscribeLogo } from "~/components/medscribe-logo";
|
||||||
@@ -131,6 +132,11 @@ const printLightTokens = {
|
|||||||
} as CSSProperties;
|
} as CSSProperties;
|
||||||
|
|
||||||
export default function ExecutiveSummaryPage() {
|
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 (
|
return (
|
||||||
<>
|
<>
|
||||||
{/* ===================== SCREEN ===================== */}
|
{/* ===================== SCREEN ===================== */}
|
||||||
|
|||||||
+139
-298
@@ -1,332 +1,173 @@
|
|||||||
import {
|
import { HeartPulse, Layers, ShieldCheck } from "lucide-react";
|
||||||
AlertTriangle,
|
|
||||||
ChevronRight,
|
|
||||||
ClipboardList,
|
|
||||||
HeartPulse,
|
|
||||||
Lock,
|
|
||||||
Mic,
|
|
||||||
Pill,
|
|
||||||
Shield,
|
|
||||||
Users,
|
|
||||||
} from "lucide-react";
|
|
||||||
|
|
||||||
import { FeatureShowcase } from "~/components/feature-showcase";
|
|
||||||
import { PhoneMockup } from "~/components/phone-mockup";
|
import { PhoneMockup } from "~/components/phone-mockup";
|
||||||
import { SectionLabel } from "~/components/section-label";
|
import { SectionLabel } from "~/components/section-label";
|
||||||
import {
|
import { BETA_INVITE_MAILTO } from "~/lib/beta";
|
||||||
BETA_CTA_BODY,
|
|
||||||
BETA_CTA_TITLE,
|
|
||||||
BETA_INVITE_MAILTO,
|
|
||||||
} from "~/lib/beta";
|
|
||||||
import { Badge } from "~/components/ui/badge";
|
import { Badge } from "~/components/ui/badge";
|
||||||
import { Button } from "~/components/ui/button";
|
import { Button } from "~/components/ui/button";
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
CardContent,
|
|
||||||
CardDescription,
|
CardDescription,
|
||||||
CardHeader,
|
CardHeader,
|
||||||
CardTitle,
|
CardTitle,
|
||||||
} from "~/components/ui/card";
|
} from "~/components/ui/card";
|
||||||
|
|
||||||
const quickActions = [
|
const fit = [
|
||||||
|
{
|
||||||
|
icon: Layers,
|
||||||
|
title: "Complements Epic — doesn't compete",
|
||||||
|
body: "Different question. Epic answers “what does Northwell know about this patient?” Medscribe answers “what does the patient carry everywhere else?” With the patient's consent, the out-of-network and between-visit context it captures flows back into the workflows Northwell already runs — raising the value of the Epic investment rather than competing for it.",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
icon: HeartPulse,
|
icon: HeartPulse,
|
||||||
title: "Know Me card",
|
title: "Patient-centered where it counts",
|
||||||
meta: "Baseline, conditions, allergies",
|
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.",
|
||||||
featured: true,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: Pill,
|
icon: ShieldCheck,
|
||||||
title: "Time-critical meds",
|
title: "Private by architecture",
|
||||||
meta: "Schedules caregivers can trust",
|
body: "The sensitive work — transcription, 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.",
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: Mic,
|
|
||||||
title: "Visit capture",
|
|
||||||
meta: "Consent-gated, on-device",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
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.",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
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: 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.",
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export default function HomePage() {
|
export default function HomePage() {
|
||||||
const today = new Date().toLocaleDateString("en-US", {
|
|
||||||
weekday: "long",
|
|
||||||
month: "long",
|
|
||||||
day: "numeric",
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
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:ml-8 sm:mb-4 lg:ml-10">
|
||||||
|
<PhoneMockup
|
||||||
|
src="/screenshots/home.png"
|
||||||
|
alt="Medscribe home screen"
|
||||||
|
width={200}
|
||||||
|
priority
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Hero */}
|
<div className="space-y-10 lg:space-y-12">
|
||||||
<section className="grid items-center gap-8 lg:grid-cols-[minmax(0,1fr)_minmax(230px,280px)] lg:gap-14">
|
{/* Hero */}
|
||||||
<div className="space-y-6">
|
<section className="animate-fade-in-up space-y-5">
|
||||||
<div className="animate-fade-in-up space-y-5">
|
<SectionLabel>Executive summary · Northwell Health</SectionLabel>
|
||||||
<SectionLabel>Medscribe · Private complex-care context</SectionLabel>
|
<h1 className="font-serif text-4xl leading-[1.05] font-semibold tracking-tight text-foreground sm:text-5xl lg:text-6xl">
|
||||||
<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
|
||||||
Be known when care gets complicated
|
</h1>
|
||||||
</h1>
|
<p className="text-base leading-relaxed text-muted-foreground sm:text-lg">
|
||||||
<p className="max-w-xl text-base leading-relaxed text-muted-foreground sm:text-lg">
|
Medscribe is a private, patient-owned medical record that lives on the
|
||||||
A private medical context app for families managing complex care.
|
patient's phone — and stays with them exactly where Epic
|
||||||
It keeps visits, medications, baseline, contacts, allergies, and
|
can't follow.
|
||||||
emergency facts ready before the chart catches up.
|
</p>
|
||||||
</p>
|
<div className="flex flex-wrap gap-2">
|
||||||
<p className="text-sm text-muted-foreground">{today}</p>
|
<Badge variant="success">On-device · no cloud AI</Badge>
|
||||||
</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">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">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>
|
||||||
</div>
|
</section>
|
||||||
|
|
||||||
<div className="animate-fade-in-up-delay-3 flex justify-center lg:justify-end">
|
{/* What it is */}
|
||||||
<div className="flex w-fit items-start justify-center">
|
<section className="animate-fade-in-up space-y-3">
|
||||||
<PhoneMockup
|
<SectionLabel>What it is</SectionLabel>
|
||||||
src="/screenshots/home.png"
|
<h2 className="font-serif text-3xl font-semibold tracking-tight sm:text-4xl">
|
||||||
alt="Medscribe home dashboard"
|
A medical record the patient owns and carries
|
||||||
priority
|
</h2>
|
||||||
width={190}
|
<p className="leading-relaxed text-muted-foreground">
|
||||||
/>
|
Current medications, allergies, baseline, visit summaries, and an
|
||||||
</div>
|
emergency “know-me” record — kept current through everyday
|
||||||
</div>
|
use, and ready to hand to a caregiver, a new doctor, or an ER in seconds.
|
||||||
</section>
|
Everything runs on the device. Nothing is uploaded.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
{/* Strategic spine */}
|
{/* The gap Epic can't close */}
|
||||||
<section className="animate-fade-in-up space-y-5">
|
<section className="animate-fade-in-up space-y-3">
|
||||||
<SectionLabel>The story</SectionLabel>
|
<SectionLabel>The gap Epic can't close</SectionLabel>
|
||||||
<div className="grid gap-6 lg:grid-cols-[minmax(0,0.9fr)_minmax(0,1.1fr)] lg:items-start">
|
<h2 className="font-serif text-3xl font-semibold tracking-tight sm:text-4xl">
|
||||||
<div className="space-y-3">
|
The record that leaves the building with the patient
|
||||||
|
</h2>
|
||||||
|
<p className="leading-relaxed text-muted-foreground">
|
||||||
|
Epic is Northwell's system of record — the definitive account 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, a specialist on a different Epic instance, the weeks at home between
|
||||||
|
visits. That out-of-hospital stretch is a structural blind spot — the
|
||||||
|
system loses sight of the patient the moment they leave, and often only
|
||||||
|
learns what happened when they return, worse.
|
||||||
|
</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="leading-relaxed text-muted-foreground">
|
||||||
|
The market is full of single-purpose apps — visit recorders, medication
|
||||||
|
trackers, record vaults, emergency-ID cards, caregiver portals. Each owns
|
||||||
|
one slice. None keeps a private, patient-owned record that stays current
|
||||||
|
through daily use, is ready to hand over in a crisis, and runs entirely on
|
||||||
|
the patient's own device. Medscribe is that combination — and no
|
||||||
|
existing product has 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">
|
<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>
|
</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>
|
|
||||||
<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>
|
||||||
<div className="rounded-lg border border-border-soft bg-surface p-5 shadow-[0_1px_4px_rgba(0,0,0,0.05)]">
|
<div className="grid gap-4 md:grid-cols-3">
|
||||||
<div className="flex items-start gap-3">
|
{fit.map((item) => {
|
||||||
<HeartPulse className="mt-0.5 size-5 shrink-0 text-primary" />
|
const Icon = item.icon;
|
||||||
<div className="space-y-2">
|
|
||||||
<h3 className="font-serif text-xl font-semibold tracking-tight">
|
return (
|
||||||
One current story
|
<Card key={item.title} className="card-hover">
|
||||||
</h3>
|
<CardHeader>
|
||||||
<p className="text-sm leading-relaxed text-muted-foreground">
|
<div className="flex size-10 items-center justify-center rounded-[10px] bg-primary-light">
|
||||||
Medscribe gives the family a private place to keep that context
|
<Icon className="size-5 text-primary" />
|
||||||
alive: current meds, timing, baseline, allergies, recent visit
|
</div>
|
||||||
summaries, emergency contacts, and the source trail behind each
|
<CardTitle className="text-lg">{item.title}</CardTitle>
|
||||||
fact.
|
<CardDescription>{item.body}</CardDescription>
|
||||||
</p>
|
</CardHeader>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Who's behind it + the ask */}
|
||||||
|
<section className="animate-fade-in-up">
|
||||||
|
<Card className="overflow-hidden border-primary-button/20 bg-primary-button 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 built Medscribe after watching his own family try
|
||||||
|
to hold this together for his grandfather — the record scattered
|
||||||
|
exactly when it mattered most. Bucknell computer-science engineer;
|
||||||
|
the app 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 an out-of-hospital,
|
||||||
|
patient-owned context layer — one that feeds Northwell's Epic
|
||||||
|
investment rather than competes with it — is worth exploring
|
||||||
|
together.
|
||||||
|
</CardDescription>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<Button
|
||||||
</div>
|
asChild
|
||||||
</div>
|
size="lg"
|
||||||
|
variant="outline"
|
||||||
<div className="grid gap-3 sm:grid-cols-2 lg:grid-cols-4">
|
className="w-fit border-white/30 bg-white text-primary-button hover:bg-white/90"
|
||||||
{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" : ""}`}
|
|
||||||
>
|
>
|
||||||
<CardHeader className="gap-3">
|
<a href={BETA_INVITE_MAILTO}>Start the conversation</a>
|
||||||
<div
|
</Button>
|
||||||
className={`flex size-10 items-center justify-center rounded-[10px] ${
|
</CardHeader>
|
||||||
action.featured ? "bg-white/18" : "bg-primary-light"
|
</Card>
|
||||||
}`}
|
</section>
|
||||||
>
|
</div>
|
||||||
<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 — and
|
|
||||||
it works alongside the systems a care team already uses, not instead
|
|
||||||
of them.
|
|
||||||
</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>
|
|
||||||
|
|
||||||
{/* 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">{BETA_CTA_TITLE}</CardTitle>
|
|
||||||
<CardDescription className="text-white/75">
|
|
||||||
{BETA_CTA_BODY}
|
|
||||||
</CardDescription>
|
|
||||||
</div>
|
|
||||||
<Button
|
|
||||||
asChild
|
|
||||||
size="lg"
|
|
||||||
variant="outline"
|
|
||||||
className="border-white/30 bg-white text-primary-button hover:bg-white/90"
|
|
||||||
>
|
|
||||||
<a href={BETA_INVITE_MAILTO}>Request an invite</a>
|
|
||||||
</Button>
|
|
||||||
</CardHeader>
|
|
||||||
</Card>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,22 +1,8 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import { useState } from "react";
|
|
||||||
import { FileText, Home, Menu, X } from "lucide-react";
|
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { usePathname } from "next/navigation";
|
|
||||||
|
|
||||||
import { MedscribeLogo } from "~/components/medscribe-logo";
|
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() {
|
export function SiteHeader() {
|
||||||
const pathname = usePathname();
|
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<header className="print-hidden sticky top-3 z-50 px-3 sm:px-6">
|
<header className="print-hidden sticky top-3 z-50 px-3 sm:px-6">
|
||||||
<div className="relative mx-auto max-w-5xl">
|
<div className="relative mx-auto max-w-5xl">
|
||||||
@@ -25,109 +11,14 @@ export function SiteHeader() {
|
|||||||
href="/"
|
href="/"
|
||||||
className="flex items-center text-foreground transition-opacity hover:opacity-80"
|
className="flex items-center text-foreground transition-opacity hover:opacity-80"
|
||||||
aria-label="Medscribe home"
|
aria-label="Medscribe home"
|
||||||
onClick={() => setIsOpen(false)}
|
|
||||||
>
|
>
|
||||||
<MedscribeLogo className="h-7 w-[88px]" />
|
<MedscribeLogo className="h-7 w-[88px]" />
|
||||||
</Link>
|
</Link>
|
||||||
|
<span className="text-xs font-medium tracking-[0.14em] text-muted-foreground uppercase">
|
||||||
{/* Desktop nav */}
|
Executive summary
|
||||||
<nav className="hidden items-center gap-6 text-sm sm:flex">
|
</span>
|
||||||
{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>
|
|
||||||
</div>
|
</div>
|
||||||
</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>
|
</header>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user